Makina Blog

Le blog Makina-corpus

Géné­rer un fichier PMTiles avec Tippe­ca­noe


Exemple de géné­ra­tion et d’af­fi­chage d’un jeu de tuiles vecto­rielles en PMTiles à partir de données publiques.

Sommaire

Prérequis tech­niques

Pour ne pas avoir à instal­ler des dépen­dances complexes, nous utili­sons Tippe­ca­noe à travers Docker et des conte­neurs éphé­mères.
Nous avons donc besoin d’avoir une image Docker de Tippe­ca­noe que nous pouvons géné­rer nous même à partir des sources :

git clone https://github.com/felt/tippecanoe
cd tippecanoe
docker build -t tippecanoe:latest .

Après le trai­te­ment qui dure quelques minutes, vous pouvez véri­fier la présence d’une image de Tippe­ca­noe avec la commande : docker images

Récu­pé­rer et prépa­rer les données

Le jeu de données qui est utilisé pour cet exemple est une base des aména­ge­ments cyclables de France Métro­po­li­taine, numé­ri­sés dans OpenS­treet­Map et trai­tés par Geovelo.

On télé­charge le jeu de données au format GeoJ­SON :

wget https://www.data.gouv.fr/fr/datasets/r/d03abb03-9197-4a4f-b758-5062595eb9ce -O amenagements.geojson

À noter : nous utili­sons ici des données GeoJ­SON pour produire les tuiles, mais Tippe­ca­noe accepte aussi des données CSV ou Flat­Geo­buf.

Pour faci­li­ter le trai­te­ment de la source de données, nous allons conver­tir le GeoJ­SON origi­nal en un fichier GeoJ­SON-NL : au lieu d’un objet racine unique de type Featu­re­Col­lec­tion conte­nant ensuite toutes les Features, nous aurons un fichier dont chaque ligne est une Feature. Cela permet­tra à Tippe­ca­noe de paral­lé­li­ser ses trai­te­ments.

docker run -it --rm -v .:/app tippecanoe:latest \
    tippecanoe-json-tool amenagements.geojson > amenagements.geojson-nl

GeoJ­SON :

Capture d'écran d'un exemple de fichier GeoJSON

GeoJ­SON-NL :

Capture d'écran d'un exemple de fichier GeoJSON-NL

D’autres possi­bi­li­tés pour faire cette même conver­sion :

# Avec jq : https://jqlang.github.io/jq/
cat amenagements.geojson | jq -c ".features[]" > amenagements.geojson-nl

# Avec GDAL : https://gdal.org/programs/ogr2ogr.html
ogr2ogr -f GeoJSONSeq amenagements.geojson-nl amenagements.geojson

Géné­rer la pyra­mide de tuiles en PMTiles

On peut ensuite produire notre fichier PMTiles :

docker run -u $UID:$GID -it --rm -v .:/app tippecanoe:latest \
  tippecanoe --read-parallel \
  --maximum-zoom=g --drop-densest-as-needed --extend-zooms-if-still-dropping \
  --output=amenagements.pmtiles \
  amenagements.geojson-nl

À noter : Il est impor­tant para­mé­trer Tippe­ca­noe avec des options de simpli­fi­ca­tion adap­tées au type des données : réseau de linéaires, géomé­tries de zones, ensemble de points, …

Après quelques minutes, le fichier .pmtiles est créé, et on peut visua­li­ser le résul­tat en le faisant glis­ser sur https://proto­maps.github.io/PMTiles/.

Pour un GeoJ­SON initial de 235 Mo, nous obte­nons ici une pyra­mide complète de tuiles de 40 Mo !

Affi­cher le fichier PMTiles

Nous partons d’un simple fichier carte.html, dans lequel on va char­ger MapLibre GL JS et un petit complé­ment pour le rendre capable d’uti­li­ser le fichier PMTiles :

<script src="https://unpkg.com/maplibre-gl@4/dist/maplibre-gl.js"></script>
<script src="https://unpkg.com/pmtiles@3/dist/pmtiles.js"></script>
<link href="https://unpkg.com/maplibre-gl@4/dist/maplibre-gl.css" rel="stylesheet" />

Pour anti­ci­per un peu le rendu, on ajoute un peu de CSS :

<style>
  html { box-sizing: border-box; font-size: 16px; }
  *, *:before, *:after { box-sizing: inherit; }
  html, body { margin: 0; padding: 0; }
  pre { white-space: pre-wrap; }
</style>

Dans le corps de la page, on place la balise où se char­gera la carte :

