The makemap tool uses rules written in a small XML-based language to control the import of data from OSM and other sources. The idea is that an input data set is supplied containing a set of attributes and values (a row in the map database), and a program made up from a series of statements uses it to create one or more output map objects.

makemap uses a standard set of rules. You can write them out using makemap (see makemap's usage message for details) and edit them to create your own rules.

Thus a single input data set (database row) can create more than one map object, but the converse is not true.

The program is executed in its entirety once for each input data set.

File format

Files have the extension .makemap and are used by the makemap tool. They can be placed on the makemap command line using either /input=, or as the main input file.

<CartoTypeImportRules> element contains the whole file.

It can contain:

  • <file_type> elements
  • <file> elements
  • commands

The <file_type> elements contain commands to be applied to a certain file type or sett of files, specified using a name containing wild cards. For example, <file_type name='*.osm'> ... </file_type> specifies a program to be applied to all OSM (OpenStreetMap data) files; the program is applied to OSM files placed later on the makemap command line.

The <file> elements contain commands to be applied to a named file, and cause that file to be loaded and processed. The <file> command is usually used for SHP files.

Any commands outside any <file_type> or <file> elements are treated as if they are in an implicit <file_type name='*.osm'> ... </file_type>  element.

Summary

An import rules program is run once for every row in the input database. For example, when reading OSM data it is run once for every node, way and relation.

The statements in the program can set output map object attributes like the layer name, the name of the object and other string attributes, and the integer attribute. The <commit/> statement creates a new output object.

The interpreter state

It consists of a stack of output objects reflecting the dynamic nesting of <group> elements (see below), and a list of output data objects for conversion to map objects.

The current input object represents a single database row. When processing OSM data, it contains the data from a <node>, <way> or <relation> element. When expressions use an ordinary variable, it refers to one of the attributes of the current input object; in OSM terms, that would be a element with k and v attributes giving the key and the value respectively; the k attribute is the variable name, and the v attribute is its value.

The current output object is the one on the top of the stack. When the <commit/> statement is executed, it is copied to the list of output data objects, and each one of these becomes a map object after the import rules program has been fully executed.

Output object data in detail

Each output object that is committed by a <commit/> statement contains the following information:

The layer name: a string. If the layer is empty, the map object is used only if it is part of an OSM relation. Normally the layer must not be empty. Set it using <set_layer>.

The map object type. This should be set using <set_point/>, <set_line/> or <set_polygon/>. If it is not set, a default is used, but it is always best to set it explicitly, especially when processing OSM ways and relations.

The 32-bit integer attribute. This is set using <set_int> , <set_int_low>  and <set_osm_type>. Any statement that changes any of the top 15 bits (usually this will be <set_osm_type>) causes the output map object to use those bits instead of the top 15 bits of the road type. Any statement that changes any of the lower 17 bits causes the output map object to use those bits instead of the lower 17 bits of the road type.

The 32-bit road type. This is set using <set_road>, <set_road_type>, <set_roundabout>, <set_level>, etc.: see below under <set_road>. Any statement that changes any of the top 15 bits causes the output map object to use those bits instead of the top 15 bits of the integer attribute. Any statement that changes any of the lower 17 bits causes the output map object to use those bits instead of the lower 17 bits of the integer attribute.

Flags indicating how to create the final integer attribute written to the output map object: this is made up of the top 15 bits of either the integer attribute or the road attribute, and the bottom 17 bits of either of the two. These flags are not settable explicitly, but are set implicitly by the commands: see above. You tell the style sheet which bits of the integer attribute of a map object represent the road type using the roadflags attribute of the <layer> element; since this is decided at the layer level, it's important to be consistent within a layer when writing import rules.

The string attributes. They are set by the <set> and <copy> statements.

Conditional execution of statements

All statements except <file_type> and <file> may be executed conditionally. That happens if the statement has a non-empty test attribute containing a boolean expression. The test is compulsory in the if statement, and optional for all other statements.

A statement may also contain an else attribute instead of a test attribute, causing it to be executed only if the preceding statement (at the current level of nesting inside <group> or <if> statements) had a test or else with a false result, and if the else attribute had a true result.

example:

 <if test='place=="city"'>
   <commit test='population!=@ and population lt 1000' layer='place/major' osm_type='vil'/>
   <commit else='population!=@ and population lt 100000' layer='place/major' osm_type='tow'/>
   <commit else='1' layer='place/major' osm_type='cit'/>
 </if>
 <if else='place=="town"'>
   <commit test='population!=@ and population lt 1000' layer='place/major' osm_type='vil'/>
   <commit else='1' layer='place/major' osm_type='tow'/>
 </if>
 <if else='place=="village"'>
   <commit test='population!=@ and population lt 250' layer='place/minor' osm_type='ham'/>
   <commit else='1' layer='place/minor' osm_type='vil'/>
 </if>

Here, the construct else='1' is used to provide an unconditional 'else', because an ordinary else is an 'else-if'.

Expressions

Expressions can refer to the attributes of the current input object by name, and can test whether the object is in a node, a way or a relation, using the special boolean variables @node, @way and @relation; exactly one of these is true if the data source is OpenStreetMap, otherwise none of them is true. The variable @ means 'undefined'.

More details of expression syntax and operators.

Statements

<file_type name='string'> statements </file_type>

Creates a program defined by statements to be used for the file type given by name. The file type is a file extension like 'osm', without a leading full stop. This statement may not appear inside any other element, but must appear directly inside <CartoTypeinputRules>

<file name='string' { codepage='number' } > statements </file>

Loads the named file and processes it using the program defined by statements. The optional codepage attribute gives the 8-bit character encoding as a codepage number, which is used in DBF files associated with ESRI shapefiles.

example:

<?xml version="1.0" encoding="UTF-8"?>
<CartoTypeImportRules>
   <file name='ne_10m_land.shp'>
      <commit layer='outline'/>
   </file>
   <file name='ne_10m_lakes.shp' codepage='1252'>
      <copy name='name'/>
      <commit layer='land/major' osm_type='wat'/>
   </file>
   <file name='ne_10m_populated_places.shp' codepage='1252'>
      <set name='name' value='NAME'/>
      <set_layer name='place/major'/>
      <commit test='SCALERANK==1 or SCALERANK==2' osm_type='cit'/>
      <commit else='SCALERANK gt 3' osm_type='tow'/>
   </file>
   <file name='ne_10m_admin_0_countries.shp' codepage='1252'>
      <set name='name' value='NAME'/>
      <set_layer name='boundary/major'/>
      <set_int_low value='2'/>
      <commit/>
   </file>
   <file name='ne_10m_roads.shp' codepage='1252'>
      <set name='ref' value='name'/>
      <set_layer name='road/major'/>
      <if test='type=="Major Highway"'>
         <set_road_type name='Motorway'/>
         <commit/>
      </if>
      <if else='type=="Secondary Highway"'>
         <set_road_type name='TrunkRoad'/>
         <commit/>
      </if>
      <if else='type=="Road" or type=="Unknown"'>
         <set_road_type name='PrimaryRoad'/>
         <commit/>
      </if>
   </file>
</CartoTypeImportRules>

These <file> statements create a world map from Natural Earth data.

<group> statements </group>

Pushes a new output data object on the stack, copying its values from those of the top of the stack. At the end of the group the top-of-stack object is popped and discarded. Thus the <group> must contain at least one <commit> statement to have any useful effect.

example:

<group test='highway!=@'>;

opens a new group if the 'highway' attribute is defined.

<if {test|else}='expression' > statements </if>

Tests the expression and executes the contained statements if it is true. The expression may refer to attributes of the current input object by their ordinary names. The difference between <if> and <group> is that <if> does not push a new output data object on to the stack.

example:

 <if test='highway=="motorway"'>
 <set_road_type name='Motorway'/>
 <commit layer='road/major'/>
 </if>

tests whether the 'highway' attribute has the value 'motorway' and if so sets the road type to Motorway and commits a map object to the layer 'road/major'.

Note that all other statements may also contain a test or an else, so <if> exists to provide a tested grouping of statements; there is no need to use an <if> ... </if> construct around a single statement.

<commit { layer='string' type='string' osm_type='string' int='expression' }/>

Creates a new output data object by copying the top-of-stack values, and adds it to the list of output data objects to be made into map objects.

If layer is set, it is used as the layer for this object instead of the layer set by <set_layer>.

If type is set, it is used as the map object type instead of the type set by <set_point>, <set_line> or <set_polygon> .

If osm_type is set, it is used as the OSM type instead of the value set by <set_osm_type>.

If int is set, it is used as the integer attribute instead of the value set by <set_int>, <set_int_low>, <set_road> or any other statement that sets the integer attribute, but osm_type, if present, overrides the top 15 bits of the integer attribute.

<break/>

Breaks out of the current <group> or <if>.

<exit/>

Terminates execution of the whole program.

<set name='string' value='expression'/>

Sets the string attribute name to value.

example:

<set name='_ele' value='ele'/>

sets the string attribute '_ele' to the value of the input attribute 'ele'.

<copy name='string'/>

Sets the string attribute(s) named by key, which may contain wild cards, to the same string attributes in the output object. The name 'name' copies the 'name' attribute to the unnamed output attribute, which by convention is the name.

example:

<copy name='name:*'/>

copies all attributes matching 'name:*', like 'name:en', 'name:de', etc., to output string attributes of the same names.

You can also use the prefix attribute to add a prefix to the output string attributes:

<copy name='*' prefix='_'/>

The above example copies all attributes to output attributes, prefixing the output attributes with an underscore. Attributes starting with an underscore are not indexed for text searching. Therefore this example allows you to import all OpenStreetMap attributes without unnecessarily enlarging the text index. When a prefix is used, attributes which have already been set, even without the prefix, are not duplicated by new attributes with a prefix. This is in general what you want to do; if you have imported 'addr:street' explicitly, <copy name='*' prefix='_'/> does not create a new attribute called '_addr:street'.

<set_int mask='expression' shift='expression' value='expression'/>

Sets the integer attribute to value, using the optional mask (default = 0xFFFFFFFF) and shift (default = 0) values. This shortcut is available:

<set_int_low> = <set_int mask='01FFFF' shift='0'>

example:

<set_int_low value='admin_level'/>

sets the low 17 bits of the integer attribute to the value of the input attribute 'admin_level', converted to an integer.

<set_osm_type name='string'>

Sets the OSM type (top 15 bits of the integer attribute) to 'name', encoded as 3 5-bit values; 'name' must be a string of three letters.

example:

<set_osm_type name='bou'/>

sets the OSM type to 'bou'.

<set_road mask='expression' shift='expression' value='expression'/>

Sets the road attribute to value, using the optional mask (default = 0xFFFFFFFF) and shift (default = 0) values. Some shortcuts are available:

<set_one_way> = <set_one_way_forward>; sets the one-way state to 'forwards'

<set_one_way_forward> = <set_road mask='48' value='16'/>; sets the one-way state to 'forwards'

<set_one_way_backward> = <set_road mask='48' value='32'/>; sets the one-way state to 'backwards'

<set_roundabout> = <set_road mask='4' value='4'/>; sets the roundabout flag

<set_toll> = <set_road mask='2' value='2'/>; sets the toll-road flag

<set_level> = <set_road mask='0xF000' shift=12' value='expression'/>; sets the 'level' value (layer in OSM parlance) to the value of expression, which must be in the range -8 ... 7.

<set_bridge>; sets the bridge flag

<set_tunnel>; sets the tunnel flag

<set_road_type name='string'/>

Sets the road type (by convention, bits 6...11 of the road attribute) to the predefined value in name, which must be one of Motorway, MotorwayLink, TrunkRoad, TrunkRoadLink, PrimaryRoad, PrimaryRoadLink, SecondaryRoad, SecondaryRoadLink, TertiaryRoad, TertiaryRoadLink, UnclassifiedRoad, ResidentialRoad, Track, ServiceRoad, PedestrianRoad, VehicularFerry, PassengerFerry, Other0 ... Other7.

<set_layer name='string'/>

Sets the layer to name.

<set_point/>

Sets the map object type to 'point'.

<set_line/>

Sets the map object type to 'line'.

<set_polygon/>

Sets the map object type to 'polygon'.