PHP och SQL: Beräkna eller fråga stort cirkelavstånd mellan poäng för latitud och longitud med Haversine-formeln

Haversine Formula - Beräkna stort cirkelavstånd med PHP eller MySQL

Den här månaden har jag programmerat ganska mycket i PHP och MySQL med avseende på GIS. Snyggande runt nätet hade jag faktiskt svårt att hitta några av Geografiska beräkningar för att hitta avståndet mellan två platser så jag ville dela dem här.

Flygkarta Europa med stort cirkelavstånd

Det enkla sättet att beräkna ett avstånd mellan två punkter är att använda den pythagoreiska formeln för att beräkna hypotenusen för en triangel (A² + B² = C²). Detta är känt som Euklidiskt avstånd.

Det är en intressant start men det gäller inte geografi eftersom avståndet mellan latitud och longitud är inte lika långt isär. När du kommer närmare ekvatorn, blir latitudlinjer längre ifrån varandra. Om du använder någon form av enkel triangulationsekvation kan den mäta avståndet exakt på en plats och väldigt fel på den andra på grund av jordens krökning.

Stor cirkelavstånd

Rutterna som färdas långa sträckor runt jorden är kända som Stor cirkelavstånd. Det är ... det kortaste avståndet mellan två punkter på en sfär skiljer sig från punkterna på en platt karta. Kombinera det med det faktum att latitud- och longitudlinjerna inte är lika stora ... och du har en svår beräkning.

Här är en fantastisk videoförklaring av hur Great Circles fungerar.

Haversine-formeln

Avståndet med jordens krökning är införlivat i Haversine formel, som använder trigonometri för att möjliggöra krökning av jorden. När du hittar avståndet mellan två platser på jorden (som kragen flyger) är en rak linje verkligen en båge.

Detta gäller vid flygflygning - har du någonsin tittat på den faktiska kartan över flygningar och märkt att de är välvda? Det beror på att det är kortare att flyga i en båge mellan två punkter än direkt till platsen.

PHP: Beräkna avståndet mellan 2 poäng av latitud och longitud

Hur som helst, här är PHP-formeln för att beräkna avståndet mellan två punkter (tillsammans med Mile vs. Kilometer-omvandling) avrundat till två decimaler.

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: Hämta alla poster inom ett intervall genom att beräkna avstånd i mil med hjälp av latitud och longitud

Det är också möjligt att använda SQL för att göra en beräkning för att hitta alla poster inom ett visst avstånd. I det här exemplet ska jag fråga MyTable i MySQL för att hitta alla poster som är mindre än eller lika med variabla $ avstånd (i Miles) till min plats på $ latitud och $ longitud:

Frågan för att hämta alla poster inom en specifik avstånd genom att beräkna avståndet i miles mellan två latitud och longitud är:

$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åste anpassa detta:

  • $ longitud - det här är en PHP-variabel där jag passerar punktens longitud.
  • latitud $ - det här är en PHP-variabel där jag passerar punktens longitud.
  • $ avstånd - det här är avståndet som du vill hitta alla poster mindre eller lika med.
  • bord - det här är bordet ... du vill ersätta det med ditt bordsnamn.
  • latitud - det här är ditt latitudfält.
  • longitud - det här är din longitud.

SQL: Hämta alla poster inom ett intervall genom att beräkna avstånd i kilometer med hjälp av latitud och longitud

Och här är SQL-frågan som använder 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åste anpassa detta:

  • $ longitud - det här är en PHP-variabel där jag passerar punktens longitud.
  • latitud $ - det här är en PHP-variabel där jag passerar punktens longitud.
  • $ avstånd - det här är avståndet som du vill hitta alla poster mindre eller lika med.
  • bord - det här är bordet ... du vill ersätta det med ditt bordsnamn.
  • latitud - det här är ditt latitudfält.
  • longitud - det här är din longitud.

Jag använde den här koden i en företagsmappningsplattform som vi använde för en butik med över 1,000 platser i hela Nordamerika och det fungerade vackert.

