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
two symbols, a pmtilesRules
Map instance, and a Promise-valued pmtilesRulesReady
, which is resolved when
the required Sheet is loaded, per the following example:
const sheet = new protomapsL.Sheet(`
<html>
<body>
<svg id="icon_0" width="99px" height="97px" xmlns="http://www.w3.org/2000/svg">
<image href="data:image/svg+xml;base64,PHN2ZyBpZD0iaWNvbl82NCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgICAgPGltYWdlIGhyZWY9ImRhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBQkFBQUFBUUNBWUFBQUFmOC85aEFBQUFDWEJJV1hNQUFBN0VBQUFPeEFHVkt3NGJBQUFBMlVsRVFWUjRuTldQTVdvQ1lSQ0Z2MElDZ2xZaFZnRUwzZmNyZGlsczBteVJUdkFLa2xaSXdKM2ZpTjE2aGhoeUFCRkVjZ2J2WUtFbmtCUkpiNWtFSlRheXNNdFd5WVBYRFBPOW1RZC9YcjVHSlRkc1Rhb21wdm12TzBZbVB1T1FRcjRQeE5yRXR3L29wQzdITFM1TVRFd3NEdmFPdHdOOERCRGIwenh5eklkMWJoSkQraTFLWHN4T1lJSjN2a0dZM2ozZ1BnRmVqUnRjWnVvK2ROeWVCM2l4Skt0TXZQeUNtMGc4bS9neXNYOXlsRlBoT0tUZ3hZYzVYZ2ZYRkkrVnhKMkpkeE85MUlDQmFFZU83dm44b2M2VkQzak1YT1AvNkFmS0Ywd3dXeU9Oc3dBQUFBQkpSVTVFcmtKZ2dnPT0iIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIvPgogICAgPC9zdmc+" width="99" height="97" />
</svg>
</body>
</html>
`);
const pmtilesRules = new Map();
const pmtilesRulesReady = sheet.load().then(() => {
...
pmtilesRules.set(
'https://maps4html.org/experiments/vector-tiles/pmtiles/spearfish.pmtiles?theme=light',
{ theme: { theme: 'light' } }
);
...
return pmtilesRules;
});
export { pmtilesRules, pmtilesRulesReady };
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 required structure of the pmtiles 'stylesheet'
The stylesheet module must export two symbols: pmtilesRulesReady
and pmtilesRules
.
The pmtilesRulesReady
export must be a Promise that signals to the MapML library that
the pmttilesRules export is fully defined and ready for rendering. If your stylesheet
uses IconSymbolizer, it may be convenient to perform resource loading and processing
via the asynchronous Sheet.load() function, which itself returns a Promise object that
may be returned by your stylesheet as the pmtilesRulesReady value, as
shown in the example above. If your stylesheet does not require icons, it should
be possible to assign an already-resolved promise as the value of pmtilesRulesReady,
via Promise.resolve(), and return that.
const pmtilesRules = new Map();
// a Map requires a key, in this case the absolute URL of the data source (but,
// can also be the absolute value of the URL _template_ for the data source),
// and an object to be mapped by the key, in this case, an object with a `theme`
// member, whose value is itself an object with a `theme` member.
pmtilesRules.set(
'https://maps4html.org/experiments/vector-tiles/pmtiles/spearfish.pmtiles?theme=light',
{ theme: { theme: 'light' } }
);
const pmtilesRulesReady = Promise.resolve();
export { pmtilesRules, pmtilesRulesReady };
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();
}
}
// the only other pmtilesRules keyed value besides `theme` that is supported is
// an object with a `rules` member. The value of the `rules` member is an object
// that has two optional array-valued members, `PAINT_RULES` and `LABEL_RULES`.
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',
{
// PAINT_RULES and LABEL_RULES are each an array of dataLayer/symbolizer objects, with optional
// function-valued `filter` member (filter is a member of the same object as
// dataLayer and symbolizer, not a property of those objects).
rules: {
PAINT_RULES: [
{
dataLayer: 'streams',
symbolizer: new protomapsL.LineSymbolizer({ color: 'steelblue', width: 2 })
},
{
dataLayer: 'roads',
symbolizer: new protomapsL.LineSymbolizer({ color: 'maroon', width: 2 })
},
{
dataLayer: 'restricted',
symbolizer: new protomapsL.PolygonSymbolizer({
fill: 'red',
opacity: 0.5
})
},
{
dataLayer: 'restricted',
symbolizer: new 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 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 protomapsL
import, 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
*/