Skip to content

Add NIMBO to a MapLibre GL JS web map

The preview deliberately keeps a reference map beneath NIMBO so that administrative context, roads and place names remain visible. Use the control in the preview to switch between NIMBO + reference map and NIMBO only.

The preview uses the public July 2023 NIMBO sandbox. It adds an OpenStreetMap reference layer only to preserve geographic context; that reference layer is not part of NIMBO.

Already have a MapLibre map? This is the essential configuration used by the preview, including its optional reference layer:

import * as maplibregl from 'https://cdn.jsdelivr.net/npm/maplibre-gl@6.3.0/dist/maplibre-gl.mjs';
const NIMBO_DEMO_TMS =
'https://prod-data.nimbo.earth/mapcache/tms/1.0.0/demo_2023_7_1@kermap/{z}/{x}/{y}.png?kermap_token=750bf73bb0f6cb9639286ea471b05e335dd4595ce1';
const map = new maplibregl.Map({
container: 'map',
center: [2.35, 46.5],
zoom: 5,
style: {
version: 8,
sources: {
reference: {
type: 'raster',
tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '© OpenStreetMap contributors',
},
nimbo: {
type: 'raster',
tiles: [NIMBO_DEMO_TMS],
tileSize: 256,
scheme: 'tms',
attribution:
'NIMBO by KERMAP — Contains modified Copernicus Sentinel data 2023.',
},
},
layers: [
{
id: 'reference-map',
type: 'raster',
source: 'reference',
},
{
id: 'nimbo-basemap',
type: 'raster',
source: 'nimbo',
paint: {
'raster-opacity': 0.82,
},
},
],
},
});
const referenceToggle = document.getElementById('reference-toggle');
referenceToggle.addEventListener('change', () => {
const showReference = referenceToggle.checked;
map.setLayoutProperty(
'reference-map',
'visibility',
showReference ? 'visible' : 'none',
);
map.setPaintProperty(
'nimbo-basemap',
'raster-opacity',
showReference ? 0.82 : 1,
);
});

Set the NIMBO raster opacity to 1 and hide or remove reference-map when you want to display NIMBO alone. In a production application, a better pattern is often to keep NIMBO fully opaque and add only the required vector boundaries, labels or operational layers above it.

The important MapLibre-specific detail is the combination of {y} in the URL and scheme: 'tms' in the source.

You need:

  • a modern web browser;
  • a text editor;
  • Python or another simple local HTTP server;
  • approximately five minutes for the sandbox test.

You do not need Node.js, npm or a NIMBO account for the standalone sandbox example.

  1. Create a project folder

    Create an empty folder named nimbo-maplibre.

  2. Create index.html

    Add the following complete page to the folder:

    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>NIMBO with MapLibre GL JS</title>
    <link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.3.0/dist/maplibre-gl.css"
    />
    <style>
    html,
    body,
    #map {
    width: 100%;
    height: 100%;
    margin: 0;
    }
    .reference-control {
    position: absolute;
    z-index: 2;
    top: 12px;
    left: 12px;
    display: flex;
    gap: 7px;
    align-items: center;
    padding: 7px 10px;
    border-radius: 6px;
    background: rgb(255 255 255 / 94%);
    color: #17202a;
    font: 600 12px/1.35 system-ui, sans-serif;
    box-shadow: 0 1px 5px rgb(0 0 0 / 25%);
    cursor: pointer;
    }
    </style>
    </head>
    <body>
    <div id="map" aria-label="NIMBO satellite map"></div>
    <label class="reference-control">
    <input id="reference-toggle" type="checkbox" checked disabled />
    Reference map
    </label>
    <script type="module">
    import * as maplibregl from 'https://cdn.jsdelivr.net/npm/maplibre-gl@6.3.0/dist/maplibre-gl.mjs';
    const NIMBO_DEMO_TMS =
    'https://prod-data.nimbo.earth/mapcache/tms/1.0.0/demo_2023_7_1@kermap/{z}/{x}/{y}.png?kermap_token=750bf73bb0f6cb9639286ea471b05e335dd4595ce1';
    const REFERENCE_OPACITY = 0.82;
    const map = new maplibregl.Map({
    container: 'map',
    style: {
    version: 8,
    sources: {
    reference: {
    type: 'raster',
    tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
    tileSize: 256,
    maxzoom: 19,
    attribution: '© OpenStreetMap contributors',
    },
    nimbo: {
    type: 'raster',
    tiles: [NIMBO_DEMO_TMS],
    tileSize: 256,
    scheme: 'tms',
    minzoom: 0,
    maxzoom: 18,
    attribution:
    'NIMBO by KERMAP — Contains modified Copernicus Sentinel data 2023.',
    },
    },
    layers: [
    {
    id: 'reference-map',
    type: 'raster',
    source: 'reference',
    },
    {
    id: 'nimbo-basemap',
    type: 'raster',
    source: 'nimbo',
    paint: {
    'raster-opacity': REFERENCE_OPACITY,
    },
    },
    ],
    },
    center: [2.35, 46.5],
    zoom: 5,
    maxZoom: 18,
    });
    window.nimboMap = map;
    map.addControl(new maplibregl.NavigationControl(), 'top-right');
    map.addControl(new maplibregl.ScaleControl(), 'bottom-left');
    const referenceToggle = document.getElementById('reference-toggle');
    map.once('load', () => {
    referenceToggle.disabled = false;
    });
    referenceToggle.addEventListener('change', () => {
    const showReference = referenceToggle.checked;
    map.setLayoutProperty(
    'reference-map',
    'visibility',
    showReference ? 'visible' : 'none',
    );
    map.setPaintProperty(
    'nimbo-basemap',
    'raster-opacity',
    showReference ? REFERENCE_OPACITY : 1,
    );
    });
    map.on('error', (event) => {
    console.error('MapLibre or NIMBO tile error:', event.error ?? event);
    });
    </script>
    </body>
    </html>
  3. Start a local web server

    Open a terminal in the nimbo-maplibre folder and run one of these commands.

    On Windows:

    Terminal window
    py -m http.server 8000

    On macOS or Linux:

    Terminal window
    python3 -m http.server 8000
  4. Open the map

    Open http://localhost:8000/ in your browser.

