PHP i SQL: calcular o consultar la gran distància del cercle entre els punts de latitud i longitud amb la fórmula Haversine

Fórmula Haversine: calcula la gran distància del cercle amb PHP o MySQL

Aquest mes he estat programant força en PHP i MySQL pel que fa als SIG. Buscant per la xarxa, en realitat em va costar trobar alguns dels Càlculs geogràfics per trobar la distància entre dues ubicacions, així que volia compartir-les aquí.

Mapa de vol Europa amb gran distància de cercle

La forma senzilla de calcular la distància entre dos punts és utilitzar la fórmula pitagòrica per calcular la hipotenusa d’un triangle (A² + B² = C²). Això es coneix com el Distància euclidiana.

Aquest és un començament interessant, però no s’aplica a Geografia ja que la distància entre les línies de latitud i longitud és ni una distància igual a part. A mesura que s’acosta a l’equador, les línies de latitud s’allunyen. Si utilitzeu algun tipus d’equació de triangulació simple, pot mesurar la distància amb precisió en un lloc i terriblement equivocada a l’altra, a causa de la curvatura de la Terra.

Distància Gran Cercle

Les rutes que es recorren a llargues distàncies al voltant de la Terra es coneixen com a Distància Gran Cercle. És a dir ... la distància més curta entre dos punts d'una esfera és diferent dels punts d'un mapa pla. Combineu-ho amb el fet que les línies de latitud i longitud no són equidistants ... i teniu un càlcul difícil.

Aquí teniu una fantàstica explicació en vídeo de com funcionen els grans cercles.

La Fórmula Haversine

La distància que utilitza la curvatura de la Terra s'incorpora a la Fórmula Haversine, que utilitza la trigonometria per permetre la curvatura de la terra. Quan trobeu la distància entre dos llocs de la terra (en línia recta), la línia recta és realment un arc.

Això és aplicable al vol aeri. Alguna vegada heu mirat el mapa real dels vols i heu observat que estan arquejats? Això es deu al fet que és més curt volar en un arc entre dos punts que directament a la ubicació.

PHP: Calculeu la distància entre 2 punts de latitud i longitud

De totes maneres, aquí teniu la fórmula PHP per calcular la distància entre dos punts (juntament amb la conversió Milles vs. Kilòmetres) arrodonides a dos decimals.

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: Recuperació de tots els registres dins d'un rang mitjançant el càlcul de la distància en quilòmetres mitjançant latitud i longitud

També és possible utilitzar SQL per fer un càlcul per trobar tots els registres a una distància específica. En aquest exemple, vaig a consultar MyTable a MySQL per trobar tots els registres que siguin inferiors o iguals a la distància variable $ (en milles) de la meva ubicació a $ latitud i $ longitud:

La consulta per recuperar tots els registres d'un determinat document distància calculant la distància en milles entre dos punts de latitud i longitud són:

$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."

Haureu de personalitzar-ho:

  • $ longitud - Aquesta és una variable PHP on estic passant la longitud del punt.
  • $ latitud - Aquesta és una variable PHP on estic passant la longitud del punt.
  • $ distància - aquesta és la distància a la qual voldríeu trobar tots els registres menys o iguals.
  • taula - Aquesta és la taula ... voldreu substituir-la pel nom de la vostra taula.
  • latitud - aquest és el camp de la vostra latitud.
  • longitud - aquest és el camp de la vostra longitud.

SQL: Recuperació de tots els registres dins d'un rang mitjançant el càlcul de la distància en quilòmetres mitjançant latitud i longitud

I aquí teniu la consulta SQL que utilitza quilòmetres a 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."

Haureu de personalitzar-ho:

  • $ longitud - Aquesta és una variable PHP on estic passant la longitud del punt.
  • $ latitud - Aquesta és una variable PHP on estic passant la longitud del punt.
  • $ distància - aquesta és la distància a la qual voldríeu trobar tots els registres menys o iguals.
  • taula - Aquesta és la taula ... voldreu substituir-la pel nom de la vostra taula.
  • latitud - aquest és el camp de la vostra latitud.
  • longitud - aquest és el camp de la vostra longitud.

He utilitzat aquest codi en una plataforma de mapes empresarials que hem utilitzat per a una botiga minorista amb més de 1,000 ubicacions a tota Amèrica del Nord i ha funcionat molt bé.

