Créer des tuiles à partir des données OpenStreetMap🔗

Posted by Médéric Ribreux 🗓 In blog/ OpenStreetMap/

#osm #qgis

Préambule

Pour ceux qui veulent aller vite, il suffit d'aller lire dans la partie Base de connaissance. Pour ceux qui veulent comprendre, la suite est ci-dessous…

Pour mes vacances, j'ai choisi d'explorer la côte sud de l'Irlande. Pour me diriger dans le pays, je me suis demandé si les données OSM étaient d'un niveau suffisant pour se repérer efficacement sur place. J'ai donc mené une petite expérimentation consistant à utiliser QGis sur un ordinateur portable ainsi que des fonds de plans issus d'OSM, le tout directement sur le terrain.

Pour répondre rapidement à la question des résultats de cette expérience, sachez que l'état des données OSM sur l'Irlande est vraiment d'un bon niveau. Elles sont suffisamment précises, même sous la forme d'un simple fond raster pour se repérer sur la route, pour faire du shopping, pour retirer de l'argent liquide, pour se repérer dans la ville (même de petite taille), pour faire de la randonnée, pour trouver un pub ou un restaurant, etc.

Reste la question du comment ? Exploiter des données OSM sur un pays entier demande de télécharger les données pour les afficher. Vu la complexité du modèle OSM qui est plutôt un meta-modèle (un modèle de modèle), pas facile de le représenter directement à partir des informations vectorielles (stockées dans une DB PostGIS). Je n'ai pas essayé, mais je pense que le rendu de ces données vectorielles est lourd sur une machine limitée en performances (un ordinateur portable de 2014). De plus, il faut passer beaucoup de temps pour styler les objets. Je me suis donc simplement fixé l'objectif de générer des dalles raster qui correspondent à celles qui sont récupérées depuis les serveurs de tuile de la fondation (qui permettent l'affichage sur la carte dynamique du site openstreetmap.org).

Introduction

Pour ceux qui ne savent pas, les tuiles sont des dalles raster de petit format (256x256 pixels souvent) qui sont stockées sous forme de fichiers (un par dalle). Ces fichiers sont organisés dans une arborescence du type /zoom/longitude/latitude, comme: /6/234/456.png. L'intérêt de cette méthode c'est qu'une simple requête HTTP permet d'accéder à ce fichier si celui-ci est servi par un serveur Web. Pour calculer la longitude et la latitude en fonction du zoom, un algorithme assez simple a été mis en œuvre. Ce que je viens de décrire (algorithme+règle de stockage+serveur web) est un protocole nommé TMS Son application est plutôt simple. Par exemple, QGis est capable d'accéder à ces rasters directement. Un autre exemple plus concret est celui de la carte dynamique OpenStreetMap: votre navigateur web contient une bibliothèque Javascript (Leaflet) qui se charge d'aller récupérer les tuiles en fonction de l'endroit où vous êtes situé. Leaflet sait accéder à des rasters servis par le protocole TMS.

Générer ces dalles ou ces tuiles n'est pas si complexe que ça pour peu qu'on dispose d'un peu de puissance de calcul et qu'on ne souhaite pas zoomer trop loin tout en travaillant sur un pays à taille raisonnable. Cet article se propose de présenter une méthode pour générer des tuiles ou des dalles rasters telles qu'elles sont représentées sur le site d'OpenStreetMap. Il n'a pas la prétention de faire le tour du sujet mais l'objectif est bien de faire comprendre ce qu'on fait. Pour des raisons de facilité de déploiement, le système d'exploitation utilisé est GNU/Linux Debian Jessie. On doit pouvoir faire la même chose sous MS-Windows mais le processus sera plus compliqué, notamment parce qu'il y aura beaucoup plus d'éléments à installer et que certaines versions en ligne de commande des outils employés ne sont pas disponibles sous forme de binaires. De plus, la chaîne de traitement est complètement réalisable en ligne de commande ce qui permet de lancer et d'automatiser le travail sur un (ou plusieurs) serveur(s), sans besoin d'une interface graphique.

Au-delà des données OSM, cet article permettra de vous montrer que la génération de tuiles avec des outils libres est à portée de main de tout chef de projet technique géomatique ou même à l'amateur éclairé. Si vous avez du contenu propre (qui vous appartient), vous pouvez donc monter un service TMS à moindres frais tout en maîtrisant complètement ce que vous faites.

La méthode

Le principe est assez simple. On souhaite fabriquer de la donnée raster à partir de données vectorielles publiquement disponibles. La communauté OSM a développé un grand nombres d'outils pour réaliser ce travail. Ces outils sont utilisés sur la chaîne de production d'OSM pour afficher la carte dynamique du même nom. Ils sont donc largement éprouvés. Je me suis d'ailleurs cantonné à rester au plus proche de ce que la fondation fait histoire d'être sûr d'avoir des résultats.

Voici une petite synthèse du déroulement de la génération des tuiles:

Recette à suivre pour la génération de tuiles

Preparation d'une base PostGIS

Je vais aller très vite sur ce point. L'article n'a pas vocation à présenter PostGIS, ni à indiquer comment on configure l'ensemble. Pour éviter d'être sec, je présente une méthode simpliste, qui privilégie la faible durée de mise en place, mais qui fonctionne. La sécurité est bien sûr complètement à revoir.

On commence par installer postgresql et PostGIS:

# aptitude install postgresql-9.3-postgis-2.1

Ensuite, on peut créer une base de données spatiale (nommée geobase) et on y ajoute les bonnes extensions. D'abord postgis qui permet d'installer les fonctions PostGIS et ensuite hstore dont MapNik a besoin pour requêter efficacement la base.

# su postgres
$ createdb geobase
$ psql -d geobase -c "CREATE EXTENSION postgis;"
$ psql -d geobase -c "CREATE EXTENSION hstore;"

Pour des questions de rapidité, j'ai donné un accès total à toutes les bases à l'utilisateur postgres en localhost. C'est juste pour faire l'économie de création d'un utilisateur et de la gestion des droits qui va avec (je ne fais pas un cours PostgreSQL). Modifiez votre fichier /etc/postgresql/9.4/main/pg_hba.conf et vérifier que vous avez au moins les lignes suivantes:

local   all             postgres                                trust
host    all             postgres         127.0.0.1/32           trust
host    all             postgres         ::1/128                trust

Installation des outils indispensables présents sous Debian

Debian met déjà à disposition des outils dont nous avons besoin. Pas besoin d'aller les chercher sur Internet, il suffit de mobiliser les serveurs de dépôt de la distribution.

Ici, on a besoin de GDAL pour mapnik. Il nous faut également MapNik, carto-css (qui a pour nom node-carto dans Debian) ainsi que l'utilitaire de chargement de données OSM dans PostGIS, osm2pgsql:

# aptitude install gdal-bin mapnik-utils osm2pgsql node-carto phyton-shapely

Téléchargement des données de base et des outils externes

Nous allons d'abord préparer un répertoire qui va contenir les données sources ainsi que les outils externes de la distribution Debian. Ce répertoire sera nommé osm_tiles. Il contiendra des outils présents dans le répertoire tools:

$ mkdir -p ~/osm_tiles/tools
$ cd ~/osm_tiles

Il nous faut maintenant les styles officiels qui sont utilisés par OpenStreetMap pour la génération de tuiles. Ils sont disponibles en ligne sur GitHub. Il suffit donc de les rapatrier pour pouvoir s'en servir:

$ git clone https://github.com/gravitystorm/openstreetmap-carto
$ cd openstreetmap-carto

Maintenant, nous pouvons télécharger l'outil generate_tiles_multiprocess.py. Ce script Python se charge de générer les tuiles tout en parralélisant le travail sur plusieurs files simultanées. Si vous avez un processeur avec plusieurs cœurs, cela permet de les faire fonctionner et d'augmenter un peu plus les performances. Donc même si le script est en Python, il reste néanmoins assez performant et tire parti du multi-cœur (ce qui, en 2014 est souvent un constat sur la majorité des machines modernes, y compris ARM):

$ wget http://svn.openstreetmap.org/applications/rendering/mapnik/generate_tiles_multiprocess.py
$ chmod 755 generate_tiles_multiprocess.py

En plus des styles, il faudra télécharger des données de base. Ces dernières vous permettront d'afficher les zooms à partir de 0. En effet, lorsque vous générez des tuiles, vous utilisez les données que vous avez incorporées dans votre base PostGIS. Néanmoins, si on se contente de ces données, il sera impossible d'afficher les premiers niveaux de zoom. Il faut donc des données (dites de base parce qu'elle ne concerne que les premiers niveaux de zoom) soient présentes pour pouvoir générer l'image du globe terrestre qui correspond au niveau de zoom 0. De plus, les fichiers de style d'OSM ont recours à ces fichiers de base donc si l'on souhaite les utiliser tels quels, il faudra récupérer ces fichiers. Les fichiers qu'on a récupérés depuis GitHub contiennent un script qui va se charger de télécharger ces données de base, les décompresser et effectuer une préparation minimale (quelques modifications pour les rendre compatibles avec les styles d'OSM).

$ ./get-shapefiles.sh

Une fois les styles, l'outil et les données de base téléchargés, il reste à télécharger les données d'OSM. Vous pouvez aller sur le site geofabrik.de qui recense les jeux de données librement accessibles. Pour ma part, c'est celui de l'Irlande:

$ wget http://download.geofabrik.de/europe/ireland-and-northern-ireland-latest.osm.pbf

Maintenant, il reste à importer ces données dans notre base PostGIS.

Import des données d'OSM dans une base PostGIS

Nous allons injecter les données dans la base de données geobase que nous avions créée auparavant. OSM propose un outil pour ça: osm2pgsql (installé par le paquet debian du même nom). Son utilisation est assez simple:

$ osm2pgsql -k -c -S openstreetmap-carto.style -d geobase -U postgres ireland-and-northern-ireland-latest.osm.pbf

On utilise un fichier de style de base fourni par ce qu'on a récupéré depuis GitHub. Ce fichier permet d'affecter un style par défaut aux objets OSM qui seront stockés dans la base PostGIS. Les autres options sont connues:

Enfin, le dernier paramètre est le nom du fichier .pbf qui contient les données brutes compressées d'OSM.

Compiler les fichiers carto-css en style MapNik

Comme je l'avais déjà évoqué plus haut, MapNik utilise un fichier de style dans son format propre. Il ne sait pas lire les styles carto-css. Il faut donc compiler ces derniers pour obtenir un fichier de style mapnik. Le fichier de style au format carto-css qui nous intéresse se nomme project.mml.

Les fichiers carto-css sont assez complets, notamment, ils contiennent de quoi accéder à la donnée. Par exemple, on doit indiquer dans ces fichiers que telle couche est contenu dans telle table PostgreSQL (en général, c'est même carrément une requête SQL). Il nous faut donc changer le mode d'accès par défaut et renseigner les bons éléments de connexion pour indiquer que le serveur est accessible via localhost, que notre base de données s'appelle geobase et qu'on va y accéder par l'utilisateur postgres (pas bien) sans mot de passe (encore plus pas bien).

Une simple manipulation par sed permet de s'en sortir sans devoir éditer le fichier à la main, ce qui se révèle assez fastidieux vu le nombre de couches à changer:

$ sed -i '/"dbname": "gis"/c \\t"dbname": "geobase"' project.mml
$ sed -i '/"dbname": "geobase"/ i \\t"user": "postgres", \n\t"host": "localhost",' project.mml

Une fois ces corrections réalisées, on peut générer le fichier de style MapNik avec le compilateur carto-css qui se nomme simplement carto:

$ carto project.mml > mapnik.xml

Il reste enfin un dernier nettoyage à faire dans le fichier mapnik.xml: celui des polices. En effet, le fichier généré indique un grand nombre de polices de caractères requises pour générer les tuiles. C'est notamment utile lorsqu'on souhaite gérer des alphabets différents (mandarin, japonais, etc.). Je vous conseille de supprimer toutes les polices indiquées sauf la police DejaVu car, en règle générale elle est installée par défaut sur les systèmes d'exploitation GNU/Linux, ce qui n'est pas le cas des autres.

Déterminer l'emprise géographique à générer et les seuils de zoom

Lorsque vous allez générer des tuiles ou des dalles raster, il faudra indiquer dans quelles limites de carte vous souhaiter opérer. C'est un pré-requis car l'outil generate_tiles_multiprocess ne peut fonctionner sans. Dans notre cas, il faudra récupérer les coordonnées de la zone qu'on souhaite exporter, c'est-à-dire l'emprise de l'Irlande (ou d'un peu moins si vous explorez juste le sud ou le nord).

De même, il vous faudra déterminer de quels niveaux de zoom vous avez besoin. Plus le zoom est important, plus le niveau de détails sera fin, plus il y aura de tuiles à générer. Pour ma part, je suis descendu au niveau 17.

Pour déterminer l'emprise, le plus simple est de se rendre sur la carte dynamique d'OpenStreetMap et d'utiliser l'outil Exporter. Vous pouvez choisir la zone à exporter manuellement. Ensuite, pour récupérer l'emprise dans le bon ordre, le plus simple est de récupérer l'URL de l'API overpass (en rouge):

overpass URL
overpass URL

Voici un exemple de cette URL pour l'Irlande: http://overpass-api.de/api/map?bbox=-10.673,51.406,-5.938,54.304 . Ce qui nous intéresse est à droite de la variable bbox.

Pour les niveaux de zoom, utilisez encore la carte d'OSM et zoomez au maximum de ce que vous voulez voir. Il suffit ensuite de relever l'URL dans la fenêtre de titre. Par exemple: http://www.openstreetmap.org/#map=18/52.44848/-9.05243. Le zoom vaut ici 18.

Génération des tuiles

Avant de lancer generate_tiles_multiprocess.py, il faut modifier la dernière partie de son code. En effet, on doit lui indiquer le fichier de style MapNik à utiliser, le répertoire qui va contenir les tuiles ainsi que l'emprise de calcul et les seuils de zoom qu'on désire obtenir. Il suffit d'avoir des lignes qui ressemblent à ce qui suit:

if __name__ == "__main__":

home = os.environ['HOME']
try:
	mapfile = os.environ['MAPNIK_MAP_FILE']
except KeyError:
	   # emplacement du fichier de style MapNik
	   mapfile = '/home/medspx/osm_tiles/openstreetmap-carto/mapnik.xml'
try:
	tile_dir = os.environ['MAPNIK_TILE_DIR']
except KeyError:
# répertoire de stockage des tuiles
	tile_dir = '/home/medspx/osm_tiles/wtms/'

if not tile_dir.endswith('/'):
	tile_dir = tile_dir + '/'

#-------------------------------------------------------------------------
# Change the following for different bounding boxes and zoom levels
#
# Emprise de l'irlande:
bbox = (-10.673, 51.406, -5.938, 54.304)
# 18 correspond à notre seuil de zoom maximal
render_tiles(bbox, mapfile, tile_dir, 0, 18, "World")

Une fois les modifications réalisées, il n'y a plus qu'à lancer le script.

$ ./generate_tiles_multiprocess.py

Toutes les tuiles seront stockées dans le répertoire osm_tiles/wtms.

Attention, le processus de génération est très gourmand en ressources CPU et il est très long. Pour l'étendue indiquée précédemment et pour aller jusqu'au seuil de zoom, il m'a fallu plus de 48h sur un core i7 avec 8G de RAM !

Voir les tuiles avec QGis

Pour voir les tuiles avec QGis, on va émuler un serveur de tuiles qui parle TMS. La seule différence c'est qu'on va se passer du serveur Web. En effet, tout est stocké dans un répertoire. Donc il suffit de faire une "requête" sur une URL locale (celles qui commencent par file:// au lieu de http://).

Pour accéder à un service TMS, QGis utilise un fichier de définition. Ce dernier est un fichier XML avec quelques balises adaptées. Sans entrer dans les détails du service TMS, sachez que celui-ci utilise souvent une projection spécifique: EPSG:3857, destinée à servir au mieux les services de tuiles dont l'emprise est mondiale. Dans notre cas, nous avons utilisé les outils de génération de tuile d'OSM qui a une emprise mondiale. Donc, même si je ne l'ai pas indiqué auparavant, sachez que la projection par défaut de nos tuiles est l'EPSG:3857.

Voici le contenu d'un fichier de définition de service TMS pour QGis:

<GDAL_WMS>
<Service name="TMS">
	<ServerUrl>file:///home/medspx/osm_tiles/wtms/${z}/${x}/${y}.png</ServerUrl>
</Service>
<DataWindow>
	<UpperLeftX>-20037508.34</UpperLeftX>
	<UpperLeftY>20037508.34</UpperLeftY>
	<LowerRightX>20037508.34</LowerRightX>
	<LowerRightY>-20037508.34</LowerRightY>
	<TileLevel>18</TileLevel>
	<TileCountX>1</TileCountX>
	<TileCountY>1</TileCountY>
	<YOrigin>top</YOrigin>
</DataWindow>
<Projection>EPSG:3857</Projection>
<BlockSizeX>256</BlockSizeX>
<BlockSizeY>256</BlockSizeY>
<BandsCount>3</BandsCount>
<Cache />
</GDAL_WMS>

Il suffit d'en enregistrer le contenu dans un fichier et de l'ouvrir en tant que raster dans QGis. On voit que l'URL de la balise est spécialement modifiée pour accéder à des tuiles par fichiers plutôt que par une requête HTTP. Cela permet de charger la couche directement dans QGis, simplement en indiquant le répertoire où vous avez stocké les tuiles (~/osm_tiles/wtms normalement).

Attention, ce mode de consultation a quelques lacunes. En effet, nous n'avons pas généré toutes les tuiles de toute la planète (et pour cause, il faudrait quelques semaines ou mois entiers). Seules sont disponibles celles qui concernent notre "zone", c'est-à-dire l'emprise de l'Irlande. Si vous tentez de zoomer en dehors de cette emprise, QGis va chercher les fichiers qui correspondent mais comme ces derniers n'existent pas, il va déclencher l'affichage d'un raster vide. Par effet de bord, cet affichage va corrompre les données affichées et généralement, un grand cadre noir va s'afficher. Parfois, on tombe sur un mélange plus complexe de couleurs mais qui n'ont toujours rien à voir avec ce qui devrait s'afficher (du blanc pour ce qui est vide et le reste des tuiles existantes si on est dans l'emprise).

Vous devez donc toujours prendre soin de vous trouver à un endroit où il y a des tuiles sinon vous aurez un bug d'affichage…

Autre point négatif: le chargement de la couche est un poil long. Il faut quelques secondes pour que l'affichage se charge correctement ce qui rend la navigation un peu fastidieuse pour cause de rafraîchissement intempestif.

Pour régler ce problème, on peut s'employer à générer des dalles raster plutôt que des tuiles…

Je veux des dalles raster !

Cette opération est un peu plus technique, car elle a recours à une chaîne d'utilitaires un peu plus complexe et moins automatisée (même si un bon administrateur système doit savoir gérer ça sous forme d'un bon script shell des bois).

Pour générer des dalles raster plutôt que des tuiles, on va utiliser un autre utilitaire que j'ai légèrement hacké pour le rendre plus docile. Il se nomme Nik4 et il est disponible sur GitHub également. Vous trouverez la version modifiée ici.

Le hack permet de faire en sorte que Nik4 génère des dalles de gros volume en effectuant son travail par découpage de la plus grande zone qui peut tenir en mémoire. Il génère alors des fichiers Tiff qu'on peut ensuite assembler dans un fichier vrt (raster virtuel). Attention, Nik4 ne travaille qu'avec un niveau de zoom. Il faudra donc lancer la commande autant de fois qu'il faut pour chaque niveau de zoom. Pour disposer de la même couverture qu'avec les tuiles, il faudra recommencer l'opération 18 fois (18 niveaux de zoom).

Voici le principe de fonctionnement de la génération de dalles:

Je vous montre à quoi ça ressemble:

$ ../Nik4/nik4.py -b -10.673 51.406 -5.938 54.304 -z 14 mapnik.xml ireland.tiff --wld ireland.wld --tiles 4

Avec cette commande, on demande à Nik4 de:

Sur le dernier argument, Nik4 rend la main s'il n'y a pas assez de dalles. Pour le niveau 17 par exemple, j'ai dû monter cette valeur à 28 pour que tout tienne en RAM. Globalement Nik4 est un peu plus rapide que generate_tiles_multiprocess.py mais l'ordre de grandeur sera identique (compter deux jours).

Une fois un ensemble de dalles obtenu, il faut les mettre dans un répertoire. Dans mon cas, j'ai créé un répertoire par niveau de zoom. Ensuite, il suffit de demander à gdalbuildvrt de faire le travail de fabrication du raster virtuel:

$ gdalbuildvrt -a_srs EPSG:3857 Ireland_13.vrt *.tiff

Ici, on demande à gdalbuildvrt de fabriquer un raster virtuel nommé Ireland_13.vrt avec tous les tiff du répertoire courant. La projection sera ESPG:3857.

Ensuite, vous avez juste à ouvrir le fichier Ireland_13.vrt dans QGis pour profiter du niveau de zoom 13 de vos dalles OSM. L'opération est à renouveler pour chaque niveau de zoom. Plus le niveau de zoom est élevé plus la génération de dalles sera lente.

Dans la pratique, les dalles s'ouvrent nettement plus rapidement que les tuiles locales. Pour obtenir un comportement identique aux tuiles TMS qui changent en fonction du niveau de zoom, vous pouvez indiquer des échelles d'affichage mini et maxi pour chacune de vos dalles raster. De cette manière, un zoom fera disparaître une dalle et en affichera une plus détaillée (passage du zoom 13 au zoom 14 par exemple). Cette manipulation ne devrait pas venir à bout d'un utilisateur QGis de niveau intermédiaire.

Conclusion

Cet article démontre qu'il est assez facile de générer des tuiles raster à partir des données OSM. Le tout est facilement industrialisable, car les outils s'exécutent tous en ligne de commande. On peut donc imaginer un système de génération automatisé de ces tuiles qui se lance tous les jours pour calculer différents jeux de tuiles. En effet, dans notre exemple, nous n'avons utilisé qu'un seul jeu de style: celui d'OSM. On aurait très bien pu imaginer une dizaine de jeux de styles différents, chacun se rapportant à une thématique ou à un objectif. Par exemple, vous voulez peut-être faire apparaître uniquement les contours administratifs, ou encore uniquement les zones urbaines, ou encore uniquement tout ce qui se rapporte à la préservation de l'environnement naturel, etc.

Pour réaliser des jeux de style, vous pouvez vous aider d'un outil assez sérieux sur le sujet: TileMill. Il est développé sous une licence libre par la société MapBox. Ce logiciel se veut un moyen simple et visuel de créer des styles cartographiques à partir de différentes sources (requêtes postGIS/shapefiles/etc.) et d'en générer les fichiers carto-css qui en découlent.

Pour terminer et en guise de prospective, on pourrait imaginer que QGis prenne en charge ce format carto-css. L'élaboration de ces fichiers et des tuiles qui s'y rapportent serait alors très accessible: l'utilisateur utiliserait les propriétés de style très fournies de QGis ainsi que la possibilité de ce dernier de lire un très grand nombre de formats de fichiers géographiques pour fabriquer visuellement un ensemble de cartes. Avec la chaîne de traitement (et quelques améliorations sans doute) présentée ci-dessus, une organisation pourrait alors facilement mettre en œuvre une gamme élargie de services cartographiques Web via le protocole TMS avec peu d'efforts.