[PHP/SQL] Géolocalisation et distance entre des villes

Posté le 24. sept, 2008 par Tony Samson dans Développement Web

J’ai besoin pour un développement spécifique mais aussi pour Agenda Culturel de pouvoir lister dans un script PHP les villes à proximité d’une ville donnée. Concrètement, cela signifie afficher les villes situées à l’intérieur d’un périmètre d’une distance de X kilomètres de la ville de référence.

J’ai longuement cherché sur le web, et je n’ai trouvé que peu d’infos à ce sujet. Je vous livre ici le résultat de mes recherches et de mes multiples tests, de l’intégration des coordonnées de latitude/longitude à l’aide des API de géolocalisation google map et yahoo map jusqu’à la requête sql finale qui inclue la formule de calcul de distance à partir des coordonnées géolocalisées.

geolocalisation-proximite-php-sql

Une base de donnée fiable des villes associées à leurs coordonnées de latitude/longitude

En premier lieu, il s’agit d’avoir une base de données de villes. Il en existe de nombreuses sur le net, celle-ci intègre même les coordonnées de latitude et de longitude.

Dans mon cas, je possédais déjà une base de données de villes françaises mais sans ces coordonnées. La meilleure méthode pour y intégrer la latitude et la longitude de chaque ville est d’utiliser l’API de Yahoo ou de google.

Ces deux API permettent de récupérer un flux XML correspondant à la recherche (ex : Amiens, France) contenant la latitude et la longitude.

Par exemple, voici le résultat de la recherche de la ville d’Amiens avec l’API de Yahoo :

<ResultSet xsi:schemaLocation="urn:yahoo:maps http://api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd">
<Result precision="zip">
<Latitude>49.896309</Latitude>
<Longitude>2.279720</Longitude>
<City>80000 Amiens</City>
<State>France</State>
<Country>FR</Country>
</Result>
</ResultSet>

Il n’y a donc plus qu’à faire une boucle sql sur la table des villes en parsant le flux de yahoo ou google correspondant (en indiquant la ville, le code postal et le pays) et intégrer à l’enregistrement de la ville la latitude et la longitude. Un vrai jeu d’enfant avec simplexml en php5 ! Cependant, même si ces API sont puissantes, il se peut que parfois la ville ne soit pas trouvée, que la ville retournée ne soit pas la bonne ou que plusieurs résultats soient retournés. Pour plus d’informations, et pour choisir entre les API de Yahoo et de Google, je vous invite à lire cet article sur la géolocalisation via yahoo et google map très bien fait et qui m’a bien aidé.

Vous avez à présent une table contenant les villes associées à leurs coordonnées géographique de latitude et de longitude.


Il ne reste plus qu’à exploiter cela dans une requête SQL et afficher le tout en PHP.

J’ai d’abord tenté une conversion des données latitude/longitude en points de coordonnées lambert 2 étendue (norme française) grâce à la fonction geos2lambert() disponible sur google code mais son exploitation s’est révélé compliquée et imprécise (ou alors c’est moi qui suis vraiment nul en maths !).

Je suis donc reparti sur les données de latitude/longitude. Grâce à une formule mathématique complexe trouvée sur le net je suis arrivé au résultat suivant.

Je reçois en variable GET les coordonnées de ma ville de référence :

$latitude=$_GET[lat];
$longitude=$_GET[lon];

La fameuse formule utilisant les colonnes lat et lon de ma table sql et les données de latitude et longitude de ma ville de référence :

$formule="(6366*acos(cos(radians($latitude))*cos(radians(`lat`))*cos(radians(`lon`) -radians($longitude))+sin(radians($latitude))*sin(radians(`lat`))))";

Et la requête SQL prête à être exécutée :

$sql="SELECT ville,$formule AS dist FROM ville WHERE $formule<='$_GET[distance]' ORDER by dist ASC";

  • le SELECT $formule AS dist permet de classer les villes de la plus proche à la plus éloignée avec ORDER by dist ASC
  • le WHERE $formule<=’$_GET[distance]‘ permet de définir un rayon en km ($_GET[distance]) à partir de la ville de référence

