Creating custom mvt styles
What are mvt files
Mapbox vector tiles (mvt) files are a de facto standard encoding of map data optimized for rendering to HTML canvas element(s) via JavaScript and/or JSON-based encoded style information. Cascading Style Sheet rules do not apply, because mvt content is not exposed via browser APIs, so style rules, while sort-of declarative, are implemented in script.
What are pmtiles files
pmtiles is a map tile archive format that is intended to obviate (some of) the need for complex geospatial-specific servers and APIs for tile resources on the web, to enable people to create their own localized web map sites without having to host the whole world on their domain. The pmtiles archive format contains metadata that can be requested via http byte range requests, and subsequent requests for individual tiles are also sent as byte range requests.
The pmtiles map archive and mvt formats in the MapML polyfill are integrated via
the protomaps-leaflet library. Protomaps-leaflet is designed to support the (de facto standard) Web Mercator tile grid. This allows
the MapML polyfill to integrate support for pmtiles and mvt data sources for only the OSMTILE
projection. <map-layer>
and <map-extent>
elements and the <mapml-viewer>
will
not work (and will be permanently disabled
) when used with <mapml-viewer projection="ANYTHING"...>
or <map-extent units="ANYTHING"...>
values apart from OSMTILE
.
Before proceeding below, you should read the protomaps-leaflet documentation on custom styling. Another useful source of information for styling includes the source code examples.
Working examples of <mapml-viewer>
usage of themes and styles are available on-line, with available source code.
How to create a stylesheet module
A pmtiles stylesheet used in the MapML polyfill is a JavaScript module source file which exports a
single symbol, which must be named pmtilesRules
:
const pmtilesRules = new Map();
...
pmtilesRules.set(
'https://maps4html.org/experiments/vector-tiles/pmtiles/spearfish.pmtiles?theme=light',
{ theme: { theme: 'light' } }
);
...
export { pmtilesRules };
Pre-defined themes that you can use include: light
, dark
, white
, grayscale
and black
. You should include the theme name in the URL template as a parameter (even though it is not required by the pmtiles or mvt resource), so that the URL template can be made unique and distinct from other pmtilesRules keys' use of the same pmtiles or mvt resource.
The pmtilesRules
export must be a JavaScript Map-type object, structured as follows:
- must have a unique URL template string key that will be used on the web to identify a set of rules
- has an object value that has a
theme
or arules
property theme
property must be an object literal, itself with atheme
property with a string theme name value:{theme:{theme:'grayscale'}}
rules
property must be an object literal, withPAINT_RULES: [...]
and/or aLABEL_RULES: [...]
array-valued propertiesPAINT_RULES
andLABEL_RULES
are arrays of objects that have requireddataLayer
andsymbolizer
propertiesPAINT_RULES
andLABEL_RULES
can have optional properties e.g.filter
,minzoom
,maxzoom
dataLayer
is a string property that is the name of a layer encoded in the mvt datasymbolizer
is property ofSymbolizer
-type value
An example custom pmtiles stylesheet module for a localhost GeoServer 'spearfish' Layer Group (set up with a mvt tile cache) is as follows:
class SpearfishSymbolizer {
constructor(options) {
this.color = options.color;
this.shape = options.shape;
}
draw(context, geom, z, feature) {
let pt = geom[0][0];
context.fillStyle = this.color;
context.strokeStyle = this.color;
context.beginPath();
if (this.shape === 'circle') {
context.arc(pt.x, pt.y, 3, 0, 2 * Math.PI);
} else {
context.rect(pt.x - 2, pt.y - 2, 4, 4);
}
context.stroke();
context.fill();
}
}
const pmtilesRules = new Map();
pmtilesRules.set(
'http://localhost:8080/geoserver/gwc/service/wmts/rest/spearfish/OSMTILE/{z}/{y}/{x}?format=application/vnd.mapbox-vector-tile',
{
rules: {
PAINT_RULES: [
{
dataLayer: 'streams',
symbolizer: new M.protomapsL.LineSymbolizer({ color: 'steelblue', width: 2 })
},
{
dataLayer: 'roads',
symbolizer: new M.protomapsL.LineSymbolizer({ color: 'maroon', width: 2 })
},
{
dataLayer: 'restricted',
symbolizer: new M.protomapsL.PolygonSymbolizer({
fill: 'red',
opacity: 0.5
})
},
{
dataLayer: 'restricted',
symbolizer: new M.protomapsL.LineSymbolizer({ color: 'red', width: 2 })
},
{
dataLayer: 'archsites',
symbolizer: new SpearfishSymbolizer({
color: 'red',
shape: 'square'
})
},
{
dataLayer: 'bugsites',
symbolizer: new SpearfishSymbolizer({
color: 'black',
shape: 'circle'
})
}
],
LABEL_RULES: [
{
dataLayer: 'archsites',
symbolizer: new M.protomapsL.CenteredTextSymbolizer({
labelProps: ['str1'],
fill:'white',
width:2,
stroke:'black',
font:"600 16px sans-serif"
}),
// note that filter is a property of a rule, not an option to a symbolizer
filter: (z,f) => { return f.props['str1'].trim().toLowerCase() !== 'no name'; }
}
]
}
}
);
export { pmtilesRules };
Available imported symbols from protomaps-leaflet
The following imports from protomaps-leaflet are available on the M.protomapsL
global variable e.g. M.protomapsL.CenteredSymbolizer
, for use by your stylesheets:
/*
CenteredSymbolizer,
CenteredTextSymbolizer,
CircleSymbolizer,
FlexSymbolizer,
Font,
GeomType,
GroupSymbolizer,
IconSymbolizer,
Index,
Justify,
Labeler,
Labelers,
LineLabelPlacement,
LineLabelSymbolizer,
LineSymbolizer,
OffsetSymbolizer,
OffsetTextSymbolizer,
Padding,
PmtilesSource,
PolygonSymbolizer,
Sheet,
ShieldSymbolizer,
Static,
TextPlacements,
TextSymbolizer,
TileCache,
View,
ZxySource,
arr,
covering,
createPattern,
exp,
getZoom,
isCcw,
isInRing,
labelRules,
leafletLayer,
linear,
paint,
paintRules,
pointInPolygon,
pointMinDistToLines,
pointMinDistToPoints,
sourcesToViews,
step,
toIndex,
transformGeom,
wrap
*/