↑ OpenStreetmap Hacker's guide ↑↑ Net & Web  

Custom map style (1) - tweaking the standard OpenStreetmap style

OSM style overview -- Place names -- Change road colour -- New symbols -- Choose zoom level

The first, easiest step in developing a custom map style is tweaking what is already there. Here we will start out from the old standard OpenStreetmap style that can be obtained here. It consists of a collection of explicit XML style files for mapnik rather than generating them from more high-level descriptions and is therefore more suitable for seeing what is going on.

The standard OpenStreetmap style

Below is an overview of the XML files making up the standard style, how they include each other, and what they contain. If you want to modify an existing map feature, look here where to find its definition. If you want to add a feature below certain existing layers and on top of others, you cannot go by the order in which the included files reference each others: Most assign the content of other files to XML entities (constants) that are expanded in the main style file. The order of expansion is what matters.

osm.xml
Top-level file and the largest by far. Includes inc/entities.xml.inc in the top <!DOCTYPE> tag by assigning it to an entity and immediately expanding it. osm.xml also contains roads and some waterway features and expands XML entities containing layers defined in other files.
inc/entities.xml.inc
Includes inc/settings.xml.inc and inc/layers.xml.inc by assignment to entities and immediate expansion. Assigns inc/datasource-settings.xml.inc and inc/fontset-settings.xml.inc to entities that are expanded in osm.xml and layer style files. Defines spatial reference systems (map projections) that may be used by OpenStreetmap in terms of initialisation strings for the proj program and library.
Also defines maximum / minimum scale qualifiers that allow map appearance to vary by scale. A maximum scale corresponds to a minimum zoom level, and maximum and minimum scale limits are off by one so that the minimum scale for zoom 10 is the maximum scale for zoom 11. Map scales are defined at the equator based on a pixel size of 0.28 mm.
inc/settings.xml.inc
Selects the spatial reference system (map projection) and the database prefix, both of which have to be the same that were used when creating the database with osm2pgsql. Defines a distance tolerance for proximity searches depending on the map projection. Sets the directories for map symbols and the coastline shapefiles. The latter are not inside the OpenStreetmap style repository, and their location has to be set according to your setup.
inc/datasource-settings.xml.inc
This defines an XML entity containing Parameter tags describing access to your OpenStreetmap database. This entity is used in almost all map features in osm.xml and layer style files. See here for a brief discussion of the parameters and typical values.
inc/fontset-settings.xml.inc
Contains definitions of font sets used in the map style. Each font set contains a series of Font tags in order of decreasing preference.
inc/layers.xml.inc
Assigns the content of the following layer style files to XML entities. The order in which the entities are defined is not the same as the order in which they are expanded in osm.xml. Only the order of expansion matters &emdash; later layers overwrite earlier ones during rendering.
inc/layer-addressing.xml.inc
House numbers and (where available) names.
inc/layer-admin.xml.inc
Lines signifying administrative boundaries.
inc/layer-aerialways.xml.inc
Cable cars and ski lifts.
inc/layer-amenity-points.xml.inc
Symbols for amenities commonly found in cities such as pubs, cafes, restaurants, post boxes and offices, banks and ATMs, libraries, embassies, police stations, theatres, cinemas, embassies, bakeries, book shops, toilets, playgrounds and others.
inc/layer-amenity-stations.xml.inc
Symbols for railway, tram and underground stations and stops. Bus stops are in the previous file.
inc/layer-amenity-symbols.xml.inc
Symbols for amenities commonly found in the country, such as windmills, significant trees, helipads and airports, mountain peaks, caves, volcanos, gates etc.
inc/layer-buildings.xml.inc
Some types of buildings like railway stations, supermarkets, churches and airport terminals.
inc/layer-citywall.xml.inc
City and castle walls.
inc/layer-ferry-routes.xml.inc
Lines signifying ferry routes.
inc/layer-landcover.xml.inc
Features extending over areas like residential and industrial neighbourhoods, cemeteries, parks, university campuses, parkings, campsites etc.
inc/layer-placenames.xml.inc
Names of continents, countries and towns.
inc/layer-power.xml.inc
Power lines and pylons.
inc/layer-shapefiles.xml.inc
Outlines of countries and cities, shading of continents and built-up areas.
inc/layer-water.xml.inc
Appearance of water areas, from glaciers via lakes and rivers to wetlands. Their names if available. Waterway bridges and names of some water features are in the top-level osm.xml.
inc/layer-water_features.xml.inc
Marinas, piers, breakwaters and locks.

