CartoSym 2.0 draft: judgement calls, rendered
Where the spec text does not settle how a renderer should behave, the draft requirements in PR #133 make a choice. Each card renders the same small map with the draft's choice and the alternatives. Because the draft renderer implements the draft itself, it can show what each choice looks like, but not whether that choice is the intended one: that is for the SWG to decide. Comparing with an independent renderer shows whether others read the requirements the same way, so each card links its style, data and view settings (all in kit.zip), and the playground lets you edit a style and see the result.
All 134 requirements and how each is checked → · Playground: edit a style and see it rendered →
draft the draft's choice. open in draft the draft
does not settle the question; the variant labelled "draft renderer's choice" is the proposed resolution. Requirement text from branch requirements-placeholders at 0405c09.
Decision matrix#
Proposed resolutions from the PR author, for discussion at the review.
| Question | Draft says | Proposed resolution | Status in PR #133 | Question for the SWG |
|---|---|---|---|---|
| Symbolizer opacity: whole symbol or each part | whole symbol (group opacity) | Keep as drafted. | unchanged | |
| Casing beneath or above the stroke | casing beneath the stroke | Keep as drafted. | unchanged | |
| Casing at crossings | all casings with the same zOrder beneath the strokes | Done, as Jérôme answered and as the painter's algorithm in clause 7 loops (casing passes inside each zOrder). | resolved, #147 | |
| What casing width means | extends beyond each side of the stroke | Done, as Jérôme answered (total = stroke + 2 × casing). | resolved, #147 | |
| Font outline beneath or above the glyphs | outline beneath the glyphs | Keep as drafted (as SE Halo). | unchanged | |
| Which zOrder is drawn on top | higher zOrder on top | Keep as drafted. | unchanged | |
| Hatch angle direction | clockwise (table) | Probably counter-clockwise from 0 pointing right, as other angles drawn on the map (Jérôme); check graphics toolkits first. | TODO(review), #146 | What do Win32 GDI, X and newer toolkits do? (@ebocher, @oertz, @maxcollombin) |
| Units of dashPattern | pixels unless uom-qualified | Done, as Jérôme answered (as SE). | resolved | |
| Default hot spot of an Image | centre (0.5, 0.5) | Keep as drafted (Jérôme confirmed; same as libCartoSym). | resolved; tint and blackTint added, alphaThreshold for picking (permission for the alpha test), #145 | |
| The default marker | a single default Dot (white, 10 px) | Keep, as intended (Jérôme); clearing it (marker: null) is now in the NOTE. Fix the schema (anyOf in graphicArray plus a marker change, #143). Done: Dot has size and color, the same values as its stroke's width and color, always a filled circle; CS-JSON forbids setting both in one object (#144). | NOTE + TODO(review), #143; resolved, #144 | Schema change OK? |
| What a stipple or dot-pattern dot looks like | not stated | Add dot size and color properties; until then, small dots in the fill color. | TODO(review), #84 | Should stipple/dot-pattern dots have their own size and color? |
| Which label priority wins | higher value wins | Keep as drafted (as QGIS and GeoServer, and as zOrder); libCartoSym's engine currently places lower values first. | TODO(review), #148 | Higher wins, or rename the property if lower should come first? |
| Units of label minSpacing | pixels unless uom-qualified | Done, as Jérôme answered. | resolved | |
| Labels on top of everything | separate pass on top | Keep as drafted. | unchanged | |
| Example 5: colorMap between and beyond entries | interpolate (method open), exact at entries, clamp outside | Done, as Jérôme answered; libCartoSym interpolates quadratically. SHOULD stay between the two closest entries; stepped classes via conditional expressions (Jérôme). | recommendation + NOTE, #149 | |
| Direction of coverage gamma | c ^ gamma (darkens above 1) | Remove from coverage (not in the model); if kept, use SE's direction. | TODO(review), #86 | |
| Example 6: scaling colorChannels | 0..1; example 6 scales the bands | Done: as Jérôme's renderer, the style scales the bands (÷ 2500 in example 6). | resolved; default: TODO(review), #150 | The default colorChannels (fields[0..2]) is unscaled too: keep it? |
| Example 7: NDVI with integer bands | real quotient | Keep as drafted (confirmed by Jérôme, as CQL2); libCartoSym should change (it returns 0 for example 7). | unchanged | Open the libCartoSym issue? |
| Example 8: what hillShading factor means | multiplies the horizontal components of the normal | Done, as Jérôme's renderer, including the sample distance and the shade without a colorMap. | resolved | |
| Example 9: shading intensity mapped to color and opacity | dot product, 0 (full shade) to 1 (no shade) | Done, as Jérôme described. | resolved | |
| Direction of the sun azimuth | clockwise from north, a compass bearing | Done, as Jérôme answered (as GDAL). | resolved | |
| Gradient angle convention | degrees, 0 pointing right, turning counter-clockwise | Done, as Jérôme answered. | resolved | |
| Gradient position and size: per feature or per map | pixels unless uom-qualified; frame not stated (Part 2) | Wait until two or three renderers can be compared (Jérôme). | TODO(review), #153 | |
| notLike, notContains, notStartsWith, notEndsWith | separate operations | Encode as not applied to the positive form (as CQL2); no schema change. | TODO(review), #140 | |
| caseInsensitize / accentInsensitize vs casei / accenti | long names (model); short in the schema | Use the CQL2 names casei and accenti. | TODO(review), #140 | |
What counts as an accent (Ø) | “removes accents” | Reference CQL2 Requirement 9. Either remove nonspacing marks only (Ø stays, as this renderer does), or also fold with a named one-to-one table (Ø → O, as libCartoSym does). | TODO(review), #152 | Which of the two? |
| Right-hand identifiers vs the CSCSS examples | any expression on the right-hand side | Done: where an enumeration is expected, a bare word is an enumeration value (clause 17); a NOTE now points there. | resolved, #151 | |
| center vs centerLine | center (UML) | Decide one name. | TODO(review), #120 | Which name is intended? |
| Schema: colors from expressions, empty arrays, and clearing the marker | (schema on main) | Make the oneOf branches exclusive (e.g., require r, g, b in the color object). For the marker: anyOf in graphicArray, and marker accepting null and an object without elements (tested). | not in PR (schema), #140, #143 | |
| format string syntax | C printf conversion specifiers (ANSI C) | Done, as Jérôme answered. | resolved | |
| intDiv in libCartoSym 0.0.1 | truncating integer division | Fix in libCartoSym: cs-canif crashes on 7 div 2. | n/a (implementation), ecere/libCartoSym#5 |
Part 1: vector styling
Symbolizer opacity: whole symbol or each part#
What the draft says
/req/core/opacity- An Encoding SHALL support defining an
opacitySymbolizer property as a float between 0 (fully transparent) and 1 (fully opaque). - A Renderer SHALL support portraying a feature or coverage cell with the opacity specified by the resolved
opacityproperty, applied to the portrayal as a whole.
With opacity: 0.5, is the symbol made translucent as a whole, or are fill and stroke made translucent separately? Separately, the stroke and fill overlap shows through.
The draft says the opacity applies to the portrayal as a whole, as SVG and CSS group opacity do. Applied to fill and stroke separately, the overlap shows through.
Render it yourself: style.cs.json · Background.geojson · Parcels.geojson · view.json↑ Matrix
Casing beneath or above the stroke#
What the draft says
/req/casing/casing- An Encoding SHALL support defining a
casingStroke property as a StrokeStyling object. - A Renderer SHALL support drawing the casing of a stroke along the same geometry as the stroke, using the StrokeStyling specified by the
casingproperty, beneath the main stroke. - A Renderer SHALL support drawing the casing so that it extends the
widthof the casing beyond each side of the main stroke, for a total width of the main stroke'swidthplus twice the casing'swidth. - A Renderer SHALL support drawing, within a single visualization pass, the casings of all features with the same
zOrderbeneath the main strokes of all those features, so that the casings of crossing and joining lines merge.
/req/casing/center- An Encoding SHALL support defining a
centerStroke property as a StrokeStyling object. - A Renderer SHALL support drawing the center line of a stroke along the same geometry as the stroke, using the StrokeStyling specified by the
centerproperty, on top of the main stroke.
A road with a 14 px yellow stroke, a red casing extending 4 px on each side at 60 % opacity (so what lies under it shows through) and a thin white centre line. Is the casing drawn beneath the stroke (an outline) or on top of it?
The draft says the casing is drawn beneath the main stroke. libCartoSym models casing and center as StrokeStyling, like the draft.
Render it yourself: style.cs.json · Roads.geojson · view.json↑ Matrix
Casing at crossings#
What the draft says
/req/casing/casing- An Encoding SHALL support defining a
casingStroke property as a StrokeStyling object. - A Renderer SHALL support drawing the casing of a stroke along the same geometry as the stroke, using the StrokeStyling specified by the
casingproperty, beneath the main stroke. - A Renderer SHALL support drawing the casing so that it extends the
widthof the casing beyond each side of the main stroke, for a total width of the main stroke'swidthplus twice the casing'swidth. - A Renderer SHALL support drawing, within a single visualization pass, the casings of all features with the same
zOrderbeneath the main strokes of all those features, so that the casings of crossing and joining lines merge.
Two cased roads crossing. Does each road draw its casing together with its own stroke, or are all casings drawn before any stroke, so the crossing merges?
The draft now says that within a visualization pass, the casings of all features with the same zOrder are drawn beneath their main strokes (Jérôme, #147; also the painter's algorithm in clause 7, which loops over casing passes inside each zOrder). A feature with a higher zOrder, such as a bridge, has its casing drawn over the roads below it. Drawing each feature in turn is possible with visualization or feature passes.
Sources: Jérôme, #147: equivalent to drawing all casings first
Render it yourself: style.cs.json · Roads.geojson · view.json↑ Matrix
What casing width means#
What the draft says
/req/casing/casing- An Encoding SHALL support defining a
casingStroke property as a StrokeStyling object. - A Renderer SHALL support drawing the casing of a stroke along the same geometry as the stroke, using the StrokeStyling specified by the
casingproperty, beneath the main stroke. - A Renderer SHALL support drawing the casing so that it extends the
widthof the casing beyond each side of the main stroke, for a total width of the main stroke'swidthplus twice the casing'swidth. - A Renderer SHALL support drawing, within a single visualization pass, the casings of all features with the same
zOrderbeneath the main strokes of all those features, so that the casings of crossing and joining lines merge.
The stroke is 12 px wide and casing.width is 3 px. Is 3 px the casing's total width, or how far it extends beyond each side of the stroke?
The draft now says the casing extends its width beyond each side of the main stroke, for a total of stroke width + 2 × casing width (Jérôme, #147). Annex C's road example agrees: its casings of 0.25 to 1 px under strokes of 2.5 to 6 px would be hidden under the total-width reading.
Sources: Jérôme, #147: total width = main width + 2 × casing width
Render it yourself: style.cs.json · Roads.geojson · view.json↑ Matrix
Font outline beneath or above the glyphs#
What the draft says
/req/font-outlines/outline- An Encoding SHALL support defining an
outlineFont property as a FontOutline object including asize, anopacityand acolor. - A Renderer SHALL support drawing an outline around the glyphs of text drawn with a Font specifying an
outline, beneath the glyphs themselves.
/req/font-outlines/size- An Encoding SHALL support defining a
sizeFontOutline property as a float. - A Renderer SHALL support drawing the outline extending outward from the glyph contours by the thickness specified by the
sizeproperty.
Text with a 2 px white outline over a dark background. Is the outline drawn beneath the glyphs (a halo) or on top of them?
Outlines beneath the glyphs are the convention: SE draws a Halo on the backgrounds of the glyphs, QGIS a text buffer, MapLibre a text-halo.
Sources: SE 1.1, 11.4.5 Halo: applied to the backgrounds of font glyphs · QGIS label buffer
Render it yourself: style.cs.json · Background.geojson · Places.geojson · view.json↑ Matrix
Which zOrder is drawn on top#
What the draft says
/req/core/z-order- An Encoding SHALL support defining a
zOrderSymbolizer property as an integer. - A Renderer SHALL support portraying features and coverage cells of a single rendering pass in increasing order of their resolved
zOrderproperty, so that portrayals with a higherzOrderare drawn on top of those with a lowerzOrder.
Two overlapping features. Lakes come first in the data but have zOrder: 2; Forest has zOrder: 1. Is the higher zOrder drawn on top?
The draft says portrayals with a higher zOrder are drawn on top. The earlier clause 7 text only said that zOrder modifies the default order, not in which direction.
Render it yourself: style.cs.json · Lakes.geojson · Forest.geojson · view.json↑ Matrix
Hatch angle direction#
What the draft says
/req/hatches/angle- An Encoding SHALL support defining an
angleproperty for the hatch fill symbolizer. - A Renderer SHALL support fill hatching vector geometry based on an angle property.
A hatch fill with angle: 30. Clockwise or counter-clockwise from horizontal?
The Part 1 Hatch table says "Hatches orientation clockwise degree". libCartoSym still uses the older forward/backward/cross hatch model.
Render it yourself: style.cs.json · Zones.geojson · view.json↑ Matrix
Units of dashPattern#
What the draft says
/req/dashes/dash-pattern- An Encoding SHALL support defining a dashPattern property for the stroke symbolizer.
- A Renderer SHALL support dashing vector geometry based on a dashPattern property, whose lengths are in pixels unless qualified with another unit (<<graphical-units-of-measure,Graphical Units of Measure>>).
A 6 px wide line with dashPattern: [4, 2]. Are the values pixels, or multiples of the stroke width?
Jérôme: units are as in Graphical Units of Measure, pixels unless qualified with another unit. The PR now says so for dashPattern, as SE does for stroke-dasharray; MapLibre's line-dasharray is in multiples of the line width.
Sources: SE 1.1, Stroke: stroke-dasharray lengths in the symbolizer's units of measure · GeoServer: stroke-dasharray in pixels · MapLibre line-dasharray: units in line widths
Render it yourself: style.cs.json · Boundaries.geojson · view.json↑ Matrix
Default hot spot of an Image#
What the draft says
/req/vector/image- An Encoding SHALL support defining an Image Graphic with an
imageproperty referencing an image resource, an optional uom-qualifiedhotSpotposition within the image, optionaltintandblackTintColors, and an optionalalphaThresholdreal number between 0 and 1. - A Renderer SHALL support drawing the referenced image so that its
hotSpot(by default, its center,{ 0.5, 0.5 }) is located at the position of the Graphic. - A Renderer SHALL support recoloring the image so that each red, green and blue component _v_ (between 0 and 1) of each pixel becomes
tint× _v_ +blackTint× (1 − _v_), using the same component oftintandblackTint.
A pin icon on a point (black cross = the point's location). Without an explicit hotSpot, which part of the image sits on the point?
Jérôme: the default hot spot is the centre { 0.5, 0.5 }, since centred icons are by far the most common case (after first saying top-left). This is what the draft says and what libCartoSym does (GraphicalElement.ec). Pin-shaped icons need the bottom centre, so styles would set it explicitly.
Render it yourself: style.cs.json · POI.geojson · view.json↑ Matrix
The default marker#
What the draft says
/req/core/default-values- An Encoding SHALL support omitting Symbolizer properties and object members for which a default value is defined in this section.
- A Renderer SHALL support using the default values defined in this section for Symbolizer properties and object members which are not set by any selected StylingRule.
/req/vector/marker- An Encoding SHALL support defining a
markerSymbolizer property as an array of graphical elements (Graphics). - A Renderer SHALL support drawing the graphical elements of a marker at the points of vector geometry (for points and line strings) and at the centroid of polygons.
/req/vector/dot- An Encoding SHALL support defining a Dot Graphic inheriting from a base Shape class specifying a stroke, with a uom-qualified
sizeproperty and acolorproperty that are the same values as thewidthand thecolorof its stroke. - A Renderer SHALL support setting the
widthand thecolorof the stroke of a Dot when itssizeandcolorare set, and the reverse, so that the value set last applies to both. - A Renderer SHALL support drawing a Dot Graphic as a filled circle whose diameter is its
size(thewidthof its stroke), in itscolor(thecolorof its stroke).
A style that sets only a fill and a stroke, and no marker. Clause 7's defaults say the default Marker is a single default Dot (white, 10 px), and markers are drawn at points, line vertices and polygon centroids. Does every feature get a white dot?
Jérôme: intended, so that every feature type is visible by default, and a single default Symbolizer is intended; a typical base style unsets the marker for lines and polygons, e.g. [dataLayer.featuresGeometryDimensions > 0] { marker: null; }. marker: { elements: [] } is equivalent, while marker: { } still yields the default Dot; the draft renderer does the same, and the PR's NOTE now says so. Open: the JSON schema rejects all three forms, and the Dot defaults (size 10 px, color white) set properties the Dot class does not have: clause 8 and the schema draw a Dot with its stroke's width and color. The other cards leave the default marker off, so they stay readable.
Render it yourself: style.cs.json · Things.geojson · view.json↑ Matrix
What a stipple or dot-pattern dot looks like open in draft#
What the draft says
/req/stipples/ratio- An Encoding SHALL support defining a ratio property for the stipple fill symbolizer.
- A Renderer SHALL support stipple fill vector geometry based on a ratio property.
/req/dot-patterns/dotpattern- An Encoding SHALL support defining a
dotpatternproperty within theFillclass, defined as aDotpatternclass. - A Renderer SHALL support filling vector geometry with a regular grid of points based on a
dotpatternproperty.
/req/dot-patterns/distance- An Encoding SHALL support defining a
distanceDotpattern property as a Distance object including ahorizontaland averticalfloat. - A Renderer SHALL support spacing the rendered points of a dot pattern fill horizontally by the
horizontaldistance and vertically by theverticaldistance.
A stipple fill (ratio: 0.15, left) and a dot pattern (distance: 14 px, right), both with a dark green fill color. Neither the requirements nor the model say how big a dot is or which color it has.
The draft leaves this open; #84 asks whether the Dot class defaults apply. Rendered, white 10 px dots read as a different symbol, and the fill color no longer shows.
Render it yourself: style.cs.json · Ground.geojson · Stipple.geojson · Dots.geojson · view.json↑ Matrix
Which label priority wins#
What the draft says
/req/labels/label- An Encoding SHALL support defining a
labelSymbolizer property as a Label, which is a MultiGraphic that can contain multiple graphical elements. - A Renderer SHALL support drawing the graphical elements of the labels as a separate pass on top of all styled vector features and coverages.
- A Renderer SHALL support omitting labels which cannot be placed without overlapping other labels.
/req/labels/placement- An Encoding SHALL support defining a
placementLabel property as a LabelPlacement object including apriority, aminSpacingand amaxSpacing. - A Renderer SHALL support placing labels so that a label with a higher
priorityis placed in preference to a conflicting label with a lowerpriority. - A Renderer SHALL support repeating a label so that the distance between repetitions of that label is at least
minSpacingand, where space allows, at mostmaxSpacing, both in pixels unless qualified with another unit (<<graphical-units-of-measure,Graphical Units of Measure>>).
Two nearby places whose labels overlap. Lillestrøm has priority: 2, Strømmen priority: 8. Only one label fits. Which one is kept?
The draft proposes that a higher value wins (#148). In QGIS and GeoServer the label with the higher priority wins; MapLibre's symbol-sort-key places lower values first, and so, for now, does libCartoSym's (incomplete) placement engine (Jérôme). Still open; the proposal is to keep the name priority with higher winning, or rename it if lower should come first.
Sources: QGIS: the label with the higher priority is displayed · GeoServer: higher priority values take precedence · MapLibre symbol-sort-key: lower values placed first
Render it yourself: style.cs.json · Places.geojson · view.json↑ Matrix
Units of label minSpacing#
What the draft says
/req/labels/placement- An Encoding SHALL support defining a
placementLabel property as a LabelPlacement object including apriority, aminSpacingand amaxSpacing. - A Renderer SHALL support placing labels so that a label with a higher
priorityis placed in preference to a conflicting label with a lowerpriority. - A Renderer SHALL support repeating a label so that the distance between repetitions of that label is at least
minSpacingand, where space allows, at mostmaxSpacing, both in pixels unless qualified with another unit (<<graphical-units-of-measure,Graphical Units of Measure>>).
A road labelled with minSpacing: 60 at 5 m per pixel. Is 60 in pixels or in metres?
Jérôme: units are as in Graphical Units of Measure, pixels unless qualified with another unit. The PR now says so for minSpacing and maxSpacing; a style that wants metres qualifies the value.
Render it yourself: style.cs.json · Roads.geojson · view.json↑ Matrix
Labels on top of everything#
What the draft says
/req/labels/label- An Encoding SHALL support defining a
labelSymbolizer property as a Label, which is a MultiGraphic that can contain multiple graphical elements. - A Renderer SHALL support drawing the graphical elements of the labels as a separate pass on top of all styled vector features and coverages.
- A Renderer SHALL support omitting labels which cannot be placed without overlapping other labels.
A labelled harbour point, and a pier polygon drawn after it (higher zOrder). Is the label drawn in a separate pass on top, or with its own feature?
The draft requires the separate pass. Drawn with the feature, a later feature can cover the label, as on the right.
Render it yourself: style.cs.json · Harbours.geojson · Piers.geojson · view.json↑ Matrix
Part 1: coverage styling (the spec's examples 5–9)
The cards use real data of Rondane, Norway, 45 × 30 km with 150 m cells: elevation (elevation data © Kartverket, CC BY 4.0), with the examples' color map stretched from 0–2500 m to 500–2000 m, and a Sentinel-2 L2A scene of 2 August 2024 (contains modified Copernicus Sentinel data 2024). Only the pyramids are synthetic (see the README). The styles are the spec's examples, unchanged unless the question says otherwise.
Example 5: colorMap between and beyond entries#
What the draft says
/req/coverage/color-map- An Encoding SHALL support defining a
colorMapSymbolizer property as an array ofvalue(float) andcolor(Color) pairs which can be tagged with aname. - A Renderer SHALL support portraying each coverage cell with the color mapped from its
singleChannelvalue by thecolorMap, using the color of an entry for a value equal to that entry'svalue, interpolating between the colors of the two closest entries for other values within the range of the map, and using the color of the first or last entry for values outside the range of the map.
/req/coverage/opacity-map- An Encoding SHALL support defining a
opacityMapSymbolizer property as an array ofvalue(float) andopacity(float) pairs which can be tagged with aname. - A Renderer SHALL support portraying each coverage cell with the opacity mapped from its
singleChannelvalue by theopacityMap, using the opacity of an entry for a value equal to that entry'svalue, interpolating between the opacities of the two closest entries for other values within the range of the map, and using the opacity of the first or last entry for values outside the range of the map.
/rec/coverage/map-interpolation- A Renderer SHOULD interpolate the colors of a
colorMapand the opacities of anopacityMapso that the result lies between those of the two closest entries.
Example 5's color map, stretched from 0–2500 m to Rondane's 500–2000 m (same colors). Between entries, are colors interpolated or stepped? Below 500 m (the valley floors) and above 2000 m (the summits), is the first or last color used, or nothing?
Clamping is confirmed (Jérôme, #149). The draft now says that colors are interpolated between entries without fixing the method (libCartoSym interpolates quadratically; this renderer linearly), and that a value equal to an entry takes exactly that entry's color. A recommendation says the result should stay between the two closest entries (a parabola through three entries can overshoot). Stepped classes for continuous values are written with conditional expressions (Jérôme: OK for Part 1). SE 1.1 makes the choice explicit with Interpolate and Categorize functions (its own DEM example uses Categorize); GeoServer's ColorMap defaults to an interpolated ramp.
Sources: SE 1.1, RasterSymbolizer ColorMap: Interpolate or Categorize · GeoServer: ColorMap type ramp (interpolated) is the default
Render it yourself: style.cs.json · Elevation.elevation.asc · view.json↑ Matrix
Direction of coverage gamma#
What the draft says
/req/coverage/gamma- An Encoding SHOULD support defining a
gammaSymbolizer property as a float. - A Renderer SHOULD support applying a gamma correction to the portrayed color values, using the value of the
gammaproperty as the exponent applied to normalized (0 to 1) color components.
Example 5 with gamma: 2 (its color map stretched to Rondane's 500–2000 m). Does a gamma above 1 darken or brighten?
SE 1.1 GammaValue brightens for values above 1. The draft's exponent does the opposite. gamma is also absent from the coverage UML, tables and schema (#86), so removing it is an option.
Sources: SE 1.1, ContrastEnhancement: GammaValue above 1.0 brightens
Render it yourself: style.cs.json · Elevation.elevation.asc · view.json↑ Matrix
Example 6: scaling colorChannels#
What the draft says
/req/coverage/color-channels- An Encoding SHALL support defining a
colorChannelsSymbolizer property as a Color object. - A Renderer SHALL support portraying each coverage cell with the red, green and blue components resolved from the
colorChannelsproperty for that cell, each in the range 0 to 1.
/req/coverage/alpha-channel- An Encoding SHALL support defining a
alphaChannelSymbolizer property as a float. - A Renderer SHALL support portraying each coverage cell with the opacity resolved from the
alphaChannelproperty for that cell, where 0 is fully transparent and 1 is fully opaque.
Example 6 set colorChannels: B04 B03 B02, Sentinel-2 digital numbers (reflectance × 10 000), while the schema types colorChannels as 0..1. The PR now scales the bands in the style: colorChannels.r: B04 / 2500 and so on. Here on a real Sentinel-2 L2A scene of Rondane (2 August 2024).
Jérôme: colorChannels is 0..1 and the example assumed pre-scaled bands; one of his working examples divides each band by 2500. The PR now states the 0..1 range and scales example 6 that way. Still to raise: the defaults table gives colorChannels as fields[0], fields[1], fields[2], unscaled (#150).
Render it yourself: style.cs.json · style-variant-2.cs.json · sentinel2-l2a.B02.asc · sentinel2-l2a.B03.asc · sentinel2-l2a.B04.asc · sentinel2-l2a.B08.asc · sentinel2-l2a.SCL.asc · view.json↑ Matrix
Example 7: NDVI with integer bands#
What the draft says
/req/arithmetic/div- An Encoding SHALL support defining a
divoperation expression taking two numeric operands. - A Renderer SHALL support evaluating a
divoperation expression as the real-valued quotient of the first operand divided by the second operand, including when both operands are integers.
/req/coverage/single-channel- An Encoding SHALL support defining a
singleChannelSymbolizer property as a float. - A Renderer SHALL support using the value resolved from the
singleChannelproperty for each coverage cell as the input value to thecolorMapandopacityMapproperties.
/req/coverage/color-map- An Encoding SHALL support defining a
colorMapSymbolizer property as an array ofvalue(float) andcolor(Color) pairs which can be tagged with aname. - A Renderer SHALL support portraying each coverage cell with the color mapped from its
singleChannelvalue by thecolorMap, using the color of an entry for a value equal to that entry'svalue, interpolating between the colors of the two closest entries for other values within the range of the map, and using the color of the first or last entry for values outside the range of the map.
Example 7 computes singleChannel: (B08 - B04)/(B08 + B04). Sentinel-2 L2A bands are integers; here a real scene of Rondane (2 August 2024). Is / a real division?
Jérôme confirmed: / is real-valued division and div is integer division, as in CQL2 arithmetic expressions. libCartoSym's cs-canif (0.0.1 and main) evaluates (3000 - 1000) / (3000 + 1000) to 0, and to 0.5 only when an operand is real; with integer division every NDVI value truncates to 0, so the example's color map collapses to one color. Cloud cells are hidden by the example's SCL rules (alphaChannel 0 and 0.5).
Sources: Test report: arithmetic/div cases
Render it yourself: style.cs.json · sentinel2-l2a.B02.asc · sentinel2-l2a.B03.asc · sentinel2-l2a.B04.asc · sentinel2-l2a.B08.asc · sentinel2-l2a.SCL.asc · view.json↑ Matrix
Example 8: what hillShading factor means#
What the draft says
/req/hill-shading/hill-shading- An Encoding SHALL support defining a
hillShadingSymbolizer property as an object includingfactor,sun,colorMapandopacityMapproperties. - A Renderer SHALL support computing the terrain surface normal of each cell from the differences in elevation between neighbouring samples of the elevation coverage at the resolution being rendered, over the horizontal distance between those samples, in the same units as the elevation.
- A Renderer SHALL support portraying an elevation coverage with a shade value computed for each cell as the dot product of the unit normal of the terrain surface and the unit vector pointing towards the sun, clamped to the range 0 to 1, where 0 is full shade (the sun at 90 degrees or more from the normal) and 1 is no shade (the sun in the direction of the normal).
- A Renderer SHALL support, where the HillShading has no
colorMap, multiplying the red, green and blue components of each cell by its shade value.
/req/hill-shading/factor- An Encoding SHALL support defining a
factorHillShading property as a float. - A Renderer SHALL support multiplying the horizontal components of the terrain surface normal by the
factorproperty before normalizing it, so that afactorof 1 shades the terrain as it is and a largerfactorexaggerates the relief.
Example 8 shades the DEM color map with factor: 56, sun at azimuth 45°, elevation 60°. Here on Rondane at 150 m cells, with the color map stretched from 0–2500 m to Rondane's 500–2000 m (same colors), and the factors of Jérôme's renders (1, 8, 32). Over what distance is the slope measured?
Jérôme: factor multiplies the horizontal components of the terrain normal before normalization, so factor 1 is the plain dot product of the sun direction and the normal. The PR now says so, and the draft renderer uses the same formula. His renders of the Tibetan Plateau, with pixels of several km, show faint relief at 1, clear relief at 8 and strong relief at 32 and 64. On Rondane at 150 m cells the slopes per cell are steeper than at his pixel size, so the same factor gives stronger relief: 1 already shows it clearly, 8 is strong, and the example's 56 saturates it. Jérôme: the normal is computed between the samples of the DEM at the resolution being rendered, over the horizontal distance between them, in the same units as the heights, so the factor that suits a style depends on that resolution; and without a hillShading colorMap the cell's color is multiplied by the shade, as in a typical shader. The draft renderer does both, and the PR now says so.
Sources: Jérôme's renders for factor 1, 8, 32 and 64 (PR #133) · gdaldem hillshade: -z is a vertical exaggeration
Render it yourself: style.cs.json · style-variant-2.cs.json · style-variant-3.cs.json · style-variant-4.cs.json · style-variant-5.cs.json · Elevation.elevation.asc · view.json↑ Matrix
Example 9: shading intensity mapped to color and opacity#
What the draft says
/req/hill-shading/factor- An Encoding SHALL support defining a
factorHillShading property as a float. - A Renderer SHALL support multiplying the horizontal components of the terrain surface normal by the
factorproperty before normalizing it, so that afactorof 1 shades the terrain as it is and a largerfactorexaggerates the relief.
/req/hill-shading/color-map- An Encoding SHALL support defining a
colorMapHillShading property as an array ofvalue(float) andcolor(Color) pairs. - A Renderer SHALL support portraying each cell with the color mapped from its computed shade value by the
colorMap, as for thecolorMapSymbolizer property.
/req/hill-shading/opacity-map- An Encoding SHALL support defining a
opacityMapHillShading property as an array ofvalue(float) andopacity(float) pairs. - A Renderer SHALL support portraying each cell with the opacity mapped from its computed shade value by the
opacityMap, as for theopacityMapSymbolizer property.
Example 9 maps the shade (0..1) to black–white and to opacity 0.75–0, for use as an overlay. Here it is drawn over example 5's elevation colors (a second layer, Terrain), stretched from 0–2500 m to Rondane's 500–2000 m. With factor: 56, how much of the terrain does it darken?
Jérôme: the shade is the dot product of the sun direction and the terrain normal, from 0 (the sun at 90° from the normal, full shade) to 1 (no shade), and is the key for the hillShading colorMap and opacityMap. His example uses a grayscale colorMap with an opacityMap that is more opaque in the shade, so imagery shows through on flat ground, as example 9 does. The PR now states this. How much is darkened depends on the factor and the distance the normal is computed over (see example 8).
Sources: Jérôme's renders for factor 1, 8, 32 and 64 (PR #133) · gdaldem hillshade: -z is a vertical exaggeration
Render it yourself: style.cs.json · style-variant-2.cs.json · style-variant-3.cs.json · style-variant-4.cs.json · style-variant-5.cs.json · Terrain.elevation.asc · Elevation.elevation.asc · view.json↑ Matrix
Direction of the sun azimuth#
What the draft says
/req/hill-shading/sun- An Encoding SHALL support defining a
sunHillShading property as an object consisting ofazimuthandelevationangle members representing the orientation of the sun relative to the terrain. - A Renderer SHALL support computing the shade of each cell for a light source at the
azimuthangle (in degrees clockwise from North, as a compass bearing) andelevationangle (relative to the ground) specified by thesunproperty.
Three pyramids, shaded with example 8's style, the sun at azimuth 315°, elevation 45°, factor: 1, in plain grey. Is 315° north-west (compass bearing) or south-east (counter-clockwise from east)?
Jérôme: directions on the ground, such as the sun azimuth, are compass bearings, clockwise from north. Angles drawn on the map, such as a gradient's, start at 0 pointing right and turn counter-clockwise. The PR now says so for the azimuth. Both examples use 45°, which is north-east as a bearing. GDAL and most GIS software use compass bearings too.
Sources: gdaldem hillshade: azimuth 0 = from the top, 90 = from the east
Render it yourself: style.cs.json · Elevation.elevation.asc · view.json↑ Matrix
Part 2: gradient fills
Gradient angle convention#
What the draft says
/req/gradients/angle- An Encoding SHALL support defining an
angleGradient property as a float. - A Renderer SHALL support orienting the gradient according to the
angleproperty, in degrees, where 0 points to the right and angles turn counter-clockwise as seen on the map.
/req/gradients/keys- An Encoding SHALL support defining a
keysGradient property as an array of ColorKey objects, each including acolor, an optionalopacityand an optionalpercentposition along the gradient. - A Renderer SHALL support interpolating the color and opacity of the gradient between consecutive keys, according to their
percentposition along the gradient.
A linear gradient with angle: 120, from navy through red to gold. Where does 0° point, and which way does the angle turn?
Jérôme: angles drawn on the map are in degrees, start at 0 pointing right and turn counter-clockwise. The PR now says so for the gradient angle. The Part 1 hatch table, however, says "clockwise degree", and the sun azimuth is relative to north, so whether this applies to every angle is still to confirm.
Sources: CSS linear-gradient: 0deg = to top, clockwise
Render it yourself: style.cs.json · Zones.geojson · view.json↑ Matrix
Gradient position and size: per feature or per map open in draft#
What the draft says
/req/gradients/position- An Encoding SHALL support defining a
positionGradient property. - A Renderer SHALL support positioning the origin of the gradient according to the
positionproperty, in pixels unless qualified with another unit.
/req/gradients/size- An Encoding SHALL support defining a
sizeGradient property. - A Renderer SHALL support scaling the extent of the gradient according to the
sizeproperty, in pixels unless qualified with another unit.
/req/gradients/type- An Encoding SHALL support defining a
typeGradient property with a value oflinear,radial,repeatingorconic. - A Renderer SHALL support filling vector geometry with a linear, radial, repeating or conic gradient according to the
typeproperty.
Three parcels with a radial gradient, position: [0.5, 0.5], size: 1. Relative to each feature, or to the map?
position and size have no reference frame in Part 2. Per feature, every polygon gets its own centred gradient; per map, one gradient runs across all features. Jérôme: units are pixels unless qualified, which the PR now says; this card's unqualified 0.5 and 1 are read as fractions of the frame, which under the draft needs a percent unit. The repeating type also needs a definition (repeating what, and how often). Jérôme (#153): decide once two or three renderers can be compared.
Render it yourself: style.cs.json · Parcels.geojson · view.json↑ Matrix