<div id="map" style="height: 100vh;"></div>

Puis enfin, le script qui va permettre de créer la carte :

<script>
  // Ajoute à MapLibre la gestion des PMTiles
  const protocol = new pmtiles.Protocol();
  maplibregl.addProtocol('pmtiles', protocol.tile);

  // Instancie la carte avec un fond de démo (coloré)
  const map = new maplibregl.Map({
    container: 'map',
    style: 'https://demotiles.maplibre.org/style.json',
    center: [1.5, 47],
    zoom: 6,
  });

  // Crée une Popup qu'on affichera au survol des Features
  const popup = new maplibregl.Popup({ closeButton: false, closeOnClick: false });

  // Une fois que la carte est instanciée…
  map.on('load', () => {
    
    // Ajoute notre fichier PMTiles en tant que source vectorielle
    map.addSource('id-de-ma-source', {
      type: 'vector',
      // La syntaxe ici est "pmtiles://" puis le chemin d'accès au fichier.
      // Ce chemin peut-être relatif ("./fichier", "fichier", "dossier/fichier"),
      //  absolu ("/fichier", "/fichier", "/dossier/fichier")
      //  ou distant ('https://domaine.tld/cheminfichier")
      url: 'pmtiles://amenagements.pmtiles',
    });

    // Ajoute à la carte une couche linéaire utilisant notre source PMTiles
    map.addLayer({
      id: 'id-de-ma-couche',
      source: 'id-de-ma-source',
      // "source-layer" est le nom de notre couche "dans" le fichier PMTiles
      //   Si on ne l'a pas précisé à la création du PMTiles, Tippecanoe génère
      //   un identifiant à partir du fichier source.
      //   Il est facile de vérifier les couches d'un PMtiles en le chargeant
      //   sur https://protomaps.github.io/PMTiles
      'source-layer': 'amenagementsgeojsonnl',
      type: 'line',
      paint: {
        'line-color': '#198EC8',
        'line-width': 3,
        'line-opacity': 0.7,
      },
      layout: {
        'line-join': 'round',
        'line-cap': 'round',
      },
    });

    // Au survol de notre couche, affiche une Popup
    map.on('mouseenter', 'id-de-ma-couche', ({ lngLat, features } = {}) => {
      map.getCanvas().style.cursor = 'pointer';
      popup
        .setHTML(
          features.map(feature => '<pre>' + JSON.stringify(feature.properties, null, 2) + '</pre>').join('</br >')
        )
        .setLngLat(lngLat)
        .addTo(map);
    });
    map.on('mouseleave', 'id-de-ma-couche', () => {
      map.getCanvas().style.cursor = '';
      popup.remove();
    });
  });
</script>

Pour affi­cher votre page HTML avec la carte, il sera néces­saire d’uti­li­ser un serveur http suppor­tant les entêtes HTTP Range

À noter : char­ger le fichier html direc­te­ment dans le navi­ga­teur ne fonc­tion­nera pas.

Par exemple :

# Avec Nodejs :
npx serve
# ou bien 
npx http-server
Capture d'écran d'une portail de la carte générée avec ce tutoriel.

 

Liens utiles

Formations associées

Formations SIG / Cartographie

Formation MapLibre

Toulouse Du 17 au 18 octobre 2023

Voir la formation

Formations SIG / Cartographie

Formation QGIS

Nantes Du 2 au 4 avril 2024

Voir la formation

Formations SIG / Cartographie

Formation Développer avec l'écosystème d'OpenStreetMap

Toulouse Du 16 au 17 mai 2023

Voir la formation

Actualités en lien

Image
Encart article Protomaps : Illustration d'une portion de pyramide de tuiles
14/02/2024

Protomaps, stockez vos pyramides de tuiles plus simplement

Présentation d'un nouveau format de stockage de tuiles cartographiques

Voir l'article
Image
Read The Docs
01/02/2024

Publier une documentation VitePress sur Read The Docs

À l'origine, le site de documentation Read The Docs n'acceptait que les documentations Sphinx ou MKDocs. Depuis peu, le site laisse les mains libres pour builder sa documentation avec l'outil de son choix. Voici un exemple avec VitePress.

Voir l'article
Image
Article : Servir sa couche raster QGIS en tuiles sans effort avec le format PMTiles
25/01/2024

Servir sa couche raster QGIS en tuiles sans effort avec le format PMTiles

Cet article vous présente une approche permet­tant de tuiler et de publier une couche raster fabriquée avec QGIS.

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus