Cet article s'inspire du chapitre "Géographie" du livre Python sans détour de Laurent Berger et Pascal Guézet (Éditions D-BookeR).

Afficher un point sur une carte, calculer un itinéraire ou personnaliser un fond de carte ne nécessite pas de maîtriser JavaScript. En Python, le module Folium permet de générer des cartes interactives complètes en quelques lignes de code, à partir des données OpenStreetMap. Combiné à GeoPy pour la géolocalisation et à Requests pour interroger des services web comme celui de l'IGN, il devient possible de construire des outils cartographiques complets : personnalisation des fonds de carte et calcul d'itinéraires à partir de simples noms de lieux.

Pourquoi Folium ?

Folium est une surcouche Python de la bibliothèque JavaScript Leaflet, l'une des plus utilisées au monde pour la cartographie web (OpenStreetMap, Wikipédia ou l'IGN s'appuient dessus). Autrement dit, Folium génère en coulisses une page HTML qui embarque du code Leaflet : vous écrivez du Python, mais le rendu final profite de toute la puissance de Leaflet. Si vous souhaitez aller plus loin côté JavaScript une fois vos données préparées en Python, le livre Cartographie web avec Leaflet constitue un excellent complément.

Pour suivre les exemples de cet article, quatre modules complémentaires sont nécessaires :

  • folium — génération des cartes interactives
  • geopy — conversion d'un nom de lieu en coordonnées GPS
  • pillow — export des cartes en image
  • requests — appel de services web comme celui de l'IGN
pip install folium geopy pillow requests

Générer sa première carte interactive

La classe Map de Folium construit une carte centrée sur des coordonnées données (latitude, longitude). Cette carte n'existe cependant qu'en mémoire : il faut la sauvegarder au format HTML pour pouvoir la visualiser dans un navigateur, à l'aide du module webbrowser de la bibliothèque standard.

import os
import webbrowser
import folium

carte = folium.Map([46.763, 2.425])
chemin_fichier = os.path.splitdrive(os.getcwd())[0] + "/tmp/ma_carte.html"
carte.save(chemin_fichier)
webbrowser.open("file://" + chemin_fichier)

La méthode save() ne génère pas une image, mais une page HTML contenant un programme JavaScript qui affiche la carte, gère le zoom et le déplacement. C'est ce fichier HTML que le navigateur interprète pour afficher la carte OpenStreetMap centrée sur le point indiqué.

Exporter une carte au format PNG avec Pillow

Il est parfois utile d'obtenir une image statique (PNG) plutôt qu'une page HTML interactive, par exemple pour l'intégrer dans un rapport ou un document généré automatiquement. La méthode interne _to_png() de la classe Map permet cette conversion.

import io
import folium
import PIL.Image

carte = folium.Map([46.763, 2.425],
                    zoom_start=15,
                    zoom_control=False,
                    control_scale=True)
img_data = carte._to_png(5)
img = PIL.Image.open(io.BytesIO(img_data))
img.show()
img.save("/tmp/ma_carte.png")

L'argument passé à _to_png() correspond au délai (en secondes) laissé au navigateur pour charger entièrement les tuiles avant la capture. Cette méthode s'appuie en interne sur Selenium pour piloter un navigateur headless : il faut donc installer ce module ainsi qu'un pilote de navigateur compatible (par exemple geckodriver pour Firefox) pour que la conversion fonctionne.

Comparer et personnaliser les fonds de carte

Folium ne se limite pas au fond de carte OpenStreetMap par défaut. Le paramètre tiles du constructeur Map accepte le nom de plusieurs fonds de carte intégrés.

Exemple de 6 types de fonds de cartes  fournis en standard

Fond de carte standard dans Folium

L'exemple suivant les génère tous et les assemble en une seule image grâce à Pillow, pour les comparer d'un coup d'œil.

import io
import PIL.Image
import folium

l_carte = ["OpenStreetMap",
           "cartodb positron",
           "cartodb dark_matter"]
largeur = 400
hauteur = 256
img_cartes = PIL.Image.new('RGB', (largeur * len(l_carte), hauteur))

for idx, src_carte in enumerate(l_carte):
    carte = folium.Map([46.59, 2.48],
                        height=hauteur,
                        width=largeur,
                        tiles=src_carte,
                        control_scale=True,
                        zoom_start=10)
    img_data = carte._to_png(2)
    img = PIL.Image.open(io.BytesIO(img_data))
    img_cartes.paste(img, (idx * largeur, 0))
img_cartes.show()

