In the previous post i explained one of the main design issues of road depiction in OpenStreetMap based maps and identified the most viable approach to address this. Here i want to demonstrate and explain how this can be practically done.
Just like in the case of viewpoint rendering i discussed earlier, this requires extending the functionality of Mapnik. If you want to re-produce what i show here you will, therefore, need to build Mapnik yourself using the modifications i have published.
To recapitulate – the problem i intend to address here is that in normal road rendering, the roads (including road polygons, like pedestrian areas) are rendered above linear features like waterways. Therefore, those line features do not render properly, even if they are diligently cut out in mapping from the road polygon (which – according to broadly established mapping conventions – is not strictly necessary). Also, road tunnels are fully covered underneath larger road polygons, which hides important information on underground road and path connections from the map user.
We want to address these issues without actually changing the road drawing order overall. Linear road tunnels are still supposed to be rendered below linear surface roads and linear road bridges above them. And surface roads, as well as tunnels and bridges of the same layer, should remain visually connected to one another to correctly communicate the connectivity in the roads system.
Impossible waterfalls
Working this problem is a bit like drawing an impossible waterfall like the one in the famous drawing by M.C. Escher.
This kind of drawing is consistent in the 2d depiction sketched and locally consistent also in three dimensions. It, however, becomes self contradicting and physically absurd in total. Therefore you cannot create this kind of drawing strictly from physical principles alone, you have to explicitly break with these principles. The challenge is to do so in a way that remains locally consistent.
The map rendering task at hand is similar, what we need to explicitly break with is the strict adherence to the vertical order of features in drawing and the rule that the styling of a feature is only defined by its own attributes. And we want to – as much as possible – maintain local consistency in drawing, giving the correct impression about connectivity and vertical order of features in reality.
This is a fairly complex task so i limited my proof-of-concept here to just the surface level road polygons. For the bridges and tunnels the setup presented here would essentially need to be duplicated once more for every layer (because for each of them a separate set of linear road features will need to be treated specially). Since bridge and tunnel road polygons are rare and large ones substantially covering other roadwork are even less common this limitation is practically not very big.
What we will need to do is rendering some road features (specifically road tunnels and linear features otherwise drawn before the road layers) differently when and for the area where they would normally be covered by a surface level road polygon. The procedure to do that is:
- Render the road layer as usual with the drawing order chosen according to the vertical oder of elements in reality as tagged, including the surface level road polygons.
- Render separately a second version of the road layers without the surface level road polygons.
the linear features that are visible in this version because they are not hidden by the road polygons need to be drawn here in the design in which they should ultimately show up above the road polygons. The layer implementing this is called
roads_noareas
. - Render a mask of the line features that are to be shown like in
roads_noareas
in the area where they are normally covered by the surface level road polygons. The layer implementing this is calledroads-line-mask
. - Render a mask of the surface level road polygons visible in the standard road display. That is essentially the surface level road polygon footprints minus the bridge road polygons. The layer implementing this is called
roads-area-mask
. - Combine these four renderings by composing
roads_noareas
overroads
only in areas covered by bothroads-area-mask
androads-line-mask
.
Advanced compositioning with GMIC
This is currently not possible to do with the rather limited compositioning capabilities of Mapnik. The only thing Mapnik can do out-of-the-box is composing what is newly drawn (either on the feature or on the layer level) with what has been previously drawn in a two component compositioning operation. The kind of multi-component compositioning operation described here is not available.
To overcome this i added support for raster post processing of the rendering using the GMIC image processing framework to Mapnik. GMIC is doing image processing using a dedicated scripting language. This framework can be invoked for every layer of the map style in addition to Mapnik’s internal per-style compositioning operation, either before or after. GMIC perfoms its processing based on an image buffer stack. The way i integrated this framework into Mapnik this stack is retained between layers with the current layer (either before or after Mapnik’s internal compositioning) being added to that stack for further processing.
To understand how to make use of that feature it is useful to understand how Mapnik does its own per-style compositioning. If you don’t use either style level opacity or comp-op (meaning no opacity
or comp-op
being set for the whole layer in Carto-CSS) Mapnik plainly renders everything into a single rendering buffer one element after the other. If opacity
or comp-op
is used Mapnik renders the layer into an empty (i.e. originally fully transparent) buffer and then composes this into the buffer retained from the previous layers using the chosen opacity
and comp-op
. If you use the new gmic
style property the compositioning is automatically activated as well, the layer is rendered into a new, empty buffer and that buffer is then added to the GMIC image buffer stack. Afterwards the script specified in the gmic
property is run.
But by default nothing is composed into the main rendering buffer. For that to happen the script has to assign the name use
to the last image buffer in the stack. Then Mapnik uses this buffer as the source for drawing the layer (potentially using the specified comp-op/opacity) and removes it from the GMIC image buffer stack for the next layer. In other words: the no-op GMIC command is (in CartoCSS syntax)
gmic: '-name. use';
To store the rendering of this layer for use in processing one of the next layers in addition to using it as is you can use
gmic: '+to_rgba. -name. use';
which duplicates the last image on the stack (the current layer rendering) into another rgba
buffer and then sets that up to be used. Since only the buffer used will get removed from the stack by Mapnik, the second version of the current buffer will remain for later re-use. Finally, if you just want to store what has been rendered for future use and not directly use the current layer at all, use something like:
gmic: '-to_rgba.';
This converts the last buffer in the stack to rgba – which it already is, hence: it essentially does nothing. Since the name of the buffer is unchanged, Mapnik will neither remove not use it and will simply move on to the next layer.
The other important thing to know is the naming of the buffers. All buffers added by Mapnik are named after the layer they are generated from – with any dash (-
) in the layer name being replaced by an underscore (_
) since dashes are special characters in the GMIC scripting language.
This whole principle of operation is just designed as i saw it fit for the moment – it is highly probable this is not ideal and needs adjustment in the future.
Back to the actual problem: The normal road layer (roads
) and the second variant without the surface level road polygons (roads_noareas
) are rendered from the same SQL code and share most of the CartoCSS code. The roads layer is rendered normally, for reference: here is how the rendering looks like after that layer:
After that comes the roads_noareas
in addition has the GMIC parameter
#roads-noareas { gmic: '-to_rgba.'; }
The roads-line-mask
uses the same, i.e. stores the rendering rather than using it. The actual compositioning happens in roads-area-mask
where we use the following GMIC script, written here – for clarity – with one command per line:
+channels[roads_noareas] 3
-name. roads_noareas_mask
-channels[roads_line_mask] 3
-channels[roads_area_mask] 3
-min[roads_area_mask] [roads_noareas_mask]
-min[roads_area_mask] [roads_line_mask]
-to_rgb[roads_noareas]
-append[roads_noareas] [roads_area_mask],c
-keep[roads_noareas]
-name. use
This is explained step by step in the following. I link to the resulting images of the different processing steps. These you can generate yourself by adding suitable -output
commands in the script. When you invoke the rendering via Nik4 or similar means, this will generate snapshots of the processing – which is useful for debugging purposes.
- duplicate the alpha channel of
roads_noareas
into a new buffer (result) - name that buffer
roads_noareas_mask
- reduce
roads_line_mask
to its alpha channel (result) - reduce
roads_area_mask
to its alpha channel (result) - calculate the minimum of
roads_area_mask
androads_noareas_mask
and store it intoroads_area_mask
(result) - calculate the minimum of
roads_area_mask
androads_line_mask
and store it intoroads_area_mask
(result) - strip the alpha channel from
roads_noareas
- append
roads_area_mask
as new alpha channel toroads_noareas
(result – with transparent parts rendered as dark gray) - remove all buffers except for
roads_noareas
- use the remaining buffer for composing this layer
The final results on the sample is here:
This, of course, not only works for pedestrian areas, but also for all the other polygon features rendered in the road layers:
Note that highway=steps
lines are rendered above highway polygons, just like barriers and tunnels, in disregard of the z-order because open air steps starting from within a pedestrian area, like at a subway entrance, are often just drawn onto the polygons. This is not a very descriptive mapping practice though – despite the convention that linear features supersede polygons (see previous post). Better options for mapping are sketched below.
That’s it essentially. This probably sounds much easier than it was though. The design of the roads_line_mask
and roads_area_mask
is pretty delicate to ensure the results are free of artefacts.
The whole exercise was about the display of line features overlapping with surface level road polygons. One remaining question in light of this is how to treat tunnel road polygons. They could reasonably stay hidden by the surface level rendering in line with the general principle of rendering according to the vertical ordering in reality. I decided instead to render them with outline only. My thought is in particular about subway platforms mapped with polygons – which are useful to be shown underneath pedestrian areas. A bit problematic is the strong difference between the outline rendering of the polygon version and the filled rendering of the linear version, making recognition as the same type of feature unlikely.
Real world examples
A few practical examples of how this looks like with real world data. First a case featuring barrier lines:
Note that the new approach not only shows barriers within the pedestrian area, the fence in this case, but also better shows the walls and retaining walls at the edge of the pedestrian area. The next example features waterways in Freiburg – the famous Bächle:
This well demonstrates that the drawing order change only applies to the road polygons and not to the linear roads – which here leads to partially visible waterway line signature within the pedestrian gray. That is somewhat confusing, but ultimately is caused by the inconsistent mixing of polygon and linear mapping of the pedestrian roads in this case. Another example from Lyon shows a partially mapped subway station:
You can see the underground platform and the subway lines as well as the entrances to the subway. Not mapped so far are the connecting footways in between. A similar case in Marseille:
Mapping is, likewise, incomplete here. And the underground platforms are mapped with linear ways so they are rendered with a fill color. Finally the example from Prague i already showed in the first post of this series:
As far as the tunnel rendering is concerned – more elaborate examples of subway stations or other underground structures underneath pedestrian areas are a bit difficult, because mapping consistency is often not that good. While mappers tend to try to apply tagging with layer
diligently, the lack of good visual feedback combined with the counterproductive incentive, that existing mainstream map rendering often has, tends to lead to issues in the data. Common examples are:
location=underground
/tunnel=yes
/covered=yes
not being applied consistently to underground platforms.- roads underneath pedestrian areas being deliberately not tagged
tunnel=yes
/covered=yes
to make them show up.
An overall discussion of the rendering technique demonstrated here will follow in the next and last part of this series.
Pingback: weeklyOSM 739 – weekly – semanario – hebdo – 週刊 – týdeník – Wochennotiz – 주간 – tygodnik