PHP et SQL: calculez ou interrogez la distance du grand cercle entre les points de latitude et de longitude avec la formule Haversine

Formule Haversine - Calculez la distance du grand cercle avec PHP ou MySQL

Ce mois-ci, j'ai beaucoup programmé en PHP et MySQL en ce qui concerne le SIG. En fouinant autour du filet, j'ai eu du mal à trouver certains des Calculs géographiques pour trouver la distance entre deux endroits donc je voulais les partager ici.

Carte de vol Europe avec grande distance de cercle

La manière simple de calculer une distance entre deux points consiste à utiliser la formule de Pythagore pour calculer l'hypoténuse d'un triangle (A² + B² = C²). Ceci est connu comme le Distance euclidienne.

C'est un début intéressant mais cela ne s'applique pas à la géographie puisque la distance entre les lignes de latitude et de longitude est pas à égale distance une part. À mesure que vous vous rapprochez de l'équateur, les lignes de latitude s'écartent de plus en plus. Si vous utilisez une sorte d'équation de triangulation simple, elle peut mesurer la distance avec précision à un endroit et terriblement fausse dans l'autre, à cause de la courbure de la Terre.

Distance du grand cercle

Les itinéraires parcourus sur de longues distances autour de la Terre sont connus sous le nom de Distance du grand cercle. Autrement dit… la distance la plus courte entre deux points sur une sphère est différente de celle des points sur une carte plane. Combinez cela avec le fait que les lignes de latitude et de longitude ne sont pas équidistantes… et vous avez un calcul difficile.

Voici une fantastique explication vidéo du fonctionnement des Grands Cercles.

La formule Haversine

