Getting started with Maperitive

Introduction

OSM

Introduction to Maperitive

Maperitive is a tool for rendering maps…

Installation

You can download Maperitive from the official website. To install on Windows you can simply extract the zip and run Maperitive.exe.

Building a Custom map

To create a custom theme, I will use Seattle, WA (US) as a reference point. I picked Seattle because it is a moderately sized city with a wide variety of transportation infrastructure like roads, highways, railways, bus routes, tram lines, a monorail system, ferry connections, multiple airports as well as bridges and tunnels. Additionally, there are major bodies of water, a river, mountain ranges, and national parks nearby.

This combination of varied infrastructure and natural features withing a relatively small geographic area will make it easer to adjust and test different configuration options (e.g., adjusting scales, labels or line-widths) without having to move the map view around too much.

Open Maperitive

Open Maperitive and move the map view to cover a wide area of Seattle.

Maperitive open over Seattle.
Maperitive open over Seattle.

Download OSM data

When you first open Maperitive, it renders the map using the OpenStreetMap pre-rendered tiles. This means it doesn’t yet have any geographical data loaded - the map shown on the screen is to help you navigate and see what is being selected.

To download raw data from OpenStreetMap, click “Map” in the toolbar and “Download OSM Data” from the dropdown.

Downloading data from OpenStreetMap
Downloading data from OpenStreetMap

When you use the “Download OSM Data” feature, Maperitive loads all the data into memory. If you close the application, all downloaded data will be lost. You can monitor the amount of RAM used (displayed in the counter at the bottom-right) as the data is downloaded.

Once Maperitive has finished downloading and processing the geographical data, you will notice a slight update to the map’s contents. This is because the downloaded contents of the map are now rendered using the default ruleset.

You can edit any ruleset by clicking “Tools” -> “Edit Rendering Rules” (this will open the current ruleset in the default text editor) or alternatively (recommended) you can find the rules in the ./Rules subdirectory of the Maperitive installation.

Creating a new ruleset

@todo

Update the Default.mrules to an empty file and then click “Map” -> “Reload rendering rules”. As you will see, nothing has changed on the map. That’s because the new ruleset has not rendered anything yet and we still have the “web map” source loaded.

In the bottom right corner of the screen, under the “Sources tab” click on the “star” icon next to “Web map” to disable it.

Map rendered with empty ruleset file
Map rendered with empty ruleset file

Once the map renders with a blank ruleset file, you will see a black or mostly white map with an outline of the sea.

Overview of Rulesets

A Maperitive rendering ruleset structure looks like this:

properties
....

features
	points
	...
    
	lines
	...
		
	areas
	...	
		

rules
....
Properties

The Properties section defines various map attributes, such as the default font, text color, and more. Most of these settings are self-explanatory.

Features

The feature selectors define map features that can be grouped based on their properties:

  • Points – A single point on the map (latitude, longitude). For example, postbox: amenity=post_box. These correspond to nodes in OSM data.
  • Lines – An ordered list of two or more points, often representing roads, railways, bus routes, etc.
  • Areas – Filled polygons that define regions on the map, bounded by three or more points.

Rules

@todo

Draw something

Add the following content to the ruleset (replace existing content) and reload the rules (ctrl + R).

properties
	map-background-color: #000000
	map-sea-color: #FF0000
	
features
	areas
		water: natural=water

rules
	target: water
		define
			fill-color: #ffff00
		draw: fill

Let’s break down the parts of this rule set.

The properties section defines some general rules for the map:

  • the background color of the map is set to black #000000
  • the map sea color is set to red #FF0000

In the features section:

  • we define an area feature selector called water and have it select all areas in OSM data that have the natural equal to water.

The rules section defines how to render these features:

  • taget: water tells the maperitive that this rule applies to the feature selector called water
  • we then have a keyword define which is just an opening block for the definitions
  • we define the fill color for the matching areas to be yellow #ff0000
  • and finally, draw:fill tells Maperitive to render the area by filling the polygon

This ruleset will render the following map.

Map rendered with sea color = red, water = yellow and background=black
Map rendered with sea color = red, water = yellow and background=black

Why Didn’t Maperitive Render the Sea Yellow?

We’ve defined a rule to fill all water with yellow, so why is the sea ignored on the map?

This happens because of how OpenStreetMap data is structured. Oceans and seas are not typically defined as polygons, since it would be impractical to create polygons for entire oceans.

