MapCSS/0.2
MapCSS is a CSS-like language for map stylesheets.
This page contains second draft of MapCSS format. See MapCSS/0.1 for the first draft and talk page and Proposals for suggested improvements. There is a MapCSS Mailing list.
Install the VS Code extension to add visual highlighting for MapCSS paint and validator syntax. Check also some kind of BNF and an antlr grammar.
Calculations in MapCSS use the eval syntax.
Language Structure
A MapCSS stylesheet is made up of a series of rules about how to style elements of a map. Each rule has two parts:
- One or more selector, used to determine to which map elements the statement applies.
- One or more declaration, used to determine the style which should be applied to matching elements.
Additionally, comments can be made using "//"
to comment the rest of the line out, or using "/*"
and "*/"
to comment out everything between these end points.
Selectors
Selectors determine whether a rule applies to a particular object or not. They may test several things: the type of the object; the zoom level of the render; the tags applied to the object; the pseudo-classes and classes applied to the object.
Types
The selector's type may be any one of the core OSM object types,
node
way
relation
plus some other interesting cases,
area
(A way where the start and finish nodes are the same node, orarea=yes
has been set)line
(A way where the start and finish nodes are not the same node, orarea=no
has been set)canvas
(The background of the render)*
(Any of the above)
Tests
To test what tags are applied to an object you can write the condition in square brackets after the object type, these binary tests may use any of these operators: =
, !=
, =~
, >
, <
, >=
, <=
.
way[highway=primary] /* Matches against all ways with the highway tag set to primary */ way[highway=~/.*ary/] /* Matches against all ways with a highway that matches the regex ".*ary" */
Note that strings used here must start with a letter or '-'
character and must contain only letters, numbers and '-'
characters. If you need to use a tag name or value that contains any other characters the string must be enclosed in quotes:
way[highway="~/.*ary/"] /* Matches against all ways with the highway tag set to"~/.*ary/" (not a regex) */ way[highway='a"b'] /* Matches against all ways with the highway tag set to 'a"b' */ way[highway="a\"b"] /* Matches against all ways with the highway tag set to 'a"b' */
You can also use unary tests to find out if a tag is set or not:
way[highway] /* Matches against all ways with the highway tag set */ way[!highway] /* Matches against all ways with the highway tag not set (or set to no/false) */ way.highway /* Matches against all ways with the highway class set */
To test more than one condition on a way, simply add more conditions after the type. To match against this rule, all conditions must be met.
node[place=city][population<50000] /* Matches against all nodes tagged place=city, whose population tag has a value of less than 50000 */ node[place=city][population>=50000] /* Matches against all nodes tagged place=city, whose population tag has a value of greater than or equal to 50000 */
You can restrict rules to only affecting elements when rendering is at certain zoom levels. If this is not specified, the rule applies to all zoom levels:
way|z12[population<50000] /* Matches against all ways whose population tag has a value of less than 50000, as long as the renderer is currently working at zoom level 12. */ way|z1-11[population>50000] /* Matches against all ways whose population tag has a value of less than 50000, as long as the renderer is currently working at any zoom level between 1 and 11. */
Theoretically this could be extended to cope with other units of measurement, e.g. way|s50000
for a 1:50,000 printed map.
Combining selectors
You can create an or condition by grouping selectors with a comma:
way[highway=primary], way[highway=trunk] /* Matches against all ways with a highway tag set to primary or trunk. */
By grouping selectors with no comma between them, you create a descendant selector. This matches against the latter selector, if and only if the object is found as a part of another object matching the first selector:
way[highway=footpath] node[barrier=gate] /* Matches against nodes with barrier=gate set which are on ways with highway=footpath set */ relation[type=route] way[highway] /* Matches against ways with the highway tag set which are within a relation with type=route set */
When combining selectors like this, pseudo-tags are added to elements to allow you to access extra data. Here the role pseudo tag is added to the way object to indicate its role within the parent relation:
relation[type=route] way[role=proposed] /* Matches ways within a relation with type=route set, whose role is set to proposed */
(This is not yet supported by Halcyon and Overpass Tubo. Another syntax is also proposed using ">" child-selectors between the relation element and the child element, without adding any pseudo-tag into the child element)
Syntactic notes
Whitespace in selectors is significant
You can be liberal in how we treat white space. The quantity of whitespace is irrelevant (1 space vs. three tabs, a CR and a space), but the presence or absence of whitespace in selectors is significant. This means that we can for example write the above selector as so:
relation[type=route] way[highway]
But, if we were to write it like this (with white space between the object type and the tests), it would not parse:
relation [type=route] way [highway]
Support of the different libraries for the selectors above
Selector-Part | Example | JOSM | pgm |
---|---|---|---|
Type | node , way , relation , area , line , canvas , *
|
+[1] | + |
Tag match | way[highway=primary]
|
+ | + |
Tag match (quoted) | way["highway"="primary"]
|
+ | + |
Tag regexp match | way[highway=~/.ary/]
|
+ | + |
Tag is set? | way[highway]
|
+ | + |
Tag is not set? | way[!highway]
|
+ | + |
Class is set? | way.highway
|
+ | + |
Numeric comparision | node[population<50000]
|
+ | + |
Zoom selector | node|z12-
|
+ | + |
Link selector, relation members | relation member
|
+ [2] | + |
Link selector, way nodes | way node
|
+ [2] | + |
Comment block style | /* ... */ | + | + |
Comment inline style | // ... | - | + |
Declarations
Declarations are written by enclosing a set of style specifiers in curly braces:
{ opacity: 0.5; color: rgb(1.0, 0.0, 0.0); }
See the #Vocabulary section for a listing of all available specifiers and which renderers support them.
Declarations may also be used to set the value of certain tags on an object:
{ set tag=value; } /* set a tag */ { set tag; } /* set a tag to 'yes' */
Finally, you can compute values using an eval instruction:
{ opacity: eval("tag('population')/100000"); } { set width_in_metres=eval("tag('lanes')*3"); }
Classes
You can set tags beginning with a full-stop to simulate classes:
{ set .minor_road; }
You can then use the .minor_road
test (as above) in the selector:
way.minor_road
This opens up possibilities when used with the @import
rule and a media type. You can have a set of rules for each medium (e.g. direct from an OSM database, or from the product of osm2pgsql), each of which set pseudo-classes. The main stylesheet is then applied to these styles:
@import url("osmtags.css") osmtags; @import url("osmpostgis.css") osmpostgis;
(The @import
rule is not yet supported by Halcyon.)
Pseudo-classes
Interactive clients can support :hover
and :active
pseudo-classes.
Support of the different libraries for the various declarations
Example | JOSM | pgm | |
---|---|---|---|
Property assignment | opacity: 0.5;
|
+ | + |
Set tag to value | set tag=value;
|
- | + |
Set tag to 'yes' | set tag;
|
-[1] | + |
Eval assignment | width: eval(2*3)
|
+ | + |
Set class | set .class;
|
+ | + |
@import statements |
@import url("tags.css");
|
- | + |
Pseudo-class from @import statements |
@import url("tags.css") osmtags;
|
- | - |
Pseudo-class :hover |
way:hover
|
- | - |
Pseudo-class :active |
way:active
|
- | - |
- ↑ it does not set the tag, but a class instead
Vocabulary
The following abbreviations will be used:
- OSPad: OpenStreetPad
- pgm: pgmapcss
Canvas properties
definition | example | Halcyon | Kothic | Ceyx | OSPad | alaCarte | JOSM | pgm | |
---|---|---|---|---|---|---|---|---|---|
antialiasing
|
How to antialias map: full (default), text (lines aren't smooth, text is), none (no antialiasing performed)
|
- | - | +* | - | - | - | - | |
fill-color
|
Colour of the canvas - either a 3-digit or 6-digit hex value or a CSS colour name or an rgb colour specifier | fill-color: lightgreen fill-color: #8F8 fill-color: #88FF88 fill-color: rgb(0.53, 1, 0.53)
|
- | + | + | + | + | + [1] | + |
Colour of the canvas with alpha component - an 8-digit hex value, or an rgba colour specifier (CSS level 3) (the alpha component normally combines with the transparency attribute when supported simultaneously). |
fill-color: #88FF8880 fill-color: rgba(0.53, 1, 0.53, 0.5)
|
? | ? | ? | ? | + | ? | + | |
fill-opacity
|
How transparent the fill is, from 0 (transparent) to 1 (opaque) | fill-opacity: 0.2
|
- | + | + | + | - [2] | - | - [3] |
fill-image
|
The URL (absolute or relative) of an image with which to fill the canvas | fill-image: url('fills/grass.png')
|
- | - | + | + | + | - | + |
Line properties
definition | example | Halcyon | Kothic | Ceyx | OSPad | alaCarte | JOSM | pgm | |
---|---|---|---|---|---|---|---|---|---|
z-index
|
Specifies a sublayer within an OSM layer | +[1] | + | + | + | + | + | + | |
width
|
The line width in pixels. | width: 5
|
+ | + | + | + | + | + | + |
color
|
Colour of the line - a 3-digit or 6-digit hex value, a CSS colour name or an rgb colour specifier | color: blue color: #00F color: #0000FF color: rgb(0.0, 0.0, 1.0)
|
+ | + | + | + | + | + | + |
Colour of the line with alpha component - an 8-digit hex value, or an rgba colour specifier (CSS level 3) (the alpha component normally combines with the transparency attribute when supported simultaneously). |
color: #0000FF80 color: rgba(0.0, 0.0, 1.0, 0.5)
|
? | ? | ? | ? | + | ? | ? | |
opacity
|
How transparent the line is, from 0 (transparent) to 1 (opaque) | opacity: 0.5
|
+ | + | + | + | - [2] | + | + |
dashes
|
An array of alternating on/off lengths | dashes: 2,2,4,2
|
+ | + | + | + | + | + | + |
image
|
The URL of an image to use for filling the line | - | + | - | + | + | - | + | |
linecap
|
The style for the end of the line: none (default), round or square
|
+ | + | + | + | + | + | + | |
linejoin
|
The style for line corners: round (default), miter or bevel
|
+ | + | + | + | + | + | + | |
fill-color
|
Colour in which to fill the area, if closed - hex value or CSS colour | fill-color: lightgreen
|
+ | + | + | + | + | + | + |
fill-opacity
|
How transparent the fill is, from 0 (transparent) to 1 (opaque) | fill-opacity: 0.2
|
+ | + | + | + | - [2] | + | + |
fill-image
|
The URL (absolute or relative) of an image with which to fill the area | fill-image: url('fills/grass.png')
|
+ | + | + | + | + | + | + |
casing-width
|
Width of the casing (border) of the line. For example, a 1 pixel wide casing on either side would be specified as casing-width:1 (change from MapCSS 0.1)
|
casing-width: 3
|
- | + | + | + | + | + | + |
casing-color
|
The colour (hex or CSS name) of the casing | + | + | + | + | + | + | + | |
casing-opacity
|
The transparency of the casing | + | + | + | + | - [2] | + | + | |
casing-dashes
|
An array of alternating on/off lengths | + | + | + | + | + | + | + | |
casing-linecap
|
Style for the end of the casing line (defaults to value of linecap )
|
- | + | + | + | + | + | + | |
casing-linejoin
|
The style for casing line corners (defaults to value of linejoin )
|
- | + | + | + | + | + | + | |
extrude
|
Height of extruded line. | extrude: eval('zmetric(tag("height"))')
|
- | + | - | - | - | - | - |
extrude-edge-color
|
Color of edges of 3D object (defaults to value of color )
|
- | + | - | - | - | - | - | |
extrude-edge-opacity
|
Opacity of extruded edges (defaults to value of opacity )
|
- | + | - | - | - | - | - | |
extrude-face-color
|
Color of extruded faces (defaults to value of fill-color )
|
- | + | - | - | - | - | - | |
extrude-face-opacity
|
How transparent extruded faces are (defaults to value of fill-opacity )
|
- | + | - | - | - | - | - |
Point/icon properties
These can apply to nodes or to areas (closed ways). In case of areas, it is up to the renderer to decide where to place the icon within the area.
definition | example | Halcyon | Kothic | Ceyx | OSPad | alaCarte | JOSM | pgm | |
---|---|---|---|---|---|---|---|---|---|
icon-image
|
URL (absolute or relative) of an image to use as an icon. | icon-image: url('icons/pharmacy.png')
|
+ | - | +* | + | + | +[1] | + |
icon-width
|
Either a unit specification or a scaling facor in %. If only one of icon-width and icon-height is given, a aspect-ratio-preserving scaling is applied in both directions.
|
icon-width: 25, icon-width: 70%
|
- | - | - | + | + | +[2] | - |
icon-height
|
Either a unit specification or a scaling facor in %. If only one of icon-width and icon-height is given, a aspect-ratio-preserving scaling is applied in both directions.
|
icon-height: 25; icon-height: 70%
|
- | - | - | + | + | +[2] | - |
icon-opacity
|
Opacity of the icon image | icon-opacity: 0.3
|
- | - | - | + | + | + | + |
- ↑ slightly different syntax than shown here, see JOSM/Help/Styles/Images
- ↑ 2.0 2.1 only absolute number of pixels, no percentage
Label properties
definition | example | Halcyon | Kothic | Ceyx | OSPad | alaCarte | JOSM | pgm | |
---|---|---|---|---|---|---|---|---|---|
font-family
|
Name of the font to use | font-family: DejaVu
|
+ | - | + | + | + | + | +[1] |
font-size
|
Size of the text (integer number) | font-size: 12
|
+ | + | + | + | + | + | + |
font-weight
|
bold or normal
|
font-weight: bold
|
+ | - | + | + | + | + | + |
font-style
|
italic or normal
|
font-style: italic
|
+ | - | + | + | + | + | + |
font-variant
|
normal (default) or small-caps (new in MapCSS 0.2)
|
font-style: small-caps
|
- | - | +* | - | - | - | - |
text-decoration
|
none or underline
|
text-decoration: underline
|
+ | - | + | + | - | - | - |
text-transform
|
none , uppercase , lowercase or capitalize
|
text-transform: uppercase
|
+ | - | + | + | - | - | + |
text-color
|
A hex value or CSS colour name | text-color: #07CF20
|
+ | + | + | + | + | + | + |
text-opacity
|
How transparent the text is, from 0 (transparent) to 1 (opaque) | text-opacity: 0.75
|
- | - | - | + | - [2] | + | + |
text-position
|
Whether the text follows the path of the way (line ) or is centred on the area (center )
|
text-position: center
|
+ | + | - | + | + | + | + |
text-offset
|
The vertical offset from the centre of the way or point. An offset of 5, on a line of width 3, would put the text "along" the line rather than within it. Similarly, an offset of 8, on a point with a 16-high icon, would put the text below the icon. | text-offset: -5
|
+ | - | + | + | + | + | + |
max-width
|
The maximum width of a text label for a point, after which it should wrap onto the next line | max-width: 30
|
+ | - | +* | + | - | - | + |
text
|
The tag key whose value is used for the label. For example, text: name would render the name tag
|
text: name
|
+ | + | + | + | + | + | + |
text-halo-color
|
The colour (hex or CSS) of the 'halo' or 'pull-out' used to make the text stand out from features underneath it | text-halo-color: white
|
+ | + | + | + | + | + | + |
text-halo-radius
|
The radius of the halo | text-halo-radius: 2
|
+ | + | + | + | + | + | + |
Shield properties
definition | example | Halcyon | Kothic | Ceyx | alaCarte | JOSM | pgm | |
---|---|---|---|---|---|---|---|---|
shield-color
|
Colour of the shield - either a hex value or a CSS colour name | shield-color: blue shield-color: #0000FF
|
- | - | - | + | - | - |
shield-opacity
|
How transparent the shield is, from 0 (transparent) to 1 (opaque) | shield-opacity: 0.5
|
- | - | - | + | - | + |
shield-frame-color
|
Color of shield's frame | - | - | - | + | - | - | |
shield-frame-width
|
Width of the frame. If 0, no frame is rendered | - | - | - | + | - | - | |
shield-casing-color
|
Color of shield's casing. | - | - | - | + | - | - | |
shield-casing-width
|
Width of shield's casing | - | - | - | + | - | - | |
shield-text
|
Text to render on shield. | - | - | - | + | - | + | |
shield-image
|
The URL (absolute or relative) of an image to use as a background for text. | fill-image: url('fills/grass.png')
|
- | - | - | + | - | + |
shield-shape
|
Shape of the shield. Can be rounded or rectangular
|
- | - | - | + | - | - |
Application of Styles
Rendered Objects
Only ways and nodes should be rendered. Relations should be used only for applying a different style to a way or node. For example, to colour a route red, you could use the following style:
relation[type=route] way[highway] { color: red; }
But not this style:
relation[type=route] { color: red; }
The one possible exception to this rule is multipolygon relations, which some renderers may interpret as area primitives.
Rendering Order
Objects should be rendered in the following order (from front to back):
- Objects with lower layer should always be rendered first.
- Within a layer, first all fills are rendered, then all casings, then all strokes, then all icons and labels.
- Within each of those categories, objects are ordered according to z-index.
- If all of the above are equal, the order is undefined.
Support of the different libraries for the rendering order
Description | JOSM | pgm |
---|---|---|
Use layer of objects for rendering order | - | + |
within a layer, render fills then casings then strokes then icons and labels | + | +[1] |
- ↑ pgmapcss ignores the layer for icons and labels and renders them on top of everything else.
Order of Application
A stylesheet describes how to determine the style for any given way or node. To determine the style you must step through rules in order determining whether they apply to any given way or note, if a later rule provides a contradictory style to an earlier one, the later style overwrites the old one. Note, this means it is not valid to process descendant selectors at the time that the parent is matched, as this can lead to the incorrect style being generated as shown below:
Take for example the following style sheet:
node { width: 2; color: blue; } way node { width: 2; color: red; }
Applied to the following objects (most details omitted for conciseness):
<way id="1"> <nd id="2" /> </way> <node id="2" />
The correct style here is for the way not to be rendered, and for the node to be rendered as a 2px wide red dot. If we were to process descendant selectors at the time the parent would match we would instead, process the way, and add the 2px wide red dot style to the node, then process the node and incorrectly override red with blue.