Développement Web

PostGIS cartographie PostgreSQL : tutoriel 2026

12 min de lecture
📍 Guide principal : La chaîne géospatiale open source : QGIS, PostGIS, GeoServer, MapLibre. Cet article pose les bases de PostGIS ; pour aller jusqu’au calcul d’itinéraires, voir PostGIS et pgRouting.

PostGIS transforme PostgreSQL en système d’information géographique (SIG) puissant. Idéal pour apps de livraison, logistique, services à domicile.

Voir notre guide extensions PG.

Activer PostGIS

# Image Docker avec PostGIS
docker run -d \
  -e POSTGRES_PASSWORD=secret \
  postgis/postgis:16-3.4

# Dans psql
CREATE EXTENSION postgis;

Stocker des points

CREATE TABLE shops (
  id serial PRIMARY KEY,
  name text,
  location geography(POINT, 4326)
);

INSERT INTO shops (name, location) VALUES
  ('Boutique Plateau', 'POINT(-17.4441 14.6928)'),
  ('Boutique Almadies', 'POINT(-17.5083 14.7402)'),
  ('Boutique HLM', 'POINT(-17.4500 14.7000)');

Recherche par distance

-- Boutiques dans un rayon de 5 km
SELECT name, ST_Distance(location, 'POINT(-17.4441 14.6928)'::geography) AS distance
FROM shops
WHERE ST_DWithin(location, 'POINT(-17.4441 14.6928)'::geography, 5000)
ORDER BY distance;

-- Boutique la plus proche
SELECT name FROM shops
ORDER BY location <-> 'POINT(-17.4441 14.6928)'::geography
LIMIT 1;

Polygones et zones

CREATE TABLE delivery_zones (
  id serial PRIMARY KEY,
  name text,
  area geography(POLYGON, 4326)
);

-- Définir une zone de livraison polygone
INSERT INTO delivery_zones (name, area) VALUES
  ('Dakar Plateau',
    'POLYGON((-17.45 14.68, -17.43 14.68, -17.43 14.70, -17.45 14.70, -17.45 14.68))'
  );

-- Une commande est-elle dans une zone ?
SELECT name FROM delivery_zones
WHERE ST_Within(
  'POINT(-17.4441 14.6928)'::geography::geometry,
  area::geometry
);

Index spatial

CREATE INDEX shops_location_idx ON shops USING GIST (location);
CREATE INDEX zones_area_idx ON delivery_zones USING GIST (area);

Cas d’usage Afrique

  • App livraison repas : trouver restaurants < 3 km du client
  • Logistique : optimiser tournées chauffeurs
  • Services à domicile : matcher prestataires nearby
  • Cartographie boutiques : carte interactive avec markers
  • Zones de tarification : urbain vs périurbain

À lire ensuite

Pourquoi PostGIS s’impose pour la cartographie en Afrique de l’Ouest

PostGIS transforme PostgreSQL en moteur géospatial complet. Pour un projet à Dakar, Abidjan ou Cotonou, vous obtenez une base ACID qui gère points GPS, polygones de quartiers et trajets de livraison sans dépendre d’un service cloud facturé en USD. À 655,957 FCFA pour 1 EUR, externaliser un PostGIS managé coûte vite cher : un VPS auto-hébergé revient à 3 000-7 000 FCFA/mois.

Avant de plonger dans le SQL, vérifiez la version : PostgreSQL 16 ou 17 avec PostGIS 3.4+ est recommandé en 2026. Les anciennes versions 2.x ne supportent pas les fonctions vectorielles modernes utilisées plus bas.

Étape 1 : installer PostGIS comme extension PostgreSQL

Sur Ubuntu 24.04 LTS, l’installation passe par apt. PostGIS n’est pas un fork de PostgreSQL : c’est une extension chargée à la demande dans une base existante. Cela signifie que vous pouvez l’activer sur une base métier déjà en production sans casse.

sudo apt update
sudo apt install -y postgresql-17 postgresql-17-postgis-3
sudo -u postgres createdb mapdb
sudo -u postgres psql -d mapdb -c "CREATE EXTENSION postgis;"

Ce que vous devez voir : CREATE EXTENSION. Si vous lisez ERROR: could not open extension control file, le paquet postgis-3 n’est pas installé. Vérifiez avec dpkg -l | grep postgis.

