PHP kaj SQL: Kalkulu aŭ Demandu Grandan Cirklan Distancon Inter Latitudaj kaj Longaj Punktoj Per La Haversina Formulo

Haversine-Formulo - Kalkulu Grandan Cirklan Distancon per PHP aŭ MySQL

Ĉi-monate mi programis sufiĉe en PHP kaj MySQL rilate al GIS. Promenante ĉirkaŭ la reto, mi efektive malfacile trovis iujn el la Geografiaj kalkuloj por trovi la distancon inter du lokoj do mi volis dividi ilin ĉi tie.

Flugmapo Eŭropo Kun Granda Cirkla Distanco

La simpla maniero kalkuli distancon inter du punktoj estas uzi la pitagorean formulon por kalkuli la hipotenuzon de triangulo (A² + B² = C²). Ĉi tio estas konata kiel la Eŭklida distanco.

Tio estas interesa komenco, sed ĝi ne validas kun Geografio, ĉar la distanco inter latitudaj kaj longitudaj linioj estas ne egala distanco aparte. Kiam vi proksimiĝas al la ekvatoro, latitudaj linioj pli disiĝas. Se vi uzas ian simplan triangulan ekvacion, ĝi eble mezuros distancon precize en unu loko kaj terure malĝuste en la alia, pro la kurbeco de la Tero.

Granda Ronda Distanco

La vojoj trairataj longajn distancojn ĉirkaŭ la Tero estas nomataj Granda Ronda Distanco. Tio estas ... la plej mallonga distanco inter du punktoj sur sfero estas malsama ol la punktoj en plata mapo. Kombinu tion kun la fakto, ke la latitudaj kaj longitudaj linioj ne egalas ... kaj vi havas malfacilan kalkulon.

Jen fantazia videoklarigo pri kiel funkcias Grandaj Rondoj.

La Haversina Formulo

La distanco uzanta la kurbecon de la Tero estas inkluzivita en la Haversina formulo, kiu uzas trigonometrion por permesi la kurbecon de la tero. Kiam vi trovas la distancon inter 2 lokoj sur la tero (kiel korvo flugas), rekto estas vere arko.

Ĉi tio aplikeblas en aera flugo - ĉu vi iam rigardis la efektivan mapon de flugoj kaj rimarkis, ke ili estas arkaĵaj? Tio estas ĉar pli mallongas flugi en arko inter du punktoj ol rekte al la loko.

PHP: Kalkulu Distancon Inter 2 Latitudoj kaj Longeco

Ĉiuokaze, jen la PHP-formulo por kalkuli la distancon inter du punktoj (kune kun Mejlo kontraŭ Kilometra konvertiĝo) rondigita al du decimalaj.

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: Rekuperi Ĉiujn Rekordojn En Areo Per Kalkulado De Distanco En Mejloj Uzante Latitudon kaj Longitudon

Eblas ankaŭ uzi SQL por fari kalkulon por trovi ĉiujn registrojn ene de specifa distanco. En ĉi tiu ekzemplo, mi pridemandos MyTable en MySQL por trovi ĉiujn registrojn malpli aŭ egalajn al varia $ distanco (en Mejloj) al mia loko je $ latitudo kaj $ longitudo:

La konsulto por rekuperi ĉiujn registrojn en specifa malproksimo kalkulante distancon en mejloj inter du punktoj de latitudo kaj longitudo estas:

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

Vi bezonos agordi ĉi tion:

  • $ longitudo - Jen PHP-variablo, kie mi preterpasas la longitudon de la punkto.
  • $ latitudo - Jen PHP-variablo, kie mi preterpasas la longitudon de la punkto.
  • $ distanco - ĉi tiu estas la distanco, kiun vi ŝatus trovi ĉiujn registrojn malpli aŭ egala al.
  • tablo - jen la tabelo ... vi volos anstataŭigi tion per via tabela nomo.
  • latitudo - jen la kampo de via latitudo.
  • longitudo - jen la kampo de via longitudo.

