PHP un SQL: aprēķiniet vai vaicājiet lielo apļa attālumu starp platuma un garuma punktiem ar Haversine formulu

Haversines formula - aprēķiniet lielo apļa attālumu ar PHP vai MySQL

Šomēnes esmu diezgan daudz programmējis PHP un MySQL attiecībā uz ĢIS. Snooping ap tīklu, man faktiski bija grūti atrast dažus no Ģeogrāfiskie aprēķini lai atrastu attālumu starp divām vietām, tāpēc es vēlējos tos kopīgot šeit.

Lidojuma karte Eiropā ar lielu apļa attālumu

Vienkāršs veids, kā aprēķināt attālumu starp diviem punktiem, ir Pitagora formulas izmantošana, lai aprēķinātu trijstūra hipotenūzu (A² + B² = C²). Tas ir pazīstams kā Eiklīda attālums.

Tas ir interesants sākums, bet tas neattiecas uz ģeogrāfiju, jo attālums starp platuma un garuma līnijām ir nav vienāds attālums atsevišķi. Tuvojoties ekvatoram, platuma līnijas attālinās. Ja izmantojat kādu vienkāršu triangulācijas vienādojumu, tas var precīzi izmērīt attālumu vienā vietā un šausmīgi nepareizi citā vietā Zemes izliekuma dēļ.

Liels apļa attālums

Maršruti, kas ir veikti lielos attālumos ap Zemi, ir pazīstami kā Liels apļa attālums. Tas ir ... īsākais attālums starp diviem sfēras punktiem atšķiras no punktiem plakanajā kartē. Apvienojiet to ar faktu, ka platuma un garuma līnijas nav vienādā attālumā ... un jums ir grūti aprēķināt.

Šeit ir fantastisks video skaidrojums par to, kā darbojas lieliski loki.

Haversines formula

Attālums, izmantojot Zemes izliekumu, ir iestrādāts Haversines formula, kas izmanto trigonometriju, lai pieļautu zemes izliekumu. Kad atrodat attālumu starp 2 vietām uz zemes (kā lido vārna), taisna līnija patiešām ir loka.

Tas ir piemērojams gaisa lidojumos - vai esat kādreiz apskatījis faktisko lidojumu karti un pamanījis, ka tie ir izliekti? Tas ir tāpēc, ka lidot arkā starp diviem punktiem ir īsāk nekā tieši uz vietu.

PHP: Aprēķiniet attālumu starp 2 platuma un garuma punktiem

Jebkurā gadījumā šeit ir PHP formula, lai aprēķinātu attālumu starp diviem punktiem (kopā ar konvertēšanu Mile pret Kilometru), noapaļojot līdz divām zīmēm aiz komata.

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: visu ierakstu izgūšana diapazonā, aprēķinot attālumu jūdzēs, izmantojot platumu un garumu

Ir iespējams arī izmantot SQL, lai veiktu aprēķinu, lai atrastu visus ierakstus noteiktā attālumā. Šajā piemērā es gatavojas vaicāt MyTable MySQL, lai atrastu visus ierakstus, kas ir mazāki vai vienādi ar mainīgo $ distance (jūdzēs) līdz manai atrašanās vietai $ latitude un $ longitude:

Vaicājums visu ierakstu izgūšanai noteiktā attālums aprēķinot attālumu jūdzēs starp diviem platuma un garuma punktiem ir:

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

Jums tas būs jāpielāgo:

  • $ garums - tas ir PHP mainīgais, kur es eju garām punkta garumam.
  • $ platums - tas ir PHP mainīgais, kur es eju garām punkta garumam.
  • $ attālums - tas ir attālums, ar kuru vēlaties atrast visus ierakstus, kas ir mazāki vai vienādi.
  • tabula - šī ir tabula ... jūs vēlaties to aizstāt ar savu galda nosaukumu.
  • platums - tas ir jūsu platuma lauks.
  • garums - tas ir jūsu garuma lauks.

SQL: Visu ierakstu izgūšana diapazonā, aprēķinot attālumu kilometros, izmantojot platumu un garumu

Un šeit ir SQL vaicājums, izmantojot MySQL kilometrus:

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

