13 — Les cartes : bloc ::map, fondation @recit/geo et format atlas (maps.recit.dev)
Statut : accepté · Phase : P15 · Mis à jour : 24/10/2026.
Ce document pousse l'idée « cartes.recit.dev » de sites/plans/pages/archive/BACKLOG_IDEA.md : «
Markdown permettant d'afficher une carte, sous différentes formes, sous différents pays ». Il suit
la même structure que 08-formats.md et dépend de 02-architecture.md (modèle Story), de
09-langues-et-json.md (JSON typé, langues) et de 07-domaines.md (sites). Chaque choix porte une
recommandation. Les questions 1, 2, 3, 5 et 6 du §8 ont été tranchées le 24/10/2026, sans
l'utilisateur et à sa demande (décisions réversibles) ; la question 4 reste ouverte.
Placement, ports, domaines et règles communes aux plans 10 à 14 : 06-roadmap.md, section « Backlog
: plans 10 à 14 ». En résumé : un lot avancé en P6, limité au bloc ::map statique (régions et
marqueurs, sans worldview ni choroplèthe, sans source data= : seul le DataTable de
@recit/data y sert, pour le tableau accessible), puis P15 Atlas après la 1.0. Le format
atlas est retenu (P15, question 1, tranchée) ; les points de vue sur les frontières (worldview,
Natural Earth par défaut) et la carte interactive (MapLibre) sont des options ultérieures
(questions 2 et 3, tranchées). Le type Place appartient au plan 12 : ce plan l'importe. Il n'y a
pas de commande de géocodage en v1 : les coordonnées sont écrites par l'auteur.
1. L'idée en une phrase
Une carte s'écrit en Markdown comme un graphique : des coordonnées et des données dans la source, une carte identique sur le web, dans une slide, dans un PDF, dans une vidéo et dans une story, sans appel réseau et sans tuile tierce par défaut.
Ce que cela apporte à Récit
- « Écrire une fois, raconter partout » appliqué à la géographie. Aujourd'hui, une carte est une
capture d'écran figée, non traduisible et sous licence floue. Avec Récit, elle devient une
donnée : la même directive
::mapproduit un SVG net à l'impression (report,book), une slide (deck), un plan avec mouvement de caméra (motion), une carte 9:16 (stories) et une figure de doc (docs). - Deux niveaux, comme
@recit/data.- Un bloc transverse
::map/:::map, utilisable dans tous les formats (comme::chart). - Un format
atlas(« story map ») où une scène = une vue de carte (emprise, couches, texte), lu au défilement. C'est le récit géographique : un voyage, une tournée, une implantation, une étude régionale.
- Un bloc transverse
- « Sous différents pays » : les deux lectures sont retenues (question 3, tranchée le 24/10) :
- une carte d'un pays et de ses régions (
region=FR level=admin1), couverte dès le lot P6 ; - un point de vue sur les frontières disputées (
worldview=), pour qu'une carte ne soit pas politiquement fausse dans le pays où elle est publiée. Option, après le lot P6, avec Natural Earth (de facto) par défaut.
- une carte d'un pays et de ses régions (
- Fidèle aux règles du projet. Aucun chiffre écrit en dur dans une choroplèthe (règle « aucune
métrique inventée » d'emaki), aucune devinette géographique (pas de géocodage implicite), une
seule stack (d3-geo est déjà dans le lockfile), licences des données listées dans
NOTICE.md. - Lien avec les autres idées du backlog. Le type
Place(adresse structurée + coordonnées facultatives) est défini une seule fois par le plan 12 et livré aveccalendar(P10). Ce plan l'importe pour la vue carte de l'annuaire et des événements, sans le redéfinir.
2. Ce que la recherche a appris
2.1 Ce que le dépôt fournit déjà
Vérifié dans pnpm-lock.yaml :
| Paquet | Présent ? | Par qui | Licence |
|---|---|---|---|
d3-geo 3.1.1 (+ @types/d3-geo 3.1.0) | oui, transitif | d3 7.9.0 ← mermaid (packages/site, packages/components) | ISC |
d3-scale 4.0.2, d3-scale-chromatic 3.1.0, d3-dsv 3.0.1 | oui, transitif | même chaîne | ISC |
commander 2.20.3 (seule dépendance de topojson-client) | oui, transitif | une autre chaîne ; @recit/cli utilise commander 14.0.3 | MIT |
remark-directive 3.0.1, hastscript, hast-util-to-html, happy-dom | oui | packages/core, tests | MIT |
topojson-client, world-atlas, maplibre-gl, leaflet, pmtiles, @turf/*, @observablehq/plot, jsdom, linkedom | non | — | — |
Conséquence : déclarer d3-geo, d3-scale et d3-scale-chromatic en dépendances directes,
épinglées sur les versions du lockfile, n'ajoute aucun paquet au graphe. topojson-client (ISC)
serait nouveau, et ferait de commander 2.x une dépendance de nos packages publiés, à côté de
commander 14.0.3 de @recit/cli : deux versions majeures du même paquet. On ne l'ajoute donc pas
(D8).
2.2 Rendu statique SVG (d3-geo)
geoPath(projection)renvoie une chaînedSVG par des fonctions pures, sans DOM : émise en JSX côté SSR ou en hast, elle sert le build, Remotion et Paged.js.- Projections fournies :
geoEqualEarth,geoNaturalEarth1,geoMercator,geoConicConformal(proche du Lambert 93 pour la France),geoAlbers,geoAlbersUsa,geoOrthographic,geoAzimuthalEqualArea.projection.fitExtentcadre automatiquement sur une emprise. - Piège du sens des anneaux. d3-geo attend des anneaux extérieurs dans le sens horaire
(géométrie sphérique) ; la RFC 7946 impose l'inverse. Un GeoJSON conforme peut donc « remplir le
globe sauf le polygone ». Parade : un utilitaire
rewindmaison (sigeoArea(f) > 2π, on inverse), sans@turf/rewind. À vérifier avant GE3 : ce point est repris de mémoire du README d3-geo, pas relu. - Encarts (DROM français, Alaska et Hawaï) :
geoAlbersUsales gère pour les États-Unis ; ailleurs, ils sont à faire à la main.d3-composite-projectionsserait une dépendance de plus : encarts maison reportés en phase ultérieure.
2.3 Données de fond
- Natural Earth : domaine public, aucune attribution obligatoire (« Made with Natural Earth » conseillé). Admin-0 (pays), admin-1 (régions, au 10m), Populated Places (villes).
- Depuis la v5 (déc. 2021), étendue en 5.1.0 (2022-05-04), Natural Earth publie des variantes « point of view » Admin-0 pour plusieurs dizaines de pays : le tracé par défaut suit le contrôle de fait, les POV suivent la position de droit du pays concerné. La liste exacte des POV de 5.1.x reste à relever.
world-atlas(ISC) fournit des TopoJSON prêts mais repose sur Natural Earth 4.1.0, sans POV.- Pièges ISO :
ISO_A3vaut-99pour quelques pays (historiquement France et Norvège). Joindre surISO_A3_EHouADM0_A3, et signaler les clés non appariées. - Noms localisés :
Intl.DisplayNames(lang, { type: "region" }), inclus dans la plateforme, sans données à embarquer. Les champsNAME_xxde Natural Earth servent de secours.
2.4 Cartes glissantes (interactives)
| MapLibre GL JS | Leaflet | |
|---|---|---|
| Licence | BSD-3 | BSD-2 |
| Rendu | tuiles vectorielles, WebGL2, styles JSON | tuiles raster (vectoriel par plugins) |
| Poids | lourd, à mesurer avant tout chiffre, chargement paresseux obligatoire | léger |
| Sources de tuiles | OpenFreeMap, PMTiles auto-hébergé | en pratique tuiles raster OSM |
Règles des sources de tuiles :
- tile.openstreetmap.org : best-effort sans SLA ; préchargement, archives de tuiles et usage hors ligne interdits ; User-Agent distinct, Referer valide, cache respecté, attribution visible. Nos exports PDF, PNG et vidéo et les captures de CI seraient du téléchargement en masse : à exclure.
- OpenFreeMap : MIT, données OSM, sans clé sur l'instance publique (financée par dons) ; attribution obligatoire, y compris à l'impression et en vidéo (« OpenFreeMap © OpenMapTiles Data from OpenStreetMap ») ; limites hors ligne et de parallélisme documentées ; auto-hébergeable.
- Protomaps PMTiles : basemap « ODbL Produced Work », attribution OSM obligatoire. Un seul
fichier statique servi par requêtes HTTP Range, découpable par région (
pmtiles extract,--maxzoom), hébergeable avec le site : aucune fuite d'IP vers un tiers. - Vie privée : toute tuile tierce transmet l'IP du visiteur à l'hébergeur des tuiles.
2.5 Géocodage
Nominatim public : 1 requête/s au plus, cache obligatoire, géocodage en masse déconseillé, User-Agent identifiant, attribution OSM et données ODbL (share-alike). Conclusion : aucun géocodage au build ni au rendu.
2.6 Syntaxes existantes
- GitHub rend les blocs
```geojsonet```topojsonen carte interactive : Récit doit les honorer, pour qu'une source reste lisible sur GitHub. - Obsidian Leaflet (bloc
```leaflet, clés YAMLlat,long,defaultZoom,marker:) et Obsidian Map View (location: [lat, lon]en frontmatter) montrent qu'un modèle déclaratif clé/valeur suffit aux auteurs. - react-simple-maps (MIT, sur d3-geo, peu maintenu) valide l'approche « d3-geo + JSX » sans justifier une dépendance.
- Mermaid n'a pas de type « carte ».
2.7 Correction à apporter à 08-formats.md
08-formats.md §0 présentait Observable Plot comme un « rendu SVG sans DOM » (corrigé le 24/10).
C'était inexact : en SSR, Plot demande qu'on lui fournisse un document virtuel (linkedom ou
jsdom), et sa doc réserve le SSR aux petits graphiques. Ce point touche @recit/data (P6) et la
question ouverte n° 10 de 06-roadmap.md. Proposition (D9) : unifier graphiques et cartes sur d3
sans DOM. Tranché le 24/10 (06, question 10) : d3 sans DOM. 03-packaging.md §6 et
08-formats.md §0 et R1 sont mis à jour dans le même changement.
2.8 Sources
- Politique des tuiles OSM : https://operations.osmfoundation.org/policies/tiles/
- Politique Nominatim : https://operations.osmfoundation.org/policies/nominatim/
- OpenFreeMap : https://openfreemap.org/
- Protomaps : https://docs.protomaps.com/basemaps/downloads
- MapLibre GL JS : https://github.com/maplibre/maplibre-gl-js
- Natural Earth, conditions : https://www.naturalearthdata.com/about/terms-of-use/
- Natural Earth, frontières disputées : https://www.naturalearthdata.com/about/disputed-boundaries-policy/
- world-atlas : https://github.com/topojson/world-atlas
- GitHub, blocs geojson/topojson : https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-diagrams
- Obsidian Leaflet : https://github.com/javalent/obsidian-leaflet
- Observable Plot, SSR : https://observablehq.com/plot/getting-started
- d3-geo : https://github.com/d3/d3-geo
- Fichiers locaux :
pnpm-lock.yaml,packages/core/package.json,sites/plans/pages/08-formats.md
3. Décisions proposées
Format des décisions : chaque décision donne les options et une recommandation.
| # | Sujet | Options | Recommandation |
|---|---|---|---|
| D1 | Nom du site | cartes.recit.dev (backlog), maps.recit.dev, atlas.recit.dev | maps.recit.dev, en anglais selon la règle commune de 07-domaines.md §2 (06, « Backlog »). cartes.recit.dev (mot du backlog) et atlas.recit.dev (nom technique) redirigent en 301. « Cartes » reste le titre de la version fr du site |
| D2 | Bloc ou format | bloc transverse seul ; format seul ; les deux | Bloc d'abord, ::map + @recit/geo (fondation, utile partout). Le format atlas (une scène = une vue) vient en P15 (tranché, question 1) |
| D3 | Nom du format | map, maps, atlas | atlas : distinct de la directive ::map et de Map de JavaScript, et parlant (« un atlas raconté ») |
| D4 | Rendu par défaut | SVG statique ; tuiles interactives | SVG statique au build, sans réseau, partout. MapLibre seulement en option, en phase ultérieure, et seulement sur le web |
| D5 | Données de fond | world-atlas ; Natural Earth 5.1.x régénéré | Natural Earth 5.1.x, converti par un script figé dans le dépôt. Variantes POV seulement avec l'option worldview. Poids mesuré avant de choisir entre assets livrés dans le package npm et assets générés chez l'auteur |
| D6 | Pays et frontières | worldview lié à lang ; explicite | Option (question 3, tranchée), après le lot P6 : explicite, worldview=default (de facto Natural Earth). La langue ne choisit jamais une position politique |
| D7 | Géocodage | en ligne au build ; gazetteer hors ligne ; jamais | Coordonnées écrites dans la source (tranché le 24/10). Pas de commande de géocodage en v1. Plus tard, en option : gazetteer hors ligne (Natural Earth Populated Places) pour place=, et recit geocode du plan 12, qui écrit dans le compagnon JSON. Jamais au rendu |
| D8 | TopoJSON | topojson-client au build ; GeoJSON préconverti | GeoJSON préconverti dans les assets, sans option topojson-client (il ajouterait commander 2.x à côté de commander 14) |
| D9 | Base de @recit/data | Observable Plot ; d3 sans DOM | Tranché le 24/10 (06, question 10) : d3 sans DOM (d3-scale, d3-shape, déjà présents), pour que graphiques et cartes partagent échelles, palettes et rendu SSR |
3.1 Syntaxe du bloc ::map (dans tous les formats)
Remark-directive, déjà dans packages/core. Même grammaire que ::chart{data=…} de
08-formats.md. Le plugin vit dans @recit/geo ; le cœur ne garde que le nom map dans son
registre de directives (règle commune de 06, « Backlog »). En JSON, la carte reste une directive
dans le body de la scène (pas de champ blocks en v1).
Lot P6 : ::map et :::map avec region, level, projection, caption, et ::marker en
lat/lon. P15 : choroplèthe (data, key, value, scheme, source), ::route,
::area, blocs geojson. Options : worldview, place= (gazetteer), mode=tiles. L'exemple
ci-dessous montre l'ensemble.
Markdown
| Élément | Rôle |
|---|---|
::map{…} | carte simple (feuille). mode = choropleth | points | route | tiles, déduit si absent. Ce n'est pas kind, réservé au discriminant des documents JSON (09-langues-et-json.md §2.2) |
:::map{…} … ::: | carte composée de couches (::marker, ::route, ::area, blocs geojson) |
region | world, un code ISO 3166-1 alpha-2 (FR), ou une emprise [w,s,e,n] |
level | admin0 (pays) ou admin1 (régions d'un pays) |
worldview | option : default ou un code ISO (variante POV Natural Earth) |
projection | liste fermée : equal-earth, natural-earth, mercator, conic-conformal, albers, albers-usa, orthographic, azimuthal-equal-area |
data key value | P15 : jointure avec une source de @recit/data (alpha-2 ou alpha-3). Seule source autorisée des valeurs d'une choroplèthe |
scheme | tokens de thème (sequential, diverging, dérivés de la teinte unique), sinon un nom d3-scale-chromatic |
::marker{lat lon label} / ::marker{place label} | point. place (option) résolu par le gazetteer hors ligne au build |
::route{from to style} | ligne, tracée en grand cercle (géodésique par défaut). from/to = libellés de marqueurs ou coordonnées |
::area{src} | polygone fourni par l'auteur (GeoJSON local) |
```geojson / ```topojson | rendus avec les options par défaut (compatibles GitHub) ; dans un :::map, ils deviennent une couche |
caption, source | légende et source. source obligatoire pour une choroplèthe |
frontmatter geo: | position par défaut d'une scène ou d'une story (geo: [48.85, 2.35]), à la manière d'Obsidian Map View |
Règles :
- une projection, une région, une clé de jointure ou un
placeinconnus produisent une erreur avec numéro de ligne (report.md:14 ::map: region "FX" unknown (did you mean "FR"?)) ; - aucune valeur numérique inline dans une choroplèthe :
valuevient dedata=, sinon erreur ; - une clé de donnée sans pays correspondant donne un avertissement (erreur avec
--strict) ; - latitude hors de [-90, 90] ou longitude hors de [-180, 180] : erreur ; inversion probable
latitude/longitude (point hors de
region) : avertissement ; - la légende, le titre SVG et le résumé accessible sont générés depuis les données.
3.2 Retenu, P15 : le format atlas (une scène = une vue)
Format retenu le 24/10 (question 1), pour P15. L'attribut d'emprise de scène s'appelle camera= (et
non view=, qui désigne ailleurs la vue d'affichage : view: du frontmatter, prop view de
StoryEmbed).
Promesse. Un récit géographique s'écrit en Markdown, une vue par scène. Au défilement, la carte reste en place et la caméra passe d'une vue à l'autre ; le texte de chaque scène défile à côté. La même source devient un deck (une vue = une slide), une vidéo (une vue = un plan avec mouvement de caméra) ou une story 9:16.
Markdown
| Élément | Rôle |
|---|---|
frontmatter atlas: | projection, fond, point de vue et mise en page par défaut pour toute la story |
--- camera=… | emprise de la scène : code ISO, world, emprise [w,s,e,n] ou center=lat,lon zoom= |
| couches de la scène | ::marker, ::route, ::area, ::map{data} s'ajoutent à la vue de la scène |
reveal= | draw (tracé progressif), fade, none : transitions pilotées par la scène |
duration= | durée du plan pour le pont motion (modèle Scene.durationMs) |
:::notes | notes, comme dans le deck |
Règles :
- une scène sans
camerahérite de la précédente ; la première scène sanscameraprend l'emprise de toutes ses couches ; - une transition entre deux vues interpole la projection (rotation, échelle, translation) : aucune tuile n'est chargée pendant la transition ;
- les mêmes règles d'erreur que
::map(§3.1).
Composants
AtlasRoot (lecteur au défilement), AtlasView (une vue, SVG), GeoMap (carte autonome, le même
composant que la directive), MapLegend, DataTable de @recit/data (alternative accessible, pas
de composant propre aux cartes), MapCaption. Utilisables en MDX via <GeoMap … /> et
<StoryEmbed slug="lorem-tour" format="atlas" view="side" />.
3.3 Schéma JSON typé
Dans @recit/core/schema (zod 4, zod/mini côté navigateur), JSON Schema générés par
z.toJSONSchema et publiés sur https://recit.dev/schema/v1/atlas.json (comme les autres formats
de 09-langues-et-json.md §2.1).
Place: importé du plan 12 (D6), sans redéfinition. Adresse structurée,geofacultatif,addresslibre pour l'affichage seulement. Une carte ignore unPlacesansgeo(avertissement).MapBlock = { type: "map", mode?, region?, level?, worldview?, projection?, data?, key?, value?, scheme?, layers?: Layer[], caption?: Localized, source?: string }.Layer = Marker | Route | Area | GeoJsonLayer;Marker = { type: "marker", lat, lon } | { type: "marker", place }aveclabel?: Localized;Route = { type: "route", from, to, style?, reveal? }.AtlasSource(option) rejoint l'union discriminéeStorySourceSchemacomme ajout compatible en v1 (règle de versionnement de 06, « Backlog ») ; une scène JSON contient{ id, camera, layers?, body?, notes?, durationMs? }.- En dehors d'
atlas, une carte reste une directive dans lebodyd'une scène deck, report ou docs :MapBlockvalide la directive, sans champblocksdansSceneSource.
Ligne à ajouter au tableau « Champs typés par format » de 09-langues-et-json.md §2.3 :
| Format | Une scène JSON contient | Exemple de champ localisable |
|---|---|---|
atlas | camera (code ISO, emprise ou centre), layers (marqueurs, tracés, zones), body, notes, durationMs | label d'un marqueur, caption, body |
Coordonnées, emprises, codes ISO, valeurs et worldview ne sont jamais localisables.
Exemple de scène JSON :
JSON
Équivalence testée comme pour les autres formats : compile(md) == compile(toJson(md)).
3.4 Ce que la langue change
Ligne à ajouter au tableau de 09-langues-et-json.md §5 :
| Domaine | Règle |
|---|---|
atlas et ::map | noms de pays et de régions par Intl.DisplayNames (repli NAME_xx de Natural Earth) ; valeurs et légendes par Intl.NumberFormat ; libellés de marqueurs via Localized ; en RTL, la légende et le panneau de texte basculent par propriétés CSS logiques, la carte ne se retourne jamais ; worldview indépendant de la langue |
3.5 Rendu dans les autres formats
| Format | Ce que devient une carte | Phase |
|---|---|---|
docs | figure SVG dans une page du framework de sites, <details> avec le tableau | lot P6 |
report | figure SVG ; en PDF via @recit/paged, vectorielle, avec la source en légende | lot P6 |
deck | figure dans une slide ; pont atlas → deck (P15) : une vue = une slide | lot P6 / P15 |
book | figure numérotée, table des figures ; jamais de tuiles | P15 |
motion | blocs map dans @recit/motion-blocks : caméra (interpolation de projection en fonction de la frame), tracé progressif (stroke-dashoffset), apparition des classes d'une choroplèthe. Pont atlas → motion : une vue = un plan | P15 |
stories | une vue = une carte 9:16 ; export PNG par le chemin d'export d'images existant | P15 |
calendar | vue carte des événements qui ont un Place avec geo (facultative) | P15 |
toon | aucun pont promis : pas de correspondance fermée | — |
Ponts à ajouter au tableau de 08-formats.md §0 (correspondances fermées uniquement) :
| De → vers | Correspondance | Phase |
|---|---|---|
| atlas → deck | une vue → une slide, couches gardées, texte soumis aux règles de densité | P15 |
| atlas → motion | une vue → un plan, transition = interpolation de projection, reveal → animation | P15 |
| atlas → stories | une vue → une carte 9:16 | P15 |
formats de la liste fermée de 15-export-pdf.md §1 → PDF | ::map en SVG, imprimé par la CLI et @recit/paged | lot P6 |
Une carte mode=tiles se replie automatiquement sur le rendu statique (fond Natural Earth et
couches) dans tout format non web, avec un avertissement si le style dépendait des tuiles.
3.6 Accessibilité
<figure> + <svg role="img" aria-labelledby> avec <title> et <desc> (résumé généré depuis les
données, comme ::chart) + <figcaption> + <details> contenant un DataTable de @recit/data
(une ligne par région ou marqueur, valeur formatée par Intl). La couleur ne porte jamais seule
l'information : légende avec seuils explicites, palettes lisibles par les daltoniens, motifs en
option pour l'impression en noir et blanc. Pour MapLibre (plus tard) : contrôles au clavier, et le
tableau reste l'alternative de référence.
4. Packages, site, exemples et e2e
4.1 Packages
| Package | Groupe | Statut | Publié | Source | Contenu | Dépendances notables |
|---|---|---|---|---|---|---|
@recit/geo | fondations (packages/geo) | 📅 P6 | oui | nouveau | projections (liste fermée), rewind, assets Natural Earth versionnés (110m et 50m, admin-1 par pays), composants SSR GeoMap, MapLegend (le tableau accessible est le DataTable de @recit/data), plugin remark ::map. P15 : jointure ISO et rapport des clés non appariées. Options : gazetteer hors ligne, POV | d3-geo 3.1.1, d3-scale 4.0.2, d3-scale-chromatic 3.1.0 (déjà dans le lockfile), @recit/core, @recit/data dès P6 (DataTable du tableau accessible ; jointure data= à partir de P15) |
@recit/atlas | formats (packages/formats/atlas) | 📅 P15 | oui | nouveau | format atlas (retenu, question 1), lecteur au défilement, AtlasRoot, AtlasView, ponts vers deck, motion, stories | @recit/geo, @recit/core, @recit/theme |
@recit/geo/tiles (sous-chemin) | — | option | oui | nouveau | option, plus tard (question 2) : île cliente MapLibre, chargée paresseusement, web seulement | maplibre-gl, pmtiles en dépendances optionnelles (peer) |
Pourquoi une fondation et non un sous-chemin de @recit/data : @recit/geo a une raison d'être
installé seul (une carte dans une doc sans données tabulaires) et embarque des assets lourds que
report sans carte ne doit pas tirer. Il dépend de @recit/data (dès P6, pour le DataTable ; la
jointure arrive en P15), jamais l'inverse ; aucun format n'est importé (scope:foundation). À
reporter : @recit/geo dans le glob des fondations de 03-packaging.md §1, @recit/geo et
@recit/atlas dans 03 §6 et dans les noms npm à réserver, README.md (« Règles du cœur ») règle 2,
et la ligne Récit de NOTICE.md.
Réutilisé tel quel :
remark-directiveet le pipeline unified de@recit/core(directives, numéros de ligne) ;- loaders CSV/JSON de
@recit/data(d3-dsvdéjà présent) ; - pour le PDF et les PNG, la CLI, qui imprime ou capture la vue de la carte par
@recit/paged/runner(Chromium de Playwright, pair optionnel) ;@recit/geon'importe pas@recit/paged;@recit/motion-render(Remotion) pour les plans vidéo ; - tokens de
@recit/themepour les palettes (teinte unique Q07, dégradés séquentiels et divergents).
Rejetés : Leaflet (doublon de MapLibre, centré raster), Observable Plot pour les cartes (DOM virtuel
en SSR), @turf/*, world-atlas (pas de POV), resvg, sharp, géocodeurs en ligne au build.
Génération des assets : un script packages/geo/scripts/build-assets.ts télécharge une version
épinglée de Natural Earth, simplifie et convertit en GeoJSON, puis écrit les fichiers versionnés
dans packages/geo/assets/. Les outils de conversion (topojson-server, topojson-simplify, ISC) ne
sont que des devDependencies du script, ou bien l'on part d'assets préconstruits (à décider au lot
P6). Le poids des assets est mesuré (GE2) avant de choisir s'ils sont livrés dans le package npm
ou générés chez l'auteur ; aucun chiffre n'est écrit dans ce plan avant la mesure.
4.2 Site maps.recit.dev
Lignes à ajouter dans 07-domaines.md :
| Domaine | Rôle | Dossier | Contenu | Phase |
|---|---|---|---|---|
| maps.recit.dev | format atlas (P15) et bloc ::map | sites/maps | hero = un atlas Lorem jouable (France puis monde) ; galerie des formes de carte, démo multilingue | P15 |
| Alias | Cible | Pourquoi |
|---|---|---|
cartes.recit.dev | maps.recit.dev | mot du backlog (règle commune de 07 §2) |
atlas.recit.dev | maps.recit.dev | nom technique du format |
| Site | Projet Vercel | Racine du projet | Build | Ouverture |
|---|---|---|---|---|
maps.recit.dev | recit-maps | sites/maps | nx run maps:build. Les SVG sont générés au build, les PNG et MP4 d'exemple en CI | P15 |
- Port local : 3013, d'après la table d'attribution unique de
07-domaines.md§4 (components 3010, form 3011, directory 3012, maps 3013, ai 3014). Ajouter"maps"àSiteIdet à la tableSITES(seule source des liens inter-sites). - Si la décision sur
atlas(24/10, réversible) était revue, maps.recit.dev reste la vitrine d'un bloc, dans la catégorie « site d'outil » de07-domaines.md§1. - Composants site-kit (aucun nouveau composant de site-kit) :
| Composant | Rôle | Exemple sur maps.recit.dev |
|---|---|---|
FormatHero | démo jouable écrite dans le format | l'atlas atlas-tour en mode side, source dépliable |
SourceToggle | rendu / Markdown / JSON | la même carte vue en ::map, en JSON et en SVG |
LocaleDemo | même source en en et fr | noms de pays par Intl.DisplayNames, en ar pour montrer le RTL |
FormatGallery | galerie des cas | choroplèthe monde, régions d'un pays, points, tracé, worldview |
ExportPanel | exports (composant unique de @recit/site-kit, lit exports.json) | PDF, PNG 9:16, MP4 (générés en CI), variantes kind de ExportManifestSchema (@recit/core), ajoutées dans leur phase |
BridgeCard | ponts | atlas → deck, motion, stories |
TemplateCard | gabarits | npm create recit -- --template atlas-tour |
DocsLinks | renvoi | docs.recit.dev/atlas/… et docs.recit.dev/components/map |
- Règle anti-doublon : aucune doc sur maps.recit.dev, renvoi vers docs.recit.dev › Formats › Atlas
et › Components › Map (section unique des blocs transverses). La vitrine est écrite en
atlassi le format existe, en en et en fr. - Icône :
brand/formats/fmt-atlas-{icon,color,dark,light,stacked}.svg, dans la teinte unique.
4.3 Exemples et e2e
| Exemple | Paquet | Contenu |
|---|---|---|
examples/map-country | recit-example-map-country | lot P6 : page docs et report d'une page, régions d'un pays (admin1), marqueurs lat/lon |
examples/map-world | recit-example-map-world | lot P6 : monde en admin0 avec marqueurs ; P15 : choroplèthe sur données Lorem générées |
examples/atlas-tour | recit-example-atlas-tour | P15 : format atlas, tournée Lorem en cinq vues, en en et fr (atlas-tour.fr.md), JSON compagnon |
Nommage : map est un bloc, pas un format ; la règle commune (06, « Backlog ») autorise
examples/<bloc ou outil>-*. Données Lorem uniquement, générées par script, jamais de chiffres
réels. Lieux = villes réelles seulement comme coordonnées (domaine public via Natural Earth),
libellés en Lorem. Une carte pour le gabarit lebrocoli.fr serait une section optionnelle du dossier
unique examples/scenario-lebrocoli (plan 14), jamais un second dossier.
e2e/atlas.e2e.ts et e2e/map-block.e2e.ts avec captures :
- choroplèthe en clair et sombre, en en et fr, mobile ;
- vue
admin1, marqueurs, tracé ; - défilement de l'atlas (une capture par vue) ;
- aucune requête réseau pendant le rendu (interception Playwright qui fait échouer le test) ;
- PDF rendu page par page en PNG (goldens), golden PNG d'un plan motion ;
- erreurs avec numéro de ligne (région inconnue, valeur inline, coordonnées hors bornes) ;
- axe sans violation sur la figure et le tableau.
5. Sécurité, vie privée, licences
- Aucune requête réseau par défaut, ni au build ni au rendu : le fond, le gazetteer et les données sont locaux. Le rendu statique ne transmet donc l'IP du visiteur à personne.
- Tuiles en opt-in explicite (
mode=tiles, option) : OpenFreeMap ou PMTiles auto-hébergé ; jamaistile.openstreetmap.org. La doc et la page vie privée du site signalent la transmission de l'IP à l'hébergeur des tuiles. Attribution visible imposée par le composant, non désactivable. - Aucun géocodage implicite, et aucune commande de géocodage en v1 (tranché le 24/10). Plus
tard, le seul géocodage sera
recit geocodedu plan 12 : 1 requête/s, cache local, User-Agent Récit, attribution ODbL, résultat écrit dans le compagnon JSON (jamais dans la source) et relu par l'auteur. - Frontières. Politique documentée sur docs.recit.dev : Natural Earth de facto par défaut,
worldview=pour les positions de droit nationales. Récit ne tranche aucun différend. - Données personnelles. Un marqueur peut désigner un domicile : avertissement de lint si un
::markerporteperson=truesansconsent=true, arrondi optionnel des coordonnées (precision=). Exemples sans aucune adresse de personne. - Sources de l'auteur.
source=obligatoire pour une choroplèthe ; une source de données non Natural Earth (IGN Admin Express, GADM…) porte un champlicensereporté dans la légende.
Lignes à prévoir dans NOTICE.md, « What comes from where » : étendre la ligne des formats à
formats/{…,atlas} et ajouter packages/geo (fondation, code original).
« Third-party assets » :
| Item | Used in | License / terms |
|---|---|---|
| Natural Earth 5.1.x (admin-0, admin-1, POV, populated places) | packages/geo/assets | Public domain ; « Made with Natural Earth » cité par courtoisie |
| d3-geo, d3-scale, d3-scale-chromatic | @recit/geo | ISC |
| topojson-server, topojson-simplify (si retenus) | packages/geo/scripts (devDependencies) | ISC |
| maplibre-gl (option) | @recit/geo/tiles | BSD-3-Clause |
| pmtiles (option) | @recit/geo/tiles | BSD-3-Clause |
| OpenFreeMap styles et tuiles (option) | @recit/geo/tiles | MIT ; attribution « OpenFreeMap © OpenMapTiles Data from OpenStreetMap » obligatoire |
| Données OpenStreetMap via tuiles (option) | @recit/geo/tiles | ODbL 1.0, attribution obligatoire. Nominatim : ligne unique du plan 12 (recit geocode) |
| Protomaps basemap (option) | extraction PMTiles des sites | ODbL Produced Work, attribution OSM |
6. Phases
Placement dans 06-roadmap.md (« Backlog : plans 10 à 14 ») : lot avancé en P6, puis P15
Atlas après la 1.0, et des options. Rien en P4 ni en P5. Identifiants de tâches : GE pour
@recit/geo, AT pour atlas (préfixe + numéro, comme R1 ou ST1 dans 08-formats.md). La
décision Plot ou d3 (06, question 10) est prise : d3 sans DOM (24/10).
Lot P6 — Bloc ::map statique (M)
- GE1 —
packages/geo: squelette tsdown, exportstypes/development/default, tag nxscope:foundation, version plan, nom npm réservé. - GE2 — Script d'assets Natural Earth épinglé : monde 110m et 50m, admin-1 par pays. Mesure du poids des assets avant de choisir leur mode de livraison (package npm ou génération chez l'auteur).
- GE3 — Projections (liste fermée),
fitExtent,rewind(point à vérifier dans le README d3-geo). Tests unitaires (anneaux inversés, emprises). - GE4 — Schémas
MapBlocketLayer(marqueurs) dans@recit/core/schema, JSON Schema générés.Placeest importé du plan 12 à partir de P10. - GE5 — Plugin remark
::map/:::map/::marker(lat/lon), nommapdans le registre de directives, erreurs avec numéro de ligne. - GE6 — Composants SSR
GeoMap,MapLegend, tableau accessible parDataTable(§3.6), palettes depuis@recit/theme. - GE7 — Intégration docs, report, deck ; PDF par la CLI (
@recit/paged). - GE8 —
examples/map-country,examples/map-world;sites/docs› Components › Map. - Tests : goldens SVG et PDF, e2e sans réseau, erreurs, axe.
Sortie :
- la même source rend une carte identique en web et en PDF, sans aucune requête réseau ;
- une région, une projection ou des coordonnées invalides sont refusées avec numéro de ligne ;
- les deux exemples s'affichent en en et en fr.
P15 — Choroplèthe, mouvement, atlas et maps.recit.dev (L)
- GE9 — Choroplèthe : jointure ISO (
ISO_A3_EH/ADM0_A3) avec@recit/data, rapport des clés non appariées,source=obligatoire, aucune valeur inline. Tests (-99, clés orphelines). - GE10 —
::route,::area, blocsgeojson. - GE11 — Blocs
mapdans@recit/motion-blocks: caméra (interpolation de projection), tracé progressif, apparition des classes, fonctions pures de la frame. Vue 9:16 pourstories. - AT1 —
packages/formats/atlas:"atlas"ajouté àStoryFormatcomme ajout compatible en v1, sectionatlas:derecit.config.ts, attribut de scènecamera=. - AT2 — Lecteur au défilement
AtlasRoot,AtlasView, mises en pageside,overlay,full, navigation clavier,prefers-reduced-motionrespecté. - AT3 —
examples/atlas-touren en et fr, avec JSON compagnon ; équivalence md ⇄ JSON testée. - AT4 —
sites/maps(maps.recit.dev, port 3013, projet Vercelrecit-maps, aliascartesetatlas) ;sites/docs› Formats › Atlas ; icônefmt-atlas-*. - AT5 — Ponts atlas → deck, motion, stories ; vue carte facultative de
calendarpour les événements dont lePlacea ungeo. - Tests : goldens PNG, déterminisme, e2e par vue, mobile, RTL, sans réseau, chaque pont.
Sortie :
- une choroplèthe sans
data=ou sanssource=est refusée avec numéro de ligne ; - une
::mapd'un exemple produit un plan MP4 et une carte 9:16 sans nouvelle dépendance de rendu ; examples/atlas-tourse lit au défilement et au clavier, en en et en fr, et produit un deck, un MP4 et une story ;- maps.recit.dev est en ligne.
Options, plus tard (questions 2 et 3, tranchées)
- GE12 —
worldview(défaut : Natural Earth de facto) et variantes POV de Natural Earth, après relevé de la liste exacte des POV de 5.1.x. - GE13 — Sous-chemin
@recit/geo/tiles(option) : île MapLibre chargée paresseusement (React.lazy), web seulement, jamais sur le chemin d'entrée ; sources OpenFreeMap (attribution imposée) et PMTiles auto-hébergé ; repli statique automatique hors web ;size-limit. - GE14 — Gazetteer hors ligne pour
::marker{place=…}; encarts maison (DROM).
Sortie : chaque option retenue a son exemple, ses goldens et ses lignes NOTICE.md ; le bundle
d'entrée ne grossit pas (size-limit).
7. Risques
| Risque | Parade |
|---|---|
| Frontières disputées jugées fausses dans un pays | worldview= en option, de facto par défaut, politique documentée, aucune position déduite de la langue |
| Polygones inversés (sens des anneaux) | rewind systématique et tests sur GeoJSON RFC 7946 |
| Poids des assets admin-1 et 10m | 110m par défaut, admin-1 découpé par pays, poids mesuré en GE2 avant de choisir le mode de livraison |
| Coupure ou changement des règles des tuiles tierces | statique par défaut ; tuiles en option ; PMTiles auto-hébergé |
| Fuite d'IP vers un tiers | aucun réseau par défaut ; opt-in documenté |
| Bundle alourdi par MapLibre | sous-chemin optionnel, chargement paresseux, size-limit |
Clés ISO non appariées (-99, codes historiques) | jointure sur ISO_A3_EH/ADM0_A3, rapport des clés orphelines |
| Licence d'une source de l'auteur (GADM, restrictions commerciales) | champ license, lint, mention en légende |
| Inversion latitude/longitude | lint (bornes, point hors région) |
Deux moteurs de graphiques (Plot dans @recit/data, d3 dans @recit/geo) | écarté : d3 sans DOM pour les deux (06, question 10, tranchée le 24/10), reporté dans 03 et 08 |
| Périmètre : un format de plus avant la 1.0 | seul le bloc statique en lot P6 ; atlas retenu pour P15, après la 1.0 |
8. Questions ouvertes
Les questions communes au backlog (domaine cartes en français et doublon de la ligne du backlog,
le brocoli, l'exception lebrocoli.fr, « Pet », « skills », « projet basic ») sont regroupées une
seule fois dans 06-roadmap.md, « Questions ouvertes ». Le choix Plot ou d3 y est la question 10
(tranchée : d3 sans DOM). Le géocodage est la question 7 du plan 12 (tranchée : coordonnées écrites
par l'auteur, pas de commande en v1). Les lieux réels ou fictifs de l'exemple lebrocoli.fr et le
second nom de la ligne « cartes.recit.dev | cartes.recit.dev » restent à l'utilisateur (06, « À
l'utilisateur »). Les questions ci-dessous ont été tranchées le 24/10/2026, sans l'utilisateur et à
sa demande (décisions réversibles), sauf la 4 :
Format : un vrai formatTranché (24/10) : blocatlas, ou seulement le bloc::map+@recit/geoavec un site vitrine ? Le nomatlasconvient-il ?::mapd'abord (lot P6), formatatlasen P15.Interactif : faut-il prévoir MapLibre en option, ou s'en tenir au SVG statique ?Tranché (24/10) : MapLibre plus tard, en option ; SVG statique par défaut.« Sous différents pays » : régions d'un pays, points de vue sur les frontières, ou les deux ? QuelTranché (24/10) : les régions d'un pays, plus l'optionworldviewpar défaut ?worldview, avec Natural Earth par défaut.- Formes de carte attendues : choroplèthe, points, tracés suffisent-ils, ou faut-il aussi cartes en cercles proportionnels, cartogrammes, cartes de chaleur ?
Sources hors Natural Earth (IGN Admin Express sous Licence Ouverte, GADM) : autorisées avec un champTranché (24/10) : Natural Earth seul en v1 ; les autres sources (aveclicenseobligatoire ?licenseobligatoire) plus tard.Placement : bloc statique en lot avancé de P6, puis P15 Atlas après la 1.0 : d'accord ?Tranché (24/10) : oui (ordre du backlog accepté, 06 question 14).