Skip to main content

MapCSS

The MapCSS style file consists of rules that apply to each object from top to bottom.

For example, a style:

node[natural=tree] {
icon-image: "tree.svg";
}

will display a picture of tree.svg for points where the tag natural has the value tree.

Each rule in the style has two parts: the filter node[natural=tree] and the drawing parameters icon-image: tree.svg;. The filter determines which objects the parameters apply to.

Rules can be nested and set parameters at each level.

|z16- {
icon-scale: 0.50;
icon-tint: @color_icon_tint;

[amenity=bicycle_parking] {
icon-image: "bike_parking.svg";
}
[amenity=bicycle_rental] {
icon-image: "bike_rental.svg";
}
[amenity=bicycle_repair_station] {
icon-image: "bike_service.svg";
}
}

These nested style rules keep the style concise and structured. You can define shared parameters once and avoid repeating them across multiple blocks.

This documentation describes the MapCSS dialect of GLMap. Some original drawing parameters are not implemented, but preprocessing macros, nested rules, and new functions for evaluated operations are available. The style of Guru Maps, an app built on GLMap, can be found in the repository.

New in GLMap 2.3

GLMap 2.2 rejects these features or treats them differently:

  • tag filters ^=, $=, and *=, and values in quotes; in GLMap 2.2, ~= checks for a substring, and in 2.3 for an item of a ; list;
  • linejoin, which GLMap 2.2 rejects with any value;
  • offset, per-relation, icon-fallback, text-letter-spacing, and the road shield parameters;
  • font-weight values medium and semibold, and font-size that changes with zoom;
  • the functions relationSlot, listItem, length, mapColor, and mapValue;
  • strings with escapes, such as "say \"hi\"", and ;, {, }, //, and @ inside quotes; a quoted value keeps its spaces. A malformed eval() is reported as a parse error.

Filters​

Let's consider a complex filter:

node,area|z14-[boundary=forest_compartment][ref]

It consists of:

  1. Type filter node,area. Use node for points, line for lines, area for polygons, or separate multiple types with commas. Use * if the type does not matter. canvas selects the map background.
  2. Zoom filter z14-. This specifies the zoom levels where the rule should trigger. You can define only the minimum zoom level z8-, only the maximum z-9, a single zoom level z12, or a range such as z3-9. Both ends of a range are included: z3-9 applies on zoom levels 3 through 9.
  3. Tag filters [boundary=forest_compartment][ref]. This keeps only objects with the boundary tag set to forest_compartment and any value for the ref tag. To keep objects that do not have the ref tag, use the filter [!ref].

Each part is optional. A zoom filter can stand on its own, as |z16- does in the example above.

Tag filters​

FilterKeeps objects where
[ref]the ref tag is present
[!ref]the ref tag is missing
[highway=primary]the value equals primary
[highway!=primary]the value is not primary
[population>100000]the value is greater; <, >=, and <= work the same way
[ele%100]the value is a multiple of 100
[ref^=E]the value starts with E
[name$=Street]the value ends with Street
[name*=Park]the value contains Park
[cuisine~=pizza]pizza is one of the values separated by ;, as in pizza;burger

The comparisons >, <, >=, and <= compare numbers when both values are numbers, and text otherwise.

Put a value in double quotes when it contains an apostrophe, a quote, or ], or when it starts or ends with a space:

[name="McDonald's"],
[ref^="B "] {
icon-image: "logo.svg";
}

Inside quotes, a backslash escapes a quote, and ;, {, }, //, and @ are ordinary characters: [name='McDonald\'s'], [website="https://example.com"].

Grouping filters​

Several filters can be separated by a comma:

node,area|z14-[boundary=forest_compartment][ref],
node,area|z14-[boundary=forestry_compartment][ref]

In this example, shared parameters can be moved to a higher level:

node,area|z14-[ref] {
[boundary=forest_compartment],
[boundary=forestry_compartment] {

}
}

Sometimes you need to combine several rules into a group. Wrap the filters in parentheses ( and ) to group them without immediately starting a rule block.

node,area|z14-[ref](
[boundary=forest_compartment],
[boundary=forestry_compartment]) {
}

A zoom filter can apply to a whole group:

|z11-([highway=trunk], [highway=primary]) {
shield-image: "shield.svg";
}

