Phase 2 — Cartographie
“Construire et interroger un graphe de routes pour naviguer vers vos objectifs.”
Mode : Groupe (les 4 rôles fusionnent leurs POIs et travaillent sur le même graphe). Durée estimée : 5h (tâches principales 2h + Cypher approfondi 1h + pgRouting avancé 1h + réflexion 1h)
Comprendre : le pivot r2gg, ou comment modéliser un graphe en SQL
La BDTOPO stocke les routes comme des lignes géométriques dans une table relationnelle. Un tronçon de route est une ligne avec des attributs (nature, vitesse, largeur…). Ce n’est pas un graphe : il n’y a pas de nœuds, pas d’arêtes orientées, pas de coût de traversée.
r2gg (route-graph-generator) transforme ces lignes en un graphe navigable. Il le fait en deux étapes :
Étape 1 : Extraction → Pivot
BDTOPO (PostGIS) Pivot (schéma normalisé)
┌──────────────────────┐ ┌──────────────────────┐
│ troncon_de_route │ │ nodes │
│ ├─ geometrie (line) │ ──r2gg──> │ ├─ id, lon, lat, geom│
│ ├─ nature │ sql2pivot │ edges │
│ ├─ vitesse_moyenne │ │ ├─ source_id → node │
│ ├─ largeur_de_chaussee │ │ ├─ target_id → node │
│ └─ restriction_* │ │ ├─ length_m │
└──────────────────────┘ │ ├─ cost_car, cost_pieton │
│ └─ direction (0/1/-1) │
└──────────────────────┘
Le pivot n’est rien d’autre qu’un modèle de graphe : une table de nœuds (nodes) et une table d’arêtes (edges) reliées par source_id/target_id. C’est le schéma classique d’un graphe orienté pondéré, stocké en SQL.
Étape 2 : Pivot → Moteurs de routage
Le même pivot alimente plusieurs moteurs :
┌─→ pgRouting (tables ways + ways_vertices_pgr dans PostGIS)
Pivot (nodes+edges) ├─→ OSRM (fichiers .osrm)
└─→ Valhalla (tuiles)
Chaque moteur réinterprète le même graphe selon ses propres structures. C’est l’avantage du pivot : une seule extraction, plusieurs formats.
Pourquoi c’est pédagogique ?
| Question | Modèle relationnel (BDTOPO) | Modèle graphe (pivot r2gg) |
|---|---|---|
| “Quels tronçons croisent un polygone ?” | ✅ ST_Intersects (PostGIS) | ❌ Pas natif |
| “Quel est le plus court chemin entre A et B ?” | ❌ Il faudrait écrire Dijkstra en SQL pur | ✅ pgr_dijkstra |
| “Que se passe-t-il si on coupe une arête ?” | ❌ Pas de notion d’arête | ✅ cost = -1 → recalcul |
| “Quels sont les voisins du nœud 42 ?” | ❌ Jointure géométrique complexe | ✅ WHERE source_id = 42 OR target_id = 42 |
Leçons :
- On peut modéliser un graphe en SQL (tables nodes/edges), mais l’interrogation des parcours reste limitée.
- r2gg fait ce travail de transformation linéaire → graphe automatiquement.
- pgRouting ajoute les algorithmes (Dijkstra, A*) qui manquent au SQL pur.
- Mais pour l’ontologie (hiérarchies profondes), Neo4j restera plus naturel que SQL récursif.
Prérequis
- Phase 1 terminée :
mission_poiscontient les POIs des 4 rôles - Gold Dumps r2gg chargés (tables
ways+ways_vertices_pgrdans PostGIS)- Générés par l’instructeur :
python scripts/admin_generate_gold_dumps.py --epci "<EPCI>"
- Générés par l’instructeur :
Tâches principales (2h)
T1 — Explorer le graphe généré par r2gg
r2gg a transformé la BDTOPO en graphe. Observez sa structure :
Objectifs :
- Explorer les tables
waysetways_vertices_pgr - Compter les arêtes et les nœuds
- Comparer avec le nombre de tronçons dans
troncon_de_route
Question : pourquoi le nombre d’arêtes est-il différent du nombre de tronçons ? Indice : r2gg découpe les tronçons aux intersections.
→ Corrigé
T2 — Associer les POIs aux sommets du graphe
Les POIs sont des points géométriques, pas des nœuds du graphe. Il faut les “snapper” au sommet le plus proche.
Objectif : trouver le sommet le plus proche de chaque POI. Indice : CROSS JOIN LATERAL + ORDER BY geom <-> p.geom LIMIT 1.
Pourquoi les POIs éloignés des routes ont-ils un distance_snap élevé ? Est-ce un problème ?
→ Corrigé
T3 — Calculer des itinéraires (Dijkstra)
Maintenant que le graphe existe, calculez le plus court chemin entre 2 POIs.
Objectif : utiliser pgr_dijkstra entre un POI attaque et un POI défense. Indice : sous-requêtes pour les vertex_ids + directed := false.
Essayez plusieurs paires de rôles (attaque→énergie, ravitaillement→défense…).
→ Corrigé
T4 — Routage contraint par rôle
Le coût dans le graphe n’est pas fixe — il dépend du profil du véhicule.
| Rôle | Profil | Clause SQL |
|---|---|---|
| Ravitaillement | Poids lourds | CASE WHEN restriction_de_poids_total IS NOT NULL THEN -1 ELSE cost END |
| Énergie | Convois exceptionnels | CASE WHEN largeur_de_chaussee < 4 THEN -1 ELSE cost END |
| Attaque | Discrétion (chemins) | CASE WHEN nature IN ('Chemin','Sentier') THEN cost*0.7 ELSE cost*1.3 END |
| Défense | Rapidité (grandes routes) | CASE WHEN importance >= 3 THEN cost*0.5 ELSE cost END |
Objectif : recalculer un itinéraire avec votre contrainte de rôle. Comparez avec le chemin “normal”.
→ Corrigé
T5 — Migrer dans Neo4j et combiner graphe routier + ontologie
Le graphe r2gg est en SQL. L’ontologie BDTOPO est dans Neo4j. La phase 2 consiste à connecter les deux mondes :
python scripts/02_migrate_to_neo4j.py
Objectifs dans Neo4j Browser :
- Vérifier les POIs chargés (
MATCH (p:POI)) - Traverser l’ontologie (
[:EST_SOUS_TYPE_DE*]) - Calculer un plus court chemin entre POIs (
apoc.algo.dijkstra)
→ Corrigé
T6 — Réflexion : SQL vs Graphe vs Les deux
| Tâche | Quel outil ? | Pourquoi ? |
|---|---|---|
| Trouver les hôpitaux dans un polygone EPCI | PostGIS (SQL) | ST_Intersects est natif |
| Trouver le plus court chemin entre 2 points | pgRouting (graphe dans SQL) | Dijkstra nécessite un graphe |
| Explorer une hiérarchie de types sur 3 niveaux | Neo4j (Cypher) | [:EST_SOUS_TYPE_DE*] vs WITH RECURSIVE |
| Mapper un POI au sommet le plus proche | PostGIS (SQL) | ORDER BY geom <-> poi.geom |
| Trouver tous les chemins entre 2 nœuds d’ontologie | Neo4j (Cypher) | allShortestPaths natif |
Conclusion : dans la vraie vie, on combine SQL (géométrie, filtres) et graphe (parcours, routage). Aucun des deux n’est suffisant seul.
Cypher approfondi (1h)
Maintenant que les données sont dans Neo4j, allez plus loin que les requêtes de base.
T7 — Créer des nœuds personnalisés (CREATE / MERGE)
Votre groupe a des bases avancées et des points de ralliement qui ne sont pas dans la BDTOPO. Créez-les.
Objectifs :
- Créer au moins 2 nœuds
:Base(base avancée, hôpital de campagne, dépôt…) - Les relier aux POIs proches avec des relations
[:DISTANCE]
Indice : CREATE (b:Base {...}), puis MATCH (b:Base), (p:POI) WHERE distance(...) < 10000 MERGE (b)-[r:DISTANCE {meters: ...}]->(p)
→ Corrigé
T8 — OPTIONAL MATCH et UNWIND
Objectifs :
- Trouver les POIs sans connexion (isolés dans le graphe)
- Lister les POIs avec leur nombre de voisins (
OPTIONAL MATCH) - Utiliser
UNWINDpour aplatir les listes
Exercice : combien de POIs sont “isolés” (0 voisins dans le graphe Neo4j) ? Pourquoi ?
→ Corrigé
T9 — Pattern matching avancé
Objectifs :
- Trouver les chemins entre attaque et défense passant uniquement par des POIs énergie
- Détecter les triangles (3 POIs mutuellement connectés)
Défi : écrivez une requête Cypher qui trouve le chemin le plus court entre un POI attaque et un POI défense, en évitant tous les POIs énergie. Indice : WHERE NOT ... IN ou WHERE ALL(n IN nodes(path) WHERE ...)
→ Corrigé
pgRouting avancé (1h)
T10 — Matrice de distances entre POIs
Objectif : calculer les distances de chaque source vers chaque cible en un seul appel. Indice : pgr_dijkstraCostMatrix + array_agg(vid).
Exercice : calculez la matrice pour votre rôle. Quel est le POI le plus éloigné des autres ?
→ Corrigé
T11 — Isochrones (zone accessible en X minutes)
Une isochrone montre la zone atteignable en un temps donné depuis un point.
Objectif : trouver tous les sommets atteignables en moins de 10 minutes (cost = secondes). Indice : pgr_dijkstra avec WHERE agg_cost <= 600.
Exercice : tracez les isochrones à 5 min, 10 min, 15 min depuis le POI défense le plus important.
Bonus cartographie : les sommets atteignables peuvent être convertis en polygone (convex hull) pour affichage sur la carte.
→ Corrigé
Réflexion de groupe (1h)
T12 — Débat : “Quel outil pour quelle question ?”
En groupe, complétez ce tableau pour votre EPCI avec vos mesures réelles :
| Question | SQL ? | Cypher ? | pgRouting ? | Temps (ms) | LOC | Résultat |
|---|---|---|---|---|---|---|
| Top 5 POIs par nombre de voisins | ||||||
| Chemin le plus court A→B via C | ||||||
| POIs isolés (0 connexion) | ||||||
| Zone atteignable en 10 min | ||||||
| Hiérarchie ontologique complète |
Débattez :
- Y a-t-il des requêtes où SQL est clairement meilleur ?
- Y a-t-il des requêtes impossibles dans un des outils ?
- Si vous deviez choisir un seul outil pour tout faire, lequel ?
Indices Codestral
"Explique comment r2gg (route-graph-generator) transforme des tronçons
de route BDTOPO en un graphe navigable. Décris le schéma pivot
(nodes + edges) et pourquoi ce modèle est nécessaire pour le routage."
"Écris une requête pgRouting pgr_dijkstra avec un coût dynamique :
si la route a une restriction de poids, le coût est -1 (bloqué).
Montre comment passer ce coût personnalisé dans la sous-requête SQL."
"Écris une requête Cypher qui utilise OPTIONAL MATCH pour trouver
les POIs qui n'ont pas de voisins dans un graphe de distances.
Puis utilise UNWIND pour lister les noms par rôle."
Critères de validation
SELECT count(*) FROM waysetways_vertices_pgr→ graphe non vide- Snap des POIs aux sommets : chaque POI a un
vertex_id - Dijkstra retourne un chemin valide entre 2 POIs de rôles différents
- Le routage contraint montre une différence mesurable vs le routage normal
- Neo4j contient les POIs des 4 rôles + requêtes Cypher fonctionnelles
- T7 : au moins 2 nœuds personnalisés créés dans Neo4j
- T8 : nombre de POIs isolés identifié
- T10 : matrice de distances calculée pour au moins 1 rôle
- T11 : isochrone calculée (nombre de sommets atteignables)
- T12 : tableau comparatif complété avec mesures réelles