MapCSS/0.2/Proposal loops

From OpenStreetMap Wiki
< MapCSS‎ | 0.2
Jump to navigation Jump to search

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:

Comments