PHP og SQL: Beregn eller spør om stor sirkelavstand mellom bredde- og lengdegrad med Haversine-formelen

Haversine Formula - Beregn stor sirkelavstand med PHP eller MySQL

Denne måneden har jeg programmert ganske mye i PHP og MySQL med hensyn til GIS. Snokende rundt på nettet hadde jeg faktisk vanskelig for å finne noe av Geografiske beregninger for å finne avstanden mellom to steder, så jeg ønsket å dele dem her.

Flykart Europa med stor sirkelavstand

Den enkle måten å beregne en avstand mellom to punkter på er å bruke den pythagoreiske formelen til å beregne hypotenusen til en trekant (A² + B² = C²). Dette er kjent som Euklidisk avstand.

Det er en interessant start, men det gjelder ikke for geografi siden avstanden mellom breddegrad og lengdegrad er ikke like lang avstand fra hverandre. Når du kommer nærmere ekvator, kommer breddelinjene lenger fra hverandre. Hvis du bruker en slags enkel trianguleringsligning, kan den måle avstanden nøyaktig på det ene stedet og veldig galt på det andre, på grunn av jordens krumning.

Stor sirkelavstand

Rutene som reises lange avstander rundt jorden er kjent som Stor sirkelavstand. Det er ... den korteste avstanden mellom to punkter på en kule er forskjellig fra punktene i et flatt kart. Kombiner det med det faktum at bredde- og lengdegradene ikke er like store ... og du har en vanskelig beregning.

Her er en fantastisk videoforklaring på hvordan Great Circles fungerer.

Haversine-formelen

Avstanden ved hjelp av jordens krumning er innlemmet i Haversine formel, som bruker trigonometri for å tillate krumning av jorden. Når du finner avstanden mellom to steder på jorden (i luftlinje), er en rett linje virkelig en bue.

Dette gjelder i flyreise - har du noen gang sett på det faktiske kartet over flyreiser og lagt merke til at de er buet? Det er fordi det er kortere å fly i en bue mellom to punkter enn direkte til stedet.

PHP: Beregn avstand mellom 2 punkter i bredde og lengdegrad

Uansett, her er PHP-formelen for å beregne avstanden mellom to punkter (sammen med Mile vs. Kilometer-konvertering) avrundet til to desimaler.

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: Henter alle poster innen et område ved å beregne avstand i mil ved hjelp av bredde og lengdegrad

Det er også mulig å bruke SQL til å gjøre en beregning for å finne alle poster innenfor en bestemt avstand. I dette eksemplet skal jeg spørre MyTable i MySQL for å finne alle postene som er mindre enn eller lik variabel $ avstand (i Miles) til min plassering på $ latitude og $ longitude:

Spørsmålet for å hente alle postene i et bestemt avstand ved å beregne avstanden i miles mellom to breddepunkter og lengdegrad er:

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

Du må tilpasse dette:

  • $ lengdegrad - dette er en PHP-variabel der jeg passerer punktets lengdegrad.
  • latitude - dette er en PHP-variabel der jeg passerer punktets lengdegrad.
  • $ avstand - dette er avstanden du vil finne alle postene mindre eller lik.
  • bord - dette er bordet ... du vil erstatte det med bordnavnet ditt.
  • breddegrad - dette er feltet for din breddegrad.
  • lengdegrad - dette er ditt lengdegrad.

SQL: Henter alle poster innen et område ved å beregne avstand i kilometer ved bruk av bredde og lengdegrad

Og her er SQL-spørringen som bruker kilometer i 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."

Du må tilpasse dette:

  • $ lengdegrad - dette er en PHP-variabel der jeg passerer punktets lengdegrad.
  • latitude - dette er en PHP-variabel der jeg passerer punktets lengdegrad.
  • $ avstand - dette er avstanden du vil finne alle postene mindre eller lik.
  • bord - dette er bordet ... du vil erstatte det med bordnavnet ditt.
  • breddegrad - dette er feltet for din breddegrad.
  • lengdegrad - dette er ditt lengdegrad.

Jeg brukte denne koden i en kartleggingsplattform for bedrifter som vi brukte for en butikk med over 1,000 steder over hele Nord-Amerika, og det fungerte vakkert.

