TopoExport logo
Developer docs

Map tiles

Worldwide OpenStreetMap basemap styles, cadastral overlays, 3D Tiles and terrain, served as vector tiles for MapLibre GL JS, Mapbox GL JS, QGIS, ArcGIS and Cesium.

Host: https://tiles.topoexport.com. Every request carries a tpk_ key, see Authentication.

Quick start with MapLibre GL JS

The simplest way to display a map is a ready-made style. It references the tiles, fonts and sprites, with your key already set in every URL.

<link href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" rel="stylesheet">
<script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js"></script>

<div id="map" style="height: 400px"></div>
<script>
  const map = new maplibregl.Map({
    container: "map",
    style: "https://tiles.topoexport.com/v1/styles/osm-world/light.json?key=tpk_YOUR_KEY&lang=en",
    center: [2.35, 48.86],
    zoom: 12,
  });
</script>

With Mapbox GL JS

Mapbox GL JS loads the same style URL.

import mapboxgl from "mapbox-gl";

// Mapbox GL JS v2 and later require a Mapbox access token whatever the style, under Mapbox's terms.
// Version 1.x does not.
mapboxgl.accessToken = MAPBOX_ACCESS_TOKEN;

const map = new mapboxgl.Map({
  container: "map",
  style: "https://tiles.topoexport.com/v1/styles/osm-world/light.json?key=tpk_YOUR_KEY&lang=en",
  center: [2.35, 48.86],
  zoom: 12,
});

With Leaflet or OpenLayers

Both load the same style through a MapLibre-based plugin: @maplibre/maplibre-gl-leaflet for Leaflet, ol-mapbox-style for OpenLayers.

import L from "leaflet";
import "maplibre-gl";
import "@maplibre/maplibre-gl-leaflet";

const map = L.map("map").setView([48.86, 2.35], 12);
L.maplibreGL({
  style: "https://tiles.topoexport.com/v1/styles/osm-world/light.json?key=tpk_YOUR_KEY&lang=en",
}).addTo(map);

Styles

GET /v1/styles/osm-world/{flavor}.json?key=tpk_…&lang={language}

A style response contains your key: it is never cached by shared caches. Fonts (/v1/fonts/…) and sprites (/v1/sprites/v4/…) are referenced by the style and cost no credits. An unknown flavor answers 404.

Flavors

flavorLook
positronTopoExport's own light theme: off-white land, white roads with soft grey outlines, pale green parks and blue-grey water.
lightColourful light theme: beige land, light blue water, green parks and white roads.
darkDark grey land and water, for dark interfaces and data overlays.
whiteAlmost entirely white, with light grey water and roads: a quiet background for your own data.
grayscaleShades of grey only, with no colour.
blackNear-black land with dark grey water and roads.

Label languages

lang sets the language of the labels: place, road, water and point-of-interest names. It defaults to en.

  • Each label uses the name in the requested language (name:{lang} in the tiles), then the English name, then the local name (name) when neither exists. Where the local name is written in another script, it can be shown under the translated one.
  • Any value outside the list below answers 400 with the supported values:
{ "error": "Unsupported lang", "supported": ["ar", "cs", "bg", "…"] }
langLanguageScript
arArabicArabic
bgBulgarianCyrillic
csCzechLatin
daDanishLatin
deGermanLatin
elGreekGreek
enEnglishLatin
esSpanishLatin
etEstonianLatin
faPersianArabic
fiFinnishLatin
frFrenchLatin
gaIrishLatin
heHebrewHebrew
hiHindiDevanagari
hrCroatianLatin
huHungarianLatin
idIndonesianLatin
itItalianLatin
jaJapaneseJapanese (kana and kanji)
koKoreanHangul
ltLithuanianLatin
lvLatvianLatin
mrMarathiDevanagari
mtMalteseLatin
neNepaliDevanagari
nlDutchLatin
noNorwegianLatin
plPolishLatin
ptPortugueseLatin
roRomanianLatin
ruRussianCyrillic
skSlovakLatin
slSlovenianLatin
svSwedishLatin
trTurkishLatin
ukUkrainianCyrillic
urUrduArabic
viVietnameseLatin
zh-HansChinese (Simplified)Han
zh-HantChinese (Traditional)Han

