PHP e SQL: calcola o interroga la distanza del cerchio tra i punti di latitudine e longitudine con la formula di Haversine

Formula Haversine - Calcola la distanza del cerchio grande con PHP o MySQL

Questo mese ho programmato un bel po 'in PHP e MySQL rispetto a GIS. Snooping in rete, in realtà ho avuto difficoltà a trovare alcuni dei file Calcoli geografici per trovare la distanza tra due posizioni, quindi ho voluto condividerle qui.

Mappa di volo in Europa con grande distanza del cerchio

Il modo più semplice per calcolare una distanza tra due punti è utilizzare la formula pitagorica per calcolare l'ipotenusa di un triangolo (A² + B² = C²). Questo è noto come Distanza euclidea.

È un inizio interessante ma non si applica alla geografia poiché la distanza tra le linee di latitudine e longitudine lo è non una distanza uguale a parte. Man mano che ti avvicini all'equatore, le linee di latitudine si allontanano ulteriormente. Se usi una sorta di semplice equazione di triangolazione, potresti misurare la distanza con precisione in una posizione e terribilmente sbagliata nell'altra, a causa della curvatura della Terra.

Grande distanza dal cerchio

Le rotte percorse per lunghe distanze intorno alla Terra sono note come Grande distanza dal cerchio. Cioè ... la distanza più breve tra due punti su una sfera è diversa dai punti in una mappa piatta. Combina questo con il fatto che le linee di latitudine e longitudine non sono equidistanti ... e hai un calcolo difficile.

Ecco un fantastico video che spiega come funzionano le Great Circles.

La formula Haversine

