PHP in SQL: Izračunajte ali poizvedite razdaljo velikega kroga med točkama zemljepisne širine in dolžine s formulo Haversine

Formula Haversine - Izračunajte razdaljo velikega kroga s PHP ali MySQL

Ta mesec že kar nekaj programiram v PHP in MySQL v zvezi z GIS. Snooping po mreži, sem bil dejansko težko najti nekaj Geografski izračuni najti razdaljo med dvema lokacijama, zato sem jih želel deliti tukaj.

Letalski zemljevid Evropa z veliko krožno razdaljo

Preprost način izračuna razdalje med dvema točkama je uporaba pitagorejske formule za izračun hipotenuze trikotnika (A² + B² = C²). To je znano kot Evklidska razdalja.

To je zanimiv začetek, vendar to ne velja za Geografijo, saj je razdalja med črtami zemljepisne širine in dolžine ni enaka razdalja narazen. Ko se približate ekvatorju, se širinske črte še bolj ločijo. Če uporabljate nekakšno preprosto enačbo triangulacije, lahko na eni lokaciji zaradi ukrivljenosti Zemlje natančno izmeri razdaljo, na drugem pa strašno napačno.

Oddaljenost od velikega kroga

Poti, ki se prevozijo na velike razdalje okoli Zemlje, so znane kot Oddaljenost od velikega kroga. To je… najkrajša razdalja med dvema točkama na krogli je drugačna od točk na ravnem zemljevidu. Združite to z dejstvom, da črte zemljepisne širine in dolžine niso enako oddaljene ... in imate težek izračun.

Tu je fantastična video razlaga, kako delujejo Veliki krogi.

Formula Haversine

Razdalja z uporabo ukrivljenosti Zemlje je vključena v Formula Haversine, ki s pomočjo trigonometrije omogoča ukrivljenost zemlje. Ko najdete razdaljo med dvema krajema na zemlji (zračne linije), je ravna črta v resnici lok.

To velja v zračnem letu - ste že kdaj pogledali dejanski zemljevid letov in opazili, da so obokani? To je zato, ker je krajši lok v loku med dvema točkama kot neposredno do lokacije.

PHP: Izračunajte razdaljo med dvema točkama zemljepisne širine in dolžine

Kakorkoli že, tukaj je formula PHP za izračun razdalje med dvema točkama (skupaj s pretvorbo Mile v kilometer), zaokroženo na dve decimalni mesti.

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: Pridobivanje vseh zapisov znotraj obsega z izračunom razdalje v miljah z uporabo zemljepisne širine in dolžine

SQL lahko uporabite tudi za izračun, da najdete vse zapise na določeni razdalji. V tem primeru bom poizvedel MyTable v MySQL, da najdem vse zapise, ki so manjši ali enaki spremenljivki $ distance (v miljah) do moje lokacije na $ latitude in $ longitude:

Poizvedba za pridobivanje vseh zapisov znotraj določenega razdalja z izračunom razdalje v miljah med dvema točkama zemljepisne širine in dolžine sta:

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

To boste morali prilagoditi:

  • $ zemljepisne dolžine - to je spremenljivka PHP, pri kateri prehajam zemljepisno dolžino točke.
  • $ zemljepisne širine - to je spremenljivka PHP, pri kateri prehajam zemljepisno dolžino točke.
  • $ razdalja - to je razdalja, za katero bi radi našli vse zapise, ki so manjši ali enaki.
  • miza - to je tabela ... to boste želeli nadomestiti z imenom vaše tabele.
  • zemljepisna širina - to je polje vaše zemljepisne širine.
  • zemljepisne dolžine - to je polje vaše zemljepisne dolžine.

SQL: Pridobivanje vseh zapisov znotraj obsega z izračunom razdalje v kilometrih z uporabo zemljepisne širine in dolžine

In tukaj je poizvedba SQL z uporabo kilometrov v 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."

To boste morali prilagoditi:

  • $ zemljepisne dolžine - to je spremenljivka PHP, pri kateri prehajam zemljepisno dolžino točke.
  • $ zemljepisne širine - to je spremenljivka PHP, pri kateri prehajam zemljepisno dolžino točke.
  • $ razdalja - to je razdalja, za katero bi radi našli vse zapise, ki so manjši ali enaki.
  • miza - to je tabela ... to boste želeli nadomestiti z imenom vaše tabele.
  • zemljepisna širina - to je polje vaše zemljepisne širine.
  • zemljepisne dolžine - to je polje vaše zemljepisne dolžine.

