PHP a SQL: Výpočet nebo dotaz na velkou vzdálenost kruhu mezi body zeměpisné šířky a délky pomocí vzorce Haversine

Haversine Formula - Vypočítejte velkou vzdálenost kruhu pomocí PHP nebo MySQL

Tento měsíc jsem hodně programoval v PHP a MySQL s ohledem na GIS. Snooping po síti, jsem vlastně jen těžko najít některé z Geografické výpočty najít vzdálenost mezi dvěma místy, tak jsem je chtěl sdílet zde.

Letová mapa Evropy s velkou vzdáleností od kruhu

Jednoduchý způsob výpočtu vzdálenosti mezi dvěma body je pomocí Pythagorovy vzorce pro výpočet přepony trojúhelníku (A² + B² = C²). Toto je známé jako Euklidovská vzdálenost.

To je zajímavý začátek, ale neplatí to pro geografii, protože vzdálenost mezi řádky zeměpisné šířky a délky jsou není stejná vzdálenost odděleně. Když se přiblížíte k rovníku, linie zeměpisné šířky se od sebe vzdalují. Pokud použijete nějakou jednoduchou triangulační rovnici, může měřit vzdálenost přesně na jednom místě a na druhém strašně špatně, kvůli zakřivení Země.

Velká vzdálenost kruhu

Trasy, které cestují na dlouhé vzdálenosti kolem Země, jsou známé jako Velká vzdálenost kruhu. To je ... nejkratší vzdálenost mezi dvěma body na kouli se liší od bodů na ploché mapě. Zkombinujte to se skutečností, že čáry zeměpisné šířky a délky nejsou ve stejné vzdálenosti ... a máte obtížný výpočet.

Zde je fantastické video vysvětlení toho, jak Great Circles fungují.

Vzorec Haversine

Vzdálenost používající zakřivení Země je začleněna do Haversinův vzorec, který využívá trigonometrii k umožnění zakřivení Země. Když zjistíte vzdálenost mezi 2 místy na Zemi (vzdušnou čarou), přímka je opravdu oblouk.

To platí pro letecký let - podívali jste se někdy na skutečnou mapu letů a všimli jste si, že jsou klenuté? Je to proto, že létání v oblouku mezi dvěma body je kratší než přímo na dané místo.

PHP: Vypočítejte vzdálenost mezi 2 body zeměpisné šířky a délky

Tady je vzorec PHP pro výpočet vzdálenosti mezi dvěma body (spolu s převodem Mile vs. Kilometr) zaokrouhleným na dvě desetinná místa.

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: Načítání všech záznamů v rozsahu výpočtem vzdálenosti v mílích pomocí zeměpisné šířky a délky

Je také možné použít SQL k provedení výpočtu k vyhledání všech záznamů v určité vzdálenosti. V tomto příkladu se chystám dotazovat MyTable v MySQL, abych našel všechny záznamy, které jsou menší nebo rovny proměnné $ distance (v mílích) do mého umístění na $ latitude a $ longitude:

Dotaz na načtení všech záznamů v konkrétním vzdálenost výpočtem vzdálenosti v mílích mezi dvěma body zeměpisné šířky a délky jsou:

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

Toto budete muset přizpůsobit:

  • $ zeměpisná délka - toto je proměnná PHP, kde předávám zeměpisnou délku bodu.
  • $ zeměpisná šířka - toto je proměnná PHP, kde předávám zeměpisnou délku bodu.
  • $ vzdálenost - to je vzdálenost, kterou byste chtěli najít všechny záznamy menší nebo rovné.
  • tabulka - toto je tabulka ... budete ji chtít nahradit názvem vaší tabulky.
  • zeměpisná šířka - toto je pole vaší zeměpisné šířky.
  • zeměpisná délka - toto je pole vaší zeměpisné délky.

SQL: Načítání všech záznamů v rozsahu výpočtem vzdálenosti v kilometrech pomocí zeměpisné šířky a délky

A tady je dotaz SQL využívající kilometry 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."

Toto budete muset přizpůsobit:

  • $ zeměpisná délka - toto je proměnná PHP, kde předávám zeměpisnou délku bodu.
  • $ zeměpisná šířka - toto je proměnná PHP, kde předávám zeměpisnou délku bodu.
  • $ vzdálenost - to je vzdálenost, kterou byste chtěli najít všechny záznamy menší nebo rovné.
  • tabulka - toto je tabulka ... budete ji chtít nahradit názvem vaší tabulky.
  • zeměpisná šířka - toto je pole vaší zeměpisné šířky.
  • zeměpisná délka - toto je pole vaší zeměpisné délky.