Instead, Maperitive follows this rendering process:

  1. The world starts as sea by default, meaning the first layer fills the entire map with red (map-sea-color: #ff0000).
  2. Land areas (polygons) are drawn on top, filling them with the background color (black).
  3. Defined bodies of water (lakes, ponds, rivers) are added next, and these polygons are painted yellow.

Since the ocean is never explicitly defined as a “water polygon,” it remains unaffected by the rule that colors water areas yellow.

Setting more appropriate colors for water

Previously, we used red for the sea and yellow for water to illustrate how rendering works in Maperitive. Now, it’s time to use more appropriate colors to build a visually refined map.

Let’s use the following colors:

#EDEDED Land Color
#90D5FF Sea color
#FFFFFF Roads

Updating the ruleset

Replace your existing ruleset with the following:

properties
	map-background-color: #ededed
	map-sea-color: #90D5FF
	
features
	points
		
	lines

	areas
		water: natural=water
		
rules
	target: water
		define
			fill-color: #90D5FF
			line-color: #90D5FF
		draw: fill
		draw: line

Changes and Their Effects:

  • map-background-color: #ededed Sets the land color to white (a shade darker than full white)
  • map-sea-color: #90D5FF fills the sea with a very light blue

Water area rules:

  • fill-color: #90D5FF & draw: fill – Colors all water areas (lakes, rivers, ponds) with the same light blue as oceans
  • line-color: #90D5FF & draw: line – Ensures the water border matches the fill color.
    • By default, Maperitive adds a black border around areas.
    • Setting line-color to the same color as the area being filled ensures that no border is shown
      • I did not find a way to remove this default styling - setting: line-width:0 or line-style: none did not have any effect
Rendering all water light blue
Rendering all water light blue

You may need to close Maperitive and restart it to have the changes apply (I’ve found it to be buggy and for some reason does not apply the background-change until restart).

Drawing Wooded Areas

To render forests and wooded regions, we define a feature selector that targets areas labeled with natural=wood or landuse=forest in OpenStreetMap data.

Feature Selector for Wooded Areas

areas
	...
	wood: natural=wood OR landuse=fores

This ensures that both natural woodlands and human-managed forests are included in the rendering.

Applying light green color to wooded areas

rules
    ...
    target: wood
		define
			fill-color: #adebb3
			line-color: #adebb3
		draw: fill
		draw: line
Map with woods and forests rendered
Map with woods and forests rendered

Add labels for cities and other localities.

In the previous step, we created a map displaying land, water, and wooded areas around Seattle. However, it’s still difficult to identify the city, its neighborhoods, and surrounding towns.

We’re now going to add labels to the map using point feature selectors.

Defining Point Feature Selectors

features
	points
		place-city: place=city
		place-town: place=town
		place-village: place=village
		place-suburb-neighbourhood: place=suburb OR place=neighbourhood
	lines
		...
		
	areas
		...

The place key from OpenStreetMap defines locations known by specific names. We create selectors for cities, towns, villages, and a combined selector for suburbs and neighborhoods.

Drawing Labels on the Map

rules
    target: place-*
		define
			text:name
		draw:text

In this rule target: place-* applies the rule to all the feature selectors whose names start with place-.

The table below describes what each of those represent in OpenStreetMap.

City Largest urban settlements. Examples:
  • Seattle, WA, US
  • Bozeman, MT, US
  • Dublin, Ireland
  • Paris, France
Town Smaller than a city, larger than a village.. Examples:
  • Redmond, WA (east of Seattle)
  • Bray, Ireland (south of Dublin)
Village A distinct small settlement. Examples:
  • Kilternan, Ireland - South of Dublin
  • La Barre, France - Close to the border with Switzerland
Suburb A part of a city or town that is known by a name. Suburb is usually on the outskirts of an urban area.
  • Seattle: Bellevue
  • Boston: Somerville
  • Dublin: Dún Laoghaire
  • Paris: La Défense
Neighbourhood A part of a city, town or a suburb known by a name.
  • Seattle: Capitol Hill, Belltown
  • Boston: Beacon Hill, Roxbury
  • Dublin: Temple Bar, Sandyford, Rathmines
  • Paris: Le Marais

Definitions and what gets labelled as town vs. a suburb or neighbourhood can vary between locations and there can be differences in articles, wiki pages and maps.

  • In some regions, suburbs may be independent cities but are still perceived as part of a metro area.
    • Miami Beach is officially a city but often considered just part of Miami.
    • Visitors to Harvard or MIT may say they're visiting Boston, though Cambridge is technically a separate city.
    • Dún Laoghaire is officially a separate town south of Dublin but is usually percieved as a suburb of Dublin.
Cites, towns, villages, suburbs and neighbourhoods labelled on the map.
Cites, towns, villages, suburbs and neighbourhoods labelled on the map.

Zoom levels

In the previous step, our map correctly labeled all cities, towns, suburbs, and neighborhoods in the Seattle metropolitan area. However, it’s still difficult to actually find Seattle.

This happens because all places are given equal importance at every zoom level. If we zoomed out to display the entire North American continent, the map would become cluttered with text, labeling everything from major cities to individual neighborhoods across the U.S., Canada, and Mexico.

In digital mapping, zoom levels define the scale at which the map is rendered - how much of the world is visible on the map and the details visible for a particular zoom level.

OpenStreetMap defines zoom levels from 0 - entire world to 20 - an individual house.

Updating the place labels to render based on zoom level

Let’s update our place-* rendering rule.

target: place-*
	define
		font-weight: bold
		text:name
	if: *city
		define
			font-size: 6:10;14:20
			min-zoom: 6
			max-zoom: 14
			placement-value: 1000
	elseif: *town
		define
			font-size: 9:8;12:10;14:15;20:20
			min-zoom: 9
			placement-value: 800
	elseif: *village
		define
			font-size: 12:10;20:20
			min-zoom: 12
			placement-value: 600
	elseif: *suburb*
		define
			font-size: 13:10;20:20
			min-zoom: 13
			placement-value: 200
	draw:text
  • target: place-* applies the rule to all selectors starting with place- (place-city, place-town, place-village, place-suburb-neighbourhood)
  • We first define common rendering rules for all places:
    • set the font to bold
    • text:name sets the content of label to be the name attribute of the OSM node

We then selectively apply rules to every matched selector using if-else operators:

  • if: *city - from all the selectors matched in target: place-*, apply these rules to those that end with “city”
  • min-zoom and max-zoom - only render when the map is zoomed between levels 6 and 14 (inclusive)
  • placement-value: 1000 - sets a high placement value for this label - on a busy map there could be multiple overlapping labels - in case of conflict the one with the higher placement label is rendered
  • font-size: at zoom level 6 the font-size is set to 10em, at level 14 it is set to 20em - intermediate values are calculated automatically
Map with place labels adjusted for zoom levels (cities and towns rendered). Note: so far all renderings used a zoom level of approx. 10.70.
Map with place labels adjusted for zoom levels (cities and towns rendered). Note: so far all renderings used a zoom level of approx. 10.70.
Zooming past level 12 will reveal suburbs and neighbourhoods.
Zooming past level 12 will reveal suburbs and neighbourhoods.

Rendering roads and streets

Now that we have labeled key locations on the map, it’s time to add roads and streets to provide structure and connectivity.

Feature selectors

features
	points
	...

	lines
		road-motorway: highway=motorway OR highway=motorway_link
		road-trunk: highway=trunk OR highway=trunk_link
		road-primary: highway=primary OR highway=primary_link
		road-secondary: highway=secondary OR highway=secondary_link
		road-tertiary: highway=tertiary OR highway=tertiary_link 
		road-unclassified: highway=unclassified
		street-residential: highway=residential OR highway=living_street
		road-service: highway=service
	
	areas
	...

In OpenStreetMap (OSM), roads and streets are represented as “ways” using the highway key. This key classifies roads into different types, from major highways and primary roads to local streets and pedestrian paths.

This classification helps determine how roads are rendered on the map, making highways and arterial roads more prominent, while having smaller streets only appear at higher zoom levels. By applying appropriate styling rules, we can control the visibility, color (although in this example we will use the same color for all roads), and thickness of roads to create a clear and intuitive map.

highway=motorway Restricted-access, high-speed roads, such as Interstates and freeways in the U.S., Autobahnen in Germany, motorways (M-prefix) in Ireland, and autostrade in Italy.
highway=motorway_link Link roads or slip roads that connect one motorway to another, typically through various interchanges.
highway=trunk High-importance roads that are not motorways, typically featuring multiple lanes and central dividers. N-prefix roads in Ireland for example.
highway=primary Next most important roads, typically linking larger towns or cities.
highway=secondary Typically connect smaller towns.
highway=tertiary Connect smaller villages and towns.
highway=unclassified The least important roads, typically found in rural areas. These roads serve purposes beyond property access and may or may not be paved.
highway=service Alleyways, access roads to industrial sites and similar.
highway=residential Streets and roads that provide access to residential areas and are not used as throughways.
highway=living_street Roads in residential areas where pedestrians have priority over cars.

Road rendering rules

rules
	...
	target: road-motorway
		define
			line-color: #ffffff
			line-style: solid
			min-zoom: 7
				line-width : 7:1;9:2;12:3;13:7;16:10;18:12
		draw: line
		define
			min-zoom: 14
			text:name
		draw: text
	
	target: road-trunk
		define
			line-color: #ffffff
			line-style: solid
			min-zoom: 9
				line-width : 9:2;12:3;13:7;16:10;18:12
		draw: line
		define
			min-zoom: 14
			text:name
		draw: text

	target: road-primary
		define
			line-color: #ffffff
			line-style : solid
			line-width: 10:0.8;9:1;12:3;13:4;14:8;16:10;18:12
			min-zoom: 10 
		draw: line
		define
			min-zoom: 14
			font-size: 14:8;16:11;18:13
			text-halo-width : 15%
			text-halo-opacity: 0.9
			text:name
		draw: text
	
	target: road-secondary
		define
			line-color: #ffffff
			line-style : solid
			line-width : 12:0.8;13:1;14:4;16:8;18:10
			min-zoom: 12 
		draw: line
		define
			min-zoom: 15
			font-size: 15:6;16:10;18:10
			text-halo-width : 0%
			text-halo-opacity: 0.8
			text:name
		draw: text

	target: road-tertiary
		define
			line-color: #ffffff
			line-style : solid
			line-width : 12:0.8;13:1;14:4;16:8;18:10
			min-zoom: 12 
		draw: line
		define
			min-zoom: 15
			font-size: 15:6;16:10;18:10
			text-halo-width : 0%
			text-halo-opacity: 0.8
			text:name
		draw: text
	
	target: street-residential
		define
			line-color: #ffffff
			line-style : solid
			line-width : 12:0.8;13:1;14:4;16:8;18:10
			min-zoom: 12 
		draw: line
		define
			min-zoom: 15
			font-size: 15:6;16:10;18:10
			text-halo-width : 0%
			text-halo-opacity: 0.8
			text:name
		draw: text

	target: road-service
		define
			line-color: #dddddd
			line-style : solid
			line-width : 14:1;18:7
			min-zoom: 14 
		draw: line
Major roads shown prominently. Roads of lesser importance and residential roads shown as thin lines at this zoom level.
Major roads shown prominently. Roads of lesser importance and residential roads shown as thin lines at this zoom level.
Zooming in further shows alleyways in grey, increases the width primary, secondary and tertiary roads and renders their names.
Zooming in further shows alleyways in grey, increases the width primary, secondary and tertiary roads and renders their names.

Ferry routes

There are many ferry routes connecting Seattle to neighboring islands. Many people commute daily to work using ferries, making them an essential part of the region’s transport network.

Feature Selectors

To define ferry routes on our map, we can create a feature selector by specifying a line selector.

lines
  ...
  ferry: route=ferry

Rules

target: ferry
	define
		line-color: #8080ff
		line-style: dashdot
		min-zoom: 9
		line-width : 9:1;18:4
	draw: line
	define
		min-zoom: 13
		font-size: 13:11;18:16
		text:name
	draw:text
Ferry routes from Seattle.
Ferry routes from Seattle.

Adding operator information

In some locations, a single ferry route may be operated by multiple companies, each departing from different docks or areas within a city. Adding the operator name to the label might prove useful.

We can do this by changing the text:name line to text:name " (" operator ")".

The text: rule can be used to format the string that is being rendered. So far, we’ve only used the name attribute, but any attribute value can be used. The values in quotation marks are static text that will be rendered. A similar type of string concatenation in JavaScript would look like name + ' (' + operator + ')'.

Ferry routes from Seattle with operator names.
Ferry routes from Seattle with operator names.

Rendering the operator only when defined

If you look at the “Kingston Fast Ferry” line on the map, it looks like the operator for that route in not defined in OpenStreetMap which causes the text to be rendered with empty brackets as “Kingston Fast Ferry ()”. We can fix that by conditionally rendering the operator name only when it’s defined.

target: ferry
	... line rules
	define
		min-zoom: 13
		font-size: 13:11;18:16
	for: operator
		define
			text:name " (" operator ")"
	else
		define
			text:name 
	draw:text

The for keywords is used to apply the rules conditionally based on some properties of the feature being rendered. In this case we are checking for presence of the operator value and defaulting to just the name if it’s not defined. Javascript equivalent would be:

if (operator) {
    label = name + ' (' + operator + ')'
} 
else {
    label = name       
}

The boolean expressions in for can be used similarly to any programming language and can check for values too. Example:

  • for: motorcar=yes and horse=no - if we wanted to apply a rule to a ferry line that transports cars but does not allow horses
  • for: bicycle=yes or motorcar=yes - accepts either cars or bicycles

Let’s update the line rendering rule to make the line for passenger-only ferries slightly thinner and lighter color than the car ferries.

define
	line-style: dashdot
	min-zoom: 9
for: motor_vehicle=yes OR motorcar=yes
	define
		line-color: #5555bd
		line-width: 9:1;18:4
else
	define
		line-color: #8080ff
		line-width: 9:0.5;18:3
draw: line
Ferry routes from Seattle with operator names when available, emphasized line for routes that transport cars and other motor vehicles.
Ferry routes from Seattle with operator names when available, emphasized line for routes that transport cars and other motor vehicles.