Map tiles in MapLibre, Leaflet and QGIS
The tile routes render a preset, a named recipe of bands, stretch and colour map, as a mosaic of the scenes in a time window.
Presets
Section titled “Presets”Available now
| Preset | Shows | Sentinel-2 | Landsat 8-9 |
|---|---|---|---|
true-color |
Natural colour | the visual (TCI) asset | red, green, blue |
false-color |
Vegetation in red | nir (B08), red, green |
nir08, red, green |
ndvi |
Vegetation index, -0.2 to 0.8 on a red-yellow-green ramp | nir (B08), red |
nir08, red |
The recipes are the web map’s. On Sentinel-2 they use the 10 m broad NIR band, B08, rather than the cross-sensor nir08; see Presets.
Coming later ndwi, ndmi (moisture), nbr (burn ratio) and swir on optical collections, and hillshade and elevation on the DEM.
The tile URL
Section titled “The tile URL”https://api.satellitedata.net/v1/tiles/{presetId}/{tileMatrixSetId}/{z}/{x}/{y}.{format} ?collection=sentinel-2-l2a &datetime=2026-06-01T00:00:00Z/2026-08-31T23:59:59Z &maxCloudCoverage=20tileMatrixSetId:WebMercatorQuadfor web maps, orWorldCRS84Quad.format:png,jpgorwebp(ndviservespngandwebp).collection:sentinel-2-l2a(the default) orlandsat-ot-l2, or an alias.datetime: the window to mosaic over; the default is the last 30 days.maxCloudCoverage: skip scenes with more cloud than this percentage; the default is the preset’s (30, or 60 forndvi).tilesize:256(default) or512.
Each tile mosaics up to 50 scenes from the window, least cloudy first. There is no pixel cloud mask yet, so pick a window and ceiling that leave a clear scene on top.
Zoom ranges: Sentinel-2 8 to 15, Landsat 7 to 14. Outside them a tile answers 400 zoom-out-of-range.
Tiles need a key. The TileJSON document that describes them does not.
TileJSON
Section titled “TileJSON”curl -s "https://api.satellitedata.net/v1/tiles/ndvi/WebMercatorQuad/tilejson.json?collection=landsat-ot-l2&datetime=2026-06-01T00:00:00Z/2026-08-31T23:59:59Z&maxCloudCoverage=40"The answer is TileJSON 3.0.0. Its tiles template repeats your query parameters, so the map library requests exactly the mosaic you asked for, and it carries minzoom, maxzoom, bounds and attribution. See TileJSON.
MapLibre GL JS
Section titled “MapLibre GL JS”MapLibre loads TileJSON as a raster source. Add the key to requests for the API with transformRequest:
const map = new maplibregl.Map({ container: 'map', center: [-93.25, 44.95], zoom: 11, style: { version: 8, sources: {}, layers: [] }, transformRequest: (url) => url.startsWith('https://api.satellitedata.net/') ? { url, headers: { 'X-API-Key': apiKey } } : { url },});
map.on('load', () => { map.addSource('ndvi', { type: 'raster', url: 'https://api.satellitedata.net/v1/tiles/ndvi/WebMercatorQuad/tilejson.json' + '?collection=sentinel-2-l2a&datetime=2026-06-01T00:00:00Z/2026-08-31T23:59:59Z&maxCloudCoverage=20', tileSize: 256, }); map.addLayer({ id: 'ndvi', type: 'raster', source: 'ndvi' });});MapLibre shows the TileJSON attribution in its attribution control. A full page is in the Quickstart.
Leaflet
Section titled “Leaflet”Leaflet’s L.tileLayer cannot send headers, so fetch each tile yourself:
const KeyedTileLayer = L.TileLayer.extend({ createTile(coords, done) { const img = document.createElement('img'); img.alt = ''; fetch(this.getTileUrl(coords), { headers: { 'X-API-Key': apiKey } }) .then((r) => { if (!r.ok) throw new Error(`tile ${r.status}`); return r.blob(); }) .then((blob) => { img.onload = () => URL.revokeObjectURL(img.src); img.src = URL.createObjectURL(blob); done(null, img); }) .catch((err) => done(err, img)); return img; },});
const map = L.map('map').setView([44.95, -93.25], 11);new KeyedTileLayer( 'https://api.satellitedata.net/v1/tiles/true-color/WebMercatorQuad/{z}/{x}/{y}.png' + '?collection=sentinel-2-l2a&datetime=2026-06-01T00:00:00Z/2026-08-31T23:59:59Z&maxCloudCoverage=20', { minZoom: 8, // Sentinel-2: 8 to 15; Landsat: 7 to 14 maxZoom: 15, attribution: 'Contains modified Copernicus Sentinel data 2026', },).addTo(map);Set minZoom and maxZoom from the collection’s zoom range, which the TileJSON reports. The attribution line is required; see Attribution.
-
In the Browser panel, right-click XYZ Tiles and choose New Connection.
-
Paste the tile URL with
{z}/{x}/{y}placeholders and your query parameters, for example:https://api.satellitedata.net/v1/tiles/false-color/WebMercatorQuad/{z}/{x}/{y}.png?collection=landsat-ot-l2&datetime=2026-06-01T00:00:00Z/2026-08-31T23:59:59Z&maxCloudCoverage=30 -
Set the minimum and maximum zoom to the collection’s range (8 to 15 for Sentinel-2, 7 to 14 for Landsat).
-
Add an HTTP header
X-API-Keywith your key. Recent QGIS versions have an HTTP headers section in the connection dialog; without it, QGIS cannot send the key. -
Double-click the connection to add the layer.
Coming later A WMTS capabilities document per preset (.../WMTSCapabilities.xml), so QGIS and ArcGIS Pro can add a layer from one URL.
- Zoom. Tiles exist only within the collection’s zoom range.
- Time windows. A short window may leave gaps where no scene passed; a long one costs more scenes to read per tile and is slower. A few weeks to a summer is a good start.
- Rate limits. A pan or zoom asks for 20 to 60 tiles at once. The per-key limit of 50 requests a second allows for that; see Limits.
- One scene, not a mosaic. Use item previews for a quick image of one scene, or the older tiles host, which renders single scenes by id.
Coming later
Section titled “Coming later”harmonizelevels beyondoffset_removed, and a pixel cloud mask per preset.- Anonymous tiles served from the CDN cache, with cache headers by window age.