Making place names more readable

Place names in the OpenStreetmap standard style are on the small side, to fit on smartphone screens, and have strict no-overlap requirements that sometimes prevent printing all of them. In this section we will change three things about place names: We will increase the font size, remove the no-overlap constraint and reorder them so that larger place names are on top.

As can be seen from the list above, place names are drawn by the style file inc/layer-placenames.xml.inc. It contains two kinds of top-level tags, Style and Layer. The Style tags contain rules with TextSymbolizer tags that describe how to print them. Multiple rules exist for multiple map scales / zoom levels. The Layer tags describe the data source containing the place names, and their order is the drawing order &emdash; the last layer is on top.

Changing the font size is as simple as changing the size attribute of the TextSymbolizer tag, which is the font size in pixels. I prefer a font size that is ≈ 1.3 times the original, and have also changed some sizes that were the same for different zoom levels. A patch for this and the following changes is here.

To allow place names to overlap, we have to add the attribute allow-overlap="true" to the TextSymbolizer tags. This is documented in the corresponding Mapnik wiki page.

Finally, to overwrite small place names with larger ones, we have to reverse the order of the Layer tags in inc/layer-placenames.xml.inc. While we are at it, we can also raise all place names higher in the drawing stack by moving the line &layer-placenames; in osm.xml to the bottom before the closing </Map> tag. Then place names overwrite icons that are otherwise placed on top.

Changing the colour of roads

The old standard OpenStreetmap style renders trunk roads in green, which is unfortunate as it has a low contrast with surrounding forests. In this section we will replace that with violet.

Roads are described in the main style file, osm.xml. Three different colours are involved with rendering roads: The fill colour, the "casing" colour and a paler fill colour for tunnels. The fill colours apply to the bulk of the road surface, while the casing colour is for producing darker edges. The casings are drawn by drawing a slightly wider road in casing colour in a lower layer first, before the interior is drawn on top.

Changing the colour is as simple as replacing the value of the stroke attribute of the LineSymbolizer tags that draw the roads. Fortunately, nothing else in osm.xml has the same colours, so you can just make the following replacements globally:

#a9dba9  ->  #d4a0d4
#477147  ->  #6c406c
#cdeacd  ->  #eac0ea

Here is a rendering example:

   

This map section happens to contain no road number. For consistency we should also update the colour of the "shield" images in the symbols folder that carry the road number. This is easier than one might think &emdash; the following GraphicsMagick command line performs a colour space transformation that replaces shades of green by shades of violet:

gm convert symbols/tru_shield4.png  -recolor "0 1 0, 0.5 0 0.5, 0 1 0"  /tmp/tru_shield4-violet.png

The command preserves transparency, so the round shape of the shield is not changed. After converting all eight shields (which have different widths) and replacing them with the conversion result, you have a consistent trunk road colour.

Adding a new symbol

The OpenStreetmap database contains more information than is rendered by the standard style. For example, many of the smaller roads in Norway are toll roads paid for at unmanned toll points. You might want to know that in advance to have change ready and want to display the toll points on your map. (Update: Since I wrote this, toll booth symbols have been added to the OpenStreetmap web map, so perhaps someone has been reading this ;). It is still a useful simple example for how to add point features to a map style.)

To add a symbol, you first need to draw the symbol. I chose a red dollar sign for the toll stations. Each map element specification has two parts &emdash; the Style tag describing what it looks like and the Layer tag drawing it. The relative order of a corresponding Style and Layer tag is not important; but the different layers are drawn in order, so that the last layer is on top.

The simplest toy example for adding this symbol is adding the following code before the final </Map> tag in osm.xml:

<Style name="toll-booth">
    <Rule>
      <PointSymbolizer file="&symbols;/reddollar_s.png" allow-overlap="true" ignore-placement="true"/>
    </Rule>
