Nouveaux blocs et outils

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 ::map produit 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.
    1. Un bloc transverse ::map / :::map, utilisable dans tous les formats (comme ::chart).
    2. 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.
  • « 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.
  • 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é avec calendar (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 :

PaquetPrésent ?Par quiLicence
d3-geo 3.1.1 (+ @types/d3-geo 3.1.0)oui, transitifd3 7.9.0 ← mermaid (packages/site, packages/components)ISC
d3-scale 4.0.2, d3-scale-chromatic 3.1.0, d3-dsv 3.0.1oui, transitifmême chaîneISC
commander 2.20.3 (seule dépendance de topojson-client)oui, transitifune autre chaîne ; @recit/cli utilise commander 14.0.3MIT
remark-directive 3.0.1, hastscript, hast-util-to-html, happy-domouipackages/core, testsMIT
topojson-client, world-atlas, maplibre-gl, leaflet, pmtiles, @turf/*, @observablehq/plot, jsdom, linkedomnon——

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îne d SVG 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.fitExtent cadre 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 rewind maison (si geoArea(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ï) : geoAlbersUsa les gère pour les États-Unis ; ailleurs, ils sont à faire à la main. d3-composite-projections serait 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_A3 vaut -99 pour quelques pays (historiquement France et Norvège). Joindre sur ISO_A3_EH ou ADM0_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 champs NAME_xx de Natural Earth servent de secours.

2.4 Cartes glissantes (interactives)

MapLibre GL JSLeaflet
LicenceBSD-3BSD-2
Rendutuiles vectorielles, WebGL2, styles JSONtuiles raster (vectoriel par plugins)
Poidslourd, à mesurer avant tout chiffre, chargement paresseux obligatoireléger
Sources de tuilesOpenFreeMap, 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 ```geojson et ```topojson en carte interactive : Récit doit les honorer, pour qu'une source reste lisible sur GitHub.
  • Obsidian Leaflet (bloc ```leaflet, clés YAML lat, 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

3. Décisions proposées

Format des décisions : chaque décision donne les options et une recommandation.

#SujetOptionsRecommandation
D1Nom du sitecartes.recit.dev (backlog), maps.recit.dev, atlas.recit.devmaps.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
D2Bloc ou formatbloc transverse seul ; format seul ; les deuxBloc d'abord, ::map + @recit/geo (fondation, utile partout). Le format atlas (une scène = une vue) vient en P15 (tranché, question 1)
D3Nom du formatmap, maps, atlasatlas : distinct de la directive ::map et de Map de JavaScript, et parlant (« un atlas raconté »)
D4Rendu par défautSVG statique ; tuiles interactivesSVG statique au build, sans réseau, partout. MapLibre seulement en option, en phase ultérieure, et seulement sur le web
D5Données de fondworld-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
D6Pays et frontièresworldview lié à lang ; expliciteOption (question 3, tranchée), après le lot P6 : explicite, worldview=default (de facto Natural Earth). La langue ne choisit jamais une position politique
D7Géocodageen ligne au build ; gazetteer hors ligne ; jamaisCoordonné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
D8TopoJSONtopojson-client au build ; GeoJSON préconvertiGeoJSON préconverti dans les assets, sans option topojson-client (il ajouterait commander 2.x à côté de commander 14)
D9Base de @recit/dataObservable Plot ; d3 sans DOMTranché 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.

MarkdownMarkdown
--- title: Lorem Ipsum Quarterly format: report lang: en data: sales: ./data/sales-by-country.csv venues: ./data/venues.csv --- --- id=world ## Lorem by country ::map{data=sales key=country value=revenue projection=equal-earth region=world scheme=sequential caption="Lorem ipsum dolor sit amet" source="Lorem dataset"} --- id=france ## Ipsum in France :::map{region=FR level=admin1 projection=conic-conformal height=480 caption="Dolor sit amet"} ::marker{lat=48.8566 lon=2.3522 label="Lorem"} ::marker{place="Lyon, FR" label="Ipsum"} ::route{from="Lorem" to="Ipsum" style=dashed} ::: --- id=venues ## Dolor venues ::map{data=venues mode=points lat=lat lon=lon label=name region=FR} ```geojson { "type": "FeatureCollection", "features": [] } ```
ÉlémentRô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)
regionworld, un code ISO 3166-1 alpha-2 (FR), ou une emprise [w,s,e,n]
leveladmin0 (pays) ou admin1 (régions d'un pays)
worldviewoption : default ou un code ISO (variante POV Natural Earth)
projectionliste fermée : equal-earth, natural-earth, mercator, conic-conformal, albers, albers-usa, orthographic, azimuthal-equal-area
data key valueP15 : jointure avec une source de @recit/data (alpha-2 ou alpha-3). Seule source autorisée des valeurs d'une choroplèthe
schemetokens 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 / ```topojsonrendus avec les options par défaut (compatibles GitHub) ; dans un :::map, ils deviennent une couche
caption, sourcelé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 place inconnus 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 : value vient de data=, 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.

MarkdownMarkdown
--- title: Lorem Tour format: atlas lang: en data: stops: ./data/stops.csv atlas: projection: conic-conformal basemap: land # land | admin0 | admin1 | none | tiles (later) worldview: default layout: side # side | overlay | full --- --- id=start camera=FR # Lorem ipsum Dolor sit amet, consectetur adipiscing elit. --- id=paris camera="[2.2,48.8,2.5,48.95]" duration=4s ## Lorem in Paris ::marker{lat=48.8566 lon=2.3522 label="Lorem"} Sed do eiusmod tempor incididunt ut labore. --- id=route camera=FR ## From Lorem to Ipsum ::route{from="Lorem" to="Ipsum" reveal=draw} ::marker{place="Lyon, FR" label="Ipsum"} --- id=stops camera=FR ## Every stop ::map{data=stops mode=points lat=lat lon=lon label=name} :::notes Lorem ipsum notes for the presenter. :::
ÉlémentRô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)
:::notesnotes, comme dans le deck

Règles :

  • une scène sans camera hérite de la précédente ; la première scène sans camera prend 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, geo facultatif, address libre pour l'affichage seulement. Une carte ignore un Place sans geo (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 } avec label?: Localized ; Route = { type: "route", from, to, style?, reveal? }.
  • AtlasSource (option) rejoint l'union discriminée StorySourceSchema comme 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 le body d'une scène deck, report ou docs : MapBlock valide la directive, sans champ blocks dans SceneSource.

Ligne à ajouter au tableau « Champs typés par format » de 09-langues-et-json.md §2.3 :

FormatUne scène JSON contientExemple de champ localisable
atlascamera (code ISO, emprise ou centre), layers (marqueurs, tracés, zones), body, notes, durationMslabel d'un marqueur, caption, body

Coordonnées, emprises, codes ISO, valeurs et worldview ne sont jamais localisables.

Exemple de scène JSON :

JSONJSON
{ "id": "paris", "camera": [2.2, 48.8, 2.5, 48.95], "layers": [{ "type": "marker", "lat": 48.8566, "lon": 2.3522, "label": { "en": "Lorem", "fr": "Lorem FR" } }], "body": { "en": "Sed do eiusmod tempor.", "fr": "Sed do eiusmod tempor (fr)." } }

É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 :

DomaineRègle
atlas et ::mapnoms 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

FormatCe que devient une cartePhase
docsfigure SVG dans une page du framework de sites, <details> avec le tableaulot P6
reportfigure SVG ; en PDF via @recit/paged, vectorielle, avec la source en légendelot P6
deckfigure dans une slide ; pont atlas → deck (P15) : une vue = une slidelot P6 / P15
bookfigure numérotée, table des figures ; jamais de tuilesP15
motionblocs 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 planP15
storiesune vue = une carte 9:16 ; export PNG par le chemin d'export d'images existantP15
calendarvue carte des événements qui ont un Place avec geo (facultative)P15
toonaucun pont promis : pas de correspondance fermée—

Ponts à ajouter au tableau de 08-formats.md §0 (correspondances fermées uniquement) :

De → versCorrespondancePhase
atlas → deckune vue → une slide, couches gardées, texte soumis aux règles de densitéP15
atlas → motionune vue → un plan, transition = interpolation de projection, reveal → animationP15
atlas → storiesune vue → une carte 9:16P15
formats de la liste fermée de 15-export-pdf.md §1 → PDF::map en SVG, imprimé par la CLI et @recit/pagedlot 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

PackageGroupeStatutPubliéSourceContenuDépendances notables
@recit/geofondations (packages/geo)📅 P6ouinouveauprojections (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, POVd3-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/atlasformats (packages/formats/atlas)📅 P15ouinouveauformat 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)—optionouinouveauoption, plus tard (question 2) : île cliente MapLibre, chargée paresseusement, web seulementmaplibre-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-directive et le pipeline unified de @recit/core (directives, numéros de ligne) ;
  • loaders CSV/JSON de @recit/data (d3-dsv dé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/geo n'importe pas @recit/paged ; @recit/motion-render (Remotion) pour les plans vidéo ;
  • tokens de @recit/theme pour 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 :

DomaineRôleDossierContenuPhase
maps.recit.devformat atlas (P15) et bloc ::mapsites/mapshero = un atlas Lorem jouable (France puis monde) ; galerie des formes de carte, démo multilingueP15
AliasCiblePourquoi
cartes.recit.devmaps.recit.devmot du backlog (règle commune de 07 §2)
atlas.recit.devmaps.recit.devnom technique du format
SiteProjet VercelRacine du projetBuildOuverture
maps.recit.devrecit-mapssites/mapsnx run maps:build. Les SVG sont générés au build, les PNG et MP4 d'exemple en CIP15
  • 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" à SiteId et à la table SITES (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 » de 07-domaines.md §1.
  • Composants site-kit (aucun nouveau composant de site-kit) :
ComposantRôleExemple sur maps.recit.dev
FormatHerodémo jouable écrite dans le formatl'atlas atlas-tour en mode side, source dépliable
SourceTogglerendu / Markdown / JSONla même carte vue en ::map, en JSON et en SVG
LocaleDemomême source en en et frnoms de pays par Intl.DisplayNames, en ar pour montrer le RTL
FormatGallerygalerie des caschoroplèthe monde, régions d'un pays, points, tracé, worldview
ExportPanelexports (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
BridgeCardpontsatlas → deck, motion, stories
TemplateCardgabaritsnpm create recit -- --template atlas-tour
DocsLinksrenvoidocs.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 atlas si 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

ExemplePaquetContenu
examples/map-countryrecit-example-map-countrylot P6 : page docs et report d'une page, régions d'un pays (admin1), marqueurs lat/lon
examples/map-worldrecit-example-map-worldlot P6 : monde en admin0 avec marqueurs ; P15 : choroplèthe sur données Lorem générées
examples/atlas-tourrecit-example-atlas-tourP15 : 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é ; jamais tile.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 geocode du 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 ::marker porte person=true sans consent=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 champ license reporté 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 » :

ItemUsed inLicense / terms
Natural Earth 5.1.x (admin-0, admin-1, POV, populated places)packages/geo/assetsPublic domain ; « Made with Natural Earth » cité par courtoisie
d3-geo, d3-scale, d3-scale-chromatic@recit/geoISC
topojson-server, topojson-simplify (si retenus)packages/geo/scripts (devDependencies)ISC
maplibre-gl (option)@recit/geo/tilesBSD-3-Clause
pmtiles (option)@recit/geo/tilesBSD-3-Clause
OpenFreeMap styles et tuiles (option)@recit/geo/tilesMIT ; attribution « OpenFreeMap © OpenMapTiles Data from OpenStreetMap » obligatoire
Données OpenStreetMap via tuiles (option)@recit/geo/tilesODbL 1.0, attribution obligatoire. Nominatim : ligne unique du plan 12 (recit geocode)
Protomaps basemap (option)extraction PMTiles des sitesODbL 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, exports types/development/default, tag nx scope: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 MapBlock et Layer (marqueurs) dans @recit/core/schema, JSON Schema générés. Place est importé du plan 12 à partir de P10.
  • GE5 — Plugin remark ::map / :::map / ::marker (lat/lon), nom map dans le registre de directives, erreurs avec numéro de ligne.
  • GE6 — Composants SSR GeoMap, MapLegend, tableau accessible par DataTable (§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, blocs geojson.
  • GE11 — Blocs map dans @recit/motion-blocks : caméra (interpolation de projection), tracé progressif, apparition des classes, fonctions pures de la frame. Vue 9:16 pour stories.
  • AT1 — packages/formats/atlas : "atlas" ajouté à StoryFormat comme ajout compatible en v1, section atlas: de recit.config.ts, attribut de scène camera=.
  • AT2 — Lecteur au défilement AtlasRoot, AtlasView, mises en page side, overlay, full, navigation clavier, prefers-reduced-motion respecté.
  • AT3 — examples/atlas-tour en en et fr, avec JSON compagnon ; équivalence md ⇄ JSON testée.
  • AT4 — sites/maps (maps.recit.dev, port 3013, projet Vercel recit-maps, alias cartes et atlas) ; sites/docs › Formats › Atlas ; icône fmt-atlas-*.
  • AT5 — Ponts atlas → deck, motion, stories ; vue carte facultative de calendar pour les événements dont le Place a un geo.
  • Tests : goldens PNG, déterminisme, e2e par vue, mobile, RTL, sans réseau, chaque pont.

Sortie :

  • une choroplèthe sans data= ou sans source= est refusée avec numéro de ligne ;
  • une ::map d'un exemple produit un plan MP4 et une carte 9:16 sans nouvelle dépendance de rendu ;
  • examples/atlas-tour se 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

RisqueParade
Frontières disputées jugées fausses dans un paysworldview= 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 10m110m 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 tiercesstatique par défaut ; tuiles en option ; PMTiles auto-hébergé
Fuite d'IP vers un tiersaucun réseau par défaut ; opt-in documenté
Bundle alourdi par MapLibresous-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/longitudelint (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.0seul 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 :

  1. Format : un vrai format atlas, ou seulement le bloc ::map + @recit/geo avec un site vitrine ? Le nom atlas convient-il ? Tranché (24/10) : bloc ::map d'abord (lot P6), format atlas en P15.
  2. 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.
  3. « Sous différents pays » : régions d'un pays, points de vue sur les frontières, ou les deux ? Quel worldview par défaut ? Tranché (24/10) : les régions d'un pays, plus l'option worldview, avec Natural Earth par défaut.
  4. Formes de carte attendues : choroplèthe, points, tracés suffisent-ils, ou faut-il aussi cartes en cercles proportionnels, cartogrammes, cartes de chaleur ?
  5. Sources hors Natural Earth (IGN Admin Express sous Licence Ouverte, GADM) : autorisées avec un champ license obligatoire ? Tranché (24/10) : Natural Earth seul en v1 ; les autres sources (avec license obligatoire) plus tard.
  6. 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).