Comparer distance à vol d’oiseau et distance routière avec PHP, SQL et OpenRouteService
Dans Rechercher des utilisateurs dans un rayon géographique avec PHP et SQL, nous avons mis en place une recherche d’utilisateurs dans un rayon géographique à partir d’un code postal, d’une commune sélectionnée et d’une distance exprimée en kilomètres.
Cette première approche repose sur une distance à vol d’oiseau. Elle est rapide, simple à calculer en SQL, et suffisante pour produire une première sélection. Mais elle ne représente pas toujours la distance réellement parcourue. Deux points peuvent sembler proches sur une carte et demander pourtant un long détour par la route.
Dans cette deuxième partie, nous allons donc conserver le calcul SQL comme base de travail, puis le compléter avec OpenRouteService afin d’obtenir une distance plus proche du trajet réel. L’objectif n’est pas de remplacer ce que nous avons déjà construit, mais de comparer deux lectures d’un même territoire : la proximité théorique et la distance réellement parcourable.
Ajouter une distance réelle avec OpenRouteService
Nous utiliserons ici OpenRouteService, une API capable de calculer des distances et des durées entre plusieurs points géographiques. Dans notre cas, le point de départ sera, à l’instar du scénario mis en place dans l’article précédent, la commune associée à l’évènement, et les destinations seront les communes des utilisateurs retournés par notre première recherche basée uniquement sur SQL, voir le fichier finalisé.
Cette précision a son importance : nous n’allons pas envoyer toute notre base d’utilisateurs à l’API. Avant cela, SQL va continuer à jouer son rôle de premier filtre. Si nous cherchons des personnes dans un rayon de 100 kilomètres, un utilisateur situé à 130 kilomètres à vol d’oiseau ne pourra pas être à moins de 100 kilomètres par la route. Il est donc inutile de demander à OpenRouteService de vérifier ce cas.
Le calcul SQL nous permet ainsi de réduire la liste à traiter. L’API intervient seulement ensuite, sur les utilisateurs qui peuvent réellement entrer dans le périmètre demandé. Nous limitons les échanges avec le service externe, nous gardons un traitement plus rapide, et nous évitons de transmettre des données qui n’ont pas besoin de sortir de notre application.
L’appel à cette API ne se fera pas directement depuis JavaScript. Nous passerons par PHP, afin de garder la clé d’accès côté serveur et de mieux contrôler les données envoyées. Le navigateur continuera donc à interroger notre propre application ; c’est ensuite le serveur qui décidera s’il doit compléter les résultats avec OpenRouteService.
Avant de poursuivre : repartir des mêmes fichiers
Avant de reprendre le développement, posons deux repères importants. Les fichiers utilisés tout au long de cet article sont disponibles en téléchargement, afin que nous puissions repartir d’une base identique et suivre les manipulations étape par étape. Cela évite aussi de mélanger les fichiers de la première partie avec ceux que nous allons faire évoluer ici.
Nous supposons également que la base de données, les tables de codes postaux et l’environnement de développement ont déjà été mis en place comme expliqué dans la première partie de cette série. Si ce n’est pas encore le cas, mieux vaut commencer par les chapitres Préparer les données de travail et Préparer l’environnement de développement, du précédent article, afin de disposer du même socle avant de continuer.
Une fois ces éléments en place, nous pouvons reprendre l’application exactement là où nous l’avions laissée, puis la faire évoluer progressivement vers le calcul de distance routière.
Reprendre l’application au point où nous l’avions laissée
Pour cette deuxième partie, nous ne repartons pas du début de l’application. Toute la première séquence reste valable : la saisie du code postal, le choix éventuel de la commune, la récupération des coordonnées GPS et la sélection du rayon de recherche sont déjà en place. Nous allons donc repartir du fichier home_09.html. À ce stade, le formulaire est déjà intercepté en JavaScript : les données sont regroupées dans un objet FormData, puis envoyées au serveur avec fetch(). C’est exactement le comportement dont nous avons besoin pour faire évoluer le traitement sans recharger la page. Pour marquer le début de cette nouvelle progression, nous dupliquons ce fichier et nous le renommons :
home_09.html
→ distance_01.htmlCe nouveau fichier devient notre point de départ. Il conserve l’interface existante, mais il va progressivement accueillir les adaptations nécessaires au calcul de distance réelle. Avant d’aller plus loin, une première modification s’impose. Dans la version précédente, le formulaire appelait le script get_near.php, chargé de retourner les utilisateurs situés dans le rayon demandé à partir du calcul SQL. Nous allons désormais orienter le formulaire vers un nouveau script serveur :
<form action="php/get_distance.php" method="post" id="search_form">Ce changement est volontairement minimal. Le formulaire continue à transmettre les mêmes informations : la commune sélectionnée, ses coordonnées GPS et le rayon de recherche. En revanche, le traitement appelé côté serveur change de rôle. Le fichier get_distance.php ne devra plus seulement retourner une distance à vol d’oiseau ; il préparera aussi l’ajout d’une distance issue d’OpenRouteService.
À ce stade, nous ne modifions pas encore la logique complète de recherche. Nous préparons simplement un nouveau point d’entrée côté serveur, afin de pouvoir faire évoluer le traitement sans casser le fonctionnement déjà acquis.
Faire évoluer le traitement serveur existant
Nous repartons du script existant get_near.php, qui sait déjà recevoir les données du formulaire et retourner les utilisateurs situés dans le rayon demandé. Pour cette deuxième partie, nous le dupliquons simplement :
get_near.php
→ get_distance.php [get_distance_01.php] Avant d’ajouter OpenRouteService, ce nouveau fichier doit produire le même résultat que l’ancien. Cette vérification nous permet de confirmer que le changement de nom, l’action du formulaire et l’appel fetch() fonctionnent toujours correctement. Une fois cette base validée, nous pouvons enrichir get_distance.php avec une seconde étape : appeler l’API pour compléter les résultats SQL avec une distance réelle.

