PHP og SQL: Beregn eller forespørgsel Stor cirkelafstand mellem punkter i bredde og længdegrad med Haversine-formlen

Haversine Formula - Beregn stor cirkelafstand med PHP eller MySQL

Denne måned har jeg programmeret en hel del i PHP og MySQL med hensyn til GIS. Snokende rundt på nettet havde jeg faktisk svært ved at finde noget af det Geografiske beregninger for at finde afstanden mellem to placeringer, så jeg ville dele dem her.

Flykort Europa med stor cirkelafstand

Den enkle måde at beregne en afstand mellem to punkter på er at bruge den pythagoriske formel til at beregne hypotenusen i en trekant (A² + B² = C²). Dette er kendt som Euklidisk afstand.

Det er en interessant start, men det gælder ikke for geografi, da afstanden mellem breddegrad og længdegrad er ikke en lige afstand en del. Når du kommer tættere på ækvator, kommer breddelinjerne længere fra hinanden. Hvis du bruger en slags simpel trianguleringsligning, måler den muligvis afstanden nøjagtigt det ene sted og frygteligt forkert på det andet på grund af jordens krumning.

Stor cirkelafstand

Ruterne, der tilbagelægges lange afstande rundt om jorden, er kendt som Stor cirkelafstand. Det er ... den korteste afstand mellem to punkter på en kugle er forskellig fra punkterne på et fladt kort. Kombiner det med det faktum, at bredde- og længdegraderne ikke er lige store ... og du har en vanskelig beregning.

Her er en fantastisk videoforklaring af, hvordan Great Circles fungerer.

Haversine-formlen

Afstanden ved hjælp af jordens krumning er indarbejdet i Haversine formel, som bruger trigonometri for at muliggøre jordens krumning. Når du finder afstanden mellem to steder på jorden (som kragen flyver), er en lige linje virkelig en bue.

Dette gælder i flyafgang - har du nogensinde set på det faktiske kort over flyvninger og bemærket, at de er buede? Det er fordi det er kortere at flyve i en bue mellem to punkter end direkte til placeringen.

PHP: Beregn afstand mellem 2 punkter i bredde og længdegrad

Under alle omstændigheder er her PHP-formlen til beregning af afstanden mellem to punkter (sammen med Mile vs. Kilometer-konvertering) afrundet til to 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: Henter alle poster inden for et interval ved at beregne afstand i kilometer ved hjælp af bredde og længdegrad

Det er også muligt at bruge SQL til at foretage en beregning for at finde alle poster inden for en bestemt afstand. I dette eksempel vil jeg forespørge MyTable i MySQL for at finde alle de poster, der er mindre end eller lig med variabel $ afstand (i Miles) til min placering på $ latitude og $ longitude:

Forespørgslen om at hente alle poster inden for en bestemt afstand ved at beregne afstanden i miles mellem to breddepunkter og længdegrader 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 bliver nødt til at tilpasse dette:

  • $ længdegrad - dette er en PHP-variabel, hvor jeg passerer punktets længdegrad.
  • $ breddegrad - dette er en PHP-variabel, hvor jeg passerer punktets længdegrad.
  • $ afstand - dette er den afstand, som du gerne vil finde alle poster mindre eller lig med.
  • bord - dette er bordet ... du vil gerne erstatte det med dit bordnavn.
  • breddegrad - dette er marken for din breddegrad.
  • længde - dette er din længdegrad.

SQL: Henter alle poster inden for et interval ved at beregne afstand i kilometer ved hjælp af bredde og længdegrad

Og her er SQL-forespørgslen, der bruger 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 bliver nødt til at tilpasse dette:

  • $ længdegrad - dette er en PHP-variabel, hvor jeg passerer punktets længdegrad.
  • $ breddegrad - dette er en PHP-variabel, hvor jeg passerer punktets længdegrad.
  • $ afstand - dette er den afstand, som du gerne vil finde alle poster mindre eller lig med.
  • bord - dette er bordet ... du vil gerne erstatte det med dit bordnavn.
  • breddegrad - dette er marken for din breddegrad.
  • længde - dette er din længdegrad.

Jeg brugte denne kode i en virksomheds kortlægningsplatform, som vi brugte til en detailbutik med over 1,000 placeringer i hele Nordamerika, og det fungerede smukt.

