Styling multiple related layers using variables
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
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.
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.