glug icon indicating copy to clipboard operation
glug copied to clipboard

Styling multiple related layers using variables

Open pnorman opened this issue 5 months ago • 2 comments

I have a large number of layers which mix very similar properties in different ways. e.g.

layer(:road_base_casing, :source=>:spirit, :source_layer=>:roads) {
    filter foo
    line_width A
    line_color M
    line_cap round
}

layer(:road_tunnel_casing, :source=>:spirit, :source_layer=>:roads) {
    filter foo & X
    line_width A
    line_color N
}

layer(:road_base_fill, :source=>:spirit, :source_layer=>:roads) {
    filter foo
    line_width B
    line_color N
}

layer(:road_casing, :source=>:spirit, :source_layer=>:roads) {
    filter foo & !X & !Y
    line_width A
    line_color M
}

In reality, all those variables are fairly complicated expressions using interpolates, matches, etc

I don't want to attempt something this complex with cascading but instead want to use variables. I have an example of how to use variables for numbers, colors, and other simple values.

I don't know how to assign part of an expression or conditional to a variable to reuse it in multiple definitions. I tried

ROAD_FILTER = (highway.in('motorway', 'trunk', 'primary', 'secondary', 'tertiary',
    'unclassified', 'residential', 'living_street') |
    ((highway == 'service') & (((minor == nil) & (zoom() >= 14)) | (zoom() >= 15))))

but it doesn't know what to do with the highway.in and gives

(eval):1:in `block in <top (required)>': undefined method `in' for nil:NilClass (NoMethodError

pnorman avatar Jul 10 '25 02:07 pnorman

Ruby appears to be happy to hoist a definition outside the layer in which it's defined. So, for example, I have:

	layer(:roads_casing_11, :zoom=>11..12, :source_layer=>:roads) {
		Is_Minor = _!(type.in(MAIN_ROADS))
		Is_Rural_Minor = (urban< 1) & (_!(type.in(MAIN_ROADS)))
		Is_Urban_Minor = (urban>=1) & (_!(type.in(MAIN_ROADS)))
		filter (urban<2) | (type.in(MAIN_ROADS))

		line_cap :round
		line_color case_when(Is_Urban_Minor, UNCASED_MED_GREY.to_hex_color, CASING_LIGHTER.to_hex_color)

		line_width interpolate([:linear], zoom(),
			10, case_when(
				(urban >= 2) & (type.in(MAIN_ROADS)), urban_c10,
				Is_Rural_Minor, minor_c10,
				Is_Urban_Minor, urbmn_c10,
				rural_c10),
			15, case_when(
				(urban >= 2) & (type.in(MAIN_ROADS)), urban_c15,
				Is_Rural_Minor, minor_c15,
				Is_Urban_Minor, urbmn_c15,
				rural_c15),
			18, case_when(
				(urban >= 2) & (type.in(MAIN_ROADS)), urban_c18,
				rural_c18)
		)
	}

	layer(:roads_casing, :zoom=>12..22, :source_layer=>:roads) {
		filter _!((tunnel==true) | (unpaved==true))
		line_cap :round
		line_color case_when(Is_Urban_Minor, UNCASED_MED_GREY.to_hex_color, CASING_GREY.to_hex_color)
		line_width interpolate([:linear], zoom(),
			10, case_when( Is_Urban_Minor, urbmn_c10, Is_Minor, minor_c10, urban>=2, urban_c10, rural_c10 ),
			15, case_when( Is_Urban_Minor, urbmn_c15, Is_Minor, minor_c15, urban>=2, urban_c15, rural_c15 ),
			18, case_when( urban>=2, urban_c18, rural_c18 )
		)
	}

where Is_Urban_Minor etc. can be reused in subsequent definitions.

systemed avatar Sep 01 '25 07:09 systemed

I was going to use this but rubocop didn't like constants in blocks. I can easily disable that cop[^1] but it reminded me why I wasn't a fan of this solution.

In the case I was working on I had several road casing layers, then a mix of fill and casing layers. If I define a variable for CasingColor and FillColor the first time I use them then the definitions are at two completely different parts of the file. If I rearrange the layer order I have to make sure that the first casing or fill layer has the definition in it. If I define them both in the first layer I have a fill definition in a casing layer.

baz = Glug::Condition.new.from_list(:match, ['static', 'motorway', '#b40017', :red]) gives me a variable which works, but is ugly. I think the solution is to allow Condition to take a block. I'm sufficiently deep in ruby DSL stuff I'm going to try to find some help.

[^1]: And will have to since outside the layer block is still within the stylesheet block.

pnorman avatar Oct 27 '25 06:10 pnorman