Je vous laisse coder l’éxécution de la requête sql et l’affichage des données en php, ce n’est pas vraiment le plus dur !

Vous pouvez voir le résultat sur cette page. La ville de référence est ici Amiens avec un périmètre de 15 km.


En guise de conclusion…

Il faut savoir que ce calcul et le résultat ne sont pas précis pour plusieurs raisons :

  • Le calcul ne tient pas compte de l’altitude, qui, dans certains endroits peut influer sur les distances
  • La formule a été simplifiée au maximum par souci de simplicité et d’efficacité.
  • La distance est « à vol d’oiseau » et non pas une distance routière
  • La localisation latitude/longitude d’une ville repose sur un point (généralement la mairie de la ville en question), donc la distance ne commence pas à la sortie de la ville mais du centre de la ville. Ainsi, par exemple, pour Paris cela ne sert à rien de vouloir localiser les villes à 5km…

Cependant, malgré l’imprécision cela fonctionne, car l’intérêt premier est d’afficher les villes proches et non de concurrencer google map ! Il convient juste de ne pas forcément afficher les distances en km qui sont souvent fausses (à quelques kilomètres près).

L’utilisation de cette technique qui est finalement assez simple à mettre en place peut être très utile sur du web local afin d’élargir les recherches sur une ville aux villes à proximité (dans le cas où aucune réponse n’est disponible pour la ville concernée par exemple) ou proposer d’autres villes ou contenus (ou publicités géolocalisées !) à l’internautes en rapport avec sa requête.

Si vous avez des questions sur la mise en place d’une recherche de proximité ou si cet article vous a aidé, n’hésitez pas, les commentaires sont fait pour ça !

Tags: , , , , , , ,