76 Kommentarer

  1. 1

    Tack så mycket för att du delar. Det här var ett enkelt kopierings- och klistrajobb och fungerar bra. Du har sparat mig mycket tid.
    FYI för alla som porterar till C:
    dubbel deg2rad (dubbel deg) {retur deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Mycket trevligt inlägg - fungerade väldigt trevligt - jag var bara tvungen att ändra namnet på bordet som innehöll det långa. Det fungerar ganska snabbt för att .. Jag har ett ganska litet antal lat-longs (<400) men jag tror att det skulle skala fint. Trevlig webbplats också - jag har just lagt till den på mitt del.icio.us-konto och kommer att komma tillbaka regelbundet.

  3. 4
  4. 5

    Jag sökte hela dagen efter avståndsberäkningar och hittade harversinalgoritmen, tack till dig för att du gav exemplet på hur man lägger den i ett SQL-uttalande. Tack och hälsningar, Daniel

  5. 8

    Jag tror att din SQL behöver ha ett uttalande.
    istället för VAR avstånd <= $ avstånd du kan behöva
    använd HAVING distance <= $ distance

    annars tack för att du sparar en massa tid och energi.

  6. 10
  7. 11
  8. 12

    Tack så mycket för att du delar den här koden. Det sparade mig mycket utvecklingstid. Tack även dina läsare för att påpeka att ett HAVING-uttalande är nödvändigt för MySQL 5.x. Väldigt hjälpsam.

  9. 14
  10. 15

    Hallå,

    En annan fråga. Finns det en formel för NMEA-strängar som den nedan?

    1342.7500, N, 10052.2287, E

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

    Tack,
    Harry

  11. 16

    Jag upptäckte också att WHERE inte fungerade för mig. Ändrade till HAVANDE och allt fungerar perfekt. Först läste jag inte kommentarerna och skrev om dem med en kapslad markering. Båda fungerar bra.

  12. 17
  13. 18

    Otroligt hjälpsam, tack så mycket! Jag hade några problem med det nya ”HAVING”, snarare än “WHERE”, men när jag väl läst kommentarerna här (efter ungefär en halvtimme med att slipa tänderna i frustration = P) fick jag det att fungera bra. Tack ^ _ ^

  14. 19
  15. 20

    Tänk på att ett sådant utvalt uttalande kommer att vara mycket beräkningsintensivt och därför långsamt. Om du har många av dessa frågor kan det gå ner i saker ganska snabbt.

    Ett mycket mindre intensivt tillvägagångssätt är att köra en första (rå) markering med hjälp av ett SQUARE-område som definieras av ett beräknat avstånd, dvs. ”välj * från tabellnamn där latitud mellan lat1 och lat2 och longitud mellan lon1 och lon2”. lat1 = mållängd - latdiff, lat2 = mållängd + latdiff, liknande med lon. latdiff ~ = avstånd / 111 (för km), eller avstånd / 69 för miles eftersom 1 latitud är ~ 111 km (liten variation eftersom jorden är något oval, men tillräcklig för detta ändamål). londiff = avstånd / (abs (cos (deg2rad (latitud)) * 111)) - eller 69 för miles (du kan faktiskt ta en något större kvadrat för att ta hänsyn till variationer). Ta sedan resultatet av det och mata det i radialvalet. Glöm inte att ta hänsyn till koordinater utanför gränserna - dvs. intervallet för acceptabel longitud är -180 till +180 och intervallet för acceptabel latitud är -90 till +90 - om din latdiff eller londiff springer utanför detta intervall . Observera att detta i de flesta fall kanske inte är tillämpligt eftersom det bara påverkar beräkningar över en linje genom Stilla havet från pol till pol, även om det skär en del av chukotka och en del av alaska.

    Vad vi uppnår med detta är en betydande minskning av antalet poäng som du gör denna beräkning med. Om du har en miljon globala poäng i databasen fördelat ungefär jämnt och du vill söka inom 100 km, är din första (snabba) sökning på ett område på 10000 kvm och kommer troligen att ge cirka 20 resultat (baserat på jämn fördelning över en yta på cirka 500 M kvadratkilometer), vilket innebär att du kör den komplexa avståndsberäkningen 20 gånger för denna fråga istället för en miljon gånger.

    • 21
      • 22

        Fantastiskt råd! Jag arbetade faktiskt med en utvecklare som skrev en funktion som drog inuti kvadrat och sedan en rekursiv funktion som gjorde "rutor" runt omkretsen för att inkludera och utesluta de återstående punkterna. Resultatet var ett otroligt snabbt resultat - han kunde utvärdera miljontals poäng i mikrosekunder.

        Min inställning ovan är definitivt "rå" men kapabel. Tack igen!

        • 23

          Doug

          Jag har försökt använda mysql och php för att utvärdera om en lat lång punkt ligger inom en polygon. Vet du om din utvecklarvän publicerade några exempel på hur du kan utföra denna uppgift. Eller känner du några bra exempel. Tack på förhand.

  16. 24

    Hej alla detta är mitt test SQL-uttalande:

    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

    och Mysql säger till mig att avståndet inte existerar som en kolumn, jag kan använda ordning efter, jag kan göra det utan WHERE, och det fungerar, men inte med det ...

  17. 26

    Det här är fantastiskt, men det är precis som fåglarna flyger. Det skulle vara jättebra att försöka införliva google maps API till detta på något sätt (kanske med hjälp av vägar etc.) Bara för att ge en idé med en annan transportform. Jag har fortfarande inte gjort en simulerad glödgningsfunktion i PHP som skulle kunna erbjuda en effektiv lösning på det resande säljarens problem. Men jag tror att jag kanske kan återanvända en del av din kod för att göra det.

  18. 27
  19. 28

    Bra artikel! Jag hittade många artiklar som beskriver hur man beräknar avståndet mellan två punkter men jag letade verkligen efter SQL-kodavsnittet.

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

    2 dagars forskning för att äntligen hitta den här sidan som löser mitt problem. Det verkar som om jag bättre ska ta bort min WolframAlpha och borsta på min matematik. Förändringen från WHERE till HAVING har mitt skript i fungerande skick. TACK

  25. 37
    • 38

      Tack Georgi. Jag fick kolumnen "avstånd" hittades inte. En gång ändrade jag WHERE till HAVING det fungerade som en charm!

  26. 39

    Jag önskar att det här var den första sidan jag hittade på den här. Efter att ha provat många olika kommandon var det den enda som fungerade ordentligt, och med minimala ändringar som behövdes för att passa min egen databas.
    Tack så mycket!

  27. 40

    Jag önskar att det här var den första sidan jag hittade på den här. Efter att ha provat många olika kommandon var det den enda som fungerade ordentligt, och med minimala ändringar som behövdes för att passa min egen databas.
    Tack så mycket!

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

    Bra grejer Douglas. Har du försökt få skärningspunkten med tanke på Lång / Lat / Bearing av två punkter?

  36. 52
  37. 53
  38. 55

    Tack för den här fantastiska artikeln! Testade bara koden på min DB och fungerade bra! 

  39. 56
  40. 58

    tack för att du publicerade den här hjälpsamma artikeln,  
    men av någon anledning skulle jag vilja fråga
    hur får man avståndet mellan koordiner inuti mysql db och koordiner infogade till php av användaren?
    för mer tydligt beskriva:
    1.användaren måste infoga [id] för att välja specificerad data från db och användarens själva koordinater
    2. php-filen hämtar måldata (koordar) med [id] och beräknar sedan avståndet mellan användare och målpunkt

    eller kan du helt enkelt få avstånd från koden nedan?

    $ qry = “VÄLJ *, (((acos (sin ((“. $ latitud. ”* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((“. $ latitud. ”* pi () / 180)) * cos ((` Latitude` * pi () / 180)) * cos ((((“. $ longitude.” - `Longitude`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) som avstånd FRÅN `MyTable` VAR avstånd> =“. $ Avstånd. ” >>>> kan jag "ta bort" avståndet härifrån?
    tack igen,
    Timmy S.

  41. 60

    ok, allt jag har provat fungerar inte. Jag menar, vad jag har fungerar, men avstånden är långt borta.

    Kan någon se vad som är fel med den här koden?

    om (isset ($ _ POST ['skickat'])) {$ z = $ _POST ['postnummer']; $ r = $ _POST ['radie']; eko “Resultat för“. $ z; $ sql = mysql_query (“VÄLJ DISTINCT m.zipcode, m.MktName, m.LocAddSt, m.LocAddCity, m.LocAddState, m.x1, m.y1, m.verified, z1.lat, z2.lon, z1. stad, z1.stat FRÅN mrk m, zip z1, zip z2 VAR m.zipcode = z1.zipcode OCH z2.zipcode = $ z OCH (3963 * acos (trunkera (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ör (mysql_error ()); medan ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. "”; $ store = $ row ['LocAddSt']. ””; $ store. = $ row ['LocAddCity']. ”,“. $ row ['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 = avstånd ($ lat1, $ lon1, $ lat2, $ lon2); $ verified = $ row ['verified']; if ($ verified == '1') {echo “”; eko “”. $ store. ””; echo $ dis. " mil bort"; eko “”; } annat {eko “”. $ store. ””; echo $ dis. " mil bort"; eko “”; }}}

    mina funktioner.php-kod
    funktion getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi') {$ theta = $ longitude1 - $ longitude2; $ avstånd = (sin (deg2rad ($ latitude1)) * sin (deg2rad ($ latitude2))) + (cos (deg2rad ($ latitude1)) * cos (deg2rad ($ latitude2)) * cos (deg2rad ($ theta)) ); $ avstånd = acos ($ avstånd); $ avstånd = rad2deg ($ avstånd); $ avstånd = $ avstånd * 60 * 1.1515; switch ($ unit) {case 'Mi': break; fall 'Km': $ avstånd = $ avstånd * 1.609344; } retur (runda ($ avstånd, 2)); }

    Tack på förhand

  42. 61
  43. 62

    Hej Douglas, bra artikel. Jag tyckte att din förklaring av de geografiska begreppen och koden var väldigt intressant. Mitt enda förslag skulle vara att placera och dra in koden för visning (till exempel Stackoverflow). Jag förstår att du vill spara utrymme, men konventionellt kodavstånd / indrag skulle göra det mycket lättare för mig som programmerare att läsa och dissekera. Hur som helst, det är en liten sak. Fortsätt det fantastiska arbetet.

  44. 64
  45. 65

    här när vi använder med funktionen får vi en typ av avstånd .. medan vi använder frågan om den kommande andra typen av avstånd

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

    det verkar snabbare (mysql 5.9) att använda två gånger formeln i select och var:
    $ formel = “(((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 = 'VÄLJ *,'. $ formel. ' som avstånd FRÅN tabellen VAR '.. $ formel.' <= '. $ avstånd;

  51. 71
  52. 72

    Tack så mycket för att klippa den här artikeln. Den är mycket hjälpsam.
    PHP skapades till en början som en enkel skriptplattform som kallades ”Personlig hemsida”. Numera är PHP (förkortningen för Hypertext Preprocessor) ett alternativ till Microsofts Active Server Pages (ASP) -teknologi.

    PHP är ett öppen källkodsserver-språk som används för att skapa dynamiska webbsidor. Den kan bäddas in i HTML. PHP används vanligtvis i kombination med en MySQL-databas på Linux / UNIX-webbservrar. Det är förmodligen det mest populära skriptspråket.

  53. 73

    Jag hittade ovanstående lösning fungerar inte ordentligt.
    Jag måste byta till:

    $ qqq = “VÄLJ *, (((acos (sin ((“. $ latitud. ”* pi () / 180)) * sin ((` latt` * pi () / 180)) + cos ((”. $ latitud. ”* pi () / 180)) * cos ((` latt` * pi () / 180)) * cos (((”. $ longitud.“ - `longt`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515) som avstånd FRÅN "register" ";

  54. 75
  55. 76

    Hej, snälla, jag behöver verkligen din hjälp med detta.

    Jag gjorde en begäran till min webbserver http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ latitud
    -2.23389 = $ longitud
    och 20 = avståndet jag vill hämta

    Men med din formel hämtar den alla rader i min db

    $ resultat = DB :: välj (DB :: raw (“SELECT *, (((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((lat * pi () / 180 )) + cos ((“. $ latitud.” * pi () / 180)) * cos ((lat * pi () / 180)) * cos (((“. $ longitud.” - lng) * pi ( ) / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) som avstånd FRÅN markörer HAR avstånd> = “. $ Avstånd));

    [{"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}]

    Jag vill hämta bara rader med 20 mil men det tar alla rader. Snälla vad gör jag fel

Vad tror du?

Den här sidan använder Akismet för att minska spam. Läs om hur din kommentardata behandlas.