SQL: Rekuperi Ĉiujn Rekordojn En Areo Per Kalkulado De Distanco En Kilometroj Uzante Latitudon kaj Longitudon

Kaj jen la SQL-konsulto uzanta kilometrojn en 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."

Vi bezonos agordi ĉi tion:

  • $ longitudo - Jen PHP-variablo, kie mi preterpasas la longitudon de la punkto.
  • $ latitudo - Jen PHP-variablo, kie mi preterpasas la longitudon de la punkto.
  • $ distanco - ĉi tiu estas la distanco, kiun vi ŝatus trovi ĉiujn registrojn malpli aŭ egala al.
  • tablo - jen la tabelo ... vi volos anstataŭigi tion per via tabela nomo.
  • latitudo - jen la kampo de via latitudo.
  • longitudo - jen la kampo de via longitudo.

Mi uzis ĉi tiun kodon en platformo pri entreprena mapado, kiun ni uzis por podetala butiko kun pli ol 1,000 lokoj tra Nordameriko kaj ĝi funkciis bele.

76 Komentoj

  1. 1

    Mi tre dankas vin por dividi. Ĉi tio estis facila laboro por kopii kaj bati kaj bonege funkcias. Vi ŝparis al mi multan tempon.
    FYI por iu ajn portanta al C:
    duobla deg2rad (duobla deg) {return deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Tre bela afiŝeto - funkciis tre bone - mi devis nur ŝanĝi la nomon de la tablo tenanta la lat-longan. Ĝi funkcias sufiĉe rapide al .. Mi havas sufiĉe malgrandan nombron da lat-longs (<400) sed mi pensas, ke ĉi tio bele grimpus. Bela retejo ankaŭ - mi ĵus aldonis ĝin al mia konto del.icio.us kaj regule kontrolos.

  3. 4
  4. 5
  5. 8

    mi pensas, ke via SQL bezonas deklaron.
    anstataŭ KIE distanco <= $ distanco vi eble bezonos
    uzu HAVAN distancon <= $ distanco

    alie dankon, ke vi ŝparis al mi amason da tempo kaj energio.

  6. 10
  7. 11
  8. 12

    Dankegon pro dividi ĉi tiun kodon. Ĝi ŝparis al mi multan disvolvan tempon. Krome, dankon al viaj legantoj, ke vi atentigis, ke HAVING-aserto estas necesa por MySQL 5.x. Tre helpema.

  9. 14
  10. 15
  11. 16

    Mi ankaŭ trovis, ke KIE ne funkciis por mi. Ŝanĝis ĝin al HAVING kaj ĉio funkcias perfekte. Unue mi ne legis la komentojn kaj reskribis ĝin per nestita elekto. Ambaŭ funkcios bone.

  12. 17
  13. 18

    Nekredeble helpema, dankegon! Mi havis iujn problemojn kun la nova "HAVI", anstataŭ "KIE", sed post kiam mi legis la komentojn ĉi tie (post ĉirkaŭ duonhoro grincante miajn dentojn pro frustriĝo = P), mi funkciigis ĝin bone. Dankon ^ _ ^

  14. 19
  15. 20

    Memoru, ke elektita aserto tia estos tre kompute intensa kaj tial malrapida. Se vi havas multajn el tiuj demandoj, ĝi povas malhelpi aferojn sufiĉe rapide.

    Multe malpli intensa aliro estas lanĉi unuan (krudan) elekton uzante KVARAN areon difinitan per kalkulita distanco te "elektu * el tablonomo kie latitudo inter lat1 kaj lat2 kaj longitudo inter lon1 kaj lon2". lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, simile al lon. latdiff ~ = distanco / 111 (por km), aŭ distanco / 69 por mejloj ĉar 1 latitudo estas ~ 111 km (eta variado ĉar tero estas iomete ovala, sed sufiĉa por ĉi tiu celo). londif = distanco / (abs (cos (deg2rad (latitudo)) * 111)) - aŭ 69 mejlojn (vi povas efektive preni iomete pli grandan kvadraton por kalkuli variaĵojn). Tiam prenu la rezulton de tio kaj enmetu ĝin en la radian elekton. Simple ne forgesu respondeci pri eksterlimaj koordinatoj - do la gamo de akceptebla longitudo estas -180 ĝis +180 kaj la gamo de akceptebla latitudo estas -90 ĝis +90 - se via latdiff aŭ londifiko funkcias ekster ĉi tiu rango . Rimarku, ke plejofte tio eble ne aplikeblas, ĉar ĝi nur influas kalkulojn super linio tra la pacifika oceano de poluso al poluso, kvankam ĝi sekcas parton de ĉukotko kaj parto de alasko.

    Kion ni plenumas per tio estas signifa redukto en la nombro da punktoj, kontraŭ kiuj vi faras ĉi tiun kalkulon. Se vi havas milionon da tutmondaj punktoj en la datumbazo distribuitaj proksimume egale kaj vi volas serĉi ene de 100 km, tiam via unua (rapida) serĉo estas de areo 10000 kv. Km kaj probable donos ĉirkaŭ 20 rezultojn (surbaze de egala distribuo super surfaco de ĉirkaŭ 500M kvadrataj km), kio signifas, ke vi plenumas la kompleksan distancan kalkulon 20 fojojn por ĉi tiu demando anstataŭ miliono da fojoj.

    • 21
      • 22

        Mirinda konsilo! Mi efektive laboris kun programisto, kiu verkis funkcion, kiu tiris la internan kvadraton kaj poste rekursivan funkcion, kiu faris 'kvadratojn' ĉirkaŭ la perimetro por inkluzivi kaj ekskludi la ceterajn punktojn. La rezulto estis nekredeble rapida rezulto - li povis taksi milionojn da punktoj en mikrosekundoj.

        Mia supera aliro certe estas 'kruda' sed kapabla. Redankon!

        • 23

          Doug,

          Mi provis uzi mysql kaj php por taksi ĉu lat longa punkto estas ene de plurlatero. Ĉu vi scias, ĉu via ellaboranto-amiko publikigis iujn ekzemplojn pri kiel plenumi ĉi tiun taskon. Aŭ ĉu vi konas bonajn ekzemplojn. Antaŭdankon.

  16. 24

    Saluton al ĉiuj, jen mia testo SQL-deklaro:

    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

    kaj Mysql diras al mi, ke distanco ne ekzistas kiel kolumno, mi povas uzi ordon laŭ, mi povas fari ĝin sen KIE, kaj ĝi funkcias, sed ne kun ĝi ...

  17. 26

    Ĉi tio bonegas, tamen same kiel la birdoj flugas. Estus bonege provi enkorpigi la API de google maps al ĉi tio iel (eble uzante vojojn ktp.) Nur por doni ideon uzante alian formon de transporto. Mi ankoraŭ devas fari simulitan kalcinan funkcion en PHP, kiu povus oferti efikan solvon al la vojaĝanta vendista problemo. Sed mi pensas, ke mi eble povos reuzi iom da via kodo por fari tion.

  18. 27
  19. 28

    Bona artikolo! Mi trovis multajn artikolojn priskribantajn kiel kalkuli distancon inter du punktoj sed mi vere serĉis la SQL-fragmenton.

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

    2 tagojn da esplorado por finfine trovi ĉi tiun paĝon, kiu solvas mian problemon. Ŝajnas, ke mi pli bone eksplodos mian WolframAlpha kaj pripensos mian matematikon. La ŝanĝo de WHERE al HAVING havas mian skripton en funkcia stato. DANKON

  25. 37
    • 38

      Dankon Georgi. Mi daŭre ricevis kolumnan "distancon" ne trovitan. Iam mi ŝanĝis la WHERE to HAVING, ĝi funkciis kiel ĉarmo!

  26. 39

    Mi volus, ke ĉi tiu estu la unua paĝo, kiun mi trovis sur ĉi tio. Post provi multajn malsamajn komandojn, ĉi tiu estis la sola funkcianta ĝuste, kaj kun minimumaj ŝanĝoj necesaj por kongrui kun mia propra datumbazo.
    Multaj dankoj!

  27. 40

    Mi volus, ke ĉi tiu estu la unua paĝo, kiun mi trovis sur ĉi tio. Post provi multajn malsamajn komandojn, ĉi tiu estis la sola funkcianta ĝuste, kaj kun minimumaj ŝanĝoj necesaj por kongrui kun mia propra datumbazo.
    Multaj dankoj!

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

    Dankon Douglas, la SQLa Demando estas ĝuste tio, kion mi bezonis, kaj mi pensis, ke mi devos verki ĝin mem. Vi savis min de eventuale horoj da latituda longitudo-kurba lernado!

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

    dankon pro afiŝado de ĉi tiu helpema artikolo,  
    sed ial mi volus demandi
    kiel akiri la distancon inter kordoj ene de mysql db kaj kordoj enmetitaj al php de uzanto?
    ĉar pli klare priskribu:
    1.uzanto devas enmeti [id] por elekti specifajn datumojn el db kaj la akordoj de uzanto mem
    2.la php-dosiero ricevas la celajn datumojn (kunordojn) per [id] kaj tiam kalkulas distancon inter uzanto kaj cela punkto

    aŭ ĉu simple simple distanciĝi de la suba kodo?

    $ qry = “SELECT *, (((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((“. $ latitudo. "* pi () / 180)) * cos ((` Latitudo * * pi () / 180)) * cos (((". $ longitudo." - 'Longitudo') * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) kiel distanco DE `MiaTablo` KIE distanco> =“. $ Distanco. ” >>>> ĉu mi povas "elpreni" la distancon de ĉi tie?
    Redankon,
    Timmy S.

  41. 60

    bone, ĉio, kion mi provis, ne funkcias. Mi volas diri, ke tio, kion mi havas, funkcias, sed la distancoj estas malproksime.

    Ĉu iu eble povus vidi, kio misas pri ĉi tiu kodo?

    if (isset ($ _ POST ['sendita]])) {$ z = $ _POST [' poŝtkodo ']; $ r = $ _POST ['radius']; eoo "Rezultoj por". $ 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. urbo, z1.stato DE mrk m, zip z1, zip z2 KIE m.zipcode = z1.zipcode KAJ z2.zipcode = $ z KAJ (3963 * acos (truncate (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 ") aŭ mortas (mysql_error ()); while ($ vico = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. "”; $ vendejo = $ vico ['LocAddSt']. ””; $ vendejo. = $ vico ['LocAddCity']. ",". $ vico ['LocAddState']. " “. $ Vico ['poŝtkodo']; $ latitudo1 = $ vico ['lat']; $ longitudo1 = $ vico ['lon']; $ latitudo2 = $ vico ['y1']; $ longitudo2 = $ vico ['x1']; $ urbo = $ vico ['urbo']; $ ŝtato = $ vico ['ŝtato']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = distanco ($ lat1, $ lon1, $ lat2, $ lon2); $ kontrolita = $ vico ['kontrolita']; if ($ kontrolita == '1') {echo ""; eoo "". $ vendejo. ""; eoo $ dis. "Mejlo (j) for"; eoo ""; } else {echo "". $ store. ""; eoo $ dis. "Mejlo (j) for"; eoo ""; }}}

    miaj funkcioj.php-kodo
    function getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi') {$ theta = $ longitude1 - $ longitude2; $ distanco = (sin (deg2rad ($ latitude1)) * sin (deg2rad ($ latitude2))) + (cos (deg2rad ($ latitude1)) * cos (deg2rad ($ latitude2)) * cos (deg2rad ($ theta)) ); $ distanco = acos ($ distanco); $ distanco = rad2deg ($ distanco); $ distanco = $ distanco * 60 * 1.1515; ŝaltilo ($ unuo) {kazo 'Mi': rompi; kazo 'Km': $ distanco = $ distanco * 1.609344; } reveno (ronda ($ distanco, 2)); }

    Antaŭdankon

  42. 61
  43. 62

    Hej Douglas, bonega artikolo. Mi trovis vian interesan klarigon pri la geografiaj konceptoj kaj la kodo. Mia sola sugesto estus spacigi kaj indentigi la kodon por montriĝo (kiel Stackoverflow, ekzemple). Mi komprenas, ke vi volas konservi spacon, sed konvencia koda interspaco / indentaĵo faciligus al mi, kiel programisto, legi kaj dissekci. Ĉiuokaze tio estas malgranda afero. Daŭrigu la bonegan laboron.

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

    ŝajnas pli rapide (mysql 5.9) uzi duoble la formulon en la elekto kaj kie:
    $ formulo = “(((acos (sin ((“. $ latitudo. ”* pi () / 180)) * sin ((` Latitudo * * pi () / 180)) + cos ((“. $ latitudo. "* Pi () / 180)) * cos ((` Latitudo * * pi () / 180)) * cos (((". $ Longitudo." - `Longitudo") * pi () / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) ”;
    $ sql = 'SELECT *,'. $ formulo. ' kiel distanco DE tabelo KIE '.. $ formulo.' <= '. $ distanco;

  51. 71
  52. 72

    Multan dankon pro tondado de ĉi tiu artikolo, tre helpas.
    PHP unue kreiĝis kiel simpla skripta platformo nomata "Persona Hejmpaĝo". Nuntempe PHP (la mallongigo de Hiperteksto-Antaŭprocesilo) estas alternativo de la teknologio Microsoft Active Active Pages (ASP).

    PHP estas malfermfonteca servila lingvo uzata por krei dinamikajn retpaĝojn. Ĝi povas esti enigita en HTML. PHP estas kutime uzata kune kun MySQL-datumbazo en retaj serviloj Linukso / UNIX. Ĝi probable estas la plej populara skripta lingvo.

  53. 73

    Mi trovis supre solvon ne funkcianta ĝuste.
    Mi bezonas ŝanĝi al:

    $ qqq = "SELECT *, (((acos (sin ((". $ latitude. "* pi () / 180)) * sin ((` latt` * pi () / 180)) + cos ((". $ latitudo. "* pi () / 180)) * cos ((` latt` * pi () / 180)) * cos (((". $ longitudo." - `longt`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515) kiel distanco DE `registri`“;

  54. 75
  55. 76

    Saluton, bonvolu, mi vere bezonos vian helpon pri tio.

    Mi faris peton al mia retservilo http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ latitudo
    -2.23389 = $ longitudo
    kaj 20 = la distanco, kiun mi volas rekuperi

    Tamen uzante vian formulon, ĝi retrovas ĉiujn vicojn en mia db

    $ rezultoj = DB :: select (DB :: raw ("SELECT *, (((acos (sin ((". $ latitude. "* pi () / 180)) * sin ((lat * pi () / 180 )) + cos ((". $ latitude." * pi () / 180)) * cos ((lat * pi () / 180)) * cos (((". $ longitudo." - lng) * pi ( ) / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) kiel distanco DE markiloj HAVAN distancon> = “. $ Distanco));

    [{"Id": 1, "name": "Frankie Johnnie & Luigo Too", "address": "939 W El Camino Real, Mountain View, CA", "lat": 37.386337280273, "lng": - 122.08582305908, "Distanco": 16079.294719663}, {"id": 2, "nomo": "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": "Ronda Tabla Pico: 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}, {" identigilo ": 7," nomo ":" La stangoj kaj kradrostejoj "," adreso ":" 24 Whiteley Street, Manĉestro "," lat ": 53.485118865967," lng ": - 2.1828699111938," distanco ": 8038.7620112314}]

    Mi volas retrovi nur vicojn kun 20 mejloj sed ĝi alportas ĉiujn vicojn. Bonvolu, kion mi faras malbone

Kion vi pensas?

Ĉi tiu retejo uzas Akismeton por redukti spamon. Lernu, kiel via komento datiĝas.