Étape 2 : créer une table de points géolocalisés

Le type geometry(Point, 4326) stocke des coordonnées WGS 84, le système GPS standard utilisé par OpenStreetMap et Google Maps. Pour des distances précises au mètre près sur le Sénégal, on reprojette ensuite en EPSG:32628 (UTM zone 28N qui couvre Dakar et Saint-Louis).

CREATE TABLE boutiques (
  id SERIAL PRIMARY KEY,
  nom TEXT NOT NULL,
  ville TEXT,
  geom geometry(Point, 4326)
);
INSERT INTO boutiques (nom, ville, geom) VALUES
  ('Marché Sandaga', 'Dakar', ST_SetSRID(ST_MakePoint(-17.4441, 14.6731), 4326)),
  ('Marché HLM', 'Dakar', ST_SetSRID(ST_MakePoint(-17.4570, 14.7167), 4326)),
  ('Marché Cocody', 'Abidjan', ST_SetSRID(ST_MakePoint(-3.9866, 5.3505), 4326));

L’ordre est longitude puis latitude dans ST_MakePoint, contrairement à l’usage humain. Erreur classique : inverser et obtenir des points en plein océan Atlantique.

Étape 3 : indexer la colonne geometry avec GiST

Sans index, une requête « points dans un rayon de 5 km » fait un scan séquentiel. Avec un index GiST (Generalized Search Tree), PostGIS bascule sur un parcours d’arbre R-tree et la requête passe de 800 ms à 4 ms sur 100 000 points.

CREATE INDEX boutiques_geom_idx ON boutiques USING GIST (geom);
ANALYZE boutiques;

Le ANALYZE recalcule les statistiques que le planner utilise pour décider d’employer l’index. Vérifiez avec EXPLAIN ANALYZE qu’un nœud Index Scan using boutiques_geom_idx apparaît bien dans le plan.

Étape 4 : requêter les boutiques dans un rayon de 5 km

La fonction ST_DWithin avec le type geography calcule en mètres sans projection manuelle. C’est le pattern le plus rapide pour « tous les points autour de moi », typique d’une app de livraison ou d’annuaire.

SELECT nom, ville
FROM boutiques
WHERE ST_DWithin(
  geom::geography,
  ST_SetSRID(ST_MakePoint(-17.4441, 14.6731), 4326)::geography,
  5000
);

Ce que vous devez voir : Sandaga et HLM (les deux marchés dakarois), Cocody est filtrée car à plus de 1 800 km. Si vous obtenez 0 ligne, vérifiez l’ordre lon/lat du point central.

Étape 5 : tracer des polygones de quartiers

Pour découper une ville en zones de livraison, on stocke des polygones. Un polygone PostGIS se ferme en répétant le premier point en dernier — sinon ERROR: geometry contains non-closed rings.

CREATE TABLE quartiers (
  id SERIAL PRIMARY KEY,
  nom TEXT,
  zone geometry(Polygon, 4326)
);
INSERT INTO quartiers (nom, zone) VALUES
  ('Plateau Dakar', ST_GeomFromText(
    'POLYGON((-17.435 14.665, -17.420 14.665, -17.420 14.680, -17.435 14.680, -17.435 14.665))',
    4326
  ));

Pour savoir quel quartier contient une boutique : SELECT q.nom FROM boutiques b JOIN quartiers q ON ST_Contains(q.zone, b.geom);. C’est la base d’un module de tarification livraison par zone.

Étape 6 : importer un fichier shapefile officiel

L’ANSD au Sénégal et l’INS en Côte d’Ivoire publient des découpages administratifs en shapefile. L’outil shp2pgsql convertit un .shp en SQL chargeable. Vérifiez la projection d’origine avec ogrinfo avant d’importer.

shp2pgsql -s 4326 -I -W "UTF-8" departements_sn.shp public.departements > deps.sql
psql -d mapdb -f deps.sql

Le flag -I crée automatiquement l’index GiST. -W force l’encodage UTF-8 — sans ça, les accents de « Saint-Louis » ou « Kédougou » deviennent des points d’interrogation.

Étape 7 : exposer les données via une API tuiles vectorielles