Comprendre ce que nous demandons à OpenRouteService
Avant d’ajouter du code, précisons ce que nous allons demander à OpenRouteService. L’API propose plusieurs services, mais nous n’avons pas besoin ici d’un itinéraire complet avec toutes les étapes du trajet. Nous voulons simplement obtenir une distance réelle entre la commune de l’évènement et les communes des utilisateurs présélectionnés. Pour cela, nous utiliserons le service Matrix. Il permet de calculer des distances et des durées entre une ou plusieurs sources et plusieurs destinations. Dans notre cas, la source sera unique : le lieu de l’évènement. Les destinations seront les communes retournées par notre premier filtrage SQL.
Dans le cadre de cette démonstration, cette organisation reste volontairement limitée. Avec une source et 100 destinations, nous générons 100 relations origine-destination dans une seule requête. Le plan standard gratuit indique actuellement 500 requêtes Matrix par jour, 40 par minute, avec une limite de 3 500 relations par requête Matrix. Pour un projet réellement déployé, il faudrait évidemment vérifier le plan, les quotas et les conditions d’usage correspondant au volume attendu.
Pendant les phases de tests intensifs, nous pouvons aussi limiter volontairement le nombre de destinations envoyées à l’API, par exemple à deux utilisateurs (destinations). Cela évite de consommer inutilement le quota pendant les essais, tout en permettant de vérifier que la requête, la réponse et le formatage JSON fonctionnent correctement.
Nous demanderons donc deux métriques : la distance, retournée en mètres, et la durée, retournée en secondes. Ces valeurs viendront compléter les résultats SQL, sans remplacer le premier calcul à vol d’oiseau. SQL sélectionne les candidats, OpenRouteService affine la distance.
Préparer les coordonnées à envoyer à l’API
À ce stade, get_distance_02.php, notre requête SQL a déjà retourné une première liste d’utilisateurs au travers de la dernière instruction $users_list = $stmt->fetchAll(). Cette sélection repose encore sur la distance à vol d’oiseau, mais elle nous donne une base suffisamment réduite pour préparer l’appel à OpenRouteService.
L’API n’a pas besoin de connaître les noms, les adresses électroniques ou les autres informations liées aux utilisateurs. Pour calculer une distance, elle a seulement besoin de coordonnées GPS. La précédente requête SQL ne nous les retournait pas encore. Nous devons donc ajouter la latitude et la longitude de la commune associée à chaque utilisateur.
-- AUtres éléments présents dans le SELECT
USER_CP.ch_cp_lat AS USER_CITY_LAT,
USER_CP.ch_cp_lon AS USER_CITY_LON,
-- Autres éléments présents dans le SELECTDans l’article, nous ne reprendrons que les extraits nécessaires à la compréhension du cheminement. Les fichiers sources fournis avec la démonstration contiennent une version plus complète et abondamment commentée du code, afin de pouvoir suivre chaque étape directement dans le projet.
Préparons ensuite un tableau contenant le point de départ, puis les différentes destinations à vérifier. Le premier point ajouté correspond à la commune de l’évènement. Il occupe l’index 0 du tableau. Les utilisateurs présélectionnés sont ajoutés ensuite, un par un. Cette position est importante, car elle nous permettra d’indiquer à OpenRouteService que le premier point est la source, tandis que tous les autres sont les destinations.
$locations = [
[
(float) $event_lon,
(float) $event_lat
]
];
foreach ($users_list as $user) {
$locations[] = [
(float) $user['USER_CITY_LON'],
(float) $user['USER_CITY_LAT']
];
}Il faut également faire attention à l’ordre des coordonnées. Dans beaucoup de formulaires ou de bases de données, nous pensons spontanément en latitude, longitude. OpenRouteService attend ici l’ordre inverse : longitude, latitude. Cette inversion est une source d’erreur fréquente lorsqu’on commence à manipuler des API géographiques.
La requête envoyée à l’API est regroupée dans une variable appelée $payload. Ce terme est générique, il signifie charge utile : il désigne simplement le paquet de données transmis au service externe. Le nom n’est pas obligatoire, mais il est courant dans les échanges avec des API.
$payload = [
'locations' => $locations,
'sources' => [0],
'destinations' => range(1, count($locations) - 1),
'metrics' => ['distance']
];Cette structure se lit assez simplement. locations contient tous les points GPS. sources indique que le point de départ est celui placé à l’index 0. destinations désigne tous les autres points. Enfin, metrics précise que nous attendons une distance en retour.

