-
Notifications
You must be signed in to change notification settings - Fork 68
CartoCSS notes
These notes are intended to clarify how Carto Mobile SDK implements the CartoCSS styling language. The notes assume good understanding of the CartoCSS specification and cover various aspects specific to the SDK.
SDK supports the following parameters from the CartoCSS specification:
Symbolizer | Parameter |
---|---|
line | line-color |
line-opacity | |
line-width | |
line-dasharray | |
line-join | |
line-cap | |
line-offset | |
line-geometry-transform | |
line-comp-op | |
line-pattern | line-pattern-file |
line-pattern-fill | |
line-pattern-opacity | |
line-pattern-offset | |
line-pattern-geometry-transform | |
line-pattern-comp-op | |
polygon | polygon-fill |
polygon-opacity | |
polygon-geometry-transform | |
polygon-comp-op | |
polygon-pattern | polygon-pattern-file |
polygon-pattern-fill | |
polygon-pattern-opacity | |
polygon-pattern-geometry-transform | |
polygon-pattern-comp-op | |
point | point-file |
point-opacity | |
point-allow-overlap | |
point-ignore-placement | |
point-transform | |
point-comp-op | |
text | text-name |
text-face-name | |
text-placement | |
text-size | |
text-spacing | |
text-fill | |
text-opacity | |
text-halo-fill | |
text-halo-opacity | |
text-halo-radius | |
text-halo-rasterizer | |
text-allow-overlap | |
text-min-distance | |
text-transform | |
text-orientation | |
text-dx | |
text-dy | |
text-avoid-edges | |
text-wrap-width | |
text-wrap-before | |
text-wrap-character | |
text-character-spacing | |
text-line-spacing | |
text-horizontal-alignment | |
text-vertical-alignment | |
text-comp-op | |
text-clip | |
text-placement-priority | |
shield | shield-name |
shield-face-name | |
shield-file | |
shield-dx | |
shield-dy | |
shield-unlock-image | |
shield-placement | |
shield-size | |
shield-spacing | |
shield-fill | |
shield-text-opacity | |
shield-halo-fill | |
shield-halo-opacity | |
shield-halo-radius | |
shield-halo-rasterizer | |
shield-allow-overlap | |
shield-min-distance | |
shield-text-transform | |
shield-orientation | |
shield-text-dx | |
shield-text-dy | |
shield-avoid-edges | |
shield-wrap-width | |
shield-wrap-before | |
shield-wrap-character | |
shield-character-spacing | |
shield-line-spacing | |
shield-horizontal-alignment | |
shield-vertical-alignment | |
shield-comp-op | |
shield-clip | |
shield-placement-priority | |
marker | marker-file |
marker-placement | |
marker-type | |
marker-opacity | |
marker-color | |
marker-fill | |
marker-fill-opacity | |
marker-width | |
marker-height | |
marker-line-color | |
marker-line-opacity | |
marker-line-width | |
marker-spacing | |
marker-allow-overlap | |
marker-ignore-placement | |
marker-transform | |
marker-comp-op | |
marker-clip | |
marker-placement-priority | |
building | building-fill |
building-fill-opacity | |
building-height | |
building-min-height | |
building-geometry-transform |
In addition, SDK support layer-level 'opacity' and limited set of 'comp-op' options. The following table lists the supported 'comp-op' values:
Comp-op |
---|
src |
src-over |
src-in |
src-atop |
dst |
dst-over |
dst-in |
dst-atop |
clear,zero |
plus |
minus |
multiply |
screen |
darken |
lighten |
Most of the CartoCSS parameters are evaluated during tile loading ('loading domain'), but some parameters are evaluated during each frame ('rendering domain').
A typical example of view-level expression includes [view::zoom]
parameter, for example:
line-width: [view::zoom] * 0.2;
The list of all parameters that are evaluated in rendering domain:
Symbolizer | Parameter |
---|---|
line | line-color |
line-opacity | |
line-width | |
line-pattern | line-pattern-fill |
line-pattern-opacity | |
polygon | polygon-fill |
polygon-opacity | |
polygon-pattern | polygon-pattern-fill |
polygon-pattern-opacity | |
point | point-opacity |
marker | marker-width |
marker-height | |
marker-color | |
marker-opacity | |
text | text-size |
text-fill | |
text-opacity | |
text-halo-fill | |
text-halo-opacity | |
text-halo-radius | |
shield | shield-size |
shield-fill | |
shield-text-opacity | |
shield-halo-fill | |
shield-halo-opacity | |
shield-halo-radius | |
building | building-fill |
building-fill-opacity |
When a view-level expression is used for a parameter not in this table, its value is replaced with a reasonable default and the expression is evaluated during tile loading time.
For example, [view::zoom]
is replaced with [zoom] + 0.5
. This gives forward compatibility, if more parameters will be converted to view-level parameters.
Nutiparameters are typed parameters that can be controlled from the application. This allows certain aspects of the styles to be controlled by the application. Typical examples include showing certain POIs, selecting map language and so on. Nutiparameters can be accessed from the style by using 'nuti::' prefix. For example:
line-color: [nuti::color];
Here parameter 'color' is defined by the application.
SDK supports dynamic field names. Dynamic field names are specified using an expression enclosed in square or curly brackets. For example:
text-name: [name_[nuti::lang]];
Here the field used for text names depends on the nutiparameter called 'lang'. If its value is 'en', then this expression is equivalent to [name_en]
.
SDK supports following operators in parameter values (in the order of precedence):
Operator |
---|
unary -, unary ! |
*, / |
+, - |
=~, =, !=, <=, <, >=, > |
&&, || |
?: |
For example, the following expression can be used for language-selection based on nutiparameter named 'lang':
text-name: [name_[nuti::lang]] ? [name_[nuti::lang]] : [name];
In addition to the operators listed above, the following functions are supported:
Function |
---|
exp |
log |
pow |
step |
linear |
cubic |
length |
uppercase |
lowercase |
capitalize |
concat |
replace |
match |
SDK contains three special functions for key/value interpolation called step
, linear
and cubic
. The first one provides 'constant' interpolation, or simply a table lookup, the second one provides linear interpolation between two nearest key values and the last one provides cubic interpolation with 2nd order continuity. The most useful is linear interpolation. For example:
line-width: linear([x], (2, 1), (3, 2), (5, 8));
The following table demonstrates the values this expression produces depending on the value of [x]
:
x | Value |
---|---|
<=2 | 1 |
2.5 | 1.5 |
3 | 2 |
4 | 5 |
>=5 | 8 |
Starting from SDK 4.4.2, SDK supports when
selectors. This allows using any expression when filtering features, for example:
when (([class]='minor' || [class]=[subclass]) && [name]='Lane') {
line-color: #fff;
}
It is recommended to use this feature sparingly, as when
selectors may cause combinatorial explosion in the number of rules produced.
SDK supports global options for defining map pole colors when using globe view:
Map {
south-pole-color: #fff;
north-pole-color: #00f;
}
SDK has special logic for labels (texts, shields, markers) and tries to keep them stationary while tiles change. In order to do so, labels need to be uniquely identified. By default SDK uses a combination of tile-level feature id and text/file name of the label. Starting from SDK 4.2, SDK has option to specify custom expression/key for the id via parameters text-feature-id
, shield-feature-id
and marker-feature-id
. For example:
text-feature-id: [class] + '\n' + [name];
Starting from SDK 4.3, if 'feature id' expression evaluates to null, 0 or empty string, a unique id will be autogenerated.
Carto Mobile SDK uses 'from scratch' implementation of the CartoCSS styling language and interprets few aspects of the styling differently compared to the MapBox Studio Classic (shorthand MBSC).
The following line is interpreted differently in the SDK vs MBSC:
line-width: mix(#fff, #000, 50);
While the following line works in both implementations:
line-width: mix(#fff, #000, 50%);
MBSC interprets %-sign as an optional 'unit' while SDK treats this as a multiplicator with value of 1/100. The interpretation of SDK is consistent across all values and functions while the MBSC interpretation depends on the context and does not work everywhere. It is recommended to always add explicit % suffix to the functions that mix or modify colors. This makes the expressions compatible with both SDK and MBSC.
The trailing comma after '#layer0' in the following example causes different interpretation of the style in the SDK vs MBSC:
#layer0, {
line-width: 1;
}
MBSC ignores the trailing command while SDK interprets the command as a separator and assumes that the rules applies to elements of layer named 'layer0' and to all other elements. A simple fix is to remove all trailing commas from selectors to make the behavior consistent.
The following expression results in different output in SDK vs MBSC:
marker-width: ([scalerank]-1)*5;
SDK handles this as expected while MBSC provides very strange result. This seems to be a limitation in MBSC CartoCSS parser or CartoCSS -> MapnikXML translator. If compatibility
between SDK and MBSC is required, complex expressions involving fields and pseudo-fields like [zoom]
should be avoided.
SDK supports two separate rendering modes for points/markers/texts: 'label' mode and 'simple' mode. By default the mode is controlled by two parameters: 'allow-overlap' and 'clip'. If 'clip' is not defined, then 'allow-overlap' is used implicitly as its value. If 'clip' is set to false (explicitly or implicitly via 'allow-overlap'), then 'label' rendering mode is used. Otherwise 'simple' mode is used.
Label rendering mode does no clipping at tile boundaries and supports overlap detection and line geometry but is quite costly both memory wise and performance wise. It does not scale over 10,000 features. Label mode also requires unique feature ids to work properly. Labels are never clipped at tile borders.
Simple mode does not support overlap detection nor other label features but is very low cost and scales to 100,000 features and above. Simple mode features are always clipped at tile borders. Also simple mode features ignore 'placement' parameter and the features always rotate with the map.
SDK uses OpenGL as a rendering backend and few features can be unexpectedly expensive due to technical reasons.
The following example uses 'polygon-pattern' and 'polygon' symbolizers:
#layer {
polygon-pattern-file: url('pattern.png');
polygon-fill: #fff;
}
If the layer contains many polygons (over a thousand) this results in lots of OpenGL draw calls and will most likely drop the rendering rate to a 1 frame per second. There are few special cases that are optimized and are not affected by this: SDK supports batching 'line' and 'line-pattern' symbolizers and 'line' and 'polygon' symbolizers.
Both layer-level 'comp-op' and 'opacity' require drawing to a separate frame buffer and full viewport blending, thus making these operations very expensive especially on high-resolution devices. If possible, element-level 'comp-op' and 'opacity' should be used ('polygon-opacity', for example).