MapCSS/0.2/Proposal loops
There have been repeated requests for loops in MapCSS. This page is not a proper proposal yet, but collects use cases and ideas for solutions.
Use cases
hiking trails
One style for display of hiking trails currently looks like this:
way::osmc_0 { offset: 2; } way::osmc_1 { offset: prop("offset", "osmc_0"); } relation[osmc:symbol=~/^red:/] > way::osmc_1 { color: red; width: 3; offset: prop("offset") + 4; } way::osmc_2 { offset: prop("offset", "osmc_1"); } relation[osmc:symbol=~/^green:/] > way::osmc_2 { color: green; width: 3; offset: prop("offset") + 4; } ...
This goes on for all the colors that are in use. Problems:
- The same code has to be repeated countless times
- You may forget some colors, that are rarely used
There is a proof of concept for a hiking style using a looping selector (patch probably outdated).
destination signs
The following rule matches ways that are member of a destination_sign relation with role to.
relation[type="destination_sign"] >[role="to"] way { ... }
However, there can be multiple such relations, and an arbitrary one is selected. The goal would be to visualize all the texts from all destination_sign relations.
Ideas for solutions
Simple loop structures using at-rules
Goto
Jump to another line.
possible syntax:
@label START; way[...][expr] { ... goto START; }
- Jump target must be defined outside a rule at top level
- Goto statements are inside a declaration block, usually at the end
- Eval expression should be supported inside a selector, to allow conditional jumps.
while loops
Executes some rules multiple times, as long as a certain expression evaluates to true.
@while (expr) { /* rules */ }
This would be roughly equivalent to
@label START; node[expr=true][...] { ... } way[expr=true][...] { ... } ... node[expr=true][...] { ... goto START; }
for loops
Loop over a list. The loop variable is a property which is set at the start of each run.
@for (i: list-expr) { /* rules */ }
This would be roughly equivalent to
* { mylst: list(1,2,3,4); } @while (count(prop("mylst")) > 0) { * { i: get(prop("mylst"), 0); mylst: sublist(prop("mylst"), 1, count(prop("mylst")) - 1); } /* rules */ }
Application
To apply these control structures to the problems described above, you would first need to get a list of all the parent relations that you like to loop over or some way to exclude relations that have already been processed.
Looping selector
This is an alternative solution. In contrast to the child selector ">", the looping child selector ">>" would run the rule for each of the matching parents.
relation[...] >> way { /* executed multiple times */ }
This integrates better with the common selector syntax. For extended use cases, it may be a restriction that the loop is tied to a single rule. If one would support nested rules, this restriction would no longer apply.
Although the loop is implicit, some context variables or functions to determine the current index and total length of the loop would be helpful, e.g. for calculating offsets.
Multiple layers
To allow one graphical primitive for each loop run (see hiking trail example), one needs a new layer for each run. Currently, the layer identifiers are constant literals, so this requires further extensions.
List comprehension
Python-like list comprehensions for eval statements. The basic structure is:
prop: [ somefun($a) for $a in list(1, 2, 3, 4) ]
An advanced example:
node[amenity=restaurant] { text: join("\n", [ concat($a, "=", tag($a)) for $a in tag_keys() if $a != "source" ]); }
Support:
- pgmapcss is planning to support this: https://github.com/plepe/pgmapcss/issues/45