</Style>
<Layer name="toll-booths" status="on" srs="&osm2pgsql_projection;">
      <StyleName>toll-booth</StyleName>
    <Datasource>
      <Parameter name="table">
        (select * from &prefix;_point where barrier = 'toll_booth') as tolls
      </Parameter>
      &datasource-settings;
    </Datasource>
</Layer>

The Layer references the Style via the StyleName tag. Like the standard map features, the code snippet uses the XML entities symbols (directory where the icons are stored), osm2pgsql_projection (map projection, used frequently), datasource-settings and prefix (database access parameters and table prefix). The Parameter tag contains an SQL query for all toll booths. The parentheses and the suffix as tolls are needed because the query is embedded into another query, though the name tolls does not matter. The result is the map on the right versus the standard style on the left (for a toll road a short distance north of Otta, Norway):

   

The example above causes the same symbol to be used independently of the map scale. That is not what you normally want &emdash; toll points should not be indicated in very large scale maps, and even when present the symbol sould be larger for smaller scales. The following more complete version uses the scale qualifier entities from entities.xml.inc and two more icons to achieve that:

<Style name="toll-booth">
    <Rule>
      &maxscale_zoom14;
      &minscale_zoom15;
      <PointSymbolizer file="&symbols;/reddollar_xs.png" allow-overlap="true" ignore-placement="true"/>
    </Rule>
    <Rule>
      &maxscale_zoom16;
      &minscale_zoom17;
      <PointSymbolizer file="&symbols;/reddollar_s.png" allow-overlap="true" ignore-placement="true"/>
    </Rule>
    <Rule>
      &maxscale_zoom18;
      <PointSymbolizer file="&symbols;/reddollar.png" allow-overlap="true" ignore-placement="true"/>
    </Rule>
</Style>
<Layer name="toll-booths" status="on" srs="&osm2pgsql_projection;">
      <StyleName>toll-booth</StyleName>
    <Datasource>
      <Parameter name="table">
        (select * from &prefix;_point where barrier = 'toll_booth') as tolls
      </Parameter>
      &datasource-settings;
    </Datasource>
</Layer>

Choosing the style of a specific zoom level

The standard style changes the thickness of roads and the amount of information displayed depending on the zoom level, each of which represents a range of map scales. But this is a compromise in more than one way: The amount of information that could be displayed varies widely between cities and unpopulated regions. Due to the Mercator projection, the map resolution is defined at the equator and will therefore differ between different latitudes by up to a factor of 4. If you render complete map regions, fixing the zoom level saves you the trouble of calculating the right map image size &emdash; rather, you can choose the information you want in the map first and then adapt the image size so it is not too crowded.

You can force the renderer to use the style of a specific zoom level by editing inc/entities.xml.inc, which defines the range of scales corresponding to zoom levels. Simply extend the interval for the zoom level you want and shrink the adjacent ones. The OpenStreetmap style assumes zoom levels to be mutually exclusive, so if the intervals overlap, you will get features from more than one zoom level. For example, the following modification gets you the style of zoom level 13 for a wide range of scales:

...
<!ENTITY maxscale_zoom11 "<MaxScaleDenominator>400000</MaxScaleDenominator>">
<!ENTITY minscale_zoom11 "<MinScaleDenominator>375000</MinScaleDenominator>">
<!ENTITY maxscale_zoom12 "<MaxScaleDenominator>375000</MaxScaleDenominator>">
<!ENTITY minscale_zoom12 "<MinScaleDenominator>350000</MinScaleDenominator>">
<!ENTITY maxscale_zoom13 "<MaxScaleDenominator>350000</MaxScaleDenominator>">
<!ENTITY minscale_zoom13 "<MinScaleDenominator>15000</MinScaleDenominator>">
<!ENTITY maxscale_zoom14 "<MaxScaleDenominator>15000</MaxScaleDenominator>">
<!ENTITY minscale_zoom14 "<MinScaleDenominator>13000</MinScaleDenominator>">
<!ENTITY maxscale_zoom15 "<MaxScaleDenominator>13000</MaxScaleDenominator>">
<!ENTITY minscale_zoom15 "<MinScaleDenominator>12500</MinScaleDenominator>">
...

Licensed under the Creative Commons Attribution-Share Alike 3.0 Germany License