PHP i SQL: Izračunavanje ili ispitivanje velike udaljenosti kruga između tačaka geografske širine i dužine pomoću Haversine formule

Haversine formula - Izračunajte udaljenost velike kružnice pomoću PHP-a ili MySQL-a

Ovog sam mjeseca prilično programirao na PHP-u i MySQL-u s obzirom na GIS. Pregledavajući mrežu, zapravo mi je bilo teško pronaći neke od njih Geografski proračuni kako bih pronašao udaljenost između dvije lokacije, pa sam ih želio podijeliti ovdje.

Karta leta Europa s velikom kružnom udaljenostom

Jednostavan način izračuna udaljenosti između dvije točke je pomoću pitagorejske formule za izračunavanje hipotenuze trokuta (A² + B² = C²). Ovo je poznato kao Euklidska udaljenost.

To je zanimljiv početak, ali se ne odnosi na Geografiju jer je udaljenost između linija geografske širine i dužine nije jednaka udaljenost odvojeno. Kako se približavate ekvatoru, geografske širine se sve više razdvajaju. Ako koristite neku vrstu jednostavne jednadžbe triangulacije, ona može precizno izmjeriti udaljenost na jednom mjestu, a užasno pogrešno na drugom, zbog zakrivljenosti Zemlje.

Udaljenost velike kružnice

Rute koje se prelaze na velike udaljenosti oko Zemlje poznate su kao Udaljenost velike kružnice. To jest ... najkraća udaljenost između dvije točke na sferi je različita od tačaka na ravnoj mapi. Kombinirajte to s činjenicom da linije geografske širine i dužine nisu jednako udaljene ... i imate težak proračun.

Evo fantastičnog video objašnjenja kako funkcioniraju Veliki krugovi.

Formula Haversine

Udaljenost koja koristi zakrivljenost Zemlje uključena je u Formula haversina, koji koristi trigonometriju kako bi omogućio zakrivljenost zemlje. Kada pronađete udaljenost između dva mjesta na zemlji (dok vrana leti), ravna linija je zaista luk.

Ovo je primjenjivo u zračnom letu - jeste li ikada pogledali stvarnu mapu letova i primijetili da su zaobljeni? To je zato što je kraće letjeti u luku između dvije točke nego direktno do lokacije.

PHP: Izračunajte udaljenost između 2 tačke širine i dužine

U svakom slučaju, ovdje je PHP formula za izračunavanje udaljenosti između dvije točke (zajedno s pretvaranjem milje u odnosu na kilometar) zaokruženo na dvije decimale.

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: Dohvaćanje svih zapisa u rasponu izračunavanjem udaljenosti u miljama pomoću geografske širine i dužine

Također je moguće koristiti SQL za izračun kako bi se pronašli svi zapisi na određenoj udaljenosti. U ovom primjeru postaviću upit MyTable u MySQL-u kako bih pronašao sve zapise koji su manji ili jednaki varijabli $ distance (u miljama) do moje lokacije na $ latitude i $ longitude:

Upit za dohvaćanje svih zapisa unutar određenog rastojanje izračunavanjem udaljenosti u miljama između dvije tačke geografske širine i dužine su:

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

Morat ćete prilagoditi ovo:

  • $ zemljopisne dužine - ovo je PHP varijabla gdje prolazim dužinu točke.
  • $ geografska širina - ovo je PHP varijabla gdje prolazim dužinu točke.
  • $ udaljenost - ovo je udaljenost na kojoj biste željeli pronaći sve zapise manje ili jednake.
  • sto - ovo je tablica ... morat ćete to zamijeniti imenom svoje tablice.
  • širina - ovo je polje vaše geografske širine.
  • zemljopisnu dužinu - ovo je polje vaše geografske dužine.

SQL: Dohvaćanje svih zapisa u rasponu izračunavanjem udaljenosti u kilometrima pomoću geografske širine i dužine

Evo i SQL upita koji koristi kilometre u 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."

Morat ćete prilagoditi ovo:

  • $ zemljopisne dužine - ovo je PHP varijabla gdje prolazim dužinu točke.
  • $ geografska širina - ovo je PHP varijabla gdje prolazim dužinu točke.
  • $ udaljenost - ovo je udaljenost na kojoj biste željeli pronaći sve zapise manje ili jednake.
  • sto - ovo je tablica ... morat ćete to zamijeniti imenom svoje tablice.
  • širina - ovo je polje vaše geografske širine.
  • zemljopisnu dužinu - ovo je polje vaše geografske dužine.

Koristila sam ovaj kod u platformi za mapiranje preduzeća, koju smo koristili za maloprodaju sa preko 1,000 lokacija širom Sjeverne Amerike, i to je djelovalo lijepo.