Change the language at runtime

The language is part of the style, so switching it means loading the style again. setStyle drops the sources and layers you added yourself: add them back once the new style has loaded.

const KEY = "tpk_YOUR_KEY";
const styleUrl = (lang) => `https://tiles.topoexport.com/v1/styles/osm-world/light.json?key=${KEY}&lang=${lang}`;

const map = new maplibregl.Map({ container: "map", style: styleUrl("en"), center: [2.35, 48.86], zoom: 12 });

function addOverlays() {
  // your own sources and layers, for example the cadastre below
}
map.on("style.load", addOverlays);

document.querySelector("#language").addEventListener("change", (event) => {
  map.setStyle(styleUrl(event.target.value), { diff: false });
});

Tilesets

TilesetContentsSource layersZooms
osm-worldWorldwide basemap from OpenStreetMap and Natural Earth9 layers, see below0 to 15
cadastre-frParcels of the French cadastrecadastre-fr12 to 15
cadastre-deParcels (Flurstücke) published by the German Ländercadastre-de12 to 15

Beyond their last zoom level, map libraries keep displaying the tiles of that level (overzoom): parcels stay visible at zoom 18.

The tilesets explorer lists every published tileset with its coverage, and previews the tiles with your key.

osm-world layers

osm-world follows the Protomaps basemap schema, version 4, and is updated regularly from OpenStreetMap. The kind field gives the type of each feature (park, forest, highway, rail, locality…); the Protomaps documentation lists its values.

Source layerContentsMain fieldsZooms
earthLand polygons and islandsname, name:{lang}, kind0–15
waterOceans, lakes, rivers and streams, as polygons and linesname, name:{lang}, kind, kind_detail, min_zoom, bridge, tunnel, layer, sort_rank0–15
landcoverGeneralised land cover for small scaleskind0–7
landuseParks, forests, farmland, residential and industrial areas, airports…kind, sort_rank2–15
roadsRoads, paths and railwaysname, name:{lang}, kind, kind_detail, min_zoom, is_bridge, is_tunnel, is_link, oneway, access, ref, network, shield_text, sort_rank3–15
buildingsBuilding footprints and house numberskind, kind_detail, height, min_height, addr_housenumber, layer, sort_rank11–15
poisPoints of interest: shops, schools, stations, peaks…name, name:{lang}, kind, kind_detail, min_zoom, elevation, iata5–15
placesCountries, regions, cities, towns, villages and neighbourhoodsname, name:{lang}, kind, kind_detail, min_zoom, capital, population, population_rank, sort_key, wikidata1–15
boundariesCountry and region boundarieskind, kind_detail, disputed, brk_a3, sort_rank0–15

Cadastre layers

cadastre-fr and cadastre-de merge the parcel datasets of each publisher into one tileset with the same fields:

FieldMeaning
idIdentifier of the parcel
source_idIdentifier of the parcel in the publisher's data
display_idParcel reference as displayed by the publisher

A field is left out of a feature when the publisher's data has no value for it.

TileJSON and tiles

GET /v1/tiles/{tileset}.json?key=tpk_…
GET /v1/tiles/{tileset}/{z}/{x}/{y}.mvt?key=tpk_…

The TileJSON (version 3.0.0) gives the tile URL, zoom range, bounds, vector_layers and the attribution to display. It is cached for 5 minutes. A tile with no data answers 204 No Content.

Add the cadastre over the basemap

Parcel outlines, their reference from zoom 16, and the fields of a parcel on click. The parcels are inserted below the basemap's labels so that street and place names stay readable.