Tento kód jsem využil na platformě podnikového mapování, kterou jsme využili pro maloobchod s více než 1,000 XNUMX místy v Severní Americe a fungoval skvěle.

76 Komentáře

  1. 1

    Moc děkuji za sdílení. Byla to snadná práce s kopírováním a vkládáním a funguje skvěle. Ušetřil jsi mi hodně času.
    FYI pro kohokoli, kdo portuje na C:
    double deg2rad (double deg) {return deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Velmi pěkný příspěvek - fungoval velmi pěkně - stačilo mi změnit název stolu, který drží lat-long. Funguje to docela rychle ... Mám rozumně malý počet lat-longů (<400), ale myslím, že by se to pěkně změnilo. Pěkný web - právě jsem jej přidal do svého účtu del.icio.us a pravidelně ho kontroluji.

  3. 4
  4. 5
  5. 8

    Myslím, že vaše SQL potřebuje mít prohlášení.
    místo WHERE vzdálenost <= $ vzdálenost možná budete muset
    použijte HAVING distance <= $ distance

    jinak děkuji, že jste mi ušetřili spoustu času a energie.

  6. 10
  7. 11
  8. 12

    Děkujeme za sdílení tohoto kódu. Ušetřilo mi to spoustu času na vývoj. Děkujeme také vašim čtenářům za to, že poukázali na to, že pro MySQL 5.x je nutné prohlášení HAVING. Velmi nápomocný.

  9. 14
  10. 15
  11. 16

    Také jsem zjistil, že WHERE pro mě nefungovalo. Změněno na HAVING a vše funguje perfektně. Nejprve jsem nečetl komentáře a přepsal je pomocí vnořeného výběru. Oba budou fungovat dobře.

  12. 17
  13. 18

    Neuvěřitelně užitečné, děkuji moc! Měl jsem nějaké problémy s novým „HAVING“, spíše než „WHERE“, ale jakmile jsem si přečetl komentáře zde (asi po půl hodině frustrujících zubů = P), začal mi fungovat dobře. Děkuji ^ _ ^

  14. 19
  15. 20

    Pamatujte, že takový výrok bude velmi výpočetně intenzivní, a proto pomalý. Pokud máte spoustu těchto dotazů, může to docela rychle zabrat věci.

    Mnohem méně intenzivním přístupem je spuštění prvního (surového) výběru pomocí oblasti NÁMĚSTÍ definovaného vypočítanou vzdáleností, tj. „Výběr * z názvu tabulky, kde zeměpisná šířka mezi lat1 a lat2 a zeměpisná délka mezi lon1 a lon2“. lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, podobně jako lon. latdiff ~ = vzdálenost / 111 (pro km), nebo vzdálenost / 69 pro míle, protože 1 stupeň zeměpisné šířky je ~ 111 km (mírná odchylka, protože Země je mírně oválná, ale pro tento účel dostatečná). londiff = vzdálenost / (abs (cos (deg2rad (zeměpisná šířka)) * 111)) - nebo 69 na míle (pro zohlednění variací můžete ve skutečnosti vzít o něco větší čtverec). Pak vezměte výsledek a vložte jej do radiálního výběru. Nezapomeňte zohlednit souřadnice mimo hranice - tj. Rozsah přijatelné délky je -180 až +180 a rozsah přijatelné zeměpisné šířky je -90 až +90 - v případě, že váš latdiff nebo londiff běží mimo tento rozsah . Všimněte si, že ve většině případů to nemusí být použitelné, protože to ovlivňuje pouze výpočty přes čáru přes Tichý oceán od pólu k pólu, ačkoli to protíná část chukotky a část Aljašky.

    Tím dosáhneme významného snížení počtu bodů, proti kterým provedete tento výpočet. Pokud máte milion globálních bodů v databázi distribuovaných zhruba rovnoměrně a chcete hledat do 100 km, pak vaše první (rychlé) vyhledávání má rozlohu 10000 20 km500 a pravděpodobně přinese asi 20 výsledků (na základě rovnoměrného rozdělení po plocha přibližně XNUMX milionů čtverečních km), což znamená, že pro tento dotaz spustíte komplexní výpočet vzdálenosti XNUMXkrát, nikoli milionkrát.

    • 21
      • 22

        Fantastická rada! Vlastně jsem pracoval s vývojářem, který napsal funkci, která vytáhla vnitřní čtverec a poté rekurzivní funkci, která vytvořila 'čtverce' po obvodu, aby zahrnovaly a vylučovaly zbývající body. Výsledkem byl neuvěřitelně rychlý výsledek - dokázal vyhodnotit miliony bodů za mikrosekundy.

        Můj výše uvedený přístup je rozhodně „surový“, ale schopný. Ještě jednou děkuji!

        • 23

          Doug,

          Snažil jsem se použít mysql a php k vyhodnocení, zda je lat long point v polygonu. Víte, jestli váš přítel vývojáře zveřejnil nějaké příklady toho, jak splnit tento úkol. Nebo znáte nějaké dobré příklady. Díky předem.

  16. 24

    Ahoj všichni, toto je můj testovací příkaz 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

    a Mysql mi říká, že vzdálenost, neexistuje jako sloupec, mohu použít příkaz do, mohu to udělat bez WHERE, a funguje to, ale ne s ním ...

  17. 26

    To je skvělé, ale stejně jako ptáci létají. Bylo by skvělé zkusit nějak do toho začlenit API google maps (možná pomocí silnic atd.). Jen pro představu s použitím jiné formy dopravy. Stále ještě musím vytvořit simulovanou žíhací funkci v PHP, která by dokázala nabídnout efektivní řešení problému obchodního cestujícího. Ale myslím si, že možná budu schopen znovu použít některý z vašich kódů.

  18. 27
  19. 28
  20. 29
  21. 30
  22. 31
  23. 32
  24. 36

    2 dny výzkumu, abych konečně našel tuto stránku, která řeší můj problém. Vypadá to, že bych měl lépe vypustit svůj WolframAlpha a oprášit si matematiku. Změna z WHERE na HAVING má můj skript v provozuschopném stavu. DĚKUJU

  25. 37
  26. 39

    Přál bych si, aby to byla první stránka, kterou jsem na tom našel. Po vyzkoušení mnoha různých příkazů to byl jediný, který fungoval správně as minimálními změnami potřebnými k přizpůsobení mé vlastní databázi.
    Díky moc!

  27. 40

    Přál bych si, aby to byla první stránka, kterou jsem na tom našel. Po vyzkoušení mnoha různých příkazů to byl jediný, který fungoval správně as minimálními změnami potřebnými k přizpůsobení mé vlastní databázi.
    Díky moc!

  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

    děkuji za zveřejnění tohoto užitečného článku,  
    ale z nějakého důvodu bych se chtěl zeptat
    jak se dostat na vzdálenost mezi coordy uvnitř mysql db a coordy vloženými do php uživatelem?
    pro jasnější popis:
    1. uživatel musí vložit [id] pro výběr specifikovaných dat z coordů db a samotných uživatelů
    2. soubor php získá cílová data (coords) pomocí [id] a poté vypočítá vzdálenost mezi uživatelem a cílovým bodem

    nebo můžete jednoduše získat vzdálenost od níže uvedeného kódu?

    $ qry = “SELECT *, (((acos (sin ((“. $ latitude. * pi () / 180)) * sin ((`Latitude` * pi () / 180)) + cos ((“. $ zeměpisná šířka. “* pi () / 180)) * cos ((` `Latitude` * pi () / 180)) * cos ((((„. $ longitude. “-` zeměpisná délka`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) jako vzdálenost OD `MyTable` WHERE distance> =„. $ Distance. “ >>>> Mohu odtud „odtáhnout“ vzdálenost?
    ještě jednou děkuji,
    Timmy S.

  41. 60

    ok, všechno, co jsem zkoušel, nefunguje. Myslím tím, co mám, funguje, ale vzdálenosti jsou daleko.

    Mohl by někdo vidět, co je s tímto kódem špatně?

    if (isset ($ _ POST ['submitted'])) {$ z = $ _POST ['zipcode']; $ r = $ _POST ['radius']; echo „Výsledky pro“. $ z; $ sql = mysql_query („VYBRAT DISTINCT m.zipcode, m.MktName, m.LocAddSt, m.LocAddCity, m.LocAddState, m.x1, m.y1, m.verified, z1.lat, z2.lon, z1. city, z1.state FROM mrk m, zip z1, zip z2 WHERE m.zipcode = z1.zipcode AND z2.zipcode = $ z AND (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 ") nebo zemřít (mysql_error ()); while ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. ""; $ store = $ row ['LocAddSt']. ””; $ store. = $ row ['LocAddCity']. ”,“. $ row ['LocAddState']. “ „. $ Řádek ['zipcode']; $ latitude1 = $ řádek ['lat']; $ longitude1 = $ row ['lon']; $ latitude2 = $ řádek ['y1']; $ longitude2 = $ row ['x1']; $ city = $ row ['city']; $ state = $ řádek ['state']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = vzdálenost ($ lat1, $ lon1, $ lat2, $ lon2); $ ověřeno = $ řádek ['ověřeno']; if ($ ověřeno == '1') {echo “”; echo "". $ store. ""; echo $ dis. „Na míle daleko“; ozvěna „“; } else {echo "". $ store. ""; echo $ dis. „Na míle daleko“; ozvěna „“; }}}

    můj functions.php kód
    funkce 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)) ); $ distance = acos ($ distance); $ distance = rad2deg ($ vzdálenost); $ vzdálenost = $ vzdálenost * 60 * 1.1515; switch ($ unit) {case 'Mi': break; případ 'Km': $ vzdálenost = $ vzdálenost * 1.609344; } návrat (zaokrouhlení ($ vzdálenost, 2)); }

    Děkuji předem

  42. 61
  43. 62

    Ahoj Douglasi, skvělý článek. Vaše vysvětlení geografických konceptů a kódu mi připadalo opravdu zajímavé. Můj jediný návrh by byl do mezery a odsazení kódu pro zobrazení (například Stackoverflow). Chápu, že chcete šetřit prostor, ale konvenční rozteč / odsazení kódu by mi jako programátorovi mnohem snáze četlo a rozřezávalo. To je každopádně maličkost. Pokračuj v dobré práci.

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

    zdá se rychlejší (mysql 5.9) použít dvakrát vzorec ve výběru a kde:
    $ 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. ' jako vzdálenost OD tabulky WHERE '.. $ formula.' <= '. $ vzdálenost;

  51. 71
  52. 72

    Díky moc za stříhání tohoto článku. Je to velmi užitečné.
    PHP bylo nejprve vytvořeno jako jednoduchá skriptovací platforma s názvem „Personal Home Page“. V dnešní době je PHP (zkratka pro Hypertext Preprocessor) alternativou technologie Microsoft Active Server Pages (ASP).

    PHP je open-source jazyk na straně serveru, který se používá k vytváření dynamických webových stránek. Může být vložen do HTML. PHP se obvykle používá ve spojení s databází MySQL na webových serverech Linux / UNIX. Je to pravděpodobně nejpopulárnější skriptovací jazyk.

  53. 73

    Zjistil jsem, že výše uvedené řešení nefunguje správně.
    Musím změnit 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) jako vzdálenost OD „registru“ “;

  54. 75
  55. 76

    Dobrý den, prosím, opravdu v tom budu potřebovat vaši pomoc.

    Na svůj webový server jsem odeslal požadavek na získání http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = zeměpisná šířka $
    -2.23389 = zeměpisná délka
    a 20 = vzdálenost, kterou chci získat

    Nicméně pomocí vašeho vzorce načte všechny řádky v mém db

    $ results = DB :: select (DB :: raw (“SELECT *, (((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((lat * pi () / 180 )) + cos ((„„ $ zeměpisná šířka. “* pi () / 180)) * cos ((lat * pi () / 180)) * cos ((((„. $ zeměpisná délka. - lng) * pi ( ) / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) jako vzdálenost FROM značek HAVING vzdálenost> = “. $ Vzdálenost));

    [{“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 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,” vzdálenost ”: 16077.937560795}, {“ id ”: 7,” name ”:” Bar and grills ”,” address ”:” 24 Whiteley Street, Manchester ”,” lat ”: 53.485118865967,” lng ”: - 2.1828699111938,” distance ”: 8038.7620112314}]

    Chci načíst jen řádky s 20 mil, ale přináší všechny řádky. Prosím, co dělám špatně

Co si myslíte?

Tyto stránky používají Akismet k omezení spamu. Zjistěte, jak jsou vaše údaje komentářů zpracovávány.