Jums tas būs jāpielāgo:

  • $ garums - tas ir PHP mainīgais, kur es eju garām punkta garumam.
  • $ platums - tas ir PHP mainīgais, kur es eju garām punkta garumam.
  • $ attālums - tas ir attālums, ar kuru vēlaties atrast visus ierakstus, kas ir mazāki vai vienādi.
  • tabula - šī ir tabula ... jūs vēlaties to aizstāt ar savu galda nosaukumu.
  • platums - tas ir jūsu platuma lauks.
  • garums - tas ir jūsu garuma lauks.

Es izmantoju šo kodu uzņēmuma kartēšanas platformā, kuru izmantojām mazumtirdzniecības veikalam ar vairāk nekā 1,000 atrašanās vietām visā Ziemeļamerikā, un tas darbojās lieliski.

77 Komentāri

  1. 1

    Liels paldies par dalīšanos. Tas bija viegli kopējams un ielīmējams darbs, un tas lieliski darbojas. Jūs esat ietaupījis mani daudz laika.
    FYI ikvienam, kurš portē uz C:
    dubultā deg2rad (dubultā deg) {atgriešanās deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Ļoti jauks sūtījums - strādāja ļoti jauki - man vajadzēja nomainīt tikai galda nosaukumu, turot garo garumu. Tas darbojas diezgan ātri .. Man ir samērā maz lat-long garumu (<400), bet es domāju, ka tas varētu skaisti palielināties. Arī jauka vietne - es tikko pievienoju to savam del.icio.us kontam un regulāri pārbaudīšu.

  3. 4
  4. 5
  5. 8

    Es domāju, ka jūsu SQL ir nepieciešams paziņojums.
    nevis WHERE distance <= $ distance, kas jums varētu būt nepieciešama
    izmantojiet HAVING distance <= $ distance

    pretējā gadījumā paldies, ka ietaupījāt man laiku un enerģiju.

  6. 10
  7. 11
  8. 12

    Liels paldies par šī koda kopīgošanu. Tas man ietaupīja daudz attīstības laika. Paldies arī lasītājiem par norādīšanu, ka MySQL 5.x ir nepieciešams HAVING paziņojums. Ļoti izpalīdzīgs.

  9. 14
  10. 15
  11. 16

    Es arī atklāju, ka WHERE man nederēja. Mainīja to uz HAVING, un viss darbojas nevainojami. Sākumā es neizlasīju komentārus un pārrakstīju tos, izmantojot ligzdotu atlasi. Abi darbosies lieliski.

  12. 17
  13. 18

    Neticami noderīgi, liels paldies! Man bija dažas problēmas ar jauno “HAVING”, nevis ar “WHERE”, bet, kad es šeit izlasīju komentārus (pēc apmēram pusstundas neapmierinātā zobu griešanas = P), es to labi darbojos. Paldies ^ _ ^

  14. 19
  15. 20

    Paturiet prātā, ka šāds atlasīts paziņojums būs ļoti intensīvs skaitļošanas ziņā un tāpēc lēns. Ja jums ir daudz šo vaicājumu, tas diezgan ātri var aizsprostot.

    Daudz mazāk intensīva pieeja ir veikt pirmo (neapstrādāto) atlasi, izmantojot kvadrātveida laukumu, ko nosaka aprēķinātais attālums, ti, “izvēlēties * no galda nosaukuma, kur platums starp lat1 un lat2 un garums starp lon1 un lon2”. lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, līdzīgi kā lon. latdiff ~ = attālums / 111 (km) vai attālums / 69 jūdzes, jo 1 platuma grāds ir ~ 111 km (nelielas variācijas, jo zeme ir nedaudz ovāla, bet pietiek šim nolūkam). londifs = attālums / (abs (cos (deg2rad (platums)) * 111)) - vai 69 jūdzes (lai ņemtu vērā variācijas, faktiski varat ņemt nedaudz lielāku kvadrātu). Pēc tam ņemiet rezultātu un ievadiet to radiālajā atlasē. Vienkārši neaizmirstiet ņemt vērā ārpus robežas esošās koordinātas - ti, pieņemamā garuma diapazons ir no -180 līdz +180 un pieņemamā platuma diapazons ir no -90 līdz +90 - ja jūsu latdifs vai londifs iet ārpus šī diapazona . Ņemiet vērā, ka vairumā gadījumu tas var nebūt piemērojams, jo tas ietekmē tikai aprēķinus, kas veikti pāri līnijai caur Kluso okeānu no pole līdz pole, lai gan tas krustojas ar daļu no chukotka un daļu no alaska.

    Tas, ko mēs panākam, ir ievērojams punktu skaita samazinājums, pret kuriem jūs veicat šo aprēķinu. Ja datu bāzē ir miljons globālo punktu, kas sadalīti aptuveni vienmērīgi un vēlaties meklēt 100 km attālumā, tad jūsu pirmā (ātrā) meklēšana ir 10000 kv km platībā un, iespējams, sniegs aptuveni 20 rezultātus (pamatojoties uz vienmērīgu sadalījumu pa virsmas laukums ir aptuveni 500 miljoni kvadrātkilometru), kas nozīmē, ka kompleksā attāluma aprēķinu šim vaicājumam izpildāt 20 reizes, nevis miljonu reižu.

    • 21
      • 22

        Fantastisks padoms! Es faktiski strādāju ar izstrādātāju, kurš uzrakstīja funkciju, kas izvilka iekšējo kvadrātu, un pēc tam rekursīvo funkciju, kas ap perimetru izveidoja "kvadrātus", lai iekļautu un izslēgtu atlikušos punktus. Rezultāts bija neticami ātrs rezultāts - viņš varēja novērtēt miljoniem punktu mikrosekundēs.

        Mana pieeja iepriekš noteikti ir “neapstrādāta”, bet spējīga. Vēlreiz paldies!

        • 23

          Dags,

          Esmu mēģinājis izmantot mysql un php, lai novērtētu, vai lata garais punkts atrodas daudzstūrī. Vai zināt, vai jūsu draugs izstrādātājs ir publicējis piemērus, kā veikt šo uzdevumu? Vai arī jūs zināt kādus labus piemērus. Paldies jau iepriekš.

  16. 24

    Sveiki visiem, tas ir mans testa SQL paziņojums:

    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

    un MySQL man saka, ka attālums nepastāv kā kolonna, es varu izmantot kārtību pēc, es varu to izdarīt bez KUR, un tas darbojas, bet ne ar to ...

  17. 26

    Tas ir lieliski, tomēr tieši tāpat kā putni lido. Būtu lieliski mēģināt kaut kā iekļaut tajā google maps API (iespējams, izmantojot ceļus utt.), Lai tikai radītu ideju, izmantojot citu transporta veidu. Man vēl ir jāizveido simulēta atlaidināšanas funkcija PHP, kas spētu piedāvāt efektīvu ceļojošā pārdevēja problēmas risinājumu. Bet es domāju, ka es varētu atkārtoti izmantot dažus jūsu kodus, lai to izdarītu.

  18. 27
  19. 28

    Labs raksts! Es atradu daudz rakstu, kuros aprakstīts, kā aprēķināt attālumu starp diviem punktiem, bet es patiešām meklēju SQL fragmentu.

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

    2 dienu pētījumi, lai beidzot atrastu šo lapu, kas atrisina manu problēmu. Izskatās, ka es labāk izsistu savu WolframAlpha un pielaboju matemātiku. Pārejot no WHERE uz HAVING, mans skripts ir darba kārtībā. PALDIES

  25. 37
    • 38

      Paldies Georgi. Es turpināju atrast kolonnas “attālums”, kas nav atrasts. Kad es mainīju WHERE uz HAVING, tas darbojās kā šarms!

  26. 39

    Es vēlos, lai šī būtu pirmā lapa, kuru es šajā vietnē atradu. Pēc daudzu dažādu komandu izmēģināšanas tas bija vienīgais, kas darbojās pareizi un ar minimālām izmaiņām, kas nepieciešamas, lai ietilptu manā datu bāzē.
    Thanks daudz!

  27. 40

    Es vēlos, lai šī būtu pirmā lapa, kuru es šajā vietnē atradu. Pēc daudzu dažādu komandu izmēģināšanas tas bija vienīgais, kas darbojās pareizi un ar minimālām izmaiņām, kas nepieciešamas, lai ietilptu manā datu bāzē.
    Thanks daudz!

  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

    paldies par šī noderīgā raksta ievietošanu,  
    bet nez kāpēc es gribētu pajautāt
    kā iegūt attālumu starp koordinātām mysql db iekšienē un koordiem, kurus lietotājs ievietojis php?
    skaidrāk aprakstīt:
    1. lietotājam ir jāievieto [id], lai atlasītu norādītos datus no db un paša lietotāja koordinātām
    2. php fails iegūst mērķa datus (koordinātes), izmantojot [id], un pēc tam aprēķina attālumu starp lietotāju un mērķa punktu

    vai vienkārši varat iegūt attālumu no zemāk redzamā koda?

    $ qry = “SELECT *, (((acos (sin ((“. $ platums. ”* pi () / 180)) * sin ((“ Platums ”* pi () / 180)) + cos ((“. $ platums. ”* pi () / 180)) * cos ((“ Platums ”* pi () / 180)) * cos (((“. $ garums. ”-“ Garums ”) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) kā attālums NO `MyTable` WHERE attālums> =“. $ Distance. ” >>>> vai es varu “izņemt” attālumu no šejienes?
    vēlreiz paldies,
    Timijs S

  41. 60

    labi, viss, ko esmu mēģinājis, nedarbojas. Es domāju, tas, kas man ir, darbojas, bet attālumi ir tālu.

    Vai kāds varētu redzēt, kas ir nepareizs ar šo kodu?

    if (isset ($ _ POST ['iesniegts'])) {$ z = $ _POST ['zipcode']; $ r = $ _POST ['rādiuss']; atbalss “Rezultāti vaicājumam“. $ 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. pilsēta, z1.valsts NO mrk m, zip z1, zip z2 KUR m.zipcode = z1.zipcode AND z2.zipcode = $ z AND (3963 * acos (saīsināt (sin (z2.lat / 57.2958) * grēks (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 ") vai mirst (mysql_error ()); while ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. ""; $ veikals = $ rinda ['LocAddSt']. ””; $ store. = $ row ['LocAddCity']. ", '. $ row [' LocAddState ']." $ .Rinda ['pasta indekss']; $ latitude1 = $ rinda ['lat']; $ longitude1 = $ rinda ['lon']; $ platums2 = $ rinda ['y1']; $ longitude2 = $ rinda ['x1']; $ city = $ row ['pilsēta']; $ valsts = $ rinda ['valsts']; $ dis = getnew ($ platums1, $ garums1, $ platums2, $ garums2, $ vienība = 'Mi'); // $ dis = attālums ($ lat1, $ lon1, $ lat2, $ lon2); $ verified = $ row ['verified']; ja ($ verified == '1') {atbalss ""; atbalss “”. $ veikals. ””; atbalss $ dis. " jūdžu attālumā"; atbalss “”; } cits {atbalss “”. $ veikals. ””; atbalss $ dis. " jūdžu attālumā"; atbalss “”; }}}

    manas funkcijas.php kods
    funkcija getnew ($ platums1, $ garums1, $ platums2, $ garums2, $ vienība = 'Mi') {$ theta = $ garums1 - $ garums2; $ attālums = (grēks (deg2rad ($ platums1)) * grēks (deg2rad ($ platums2))) + (cos (deg2rad ($ platums1)) * cos (deg2rad ($ platums2)) * cos (deg2rad ($ teta)) ); $ distance = acos ($ distance); $ distance = rad2deg ($ distance); $ attālums = $ attālums * 60 * 1.1515; slēdzis ($ vienība) {gadījums 'Mi': pārtraukums; gadījums 'Km': $ attālums = $ attālums * 1.609344; } atgriešanās (apaļa ($ distance, 2)); }

    Pateicos jau iepriekš

  42. 61
  43. 62

    Hey Douglas, lielisks raksts. Man patiešām šķita interesants jūsu skaidrojums par ģeogrāfiskajiem jēdzieniem un kodu. Mans vienīgais ieteikums būtu atstarot un ievilkt kodu parādīšanai (piemēram, Stackoverflow). Es saprotu, ka vēlaties ietaupīt vietu, taču parastais kodu atstarpes / atkāpe man kā programmētājam ļautu daudz vieglāk lasīt un sadalīt. Jebkurā gadījumā tā ir maza lieta. Turpini iesākto.

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

    šķiet ātrāk (mysql 5.9) divreiz izmantot formulu atlasē un kur:
    $ formula = “(((acos (sin ((“. $ platums. ”* pi () / 180)) * grēks ((“ Platums ”* pi () / 180)) + cos ((“. $ platums. ”* Pi () / 180)) * cos ((“ Platums ”* pi () / 180)) * cos (((“. $ Garums. ”-“ Garums ”) * pi () / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) ”;
    $ sql = 'SELECT *,'. $ formula. ' kā attālums NO tabulas WHERE '.. $ formula.' <= '. $ attālums;

  51. 71
  52. 72

    Liels paldies par šī raksta griešanu. Tas ir ļoti noderīgi.
    Sākumā PHP tika izveidota kā vienkārša skriptu platforma ar nosaukumu “Personīgā mājas lapa”. Mūsdienās PHP (saīsne no Hypertext Preprocessor) ir Microsoft aktīvo serveru lapu (ASP) alternatīva.

    PHP ir atvērtā koda servera valoda, ko izmanto dinamisku tīmekļa lapu izveidošanai. To var iegult HTML. PHP parasti lieto kopā ar MySQL datu bāzi Linux / UNIX tīmekļa serveros. Tā, iespējams, ir vispopulārākā skriptu valoda.

  53. 73

    Es atklāju, ka iepriekš minētais risinājums nedarbojas pareizi.
    Man jāpārslēdzas uz:

    $ qqq = “SELECT *, (((acos (sin ((“. $ platums. ”* pi () / 180)) * sin ((` latt` * pi () / 180)) + cos ((”. $ platums. “* pi () / 180)) * cos ((“ latt ”* pi () / 180)) * cos (((” ”. $ garums.“ - “longt”) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515) kā attālums no `reģistra``;

  54. 75
  55. 76

    Sveiki, lūdzu, man patiešām būs nepieciešama jūsu palīdzība šajā jautājumā.

    Es iesniedzu pieprasījumu savam tīmekļa serverim http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ platums
    -2.23389 = $ garums
    un 20 = attālums, kuru vēlos iegūt

    Tomēr, izmantojot jūsu formulu, tā izgūst visas rindas manā db

    $ results = DB :: select (DB :: raw (“SELECT *, ((acos (sin ((“ “. $ platums.” * pi () / 180)) * sin ((lat * pi () / 180 )) + cos ((“. $ platums.” * pi () / 180)) * cos ((lat * pi () / 180)) * cos (((“. $ garums.” - lng) * pi ( ) / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) kā attālums no marķieriem, kam attālums ir = = “. $ Attālums));

    [{“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 Skats, Kalifornija ”,” lat ”: 37.394012451172,” lng ”: - 122.09552764893,” distance ”: 16078.563225154}, {“ id ”: 6,“ name ”:“ Oregano's Wood-Fired Pizza ”,“ address ”:” 4546 El Camino Real, Los Altos, Kalifornija ”,” 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}]

    Es gribu izgūt tikai rindas ar 20 jūdzēm, bet tas atnes visas rindas. Lūdzu, ko es daru nepareizi

  56. 77

    Es meklēju līdzīgu vaicājumu, bet nedaudz pastiprinājos - īsumā tas nozīmē, ka visas koordinātas ir jāgrupē 2 jūdžu attālumā no katras koordinātas un pēc tam jāskaita, cik koordinātu katrā grupā, un jāizdod tikai viena grupa, kurai ir visvairāk koordinātu - pat ja jums ir vairāk nekā viena grupa starp grupām, kurām ir visvairāk koordinātu - vienkārši izvadiet izlases grupu no grupām ar tādu pašu lielāko skaitu -

Ko jūs domājat?

Šī vietne izmanto Akismet, lai samazinātu surogātpastu. Uzziniet, kā tiek apstrādāts jūsu komentārs.