77 Comentaris

  1. 1

    Moltes gràcies per compartir. Va ser una tasca fàcil de copiar i enganxar i funciona molt bé. M’heu estalviat molt de temps.
    FYI per a qualsevol persona que porti a C:
    doble deg2rad (doble deg) {retorn deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Un tros molt bonic de publicació: ha funcionat molt bé, només he hagut de canviar el nom de la taula que conté el llatí. Funciona bastant ràpidament. Tinc un nombre raonablement reduït de lat-longs (<400), però crec que això escalaria molt bé. Bonic lloc també: l'acabo d'afegir al meu compte del.icio.us i tornaré a comprovar-ho regularment.

  3. 4
  4. 5

    Vaig buscar durant tot el dia els càlculs de la distància i vaig trobar l'algoritme de conversió, gràcies a vosaltres per donar l'exemple de com posar-lo en una declaració sql. Gràcies i salutacions, Daniel

  5. 8

    Crec que el vostre SQL necessita una declaració de tenir.
    en lloc de WHERE distance <= $ distance que potser necessiteu
    utilitzeu HAVING distance <= $ distance

    en cas contrari, gràcies per estalviar-me un munt de temps i energia.

  6. 10
  7. 11
  8. 12

    Moltes gràcies per compartir aquest codi. Em va estalviar molt de temps de desenvolupament. A més, gràcies als vostres lectors per haver assenyalat que és necessària una declaració HAVING per a MySQL 5.x. Molt util.

  9. 14
  10. 15

    Hola,

    Una altra pregunta. Hi ha una fórmula per a les cadenes NMEA com la següent?

    1342.7500, N, 10052.2287, E

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

    Gràcies,
    Harry

  11. 16

    També vaig trobar que WHERE no funcionava per a mi. L'he canviat a HAVING i tot funciona perfectament. Al principi no vaig llegir els comentaris i els vaig tornar a escriure amb una selecció imbricada. Tots dos funcionaran bé.

  12. 17
  13. 18

    Molt útil, moltes gràcies. Tenia alguns problemes amb el nou "TENIR", en lloc de "ON", però un cop llegits els comentaris aquí (després de mitja hora aprenent les dents de frustració = P), vaig aconseguir que funcionés bé. Gràcies ^ _ ^

  14. 19
  15. 20

    Tingueu en compte que una afirmació tan selecta serà molt intensa i, per tant, lenta. Si teniu moltes d’aquestes consultes, pot enfonsar-se bastant ràpidament.

    Un enfocament molt menys intens consisteix a executar una primera selecció (crua) mitjançant una àrea SQUARE definida per una distància calculada, és a dir, “seleccionar * del nom de taula on la latitud entre lat1 i lat2 i la longitud entre lon1 i lon2”. lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, similar amb lon. latdiff ~ = distància / 111 (per km), o distància / 69 per milles, ja que 1 grau de latitud és de ~ 111 km (lleugera variació ja que la terra és lleugerament ovalada, però suficient per a aquest propòsit). londiff = distància / (abs (cos (deg2rad (latitud)) * 111)) - o 69 per milles (en realitat es pot prendre un quadrat una mica més gran per tenir en compte les variacions). A continuació, agafeu el resultat i introduïu-lo a la selecció radial. No us oblideu de tenir en compte les coordenades fora dels límits (és a dir, l’interval de longitud acceptable és de -180 a +180 i l’interval de latitud acceptable és de -90 a +90) en cas que el vostre latdiff o londres estigui fora d’aquest interval. . Tingueu en compte que en la majoria dels casos això pot no ser aplicable, ja que només afecta els càlculs sobre una línia a través de l'oceà Pacífic de pol a pol, tot i que interseca part de chukotka i part d'alaska.

    El que aconseguim amb això és una reducció significativa del nombre de punts contra els quals feu aquest càlcul. Si teniu un milió de punts globals a la base de dades distribuïts aproximadament de manera uniforme i voleu cercar a menys de 100 km, la vostra primera cerca (ràpida) té una superfície de 10000 km quadrats i probablement donarà uns 20 resultats (basats en una distribució uniforme en superfície d’uns 500 milions de quilòmetres quadrats), cosa que significa que executeu el càlcul de la distància complexa 20 vegades per a aquesta consulta en lloc d’un milió de vegades.

    • 21
      • 22

        Un consell fantàstic! De fet, vaig treballar amb un desenvolupador que va escriure una funció que tirava del quadrat interior i després una funció recursiva que feia "quadrats" al voltant del perímetre per incloure i excloure els punts restants. El resultat va ser un resultat increïblement ràpid: va poder avaluar milions de punts en microsegons.

        El meu enfocament anterior és definitivament "cru" però capaç. Gràcies de nou!

        • 23

          Doug,

          He estat intentant utilitzar mysql i php per avaluar si un punt llarg lat es troba dins d'un polígon. Sabeu si el vostre amic desenvolupador va publicar algun exemple sobre com dur a terme aquesta tasca. O coneixeu bons exemples? Gràcies per endavant.

  16. 24

    Hola a tots, aquesta és la meva declaració SQL de prova:

    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

    i Mysql em diu que la distància no existeix com a columna, puc fer servir ordre per, ho puc fer sense ON, i funciona, però no amb ella ...

  17. 26

    Això és fantàstic, però és igual que volen els ocells. Seria fantàstic provar d’incorporar l’API de google maps a això d’alguna manera (potser utilitzant carreteres, etc.) Només per fer-vos una idea utilitzant una forma de transport diferent. Encara he de fer una funció de recuit simulada en PHP que pugui oferir una solució eficient al problema del venedor ambulant. Però crec que puc ser capaç de reutilitzar part del vostre codi per fer-ho.

  18. 27
  19. 28

    Bon article! Vaig trobar molts articles que descrivien com calcular la distància entre dos punts, però realment buscava el fragment SQL.

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

    2 dies de recerca per trobar finalment aquesta pàgina que resol el meu problema. Sembla que és millor que tregui el meu WolframAlpha i que explori les meves matemàtiques. El canvi de WHERE a HAVING té el meu guió en bon estat. GRÀCIES

  25. 37
    • 38

      Gràcies Georgi. He seguit aconseguint que no es trobés la "distància" de la columna. Una vegada que he canviat el WHERE to HAVING, ha funcionat com un encant!

  26. 39

    M’agradaria que aquesta fos la primera pàgina que hi trobés. Després de provar moltes ordres diferents, aquest va ser l'únic que va funcionar correctament i amb els canvis mínims necessaris per adaptar-se a la meva pròpia base de dades.
    Moltes gràcies!

  27. 40

    M’agradaria que aquesta fos la primera pàgina que hi trobés. Després de provar moltes ordres diferents, aquest va ser l'únic que va funcionar correctament i amb els canvis mínims necessaris per adaptar-se a la meva pròpia base de dades.
    Moltes gràcies!

  28. 41
  29. 42
  30. 43
  31. 45
  32. 46
  33. 47

    Sé que aquesta fórmula funciona, però no puc veure on es té en compte el radi de la terra. Algú em pot il·luminar, si us plau?

  34. 49
  35. 50
  36. 52
  37. 53
  38. 55
  39. 56
  40. 58

    gràcies per publicar aquest útil article,  
    però per alguna raó m'agradaria preguntar-ho
    Com obtenir la distància entre els coords dins de mysql db i els coords inserits a php per l'usuari?
    per descriure més clarament:
    1. L'usuari ha d'inserir [id] per seleccionar les dades especificades de db i els mateixos codis de l'usuari
    2. El fitxer php obté les dades de destinació (coords) mitjançant [id] i després calcula la distància entre l'usuari i el punt de destinació

    o simplement es pot obtenir distància del codi següent?

    $ 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) com a distància DES DE `La meva taula` WHERE distance> =“. $ Distance. ” >>>> puc "treure" la distància d'aquí?
    gràcies de nou,
    Timmy S

  41. 60

    d'acord, tot el que he provat no funciona. Vull dir, el que tinc funciona, però les distàncies estan molt lluny.

    Algú podria veure què passa amb aquest codi?

    if (isset ($ _ POST ['enviat'])) {$ z = $ _POST ['codi postal']; $ r = $ _POST ['radi']; eco "Resultats per a". $ 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 WHERE m.zipcode = z1.zipcode AND 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 ") o morir (mysql_error ()); while ($ fila = mysql_fetch_array ($ sql)) {$ store1 = $ fila ['MktName']. "”; $ store = $ fila ['LocAddSt']. ””; $ store. = $ row ['LocAddCity']. ",". $ row ['LocAddState']. " “. $ Fila ['codi postal']; $ latitude1 = $ fila ['lat']; $ longitud1 = $ fila ['lon']; $ latitude2 = $ fila ['y1']; $ longitud2 = $ fila ['x1']; $ ciutat = $ fila ['ciutat']; $ estat = $ fila ['estat']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = distància ($ lat1, $ lon1, $ lat2, $ lon2); $ verificat = $ fila ['verificat']; if ($ Verified == '1') {echo “”; eco "". $ store. ""; eco $ dis. " molt lluny"; ressò ""; } else {echo "". $ store. ""; eco $ dis. " molt lluny"; ressò ""; }}}

    el meu codi functions.php
    function getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi') {$ theta = $ longitude1 - $ longitude2; $ distància = (sin (deg2rad ($ latitude1)) * sin (deg2rad ($ latitude2))) + (cos (deg2rad ($ latitude1)) * cos (deg2rad ($ latitude2)) * cos (deg2rad ($ theta)) ); $ distància = acos ($ distància); $ distància = rad2deg ($ distància); $ distància = $ distància * 60 * 1.1515; switch ($ unit) {case 'Mi': break; cas 'Km': $ distance = $ distance * 1.609344; } return (round ($ distance, 2)); }

    Gràcies per endavant

  42. 61
  43. 62

    Ei Douglas, gran article. Em va semblar realment interessant la vostra explicació sobre els conceptes geogràfics i el codi. El meu únic suggeriment seria espaiar i sagnar el codi per mostrar (com Stackoverflow, per exemple). Entenc que voleu estalviar espai, però l’espaiat / sagnat de codi convencional em facilitaria la lectura i la dissecció, com a programador. De tota manera, això és poc. Continueu així.

  44. 64
  45. 65

    aquí, mentre s'utilitza amb la funció, estem obtenint un tipus de distància ... mentre s'utilitza la consulta sobre el seu altre tipus de distància

  46. 66
  47. 67
  48. 68
  49. 69
  50. 70

    sembla més ràpid (mysql 5.9) utilitzar el doble de la fórmula a la selecció i on:
    $ formula = “((((acos (sin ((.. $ latitude.” * pi () / 180)) * sin ((`Latitude` * pi () / 180)) + cos ((“. $ latitude. ”* Pi () / 180)) * cos ((` Latitud` * pi () / 180)) * cos (((“. $ Longitud.” - `Longitud`) * pi () / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) ”;
    $ sql = 'SELECT *,'. $ formula. ' com a distància DE la taula ON '.. $ formula.' <= '. $ distància;

  51. 71
  52. 72

    Moltes gràcies per tallar aquest article. És molt útil.
    PHP es va crear al principi com una plataforma de seqüència d’ordres simple anomenada “Pàgina inicial personal”. Avui dia PHP (l'abreviació de Hypertext Preprocessor) és una alternativa a la tecnologia Active Server Pages (ASP) de Microsoft.

    PHP és un llenguatge de servidor de codi obert que s’utilitza per crear pàgines web dinàmiques. Es pot incrustar a HTML. Normalment s’utilitza PHP juntament amb una base de dades MySQL en servidors web Linux / UNIX. Probablement és el llenguatge de seqüència d’ordres més popular.

  53. 73

    He trobat la solució anterior que no funciona correctament.
    He de canviar a:

    $ qqq = “SELECT *, (((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((` latt` * pi () / 180)) + cos ((”. $ latitud. "* pi () / 180)) * cos ((` latt` * pi () / 180)) * cos (((". $ longitud." - `longt ') * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515) com a distància DES de `registre`“;

  54. 75
  55. 76

    Hola, si us plau, realment necessitaré la vostra ajuda.

    He fet una sol·licitud d'obtenció al meu servidor web http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ latitud
    -2.23389 = $ longitud
    i 20 = la distància que vull recuperar

    Tot i que usant la fórmula, recupera totes les files del meu db

    $ resultats = DB :: select (DB :: raw ("SELECT *, (((acos (sin ((". $ latitude. "* pi () / 180)) * sin ((lat * pi () / 180 )) + cos ((". $ latitude." * pi () / 180)) * cos ((lat * pi () / 180)) * cos (((". $ longitud." - lng) * pi ( ) / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) com a distància DES DE marcadors QUE TENEN distància> = “. $ Distància));

    [{"Id": 1, "name": "Frankie Johnnie & Luigo Too", "address": "939 W El Camino Real, Mountain View, CA", "lat": 37.386337280273, "lng": - 122.08582305908, "Distance": 16079.294719663}, {"id": 2, "name": "Amici's East Coast Pizzeria", "address": "790 Castro St, Mountain View, CA", "lat": 37.387138366699, "lng": -122.08323669434, "distance": 16079.175940152}, {"id": 3, "name": "Kapp's Pizza Bar & Grill", "address": "191 Castro St, Mountain View, CA", "lat": 37.393886566162, "Lng": - 122.07891845703, "distance": 16078.381373826}, {"id": 4, "name": "Pizza de taula rodona: 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 ":" Oregano's Wood-Fired Pizza "," address ":" 4546 El Camino Real, Los Altos, CA "," 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}]

    Vull recuperar només files amb 20 milles, però inclou totes les files. Si us plau, què faig malament?

  56. 77

    Estic buscant una consulta similar, però he intensificat una mica; en resum, es tracta d'agrupar totes les coordenades a menys de 2 milles de cada coordenada i després comptar quantes coordenades de cada grup i generar només un grup que tingui més coordenades, fins i tot si teniu més d'un grup entre els grups que tenen el major nombre de coordenades - simplement publiqueu el grup aleatori dels grups amb el mateix nombre més gran -

Què et sembla?

Aquest lloc utilitza Akismet per reduir el correu no desitjat. Esbrineu com es processa el vostre comentari.