La distance utilisant la courbure de la Terre est incorporée dans le Formule Haversine, qui utilise la trigonométrie pour tenir compte de la courbure de la terre. Lorsque vous trouvez la distance entre 2 endroits sur terre (à vol d'oiseau), une ligne droite est vraiment un arc.

Ceci est applicable en vol aérien - avez-vous déjà regardé la carte réelle des vols et remarqué qu'ils sont voûtés? C'est parce qu'il est plus court de voler dans une arche entre deux points que directement vers l'emplacement.

PHP: Calculez la distance entre 2 points de latitude et de longitude

Quoi qu'il en soit, voici la formule PHP pour calculer la distance entre deux points (avec la conversion Mile vs Kilomètre) arrondie à deux décimales.

function getDistanceBetweenPointsNew($latitude1, $longitude1, $latitude2, $longitude2, $unit = 'miles') {
  $theta = $longitude1 - $longitude2; 
  $distance = (sin(deg2rad($latitude1)) * sin(deg2rad($latitude2))) + (cos(deg2rad($latitude1)) * cos(deg2rad($latitude2)) * cos(deg2rad($theta))); 
  $distance = acos($distance); 
  $distance = rad2deg($distance); 
  $distance = $distance * 60 * 1.1515; 
  switch($unit) { 
    case 'miles': 
      break; 
    case 'kilometers' : 
      $distance = $distance * 1.609344; 
  } 
  return (round($distance,2)); 
}

SQL: Récupération de tous les enregistrements dans une plage en calculant la distance en miles à l'aide de la latitude et de la longitude

Il est également possible d'utiliser SQL pour effectuer un calcul afin de trouver tous les enregistrements à une distance spécifique. Dans cet exemple, je vais interroger MyTable dans MySQL pour trouver tous les enregistrements inférieurs ou égaux à la variable $ distance (en miles) de ma position à $ latitude et $ longitude:

La requête pour récupérer tous les enregistrements dans un distance en calculant la distance en miles entre deux points de latitude et de longitude sont:

$query = "SELECT *, (((acos(sin((".$latitude."*pi()/180)) * sin((`latitude`*pi()/180)) + cos((".$latitude."*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((".$longitude."- `longitude`)*pi()/180)))) * 180/pi()) * 60 * 1.1515) as distance FROM `table` WHERE distance <= ".$distance."

Vous devrez personnaliser ceci:

  • $ longitude - c'est une variable PHP où je passe la longitude du point.
  • $ latitude - c'est une variable PHP où je passe la longitude du point.
  • $ distance - c'est la distance à laquelle vous aimeriez trouver tous les enregistrements inférieurs ou égaux.
  • table - c'est la table… vous voudrez la remplacer par le nom de votre table.
  • latitude - c'est le champ de votre latitude.
  • longitude - c'est le champ de votre longitude.

SQL: Récupération de tous les enregistrements dans une plage en calculant la distance en kilomètres à l'aide de la latitude et de la longitude

Et voici la requête SQL utilisant des kilomètres dans MySQL:

$query = "SELECT *, (((acos(sin((".$latitude."*pi()/180)) * sin((`latitude`*pi()/180)) + cos((".$latitude."*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((".$longitude."- `longitude`) * pi()/180)))) * 180/pi()) * 60 * 1.1515 * 1.609344) as distance FROM `table` WHERE distance <= ".$distance."

Vous devrez personnaliser ceci:

  • $ longitude - c'est une variable PHP où je passe la longitude du point.
  • $ latitude - c'est une variable PHP où je passe la longitude du point.
  • $ distance - c'est la distance à laquelle vous aimeriez trouver tous les enregistrements inférieurs ou égaux.
  • table - c'est la table… vous voudrez la remplacer par le nom de votre table.
  • latitude - c'est le champ de votre latitude.
  • longitude - c'est le champ de votre longitude.

J'ai utilisé ce code dans une plate-forme de cartographie d'entreprise que nous avons utilisée pour un magasin de détail avec plus de 1,000 emplacements à travers l'Amérique du Nord et cela a fonctionné à merveille.

76 Commentaires

  1. 1

    Merci beaucoup pour le partage. C'était un travail de copier-coller facile et fonctionne très bien. Vous m'avez fait gagner beaucoup de temps.
    FYI pour toute personne portant sur C:
    double deg2rad (double deg) {retour deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Très belle pièce d'affichage - fonctionnait très bien - je n'avais qu'à changer le nom de la table contenant le lat-long. Cela fonctionne assez vite pour .. J'ai un nombre raisonnablement petit de lat-longs (<400) mais je pense que cela évoluerait bien. Beau site aussi - je viens de l'ajouter à mon compte del.icio.us et je reviendrai régulièrement.

  3. 4
  4. 5

    J'ai cherché toute la journée des calculs de distance et j'ai trouvé l'algorithme harversine, grâce à vous pour avoir donné l'exemple sur la façon de le mettre dans une instruction sql. Merci et salue, Daniel

  5. 8

    Je pense que votre SQL a besoin d'une déclaration positive.
    au lieu de WHERE distance <= $ distance dont vous pourriez avoir besoin
    utiliser HAVING distance <= $ distance

    sinon merci de m'avoir économisé beaucoup de temps et d'énergie.

  6. 10
  7. 11
  8. 12

    Merci beaucoup d'avoir partagé ce code. Cela m'a fait gagner beaucoup de temps de développement. Merci également à vos lecteurs d'avoir signalé qu'une instruction HAVING est nécessaire pour MySQL 5.x. Très utile.

  9. 14
  10. 15

    Bonjour,

    Une autre question. Existe-t-il une formule pour les chaînes NMEA comme celle ci-dessous?

    1342.7500, N, 10052.2287, E

    $GPRMC,032731.000,A,1342.7500,N,10052.2287,E,0.40,106.01,101106,,*0B

    Merci,
    Harry

  11. 16

    J'ai également constaté que WHERE ne fonctionnait pas pour moi. Je l'ai changé en HAVING et tout fonctionne parfaitement. Au début, je n'ai pas lu les commentaires et les ai réécrits en utilisant un select imbriqué. Les deux fonctionneront très bien.

  12. 17
  13. 18

    Incroyablement utile, merci beaucoup! J'avais des problèmes avec le nouveau «HAVING», plutôt que «WHERE», mais une fois que j'ai lu les commentaires ici (après environ une demi-heure à grincer des dents de frustration = P), je l'ai bien fait fonctionner. Merci ^ _ ^

  14. 19
  15. 20

    Gardez à l'esprit qu'une instruction select comme celle-ci sera très intense en calcul et donc lente. Si vous avez beaucoup de ces requêtes, cela peut enliser les choses assez rapidement.

    Une approche beaucoup moins intense consiste à exécuter une première sélection (brute) en utilisant une zone CARRÉE définie par une distance calculée, c'est-à-dire «sélectionner * à partir de nomtable où latitude entre lat1 et lat2 et longitude entre lon1 et lon2». lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, similaire à lon. latdiff ~ = distance / 111 (pour km), ou distance / 69 pour miles car 1 degré de latitude équivaut à ~ 111 km (légère variation puisque la terre est légèrement ovale, mais suffisante à cet effet). londiff = distance / (abs (cos (deg2rad (latitude)) * 111)) - ou 69 pour les miles (vous pouvez en fait prendre un carré légèrement plus grand pour tenir compte des variations). Ensuite, prenez le résultat de cela et insérez-le dans la sélection radiale. N'oubliez pas de prendre en compte les coordonnées hors limites - c'est-à-dire que la plage de longitude acceptable est de -180 à +180 et la plage de latitude acceptable est de -90 à +90 - au cas où votre latdiff ou londiff serait en dehors de cette plage . Notez que dans la plupart des cas, cela peut ne pas être applicable car il n'affecte que les calculs sur une ligne traversant l'océan Pacifique d'un pôle à l'autre, bien qu'il coupe une partie de la chukotka et une partie de l'Alaska.

    Ce que nous accomplissons, c'est une réduction significative du nombre de points par rapport auxquels vous effectuez ce calcul. Si vous avez un million de points globaux dans la base de données répartis à peu près uniformément et que vous souhaitez effectuer une recherche dans un rayon de 100 km, votre première recherche (rapide) est d'une superficie de 10000 km20 et donnera probablement environ 500 résultats (sur la base d'une distribution uniforme sur un surface d'environ 20 millions de kmXNUMX), ce qui signifie que vous exécutez le calcul de distance complexe XNUMX fois pour cette requête au lieu d'un million de fois.

    • 21
      • 22

        Des conseils fantastiques! En fait, j'ai travaillé avec un développeur qui a écrit une fonction qui a tiré le carré intérieur, puis une fonction récursive qui a fait des «carrés» autour du périmètre pour inclure et exclure les points restants. Le résultat était un résultat incroyablement rapide - il pouvait évaluer des millions de points en microsecondes.

        Mon approche ci-dessus est définitivement «brute» mais capable. Merci encore!

        • 23

          Doug,

          J'ai essayé d'utiliser mysql et php pour évaluer si un point lat long se trouve dans un polygone. Savez-vous si votre ami développeur a publié des exemples sur la façon d'accomplir cette tâche. Ou connaissez-vous de bons exemples. Merci d'avance.

  16. 24

    Salut à tous, voici ma déclaration SQL de test:

    SELECT DISTINCT area_id, (
    (
    (
    acos( sin( ( 13.65 * pi( ) /180 ) ) * sin( (
    `lat_dec` * pi( ) /180 ) ) + cos( ( 13.65 * pi( ) /180 ) ) * cos( (
    `lat_dec` * pi( ) /180 )
    ) * cos( (
    ( 51.02 - `lon_dec` ) * pi( ) /180 )
    )
    )
    ) *180 / pi( )
    ) *60 * 1.1515 * 1.609344
    ) AS distance
    FROM `post_codes` WHERE distance <= 50

    et Mysql me dit que la distance, n'existe pas en tant que colonne, je peux utiliser order by, je peux le faire sans WHERE, et ça marche, mais pas avec…

  17. 26

    C'est génial, mais c'est comme les oiseaux volent. Ce serait formidable d'essayer d'incorporer l'API google maps à cela (peut-être en utilisant des routes, etc.) Juste pour donner une idée en utilisant un autre moyen de transport. Il me reste encore à faire une fonction de recuit simulé en PHP qui pourrait offrir une solution efficace au problème du voyageur de commerce. Mais je pense que je pourrai peut-être réutiliser une partie de votre code pour le faire.

  18. 27
  19. 28

    Bon article! J'ai trouvé beaucoup d'articles décrivant comment calculer la distance entre deux points mais je cherchais vraiment l'extrait SQL.

  20. 29
  21. 30
  22. 31
  23. 32
  24. 36

    2 jours de recherche pour enfin trouver cette page qui résout mon problème. On dirait que je ferais mieux de sortir mon WolframAlpha et de réviser mes maths. Le changement de WHERE à HAVING a mon script en ordre de marche. MERCI

  25. 37
    • 38

      Merci Georgi. J'ai continué à obtenir la colonne «distance» introuvable. Une fois que j'ai changé le WHERE en HAVING, cela a fonctionné comme un charme!

  26. 39

    J'aurais aimé que ce soit la première page que j'aie trouvée à ce sujet. Après avoir essayé de nombreuses commandes différentes, c'était la seule à fonctionner correctement, et avec des modifications minimes nécessaires pour s'adapter à ma propre base de données.
    Merci beaucoup!

  27. 40

    J'aurais aimé que ce soit la première page que j'aie trouvée à ce sujet. Après avoir essayé de nombreuses commandes différentes, c'était la seule à fonctionner correctement, et avec des modifications minimes nécessaires pour s'adapter à ma propre base de données.
    Merci beaucoup!

  28. 41
  29. 42
  30. 43
  31. 45
  32. 46
  33. 47
  34. 49
  35. 50
  36. 52

    Merci Douglas, la requête SQL est exactement ce dont j'avais besoin, et j'ai pensé que je devrais l'écrire moi-même. Vous m'avez sauvé de possiblement des heures de courbe d'apprentissage de la latitude et de la longitude!

  37. 53
  38. 55
  39. 56
  40. 58

    merci d'avoir publié cet article utile,  
    mais pour une raison quelconque, j'aimerais demander
    comment obtenir la distance entre les coords dans mysql db et les coords insérés dans php par l'utilisateur?
    pour décrire plus clairement:
    1. l'utilisateur doit insérer [id] pour sélectionner les données spécifiées dans la base de données et les coordonnées de l'utilisateur lui-même
    2.le fichier php récupère les données cibles (coords) en utilisant [id], puis calcule la distance entre l'utilisateur et le point cible

    ou peut simplement obtenir la distance du code ci-dessous?

    $ qry = “SELECT *, (((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((“. $ latitude. "* pi () / 180)) * cos ((` Latitude` * pi () / 180)) * cos ((". $ longitude." - `Longitude`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) comme distance FROM `MyTable` WHERE distance> =". $ Distance. " >>>> puis-je «prendre» la distance d'ici?
    Merci encore,
    Timmy S

  41. 60

    ok, tout ce que j'ai essayé ne fonctionne pas. Je veux dire, ce que j'ai fonctionne, mais les distances sont très éloignées.

    Quelqu'un pourrait-il voir ce qui ne va pas avec ce code?

    if (isset ($ _ POST ['soumis'])) {$ z = $ _POST ['zipcode']; $ r = $ _POST ['rayon']; echo “Résultats pour“. $ z; $ sql = mysql_query («SELECT DISTINCT m.zipcode, m.MktName, m.LocAddSt, m.LocAddCity, m.LocAddState, m.x1, m.y1, m.verified, z1.lat, z2.lon, z1. city, z1.state FROM mrk m, zip z1, zip z2 O m.zipcode = z1.zipcode ET z2.zipcode = $ z AND (3963 * acos (truncate (sin (z2.lat / 57.2958) * sin (m. y1 / 57.2958) + cos (z2.lat / 57.2958) * cos (m.y1 / 57.2958) * cos (m.x1 / 57.2958 - z2.lon / 57.2958), 8))) <= $ r ") ou die (mysql_error ()); while ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. ""; $ store = $ row ['LocAddSt']. ””; $ store. = $ row ['LocAddCity']. »,«. $ row ['LocAddState']. » “. $ Row ['code postal']; $ latitude1 = $ ligne ['lat']; $ longitude1 = $ ligne ['lon']; $ latitude2 = $ ligne ['y1']; $ longitude2 = $ ligne ['x1']; $ ville = $ row ['ville']; $ state = $ row ['état']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = distance ($ lat1, $ lon1, $ lat2, $ lon2); $ vérifié = $ row ['vérifié']; if ($ vérifié == '1') {echo “”; echo “”. $ store. ””; echo $ dis. " à des miles"; écho ""; } else {echo “”. $ store. ””; echo $ dis. " à des miles"; écho ""; }}}

    mon code functions.php
    function getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi') {$ theta = $ longitude1 - $ longitude2; $ distance = (sin (deg2rad ($ latitude1)) * sin (deg2rad ($ latitude2))) + (cos (deg2rad ($ latitude1)) * cos (deg2rad ($ latitude2)) * cos (deg2rad ($ theta)) ); $ distance = acos ($ distance); $ distance = rad2deg ($ distance); $ distance = $ distance * 60 * 1.1515; commutateur ($ unit) {case 'Mi': break; cas 'Km': $ distance = $ distance * 1.609344; } retour (rond ($ distance, 2)); }

    Merci d'avance

  42. 61
  43. 62

    Salut Douglas, excellent article. J'ai trouvé votre explication des concepts géographiques et du code vraiment intéressante. Ma seule suggestion serait d'espacer et d'indenter le code pour l'affichage (comme Stackoverflow, par exemple). Je comprends que vous voulez économiser de l'espace, mais l'espacement / l'indentation du code conventionnel me faciliterait beaucoup la lecture et la dissection en tant que programmeur. Quoi qu'il en soit, c'est une petite chose. Continuez ce bon travail.

  44. 64
  45. 65
  46. 66
  47. 67
  48. 68
  49. 69
  50. 70

    il semble plus rapide (mysql 5.9) d'utiliser deux fois la formule dans le select et où:
    $ formule = "(((acos (sin ((". $ latitude. "* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((". $ latitude. "* Pi () / 180)) * cos ((` Latitude` * pi () / 180)) * cos ((". $ Longitude." - `Longitude`) * pi () / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) »;
    $ sql = 'SELECT *,'. $ formule. ' comme distance FROM table WHERE '.. $ formule.' <= '. $ distance;

  51. 71
  52. 72

    Merci beaucoup pour le cisaillement de cet article. C'est très utile.
    PHP a d'abord été créé comme une simple plate-forme de script appelée «page d'accueil personnelle». Aujourd'hui, PHP (l'abréviation de Hypertext Preprocessor) est une alternative à la technologie ASP (Active Server Pages) de Microsoft.

    PHP est un langage open source côté serveur utilisé pour créer des pages Web dynamiques. Il peut être intégré au HTML. PHP est généralement utilisé en conjonction avec une base de données MySQL sur les serveurs Web Linux / UNIX. C'est probablement le langage de script le plus populaire.

  53. 73

    J'ai trouvé la solution ci-dessus ne fonctionnant pas correctement.
    J'ai besoin de changer pour:

    $ qqq = "SELECT *, (((acos (sin ((". $ latitude. "* pi () / 180)) * sin ((` latt` * pi () / 180)) + cos ((". $ latitude. "* pi () / 180)) * cos ((` latt` * pi () / 180)) * cos ((". $ longitude." - `longt`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515) en tant que distance FROM `register`“;

  54. 75
  55. 76

    Bonjour, j'aurai vraiment besoin de votre aide à ce sujet.

    J'ai fait une demande d'obtention à mon serveur Web http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ latitude
    -2.23389 = $ longitude
    et 20 = la distance que je veux récupérer

    Cependant, en utilisant votre formule, il récupère toutes les lignes de ma base de données

    $ results = DB :: select (DB :: raw ("SELECT *, (((acos (sin ((". $ latitude. "* pi () / 180)) * sin ((lat * pi () / 180 )) + cos ((“. $ latitude.” * pi () / 180)) * cos ((lat * pi () / 180)) * cos (((“. $ longitude.” - lng) * pi ( ) / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) en tant que distance DES marqueurs HAVING distance> = ". $ Distance));

    [{"Id": 1, "name": "Frankie Johnnie & Luigo Too", "address": "939 W El Camino Real, Mountain View, Californie", "lat": 37.386337280273, "lng": - 122.08582305908, "Distance": 16079.294719663}, {"id": 2, "nom": "Amici's East Coast Pizzeria", "adresse": "790 Castro St, Mountain View, Californie", "lat": 37.387138366699, "lng": -122.08323669434, "distance": 16079.175940152}, {"id": 3, "name": "Kapp's Pizza Bar & Grill", "adresse": "191 Castro St, Mountain View, CA", "lat": 37.393886566162, "Lng": - 122.07891845703, "distance": 16078.381373826}, {"id": 4, "name": "Round Table Pizza: Mountain View", "address": "570 N Shoreline Blvd, Mountain View, CA", "Lat": 37.402652740479, "lng": - 122.07935333252, "distance": 16077.420540582}, {"id": 5, "name": "Tony & Alba's Pizza & Pasta", "address": "619 Escuela Ave, Mountain View, CA "," lat ": 37.394012451172," lng ": - 122.09552764893," distance ": 16078.563225154}, {" id ": 6," name ":" Origan's Wood-Fired Pizza "," address ":" 4546 El Camino Real, Los Altos, Californie "," lat ": 37.401725769043," lng ": - 122.11464691162," distance ": 16077.937560795}, {" id ": 7," name ":" The bars and grills "," address ":" 24 Whiteley Street, Manchester "," lat ": 53.485118865967," lng ": - 2.1828699111938," distance ": 8038.7620112314}]

    Je veux récupérer seulement des lignes avec 20 miles mais cela apporte toutes les lignes. S'il te plaît, qu'est-ce que je fais de mal

Que pensez-vous?

Ce site utilise Akismet pour réduire les spams. Découvrez comment sont traitées les données de vos commentaires..