76 Kommentarer

  1. 1

    Mange tak for delingen. Dette var et let kopierings- og indsætjob og fungerer godt. Du har sparet mig meget tid.
    FYI for enhver, der porterer til C:
    dobbelt deg2rad (dobbelt deg) {returgrad * (3.14159265358979323846 / 180.0); }

  2. 2

    Meget flot udstationering - fungerede meget flot - jeg måtte kun ændre navnet på bordet, der indeholdt den længde. Det fungerer ret hurtigt til .. Jeg har et rimeligt lille antal lat-longs (<400), men jeg tror, ​​dette ville skalere pænt. Dejligt sted også - jeg har lige føjet det til min del.icio.us-konto og vil regelmæssigt tjekke tilbage.

  3. 4
  4. 5

    Jeg søgte hele dagen efter afstandsberegninger og fandt harversin-algoritmen tak til dig for at give eksemplet om, hvordan du sætter det i en SQL-erklæring. Tak og hilser, Daniel

  5. 8

    Jeg tror, ​​din SQL har brug for en have erklæring.
    i stedet for HVOR afstand <= $ afstand du muligvis har brug for
    brug HAVING distance <= $ distance

    Ellers tak for at spare mig for en masse tid og energi.

  6. 10
  7. 11
  8. 12

    Mange tak for at dele denne kode. Det sparede mig en masse udviklingstid. Tak til dine læsere for at påpege, at en HAVING-erklæring er nødvendig for MySQL 5.x. Meget hjælpsom.

  9. 14
  10. 15

    Hej,

    Endnu et spørgsmål. Er der en formel for NMEA-strenge 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

    Tak,
    Harry

  11. 16

    Jeg fandt også ud af, at WHERE ikke fungerede for mig. Ændrede det til at have, og alt fungerer perfekt. Først læste jeg ikke kommentarerne og omskrev dem ved hjælp af et indlejret valg. Begge fungerer fint.

  12. 17
  13. 18

    Utrolig hjælpsom, meget tak! Jeg havde nogle problemer med det nye “HAVING” snarere end “WHERE”, men når jeg først har læst kommentarerne her (efter cirka en halv times slibning af tænderne i frustration = P), fik jeg det til at fungere pænt. Tak ^ _ ^

  14. 19
  15. 20

    Husk, at en sådan udsagn vil være meget beregningsintensiv og derfor langsom. Hvis du har mange af disse forespørgsler, kan det ødelægge tingene ganske hurtigt.

    En meget mindre intens tilgang er at køre en første (rå) markering ved hjælp af et SQUARE-område defineret af en beregnet afstand, dvs. "vælg * fra tabellenavn, hvor bredde mellem lat1 og lat2 og længdegrad mellem lon1 og lon2". lat1 = målbredde - latdiff, lat2 = målbredde + latdiff, ens med lon. latdiff ~ = afstand / 111 (for km) eller afstand / 69 for miles, da 1 breddegrad er ~ 111 km (lille variation, da jorden er let oval, men tilstrækkelig til dette formål). londiff = distance / (abs (cos (deg2rad (latitude)) * 111)) - eller 69 miles (du kan faktisk tage en lidt større firkant for at tage højde for variationer). Tag derefter resultatet af det og før det ind i det radiale valg. Bare glem ikke at tage højde for koordinater uden for grænserne - dvs. området for acceptabel længdegrad er -180 til +180, og området for acceptabel bredde er -90 til +90 - hvis din latdiff eller londiff løber uden for dette interval . Bemærk, at dette i de fleste tilfælde muligvis ikke kan anvendes, da det kun påvirker beregninger over en linje gennem Stillehavet fra pol til pol, selvom det skærer en del af chukotka og en del af alaska.

    Det, vi opnår med dette, er en betydelig reduktion i antallet af point, som du foretager denne beregning med. Hvis du har en million globale point i databasen fordelt nogenlunde jævnt og du vil søge inden for 100 km, er din første (hurtige) søgning i et område på 10000 kvadratkilometer og vil sandsynligvis give ca. 20 resultater (baseret på jævn fordeling over en overflade på ca. 500 M kvadratkilometer), hvilket betyder, at du kører den komplekse afstandsberegning 20 gange for denne forespørgsel i stedet for en million gange.

    • 21

      Mindre fejl i eksemplet ... det ville være inden for 50 km (ikke 100), da vi ser på "radius" af vores ... firkant.

      • 22

        Fantastisk råd! Jeg arbejdede faktisk med en udvikler, der skrev en funktion, der trak den indvendige firkant og derefter en rekursiv funktion, der lavede 'firkanter' rundt om omkredsen for at inkludere og ekskludere de resterende punkter. Resultatet var et utroligt hurtigt resultat - han kunne evaluere millioner af point i mikrosekunder.

        Min tilgang ovenfor er bestemt 'rå', men i stand til. Tak igen!

        • 23

          Doug,

          Jeg har forsøgt at bruge mysql og php til at vurdere, om et lat langt punkt er inden for en polygon. Ved du, om din udviklerven offentliggjorde eksempler på, hvordan du udfører denne opgave. Eller kender du gode eksempler. Tak på forhånd.

  16. 24

    Hej alle dette er min test SQL-sætning:

    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 fortæller mig, at afstanden ikke eksisterer som en kolonne, jeg kan bruge rækkefølge efter, jeg kan gøre det uden WHERE, og det fungerer, men ikke med det ...

  17. 26

    Dette er fantastisk, men det er ligesom fuglene flyver. Det ville være dejligt at prøve at inkorporere google maps API til dette på en eller anden måde (måske ved hjælp af veje osv.) Bare for at give en idé ved hjælp af en anden form for transport. Jeg har stadig ikke lavet en simuleret annealing-funktion i PHP, der vil være i stand til at tilbyde en effektiv løsning på det rejsende sælgerproblem. Men jeg tror, ​​at jeg muligvis kan genbruge noget af din kode til at gøre det.

  18. 27
  19. 28

    God artikel! Jeg fandt en masse artikler, der beskriver, hvordan man beregner afstanden mellem to punkter, men jeg ledte virkelig efter SQL-kodestykket.

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

    2 dages forskning for endelig at finde denne side, der løser mit problem. Det ser ud til, at jeg hellere bryder min WolframAlpha ud og børster op i min matematik. Ændringen fra WHERE til HAVING har mit script i orden. TAK SKAL DU HAVE

  25. 37
    • 38

      Tak Georgi. Jeg blev ved med at få kolonne 'afstand' ikke fundet. Når jeg skifter WHERE til HAVING fungerede det som en charme!

  26. 39

    Jeg ville ønske, at dette var den første side, jeg fandt på dette. Efter at have prøvet mange forskellige kommandoer var dette den eneste, der fungerede korrekt, og med minimale ændringer nødvendige for at passe til min egen database.
    Thanks a lot!

  27. 40

    Jeg ville ønske, at dette var den første side, jeg fandt på dette. Efter at have prøvet mange forskellige kommandoer var dette den eneste, der fungerede korrekt, og med minimale ændringer nødvendige for at passe til min egen database.
    Thanks a lot!

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

    Jeg ved, at denne formel fungerer, men jeg kan ikke se, hvor jordens radius tages i betragtning. Kan nogen oplyse mig, tak?

  34. 49
  35. 50

    Fantastiske ting Douglas. Har du prøvet at få skæringspunktet givet Long / Lat / Bearing af to point?

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

    tak for at sende denne nyttige artikel,  
    men af ​​en eller anden grund vil jeg gerne spørge
    hvordan får man afstanden mellem koordiner inde i mysql db og koordiner indsat til php af brugeren?
    for en mere klar beskrivelse:
    1.brugeren skal indsætte [id] for at vælge specificerede data fra db og brugerens koordinater
    2. php-filen henter måldata (koordiner) ved hjælp af [id] og beregner derefter afstanden mellem bruger og målpunkt

    eller kan du bare få afstand fra koden nedenfor?

    $ 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) som afstand FRA `MyTable` HVOR afstand> =“. $ Afstand. ” >>>> kan jeg "tage" afstanden herfra?
    tak igen,
    Timmy S.

    • 59

      ligeglad, jeg har fundet ud af, hvordan "funktionen" fungerer i php
      $ dis = getDistanceBetweenPointsNew ($ userLati, $ userLongi, $ lati, $ longi, $ unit = 'Km')
      mange tak!! 

  41. 60

    ok, alt hvad jeg har prøvet fungerer ikke. Jeg mener, hvad jeg har, fungerer, men afstandene er langt væk.

    Kunne nogen muligvis se, hvad der er galt med denne kode?

    hvis (isset ($ _ POST ['indsendt'])) {$ 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 HVOR m.zipcode = z1.zipcode OG z2.zipcode = $ z AND (3963 * acos (trunker (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 die (mysql_error ()); mens ($ række = mysql_fetch_array ($ sql)) {$ store1 = $ række ['MktName']. "”; $ store = $ række ['LocAddSt']. ””; $ store. = $ række ['LocAddCity']. ”,“. $ række ['LocAddState']. ” “. $ Række ['postnummer']; $ latitude1 = $ række ['lat']; $ longitude1 = $ række ['lon']; $ latitude2 = $ række ['y1']; $ longitude2 = $ række ['x1']; $ by = $ række ['by']; $ state = $ row ['state']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = distance ($ lat1, $ lon1, $ lat2, $ lon2); $ verificeret = $ række ['verificeret']; hvis ($ verificeret == '1') {echo “”; ekko "". $ butik. ""; ekko $ dis. " mil væk"; ekko ""; } andet {ekko “”. $ store. ””; ekko $ dis. " mil væk"; ekko ""; }}}

    mine funktioner.php-kode
    funktion 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)) ); $ afstand = acos ($ afstand); $ afstand = rad2deg ($ afstand); $ afstand = $ afstand * 60 * 1.1515; switch ($ enhed) {sag 'Mi': pause; sag 'Km': $ distance = $ distance * 1.609344; } retur (rund ($ afstand, 2)); }

    Tak på forhånd

  42. 61
  43. 62

    Hej Douglas, god artikel. Jeg fandt din forklaring på de geografiske begreber og koden virkelig interessant. Mit eneste forslag ville være at placere og indrykke koden til visning (som f.eks. Stackoverflow). Jeg forstår, at du vil spare plads, men konventionel kodeafstand / indrykning ville gøre det meget lettere for mig som programmør at læse og dissekere. Under alle omstændigheder er det en lille ting. Fortsæt det store arbejde.

  44. 64
  45. 65

    her, mens vi bruger med funktionen, får vi en type afstand .. mens vi bruger en forespørgsel, kommer den anden type afstand

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

    det virker hurtigere (mysql 5.9) at bruge dobbelt formlen i select og hvor:
    $ formel = “((((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((“. $ latitude. ”* Pi () / 180)) * cos ((` Breddegrad` * pi () / 180)) * cos (((“. $ Længdegrad.” - `Længdegrad`) * pi () / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) ”;
    $ sql = 'VÆLG *,'. $ formel. ' som afstand FRA tabel WHERE '.. $ formula.' <= '. $ afstand;

  51. 71
  52. 72

    Mange tak for at skære denne artikel. Det er meget nyttigt.
    PHP blev oprindeligt oprettet som en simpel scriptingsplatform kaldet “Personal Home Page”. I dag er PHP (forkortelsen for Hypertext Preprocessor) et alternativ til Microsofts Active Server Pages (ASP) teknologi.

    PHP er et open source-serversidesprog, der bruges til at oprette dynamiske websider. Det kan integreres i HTML. PHP bruges normalt sammen med en MySQL-database på Linux / UNIX-webservere. Det er sandsynligvis det mest populære script-sprog.

  53. 73

    Jeg fandt ovenstående løsning ikke fungerer korrekt.
    Jeg er nødt til at skifte til:

    $ qqq = “VÆLG *, (((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) som afstand FRA `register`“;

  54. 75
  55. 76

    Hej, tak, jeg har virkelig brug for din hjælp til dette.

    Jeg fremsendte en anmodning til mit web-server http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ bredde
    -2.23389 = $ længdegrad
    og 20 = den afstand, jeg vil hente

    Men når du bruger din formel, henter den alle rækker i min db

    $ resultater = DB :: vælg (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 afstand FRA markører HAVING afstand> = “. $ Afstand));

    [{"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 & Albas Pizza & Pasta”, ”adresse”: ”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,” navn ”:” Søjlerne og grillerne ”,” adresse ”:” 24 Whiteley Street, Manchester ”,” lat ”: 53.485118865967,” lng ”: - 2.1828699111938,” distance ”: 8038.7620112314}]

    Jeg vil gerne hente bare rækker med 20 miles, men det bringer alle rækker. Venligst hvad laver jeg forkert

Hvad mener du?

Dette websted bruger Akismet til at reducere spam. Lær, hvordan dine kommentardata behandles.