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.

Different base URL. Vector maps live at https://api.elemroot.com: not pump.elemroot.com, where the geo services are.
Contents

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
Demo key. The 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

ResourceURL
Stylehttps://api.elemroot.com/styles/v1/elemroot/style.json?key=…
Spritehttps://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):

Request
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.

In your own style use exactly one of the above as the 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

TilesetContentsZoom
planet_baltics-v1Base 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-v1Global land cover (ESA WorldCover): the background the style uses at low zooms0–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:

FieldValue
URLhttps://api.elemroot.com/tiles/v1/planet_baltics-v1/{z}/{x}/{y}.mvt?key=YOUR_KEY
Min/Max zoom0 / 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

ParamMeaningAllowed values / exampleDefault
{tileset} *Tilesetplanet_baltics-v1 (base map, z0–14) Ā· globallandcover-v1 (land cover, z0–9); unknown → 404—
{z} *Zoom level0…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

ParamMeaningAllowed values / exampleDefault
{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 2560-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.

TaskCombinationWhy
Map on a page with minimal effortready-made style /styles/v1/elemroot/style.json?key=… in MapLibretile, sprite and font URLs and the key are carried in the style
Your own designTileJSON /tiles/v1/{tileset}.json?key=… as the style's source.url + your own layerslayers 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 clientVector Tiles connection with the tile URL template + optional style URLsee QGIS
Orthophoto, L-EST97 grid or a client without WebGLraster mapsthe vector map is EPSG:3857 and needs WebGL
Six pitfalls. (1) The key goes in the query string only; a header or cookie does not work. (2) Tiles are always gzip-compressed: use --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

ResourceCache-Control
Tiles (.mvt)public, max-age=86400 (24 h)
TileJSON, style, spritespublic, max-age=3600 (1 h)
Fontspublic, 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

CodeMeaning
403Invalid 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
204Empty tile (no data): normal
404Unknown 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)
429Too many requests (the demo key is rate-limited): Too Many Requests, HTML body
Map is blank/grey? Check the browser console for tile requests returning 403 (missing or invalid key); and make sure the style URL contains ?key=….

Next

API reference (Swagger UI) ā†’ All endpoints and "try it out". Working example ā†’ A simple vector map at examples.elemroot.com.