Note > Les fonds de carte Stamen (Terrain, Toner, Watercolor), longtemps proposés gratuitement dans Folium, ont été rachetés par Stadia Maps fin 2023 et nécessitent désormais une clé d'API pour fonctionner. Si vous suivez d'anciens tutoriels qui les mentionnent, sachez que ces noms ne fonctionnent plus tels quels : privilégiez les fonds CartoDB ("cartodb positron", "cartodb dark_matter"), toujours gratuits, ou passez par un fournisseur de tuiles personnalisé comme décrit ci-dessous.

Aller plus loin avec Leaflet-providers

Pour accéder à des fonds de carte non intégrés nativement à Folium (images satellites, plans topographiques, styles sombres, etc.), le site Leaflet-providers référence des dizaines de fournisseurs de tuiles, avec pour chacun le lien HTML et la mention de copyright à utiliser. Il suffit de les transmettre aux arguments tiles et attr du constructeur Map.

import os
import webbrowser
import folium

tuile = """
https://server.arcgisonline.com/ArcGIS/rest/services/
World_Imagery/MapServer/tile/{z}/{y}/{x}
"""
credit = """
Tiles © Esri —
Source: Esri, i-cubed USDA, USGS, AEX, GeoEye,
Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community
"""
carte_sat = folium.Map([46.763, 2.425],
                        tiles=tuile,
                        attr=credit,
                        zoom_start=15,
                        control_scale=True)
chemin_fichier = os.path.splitdrive(os.getcwd())[0] + "/tmp/ma_carte_sat.html"
carte_sat.save(chemin_fichier)
webbrowser.open("file:///" + chemin_fichier)

Exemple de carte disponible sur Leaflet-Providers

Fond de carte accessible par Leaflet-providers

Cette approche crée un pont direct avec l'écosystème Leaflet : puisque Folium génère du Leaflet en coulisses, n'importe quel fond de tuiles compatible Leaflet fonctionne aussi en Python. Si vous développez par ailleurs des cartes en JavaScript, vous pouvez consulter notre article Leaflet : la cartographie web open-source, simple et puissante pour approfondir ce fonctionnement commun.

Personnaliser ses marqueurs

Au-delà de la simple goutte de couleur par défaut, Folium permet d'associer à chaque marqueur une véritable icône, puisée dans deux bibliothèques web bien connues, Glyphicons et FontAwesome (appareil photo, livre, signal...). De quoi construire des cartes où chaque type de lieu se distingue visuellement au premier coup d'œil — le détail de mise en œuvre est expliqué pas à pas dans le chapitre Géographie de Python sans détour.

Calculer et tracer un itinéraire avec l'API de l'IGN

Le service geoservices.ign.fr de l'Institut Géographique National expose une API REST gratuite de calcul d'itinéraire. Combinée à GeoPy pour convertir des noms de lieux en coordonnées et à Requests pour interroger le service, elle permet de tracer un itinéraire piéton ou routier complet sur une carte Folium.

import sys
import os
import webbrowser
import requests
import folium
import geopy.geocoders

localiseur = geopy.geocoders.Nominatim(user_agent="mon_appli")
adr_deb, (lat_deb, lon_deb) = localiseur.geocode("Saint léonard des bois")
adr_fin, (lat_fin, lon_fin) = localiseur.geocode("Saint Céneri")

url_base = "https://wxs.ign.fr/calcul/geoportail/itineraire/rest/1.0.0" \
           "/route?resource=bdtopo-pgr&profile=pedestrian"
url_iti = "&start=" + str(lon_deb) + "," + str(lat_deb)
url_iti += "&end=" + str(lon_fin) + "," + str(lat_fin)
url_param = "&geometryFormat=geojson&getSteps=true&waysAttributes=cleabs"

ma_session = requests.Session()
try:
    reponse = ma_session.get(url_base + url_iti + url_param)
    if reponse.status_code != requests.codes.ok:
        print("page non lue : ", reponse.status_code)
        sys.exit()
except requests.exceptions.RequestException:
    print("Erreur : ", sys.exc_info())
    sys.exit()

itineraire = reponse.json()
lat_centre = (lat_deb + lat_fin) / 2
lon_centre = (lon_deb + lon_fin) / 2
carte = folium.Map([lat_centre, lon_centre], zoom_start=14, control_scale=True)

tps_parcours, km_parcours = 0, 0
l_etapes = itineraire["portions"][0]["steps"]
etape_pre = l_etapes[0]["geometry"]["coordinates"][0][::-1]

