Two years ago i showed a new design concept for rendering trees in maps. This used a combination of hand designed components with automatic geometry processing to depict different types of trees of different sizes in a way the symbols naturally overlap in a well readable way when the trees are close to each other.
Technically, this was done by rendering the symbols from a polygon representation in the rendering database. This facilitates both the construction of the symbols from their hand drawn design elements, the dynamic scaling according to the tree diameter, and the cutting of intersecting tree symbols for the natural display of overlapping symbols.
One thing you might have noticed with the tree symbols is that they are non-blocking. Other symbols and labels overlap with the tree symbols freely. This matches the tree display in OpenStreetMap-Carto and is a prudent choice for tree display. But it also was a practical necessity, since polygons rendered from the database in Mapnik are inherently non-blocking. Collision detection and blocking in Mapnik is traditionally only available for point, marker and text symbolizers. The technique i demonstrated for the trees was therefore limited in its application to situations where non-blocking symbols are the goal.
I have since then extended the capabilities of Mapnik to customize dependencies between symbols drawn using the concept of anchors. I here want to show how that feature can be further extended to allow the use of symbols drawn from the database for blocking applications.
Displaying viewpoints
Viewpoints are locations that offer an extraordinary view of the environment and are therefore often a popular destination to visit. Such are tagged in OpenStreetMap with tourism=viewpoint
. A significant percentage of those (>12 percent) are also tagged with a direction
tag, indicating the direction in which the viewpoint offers a good view.
OSM-Carto has rendered viewpoints with a static point symbol for a long time, but is not taking into account the direction tag. Depiction of viewpoint directions has been pioneered by the OpenTopoMap style.
I am using SQL functions to calculate azimuth direction and viewing angle from the direction tag. Then i use those to pick and suitably rotate the best fitting symbol from a pre-generated table of symbols in the rendering database. Here is how this looks like for different variants of the direction tag:
And here are the pre-generated symbols from the symbols table these are rendered from:
There are a number of aspects about this rendering of the individual viewpoint symbols i want to point out. First: Viewpoints without a direction tag remain shown with the generic traditional symbol to distinguish them clearly from the direction=0-360
viewpoints. Second: The size of the symbols is slightly increased as you zoom in – to not take too much space at the lower zoom levels and be more clearly readable at the higher ones.
But also note that the size of the symbol varies depending on the angle depicted – for the narrow angles this is increased while for the 360 degree it is decreased. This makes the weight of the symbols of viewpoints with different angles more similar. Finally, also note the positioning of the name labels is adjusted to the symbol bounds so there is no excessive gap between symbol and label for the northward looking viewpoints.
In contrast to most other point symbols in the style, the viewpoints are rendered with a relatively subtle bright halo. This ensures a good readability of the fairly fine grained symbol design above strongly structured backgrounds – which are common near viewpoints with elements like natural=cliff
, barrier=retaining_wall
or natural=bare_rock
polygons.
Cutting and blocking
The real innovation, however, comes from the way symbols are cut and interacting with other symbols. Like in case of trees, close-by viewpoints are cut away in their overlaps, ensuring a clean display without fully dropping the display of some of the symbols. At the same time, symbols that have priority over the viewpoints (because they are displayed at earlier zoom levels) are blocking the display of close-by viewpoints. Also viewpoints are blocking the display of lower priority symbols close-by. Mountain peaks (natural=peak
) are treated differently, because they are commonly close to viewpoints or mapped in combination on a single node. Here symbols do not block each other, but the peak symbol is cut out from the viewpoint depiction to allow both to be displayed together. More on that later.
There are two modifications of Mapnik necessary to accomplish this:
- To support blocking between viewpoints and other symbols, support for
anchor-cond
was added for polygon symbolizers. This allows implementation of simple blocking by rendering a zero opacity marker symbolizer for the viewpoint like you’d do for an SVG symbol and then tie the rendering of the actual symbols to that via anchor-cond. That, however, does not work in combination with the cutting of the viewpoint symbols with one another, because even symbols that are blocked by other non-viewpoint symbols are getting included in the cutting operation – which they should not. To fix that we need to - add a way to access the list of anchors (the identifiers of symbols rendered successfully so far) from within SQL, where we are doing the cutting of the symbols.
While the first of these was fairly straightforward to implement, the second was a bit more tricky. This was mainly because of the way Mapnik handles database queries and rendering. Instead of running the query of each of the layers of the map style and then rendering its results before moving on to the next layer, Mapnik has the ability to asynchronously start all the queries up-front and then start rendering the map as soon as the queries return with the data. But this procedure, of course, does not work when the query of a layer depends on the rendering results of the layers rendered before. So, to use this feature of allowing access to the anchors from within SQL, we have to move to a strictly synchronous query and rendering procedure.
Practically the whole process looks as follows:
- The main POI (
amenity-points
) layer is rendered with the viewpoints being represented by zero opacity placeholder markers, that have a suitableanchor-set
parameter to document successful symbol placement. - A second layer (
viewpoints
) is set up to display the actual viewpoint symbols. It also performs the intersection with the other viewpoints. This layer is rendered after the POI layer and has an additional parameter in theDatasource
:anchors_table: carto_anchors
. This tells Mapnik to make available the list of anchors previously set in a temporary table in the database with the specified name. That table is then used in the SQL query of the layer in the form of an additionalWHERE
condition likeAND EXISTS (SELECT 1 FROM carto_anchors WHERE "name" = 'viewpoint_' || osm_id::text)
.
Viewpoints and peaks
The combined rendering of viewpoints and peaks required some further modifications to the point symbol and label rendering system. The peak symbols are cut out from the viewpoint symbols just like the other viewpoints. In addition, the label position is adjusted as necessary to avoid overlap/blocking with either the peak or the viewpoint symbol in all the different direction variants.
Real world examples
While there are quite a lot of viewpoints mapped with direction tags, this tagging is usually patchy – not many regions have all or even most viewpoints tagged this way. None the less, here are a few examples of how viewpoints with specified direction look like in real world contexts. A double resolution version is linked from the images.
Conclusions
What i showed here is how dynamic symbol design can be implemented in maps with various forms of collision and overlap handling. In the presented case of viewpoints, this entails in particular the following components:
- The different viewpoint symbols are not blocking each other, but instead their symbols are cut out to avoid overlaps in a manner similar to what i have demonstrated for trees in the past. Symbol priorities in cutting are based on elevation (when tagged) and view angle.
- The viewpoint symbols are blocking and get blocked by other point symbols based on their priorities. Except for
- peak symbols, which do not block viewpoints, but have their symbols cut out from the viewpoint depiction like viewpoints are cut out from each other. This includes cases where a node is tagged both as a viewpoint and a peak.
- Labels are shown under the symbol with an offset dynamically adjusted to the geometry of the viewpoint visualization used.
To accomplish these things additional functions were added to Mapnik and Carto. One allows conditional rendering of polygon symbolizers, depending on the successful placement of other symbolizers, which are subject to collision detection. The other provides access to information on what symbolizers were rendered successfully in previous layers from within SQL via temporary tables. Combined with the use of zero opacity placeholder symbolizers this allows the implementation of the design features described.
The Mapnik modifications are available on Github. To build Carto with support for these new features you also need the modified mapnik-reference.
The modifications of the Alternative-colors style implementing the dynamic viewpoint rendering have been published as well.
The map design concepts demonstrated here and the modifications of Mapnik that enable these further contribute to implementing point 5 on my list of critical features map design work requires from the tools it uses. But keep in mind i am not a software developer, i do not aim for these features to be usable more generally, they are just a proof-of-concept.
Pingback: weeklyOSM 738 – weekly – semanario – hebdo – 週刊 – týdeník – Wochennotiz – 주간 – tygodnik