To kodo sem uporabil v platformi za kartiranje podjetij, ki smo jo uporabili za maloprodajo z več kot 1,000 lokacijami po Severni Ameriki in je delovala čudovito.

76 Komentarji

  1. 1

    Najlepša hvala za delitev. To je bilo enostavno kopiranje in lepljenje in odlično deluje. Prihranili ste mi veliko časa.
    FYI za vsakogar, ki prenaša na C:
    double deg2rad (double deg) {return stopinje * (3.14159265358979323846 / 180.0); }

  2. 2

    Zelo lep prispevek - zelo lepo se je obnesel - spremeniti sem moral samo ime mize, ki je držala lat. Deluje precej hitro do .. Imam razmeroma majhno število lat-long (<400), vendar mislim, da bi to lepo obsegalo. Tudi lepo mesto - pravkar sem ga dodal na svoj račun del.icio.us in ga bom redno preverjal.

  3. 4
  4. 5
  5. 8

    mislim, da vaš SQL potrebuje izjavo o obstoju.
    namesto WHERE razdalja <= $ razdalja, ki jo boste morda morali
    uporabite HAVING distance <= $ distance

    drugače hvala, ker ste mi prihranili kup časa in energije.

  6. 10
  7. 11
  8. 12

    Najlepša hvala za skupno rabo te kode. To mi je prihranilo veliko časa za razvoj. Hvala tudi vašim bralcem, ki so opozorili, da je za MySQL 5.x potrebna izjava HAVING. Zelo koristno.

  9. 14
  10. 15

    Zdravo,

    Še eno vprašanje. Ali obstaja formula za nize NMEA, kot je spodnja?

    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

    Ugotovil sem tudi, da WHERE zame ni deloval. Spremenjeno v HAVING in vse deluje brezhibno. Sprva nisem prebral komentarjev in jih znova napisal z ugnezdenim izbirnikom. Oboje bo delovalo v redu.

  12. 17
  13. 18

    Izredno koristno, najlepša hvala! Imel sem nekaj težav z novim “HAVING”, namesto “KJE”, a ko sem prebral komentarje tukaj (po približno pol ure frustriranega škrtanja z zobmi = P), mi je lepo uspelo. Hvala ^ _ ^

  14. 19
  15. 20

    Upoštevajte, da bo taka izbrana izjava zelo računsko intenzivna in zato počasna. Če imate veliko teh poizvedb, lahko stvari precej hitro zataknejo.

    Precej manj intenziven pristop je zagon prve (surove) izbire z uporabo kvadratnega območja, ki je določeno z izračunano razdaljo, tj. lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, podobno kot lon. latdiff ~ = razdalja / 1 (za km) ali razdalja / 2 za milje, saj je 1 stopinja zemljepisne širine ~ 2 km (rahlo odstopanje, ker je zemlja rahlo ovalna, vendar zadostna za ta namen). londiff = razdalja / (abs (cos (deg111rad (zemljepisna širina)) * 69)) - ali 1 milj (dejansko lahko vzamete nekoliko večji kvadrat, da upoštevate razlike). Nato vzemite rezultat in ga vnesite v radialni izbor. Ne pozabite upoštevati koordinat zunaj meja - tj. Obseg sprejemljive zemljepisne dolžine je -111 do +2 in obseg sprejemljive zemljepisne širine je -111 do +69 - v primeru, da vaš latdiff ali londiff teče zunaj tega območja . Upoštevajte, da v večini primerov to morda ne bo veljalo, saj vpliva le na izračune preko črte skozi pacifiški ocean od pola do pola, čeprav seka del Čukotke in del Aljaske.

    S tem dosežemo znatno zmanjšanje števila točk, na podlagi katerih opravite ta izračun. Če imate v zbirki podatkov milijon globalnih točk, porazdeljenih približno enakomerno in želite iskati v razdalji 100 km, potem je vaše prvo (hitro) iskanje površine 10000 kvadratnih kilometrov in bo verjetno prineslo približno 20 rezultatov površine približno 500 milijonov kvadratnih kilometrov), kar pomeni, da za to poizvedbo namesto milijonkrat izvedete zapleteni izračun razdalje 20-krat.

    • 21
      • 22

        Fantastičen nasvet! Dejansko sem sodeloval z razvijalcem, ki je napisal funkcijo, ki je potegnila notranji kvadrat, nato pa rekurzivno funkcijo, ki je naredila "kvadratke" po obodu, da je vključila in izključila preostale točke. Rezultat je bil neverjetno hiter - v mikrosekundah je lahko ocenil milijone točk.

        Moj zgornji pristop je vsekakor "surov", a sposoben. Hvala še enkrat!

        • 23

          Doug,

          Poskušal sem uporabiti mysql in php, da ocenim, ali je lat dolga točka znotraj poligona. Ali veste, ali je vaš prijatelj razvijalec objavil primere, kako to nalogo izvesti. Ali pa poznate kakšne dobre primere. Hvala vnaprej.

  16. 24

    Pozdravljeni, to je moja testna izjava SQL:

    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

    in Mysql mi govori, da razdalja ne obstaja kot stolpec, lahko uporabim vrstni red do, lahko to storim brez WHERE in deluje, vendar ne z njim ...

  17. 26

    To je super, vendar tako kot ptice letijo. Odlično bi bilo, če bi nekako poskušali v to vključiti API za google maps (morda z uporabo cest itd.), Samo če bi dali idejo z drugo obliko prevoza. V PHP še moram narediti simulirano funkcijo žarjenja, ki bi lahko ponudila učinkovito rešitev problema potujočega prodajalca. Ampak mislim, da bom morda lahko ponovno uporabil nekaj vaše kode.

  18. 27
  19. 28

    Dober članek! Našel sem veliko člankov, ki opisujejo, kako izračunati razdaljo med dvema točkama, vendar sem resnično iskal delček SQL.

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

    2 dni raziskav, da končno najdem to stran, ki reši moj problem. Zdi se mi, da je bolje, da izločim WolframAlfo in se naučim matematike. Sprememba WHERE v HAVING ima moj skript v dobrem stanju. HVALA VAM

  25. 37
  26. 39

    Želim si, da bi bila to prva stran, ki sem jo našel na tem. Po preizkusu številnih različnih ukazov je edini delal pravilno in z minimalnimi spremembami, potrebnimi za lastno bazo podatkov.
    Najlepša hvala!

  27. 40

    Želim si, da bi bila to prva stran, ki sem jo našel na tem. Po preizkusu številnih različnih ukazov je edini delal pravilno in z minimalnimi spremembami, potrebnimi za lastno bazo podatkov.
    Najlepša 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 za objavo tega koristnega članka,  
    ampak iz neznanega razloga bi rad vprašal
    kako najti razdaljo med koordami znotraj mysql db in koordami, ki jih uporabnik vstavi v php?
    za bolj jasen opis:
    1. uporabnik mora vstaviti [id] za izbiro določenih podatkov iz db in uporabnikove meje
    2. datoteka php pridobi ciljne podatke (koordine) s pomočjo [id] in nato izračuna razdaljo med uporabnikom in ciljno točko

    ali lahko preprosto dobite razdaljo od spodnje kode?

    $ 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) kot razdalja FROM `MyTable` WHERE razdalja> =". $ Distance. " >>>> ali lahko od tod "odstranim" razdaljo?
    Hvala še enkrat,
    Timmy S.

  41. 60

    ok, vse, kar sem poskusil, ne deluje. Mislim, kar imam, deluje, vendar so razdalje daleč.

    Bi lahko kdo morda videl, kaj je narobe s to kodo?

    if (isset ($ _ POST ['submitted'])) {$ z = $ _POST ['poštna številka']; $ r = $ _POST ['radij']; odmev »Rezultati za«. $ z; $ sql = mysql_query (“IZBERI DISTINCT m.zipcode, m.MktName, m.LocAddSt, m.LocAddCity, m.LocAddState, m.x1, m.y1, m.verified, z1.lat, z2.lon, z1. mesto, z1.država OD mrk m, zip z1, zip z2 KJE m.zipcode = z1.zipcode IN z2.zipcode = $ z IN (3963 * acos (okrnilo (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 ") ali umri (mysql_error ()); medtem ko ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. ""; $ store = $ row ['LocAddSt']. ""; $ store. = $ row ['LocAddCity']. ",". $ row ['LocAddState']. " ". $ Row ['zipcode']; $ latitude1 = $ row ['lat']; $ longitude1 = $ row ['lon']; $ latitude2 = $ row ['y1']; $ longitude2 = $ row ['x1']; $ city = $ row ['city']; $ state = $ row ['state']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = razdalja ($ lat1, $ lon1, $ lat2, $ lon2); $ preverjeno = $ vrstica ['preverjeno']; if ($ preverjeno == '1') {echo “”; echo “”. $ store. ””; echo $ dis. ”Milja (milj) stran”; odmev “”; } else {echo “”. $ store. ””; echo $ dis. ”Milja (milj) stran”; odmev “”; }}}

    moje funkcije.php koda
    funkcija getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi') {$ theta = $ longitude1 - $ longitude2; $ razdalja = (sin (deg2rad ($ latitude1)) * sin (deg2rad ($ latitude2))) + (cos (deg2rad ($ latitude1)) * cos (deg2rad ($ latitude2)) * cos (deg2rad ($ theta)) ); $ razdalja = acos ($ razdalja); $ razdalja = rad2deg ($ razdalja); $ razdalja = $ razdalja * 60 * 1.1515; stikalo ($ enota) {primer 'Mi': odmor; primer 'Km': $ razdalja = $ razdalja * 1.609344; } vrnitev (krog ($ razdalja, 2)); }

    Hvala v naprej

  42. 61
  43. 62

    Hej Douglas, super članek. Vaša razlaga geografskih konceptov in kodeksa se mi zdi res zanimiva. Moj edini predlog bi bil, da razstavite in zamaknete kodo za prikaz (na primer Stackoverflow). Razumem, da želite prihraniti prostor, toda običajni razmik / zamik kode bi mi kot programerju veliko olajšal branje in seciranje. Kakorkoli že, to je majhna stvar. Nadaljujte z odličnim delom.

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

    zdi se hitreje (mysql 5.9), če dvakrat uporabimo formulo v izboru in kje:
    $ 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 = 'SELECT *,'. $ formula. ' kot oddaljenost od tabele KJE '.. $ formula.' <= '. $ razdalja;

  51. 71
  52. 72

    Najlepša hvala za striženje tega članka. Zelo koristno.
    PHP je bil sprva ustvarjen kot preprosta skriptna platforma, imenovana »Osebna domača stran«. Dandanes je PHP (okrajšava za Hypertext Preprocessor) alternativa Microsoftovi tehnologiji Active Server Pages (ASP).

    PHP je odprtokodni jezik na strani strežnika, ki se uporablja za ustvarjanje dinamičnih spletnih strani. Lahko se vdela v HTML. PHP se običajno uporablja skupaj z bazo podatkov MySQL na spletnih strežnikih Linux / UNIX. Verjetno je najbolj priljubljen skriptni jezik.

  53. 73

    Ugotovil sem, da zgornja rešitev ne deluje pravilno.
    Moram spremeniti na:

    $ qqq = “SELECT *, (((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) kot razdalja OD `register`“;

  54. 75
  55. 76

    Pozdravljeni, prosim, res bom potreboval vašo pomoč pri tem.

    Zahteval sem svoj spletni strežnik http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = zemljepisna širina
    -2.23389 = zemljepisna dolžina
    in 20 = razdalja, ki jo želim pridobiti

    Vendar pa z uporabo formule pridobi vse vrstice v mojem db

    $ results = 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) kot razdalja OD markerjev, ki IMAJO razdaljo> = “. $ Razdalja));

    [{"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": "Pizzeria Amici's East Coast", "address": "790 Castro St, Mountain View, CA", "lat": 37.387138366699, "lng": -122.08323669434, "razdalja": 16079.175940152}, {"id": 3, "ime": "Kapp's Pizza Bar & Grill", "naslov": "191 Castro St, Mountain View, CA", "lat": 37.393886566162, ”Lng”: - 122.07891845703, “distance”: 16078.381373826}, {“id”: 4, ”name”: ”Pica okrogle mize: Mountain View”, ”naslov”: ”570 N Shoreline Blvd, Mountain View, CA”, "Lat": 37.402652740479, "lng": - 122.07935333252, "distance": 16077.420540582}, {"id": 5, "name": "Pizza in testenine Tony & Alba", "naslov": "619 Escuela Ave, Mountain Pogled, 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}]

    Želim pridobiti samo vrstice z 20 miljami, vendar prinese vse vrstice. Prosim, kaj delam narobe

Kaj menite?

Ta stran uporablja Akismet za zmanjšanje nezaželene pošte. Preberite, kako se vaš komentar obravnava.