Une carte affichable n'est pas une carte livrable
L'exemple « build a map » officiel d'Overture vous mène d'une requête DuckDB à une carte rendue en une dizaine de minutes, et il se termine par un renvoi : la dernière instruction vous adresse une démo hébergée pour « le style complet ». Le rendu, c'est là que le tutoriel s'arrête. C'est aussi, à peu de choses près, l'endroit où une carte de production commence à vous réclamer des choses que le tutoriel n'ouvre jamais.
Voici la thèse, énoncée pour qu'un sceptique puisse s'y agripper : entre cette carte rendue et une carte livrée s'intercalent quatre engagements que la démo ne nomme jamais. Le style référence des couches source dont vous avez choisi les noms au moment du tuilage. La carte porte une ligne d'attribution attendue par les licences des données. L'utilisateur peut faire quelque chose au survol du doigt. Et l'actif de tuiles survit à la release suivante, ce qui compte, étant donné qu'Overture publie chaque mois et que le schéma bouge avec. Rien de tout cela ne rend la démo fausse ; cela en fait la première moitié.
Tout ce qui suit passe par une seule extraction. La boîte couvre le bassin du lac Tahoe : -121.0 à -119.6 de longitude, 38.7 à 39.5 de latitude. Comptée contre la release Overture 2026-09-23.1 avec DuckDB le 11 octobre 2026, elle contient 28 166 lieux et 228 935 bâtiments, et le thème transportation y ajoute 106 242 segments. La carte livre d'abord sa couche de lieux, parce qu'une couche de points est le moyen le moins cher de voir chacun des quatre engagements tenu ou manqué.
L'extraction décide de tout ce que la popup pourra dire
L'étape DuckDB de la démo se lit comme une sélection de données, et au premier essai, c'est tout ce qu'elle est. Dès qu'une popup apparaît, la liste des colonnes devient du design d'interface. Les lignes places du schéma actuel (v2.0.0) portent, à côté de la géométrie, names, confidence, websites, socials, une struct taxonomy et une colonne bbox. Le survol en utilisera trois, et pas davantage.
COPY (
SELECT
geometry,
names.primary AS name,
taxonomy.primary AS category,
confidence
FROM read_parquet(
's3://overturemaps-us-west-2/release/2026-09-23.1/theme=places/type=place/*.parquet',
hive_partitioning = 1
)
WHERE bbox.xmin >= -121.0 AND bbox.xmax <= -119.6
AND bbox.ymin >= 38.7 AND bbox.ymax <= 39.5
) TO 'tahoe-places.geojson';
Deux décisions se cachent dans ce SELECT. Abandonner des attributs que vous ne styliserez jamais les tient hors des tuiles, et le même script finira par courir sur des régions bien plus denses que cette boîte, où l'écart se cumule. Écrire la liste des champs à la main, c'est aussi la façon de voir venir le problème suivant : taxonomy.primary est le remplaçant v2.0.0 du champ categories que portaient les releases précédentes, et ce renommage réapparaît plus bas dans ce billet comme une casse.
La sortie du tuilage est un simple fichier .pmtiles, et une page web lit des tuiles directement dedans par requêtes de plage HTTP, sans serveur de tuiles nulle part dans le déploiement. Cette seule propriété explique pourquoi ce pipeline colle à l'hébergement statique que la plupart des petits produits font déjà tourner. Elle mérite une phrase, parce que la démo jamais ne prend le temps de la dire.
Tout ce que ce SELECT laisse tomber est discrètement sorti du vocabulaire d'interaction.
Deux fichiers doivent s'accorder sur un nom de couche, et rien ne les vérifie
tippecanoe nomme la couche vectorielle au moment d'écrire l'archive. Donnez-lui ce nom explicitement plutôt que de laisser le nom de fichier décider :
tippecanoe -o tahoe-places.pmtiles -l tahoe_places -Z8 -z14 tahoe-places.geojson
Le style doit alors répéter cette chaîne à la lettre comme source-layer :
{
"sources": {
"overture-tahoe": {
"type": "vector",
"url": "pmtiles://https://tiles.example.com/tahoe-places.pmtiles",
"attribution": "© OpenStreetMap contributors, Overture Maps Foundation"
}
},
"layers": [
{
"id": "tahoe-places-dot",
"type": "circle",
"source": "overture-tahoe",
"source-layer": "tahoe_places"
}
]
}
Manquez la chaîne, et la couche pour laquelle vous avez écrit ce code ne dessine rien. La carte s'affiche quand même, la console JavaScript reste presque muette, et le bug ressemble à des données défectueuses jusqu'à ce que quelqu'un ouvre le contenu des tuiles dans un inspecteur. Nous ne recopions plus la valeur de -l à la main dans le style : le script de tuilage émet lui-même le fragment de style, parce qu'un nom vérifié par deux fichiers et deux humains est un nom qui dérive.
Une couche source que deux fichiers peuvent contredire, sans vérificateur entre eux, est un bug découvert devant un utilisateur.
Le survol, c'est l'extraction revenue en interface
Les interactions se branchent sur les ids de couche, donc la popup s'attache à tahoe-places-dot et lit les propriétés que l'extraction a gardées :
import { Protocol } from "pmtiles";
maplibregl.addProtocol("pmtiles", new Protocol());
const popup = new maplibregl.Popup({ closeButton: false });
map.on("mouseenter", "tahoe-places-dot", (e) => {
const row = e.features[0].properties;
popup
.setLngLat(e.lngLat)
.setHTML(`<strong>${row.name ?? "Lieu sans nom"}</strong><br>${row.category ?? ""}`)
.addTo(map);
});
map.on("mouseleave", "tahoe-places-dot", () => popup.remove());
Les valeurs de repli de ce gabarit ne sont pas décoratives. Dans l'extraction Tahoe, 896 des 28 166 lieux portent un taxonomy.primary NULL dans la release 2026-09-23.1 ; un gabarit qui fait confiance au champ de catégorie affiche donc huit lignes sur cent sans contenu. La liste d'attributs coupée à l'extraction est tout le vocabulaire dont la popup disposera : le premier engagement se joue déjà là.
La popup, c'est l'extraction revenue en interface.
Une ville se contente de tippecanoe, une release complète a son outil officiel
Pour une extraction d'une seule ville comme celle-ci, tippecanoe reste un outil raisonnable : du GeoJSON en entrée, des PMTiles en sortie, un contrôle complet sur le nom de couche et l'intervalle de zoom, sans infrastructure nouvelle. C'est la voie que nous garderions pour tout ce qui ressemble à la boîte Tahoe.
Le calcul change quand la cible est la release entière. L'organisation GitHub d'Overture abrite overture-tiles, un outil Java qui produit des PMTiles directement depuis les releases officielles, avec terraform-aws-overture-tiles à ses côtés pour monter des déploiements hébergés. La ligne de partage que nous utiliserions : extraire et remodeler une ville, gardez tippecanoe ; re-tuiler une release entière à calendrier fixe sans posséder le code, lisez le README de l'outil officiel avant d'écrire le vôtre. Les deux voies aboutissent au même contrat .pmtiles, raison pour laquelle le style MapLibre ci-dessus ne changerait que son URL de source.
L'outil change ; le contrat de tuiles, non.
Le même fichier de tuiles atteint plusieurs runtimes
Le build JavaScript de MapLibre n'est qu'un des endroits où cet actif se paie. MapLibre Native, les SDK iOS et Android, parle le même modèle de source, et les archives PMTiles y fonctionnent aussi : un seul fichier de tuiles peut donc porter les cartes du web et de l'application mobile sans second pipeline. Là où un SDK épinglé un peu ancien ne lit pas les PMTiles directement, la solution de repli est petite et sans éclat : embarquez l'archive dans l'app et pointez l'adaptateur de la plateforme dessus, ou convertissez en MBTiles pour cette seule cible.
Nous faisons tourner MapLibre en production chez des clients pour des cartes de pistes de ski et d'élévation hors ligne, et la surprise de cette configuration est administrative : dès qu'un seul fichier de tuiles sert tous les runtimes, le débat récurrent n'est plus le pipeline mais le budget des appareils, où vit le vrai compromis. Le côté sentiers de cette pile a son propre billet, la construction d'un graphe de sentiers depuis OpenStreetMap.
Un seul actif, plusieurs runtimes ; le compromis qu'on sent, c'est le budget de l'appareil.
Ce que la démo ne mesure jamais
Le premier coût non mesuré est la génération de tuiles sur les thèmes denses. Compter les seuls bâtiments de la boîte Tahoe a pris 37,1 secondes de DuckDB contre la release 2026-09-23.1, et le schéma des bâtiments porte la couleur de façade et celle du toit à côté de la hauteur. Multiplié par toute une région, l'archive s'empèse avec des attributs que le style ne lit jamais : filtrez donc les champs avant le tuilage et découpez l'extraction à la zone réellement servie. L'archive des bâtiments est le projet où cette leçon vous trouve.
Le deuxième est la discipline de zoom. Les règles de densité de tippecanoe décident des éléments qui survivent à chaque niveau, et une carte peut être correcte localement à tous les niveaux tout en paraissant plus vide à celui où vivent vraiment vos utilisateurs. La solution de repli est sans miracle et elle marche : épinglez une courte liste de coordonnées reconnaissables, puis faites leurs captures d'écran à chaque zoom que le produit livre vraiment. Ce qui change entre le zoom 10 et le zoom 12, c'est l'opinion de l'utilisateur sur la carte.
Le troisième est le rythme des releases. Overture publie chaque mois et le schéma bouge avec, et le renommage categories vers taxonomy du schéma v2.0.0 (release 2026-09-23.1) en est un exemple vivant : un filtre de style écrit contre le vieux nom de champ casse sans erreur, parce que le champ est simplement absent. La réponse est procédurale. Le script de re-tuilage vit dans le gestionnaire de versions, tourne contre un identifiant de release nommé, et se relit contre les notes de release avant que les tuiles atteignent les utilisateurs.
Le quatrième est un budget que la démo n'a aucun moyen de voir : la batterie. Une carte mobile paie sur l'appareil, où les couches lourdes et les chargements de tuiles interminables se traduisent en batterie et en démarrages à froid lents sur les téléphones les moins capables de les absorber. Gardez le nombre de couches et le maxZoom délibérément petits côté natif, et faites le vrai test sur un matériel que vous décririez comme ancien.
Aucun de ces quatre coûts n'apparaît dans une démo, et les quatre apparaissent dans une release.
Le script de re-tuilage est le livrable
Si une seule décision survit à ce billet, la voici : l'actif qui mérite le versionnage n'est pas le fichier .pmtiles du jour, c'est le script qui le régénère depuis un identifiant de release nommé, avec sa liste de champs, son intervalle de zoom et sa chaîne d'attribution versionnés à côté. L'engagement du nom de couche, l'engagement d'attribution, le vocabulaire du survol et le plan de mise à jour vivent tous dans ce seul fichier, où on peut les relire et les relancer. Afficher est un état de démo. Livrer est un pipeline.
Merci d'avoir lu jusqu'ici. Si cela ressemble au problème sur lequel vous butez, parlez-nous-en. Un fondateur lit chaque message.
Engager la conversation