Map tiles
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);
import { apply } from "ol-mapbox-style";
import { fromLonLat } from "ol/proj";
// creates the OpenLayers map in the #map element from the style
const map = await apply("map", "https://tiles.topoexport.com/v1/styles/osm-world/light.json?key=tpk_YOUR_KEY&lang=en");
map.getView().setCenter(fromLonLat([2.35, 48.86]));
map.getView().setZoom(12);
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
| flavor | Look |
|---|---|
positron | TopoExport's own light theme: off-white land, white roads with soft grey outlines, pale green parks and blue-grey water. |
light | Colourful light theme: beige land, light blue water, green parks and white roads. |
dark | Dark grey land and water, for dark interfaces and data overlays. |
white | Almost entirely white, with light grey water and roads: a quiet background for your own data. |
grayscale | Shades of grey only, with no colour. |
black | Near-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
400with the supported values:
{ "error": "Unsupported lang", "supported": ["ar", "cs", "bg", "…"] }
| lang | Language | Script |
|---|---|---|
ar | Arabic | Arabic |
bg | Bulgarian | Cyrillic |
cs | Czech | Latin |
da | Danish | Latin |
de | German | Latin |
el | Greek | Greek |
en | English | Latin |
es | Spanish | Latin |
et | Estonian | Latin |
fa | Persian | Arabic |
fi | Finnish | Latin |
fr | French | Latin |
ga | Irish | Latin |
he | Hebrew | Hebrew |
hi | Hindi | Devanagari |
hr | Croatian | Latin |
hu | Hungarian | Latin |
id | Indonesian | Latin |
it | Italian | Latin |
ja | Japanese | Japanese (kana and kanji) |
ko | Korean | Hangul |
lt | Lithuanian | Latin |
lv | Latvian | Latin |
mr | Marathi | Devanagari |
mt | Maltese | Latin |
ne | Nepali | Devanagari |
nl | Dutch | Latin |
no | Norwegian | Latin |
pl | Polish | Latin |
pt | Portuguese | Latin |
ro | Romanian | Latin |
ru | Russian | Cyrillic |
sk | Slovak | Latin |
sl | Slovenian | Latin |
sv | Swedish | Latin |
tr | Turkish | Latin |
uk | Ukrainian | Cyrillic |
ur | Urdu | Arabic |
vi | Vietnamese | Latin |
zh-Hans | Chinese (Simplified) | Han |
zh-Hant | Chinese (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
| Tileset | Contents | Source layers | Zooms |
|---|---|---|---|
osm-world | Worldwide basemap from OpenStreetMap and Natural Earth | 9 layers, see below | 0 to 15 |
cadastre-fr | Parcels of the French cadastre | cadastre-fr | 12 to 15 |
cadastre-de | Parcels (Flurstücke) published by the German Länder | cadastre-de | 12 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 layer | Contents | Main fields | Zooms |
|---|---|---|---|
earth | Land polygons and islands | name, name:{lang}, kind | 0–15 |
water | Oceans, lakes, rivers and streams, as polygons and lines | name, name:{lang}, kind, kind_detail, min_zoom, bridge, tunnel, layer, sort_rank | 0–15 |
landcover | Generalised land cover for small scales | kind | 0–7 |
landuse | Parks, forests, farmland, residential and industrial areas, airports… | kind, sort_rank | 2–15 |
roads | Roads, paths and railways | name, name:{lang}, kind, kind_detail, min_zoom, is_bridge, is_tunnel, is_link, oneway, access, ref, network, shield_text, sort_rank | 3–15 |
buildings | Building footprints and house numbers | kind, kind_detail, height, min_height, addr_housenumber, layer, sort_rank | 11–15 |
pois | Points of interest: shops, schools, stations, peaks… | name, name:{lang}, kind, kind_detail, min_zoom, elevation, iata | 5–15 |
places | Countries, regions, cities, towns, villages and neighbourhoods | name, name:{lang}, kind, kind_detail, min_zoom, capital, population, population_rank, sort_key, wikidata | 1–15 |
boundaries | Country and region boundaries | kind, kind_detail, disputed, brk_a3, sort_rank | 0–15 |
Cadastre layers
cadastre-fr and cadastre-de merge the parcel datasets of each publisher into one tileset with the same fields:
| Field | Meaning |
|---|---|
id | Identifier of the parcel |
source_id | Identifier of the parcel in the publisher's data |
display_id | Parcel 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
| Request | Credits |
|---|---|
| Vector tile | 1 |
| Terrain tile | 1 |
| 3D tile resource | 3 |
| Styles, fonts, metadata | Free |
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.