Relation filters​

relation[...] keeps objects that belong to a relation with the given tags. For example, the ways of hiking routes:

relation[type=route][route=hiking] {
line|z9- {
color: #0000ff80;
width: 2pt;
}
}

The relationTag function reads the tags of that relation, and per-relation draws a way once for each of its relations.

Preprocessing macros​

The preprocessor prepares the style for parsing by substituting values and enabling or disabling blocks based on the settings.

Substitution macros​

@import​

Inserts the content of the specified file:

@import "polygons.mapcss";

@{name}​

Inserts the value of a macro wherever it is used:

// Define color using
@color_ground: #EAE3D3;

// then use it as
canvas
{
fill-color: @color_ground;
}

A macro inserts its text as is, so it can hold part of an expression:

@route_band_width: zlinear(13, 5pt, 7pt, 9pt, 11pt);

line|z13- {
outline-width: eval(@route_band_width);
}

A macro whose value starts with eval( is calculated once, when the style loads.

Conditions​

@if​

Checks the parameter and includes or excludes a block from the style:

@if Theme == Dark
@import "colors_dark.mapcss";
@else // Default colors
@import "colors.mapcss";
@endif

A condition can compare a parameter with ==, !=, <, >, <=, or >=. A value is a single word of letters, digits, and _. A parameter without a comparison checks that the parameter is on; ! checks that it is off:

@if Food
// included when the Food option is on
@endif
@if !Food
// included when it is off
@endif

The app sets the parameters with GLMapStyleParser before parsing: setOptions(_:defaultValue:) on iOS and setOptions(Map, boolean) on Android. defaultValue answers @if Name for a parameter the app did not set. allOptions (Android getAllOptions()) lists the parameters that the style checks.

The default style uses these parameters:

  • Theme {Light, Dark} - app theme. In the dark theme, pure white #FFFFFF in icons turns dark grey; use #FEFEFE for white that should stay white.
  • Style {City, Outdoor} - City highlights what matters in a city, Outdoor draws monochrome roads and colored trails.
  • SubStyle {Hike, Bike, Ski, Car}
    • Hike and Bike color hiking and cycling trails and routes, Ski draws ski pistes
    • Car draws thick roads for car navigation
  • Groups of places: AllShops, Bar, Beauty, BuildingName, CityService, Culture, Education, Entertaiment, Financial, Food, Hotel, Medical, Postal, PublicTransport, Railway, Shop, Sport. 0 hides a group, 1 shows it.

@else​

Specifies a block of code to run when the first condition is false.

@elif​

Contains a condition to run if the previous conditions did not match.

@endif​

Closes the block of the current condition.

Comments​

// comments out the rest of the line, /* and */ enclose a comment of several lines.

Draw parameters​

These parameters control how objects are drawn on the map.

Parameter values​

Width​

Line thickness or width can be specified in pixels 1px, points 2pt, meters 3m, or calculated using an expression such as eval( zlinear( 13, 1px,1pt,max(2pt, 4m)) );. Read more about expressions in the expressions section. Always write the unit: a number without a unit is measured in internal map units.

Color​

Specify color using #RGB, #RRGGBB, #RRGGBBAA, or a named color from the CSS color list.

fill-color, casing-color, dashes-color, text-color, and font-stroke-color take only a color. color, icon-tint, outline-color, shield-color, and shield-text-color also take an expression.

Text and quotes​

Single and double quotes mean the same. Inside a string, a backslash escapes the next character: \" is a double quote, \' a single quote, and \\ a backslash. So "McDonald's", 'McDonald\'s', and "say \"hi\"" are all valid strings. The examples on this page use double quotes.

Inside quotes, ;, {, }, //, and @ are ordinary characters, not the end of a parameter, a block, a comment, or a macro:

node[website="https://example.com"] {
text: "[email protected]; open {daily}";
}

Inside eval(), put every text in quotes. A tag name made only of Latin letters and digits works without them, so tag(name) is the same as tag("name"), but a name with : or _ needs quotes: tag("addr:street"). Numbers with units, such as 2pt, and colors, such as #808080, go without quotes. In this example, the palette is one text, relationTag("colour") reads a tag, and #808080 is a color:

color: eval(mapColor("red:#E53935, green:#43A047, blue:#1E88E5", relationTag("colour"), #808080));

Outside eval(), a plain value needs no quotes: it is the text up to ;, so icon-image: tree.svg; and icon-image: "tree.svg"; are the same. A quoted value is taken exactly as written: text: " A "; keeps its spaces, and text: "none"; or text: "eval(x)"; show these words instead of hiding the text or calculating it. A value without quotes loses the spaces at its ends. @import takes a file name with or without quotes.

In tag filters, quote a value that contains an apostrophe, a quote, or ], or that starts or ends with a space: [name="McDonald's"], [ref^="B "]. Tag names in filters and values in @if conditions never take quotes.

A string ends on its line. A quote without a pair, such as the apostrophe in [name=McDonald's], stops the style from loading with the error Unclosed quote, shown with the text of that line.

Numbers and switches​

text-priority, shield-priority, icon-scale, icon-offset-x, icon-offset-y, and z-index take a plain number, not an expression. Switches such as text-allow-overlap take true or false.

Draw order parameters​

layer​

The layer number is needed to separate the different levels on the map.

// for any object with tag 'layer', set its 'layer' property to the value of the tag
*|z9-[layer] {
layer: eval(tag("layer"));
}

z-index​

z-index sets the drawing order of objects of the same type within a layer.

area|z10-[natural=wood] {
z-index: -3;
fill-color: @color_wood;
}

area[natural=oceanwater] {
z-index: -2;
fill-color: @color_water;
}

Polygon parameters​

fill-color​

Fill color for the polygon.

area|z5-[natural=water] {
fill-color: @color_water;
}

fill-image​

Fill image for the polygon.

// fill image of a military area displayed on top of other objects
area|z11- {
[landuse=military],
[military=danger_area] {
z-index: 2;
fill-image:"forbiddenArea.png";
}
}

fill-image supports inline style with base64 data.

// same as arrow.svg
fill-image: eval(data("PHN2ZyB3aWR0aD0iMTI4IiBoZWlnaHQ9IjgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTI2IDNoMTBWMWw1IDMtNSAzVjVIMjZ6IiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIG9wYWNpdHk9Ii40Ii8+PC9zdmc+Cg=="));

color and width for polygon​

Polygon border color and border width.

area|z15-[building] {
z-index: 3;
width: 1px;
color: @color_landuse_residential_stroke;
}

Line parameters​

color and width for line​

Line color and width.

line|z13-[natural=tree_row] {
width: 3pt;
color: @color_wood;
z-index: -10;
}

casing-color and casing-width​

Line casing color and casing width, used mostly for roads. The casing is added on each side of the line, so it needs width too.

line|z8-[highway=motorway],
line|z11-[highway=motorway_link] {
color: @color_motorway;
casing-color: @color_motorway_casing;
casing-width: 0.5pt;
}

dashes​

Indicates the lengths of painted and blank segments, in turn. Use up to 8 numbers with a sum less than 64. Each number is the length of a line segment in points.

line|z15-[highway=cycleway] {
dashes: 2,2,2,2;
dashes-color: @color_cycleway_dashes;
}

dashes-color and dashes-width​

Dash color and width used for trails, paths, stairs, and borders. Dashes are drawn only when dashes, dashes-color, and dashes-width are all set.

line|z15-[highway=steps] {
dashes-color: @color_footway_dashes;
dashes-width: eval( zlinear( 16, 3pt, 4pt ) );
}

linecap​

The shape of the line edges. Possible values: none, square, round.

linejoin​

Shape of line joints. Possible values: round, bevel.

outline-color and outline-width​

A second line drawn along a line or a polygon border, for example a translucent band under a route. Unlike the casing, the outline does not depend on width: outline-width is its full width.

A translucent outline marks a hiking route with a wide band, and the path stays visible through it:

relation[type=route][route=hiking] {
line|z13- {
outline-width: 7pt;
outline-color: #1E88E570; // translucent blue
}
}

offset​

Shifts the line sideways: positive values to the left of the way direction, negative ones to the right. The shift applies to the line, its casing, outline, and dashes. It is mostly used with per-relation, to draw several routes on one way next to each other; see the example below.

per-relation​

per-relation: true draws a way once for each relation it belongs to, with the parameters of that relation. Together with offset and the relationSlot function, it draws routes that share a way side by side:

relation[type=route][route=subway] {
line|z11- {
per-relation: true;
width: 2pt;
offset: eval(relationSlot() * 2pt);
color: eval(mapColor("red:#E53935, green:#43A047, blue:#1E88E5", relationTag("colour"), #808080));
}
}

Text parameters​

text​

The text to draw next to a point or centered on a line or polygon.

node,area|z2-8[place=country] {
text: eval(locTag("name"));
// other params skipped
}

font-weight​

Font weight. Possible values: lighter, light, normal, medium, semibold, bold, bolder.

node,area|z9-[place=town],
node,area|z10-[place=village],
node,area|z12-[place=hamlet] {
font-weight:light;
font-size:11;
font-stroke-width:2px;
font-stroke-color: @color_label_stroke;
}

font-style​

normal or italic. For bold text, use font-weight.

Maps traditionally set water names in italics, which tells them apart from the names of places:

line|z15-[waterway=river] {
text: eval(locTag("name"));
font-style: italic;
text-color: #0F4F80;
}

font-size​

Font size in points. The size can change with zoom; write plain numbers in zlinear, and the text grows smoothly:

node|z10-[place=city] {
font-size: eval(zlinear(10, 13, 14, 15));
}

font-stroke-width and font-stroke-color​

Font stroke width and color. The width can be an expression.

text-color​

Text color.

node,area|z2-8[place=country] {
text: eval(locTag("name"));
text-color: @color_name_text;
text-priority: 10;
// other tags skipped
}

text-letter-spacing​

Extra space between letters, in fractions of the font size: 0.1 adds a tenth of the font size. It can change with zoom through zlinear.

Letter-spaced capitals set the names of regions apart from the names of cities. As the map zooms in, the letters move apart, and the name spans more of its region:

node|z6-[place=state] {
text: eval(uppercase(locTag("name")));
text-letter-spacing: eval(zlinear(6, 0.1, 0.18, 0.25));
}

text-priority​

When labels compete for space, text-priority determines which text appears first.

text-allow-overlap​

Allows labels to overlap so multiple pieces of text can occupy the same area.

text-big-padding​

Reserves twice as much padding around the text. Use this for major settlements (city, town, suburb, etc.) to prevent labels from obscuring the map.

Icon parameters​

icon-image​

The name of the image to display at a point or in the center of a polygon.

node,area|z17-[amenity=library] {
icon-image: "library.svg";
icon-scale: 0.37;
icon-tint: @color_icon_tint;
}

icon-image supports inline style with base64 data.

// same as poi_circle_small.svg
icon-image: eval(data("PHN2ZyB3aWR0aD0iOCIgaGVpZ2h0PSI4IiB2aWV3Qm94PSIwIDAgOCA4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNNyA0QzcgNS42NTY4NSA1LjY1Njg1IDcgNCA3QzIuMzQzMTUgNyAxIDUuNjU2ODUgMSA0QzEgMi4zNDMxNSAyLjM0MzE1IDEgNCAxQzUuNjU2ODUgMSA3IDIuMzQzMTUgNyA0WiIgZmlsbD0iIzgwODA4MCIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTggNEM4IDYuMjA5MTQgNi4yMDkxNCA4IDQgOEMxLjc5MDg2IDggMCA2LjIwOTE0IDAgNEMwIDEuNzkwODYgMS43OTA4NiAwIDQgMEM2LjIwOTE0IDAgOCAxLjc5MDg2IDggNFpNNCA3QzUuNjU2ODUgNyA3IDUuNjU2ODUgNyA0QzcgMi4zNDMxNSA1LjY1Njg1IDEgNCAxQzIuMzQzMTUgMSAxIDIuMzQzMTUgMSA0QzEgNS42NTY4NSAyLjM0MzE1IDcgNCA3WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg=="));

icon-fallback​

A smaller image shown instead of icon-image when the icon has no room on the map, for example a dot in place of a shop logo. On a busy street, the place stays on the map, and its logo appears when you zoom in:

node|z16-[brand:wikidata=Q38076] {
icon-image: "logo_mcdonalds.svg";
icon-fallback: "poi_circle_small.svg";
}

icon-scale​

Scales the image so it appears larger or smaller than its original size.

icon-tint​

icon-tint recolors the image. #FFF stays white, #CCC becomes a lighter version of the tint color, #888 matches the tint color, #444 becomes a darker version, and #000 stays black. Colors blend smoothly between these points.

icon-offset-x and icon-offset-y​

Offsets the image relative to its anchor point. By default, the image is centered on the map point. For example, icon-offset-y: 0; lines up the bottom of a pin icon with the target point. Values range from 0 to 1, where 0 represents the bottom or left edge and 1 represents the top or right edge.

image-allow-overlap​

Allows icons to overlap.

Road shield parameters​

A shield shows a road number on a plate along the road. It is drawn on lines when both shield-image and shield-text are set.

Shields in the colors of the road signs of a country tell main roads from local ones at a glance. In Belarus, for example, the numbers of main roads are white on red, and those of local roads are dark on white:

line|z11-[highway=primary][ref] {
shield-image: "shield.svg";
shield-text: eval(listItem(tag("ref"), 0)); // the first number of "М1;E30"
shield-color: #005387;
[ref^=М], [ref^=Р] {
shield-color: #C1121C;
}
[ref^=Н] {
shield-image: "shield_plate.svg";
shield-color: #FEFEFE;
}
}

shield-image​

The plate image.

shield-text​

The text on the plate, usually the road number.

shield-color​

Tints the plate image like icon-tint.

shield-text-color​

Text color. Without it, the text is dark on light plates and white on dark ones.

shield-priority​

Where the shield stands among the labels that compete for space, as text-priority does for text. Without it, the shield takes the text-priority of its line, the same as the road name. Any two shields stay at least 90 pt apart, and the same number repeats no closer than 320 pt, so the first placed win where roads are dense.

The default style places shields after every name and point of interest, main roads first. In a city the names fill the map and few shields remain; between places, where little else competes, every road keeps its number:

line|z9-[highway=motorway] { shield-priority: -20; }
line|z11-[highway=trunk] { shield-priority: -21; }
line|z11-[highway=primary] { shield-priority: -22; }
line|z12-[highway=secondary] { shield-priority: -23; }

shield-fit​

How the plate fits the text. stretch (the default) stretches the middle of the image to the text width and keeps its ends. fixed keeps the image as drawn and only widens it when the text does not fit; use it for plates with a shape, such as US road signs. To keep the image's own colors, set shield-color: #00000000.

California routes are numbered in a circle for two digits and in an ellipse for three. The number follows the network prefix, as in CA 1:

line|z12-[highway=secondary][ref^="CA "] {
shield-fit: fixed;
shield-text: eval(listItem(listItem(tag("ref"), 0), 1, " "));
shield-image: eval(cond(length(listItem(listItem(tag("ref"), 0), 1, " ")) == 3,
"shield_state_3.svg", "shield_state_2.svg"));
shield-color: #00000000;
shield-text-color: #1E1E1E;
}

Details parameters​

Use details-text and details-description to set the name and description for GeoJSON objects. details-text appears as the title, and details-description appears as the description. If both values are empty, taps on the object are ignored. You can use expressions in these parameters.

* {
details-text: eval(tag("name"));
details-description: eval(tag("description"));
}

details-enabled: false marks an object that has nothing to tell: updateDrawAttributesOfVectorObject of GLMapViewState returns false for it, so the app can ignore taps on it. A relation with it, such as a route, is also left out of the relations found at a tapped way:

node|z15-[amenity=hunting_stand] {
icon-image: "poi_hunting_stand.svg";
details-enabled: false;
}

Expressions​

Filters and drawing parameters can depend on external factors, be calculated with formulas, or vary by zoom level.

Calculated values must always start with the eval() function.

Functions​

The following functions can be used inside:

min and max​

Returns the minimum or maximum value from the parameters. The number of parameters is not limited.

any​

Returns the first non-null value from the parameters. You can pass any number of arguments.

data​

Decodes base64 string. Can be used for icon-image and fill-image.

tag​

Returns the value of the specified tag, or null if it is missing.

locTag​

Returns a localized tag value based on the languages in GLMapLocaleSettings. For example, if the preferred languages are English, Russian, and the native language, locTag("name") checks name:en, then name:ru, then name, and uses the first match. GLMap maps contain names in ar, be, cs, da, de, en, es, fr, it, ja, ko, nl, pl, pt, ru, sv, uk, zh. Use locTag() for these languages; for others, call tag() explicitly.

relationTag​

Returns the value of a tag of the relation the object belongs to, such as the color of a route: relationTag("colour"). With per-relation, it reads the relation being drawn.

relationSlot​

With per-relation, returns the position of the relation among the routes drawn on the way, centered on 0: -0.5 and 0.5 for two routes, -1, 0, and 1 for three. Multiply it by the line width to place the routes side by side with offset, as in the per-relation example.

listItem​

Returns an item of a list, counting from 0. The items are separated by ; or by the character in the third parameter.

shield-text: eval(listItem(tag("ref"), 0)); // "E 50" for "E 50;E 65"
color: eval(listItem(tag("osmc:symbol"), 0, ":")); // "red" for "red:white:red_bar"

length​

Returns the number of characters in the text, for example to choose a wider shield for a three-digit road number, as in the shield-fit example.

uppercase, lowercase, and capitalize​

Changes the case of the text. The optional second parameter is a language for its case rules, such as uppercase(locTag("name"), "el").

mapColor​

Picks a color from a palette. The first parameter is the palette, "name:color, name:color, ..."; the second is the value to look up; the optional third is the result when the value is empty. A name from the palette, in any letter case, returns its color; any other color returns the closest palette color.

color: eval(mapColor("red:#E53935, green:#43A047, blue:#1E88E5", relationTag("colour"), #808080));

mapValue​

Picks a value from a table by its key, so one rule can replace a rule for every tag value. The first parameter is the table, "key:value, key:value, ..."; the second is the key to look up, in any letter case; the optional third is the result when the key is empty or not in the table. Without it, such a key gives null. The last : of an entry ends its key, so a key may contain :, but a value may contain neither : nor ,.

@worship_icons: "christian:poi_worship_christian.svg, muslim:poi_worship_muslim.svg, jewish:poi_worship_jewish.svg";

node|z16-[amenity=place_of_worship] {
icon-image: eval(mapValue(@worship_icons, tag("religion"), "poi_worship.svg"));
}

allIconNames lists the icons of such a table.

formatElevation​

Formats a height in meters as 1234 m or as feet, following the unit system in GLMapLocaleSettings. A peak is labeled with its name and height, or with the height alone when it has no name:

node|z13-[natural=peak] {
text: eval(cond(locTag("name"),
cond(tag("ele"), locTag("name") . " " . formatElevation(tag("ele")), locTag("name")),
formatElevation(tag("ele"))));
}

cond​

Evaluates a logical expression. If the first argument is true, the function returns the second argument; otherwise it returns the third.

// check addr:housenumber
text: eval( cond( tag("addr:housenumber"),
// if there is addr:housenumber, then concatenate next condition
tag("addr:housenumber") . cond( any( locTag("name"), tag("addr:housename") ),
// if there is name or addr:housename - put them between ()
" (" . any( locTag("name"), tag("addr:housename") ) . ")",
// empty line otherwise
""),
// if there is no housenumber just use any from name or addr:housename
any( locTag("name"), tag("addr:housename") ) ) );

boolean​

Converts the value to a Boolean. If the value is missing or equals 0, No, Off, or False (case-insensitive) the function returns false; otherwise it returns true.

zlinear​

Smoothly changes the value as the zoom changes. The first parameter is the initial zoom level, followed by the values for each subsequent zoom level starting from that point.

line|z13-[highway=residential] {
width: eval( zlinear( 13, 1px, 1pt, max(2pt, 4m) ) );
color: @color_small_road;
linecap: round;
}

In this example, the road width is 1 pixel at zoom level 13, 1 point at zoom level 14, and the greater of 2 points or 4 meters at zoom level 15 and above.

metric​

Converts the numeric parameter to meters.

line|z12-[highway=tertiary] {
width: eval( zlinear( 12, 1px, 1pt, 1pt, max(3pt, metric(any(tag("lanes"),2)*2)) ));
}

In this example, the number of lanes is multiplied by 2, giving the distance in meters. max(3pt, metric()) returns whichever is larger: the width in meters or the width in points.

Expression operators​

Expressions support the mathematical operators +, -, *, /, the string concatenation operator ., the equality operator ==, and ~=, which checks that the text contains a substring.