76 Komentari

  1. 1

    Puno vam hvala na podjeli. Ovo je bio jednostavan posao kopiranja i lijepljenja i izvrsno funkcionira. Uštedjeli ste mi puno vremena.
    Upozorenje za sve koji prenose na C:
    double deg2rad (double deg) {return stepen * (3.14159265358979323846 / 180.0); }

  2. 2

    Vrlo lijep post - izvrsno je funkcionirao - morao sam samo promijeniti naziv stola koji je držao lat-long. Djeluje prilično brzo na .. Imam relativno mali broj lattona (<400), ali mislim da bi ovo bilo lijepo. I lijepa web lokacija - upravo sam je dodao na svoj račun na del.icio.us i redovito ću provjeravati.

  3. 4
  4. 5
  5. 8

    mislim da vašem SQL-u treba izjava o posjedovanju.
    umjesto WHERE udaljenost <= $ udaljenost koju ćete možda trebati
    koristite HAVING distance <= $ distance

    inače hvala što ste mi uštedjeli hrpu vremena i energije.

  6. 10
  7. 11
  8. 12

    Puno vam hvala što ste podijelili ovaj kod. Uštedjelo mi je puno vremena za razvoj. Takođe, hvala čitateljima što su istakli da je HAVING izjava neophodna za MySQL 5.x. Vrlo korisno.

  9. 14
  10. 15

    Zdravo,

    Još jedno pitanje. Postoji li formula za NMEA nizove poput one dolje?

    1342.7500, N, 10052.2287, E

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

    hvala,
    Harry

  11. 16

    Takođe sam otkrio da WHERE nije radio za mene. Promijenio je u HAVING i sve funkcionira savršeno. U početku nisam pročitao komentare i prepisao ih pomoću ugniježđenog odabira. Oboje će raditi sasvim u redu.

  12. 17
  13. 18

    Nevjerovatno korisno, puno vam hvala! Imao sam problema s novim „IMAM“, umjesto s „GDJE“, ali kad sam ovdje pročitao komentare (nakon otprilike pola sata frustriranog škrgutanja zubima = P), počeo sam lijepo raditi. Hvala ^ _ ^

  14. 19
  15. 20

    Imajte na umu da će odabrana takva izjava biti vrlo računski intenzivna i stoga spora. Ako imate puno tih upita, to može prilično brzo zakrčiti.

    Mnogo manje intenzivan pristup je pokretanje prvog (sirovog) odabira pomoću KVADRATNOG područja definiranog izračunatom udaljenostom, tj. „Odaberi * iz naziva tablice gdje je geografska širina između lat1 i lat2 i dužina između lon1 i lon2“. lat1 = ciljana širina - latdiff, lat2 = ciljna širina + latdiff, slično kao i lon. latdiff ~ = udaljenost / 111 (za km) ili udaljenost / 69 za milje jer je 1 stepen geografske širine ~ 111 km (mala varijacija jer je zemlja blago ovalna, ali dovoljna za ovu svrhu). londiff = udaljenost / (abs (cos (deg2rad (geografska širina)) * 111)) - ili 69 milja (zapravo možete uzeti malo veći kvadrat kako biste uzeli u obzir varijacije). Zatim uzmite rezultat i unesite ga u radijalni odabir. Samo ne zaboravite uzeti u obzir koordinate izvan granica - tj. Opseg prihvatljive dužine je -180 do +180, a opseg prihvatljive geografske širine je -90 do +90 - u slučaju da vaš latdiff ili londiff radi izvan tog raspona . Imajte na umu da u većini slučajeva ovo možda nije primjenjivo jer utječe samo na proračune preko linije kroz Tihi ocean od pola do pola, iako presijeca dio Čukotke i dio Aljaske.

    Ono što ovim postižemo je značajno smanjenje broja bodova na osnovu kojih izračunate. Ako imate milijun globalnih točaka u bazi podataka raspoređenih otprilike ravnomjerno i želite pretraživati ​​u krugu od 100 km, tada je vaša prva (brza) pretraga površine 10000 kvadratnih kilometara i vjerojatno će dati oko 20 rezultata (na osnovu ravnomjerne raspodjele po površine oko 500 miliona kvadratnih kilometara), što znači da složeni proračun udaljenosti za ovaj upit izvodite 20 puta, a ne milion puta.

    • 21
      • 22

        Fantastičan savjet! Zapravo sam radio s programerom koji je napisao funkciju koja je povukla unutarnji kvadrat, a zatim rekurzivnu funkciju koja je napravila 'kvadrate' po obodu kako bi uključila i izuzela preostale točke. Rezultat je bio nevjerovatno brz - mogao je procijeniti milione bodova u mikrosekundama.

        Moj pristup je definitivno 'grub', ali sposoban. Hvala još jednom!

        • 23

          Doug,

          Pokušavao sam koristiti mysql i php za procjenu da li je dugačka lat točka unutar poligona. Znate li je li vaš prijatelj programer objavio primjere kako izvršiti ovaj zadatak. Ili znate neke dobre primjere. Hvala unaprijed.

  16. 24

    Pozdrav svima, ovo je moja testna SQL izjava:

    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 mi govori da ta udaljenost ne postoji kao stupac, mogu koristiti poredak po, mogu to učiniti bez WHERE, i to funkcionira, ali ne s njim ...

  17. 26

    Ovo je sjajno, ali upravo dok ptice lete. Bilo bi sjajno pokušati na ovaj način nekako integrirati API za google mape (možda koristeći ceste itd.) Samo da biste dali ideju koristeći drugi oblik prijevoza. Još uvijek nisam napravio simuliranu funkciju žarenja u PHP-u koja bi mogla ponuditi efikasno rješenje problema putničkog trgovca. Ali mislim da bih možda mogao ponovno koristiti neki vaš kod da bih to učinio.

  18. 27
  19. 28

    Dobar članak! Pronašao sam puno članaka koji opisuju kako izračunati udaljenost između dvije točke, ali stvarno sam tražio SQL isječak.

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

    Dva dana istraživanja kako bih napokon pronašao ovu stranicu koja rješava moj problem. Čini mi se da je bolje da uništim WolframAlphu i počnem matematiku. Promjena iz WHERE u HAVING ima moju skriptu u ispravnom stanju. HVALA TI

  25. 37
    • 38

      Hvala Georgi. Stalno sam dobivao stupac 'distance' koji nije pronađen. Jednom kada promijenim WHERE u HAVING, to je djelovalo poput šarma!

  26. 39

    Volio bih da je ovo prva stranica koju sam našao na ovome. Nakon isprobavanja mnogih različitih naredbi, jedina je radila ispravno i uz minimalne promjene potrebne da uklopim vlastitu bazu podataka.
    Puno hvala!

  27. 40

    Volio bih da je ovo prva stranica koju sam našao na ovome. Nakon isprobavanja mnogih različitih naredbi, jedina je radila ispravno i uz minimalne promjene potrebne da uklopim vlastitu bazu podataka.
    Puno hvala!

  28. 41
  29. 42
  30. 43
  31. 45
  32. 46
  33. 47
  34. 49
  35. 50
  36. 52
  37. 53
  38. 55
  39. 56
  40. 58

    hvala što ste objavili ovaj koristan članak,  
    ali iz nekog razloga bih želio pitati
    kako dobiti udaljenost između koorda unutar mysql db i koorda koje je korisnik umetnuo u php?
    za jasniji opis:
    1.korisnik mora umetnuti [id] za odabir određenih podataka iz db-a i samih korisničkih koordinata
    2. php datoteka dobiva ciljne podatke (koordine) pomoću [id] i zatim izračunava udaljenost između korisnika i ciljne točke

    ili možete jednostavno dobiti udaljenost od donjeg koda?

    $ 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) kao udaljenost OD `MyTable` WHERE udaljenost> =". $ Distance. " >>>> Mogu li odavde "izvaditi" udaljenost?
    hvala još jednom,
    Timmy S

  41. 60

    ok, sve što sam pokušao ne radi. Mislim, ono što imam djeluje, ali udaljenosti su daleko.

    Može li itko vidjeti šta nije u redu s ovim kodom?

    if (isset ($ _ POST ['submit'])) {$ z = $ _POST ['poštanski broj']; $ r = $ _POST ['radijus']; odjek „Rezultati za“. $ z; $ sql = mysql_query (“ODABERI DISTINCT m.zipcode, m.MktName, m.LocAddSt, m.LocAddCity, m.LocAddState, m.x1, m.y1, m.verified, z1.lat, z2.lon, z1. grad, z1.država OD mrk m, zip z1, zip z2 GDJE m.zipcode = z1.zipcode I z2.zipcode = $ z AND (3963 * acos (skraći (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 ") ili umri (mysql_error ()); while ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. ""; $ store = $ row ['LocAddSt']. ""; $ store. = $ row ['LocAddCity']. ",". $ row ['LocAddState']. " “. $ Row ['poštanski broj']; $ latitude1 = $ row ['lat']; $ longitude1 = $ row ['lon']; $ latitude2 = $ row ['y1']; $ longitude2 = $ row ['x1']; $ city = $ row ['grad']; $ state = $ row ['state']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = udaljenost ($ lat1, $ lon1, $ lat2, $ lon2); $ provjereno = $ red ['provjereno']; if ($ verified == '1') {echo “”; echo “”. $ store. ””; echo $ dis. " miljama daleko"; odjek “”; } else {echo “”. $ store. ””; echo $ dis. " miljama daleko"; odjek “”; }}}

    moje funkcije.php kod
    funkcija getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi') {$ theta = $ longitude1 - $ longitude2; $ udaljenost = (sin (deg2rad ($ latitude1)) * sin (deg2rad ($ latitude2))) + (cos (deg2rad ($ latitude1)) * cos (deg2rad ($ latitude2)) * cos (deg2rad ($ theta)) ); $ udaljenost = acos ($ udaljenost); $ udaljenost = rad2deg ($ udaljenost); $ udaljenost = $ udaljenost * 60 * 1.1515; prekidač ($ jedinica) {slučaj 'Mi': prekid; slučaj 'Km': $ distance = $ distance * 1.609344; } povratak (krug ($ udaljenost, 2)); }

    Unaprijed hvala

  42. 61
  43. 62

    Hej Douglas, sjajan članak. Smatram da je vaše objašnjenje geografskih koncepata i koda zaista zanimljivo. Moj jedini prijedlog bio bi razmak i uvlačenje koda za prikaz (poput Stackoverflow-a, na primjer). Razumijem da želite uštedjeti prostor, ali konvencionalni razmak / uvlačenje koda bi mi puno olakšalo čitanje i seciranje kao programera. U svakom slučaju, to je mala stvar. Nastavite sa sjajnim radom.

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

    čini se bržim (mysql 5.9) dvostruko koristiti formulu u select-u i gdje:
    $ 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 = 'ODABERI *,'. $ formula. ' kao udaljenost OD tablice GDJE '.. $ formula.' <= '. $ udaljenost;

  51. 71
  52. 72

    Puno hvala na striženju ovog članka. Vrlo je korisno.
    PHP je isprva stvoren kao jednostavna platforma za skriptiranje nazvana „Personal Home Page“. Danas je PHP (skraćenica od Hypertext Preprocessor) alternativa Microsoftovoj tehnologiji Active Server Pages (ASP).

    PHP je jezik otvorenog koda na strani servera koji se koristi za stvaranje dinamičnih web stranica. Može se ugraditi u HTML. PHP se obično koristi zajedno sa MySQL bazom podataka na Linux / UNIX web serverima. To je vjerovatno najpopularniji skriptni jezik.

  53. 73

    Pronašao sam da gore rješenje ne radi ispravno.
    Moram promijeniti na:

    $ qqq = "ODABERI *, (((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) kao udaljenost OD `registra`“;

  54. 75
  55. 76

    Pozdrav, molim vas, zaista će mi trebati vaša pomoć oko toga.

    Zatražio sam zahtjev za svoj web server http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = geografska širina
    -2.23389 = Zemljopisna dužina
    i 20 = udaljenost koju želim dobiti

    Međutim, koristeći vašu formulu, on dohvaća sve redove u mom db-u

    $ rezultati = 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) kao udaljenost OD markera IMAJU udaljenost> = “. $ Udaljenost));

    [{„Id“: 1, „name“: „Frankie Johnnie & Luigo Too“, „adresa“: „939 W El Camino Real, Mountain View, Kalifornija“, „lat“: 37.386337280273, „lng“: - 122.08582305908, ”Distance”: 16079.294719663}, {„id”: 2, „name”: „Picerija na istočnoj obali Amičija”, „adresa”: „790 Castro St, Mountain View, CA”, „lat”: 37.387138366699, „lng”: -122.08323669434, "udaljenost": 16079.175940152}, {"id": 3, "ime": "Kapp's Pizza Bar & Grill", "adresa": "191 Castro St, Mountain View, CA", "lat": 37.393886566162, ”Lng”: - 122.07891845703, “udaljenost”: 16078.381373826}, {“id”: 4, “name”: “Pizza okruglog stola: Mountain View”, “adresa”: ”570 N Shoreline Blvd, Mountain View, CA”, "Lat": 37.402652740479, "lng": - 122.07935333252, "udaljenost": 16077.420540582}, {"id": 5, "ime": "Pizza i tjestenine Tony & Alba", "adresa": "619 Escuela Ave, Mountain Pogled, Kalifornija ”,” lat ”: 37.394012451172,” lng ”: - 122.09552764893,“ udaljenost ”: 16078.563225154}, {“ id ”: 6,“ name ”:“ Oreganova pica na drva ”,“ adresa ”:” 4546 El Camino Real, Los Altos, CA "," lat ": 37.401725769043," lng ": - 122.11464691162," udaljenost ": 16077.937560795}, {" id ”: 7,” name ”:” Barovi i rešetke ”,“ adresa ”:“ 24 Whiteley Street, Manchester ”,“ lat ”: 53.485118865967,“ lng ”: - 2.1828699111938,“ distance ”: 8038.7620112314}]

    Želim dohvatiti samo redove s 20 milja, ali donosi sve redove. Molim te, šta radim pogrešno

Šta ti misliš?

Ova stranica koristi Akismet kako bi smanjila neželjenu poštu. Saznajte kako se podaci vašeg komentara obrađuju.