Pendant les phases de test, nous pouvons limiter temporairement le nombre de destinations envoyées à l’API, par exemple aux deux premiers utilisateurs 'destinations' => range(1, 2). Cela permet de vérifier le fonctionnement sans consommer inutilement les quotas, surtout lorsque l’on recharge souvent la page ou que l’on ajuste encore le script.
Envoyer la requête avec cURL
Les données sont maintenant prêtes dans notre variable $payload. Il nous reste à les transmettre à OpenRouteService depuis PHP. Pour cela, nous utilisons cURL, une extension PHP permettant d’effectuer des requêtes vers des services externes. Nous n’allons pas détailler ici toutes ses possibilités, mais simplement utiliser les fonctions nécessaires à notre appel API. La documentation officielle propose des exemples de base ainsi qu’une référence complète des fonctions disponibles.
$apiKey = 'VOTRE_CLE_API';
$ch = curl_init('https://api.openrouteservice.org/v2/matrix/driving-car');
curl_setopt_array($ch, [
// Options de la requête :
// méthode POST, en-têtes HTTP, format JSON,
// et données transmises à OpenRouteService.
]);
$response = curl_exec($ch);Ce choix est important : l’appel à l’API ne part pas du navigateur, mais de notre serveur. La clé API reste donc côté PHP et n’est pas exposée dans le JavaScript. Le principe reste assez simple. Nous préparons une requête vers le service Matrix d’OpenRouteService, nous lui transmettons notre clé API, puis nous envoyons les données préparées dans $payload.
Le détail des paramètres passés à curl_setopt_array() est commenté dans le fichier get_distance_03.php fourni avec les sources de l’article. Enfin, curl_exec() déclenche réellement la requête. PHP contacte alors OpenRouteService et stocke la réponse brute dans $response. Cette variable contient alors la réponse brute retournée par OpenRouteService. En la renvoyant au client on peut ainsi la suivre depuis la console.
echo json_encode([
// Nous pouvons suivre la reponse de cURL dans la console
'response' => $response
]);
Dans les anciennes versions de PHP, on terminait souvent ce type de bloc avec curl_close(). Cette fonction est désormais dépréciée depuis PHP 8.5, car les objets CurlHandle sont libérés automatiquement. Nous ne l’utiliserons donc pas dans cette version du script.
Vérifier la réponse retournée par OpenRouteService
Une fois la requête exécutée, ouvrons le fichier get_distance_04.php, la variable $response contient la réponse brute retournée par OpenRouteService. Avant de l’exploiter, nous devons vérifier deux choses : l’appel a-t-il bien abouti ? et la réponse contient-elle bien les distances attendues ?
La première vérification concerne l’appel lui-même. Si curl_exec() retourne false, cela signifie que la requête n’a pas pu être menée correctement. Dans ce cas, nous renvoyons une réponse JSON d’erreur au navigateur et nous interrompons le traitement. Si l’appel a bien répondu, nous pouvons décoder la réponse avec json_decode(). Le second paramètre true permet d’obtenir un tableau associatif PHP, plus simple à manipuler dans la suite du script.
// Première vérification
if ($response === false) {
// Erreur dans le traitement
exit;
}
// Si tout est bon, nous pouvons décoder la réponse
$ors_result = json_decode($response, true);
// Seconde vérification
if (!isset($ors_result['distances'][0])) {
// Il n'y a pas de données exploitables
exit;
}
// Les données sont présentes, nous pouvons les récupérer
$distances = $ors_result['distances'][0];Le point qui peut surprendre se situe dans l’accès aux distances. Pourquoi ce [0] ? Parce que le service Matrix retourne une matrice, donc un tableau à deux dimensions. La première dimension correspond aux sources, la seconde aux destinations. Dans notre cas, nous avons une seule source : la commune de l’évènement. Elle se trouve donc à l’index 0.
Les distances qui nous intéressent sont alors rangées dans cette première ligne de la matrice. Elles correspondent, dans l’ordre, aux destinations que nous avons envoyées à l’API. Pour vérifier cette étape sans encore enrichir les utilisateurs, nous pouvons temporairement retourner uniquement les distances obtenues :
echo json_encode([
'distances' => $distances
]);
exit;
Enrichir le JSON retourné au navigateur
Nous avons maintenant deux informations distinctes : la liste des utilisateurs issue de SQL $users_list, et les distances retournées par OpenRouteService $distances. Plutôt que de renvoyer ces données dans deux tableaux séparés, nous allons les réunir dans une même structure. L’objectif est de garder un JSON simple à exploiter côté JavaScript : chaque utilisateur doit porter ses propres distances. Il contiendra donc déjà la distance à vol d’oiseau calculée en SQL, puis la distance réelle retournée par l’API.
Ouvrons le fichier get_distance_05.php, et parcourons $users_list et nous utilisons l’index de chaque utilisateur pour récupérer la distance correspondante dans $distances. Un détail mérite d’être souligné : nous parcourons ici le tableau avec &$user, c’est-à-dire par référence, afin d’enrichir directement chaque entrée de $users_list. Après ce type de boucle, la documentation PHP recommande de libérer la référence avec unset($user), pour éviter qu’elle ne continue à pointer vers le dernier élément parcouru.
foreach ($users_list as $index => &$user) {
$distance_m = $distances[$index] ?? null;
$user['DISTANCE_ORS_M'] = $distance_m;
$user['DISTANCE_ORS_KMS'] = $distance_m !== null
? round($distance_m / 1000, 1)
: null;
}La distance fournie par OpenRouteService étant exprimée en mètres, nous la conservons dans DISTANCE_ORS_M, mais nous pouvons ajouter une version en kilomètres, plus lisible pour l’affichage dans l’application, DISTANCE_ORS_KMS, (Cette conversion en kilomètres est arrondie à une décimale, afin de rester cohérente avec la distance SQL). Le résultat retourné au navigateur peut alors rester très simple :
echo json_encode([
'datas' => $users_list
]);Chaque entrée du JSON contient désormais les informations de l’utilisateur, la distance SQL (distance à vol d’oiseaux) et la distance OpenRouteService (distance routière en kilomètres). Le JavaScript n’a plus à recoller deux tableaux entre eux : il peut directement parcourir les résultats et les afficher.