PostGIS 3 inclut ST_AsMVT qui génère des Mapbox Vector Tiles directement en SQL. Couplé à un endpoint Express ou FastAPI, vous servez une carte interactive sans Mapbox payant.

SELECT ST_AsMVT(tile, 'boutiques', 4096, 'geom')
FROM (
  SELECT id, nom,
    ST_AsMVTGeom(geom, ST_TileEnvelope(13, 4083, 3805)) AS geom
  FROM boutiques
  WHERE geom && ST_TileEnvelope(13, 4083, 3805)
) AS tile;

Les paramètres (13, 4083, 3805) sont (zoom, x, y) au format XYZ. Côté client, MapLibre GL JS — un fork open source de Mapbox GL — affiche ces tuiles sans clé d’API et sans facturation.

Étape 8 : sauvegarder et restaurer une base PostGIS

Un dump PostgreSQL classique préserve les extensions, mais attention au flag --clean qui peut supprimer PostGIS avant de recréer. Pour un backup propre, utilisez le format custom et restaurez avec pg_restore.

pg_dump -Fc -d mapdb -f mapdb.dump
pg_restore -d mapdb_new --no-owner mapdb.dump

Résultat type : aucune erreur sur les types geometry. Si pg_restore renvoie type « geometry » does not exist, vous avez oublié CREATE EXTENSION postgis sur la base cible avant restauration.

Aller plus loin

Pour les routes (livraison, transport en commun), regardez l’extension pgRouting qui ajoute Dijkstra et A* directement en SQL. Pour la 3D (modélisation urbaine), PostGIS supporte le type PolyhedralSurface depuis la version 2.2. Côté visualisation, QGIS 3.34 LTS se connecte directement à votre PostGIS via PG_SERVICE et permet de styler des cartes thématiques sans écrire une ligne de Python.

Voir aussi notre tutoriel installation PostgreSQL sur VPS et le guide MapLibre GL pour cartes web.

Étape 9 : optimiser les requêtes spatiales avec EXPLAIN ANALYZE

Quand un dashboard cartographique ralentit au-delà de 50 000 lignes, le coupable est presque toujours un index manquant ou une fonction qui empêche l’utilisation de l’index. La règle absolue : ne jamais appliquer une transformation sur la colonne indexée dans le WHERE. Préférez transformer la valeur de référence.

EXPLAIN ANALYZE
SELECT nom FROM boutiques
WHERE ST_DWithin(geom::geography,
  ST_SetSRID(ST_MakePoint(-17.44, 14.67), 4326)::geography, 5000);

Vous devriez obtenir : un nœud Index Scan using boutiques_geom_idx avec un coût inférieur à 100. Si vous voyez Seq Scan, votre index n’est pas utilisé — vérifiez que ANALYZE a tourné après le dernier INSERT massif.

Étape 10 : calculer des statistiques par zone

Compter les commerces par département, mesurer la densité de population par km² ou détecter les zones blanches : tout passe par un GROUP BY géographique. La fonction ST_Within filtre les points contenus dans un polygone, plus rapide que ST_Contains côté point.

SELECT q.nom AS quartier, COUNT(b.id) AS nb_boutiques,
       ST_Area(q.zone::geography) / 1000000 AS surface_km2
FROM quartiers q
LEFT JOIN boutiques b ON ST_Within(b.geom, q.zone)
GROUP BY q.id, q.nom, q.zone
ORDER BY nb_boutiques DESC;

Cast en geography pour ST_Area : sinon le résultat est en degrés carrés, sans signification physique. Diviser par 1 000 000 convertit les m² en km².

Étape 11 : sécuriser l’accès en lecture seule pour une app publique

Si votre frontend attaque directement PostGIS via PostgREST ou Hasura, créez un rôle dédié avec lecture seule sur les tables géographiques. Ne jamais exposer le rôle propriétaire ou superuser.

CREATE ROLE map_reader LOGIN PASSWORD 'changeme';
GRANT CONNECT ON DATABASE mapdb TO map_reader;
GRANT USAGE ON SCHEMA public TO map_reader;
GRANT SELECT ON boutiques, quartiers TO map_reader;

