Vector maps guide
A practical guide with examples: putting a map on a page with MapLibre, using the style and TileJSON, building your own style, and opening the tiles in QGIS.
https://api.elemroot.com: not pump.elemroot.com,
where the geo services are.
Authentication
Style, sprite, TileJSON and tile requests require an API key passed as the query
parameter key; query string only (the key cannot be sent
in a header or cookie; its position in the query does not matter). Only fonts and
/health require no key. The key must carry the vector-map (tiles)
service permission and may be bound to an origin; then it only works from the allowed
web page (the browser sends Origin/Referer).
...&key=docs_382d376eaaa6d004b7b93b7d
docs_⦠key used in the examples is a public
demo key for trying things out; the same one as for the geo services
(it rotates regularly and is rate-limited). For your own application,
request a personal key.
The server rewrites the URLs inside the style and the TileJSON so your key is carried along automatically; you only need to supply the key once, in the style URL.
Quickstart: a map on a page (MapLibre GL JS)
A complete minimal page: copy, replace the key and open in a browser:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.js"></script>
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.css">
<style> html, body, #map { margin: 0; height: 100%; } </style>
</head>
<body>
<div id="map"></div>
<script>
const map = new maplibregl.Map({
container: 'map',
style: 'https://api.elemroot.com/styles/v1/elemroot/style.json?key=docs_382d376eaaa6d004b7b93b7d',
center: [24.75, 59.44], // [lon, lat]: Tallinn
zoom: 10
});
map.addControl(new maplibregl.NavigationControl());
</script>
</body>
</html>
That is all you need: the style itself points to the correct tile, sprite and font URLs. A working example: examples.elemroot.com/vector-map.
Style and sprites
| Resource | URL |
|---|---|
| Style | https://api.elemroot.com/styles/v1/elemroot/style.json?key=⦠|
| Sprite | https://api.elemroot.com/styles/v1/elemroot/sprite.json?key=⦠/ sprite.png?key=⦠|
| Sprite @2x | ā¦/sprite@2x.json?key=⦠/ sprite@2x.png?key=⦠|
The style is a MapLibre style spec (version: 8) document named
Elemroot v1. It combines two sources: planet_baltics-v1
(base map) and globallandcover-v1 (land-cover background); and
defines the layer design. The sprites contain map symbols and patterns.
TileJSON and building your own style
If you want to design the map yourself, use the TileJSON as the source.
The response contains the tile URL template, the zoom range and the layer
list with attribute fields (vector_layers):
curl "https://api.elemroot.com/tiles/v1/planet_baltics-v1.json?key=docs_382d376eaaa6d004b7b93b7d"
Response (abbreviated)
{
"tilejson": "3.0.0",
"name": "OpenMapTiles + OpenMapTiles",
"scheme": "xyz",
"minzoom": 0,
"maxzoom": 14,
"center": [24.904322, 56.98397, 14],
"tiles": [
"https://api.elemroot.com/tiles/v1/planet_baltics-v1/{z}/{x}/{y}.mvt?key=docs_382d376eaaa6d004b7b93b7d"
],
"vector_layers": [ { "id": "aerodrome_label", "fields": { ⦠} }, ⦠]
}
Reference the source in your own style like this:
"sources": {
"elemroot_map": {
"type": "vector",
"url": "https://api.elemroot.com/tiles/v1/planet_baltics-v1.json?key=YOUR_KEY"
}
},
"glyphs": "https://api.elemroot.com/fonts/{fontstack}/{range}.pbf"
Using tiles directly
A single tile is a gzip-compressed Mapbox Vector Tile (protobuf) in the XYZ scheme. Normally the map library requests these itself; to check by hand:
curl -s --compressed -o tile.mvt -w "%{http_code} %{content_type}\n" \
"https://api.elemroot.com/tiles/v1/planet_baltics-v1/10/584/305.mvt?key=docs_382d376eaaa6d004b7b93b7d"
The response content type is application/x-protobuf and the tile is
always gzip-compressed (Content-Encoding: gzip, even without an
Accept-Encoding header); map libraries decompress it themselves; when saving
by hand use --compressed. An empty location (no data) responds with
204 No Content: this is normal, not an error. The grid is Web Mercator
(EPSG:3857), tile extent 4096; an x/y value outside
0ā¦2zā1 does not return 404 but wraps around (mod 2z); 404 is returned only for an unknown tileset or a too-large z.
Fonts (glyphs)
To render labels, the map library loads glyph PBF files using the pattern
/fonts/{fontstack}/{range}.pbf (no key, ranges in steps of 256):
curl -s -o glyphs.pbf "https://api.elemroot.com/fonts/Noto%20Sans%20Regular/0-255.pbf"
Exactly these font stacks are available: single fonts Noto Sans Regular,
Noto Sans Bold, Noto Sans Italic, Roboto Regular,
Roboto Medium, Roboto Bold, Roboto Italic,
Open Sans Regular, Open Sans Semibold, Arial Unicode MS Regular
and the pre-built fallback stacks Roboto Regular,Noto Sans Regular,
Roboto Medium,Noto Sans Regular, Roboto Bold,Noto Sans Bold,
Roboto Italic,Noto Sans Italic, Open Sans Regular,Arial Unicode MS Regular.
text-font
value; the server does not compose fonts on the fly: any other combination (e.g.
Noto Sans Bold,Roboto Regular) returns 404 and labels disappear from
the map. Ranges exist only in steps of 256 (0-255, 256-511 ā¦).
Tilesets and layers
| Tileset | Contents | Zoom |
|---|---|---|
planet_baltics-v1 | Base map in the OpenMapTiles schema: transportation, building, place, water, waterway, landuse, landcover, boundary, poi, housenumber and other layers. Coverage: the whole world at overview zooms (z0ā11), detailed Baltics (Estonia, Latvia, Lithuania and the surrounding area) at z12ā14; elsewhere z12ā14 tiles are empty (204) | 0ā14 |
globallandcover-v1 | Global land cover (ESA WorldCover): the background the style uses at low zooms | 0ā9 |
The TileJSON bounds of both tilesets is the whole world: set the initial view
with your own center/zoom (the style's own default view is a world map).
Attribution: the ready-made style shows it itself; Ā© Elemroot Ā© OpenStreetMap
(OpenMapTiles schema) and Ā© ESA WorldCover on the land-cover layer; keep these
when building your own style.
The exact list of layers and fields comes from the TileJSON
vector_layers field (see above).
Schema description:
openmaptiles.org/schema.
QGIS and other desktop clients
QGIS (3.14+): Browser ā Vector Tiles ā New Generic Connection:
| Field | Value |
|---|---|
| URL | https://api.elemroot.com/tiles/v1/planet_baltics-v1/{z}/{x}/{y}.mvt?key=YOUR_KEY |
| Min/Max zoom | 0 / 14 |
| Style URL (optional) | https://api.elemroot.com/styles/v1/elemroot/style.json?key=YOUR_KEY |
Parameters
* = required; the tables are generated from the OpenAPI YAML. The key query
parameter is required everywhere except fonts and /health; see authentication.
Tiles: /tiles/v1/{tileset}/{z}/{x}/{y}.mvt
| Param | Meaning | Allowed values / example | Default |
|---|---|---|---|
{tileset} * | Tileset | planet_baltics-v1 (base map, z0ā14) Ā· globallandcover-v1 (land cover, z0ā9); unknown ā 404 | ā |
{z} * | Zoom level | 0ā¦14 (planet_baltics-v1), 0ā¦9 (globallandcover-v1); larger ā 404 | ā |
{x} * | Tile column (XYZ) | 0ā¦2^zā1 (larger values wrap around) | ā |
{y} * | Tile row (XYZ) | 0ā¦2^zā1 (larger values wrap around) | ā |
Fonts: /fonts/{fontstack}/{range}.pbf
| Param | Meaning | Allowed values / example | Default |
|---|---|---|---|
{fontstack} * | Font stack: exactly one of the 15 available (URL-encoded) | Noto Sans Regular, Roboto Regular,Noto Sans Regular ā¦; any other combination ā 404 | ā |
{range} * | Unicode range in steps of 256 | 0-255, 256-511 ⦠65280-65535; other split ā 404 | ā |
When to use what
The most common tasks and the endpoint for each. All rows are verified live.
| Task | Combination | Why |
|---|---|---|
| Map on a page with minimal effort | ready-made style /styles/v1/elemroot/style.json?key=⦠in MapLibre | tile, sprite and font URLs and the key are carried in the style |
| Your own design | TileJSON /tiles/v1/{tileset}.json?key=⦠as the style's source.url + your own layers | layers and fields from vector_layers; fonts only from the 15 available stacks |
| Tiles for your own server or renderer | /tiles/v1/{tileset}/{z}/{x}/{y}.mvt?key=⦠| gzip-compressed protobuf; 204 = empty location |
| QGIS or another desktop client | Vector Tiles connection with the tile URL template + optional style URL | see QGIS |
| Orthophoto, L-EST97 grid or a client without WebGL | raster maps | the vector map is EPSG:3857 and needs WebGL |
--compressed when saving by hand.
(3) text-font must be exactly one of the 15 font stacks, otherwise labels silently disappear.
(4) The TileJSON bounds is the whole world: set the initial view yourself.
(5) An out-of-range x/y does not give 404 but a wrapped tile.
(6) 204 is not an error but an empty location.
Caching and CORS
| Resource | Cache-Control |
|---|---|
Tiles (.mvt) | public, max-age=86400 (24 h) |
| TileJSON, style, sprites | public, max-age=3600 (1 h) |
| Fonts | public, max-age=604800 (7 d) |
Style, fonts and tiles are served with
Access-Control-Allow-Origin: *; the map can be embedded on
any web page. On tiles and TileJSON the header is added in response to the browser's
Origin header (Vary: Origin); always present in a browser; with
curl and no Origin the header is absent, which is not an error.
Error handling
| Code | Meaning |
|---|---|
403 | Invalid or missing key: body {"error":"invalid_api_key"}. The same response is returned for an expired key, a key without the vector-map (tiles) service permission, or an origin-bound key used outside its allowed page |
204 | Empty tile (no data): normal |
404 | Unknown tileset (Archive not found) or z above the tileset maximum (Tile not found); body is plain text; unknown path ā JSON {"error":"not_found"}; missing style or font file ā HTML. NB an out-of-range x/y does not give 404 (see using tiles directly) |
429 | Too many requests (the demo key is rate-limited): Too Many Requests, HTML body |
403 (missing or invalid key); and make sure
the style URL contains ?key=ā¦.
Next
Examples in the gallery
- Simple vector map
- Basemap switcher: vector, raster and orthophoto
- World map in the Equal Earth projection (EPSG:8857)
- Controlling the map view
- Camera flight along a path
- Map label language
- 3D buildings
- Markers and popups
- Your data layer on the basemap
- Your 3D objects on the basemap
- Point density: heatmap and hexagons