Comparer les deux distances
Nous disposons maintenant de deux mesures pour chaque utilisateur : la distance à vol d’oiseau, calculée par SQL, et la distance réelle, retournée par OpenRouteService. Nous pouvons donc calculer leur écart, afin de rendre visible ce que le calcul théorique ne montre pas toujours : détour routier, relief, route indirecte ou commune moins bien reliée qu’elle ne le paraît. Ouvrons le fichier get_distance_06.php
$user['DISTANCE_GAP_KMS'] = $user['DISTANCE_ORS_KMS'] !== null
? round($user['DISTANCE_ORS_KMS'] - $user['DISTANCE_KMS'], 1)
: null;Nous soustrayons ici la distance SQL de la distance OpenRouteService. Le résultat est stocké dans DISTANCE_GAP_KMS, avec un arrondi à une décimale pour rester lisible dans le tableau final. Si la distance retournée par l’API est absente, nous conservons null, afin de ne pas produire un écart artificiel.
OpenRouteService peut aussi retourner une durée estimée, mais cette information doit être demandée explicitement dans le paquet envoyé à l’API. Jusqu’ici, nous demandions seulement la distance. Nous pouvons maintenant demander les deux métriques :
$payload = [
// Autres paramètres
// Ajoutons la demande de durée de trajet
'metrics' => ['distance', 'duration']
];Une fois cette métrique demandée, la réponse contient aussi une entrée durations. Comme pour les distances, nous récupérons la première ligne de la matrice, car notre requête ne contient qu’une seule source.
$durations = $ors_result['durations'][0];
$user['DURATION_ORS_S'] = $duration_s;
$user['DURATION_ORS_MIN'] = $duration_s !== null
? round($duration_s / 60)
: null;Nous pouvons donc afficher trois lectures complémentaires : la distance théorique, la distance réelle, et la durée estimée du trajet. La première aide à filtrer rapidement, les deux autres rapprochent le résultat d’un usage réel.
echo json_encode([
'datas' => $users_list
]);
Afficher les distances dans un tableau
Notre script get_distance_07.php peut maintenant retourner une réponse JSON plus complète. Nous ne renvoyons plus seulement une distance isolée ou un tableau de test, mais un objet structuré contenant l’état de la réponse, les paramètres reçus, le nombre de résultats et la liste des utilisateurs enrichis.
echo json_encode([
'success' => true,
'params' => $_POST,
'count' => count($users_list),
'datas' => $users_list
]);Côté interface, nous repartons de distance_01.html, que nous dupliquons en distance_02.html. Cette nouvelle version va reprendre le principe déjà utilisé dans l’article précédent : afficher les résultats dans un tableau HTML, plus simple à lire et à exploiter qu’une réponse brute dans la console. Nous ajoutons donc une zone destinée à recevoir les utilisateurs retournés par le serveur :
<div id="users_result_HTML"></div>Lors de l’appel AJAX, nous conservons aussi la réponse complète dans une variable globale. Cela nous permettra de réutiliser ces données plus tard, notamment lorsque nous ajouterons l’exportation.
let usersData = null;
// Puis, lorsque le serveur répond, nous stockons le résultat
// et nous le transmettons à une fonction dédiée à l’affichage
.then(result => {
usersData = result;
displayUsers(result);
})La fonction displayUsers() peut repartir du tableau déjà construit dans la première version de l’application. Nous l’adaptons simplement aux nouvelles données disponibles. La colonne de distance existante devient SQL, puisqu’elle correspond au calcul à vol d’oiseau. Nous ajoutons ensuite une colonne ORS, pour la distance routière retournée par OpenRouteService, puis une colonne Durée, pour l’estimation de trajet.
<!-- Dans le corps du tableau, chaque utilisateur affiche donc les trois valeurs -->
<!-- Au niveau du <thead> -->
<th>SQL</th>
<th>ORS</th>
<th>Durée</th>
<!-- Au niveau du <tbody> -->
<td>${user.DISTANCE_KMS} km</td>
<td>${user.DISTANCE_ORS_KMS} km</td>
<td>${user.DURATION_ORS_MIN} min</td>
<!-- Au niveau du <tfoot> -->
<th colspan="9">
Pour cette première version de distance_02.html, nous laissons le pied de tableau sans bouton d’exportation. Nous pouvons simplement adapter le colspan au nombre de colonnes affichées.
Ajouter l’export CSV aux nouvelles données
Dans l’article « Rechercher des utilisateurs dans un rayon géographique avec PHP et SQL », nous avions déjà mis en place un export CSV à partir des résultats affichés dans le tableau, voir le fichier home_11.html. Nous pouvons reprendre le même principe dans distance_03.html, sans réécrire tout le mécanisme.
La différence vient simplement du contenu exporté. Notre tableau ne contient plus seulement les informations utilisateur et la distance SQL. Il intègre désormais la distance à vol d’oiseau, la distance routière retournée par OpenRouteService, ainsi que la durée estimée du trajet. Dans le pied du tableau, présent dans la fonction displayUsers(), nous pouvons donc réintroduire le bouton d’export :
<button type="button" onclick="export_csv()">Exporter ces données en CSV</button>La fonction export_csv() peut être reprise depuis home_11.html, puis adaptée aux nouvelles colonnes. La première ligne du fichier CSV doit maintenant refléter les informations réellement disponibles :
// Modification et adaptations de la fonction export_csv()
// Au niveau de la génération de la ligne d'entête
csv += "Civilité;Nom;Prénom;Ville;CP;SQL;ORS;Durée;E-mail\n";
// Lors du parcours des utilisateurs, nous ajoutons les deux valeurs issues d’OpenRouteService :
csv += `${user.DISTANCE_ORS_KMS};`;
csv += `${user.DURATION_ORS_MIN};`;Le reste du fonctionnement ne change pas. Nous parcourons les données déjà stockées côté JavaScript, nous construisons une chaîne CSV ligne par ligne, puis nous déclenchons le téléchargement du fichier. L’export ne recalcule rien : il exploite simplement les résultats déjà retournés par le serveur.