Combiné avec pg_hba.conf en mode scram-sha-256 et une connexion via Cloudflare Tunnel, vous évitez d’exposer le port 5432 sur Internet — vecteur d’attaque privilégié des bots qui scannent les VPS à Dakar comme à Lagos.

Pièges fréquents à éviter

Premier piège : confondre SRID 4326 (degrés) et 3857 (mètres web Mercator). Pour des distances exactes au Sénégal, projetez en 32628. Deuxième piège : oublier VACUUM ANALYZE après un import massif de shapefile — l’index GiST devient inefficace tant que le planner n’a pas de stats. Troisième piège : utiliser ST_Distance sur des geometry en degrés et croire que le résultat est en mètres. Toujours caster en geography ou projeter d’abord.

Quatrième piège local : les coordonnées WGS 84 d’OpenStreetMap au Sénégal sont décalées de 10-50 mètres par rapport au cadastre officiel ANAT, basé sur Adindan. Pour un projet foncier, demandez le SRID exact à l’ANAT et utilisez ST_Transform pour reprojeter.

Performance : seuils observés sur VPS 4 Go RAM

Sur un VPS Hetzner CX22 (4 vCPU, 8 Go) hébergeant une base de 2 millions de points : requête rayon 5 km avec index GiST en 8 ms, agrégation par polygone (200 zones) en 90 ms, génération MVT zoom 14 en 25 ms. Au-delà de 10 millions de points, passez à un index BRIN sur la colonne geometry pour les requêtes par bbox larges, et gardez GiST pour les requêtes ponctuelles.

Pour le monitoring, l’extension pg_stat_statements identifie les requêtes spatiales lentes. Activez-la dans postgresql.conf avec shared_preload_libraries = 'pg_stat_statements' puis redémarrez le service.

Récapitulatif workflow complet

Le pipeline minimal pour une app cartographique en production : installer PostGIS via apt, créer la base et l’extension, modéliser tables avec colonnes geometry typées et SRID explicite, indexer en GiST avec ANALYZE, importer les données officielles via shp2pgsql, exposer via API REST avec rôle lecture seule, servir des tuiles MVT au client MapLibre GL. Chaque étape se teste en isolation : un EXPLAIN ANALYZE après chaque jointure révèle immédiatement les régressions de performance.

Sur le même thème sur l’architecture, consultez le tuning PostgreSQL pour VPS modeste.

FAQ rapide PostGIS

PostGIS est-il gratuit pour usage commercial ? Oui, licence GPLv2, aucune redevance même pour une SaaS facturée en FCFA ou en EUR. Vous payez uniquement le serveur qui l’héberge.

Quelle différence entre geometry et geography ? Le type geometry traite les coordonnées comme un plan cartésien (rapide mais distance fausse sur de longues distances). Le type geography calcule sur la sphère terrestre (plus lent mais précis au mètre). Règle pratique : geometry pour le stockage et l’indexation, cast en geography pour les calculs de distance réelle.

Puis-je migrer depuis MySQL spatial ? Oui, exportez les données en WKT via ST_AsText() côté MySQL, importez via ST_GeomFromText() côté PostGIS. La couverture fonctionnelle de PostGIS est environ 5 fois plus large que MySQL 8 spatial.

Combien de Go pour 1 million de points ? Environ 80 Mo pour les données brutes, 30 Mo pour l’index GiST. Un VPS 80 Go SSD tient confortablement 50 millions de points avec marge pour les sauvegardes.

Checklist déploiement production

Avant de basculer en production, validez ces 8 points : version PostgreSQL 16 ou 17, version PostGIS 3.4 minimum, index GiST créés sur toutes les colonnes geometry, ANALYZE après import, rôle applicatif en lecture seule, port 5432 fermé sur Internet (accès via tunnel ou VPN), backup pg_dump quotidien testé en restauration, monitoring pg_stat_statements actif. Cette checklist tient sur une page A4 et évite 90 % des incidents observés sur les déploiements PostGIS au Sénégal et en Côte d’Ivoire.

Le test de restauration est le point le plus souvent négligé. Un backup non testé n’est qu’un fichier qui prend de la place : programmez un restore mensuel sur une base de staging et vérifiez que les requêtes spatiales clés renvoient les mêmes résultats.

Partager