map.on("style.load", () => {
  map.addSource("cadastre", {
    type: "vector",
    url: "https://tiles.topoexport.com/v1/tiles/cadastre-fr.json?key=tpk_YOUR_KEY",
  });
  // first symbol layer of the basemap: everything inserted before it stays under the labels
  const firstLabel = map.getStyle().layers.find((layer) => layer.type === "symbol")?.id;

  map.addLayer({
    id: "parcels-fill",
    type: "fill",
    source: "cadastre",
    "source-layer": "cadastre-fr",
    minzoom: 12,
    paint: { "fill-color": "#c2410c", "fill-opacity": 0.04 },
  }, firstLabel);
  map.addLayer({
    id: "parcels-outline",
    type: "line",
    source: "cadastre",
    "source-layer": "cadastre-fr",
    minzoom: 12,
    paint: { "line-color": "#c2410c", "line-width": 0.8 },
  }, firstLabel);
  map.addLayer({
    id: "parcels-label",
    type: "symbol",
    source: "cadastre",
    "source-layer": "cadastre-fr",
    minzoom: 16,
    layout: { "text-field": ["get", "display_id"], "text-font": ["Noto Sans Regular"], "text-size": 11 },
    paint: { "text-color": "#9a3412", "text-halo-color": "#ffffff", "text-halo-width": 1 },
  });
});

map.on("click", "parcels-fill", (event) => {
  const { id, source_id, display_id } = event.features[0].properties;
  new maplibregl.Popup().setLngLat(event.lngLat).setText(`${display_id ?? id} (${source_id ?? "no source id"})`).addTo(map);
});

The style's fonts are served by the same host: use Noto Sans Regular (or another font of the style) for your own labels.

QGIS, ArcGIS Pro and scripts

Some clients drop the query string or send no Origin header. Put the key in the path instead, and use a key with no origin restriction:

https://tiles.topoexport.com/v1/k/tpk_YOUR_KEY/tiles/cadastre-fr.json
https://tiles.topoexport.com/v1/k/tpk_YOUR_KEY/tiles/osm-world/{z}/{x}/{y}.mvt
  • QGIS: Vector Tiles → New Generic Connection, with the tile URL above.
  • ArcGIS Pro: add a vector tile layer from the TileJSON URL.

Relative URLs inside a TileJSON or a tileset.json keep the /v1/k/{key} prefix, so every request stays authenticated.

3D Tiles and terrain

GET /v1/tiles3d/{tileset}/tileset.json?key=tpk_…
GET /v1/terrain/{version}/layer.json?key=tpk_…

3D Tiles are served as tileset.json plus glTF content; terrain as quantized-mesh (layer.json plus .terrain tiles). Cesium carries the key of the root URL to every child resource. For ArcGIS, use the key-in-path form. 3D Tiles and terrain are published per area: ask us which tilesets cover yours.

const tileset = await Cesium.Cesium3DTileset.fromUrl(
  "https://tiles.topoexport.com/v1/tiles3d/YOUR_TILESET/tileset.json?key=tpk_YOUR_KEY",
);
viewer.scene.primitives.add(tileset);

viewer.terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(
  new Cesium.Resource({
    url: "https://tiles.topoexport.com/v1/terrain/YOUR_TERRAIN_VERSION",
    queryParameters: { key: "tpk_YOUR_KEY" },
  }),
);

Credits

RequestCredits
Vector tile1
Terrain tile1
3D tile resource3
Styles, fonts, metadataFree

Each response carries headers with the plan of the key and the credits the request consumed.

Rate limits

Map tiles follow the common rate limits and fair use rules. A map library requests the tiles of a view in parallel: this is expected and well within the limits.

Attribution

The attribution field of each TileJSON, and the sources of each style, list what to display: OpenStreetMap contributors for the basemap, the cadastre publishers for the overlays. MapLibre GL JS and Mapbox GL JS display them in their attribution control. On the API Trial and API plans, add © TopoExport.