La distanza utilizzando la curvatura della Terra è incorporata nel Formula Haversine, che utilizza la trigonometria per consentire la curvatura della terra. Quando trovi la distanza tra 2 luoghi sulla terra (in linea d'aria), una linea retta è davvero un arco.

Questo è applicabile in volo aereo: hai mai guardato la mappa dei voli e notato che sono ad arco? Questo perché è più breve volare in un arco tra due punti che direttamente nella posizione.

PHP: calcola la distanza tra 2 punti di latitudine e longitudine

Ad ogni modo, ecco la formula PHP per calcolare la distanza tra due punti (insieme alla conversione Miglio vs Chilometro) arrotondata a due cifre decimali.

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: recupero di tutti i record entro un intervallo calcolando la distanza in miglia utilizzando latitudine e longitudine

È anche possibile utilizzare SQL per eseguire un calcolo per trovare tutti i record entro una distanza specifica. In questo esempio, interrogherò MyTable in MySQL per trovare tutti i record che sono inferiori o uguali alla variabile $ distanza (in miglia) dalla mia posizione a $ latitude e $ longitude:

La query per il recupero di tutti i record all'interno di uno specifico distanza calcolando la distanza in miglia tra due punti di latitudine e longitudine sono:

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

Dovrai personalizzare questo:

  • $ longitudine - questa è una variabile PHP in cui sto passando la longitudine del punto.
  • $ latitudine - questa è una variabile PHP in cui sto passando la longitudine del punto.
  • $ distanza - questa è la distanza a cui vorresti trovare tutti i record minore o uguale.
  • tavolo - questa è la tabella ... ti consigliamo di sostituirla con il nome della tua tabella.
  • latitudine - questo è il campo della tua latitudine.
  • longitudine - questo è il campo della tua longitudine.

SQL: recupero di tutti i record all'interno di un intervallo calcolando la distanza in chilometri utilizzando latitudine e longitudine

Ed ecco la query SQL che utilizza i chilometri in 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."

Dovrai personalizzare questo:

  • $ longitudine - questa è una variabile PHP in cui sto passando la longitudine del punto.
  • $ latitudine - questa è una variabile PHP in cui sto passando la longitudine del punto.
  • $ distanza - questa è la distanza a cui vorresti trovare tutti i record minore o uguale.
  • tavolo - questa è la tabella ... ti consigliamo di sostituirla con il nome della tua tabella.
  • latitudine - questo è il campo della tua latitudine.
  • longitudine - questo è il campo della tua longitudine.

Ho utilizzato questo codice in una piattaforma di mappatura aziendale che abbiamo utilizzato per un negozio al dettaglio con oltre 1,000 sedi in tutto il Nord America e ha funzionato magnificamente.

Commenti

  1. 1

    Grazie mille per la condivisione. Questo è stato un facile lavoro di copia e incolla e funziona alla grande. Mi hai risparmiato un sacco di tempo.
    Cordiali saluti per chiunque effettui il porting in C:
    double deg2rad (double deg) {return deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Post molto carino - ha funzionato molto bene - ho dovuto solo cambiare il nome del tavolo con il lat-long. Funziona abbastanza velocemente per .. Ho un numero ragionevolmente piccolo di lat-long (<400) ma penso che questo scalerebbe bene. Anche un bel sito: l'ho appena aggiunto al mio account del.icio.us e controllerò regolarmente.

  3. 4
  4. 5
  5. 8

    Penso che il tuo SQL abbia bisogno di una dichiarazione di avere.
    invece di WHERE distanza <= $ distanza potresti aver bisogno
    utilizzare HAVING distance <= $ distanza

    altrimenti grazie per avermi risparmiato un sacco di tempo ed energia.

  6. 10
  7. 11
  8. 12

    Grazie mille per aver condiviso questo codice. Mi ha fatto risparmiare molto tempo per lo sviluppo. Inoltre, grazie ai tuoi lettori per aver sottolineato che un'istruzione HAVING è necessaria per MySQL 5.x. Molto utile.

  9. 14
  10. 15

    Ciao,

    Un'altra domanda. Esiste una formula per le stringhe NMEA come quella qui sotto?

    1342.7500, N, 10052.2287, E

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

    Grazie,
    tormentare

  11. 16

    Ho anche scoperto che WHERE non ha funzionato per me. L'ho cambiato in HAVING e tutto funziona perfettamente. All'inizio non ho letto i commenti e l'ho riscritto utilizzando una selezione annidata. Entrambi funzioneranno bene.

  12. 17
  13. 18

    Incredibilmente utile, grazie mille! Avevo dei problemi con il nuovo “HAVING”, piuttosto che con “WHERE”, ma una volta che ho letto i commenti qui (dopo circa mezz'ora di digrignamento dei denti per la frustrazione = P), ho capito che funzionava bene. Grazie ^ _ ^

  14. 19
  15. 20

    Tieni presente che un'istruzione select come quella sarà molto intensa dal punto di vista computazionale e quindi lenta. Se hai molte di queste domande, può impantanare le cose abbastanza rapidamente.

    Un approccio molto meno intenso consiste nell'eseguire una prima selezione (grezza) utilizzando un'area SQUARE definita da una distanza calcolata, ad esempio “select * from tablename dove latitudine tra lat1 e lat2 e longitudine tra lon1 e lon2”. lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, simile a lon. latdiff ~= distanza / 111 (per km), o distanza/69 per miglia poiché 1 grado di latitudine è ~ 111 km (leggera variazione poiché la terra è leggermente ovale, ma sufficiente per questo scopo). londiff = distanza / (abs (cos (deg2rad (latitude)) * 111)) - o 69 per miglia (puoi effettivamente prendere un quadrato leggermente più grande per tenere conto delle variazioni). Quindi prendi il risultato e inseriscilo nella selezione radiale. Basta non dimenticare di tenere conto delle coordinate fuori limite, ovvero l'intervallo di longitudine accettabile è compreso tra -180 e +180 e l'intervallo di latitudine accettabile è compreso tra -90 e +90, nel caso in cui il tuo latdiff o londiff si trovi al di fuori di questo intervallo . Si noti che nella maggior parte dei casi questo potrebbe non essere applicabile poiché influisce solo sui calcoli su una linea attraverso l'oceano pacifico da un polo all'altro, sebbene intersechi parte di chukotka e parte dell'Alaska.

    Ciò che otteniamo in questo modo è una riduzione significativa del numero di punti rispetto ai quali effettui questo calcolo. Se si dispone di un milione di punti globali nel database distribuiti in modo approssimativo e si desidera eseguire la ricerca entro 100 km, la prima ricerca (rapida) è di un'area di 10000 km quadrati e produrrà probabilmente circa 20 risultati (in base alla distribuzione uniforme su un superficie di circa 500 M kmq), il che significa che si esegue il calcolo della distanza complessa 20 volte per questa query anziché un milione di volte.

    • 21
      • 22

        Consiglio fantastico! In realtà ho lavorato con uno sviluppatore che ha scritto una funzione che tirava il quadrato interno e poi una funzione ricorsiva che creava dei "quadrati" attorno al perimetro per includere ed escludere i punti rimanenti. Il risultato è stato un risultato incredibilmente veloce: poteva valutare milioni di punti in microsecondi.

        Il mio approccio sopra è decisamente "grezzo" ma capace. Grazie ancora!

        • 23

          Doug,

          Ho provato a usare mysql e php per valutare se un punto lat long si trova all'interno di un poligono. Sai se il tuo amico sviluppatore ha pubblicato degli esempi su come eseguire questo compito. Oppure conosci qualche buon esempio. Grazie in anticipo.

  16. 24

    Ciao a tutti, questa è la mia dichiarazione SQL di 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

    e Mysql mi sta dicendo che la distanza, non esiste come colonna, posso usare order by, posso farlo senza WHERE, e funziona, ma non con esso ...

  17. 26

    Questo è fantastico, tuttavia è proprio come volano gli uccelli. Sarebbe bello provare a incorporare l'API di Google Maps in questo in qualche modo (magari usando strade ecc.) Solo per dare un'idea usando un diverso mezzo di trasporto. Devo ancora creare una funzione di ricottura simulata in PHP che sia in grado di offrire una soluzione efficiente al problema del venditore in viaggio. Ma penso che potrei essere in grado di riutilizzare parte del tuo codice per farlo.

  18. 27
  19. 28

    Buon articolo! Ho trovato molti articoli che descrivono come calcolare la distanza tra due punti, ma stavo davvero cercando lo snippet SQL.

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

    2 giorni di ricerca per trovare finalmente questa pagina che risolve il mio problema. Sembra che sia meglio che tiri fuori il mio WolframAlpha e ripassi i miei calcoli. Il passaggio da WHERE a HAVING ha il mio script funzionante. GRAZIE

  25. 37
    • 38

      Grazie Georgi. Continuavo a ricevere la colonna "distanza" non trovata. Una volta cambiato WHERE in HAVING ha funzionato a meraviglia!

  26. 39

    Vorrei che questa fosse la prima pagina che avevo trovato su questo. Dopo aver provato molti comandi diversi, questo è stato l'unico a funzionare correttamente e con modifiche minime necessarie per adattarsi al mio database.
    Grazie mille!

  27. 40

    Vorrei che questa fosse la prima pagina che avevo trovato su questo. Dopo aver provato molti comandi diversi, questo è stato l'unico a funzionare correttamente e con modifiche minime necessarie per adattarsi al mio database.
    Grazie mille!

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

    So che questa formula funziona, ma non riesco a vedere dove viene preso in considerazione il raggio della terra. Qualcuno può illuminarmi, per favore?

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

    grazie per aver pubblicato questo utile articolo,  
    ma per qualche motivo vorrei chiedere
    come ottenere la distanza tra le coordinate all'interno di mysql db e le coordinate inserite in php dall'utente?
    per descrivere più chiaramente:
    1.utente deve inserire [id] per selezionare i dati specificati dal db e le coordinate dell'utente stesso
    2.il file php ottiene i dati di destinazione (coordinate) utilizzando [id] e quindi calcola la distanza tra l'utente e il punto di destinazione

    o puoi semplicemente prendere le distanze dal codice qui sotto?

    $ qry = "SELEZIONA *, (((acos (sin ((". $ latitude. "* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((". $ latitudine. "* pi () / 180)) * cos ((` Latitude` * pi () / 180)) * cos (((". $ longitude." - `Longitude`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) come distanza FROM `MyTable` WHERE distance> =". $ Distance. " >>>> posso “togliere” la distanza da qui?
    grazie ancora,
    timmy si

  41. 60

    ok, tutto quello che ho provato non funziona. Voglio dire, quello che ho funziona, ma le distanze sono molto lontane.

    Qualcuno potrebbe vedere cosa c'è di sbagliato in questo codice?

    if (isset ($ _ POST ['submit'])) {$ z = $ _POST ['zipcode']; $ r = $ _POST ["raggio"]; echo "Risultati per". $ 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 die (mysql_error ()); while ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. "”; $ store = $ row ['LocAddSt']. ””; $ store. = $ row ['LocAddCity']. ",". $ row ['LocAddState']. " ". $ Riga [" codice postale "]; $ latitude1 = $ row ['lat']; $ longitudine1 = $ riga ['lon']; $ latitude2 = $ riga ["y1"]; $ longitudine2 = $ riga ["x1"]; $ city = $ row ["city"]; $ stato = $ riga ["stato"]; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = distanza ($ lat1, $ lon1, $ lat2, $ lon2); $ verificato = $ riga ["verificato"]; if ($ verificato == '1') {echo “”; echo “”. $ store. ””; echo $ dis. " lontano miglia"; eco ""; } altro {echo “”. $ store. ””; echo $ dis. " lontano miglia"; eco ""; }}}

    il mio codice functions.php
    funzione getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi') {$ theta = $ longitude1 - $ longitude2; $ distanza = (sin (deg2rad ($ latitude1)) * sin (deg2rad ($ latitude2))) + (cos (deg2rad ($ latitude1)) * cos (deg2rad ($ latitude2)) * cos (deg2rad ($ theta)) ); $ distanza = acos ($ distanza); $ distanza = rad2deg ($ distanza); $ distanza = $ distanza * 60 * 1.1515; switch ($ unit) {case 'Mi': break; caso 'Km': $ distanza = $ distanza * 1.609344; } ritorno (round ($ distanza, 2)); }

    Grazie in anticipo

  42. 61
  43. 62

    Hey Douglas, ottimo articolo. Ho trovato la tua spiegazione dei concetti geografici e del codice davvero interessante. Il mio unico suggerimento sarebbe quello di spaziare e indentare il codice per la visualizzazione (come Stackoverflow, per esempio). Capisco che vuoi risparmiare spazio, ma la spaziatura / rientro del codice convenzionale renderebbe molto più facile per me, come programmatore, leggere e analizzare. Comunque, è una piccola cosa. Continuate così.

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

    sembra più veloce (mysql 5.9) usare il doppio della formula nella selezione e dove:
    $ formula = "(((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 = 'SELEZIONA *,'. $ formula. ' come distanza dalla tabella WHERE '.. $ formula.' <= '. $ distanza;

  51. 71
  52. 72

    Grazie mille per aver tolto questo articolo. È molto utile.
    PHP è stato inizialmente creato come una semplice piattaforma di scripting chiamata "Home page personale". Al giorno d'oggi PHP (l'abbreviazione di Hypertext Preprocessor) è un'alternativa alla tecnologia Active Server Pages (ASP) di Microsoft.

    PHP è un linguaggio lato server open source che viene utilizzato per creare pagine web dinamiche. Può essere incorporato in HTML. PHP viene solitamente utilizzato insieme a un database MySQL su server Web Linux / UNIX. È probabilmente il linguaggio di scripting più popolare.

  53. 73

    Ho trovato la soluzione sopra non funzionante correttamente.
    Devo passare a:

    $ qqq = "SELEZIONA *, (((acos (sin ((". $ latitude. "* pi () / 180)) * sin ((` latt` * pi () / 180)) + cos ((". $ latitudine. "* pi () / 180)) * cos ((` latt` * pi () / 180)) * cos (((". $ longitudine." - `longt`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515) come distanza DA "registro" ";

  54. 75
  55. 76

    Ciao, per favore avrò davvero bisogno del tuo aiuto su questo.

    Ho effettuato una richiesta get al mio server web http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ latitudine
    -2.23389 = $ longitudine
    e 20 = la distanza che voglio recuperare

    Tuttavia, usando la tua formula, recupera tutte le righe nel mio db

    $ risultati = 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) come distanza FROM marker HAVING distance> = ". $ Distance));

    [{"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": "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 ":" Oregano's Wood-Fired Pizza "," address ":" 4546 El Camino Real, Los Altos, CA "," lat ": 37.401725769043," lng ": - 122.11464691162," distanza ": 16077.937560795}, {" id ": 7," name ":" The bars and grills "," address ":" 24 Whiteley Street, Manchester "," lat ": 53.485118865967," lng ": - 2.1828699111938," distance ": 8038.7620112314}]

    Voglio recuperare solo le righe con 20 miglia ma porta tutte le righe. Per favore, cosa sto facendo di sbagliato

Cosa ne pensi?

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati dei tuoi commenti.