Makina Blog
Carte de Cassini vectorielle
Notre démarche
La carte de Cassini est la première carte de France détaillée et topographique réalisée à l’aide de distances relevées sur le terrain. Sa création a été ordonnée par Louis XIV, les travaux dureront de 1683 à 1818. Quatre générations de la famille Cassini ont travaillé pour la produire. Voir l’article Wikipédia pour plus de détails.
Aperçu de la carte de Cassini
Extrait de la carte de Cassini. Source gallica.bnf.fr / Bibliothèque nationale de France. Le GéoPortail de l'IGN propose d'afficher la carte de Cassini numérisée, en version colorisée. Cette carte a cependant été dessinée à échelle fixe, qui ne s'adapte pas au niveau de zoom au fur et à mesure de la navigation. Nous vous présentons ici notre démarche pour la reproduire vectoriellement.
Une carte vectorielle au style de Cassini
Produire un style de carte vectorielle au format Mapbox GL inspiré des cartes de Cassini représente un défi à relever. Le style d’origine est grandement graphique et en partie figuratif. Les conventions de cartographie moderne ne sont pas encore présentes. Il est nécessaire d’adapter le style aux contraintes des outils modernes. Pour ce projet nous utilisons les données actuelles d’OpenStreetMap au travers de tuiles vectorielles proposées par le projet OpenMapTiles sans les retravailler. Nous pourrons ainsi profiter du style Cassini sur le monde entier.
Par souci de simplification, nous allons nous en tenir à une version monochrome et faire abstraction de la représentation du relief et de la couverture du terrain (bois, landes, marais, plages, prés et vignes).
Aperçu du résultat du style au format Mapbox GL.
Du mono-échelle au multi-échelles
Une difficulté à relever est de construire une carte vectorielle zoomable multi-échelles depuis une carte de Cassini mono-échelle. La carte de Cassini est réalisée à l’échelle d’une ligne pour cent toises. Pour rappel, une ligne est une unité de mesure établie au Moyen-Âge et est égale à un douzième de pouce, soit 2,256 millimètres. Quant à la toise, elle correspond à 6 pieds français, c'est à dire 1,949 mètres.L'échelle d'origine vaut donc, on sort les calculatrices… ~1/86 400.
Villes et autres lieux
Dans le style de Cassini les lieux habités ou ruinés sont représentés par des pictogrammes en fonction de leurs caractéristiques. Il n’y a pas de notion d’ordre entre ces pictogrammes. Ils sont toujours représentés avec la même taille.
Extrait de la légende simplifié de la carte de Cassini
D’une part nous n’avons pas les attributs nécessaires dans OpenMapTiles pour distinguer par exemple les abbayes des prieurés, ou encore certaines caractéristiques n’ont plus de sens aujourd’hui (comme les commanderies ou encore les fiefs). D'autre part, ce catalogue de pictogrammes ne convient pas à la classification moderne que nous faisons des lieux. Nous voulons montrer la taille des villes et leur prédominance administrative (capitales, ou « capitales » administratives locales).
OpenMapTiles catégorise les villes en fonction de leur taille (city, town, village, hamlet, isolated_dwelling). Les villes sont ensuite filtrées en fonction du niveau de zoom des tuiles vectorielles, selon une priorité basée notamment sur la catégorie et l’importance administrative.
Nous sélectionnons donc les symboles suivants et les considérons comme ordonnés du plus importants au moins importants. Afin de les rendre intuitifs, nous utilisons à la fois une progression de taille et de complexité du pictogramme :
- Parsoiſse (également écrit « paroisse » en français moderne, la lettre « ſ » n’existant plus),
- Métairie ou Ferme,
- Carré noir, bâtiment isolé (n’est pas présent dans la légende présentée plus haut).
L’idée est de conserver cette progression de symboles ordonnés et de la réutiliser à différentes échelles. Ainsi les pictogrammes ne vont pas représenter les mêmes catégories de lieux suivant le niveau de zoom, mais uniquement une notion d’ordre.
Au fur et à mesure que nous zoomons sur la carte, le niveau de détails augmente. Nous introduisons alors plus de catégories (taille de ville) à représenter et nous décalons les pictogrammes de plus faible importance sur les objets de catégories inférieures.
Hiérarchie des pictogrammes de ville selon la catégorie et le niveau de zoom :
zoom | city | town | village | hamlet |
0 | ||||
7 | ||||
8 | ||||
10 | ||||
11 | ||||
12 |
Voies
La même problématique, même approche ! Pour représenter les voies, la légende de Cassini montre des routes pavées, des avenues d’arbres (c’est presque un pléonasme, une avenue était par définition longée d’arbres) et des sentiers.
Extrait de la légende simplifiée de la carte de Cassini
Nous allons conserver le motif des voies pavées longées d’arbres pour les routes importantes. Les avenues non pavées ne sont représentées que par les allées d’arbres, sans tracé linéaire. Nous ignorons cette représentation qui est moins courante dans la carte de Cassini et qui ne correspond pas à notre intuition où la voie doit être signifiée par un linaire. Les sentiers y sont dessinés avec deux traits parallèles en pointillés. Nous faisons le choix pour les voies moins importantes d’utiliser une représentation qui n’est pas présente dans la carte de Cassini : deux traits continus, mais cette fois sans les allées d’arbres.
Ici encore les catégories de voies à considérer comme « importantes » vont varier en fonction du niveau de zoom. Pour les niveaux de zooms faibles, seules les voies les plus importantes sont représentées. Nous allons donc n’utiliser qu’un seul type de représentation, et pour ne pas alourdir le rendu, nous ne conserverons que la version sans les arbres.
Pour des raisons purement esthétiques, nous faisons le choix de ne pas représenter les autoroutes et les voies ferrées.
Extrapoler les autres échelles
Lorsque nous dézoomons nous imaginons assez facilement à quoi cela peut ressembler. Nous réduisons le nombre d’objets à représenter, nous décalons les niveaux d’importance des pictogrammes et des représentations pour conserver de la différentiabilité entre les éléments affichés. La carte de Cassini est déjà à une échelle assez faible pour ne pas avoir besoin d’utiliser d’autres modes de représentation pour ces éléments.
À l’opposé, lorsque nous augmentons l’échelle nous introduisons plus de détails qui n’ont pas ou peu de traitements dans la carte de Cassini. Il nous faut donc les extrapoler.
Nous nous inspirons de ce que l’on observe pour les grandes villes. Les pâtés de maison sont représentés de façon détourée et légèrement remplis.
Ville de Paris. Source : gallica.bnf.fr / Bibliothèque nationale de France.
Dans les tuiles OpenMapTiles nous n’avons pas les pâtés de maisons. En revanche, sont présentes les zones bâties et les bâtiments eux-mêmes. Nous jouons avec ces deux éléments pour ajuster la représentation au niveau de zoom. Dans tous les cas nous allons remplir ces polygones décrivant le bâti d’un motif quadrillé très fin.
Des niveaux de zoom 11 à 14,5 nous utilisons les surfaces bâties avec une opacité progressive d’apparition entre les niveaux de zooms de 11 à 14,5 et une disparition entre 14 et 14,5. En plus de cela, nous faisons apparaître les bâtiments progressivement entre les niveaux de zooms 14 et 15.
Transition entre les représentations des bâtiments.
Dans la carte de Cassini, les rues des grandes villes ne sont que des interstices entre les blocs de maisons. Cette approche permet aisément de distinguer les rues dans un tissu urbain dense où les maisons sont contiguës, mais moins dans des zones résidentielles modernes ou les bâtiments sont espacés.
Nous allons donc faire un entre-deux et conserver le tracé des grands axes comme nous le faisons déjà pour les autres niveaux de zoom. Toutefois, pour les rues, voies de services et autres chemins nous faisons le choix de ne pas les tracer pour reproduire l’esprit de la carte de Cassini avec ce vide entre les bâtiments.
À partir du zoom 16, nous considérons qu’il y a assez de place entre les blocs des bâtiments pour écrire le nom des rues (même dans les centres historiques des villes aux rues étroites). Cet ajout des noms de rue est un concept nouveau vis-à-vis de la carte de Cassini, qui ne traite absolument pas cela. On a donc ici ajouté un nouveau type de représentation.
Représentation du nom des voies.
Le fond
Le fond de la carte est une texture représentant du papier ancien. Il est grandement visible sur la carte. Le style Cassini n’utilise quasiment pas de remplissage opaque et était à l’origine entièrement monochrome.
Texture du fond de plan.
La ligne de côte
Sur la carte de Cassini, les mers ne sont pas remplies, le papier reste brut. La ligne de côté est décorée côté mer par des vagues figuratives.
Dans tout ce qui suit « mer » et « océan » se réfèrent à la même chose. D’ailleurs, dans la carte de Cassini l’océan Atlantique est nommé « Mer Océane ».
Représentation de la ligne de côte sur la carte de Cassini. Source : gallica.bnf.fr / Bibliothèque nationale de France.
Ne pouvant pas reproduire le motif d’origine en style vectoriel, nous allons utiliser une autre symbologie. Nous allons décorer les lignes de côte d’une ligne parallèle supplémentaire.
Nous cherchons ici à tracer uniquement la ligne de côte sans faire de remplissage des mers. OpenMapTiles fournit des polygones pour océans et non pour les continents. C’est un choix classique ; nous consultons plus souvent la cartographie de la terre que celle des océans. On réalise ainsi des économies de traitements.
Nous dessinons la ligne de côte par un trait de 3 pixels de large, suivi d’un espacement d’1 pixel, puis un nouveau trait d’1 pixel de large. Se pose alors la question de la position de cet ensemble de 5 pixels relativement à la ligne de côté.
Nous pouvons centrer le trait de 3 pixels sur la ligne de côte ou centrer l’ensemble des 5 pixels. Si nous centrons sur le trait de 3 pixels nous allons avoir 1,5 pixels à l’intérieur des côtes et 3,5 à l’extérieur. Cela va créer un effet d’exagération de la taille des îles. À l’inverse mettre le trait 3 pixels à l’intérieur des côtes va réduire la surface intérieure des îles et continents.
Différence dans la position du tracé de la ligne de côte, intérieur et extérieur.
Nous choisissons de décentrer le trait de 3 pixels de large vers l’intérieur des côtes : 2 pixels à l’intérieur et 1 à l’extérieur. Il y a donc 2 pixels à l’intérieur et 2 pixels à l’extérieur séparé d’1 pixel. Pour tracer en décalé par rapport à la ligne de côte, nous utilisons des offsets négatifs ou positifs suivant le côté considéré.
Cependant, cette approche ne fonctionne pas à tous les niveaux de zooms. OpenMapTiles utilise en effet, deux sources de données différentes pour les océans. Du zoom 0 au zoom 7, c’est un polygone unique et simplifié. À partir du zoom 8 les données détaillées d’OpenStreetMap sont utilisées. Mais pour cela, elles sont préalablement découpées selon des rectangles par le projet « OSM Data ».
Découpe des océans dans OSMData.
Cette découpe permet d’avoir des polygones plus petits et donc plus faciles à traiter. Toutefois, ils introduisent également des découpes arbitraires non différenciables des lignes de côtes. Ce découpage est fait pour être traité comme des polygones surfaciques et non des contours.
Si nous gardons la même stratégie de dessins pour la ligne de côte nous obtenons donc des tracés dans les océans le long de ces découpes arbitraires.
Contours des polygones des océans.
À partir du zoom 8 nous ne pouvons donc pas tracer la côte sur la ligne de côte. Il faut la dessiner à l’extérieur des polygones des océans, c’est dire côté terre. Ces découpes arbitraires dans les océans vont alors être tracées entièrement sur les polygones voisins. C’est là qu’intervient notre astuce ; nous affichons de nouveau les océans, par-dessus, en remplissant cette fois ces polygones avec la même texture que le fond. Cela va masquer les dessins de découpe des polygones dans les océans, mais pas ceux côté terre.
Par contre, dessiner côté terre donne une apparence amoindrie à la surface des îles et des continents. À partir de cette échelle (zoom 8 et plus) cela va moins se remarquer. Nous ne pouvons l’observer que sur les petites îles et les isthmes.
Pour dessiner à l’extérieur des polygones des océans, côté terre donc, nous pouvons utiliser un offset négatif. Malheureusement les offsets négatifs importants introduisent beaucoup d’artefacts et donnent un résultat qui n’est pas souhaitable.
_ Conséquence d'un offset négatif important._
Nous allons donc partir avec autre technique et profiter du fait que nous avons déjà choisi de redessiner les polygones des océans avec la texture du fond de plan pour masquer des tracés indésirables.
Nous dessinons un trait de côte plus épais et centré sur la ligne de côte. Nous allons même le dessiner trois fois. Une première fois pour le trait de 3 pixels, nous dessinons un trait de 10 pixels de large ((3+1+1) × 2). Nous venons l’évider en redessinant un trait toujours centré sur la ligne de côte avec la texture de fond de 4 pixels de large (2 × 2). Il va donc rester en apparence deux trait parallèles de 3 pixels de large de part et d’autres de la ligne de côte. Pour finir nous dessinons un trait de 2 pixels de large (1 × 2). Comme l’ensemble est réparti sur la ligne de côte et que nous cachons la moitié coté océan nous obtenons alors bien le résultat attendu : 1 pixel tracé, 1 pixel sauté et 3 pixels tracés.
Il reste encore un problème à traiter. Nous travaillons en vectoriel donc le passage des niveaux de zoom se fait de façon continue et non pas par saut de niveaux comme pour les tuiles rasters. Les deux traits pour représenter la ligne de côte ne sont pas positionnés de la même façon par rapport à la ligne de côte suivant le niveau de zoom. Pour les zooms faibles, les traits sont à peu près centrés dessus, mais pour les zooms plus forts ils apparaissent complètement à l’intérieur des cotes (bien que tracé sur la ligne de côte et à moitié masqué). Il va donc y avoir un saut des traits de côté lors du franchissement du niveau de zoom 8.
Pour éviter cela nous allons progressivement déplacer les tracés entre les niveaux de zoom 6 et 8. Nous passons ici sous silence les détails techniques et les calculs d’interpolations entre les deux modèles de dessin, ainsi que le passage de : "sous le masque des océans à au-dessus", pour pouvoir dessiner à l’intérieur des océans.
Toutes ces considérations pourraient laisser penser à une grande complexité. Toutefois en pratique pour l’ensemble des niveaux de zooms il ne suffit que de six calques pour atteindre ce résultat.
Pour un meilleur design, nous utilisons de discrets pointillés pour le tracé fin de la côte, sa largeur et son opacité varient en fonction du niveau de zoom. Le fond de la carte étant une texture, nous utilisons dans tous les cas une légère transparence pour faire ressortir le « grain du papier ».
Zoom sur la ligne de côte.
Cours d’eau et pièces d’eau
Contrairement aux mers, les pièces d’eau sont remplies d’un motif. Les grands cours d’eau sont remplis de traits parallèles aux berges signifiant le courant. Mais comme pour les vagues des océans nous ne pouvons pas reproduire aisément ce motif.
Représentation des pièces d’eau sur la carte de Cassini. Source : gallica.bnf.fr / Bibliothèque nationale de France.
De plus, OpenMapTiles ne fait pas la distinction entre les pièces d'eau et les cours d'eau. Nous choisissons donc d'appliquer le même motif partout.
Les lacs sont représentés par un polygone unique dans OpenStreetMap. Cependant les lits des cours d’eau, ce sont des séries de polygones mis bout à bout. Représenter le lit complet d’un fleuve par un unique polygone le rendrait trop complexe à maintenir et à manipuler. Nous devons donc faire face à nouveau au même problème que pour les découpes arbitraires des océans, mais cette fois-ci pour tracer les berges. Il est donc nécessaire de tracer les berges, puis d'évider les cours d’eau eux-mêmes pour effacer les jonctions entre les polygones mis bout à bout, et pour finir d’appliquer le motif des surfaces en eau.
Il y a toutefois deux modes de représentation des cours d’eau dans la carte de Cassini, comme dans OpenStreetMap. Sous forme surfacique pour les grands cours d’eau et sous forme linéaire pour les plus petits ou lorsque le niveau de zoom devient faible.
Nous allons représenter les pièces d’eau et les lits des grandes rivières de la même façon que les cours d’eau linéaires. Pour les linéaires nous allons ajouter de l’épaisseur au trait pour les représenter de la même façon que les cours d’eau sous forme de polygones. Nous ne faisons pas de représentation sous forme linéaire pure, nous représentons toujours les berges et l’eau.
Les confluences du Doubs, de la Saône et un peu plus en aval de la Dheune.
La voirie
Le motif de base de représentation de la voirie est deux traits parallèles. Une approche naïve pourrait être de faire deux tracés par offset depuis le linéaire de voirie, mais cela produit des artefacts. Une autre approche est d’utiliser les lignes évidées (line casing) qui tracent justement deux traits de part et d’autres d’une ligne. Malheureusement ces deux approches ne sont pas satisfaisantes aux jonctions.
Il est nécessaire d’effacer les artefacts après le dessin des voies. Il faut donc tracer un évidage dans tous les cas. Nous allons donc utiliser un trait épais de 6 pixels, puis l’évider par un trait de 2 pixels avec la texture de fond. Ce qui au final donne l’effet de deux traits parallèles de 2 pixels avec des jonctions propres.
Sans et avec évidage supplémentaire.
Pour la classe de voies la plus importante, il faut décorer les voies d’allées d’arbres. Ces arbres vont être représentés par un simple pixel. L’idée ici est de tous les représenter des deux cotés en une fois, à l’aide d’un tracé d’une ligne évidée en pointillé.
Afin de donner un effet un peu plus « tracé manuel » à ces pointillés, nous essayons de les rendre légèrement irréguliers. Nous utilisons un motif de 1 pixel pour un arbre, puis 2,5 pixels d’espacement, suivit de 1,2 pixels de large pour l’arbre suivant et enfin 2,2 pixels d’espacement avant de recommencer le motif. Cette couche « d’arbres » doit être positionnée avant le tracé général de la voie pour profiter du nettoyage par l’évidage central de la voirie.
Les différents types de voies.
Chez Cassini, ni monstre, ni muse dans les océans, juste des bateaux. La carte est décorée de gréements (pas encore vieux) et des noms des baies et des mers. Afin de reprendre cet esprit, nous créons manuellement un jeu de données complémentaire de points servant d’ancrage aux bateaux et de lignes pour supporter les noms des mers. Ce jeu de données va constituer une source de tuiles vectorielles complémentaire. Nous allons ici nous limiter à décorer les côtes françaises.
Nous mettons les bateaux de décoration à l’échelle en fonction du zoom (cette technique est une mauvaise idée, voir plus bas). Leur taille va être adaptée en fonction du niveau du zoom pour occuper un espace fixe de l’océan.
Bateaux à taille fixe dans l’océan.
Avec un style au format Mapbox GL on peut inclure de petits éléments graphiques, des sprites, pour les utiliser comme symboles. Une petite étoile pour une capitale, une enveloppe pour un bureau de poste… ou les utiliser comme motif de remplissage : des arbres pour les forêts ou des vagues pour de l’eau… Les sprites dans les styles au format Mabox GL sont des images vectorielles, mais converties en images bitmap. Bien que vectoriel, Mabox GL ne supporte pas les sprites vectoriels.
Les sprites du style Bright.
Le but ici est de construire un style largement basé sur des images. Nous allons donc détourner et abuser des sprites.
Les sprites dans le format de style Mapbox GL sont à la fois une image bitmap au format PNG et un fichier de description au format JSON qui donne les positions des sprites dans l’image. C’est l’outil spritezero qui sert à produire ces deux fichiers depuis une liste d’images vectorielles au format SVG.
Toutefois nous ne partons pas d’images vectorielles mais d’images qui sont déjà au format bitmap. Il va donc nous falloir créer le PNG et le JSON nous-même.
Dans nos sprites nous embarquons la texture de fond. Elle est de grande taille et donne l’effet de papier. Les sprites ne sont généralement pas faits pour inclure de grandes images. Nous incluons les bateaux à mettre dans les mers, les symboles de villes et de villages et des motifs pour l’eau douce et les zones urbaines. Ces images ont été extraites de la carte de Cassini elle-même.
Lors de l’affichage il est techniquement possible de mettre à l’échelle les sprites. Mais le résultat n’est graphiquement pas de qualité avec Mabox GL JS. En pratique, si l’on souhaite afficher des sprites à différentes tailles, il faut embarquer ces sprites dans différentes résolutions. Mais nous ne le faisons pas ici. À l’exception des bateaux nous gardons les sprites dans leur taille d’origine. Nous n’avons également qu’une version de chaque sprite.
Police de caractèresLa carte de Cassini utilise déjà des caractères d’imprimerie pour tous ces toponymes.
Nous choisissons une police approchante du le style de la carte : « GoudyBookletter1911 », disponible dans le domaine public. Toutefois, les styles au format Mapbox GL utilisent leur propre format de fichier pour les polices. Ce sont des fichiers binaires stockés au format PBF. Chaque fichier contient une plage de caractères UTF-8. Le but est de n’avoir à télécharger que les blocs de caractères effectivement utilisés lors de l’affichage, même si ces fichiers sont de taille relativement faible.
Nous convertissons donc la police de caractère avec l’outil « node-fontnik » créé par Mapbox.
Hébergement du styleUn style au format Mapbox GL est composé d’un fichier de style au format JSON. C’est lui qui décrit la représentation des objets. Il est accompagné des deux fichiers pour les sprites (PNG et JSON) et des fichiers pour les polices (nommés glyphes). Ce sont des ressources entièrement statiques, nous pouvons les héberger sur Github.
À côté de cela, nous avons les tuiles vectorielles pour l'affichage des données cartographiques, au format OpenMapTiles, et des tuiles vectorielles pour nos données complémentaires pour la décoration (les positions de bateaux et des lignes de supports des noms de mers et océans). Nous utilisons ici les services de MapTiler pour les tuiles OpenMapTiles et les services de Mapbox pour les tuiles vectorielles complémentaires.
Prenons un peu plus de hauteur, Cassini en 3DExtrapolons encore un peu. Si nous ajoutions une dimension supplémentaire à la carte de Cassini ? Les tuiles vectorielles OpenMapTiles contiennent les informations suffisantes pour extruder les bâtiments en 3D (en 2,5D pour être exact).
Cassini en 3D.
La réalisation de ce style vectoriel au format Mapbox GL a été l’occasion d’explorer les possibilités et les limites de réalisation de cartes largement basées sur des images et dans le souci de reproduire un style existant. Alors que les outils modernes apportent des facilités de représentation, ils introduisent également des limites graphiques et esthétiques. La représentation du relief et de la couverture du terrain a été ignorée ici, la question nécessiterait un travail complémentaire qui ne s’annonce pas si simple.
- Le style au format Mapbox GL est disponible sur Github : makinacorpus/cassini-gl-style.
- La démonstration en ligne.
Vous aussi, vous avez des besoins de styles de visualisation sur mesure, des spécificités à représenter ? Comme vous le voyez, nous aimons relever les défis cartographiques !
Formations associées
Formations Django
Formation Django REST Framework
À distance (FOAD) Du 9 au 13 juin 2025
Voir la formationActualités en lien
GeoDatadays 2024 : retrouvez-nous et participez à nos conférences
Les 19 et 20 septembre, participez aux conférences animées par nos experts SIG aux GeoDataDays 2024, en Pays de la Loire à Nantes.
SIG : Préparation de données pour la création de tuiles vectorielles
Pour servir des données sous forme des tuiles vectorielles une préparation est nécessaire comme le filtrage et la simplification des géométries. Quelles sont les questions à se poser ? Quels sont les outils pour y répondre ?
Générer des tuiles vectorielles sur mesure avec Django
Dans cet article nous allons voir comment générer dynamiquement des tuiles vectorielles utilisables par la bibliothèque de visualisation mapbox-gl-js à partir de données stockées dans un modèle GeoDjango.