for etape in l_etapes:
    ins_marqueur = True
    for lon_lat in etape["geometry"]["coordinates"]:
        ligne = folium.ColorLine([etape_pre, lon_lat[::-1]], weight=4)
        carte.add_child(ligne)
        if ins_marqueur:
            tps_parcours = tps_parcours + etape["duration"]
            km_parcours = km_parcours + etape["distance"]
            texte = str(int(tps_parcours)) + " min. / " + \
                    str(km_parcours) + "m " + \
                    etape["attributes"]["nom_1_gauche"]
            marqueur = folium.Marker(lon_lat[::-1],
                                      popup=texte,
                                      icon=folium.Icon(color='red'))
            marqueur.add_to(carte)
            ins_marqueur = False
        etape_pre = lon_lat[::-1]

chemin_fichier = os.path.splitdrive(os.getcwd())[0] + "/tmp/ma_carte_rando.html"
carte.save(chemin_fichier)
webbrowser.open("file:///" + chemin_fichier)

Le principe se décompose en trois temps. On géocode d'abord les deux extrémités du parcours avec GeoPy, pour obtenir leurs coordonnées GPS à partir de simples noms de lieux. On interroge ensuite l'API de l'IGN avec Requests, en gérant les erreurs réseau via try/except ; la réponse, au format JSON, contient la liste détaillée des étapes de l'itinéraire (distance, durée, géométrie). On parcourt enfin cette liste pour tracer chaque segment avec folium.ColorLine et poser un marqueur au début de chaque étape, avec le temps et la distance cumulés en légende.

Attention > Les coordonnées renvoyées par le service IGN sont ordonnées (longitude, latitude), alors que Folium attend l'ordre inverse (latitude, longitude). C'est ce que fait l'inversion [::-1] appliquée à chaque couple de coordonnées.

Bonus : représenter une densité de points avec une carte de chaleur

Au-delà des marqueurs individuels, Folium embarque des extensions (plugins) pour d'autres types de visualisation. Le plugin HeatMap, par exemple, permet de représenter la concentration d'un grand nombre de points géolocalisés sous forme de carte de chaleur, une approche courante en analyse de données (répartition d'entreprises, de population, de signalements...).

import folium
from folium.plugins import HeatMap

# points est une liste de couples [latitude, longitude]
carte = folium.Map(location=[48.9068, 2.2464], zoom_start=12)
HeatMap(points).add_to(carte)
carte.save("carte_chaleur.html")

Carte de densité réalisée avec Folium

Les entreprises à La Garenne-Colombes

Attention toutefois avec les gros volumes : Folium intègre l'ensemble des coordonnées directement dans le fichier HTML généré, ce qui peut vite alourdir la page (plusieurs centaines de kilooctets, voire davantage, au-delà de quelques dizaines de milliers de points). Pour une diffusion web à grande échelle, un chargement des données depuis un fichier JSON externe, en JavaScript pur, reste plus performant — c'est d'ailleurs l'approche détaillée dans le livre Cartographie web avec Leaflet.

Conseils et bonnes pratiques

  • Respectez les limites des services gratuits : Nominatim (GeoPy) comme les services de l'IGN sont mis à disposition gratuitement mais avec des quotas d'usage ; espacez vos requêtes et prévoyez un service payant pour un usage en production à fort volume.
  • Vérifiez les attributions : chaque fond de carte ou source de données (OpenStreetMap, Esri, IGN, Glyphicons...) impose une mention de copyright précise, à conserver via l'argument attr de Folium.
  • Gérez les erreurs réseau : les appels à des API externes peuvent échouer (indisponibilité, quota dépassé, réseau) ; encadrez-les systématiquement d'un try/except comme dans l'exemple d'itinéraire.
  • Testez la conversion HTML vers PNG : la méthode _to_png() dépend de Selenium et d'un navigateur piloté en arrière-plan ; installez un driver à jour (geckodriver, chromedriver) et prévoyez un délai suffisant pour le chargement des tuiles.
  • Anticipez l'évolution des fonds de carte tiers : comme le montre le cas Stamen/Stadia, les fournisseurs de tuiles gratuits peuvent changer de modèle économique ; privilégiez des sources stables (OpenStreetMap, CartoDB) pour un usage durable.

Couverture du livre Python sans détourPour aller plus loin

Pour approfondir Folium et les nombreuses autres bibliothèques pratiques de Python, Python sans détour constitue un excellent compagnon. Et si votre projet vous amène à développer des cartes directement en JavaScript, Cartographie web avec Leaflet approfondit la bibliothèque qui se cache derrière Folium.

Si vous souhaitez vous former plus spécifiquement à la cartographie, nos ouvrages Cartographie & SIG pourraient vous intéresser.

Ressources