Cette étape complète naturellement le tableau. Nous ne nous contentons plus d’afficher les écarts entre SQL et OpenRouteService ; nous pouvons aussi les transmettre à un tableur, les archiver ou les exploiter dans un autre traitement.
Gérer l’encodage et les erreurs de l’API
Avant de poursuivre la mise en forme, renforçons le fichier serveur. L’évolution se fait dans get_distance_08.php, à deux endroits : juste après l’appel à OpenRouteService, puis au moment de produire la réponse JSON finale.
La première vérification concerne le retour de l’API. Dans le chapitre précédent, nous avons utilisé cURL pour envoyer notre requête à OpenRouteService. Cette requête peut être correctement construite côté PHP, mais recevoir malgré tout une réponse d’erreur : clé invalide, quota atteint, limite de requêtes, coordonnées incorrectes ou réponse inexploitable. Avant de décoder les données, nous récupérons donc le code HTTP associé à l’appel cURL.
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);Ce code nous indique si la requête a été acceptée par le service. Un code 200 correspond à une réponse exploitable. Les autres codes doivent être traités comme des erreurs avant toute tentative d’extraction des distances.
Le second renforcement concerne l’encodage du JSON final. Une donnée mal encodée, ou un fichier enregistré dans un mauvais format, peut faire échouer json_encode() et provoquer une réponse vide côté navigateur. Pour éviter ce comportement silencieux, nous construisons d’abord la réponse dans une variable, puis nous testons le résultat avant de l’envoyer.
$json = json_encode(
$output,
JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE
);JSON_UNESCAPED_UNICODE garde les caractères accentués lisibles dans la réponse. JSON_INVALID_UTF8_SUBSTITUTE remplace les caractères UTF-8 invalides au lieu de faire échouer toute la génération du JSON.
Ces contrôles ne changent pas le principe de l’application. Ils rendent simplement le retour serveur plus fiable et plus lisible en cas de problème. Lorsqu’un traitement dépend à la fois d’une base de données, d’un encodage de fichier et d’une API externe, mieux vaut obtenir une erreur explicite qu’une réponse vide impossible à interpréter.
Améliorations et mise en forme des résultats pour mieux lire les écarts
Nous pouvons maintenant reprendre la structure visuelle déjà mise en place dans « Rechercher des utilisateurs dans un rayon géographique avec PHP et SQL », notamment dans la partie consacrée aux améliorations de l’interface. Le fichier distance_04.html réutilise la même logique générale : une zone <header> pour la saisie, un <aside> pour les paramètres de recherche, puis un <main> destiné à recevoir les résultats. Les styles de base restent répartis dans reset.css, layout.css et theme.css, comme dans la première partie de l’article. Pour ne pas perturber les styles communs à l’ancien article, ajoutons une feuille dédiée à cette deuxième partie : distance.css.
<link rel="stylesheet" href="css/distance.css">L’objectif n’est pas de transformer le tableau en outil de décision automatique. Nous voulons simplement attirer l’attention sur certaines situations : un écart important entre la distance SQL et la distance ORS, ou une durée de trajet élevée. La couleur sert ici d’aide à la lecture, pas de verdict. Pour cela, nous pouvons ajouter une colonne Écart au tableau. Elle exploitera la valeur DISTANCE_GAP_KMS déjà préparée côté PHP.
Il suffit de modifier, depuis la fonction displayUsers(result), l’entête du tableau résultat qui est généré, en ajoutant la balise <th>Écart</th> entre ORS et Durée. Il faut également basculer les colspan des <th> des <thead> et <tfoot> en les passant de 9 à 10, afin de prendre en compte cette nouvelle colonne ajoutée. Il nous suffit ensuite de préparer deux classes CSS qui s’affecteront selon les valeurs retournées :
const gapClass = user.DISTANCE_GAP_KMS >= 10
? 'is-gap-high'
: '';
const durationClass = user.DURATION_ORS_MIN >= 45
? 'is-duration-high'
: '';Ces classes sont appliquées aux cellules concernées, c’est-à-dire l’écart en kilomètres et la durée estimée du trajet. Nous ajoutons aussi une fonction formatValue() afin d’éviter l’affichage de valeurs comme null km ou undefined min. Lorsqu’une donnée est absente, l’interface affiche simplement un tiret.
<td class="${gapClass}">${formatValue(user.DISTANCE_GAP_KMS, ' km')}</td>
<td class="${durationClass}">${formatValue(user.DURATION_ORS_MIN, ' min')}</td>Les seuils retenus restent volontairement modifiables. Un écart de 10 km ou une durée de 45 minutes peuvent convenir à notre démonstration, mais ils doivent pouvoir être adaptés au contexte réel : évènement local, intervention professionnelle, livraison, club sportif ou réseau associatif.
<label for="gap_limit">Écart important à partir de (kms) :</label>
<input type="number" id="gap_limit" name="gap_limit" value="10" min="0" step="1">
<label for="duration_limit">Durée importante à partir de (min) :</label>
<input type="number" id="duration_limit" name="duration_limit" value="45" min="0" step="5">Comme les résultats sont déjà conservés dans usersData, il n’est pas nécessaire de relancer l’appel à OpenRouteService lorsque l’on modifie ces seuils. Nous ajoutons simplement des écouteurs sur les champs, afin de réafficher le tableau avec les nouvelles règles de lecture.
const gap_limit = document.getElementById('gap_limit');
const duration_limit = document.getElementById('duration_limit');
['input', 'change'].forEach(eventName => {
gap_limit.addEventListener(eventName, displayUsers);
duration_limit.addEventListener(eventName, displayUsers);
});Le tableau devient ainsi plus lisible sans changer le fonctionnement de l’application. Nous affichons toujours les mêmes données, mais nous aidons le lecteur à repérer rapidement les cas où la distance réelle s’éloigne fortement du calcul à vol d’oiseau, ou lorsque le trajet estimé devient plus long que prévu.

Conclusion : préparer la suite cartographique
Nous sommes partis d’un calcul rapide en SQL, basé sur la distance à vol d’oiseau, puis nous l’avons complété avec une distance routière réelle et une durée estimée grâce à OpenRouteService. Cette organisation garde une logique simple : SQL présélectionne les utilisateurs, l’API affine les distances, puis l’interface aide à repérer les écarts les plus significatifs. Nous obtenons ainsi un résultat plus proche d’un usage réel, sans abandonner le premier filtrage.
Une suite naturelle consisterait maintenant à représenter ces résultats sur une carte. Leaflet et OpenStreetMap deviendraient alors utiles non pas pour calculer les distances, mais pour les visualiser : placer le lieu de l’évènement, afficher les utilisateurs, ouvrir des popups, ou créer une interaction entre le tableau et la carte.
La carte ne remplacerait donc pas le travail réalisé ici. Elle viendrait simplement le prolonger, en rendant les distances plus lisibles dans l’espace.