76 Kommentarer

  1. 1

    Tusen takk for at du delte. Dette var en enkel kopierings- og limjobb og fungerer bra. Du har spart meg mye tid.
    FYI for alle som porter til C:
    dobbel deg2rad (dobbel deg) {retur deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Veldig fint stykke innlegg - fungerte veldig fint - jeg måtte bare endre navnet på bordet som holder det lengde. Det fungerer ganske fort til .. Jeg har et relativt lite antall leng-lengder (<400) men jeg tror dette ville skaleres pent. Hyggelig side også - jeg har nettopp lagt den til på del.icio.us-kontoen min og kommer tilbake regelmessig.

  3. 4
  4. 5

    Jeg søkte hele dagen etter avstandsberegninger og fant harversinalgoritmen, takk til deg for å gi eksemplet på hvordan du kan sette det i en SQL-uttalelse. Takk og hilsen, Daniel

  5. 8

    Jeg tror SQL trenger en ha uttalelse.
    i stedet for HVOR avstand <= $ avstand du kanskje trenger
    bruk HAVING distance <= $ distance

    ellers takk for at du sparte en mengde tid og energi.

  6. 10
  7. 11
  8. 12

    Tusen takk for at du delte denne koden. Det sparte meg mye utviklingstid. Også takk til leserne for å påpeke at en HAVING-uttalelse er nødvendig for MySQL 5.x. Veldig hjelpsom.

  9. 14
  10. 15

    Hallo,

    Et annet spørsmål. Er det en formel for NMEA-strenger som den nedenfor?

    1342.7500, N, 10052.2287, E

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

    Takk,
    Harry

  11. 16

    Jeg fant også ut at WHERE ikke fungerte for meg. Endret den til HAVING og alt fungerer perfekt. Først leste jeg ikke kommentarene og skrev den om med et nestet utvalg. Begge vil fungere helt fint.

  12. 17
  13. 18

    Utrolig hjelpsom, tusen takk! Jeg hadde noen problemer med det nye “HAVING”, snarere enn “WHERE”, men når jeg først har lest kommentarene her (etter omtrent en halv time med å slite tennene i frustrasjon = P), fikk jeg det til å fungere pent. Takk skal du ha ^ _ ^

  14. 19
  15. 20

    Husk at en slik uttalelse vil være veldig beregningsmessig intens og derfor treg. Hvis du har mange av disse spørsmålene, kan det ødelegge ting ganske raskt.

    En mye mindre intens tilnærming er å kjøre et første (rå) valg ved hjelp av et SQUARE-område definert av en beregnet avstand, dvs. "velg * fra tabellnavn der breddegrad mellom lat1 og lat2 og lengdegrad mellom lon1 og lon2". lat1 = målbredde - latdiff, lat2 = målbredde + latdiff, lik med lon. latdiff ~ = avstand / 111 (for km), eller avstand / 69 for miles siden 1 breddegrad er ~ 111 km (liten variasjon siden jorden er litt oval, men tilstrekkelig for dette formålet). londiff = avstand / (abs (cos (deg2rad (breddegrad)) * 111)) - eller 69 for miles (du kan faktisk ta en litt større firkant for å ta hensyn til variasjoner). Ta deretter resultatet av det og mat det inn i det radiale valget. Bare ikke glem å ta hensyn til koordinater utenfor grensene - dvs. at akseptabel lengdegrad er -180 til +180 og rekkevidde for akseptabel breddegrad er -90 til +90 - i tilfelle latdiff eller londiff løper utenfor dette området . Merk at dette i de fleste tilfeller ikke kan være aktuelt, siden det bare påvirker beregninger over en linje gjennom Stillehavet fra pol til pol, selv om det skjærer en del av chukotka og en del av alaska.

    Det vi oppnår med dette er en betydelig reduksjon i antall poeng som du gjør denne beregningen mot. Hvis du har en million globale poeng i databasen fordelt omtrent jevnt og du vil søke innen 100 km, er ditt første (raske) søk på et område på 10000 kvadratkilometer og vil trolig gi omtrent 20 resultater (basert på jevn fordeling over en overflate på omtrent 500 M kvadratkilometer), noe som betyr at du kjører den komplekse avstandsberegningen 20 ganger for dette spørsmålet i stedet for en million ganger.

    • 21
      • 22

        Fantastisk råd! Jeg jobbet faktisk med en utvikler som skrev en funksjon som trakk innsiden av firkanten og deretter en rekursiv funksjon som laget "firkanter" rundt omkretsen for å inkludere og ekskludere de gjenværende punktene. Resultatet var et utrolig raskt resultat - han kunne evaluere millioner av poeng i mikrosekunder.

        Min tilnærming ovenfor er definitivt 'rå', men i stand til. Takk igjen!

        • 23

          Doug,

          Jeg har prøvd å bruke mysql og php for å evaluere om et lat punkt er innenfor en polygon. Vet du om utviklervennen din publiserte noen eksempler på hvordan du kan utføre denne oppgaven. Eller kjenner du noen gode eksempler. Takk på forhånd.

  16. 24

    Hei alle sammen dette er SQL-testuttalelsen min:

    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

    og Mysql forteller meg at avstanden ikke eksisterer som en kolonne, jeg kan bruke ordre etter, jeg kan gjøre det uten WHERE, og det fungerer, men ikke med det ...

  17. 26

    Dette er flott, men det er akkurat som fuglene flyr. Det ville være flott å prøve å innlemme google maps API til dette på en eller annen måte (kanskje ved hjelp av veier osv.) Bare for å gi en idé ved hjelp av en annen form for transport. Jeg har ennå ikke laget en simulert annealing-funksjon i PHP som vil kunne tilby en effektiv løsning på det reisende selgerproblemet. Men jeg tror at jeg kanskje kan gjenbruke noe av koden din til å gjøre det.

  18. 27
  19. 28

    God artikkel! Jeg fant mange artikler som beskriver hvordan jeg kan beregne avstanden mellom to punkter, men jeg lette virkelig etter SQL-kodebiten.

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

    2 dagers forskning for endelig å finne denne siden som løser problemet mitt. Det ser ut til at jeg bedre kan bryte ut WolframAlphaen min og pusse opp matematikken. Endringen fra WHERE til HAVING har skriptet mitt i orden. TAKK SKAL DU HA

  25. 37
    • 38

      Takk Georgi. Jeg fortsatte å få kolonne 'avstand' ikke funnet. Når jeg bytter WHERE til å ha det fungerte det som en sjarm!

  26. 39

    Jeg skulle ønske dette var den første siden jeg fant på dette. Etter å ha prøvd mange forskjellige kommandoer var dette den eneste som fungerte ordentlig, og med minimale endringer som var nødvendige for å passe til min egen database.
    Thanks a lot!

  27. 40

    Jeg skulle ønske dette var den første siden jeg fant på dette. Etter å ha prøvd mange forskjellige kommandoer var dette den eneste som fungerte ordentlig, og med minimale endringer som var nødvendige for å passe til min egen database.
    Thanks a lot!

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

    Jeg vet at denne formelen fungerer, men jeg kan ikke se hvor det tas hensyn til jordens radius. Kan noen opplyse meg, vær så snill?

  34. 49
  35. 50

    Flotte ting Douglas. Har du prøvd å få skjæringspunktet gitt Long / Lat / Peiling av to poeng?

  36. 52

    Takk Douglas, SQL-spørringen er akkurat det jeg trengte, og jeg trodde jeg måtte skrive det selv. Du har reddet meg fra muligens timer med breddegrad-læringskurve!

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

    takk for at du skrev denne nyttige artikkelen,  
    men av en eller annen grunn vil jeg gjerne spørre
    hvordan får jeg avstanden mellom koordene i mysql db og koordiner satt inn i php av brukeren?
    for bedre å beskrive:
    1.brukeren må sette inn [id] for å velge spesifiserte data fra db og brukerens koordinater
    2. php-filen henter måldataene (koordene) ved hjelp av [id] og beregner deretter avstanden mellom brukeren og målpunktet

    eller kan du bare få avstand fra koden nedenfor?

    $ qry = “SELECT *, (((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((“. $ breddegrad. ”* pi () / 180)) * cos ((` Breddegrad` * pi () / 180)) * cos (((“. $ lengdegrad.” - `Lengdegrad`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) som avstand FRA `MyTable` WHERE distance> =“. $ Distance. ” >>>> kan jeg "ta ut" avstanden herfra?
    takk igjen,
    Timmy S

    • 59

      bryr deg ikke, jeg har funnet ut hvordan “funksjonen” fungerer i php
      $ dis = getDistanceBetweenPointsNew ($ userLati, $ userLongi, $ lati, $ longi, $ unit = 'Km')
      Takk så mye!! 

  41. 60

    ok, alt jeg har prøvd fungerer ikke. Jeg mener det jeg har fungerer, men avstandene er langt unna.

    Kan noen muligens se hva som er galt med denne koden?

    hvis (isset ($ _ POST ['sendt'])) {$ z = $ _POST ['postnummer']; $ r = $ _POST ['radius']; ekko “Resultater for“. $ 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. by, z1.stat FRA 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 ") eller dø (mysql_error ()); while ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. "”; $ store = $ row ['LocAddSt']. ””; $ store. = $ rad ['LocAddCity']. ”,“. $ rad ['LocAddState']. ” “. $ Rad ['postnummer']; $ latitude1 = $ rad ['lat']; $ longitude1 = $ rad ['lon']; $ latitude2 = $ rad ['y1']; $ longitude2 = $ rad ['x1']; $ city = $ row ['city']; $ state = $ row ['state']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = avstand ($ lat1, $ lon1, $ lat2, $ lon2); $ verified = $ row ['verified']; if ($ verified == '1') {echo “”; ekko “”. $ store. ””; ekko $ dis. ”Mil (er) unna”; ekko ""; } annet {ekko “”. $ store. ””; ekko $ dis. ”Mil (er) unna”; ekko ""; }}}

    funksjonene mine.php
    funksjon getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi') {$ theta = $ longitude1 - $ longitude2; $ avstand = (sin (deg2rad ($ latitude1)) * sin (deg2rad ($ latitude2))) + (cos (deg2rad ($ latitude1)) * cos (deg2rad ($ latitude2)) * cos (deg2rad ($ theta)) ); $ avstand = acos ($ avstand); $ avstand = rad2deg ($ avstand); $ avstand = $ avstand * 60 * 1.1515; switch ($ unit) {case 'Mi': break; sak 'Km': $ avstand = $ avstand * 1.609344; } retur (rund ($ avstand, 2)); }

    Takk på forhånd

  42. 61
  43. 62

    Hei Douglas, flott artikkel. Jeg syntes forklaringen din på de geografiske begrepene og koden var veldig interessant. Mitt eneste forslag ville være å plassere og kutte koden for visning (som for eksempel Stackoverflow). Jeg forstår at du vil spare plass, men konvensjonell kodeavstand / innrykk vil gjøre det mye lettere for meg som programmerer å lese og dissekere. Uansett, det er en liten ting. Fortsett den gode jobben.

  44. 64
  45. 65

    her mens vi bruker med funksjonen får vi en type avstand .. mens vi bruker spørsmål om den kommende andre typen avstand

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

    det virker raskere (mysql 5.9) å bruke to ganger formelen i select og hvor:
    $ formel = “(((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((“. $ latitude. ”* Pi () / 180)) * cos ((` Breddegrad` * pi () / 180)) * cos (((“. $ Lengdegrad.” - `Lengdegrad`) * pi () / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) ”;
    $ sql = 'VELG *,'. $ formel. ' som avstand FRA tabellen HVOR '' .. $ formel. ' <= '. $ avstand;

  51. 71
  52. 72

    Tusen takk for å skjære denne artikkelen. Det er veldig nyttig.
    PHP ble først opprettet som en enkel skriptplattform kalt “Personal Home Page”. I dag er PHP (forkortelsen for Hypertext Preprocessor) et alternativ til Microsofts Active Server Pages (ASP) teknologi.

    PHP er et åpent kildeservicespråk som brukes til å lage dynamiske websider. Den kan bygges inn i HTML. PHP brukes vanligvis i forbindelse med en MySQL-database på Linux / UNIX-webservere. Det er sannsynligvis det mest populære skriptspråket.

  53. 73

    Jeg fant løsningen ovenfor ikke fungerer ordentlig.
    Jeg må endre til:

    $ qqq = “SELECT *, (((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((` latt` * pi () / 180)) + cos ((”. $ breddegrad. “* pi () / 180)) * cos ((` latt` * pi () / 180)) * cos ((((.. $ longitude. “-` longt`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515) som avstand FRA `register`“;

  54. 75
  55. 76

    Hei, vær så snill, jeg vil virkelig trenge din hjelp til dette.

    Jeg sendte en forespørsel til webserveren min http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ breddegrad
    -2.23389 = $ lengdegrad
    og 20 = avstanden jeg vil hente

    Imidlertid bruker du formelen, den henter alle rader i db-en min

    $ resultater = DB :: velg (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) som avstand FRA markører HAVING avstand> = “. $ Avstand));

    [{"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,” distance ”: 16077.937560795}, {“ id ”: 7,” name ”:” The bars and grills ”,” address ”:” 24 Whiteley Street, Manchester ”,” lat ”: 53.485118865967,” lng ”: - 2.1828699111938,” distance ”: 8038.7620112314}]

    Jeg vil hente bare rader med 20 miles, men det bringer alle rader. Vennligst hva gjør jeg galt

Hva tror du?

Dette nettstedet bruker Akismet for å redusere spam. Lær hvordan kommentaren din behandles.