Rendering OSM Maps with MapCSS
After having written about where we get the data for KDE Itinerary’s train station and airport maps from, this post is about how we actually render this data.
Rendering OSM Data
When looking at the geometry, OSM data consists basically just of lines and polygons, so technically there’s only few primitives to render. To make this look like a useful map though, a lot depends on the map styling. That is, the decision with which colors, line strokes, fill patterns, z order, and iconographic or textual labels those geometric primitives should be rendered. Here we are talking about hundreds of rules to make a good map.
Since writing all those rules in code is hard to work with and to maintain, using a declarative way to define the rules is attractive. Several such systems exist in the OSM space already, such as MapCSS or CartoCSS, so we followed that and are using MapCSS for our indoor map renderer.
MapCSS
As the name suggests, there’s quite a few similarities to CSS, which makes it fairly straightforward to work with. Selectors and conditions are tied to querying OSM element types, OSM tags and the current zoom level instead of HTML elements and attributes, and similarly the properties you can declare refer to the styling capabilities of the map renderer instead.
Let’s look at a simple example for rendering railway tracks:
way[railway=rail]
{
color: #eff0f1;
dashes: 2,2;
width: 1.5m;
casing-width: 3px;
casing-color: #31363b;
linecap: none;
}
One thing you might notice there is the support for the size unit “m”, meters. Unlike sizes specified in pixel, the size on screen changes depending on the current zoom level. Another powerful mechanism is being able to refer to values from OSM tags here. Let’s expand the above example by the following additional rule:
way[railway=rail][gauge] {
width: gauge;
}
This will cause railway tracks being rendered in a width matching their track gauge. The following screenshot of the mixed-gauge Swiss station of Visp shows the result.

Just like CSS, MapCSS also supports importing of rules from other files. This allows a modular design, sharing common rules between a dark and a light style for example, or even to define special-purpose variations of a common base style. This could for example be used to specifically highlight things depending on a users need or the current use-case/workflow.
Another nice side-effect of not having this in (compiled) code is the ability to load and reload MapCSS stylesheets arbitrarily at runtime, which speeds up development considerably: just make a change, reload the stylesheet and check if things look like you want them to look.
One can also twist this into a fancy debug output for the data processing, by creating a diagnostic stylesheet which e.g. just shows geometric primitives as-is.

Such flexibility often has its price though. And indeed, the initial naive implementation didn’t scale up well enough to a realistically sized rule set. However, after a few rounds of optimizations MapCSS evaluation for an entire large building floor now easily happens within a single frame cycle, on a mobile phone. Even loading the data of an entire city into this as a stress test just takes a few seconds. And should we ever hit the limit again, there’s a few more ideas on how to further improve this.
There’s still a number of things from the MapCSS spec we haven’t implemented yet, due to so far not having needed them (e.g. fill textures, extrusion properties, chained selectors or eval expressions). Similarly, there are a few things that seem not easily doable by standard MapCSS features, such as indicating line directions (e.g. on one way streets or escalators), so a few custom extensions might become necessary. This is all fine though, having a system now that we can tailor exactly to our needs.
Outlook
While there is of course still plenty of work to do all over this, I think we are getting close to an initial integration into KDE Itinerary. While initially not offering more than showing a basic map, it would enable work on deeper integration features, and make all improvements on the map side immediately useful.
If you are interested in contributing, no matter if feedback, ideas or code, check out the workboard on Gitlab.
For playing with this locally, the best starting point is probably the
QML example.
After building KPublicTransport,
add the bin/
sub-folder of the build directory to the QML2_IMPORT_PATH
environment variable
(or install to a properly setup prefix), and then load tests/indoormap.qml
with qmlscene
.