Votre carte MapLibre est en place. Voici dix outils pour l'enrichir, du plus simple au plus spectaculaire. Chaque exemple est autonome et suppose que la variable map existe ; ceux qui ajoutent des sources ou des couches se placent dans map.on('load', …). Beaucoup sont utilisés dans les outils de CartoForge : inspectez leur code source !
1. Les contrôles natifs
MapLibre fournit d'office zoom et boussole, position GPS, plein écran et échelle :
map.addControl(new maplibregl.NavigationControl({ visualizePitch: true }), 'top-right');
map.addControl(new maplibregl.GeolocateControl({
positionOptions: { enableHighAccuracy: true },
trackUserLocation: true // suit la position en continu (randonnée, terrain)
}), 'top-right');
map.addControl(new maplibregl.FullscreenControl(), 'top-right');
map.addControl(new maplibregl.ScaleControl({ unit: 'metric' }), 'bottom-left');
La géolocalisation n'est autorisée que sur une page en https (ou localhost).
2. Recherche d'adresse (API Adresse)
La Base Adresse Nationale, interrogeable sans clé. On vole vers le résultat et on y pose un marqueur :
<input id="adresse" type="search" placeholder="Rechercher une adresse…"
style="position:absolute;top:10px;left:10px;z-index:1;width:280px;padding:8px 10px;border-radius:8px;border:1px solid #ccc">
let marqueur = null;
document.getElementById('adresse').addEventListener('keydown', async (e) => {
if (e.key !== 'Enter' || !e.target.value.trim()) return;
const url = 'https://api-adresse.data.gouv.fr/search/?limit=1&q=' + encodeURIComponent(e.target.value);
const res = await (await fetch(url)).json();
if (!res.features.length) return alert('Adresse introuvable');
const f = res.features[0];
map.flyTo({ center: f.geometry.coordinates, zoom: 18 }); // déjà en [lon, lat]
if (marqueur) marqueur.remove();
marqueur = new maplibregl.Marker().setLngLat(f.geometry.coordinates)
.setPopup(new maplibregl.Popup().setText(f.properties.label)).addTo(map).togglePopup();
});
3. Photos aériennes IGN et sélecteur de fonds
Les services de la Géoplateforme sont libres d'accès. On déclare plusieurs fonds dans le style et on n'en affiche qu'un :
const ign = (couche, format) => 'https://data.geopf.fr/wmts?layer=' + couche +
'&style=normal&tilematrixset=PM&Service=WMTS&Request=GetTile&Version=1.0.0&Format=' + format +
'&TileMatrix={z}&TileCol={x}&TileRow={y}';
map.on('load', () => {
map.addSource('ortho', { type: 'raster', tileSize: 256, maxzoom: 19, attribution: '© IGN',
tiles: [ign('ORTHOIMAGERY.ORTHOPHOTOS', 'image/jpeg')] });
map.addSource('plan', { type: 'raster', tileSize: 256, maxzoom: 19, attribution: '© IGN',
tiles: [ign('GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2', 'image/png')] });
map.addLayer({ id: 'ortho', type: 'raster', source: 'ortho' });
map.addLayer({ id: 'plan', type: 'raster', source: 'plan', layout: { visibility: 'none' } });
});
function choisirFond(id) { // ex. choisirFond('plan')
['ortho', 'plan'].forEach((f) => map.setLayoutProperty(f, 'visibility', f === id ? 'visible' : 'none'));
}
Le cadastre de la DGFiP s'ajoute de la même façon, en WMS, avec {bbox-epsg-3857} dans l'URL : voir le tutoriel Photos aériennes IGN et cadastre, dont les URL sont identiques.
4. Clusters natifs
Pas de plugin : la source GeoJSON regroupe elle-même les points. Un clic sur un groupe zoome dessus.
map.on('load', () => {
map.addSource('arbres', { type: 'geojson', data: 'arbres.geojson', cluster: true, clusterRadius: 50, clusterMaxZoom: 16 });
map.addLayer({ id: 'groupes', type: 'circle', source: 'arbres', filter: ['has', 'point_count'],
paint: {
'circle-color': ['step', ['get', 'point_count'], '#7bccc4', 50, '#2b8cbe', 500, '#084081'],
'circle-radius': ['step', ['get', 'point_count'], 16, 50, 22, 500, 30]
} });
map.addLayer({ id: 'arbres', type: 'circle', source: 'arbres', filter: ['!', ['has', 'point_count']],
paint: { 'circle-color': '#2fb673', 'circle-radius': 5, 'circle-stroke-color': '#fff', 'circle-stroke-width': 1 } });
});
map.on('click', 'groupes', async (e) => {
const f = e.features[0];
const zoom = await map.getSource('arbres').getClusterExpansionZoom(f.properties.cluster_id);
map.easeTo({ center: f.geometry.coordinates, zoom });
});
Le nombre de points d'un groupe s'affiche avec une couche symbol ('text-field': ['get', 'point_count_abbreviated']), ce qui demande des polices : voir l'outil n°7.
5. Carte de chaleur native
map.on('load', () => {
map.addSource('accidents', { type: 'geojson', data: 'accidents.geojson' });
map.addLayer({
id: 'chaleur', type: 'heatmap', source: 'accidents', maxzoom: 16,
paint: {
'heatmap-radius': ['interpolate', ['linear'], ['zoom'], 6, 8, 14, 30],
'heatmap-intensity': ['interpolate', ['linear'], ['zoom'], 6, 1, 14, 3],
'heatmap-opacity': ['interpolate', ['linear'], ['zoom'], 14, 0.9, 16, 0] // s'efface en zoomant
}
});
// au zoom fort, on montre les points eux-mêmes
map.addLayer({ id: 'points', type: 'circle', source: 'accidents', minzoom: 14,
paint: { 'circle-radius': 4, 'circle-color': '#e0553d' } });
});
6. Survol en surbrillance
L'« état » d'un objet (feature-state) change son style sans recharger les données. Il faut un identifiant par objet : generateId: true le crée.
map.on('load', () => {
map.addSource('parcelles', { type: 'geojson', data: 'parcelles.geojson', generateId: true });
map.addLayer({ id: 'parcelles', type: 'fill', source: 'parcelles', paint: {
'fill-color': '#8b6cff',
'fill-opacity': ['case', ['boolean', ['feature-state', 'survol'], false], 0.6, 0.2]
} });
});
let survol = null;
map.on('mousemove', 'parcelles', (e) => {
if (survol !== null) map.setFeatureState({ source: 'parcelles', id: survol }, { survol: false });
survol = e.features[0].id;
map.setFeatureState({ source: 'parcelles', id: survol }, { survol: true });
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'parcelles', () => {
if (survol !== null) map.setFeatureState({ source: 'parcelles', id: survol }, { survol: false });
survol = null;
map.getCanvas().style.cursor = '';
});
La visionneuse SIG utilise ce mécanisme pour surligner l'objet sélectionné dans la table attributaire.
7. Étiquettes depuis les attributs
Les textes sont dessinés par la carte, sans chevauchement. Il faut indiquer au style où trouver les polices (glyphs), par exemple celles du projet MapLibre :
// dans l'objet style : glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf'
map.addLayer({
id: 'numeros', type: 'symbol', source: 'parcelles', minzoom: 16,
layout: {
'text-field': ['concat', ['get', 'section'], ' ', ['get', 'numero']],
'text-font': ['Noto Sans Regular'],
'text-size': 13
},
paint: { 'text-color': '#111', 'text-halo-color': '#fff', 'text-halo-width': 1.5 }
});
C'est ce que fait « Étiqueter selon » dans la visionneuse, le KML / GPX et le DXF de CartoForge.
8. Mesurer distances et surfaces (Turf.js)
Turf apporte les calculs géographiques : longueurs, surfaces, tampons, intersections…
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@7.1.0/turf.min.js"></script>
map.doubleClickZoom.disable();
let pts = [];
map.on('load', () => {
map.addSource('mesure', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
map.addLayer({ id: 'mesure-surface', type: 'fill', source: 'mesure', filter: ['==', ['geometry-type'], 'Polygon'],
paint: { 'fill-color': '#8b6cff', 'fill-opacity': 0.15 } });
map.addLayer({ id: 'mesure-trait', type: 'line', source: 'mesure',
paint: { 'line-color': '#8b6cff', 'line-width': 3, 'line-dasharray': [2, 2] } });
});
map.on('click', (e) => {
pts.push([e.lngLat.lng, e.lngLat.lat]);
const ligne = turf.lineString(pts.length > 1 ? pts : [pts[0], pts[0]]);
const feats = [ligne];
let texte = `${(turf.length(ligne, { units: 'kilometers' }) * 1000).toFixed(1)} m`;
if (pts.length > 2) {
const poly = turf.polygon([[...pts, pts[0]]]);
feats.push(poly);
texte += ` · ${turf.area(poly).toFixed(0)} m²`; // surface en m²
}
map.getSource('mesure').setData(turf.featureCollection(feats));
console.log(texte); // ou dans un bandeau / une popup
});
map.on('dblclick', () => { pts = []; map.getSource('mesure').setData(turf.featureCollection([])); });
9. Relief en 3D
Une source d'altitudes (raster-dem) donne du relief au terrain et un ombrage. Les tuiles de démonstration de MapLibre ne couvrent que la région d'Innsbruck (Alpes autrichiennes) : parfait pour essayer.
const map = new maplibregl.Map({
container: 'map', center: [11.39, 47.27], zoom: 12, pitch: 65, maxPitch: 80,
style: { version: 8,
sources: { osm: { type: 'raster', tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'], tileSize: 256, attribution: '© OSM' } },
layers: [{ id: 'osm', type: 'raster', source: 'osm' }] }
});
map.on('load', () => {
map.addSource('relief', { type: 'raster-dem', url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json', tileSize: 256 });
map.setTerrain({ source: 'relief', exaggeration: 1.2 });
map.addLayer({ id: 'ombrage', type: 'hillshade', source: 'relief', paint: { 'hillshade-shadow-color': '#473B24' } });
});
map.addControl(new maplibregl.TerrainControl({ source: 'relief', exaggeration: 1.2 })); // bouton 3D on/off
Pour la France, il faut des tuiles d'altitudes encodées en couleurs (formats « Terrarium » ou « Mapbox Terrain-RGB »), que l'on peut produire à partir du RGE ALTI ou de la BD ALTI de l'IGN.
10. Exporter la carte en image
Le canevas WebGL est effacé après chaque image : pour pouvoir le lire, créez la carte avec preserveDrawingBuffer: true. C'est ainsi que le croquis de terrain produit ses PDF.
const map = new maplibregl.Map({ container: 'map', style, preserveDrawingBuffer: true, /* … */ });
function exporterPNG() {
map.once('idle', () => { // attendre que tout soit dessiné
const a = document.createElement('a');
a.href = map.getCanvas().toDataURL('image/png');
a.download = 'carte.png';
a.click();
});
map.triggerRepaint();
}
Les tuiles doivent autoriser l'accès « cross-origin » (en-tête CORS), sinon le navigateur bloque la lecture du canevas. C'est le cas de l'IGN et d'OpenStreetMap. Pour un PDF, passez l'image à jsPDF (doc.addImage).
Pour aller encore plus loin
- Dessiner et modifier des géométries : Terra Draw fonctionne avec MapLibre (points, lignes, polygones, sélection, déplacement). Le croquis de terrain de CartoForge utilise son propre moteur de dessin, avec côtes, équerre et accrochage.
- Gros volumes : convertissez vos données en tuiles vectorielles (Tippecanoe, PMTiles) : voir Tuiles vectorielles avec MapLibre GL.
- Animations :
map.flyTo(),map.rotateTo()etmap.easeTo()enchaînés créent des visites guidées. - Vous débutez avec Leaflet ? Son pendant : 10 outils à intégrer à une carte Leaflet.
Démonstration : les 10 outils réunis
Tous les outils de ce tutoriel sur une même carte (arbres et parcelles fictifs) :
- contrôles à droite : zoom, boussole, position GPS, plein écran ; échelle en bas à gauche ;
- panneau : recherche d'adresse, choix du fond IGN, clusters et carte de chaleur ;
- zoomez sur les parcelles violettes près de Bellecour : surbrillance au survol, numéros à partir du zoom 16 ;
- 📏 Mesurer : distance, et surface à partir de 3 points ; ⛰️ Relief 3D s'envole vers Innsbruck ; 🖼️ PNG télécharge l'image de la carte.