You should see the same France-centred view as in the OpenLayers example, with NIMBO displayed above an OpenStreetMap reference layer. Pan and zoom to confirm that additional tiles load correctly.

2. Understand the MapLibre source configuration

Section titled “2. Understand the MapLibre source configuration”

The NIMBO layer is a MapLibre raster source referenced by a raster layer.

SettingWhy it matters
type: 'raster'Tells MapLibre that NIMBO and the optional reference map return rendered raster images rather than vector tiles.
tiles: [NIMBO_DEMO_TMS]Provides the TMS URL template used for tile requests.
tileSize: 256Matches the size of NIMBO map tiles. MapLibre otherwise assumes a different raster tile size.
scheme: 'tms'Converts MapLibre’s tile coordinates to the bottom-left TMS Y-axis expected by the NIMBO endpoint.
minzoom and maxzoomPrevent unnecessary requests outside the documented tile pyramid.
raster-opacity: 0.82Keeps the optional reference layer visible in this tutorial. Use 1 for NIMBO alone.
attributionDisplays the NIMBO, Copernicus and reference-map credits with the map.

The URL must contain {y} because MapLibre performs the inversion through scheme: 'tms'.

// Correct for MapLibre
{
tiles: ['https://.../{z}/{x}/{y}.png?...'],
scheme: 'tms',
}

Do not use {-y} in this MapLibre source. That syntax is used in the OpenLayers tutorial.

3. Confirm that the sandbox test succeeded

Section titled “3. Confirm that the sandbox test succeeded”

Open the browser developer tools and check the Console and Network panels.

A successful test has the following characteristics:

  • the map container fills the browser window;
  • the July 2023 watermarked basemap appears across France;
  • the Reference map control reveals borders, roads and place names;
  • requests target demo_2023_7_1@kermap;
  • moving the map requests new PNG tiles;
  • no authentication, CORS or tile-coordinate errors appear.

4. Switch to a layer from your NIMBO account

Section titled “4. Switch to a layer from your NIMBO account”

The sandbox is deliberately limited to one fixed month. To display your own authorized NIMBO layer:

  1. create or open your account in NIMBO Earth Online;
  2. copy your token from the dashboard;
  3. choose the year, month and layer code;
  4. replace the sandbox tile URL.

Production monthly layer names follow this structure:

<YEAR>_<MONTH>_<LAYER_CODE>@kermap

Use this helper to build a TMS URL:

function buildNimboTmsUrl({ year, month, layerCode, token }) {
if (!Number.isInteger(year) || year < 1) {
throw new Error('year must be a positive integer');
}
if (!Number.isInteger(month) || month < 1 || month > 12) {
throw new Error('month must be an integer between 1 and 12');
}
if (!Number.isInteger(layerCode) || layerCode < 1) {
throw new Error('layerCode must be a positive integer');
}
if (typeof token !== 'string' || token.trim() === '') {
throw new Error('A NIMBO API token is required');
}
const layerName = `${year}_${month}_${layerCode}@kermap`;
return (
`https://prod-data.nimbo.earth/mapcache/tms/1.0.0/${layerName}` +
`/{z}/{x}/{y}.png?kermap_token=${encodeURIComponent(token.trim())}`
);
}

When the selected month changes, replace the raster source so that both the tile URL and the visible attribution stay synchronized:

function setNimboLayer({ year, month, layerCode, token }) {
const sourceId = 'nimbo';
const layerId = 'nimbo-basemap';
const tileUrl = buildNimboTmsUrl({ year, month, layerCode, token });
// Keep the reference map below NIMBO and operational layers above it.
const referenceLayerId = 'reference-map';
const beforeId = map
.getStyle()
.layers.find(
(layer) => ![referenceLayerId, layerId].includes(layer.id),
)?.id;
if (map.getLayer(layerId)) {
map.removeLayer(layerId);
}
if (map.getSource(sourceId)) {
map.removeSource(sourceId);
}
map.addSource(sourceId, {
type: 'raster',
tiles: [tileUrl],
tileSize: 256,
scheme: 'tms',
minzoom: 0,
maxzoom: 18,
attribution:
`NIMBO by KERMAP — Contains modified Copernicus Sentinel data ${year}.`,
});
const referenceVisible =
map.getLayer(referenceLayerId) &&
map.getLayoutProperty(referenceLayerId, 'visibility') !== 'none';
const layer = {
id: layerId,
type: 'raster',
source: sourceId,
paint: {
'raster-opacity': referenceVisible ? 0.82 : 1,
},
};
if (beforeId) {
map.addLayer(layer, beforeId);
} else {
map.addLayer(layer);
}
}
map.once('load', () => {
setNimboLayer({
year: 2023,
month: 7,
layerCode: 1,
token: '<YOUR_TOKEN>',
});
});

Replace the example date and layer code with values available to your account. When your application lets users select another date, call setNimboLayer(...) again with the new values.

CodeLayer
1RGB — 10 m
2NIR — 10 m
3NDVI — 10 m
4Radar
5NIMBO HD — 2.5 m
6Traceability

Read Authentication and token security for the complete policy.

NIMBO is generally most useful as a consistent monthly basemap beneath your assets, parcels, sites or analytical results.

Add the following code inside a map.on('load', ...) handler to place a sample project location above the raster layer:

map.on('load', () => {
map.addSource('project-sites', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {
name: 'Example project site',
},
geometry: {
type: 'Point',
coordinates: [-1.6778, 48.1173],
},
},
],
},
});
map.addLayer({
id: 'project-sites',
type: 'circle',
source: 'project-sites',
paint: {
'circle-radius': 7,
'circle-color': '#e63946',
'circle-stroke-color': '#ffffff',
'circle-stroke-width': 2,
},
});
});

Replace the inline GeoJSON with your own API, file or application data. Because this layer is added after nimbo-basemap, it renders above the satellite imagery.

The public sandbox does not consume GeoCredits from your account. Authenticated production requests do: each new tile requested from NIMBO consumes 1 GeoCredit.

A web map normally requests several tiles to fill the viewport. New requests can be triggered when a user:

  • pans or zooms;
  • changes month or layer;
  • opens a second comparison map;
  • reloads tiles that are no longer available in the browser cache.

Read Tiles and GeoCredits before estimating production traffic.

SymptomLikely causeCorrection
The live preview is blank but the standalone example worksThe old srcdoc preview is still being used, or the static demo file is missing.Use src="/demos/nimbo-maplibre.html" and copy the supplied HTML file into public/demos/.
The page is blankThe map container has no height, or the page was opened with file://.Keep the full-height CSS and use a local HTTP server.
Borders and place names are no longer visibleNIMBO is fully opaque or the reference layer is hidden.Enable reference-map and use a NIMBO raster opacity below 1, or add vector boundaries and labels above NIMBO.
Imagery is vertically inverted or repeatedThe source is using the wrong Y-axis convention.Keep {y} in the URL and set scheme: 'tms'.
Tiles appear at the wrong scaletileSize is missing or incorrect.Set tileSize: 256.
Sandbox requests failThe demo URL was modified or the public layer is unavailable.Copy the sandbox URL again and inspect the first failed request in the Network panel.
Authenticated requests return 401 or 403The token is invalid, incomplete, expired or not authorized for the layer.Test the token and verify the layer in your NIMBO dashboard.
A month returns blank tilesThe layer name is invalid or the month is not available for the selected product and territory.Verify the catalog and use <YEAR>_<MONTH>_<LAYER_CODE>@kermap.
The prototype works but cannot be deployed safelyThe personal token is exposed in browser code.Define a protected production architecture with NIMBO.

From technical test to production integration

Section titled “From technical test to production integration”

Before deploying, define:

  • who will access the map: one internal user, a team, clients or the public;
  • which NIMBO layers and dates are required;
  • expected regions, zoom levels, sessions and tile volume;
  • token protection, caching and monitoring;
  • attribution, client-facing and redistribution rights.

A Pro or Pro HD plan covers defined professional and static-deliverable uses. Interactive client-facing applications, SaaS, OEM integration, redistribution and multi-user deployments require an Enterprise agreement.

Create or open your NIMBO account

Discuss a MapLibre production integration