37 commentaires sur “[PHP/SQL] Géolocalisation et distance entre des villes”

  1. Antoine

    11. mar, 2009

    merci pour cet article :)

  2. philgood

    24. mar, 2009

    Très interessant, très bien rédigé
    merci pour ce partage

  3. topheur

    08. avr, 2009

    Trop bon.
    Merci beaucoup pour cette article.
    Question : tu dis dans ton article que tu utilises un calcul simpifié. Sais tu ou trouver la version non simplifié ?

  4. Titcoatch

    19. mai, 2009

    Merci c’était parfait pour ce que je voulais faire et ça marche du tonnerre.

  5. Ptigob

    21. juil, 2009

    Cool c’est la formule qu’il me fallait merci msieur :)

  6. Return

    28. juil, 2009

    Bonjour,

    Article très bien réalisé. Merci pour le partage.

    Une petite remarque : le lien ne marche plus :)

  7. Tony Samson

    30. juil, 2009

    Voilà le lien de démo est corrigé.
    @topheur : pour ce qui est de la formule plus complexe je ne saurais dire où je l’avais trouvé… désolé.

  8. Tony Samson

    31. juil, 2009

    Formule plus complexe en tenant compte que la terre est ronde (donc plus précis) :
    http://fr.wikipedia.org/wiki/Distance_du_grand_cercle

  9. Portekoi

    27. août, 2009

    Merci à toi pour cet article fort intéressant :)

  10. fabien tanguy

    13. sept, 2009

    la formule exacte a un nom celle d’haversine.
    merci pour cet article très clair et très enrichissant

  11. PizzaToy

    30. oct, 2009

    Bonjour,

    un grand merci pour la formule et le code, c’est vrai ça marche du tonnerre !
    L’intérêt d’une telle application est multiple. On s’approche du logique de géomarketing en délimitant une zone de chalandise. On peut y adapter un curseur pour faire varier le rayon de notre recherche. Un des points intéressant est que, par exemple, une ville se trouvant limitrophe de plusieurs départements renverra un balayage complet de sa zone en une seule recherche; alors qu’un simple moteur par départemnt obligera de relancer la procédure.
    Pour ma part j’ai ajouté un Autocompleter et une carte inréractive, tout ça avec 3 bases de données : une pour les départements et les préfectures, une autre pour les villes et positions (35000 points), et les adresses des pizzerias affiliées ou pas au concept http://www.pizzatoy.com.

    Encore merci

  12. Jerem

    19. jan, 2010

    Salut Mec, bon article ! Du vrai tuto, comme on les aime ;)
    Continue +++

  13. Sun Location

    10. mar, 2010

    Super cet article, bien simpa en tout cas !
    merci :)

  14. drhouse

    22. avr, 2010

    Article sympa
    Merci bcp

  15. Adrien

    24. avr, 2010

    Merci pour ton tuto bien sympa :)

    Par contre, juste pour info, dans mon cas la formule ne fonctionnais pas après plusieurs tentatives, du coup j’ai trouvé ceci, qui semble fonctionner pour moi :

    http://code.google.com/intl/fr/apis/maps/articles/geospatial.html#geospatial

    A savoir que c’est en miles, donc x1.6 et quelques ^^

    Merci encore :)

  16. NAS

    14. mai, 2010

    Yes ! intéressant tout ça ! Si en plus tu nous donnais un petit fichier SQL avec la table des villes de france :) ce serait le top ;-)
    mais déjà merci :)

  17. rmgl

    21. oct, 2010

    bonsoir, l’explicatif est intéressant, cependant j’ajouterai, non un bémol mais une demande d’explication plus générale:
    pour quelle raison latitude, longitude d’un même point varient selon Maps, tyre, GPS, Google etc. ?
    Car le GPS nous positionne avec 5 mètres d’écart, donc une précision importante.

  18. Thomas

    06. déc, 2010

    MERCIIIII !!!!!!!

    5h que je tente de faire un tri dans le même style (sauf que les coordonnées sont tirées via la fonction de géolocalisation) et ça marche du tonnerre !

    Il est possible d’afficher des distances routières en passant par google direction, cependant c’est assez limité en termes de volume de requête.

    encore merci pour cet article

  19. tom_over

    29. déc, 2010

    Merci pour ton article.. comme Thomas ça fait pas mal de temps que je cherche à faire ce genre de requête afin d’optimiser la géolocalisation..

    par contre juste une question… je ne comprend pas ta structure de ta table « ville »
    il y a quoi dans ta ligne « dist » ? ?

  20. Tony Samson

    07. jan, 2011

    La colonne « dist » n’existe pas dans la table. Dans la requete cela permet de classer les villes de la plus proche à la plus éloignée avec ORDER by dist ASC

  21. AkaiKen

    01. fév, 2011

    Bonjour,
    j’ai utilisé votre article pour faire afficher des commerces dans un rayon de ‘tant de km’, exactement comme vous l’avez posté, et ça marche très bien, merci :)

    J’ai cependant une question : est-ce possible, connaissant un point (latitude et longitude), la longitude d’un deuxième point, et la distance entre les deux, de sortir la latitude du second point ? (idem pour sa longitude si l’on connait sa latitude)
    J’ai essayé de sortir ces infos de la formule que vous proposiez, mais je tombe sur une hideuse suite de racines carrées de 1 + cos(truc) au carré, et je crains que le serveur ne prenne trop de temps pour calculer ça.
    Auriez-vous une idée ?
    Merci d’avance !

  22. Guillaume

    06. fév, 2011

    Merci pour ton article, je cherche a faire exactement la meme chose.
    Par contre, dans ta formule, la distance renvoyée entre les deux points GPS latitude et longitude est en quelle unité ??
    Merci d’avance.

  23. Veebee

    11. fév, 2011

    Bonjour et merci pour cet article clair et très utile

  24. Didier

    02. mar, 2011

    Simple, efficace…Que de temps gagné !
    Merci

  25. Tony Samson

    22. mar, 2011

    @guillaume : La distance renvoyée est en kilomètres.

  26. Web Apps

    15. avr, 2011

    Un petit souci cependant… l’API de Google Maps limite le nombre de requêtes par jour.

  27. Mansion JC

    15. juin, 2011

    Bonjour

    Très interessant ce tuto.
    Si je liste les commerces situés dans un rayon de 10 kms autour de A, comment faire pour calculer la distance de chaque commerce par rapport à A comme l’a fait PizzaToy
    sur son site ?
    Merci pour votre réponse

  28. azzabi

    07. oct, 2011

    merci c vraiment interessant

  29. Kalagan

    11. oct, 2011

    Merci pour la formule et pour la requête SQL, c’est exactement ce que je cherchais :)

  30. dav

    30. nov, 2011

    Bonjour,

    j’ai testé le code mais je crois que j’ai une erreur. je n’arrive pas à afficher les données voici mon code :

    $latitude=’49.89444351196299′;
    $longitude=’2.2933330535889005′;

    $formule= »(6366*acos(cos(radians($latitude))*cos(radians(‘latitude’))*cos(radians(‘longitude’) -radians($longitude))+sin(radians($latitude))*sin(radians(‘latitude’)))) »;

    $ville_base = mysql_query(« SELECT nomville,$formule AS dist FROM table_communes WHERE $formule<='50' ORDER by dist ASC ");
    while($row = mysql_fetch_array($ville_base))
    {
    $nomville= $row['nomville'];

    echo $nomville.'’;

    }

    Merci de votre aide

  31. loko

    15. déc, 2011

    interessant mais je comprends pas:
    pourquoi se fatiguer à faire appel à l’api de yahoo ou de google pour collecter les latitude/longitude puisque le lien http://www.galichon.com/codesgeo fournit déjà une base de donnees (à jour -2011: et depuis la position des villes n’a pas bcp changé…) avec ces infos. ou alors c’est qu’elles ne sont pas fiables ??

  32. Loolou

    16. déc, 2011

    Merci mille fois, j’avais justement besoin d’une telle formule pour un projet perso.
    Merci.

  33. Tony Samson

    16. déc, 2011

    @loko : tout simplement dans le cas où (comme dans mon cas à l’époque) vous avez déjà une base de donnée de villes qui est utilisée. Dans le cas d’un nouveau site, c’est clair, c’ets plus simple d’utiliser une base de villes existante.

  34. theptitprince

    03. jan, 2012

    Bonjour.
    J’ai une petite erreur étrange… Quand on fait une recherche par frejus… Et ba SQL veut plus rien savoir!

  35. theptitprince

    03. jan, 2012

    Re-bonjour…
    Il semblerait simplement que les coordonnées soient erronées ;)

  36. Chris

    09. jan, 2012

    Pour ceux qui aurais comme moi un soucis … ( dav ) !!

    pour moi comme ca, ca marche sinon ‘lon’ et ‘lat’ valent ’0′ et la dist = 5111.5757527 !!!

    $formule= »(6366*acos(cos(radians($latitude))*cos(radians(lat))*cos(radians(lon) -radians($longitude))+sin(radians($latitude))*sin(radians(lat)))) »;

    $query= »SELECT nom,lat AS lat, lon AS lon, $formule AS dist FROM villes WHERE $formule<='3' ORDER by dist ASC";

  37. franck

    20. jan, 2012

    Dav ,tuas effectivement une erreur dans ton code au niveau de la requette

    « $ville_base = mysql_query(« SELECT nomville,$formule AS dist FROM table_communes WHERE $formule mysql_query(« SELECT
    par => mysql_query(« SELECT

    le reste semble marcher si:

    Les champ de ta table correspondent à:
    contenant la latitude=> latitude
    contenant la longitude=> longitude

    et si le nom de ta table est bien => table_communes

Laissez une réponse