PHP та SQL: Обчислення або запит великої відстані кола між точками широти та довготи за формулою Хаверсіна

Формула Haversine - Обчисліть відстань великого кола за допомогою PHP або MySQL

Цього місяця я досить багато програмував на PHP та MySQL щодо ГІС. Переглядаючи мережу, мені насправді було важко знайти деякі з них Географічні розрахунки щоб знайти відстань між двома місцями, тому я хотів поділитися ними тут.

Карта польотів Європа з великим колом відстані

Простий спосіб обчислення відстані між двома точками - використання формули Піфагора для обчислення гіпотенузи трикутника (A² + B² = C²). Це відомо як Евклідова відстань.

Це цікавий початок, але це не стосується географії, оскільки відстань між лініями широти та довготи є не рівна відстань окремо. По мірі наближення до екватора лінії широти все далі віддаляються. Якщо ви використовуєте якесь просте рівняння триангуляції, воно може точно виміряти відстань в одному місці і страшенно неправильно в іншому через кривизну Землі.

Відстань великого кола

Маршрути, які проходять довгі відстані навколо Землі, відомі як Відстань великого кола. Тобто ... найкоротша відстань між двома точками на кулі відрізняється від точок на плоскій карті. Поєднайте це з тим фактом, що лінії широти та довготи не є рівновіддаленими ... і у вас складно обчислити.

Ось фантастичне відео-пояснення того, як працюють Великі кола.

Формула Гаверсіна

Відстань із використанням кривизни Землі враховано в Формула гаверзину, який використовує тригонометрію, щоб врахувати кривизну землі. Коли ви знаходите відстань між двома місцями на землі (у міру того, як летить ворона), пряма лінія насправді є дугою.

Це стосується повітряного польоту - ви коли-небудь дивились на фактичну карту польотів і помічали, що вони арочні? Це тому, що летіти в арці між двома точками коротше, ніж безпосередньо до місця.

PHP: Обчисліть відстань між 2 точками широти та довготи

У будь-якому випадку, ось формула PHP для розрахунку відстані між двома точками (разом із перетворенням милі проти кілометра), округленою до двох знаків після коми.

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: Отримання всіх записів у межах діапазону шляхом обчислення відстані в милях за допомогою широти та довготи

Також можна використовувати SQL для обчислення, щоб знайти всі записи на певній відстані. У цьому прикладі я збираюся запитати MyTable у MySQL, щоб знайти всі записи, які менше або дорівнюють змінній $ distance (у милях) до мого місцезнаходження на $ latitude та $ longitude:

Запит на отримання всіх записів у межах певного відстань шляхом обчислення відстані в милях між двома точками широти та довготи складають:

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

Вам потрібно буде налаштувати це:

  • $ довготи - це змінна PHP, де я передаю довготу точки.
  • $ широта - це змінна PHP, де я передаю довготу точки.
  • $ відстань - це відстань, на яку ви хотіли б знайти всі записи, менші або рівні.
  • таблиця - це таблиця ... ви захочете замінити її на назву вашої таблиці.
  • широта - це поле вашої широти.
  • довготи - це поле вашої довготи.

SQL: Отримання всіх записів у межах діапазону шляхом обчислення відстані в кілометрах за допомогою широти та довготи

І ось запит SQL із використанням кілометрів у 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."

Вам потрібно буде налаштувати це:

  • $ довготи - це змінна PHP, де я передаю довготу точки.
  • $ широта - це змінна PHP, де я передаю довготу точки.
  • $ відстань - це відстань, на яку ви хотіли б знайти всі записи, менші або рівні.
  • таблиця - це таблиця ... ви захочете замінити її на назву вашої таблиці.
  • широта - це поле вашої широти.
  • довготи - це поле вашої довготи.

Я використав цей код на корпоративній платформі картографування, яку ми використали для роздрібного магазину з понад 1,000 місць по всій Північній Америці, і він працював чудово.

76 Коментарі

  1. 1

    Щиро дякую за обмін. Це було легке завдання копіювання та вставлення і чудово працює. Ви заощадили мені багато часу.
    FYI для тих, хто переносить на C:
    подвійний deg2rad (подвійний град) {повернутий градус * (3.14159265358979323846 / 180.0); }

  2. 2

    Дуже гарний шматок допису - працював дуже приємно - мені довелося лише змінити назву таблиці, що тримає довжину лат. Це працює досить швидко, щоб .. У мене досить мала кількість довжин (<400), але я думаю, що це було б гарно. Хороший сайт - я щойно додав його до свого облікового запису del.icio.us і регулярно перевірятиму.

  3. 4
  4. 5
  5. 8

    я думаю, що ваш SQL потребує наявності заяви.
    замість WHERE відстань <= $ відстань вам може знадобитися
    використовуйте HAVING distance <= $ distance

    інакше дякую, що заощадили купу купу часу та енергії.

  6. 10
  7. 11
  8. 12

    Велике спасибі, що поділились цим кодом. Це заощадило мені багато часу на розробку. Крім того, дякую вашим читачам за те, що вони вказали, що оператор HAVING необхідний для MySQL 5.x. Дуже корисний.

  9. 14

    Наведена формула економить мені багато часу. Дуже дякую.
    Я також повинен переключатися між форматом NMEA та градусами. Я знайшов формулу за цією URL-адресою внизу сторінки. http://www.errorforum.com/knowledge-base/16273-converting-nmea-sentence-latitude-longitude-decimal-degrees.html

    Хтось знає, як це перевірити?

    Дякую!
    Гаррі

  10. 15
  11. 16

    Я також виявив, що WHERE для мене не працює. Змінив його на HAVING, і все працює ідеально. Спочатку я не читав коментарів і не писав їх за допомогою вкладеного елемента вибору. Обидва будуть працювати чудово.

  12. 17
  13. 18

    Неймовірно корисно, велике спасибі! У мене були деякі проблеми з новим “HAVING”, а не “WHERE”, але одного разу, коли я прочитав тут коментарі (приблизно через півгодини розчарувавши зуби = P), я змусив це працювати гарно. Дякую ^ _ ^

  14. 19
  15. 20

    Майте на увазі, що вибране твердження, подібне до цього, буде дуже обчислювальним, а отже, повільним. Якщо у вас багато цих запитів, це може досить швидко завалити ситуацію.

    Набагато менш інтенсивним підходом є запуск першого (сирого) вибору за допомогою площі КВАДРАТ, визначеної розрахунковою відстанню, тобто «вибрати * з імені таблиці, де широта між lat1 та lat2 та довгота між lon1 та lon2». lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, подібний до lon. latdiff ~ = відстань / 111 (для км), або відстань / 69 за милі, оскільки 1 градус широти становить ~ 111 км (незначне відхилення, оскільки земля трохи овальна, але достатня для цієї мети). londiff = відстань / (abs (cos (deg2rad (широта)) * 111)) - або 69 на милі (насправді ви можете взяти трохи більший квадрат, щоб врахувати зміни). Потім візьміть результат цього і подайте його в радіальний вибір. Тільки не забудьте врахувати поза межами координати - тобто діапазон допустимої довготи становить від -180 до +180 і діапазон допустимої широти від -90 до +90 - на випадок, якщо ваш латдіфф або лондіфф виходить за межі цього діапазону . Зверніть увагу, що в більшості випадків це може бути не застосовним, оскільки воно впливає лише на розрахунки за лінією через Тихий океан від полюса до полюса, хоча воно перетинає частину Чукотки та частину Аляски.

    Ми цим досягаємо значного зменшення кількості балів, щодо яких ви робите цей розрахунок. Якщо у вас є мільйон глобальних точок у базі даних, розподілених приблизно рівномірно і ви хочете шукати в межах 100 км, то ваш перший (швидкий) пошук займає площу 10000 кв. Км і, ймовірно, дасть близько 20 результатів (на основі рівномірного розподілу по площа близько 500 млн. кв. км), що означає, що ви виконуєте складний розрахунок відстані 20 разів для цього запиту замість мільйона разів.

    • 21
      • 22

        Фантастична порада! Я насправді працював із розробником, який написав функцію, яка витягувала внутрішній квадрат, а потім рекурсивну функцію, яка робила "квадрати" по периметру, щоб включити та виключити решту точок. Результат був неймовірно швидким - він міг оцінити мільйони очок за мікросекунди.

        Мій підхід вище, безумовно, "грубий", але здатний. Знову дякую!

        • 23

          Дуг,

          Я намагався використовувати mysql та php, щоб оцінити, чи є довга точка lat у полігоні. Чи знаєте ви, чи ваш друг розробник публікував якісь приклади того, як виконати це завдання? Або ви знаєте якісь хороші приклади. Заздалегідь спасибі.

  16. 24

    Привіт усім, це мій тестовий вираз 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

    і Mysql каже мені, що відстань не існує як стовпець, я можу використовувати порядок за, я можу зробити це без ДЕ, і це працює, але не з ним ...

  17. 26

    Це чудово, проте так само, як птахи літають. Було б чудово спробувати якось включити API Google Maps до цього (можливо, за допомогою доріг тощо), лише щоб дати ідею, використовуючи інший вид транспорту. Я ще не створив змодельовану функцію відпалу в PHP, яка могла б запропонувати ефективне рішення проблеми мандрівного продавця. Але я думаю, що, можливо, я зможу повторно використати для цього деякі ваші коди.

  18. 27

    Привіт Дуглас
    велике спасибі за цю статтю - ви просто заощадили мені багато часу.
    піклуватися,
    німрод @ Ізраїль

  19. 28

    Гарна стаття! Я знайшов багато статей, в яких описувалося, як обчислити відстань між двома точками, але я справді шукав фрагмент SQL.

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

    2 дні досліджень, щоб нарешті знайти цю сторінку, яка вирішує мою проблему. Схоже, мені краще вибити мою WolframAlpha і розібратися з моєю математикою. Перехід від WHERE до HAVING мій сценарій справний. ДЯКУЮ

  25. 37
    • 38

      Дякую Георгію. Я продовжував отримувати стовпець "відстань" не знайдений. Одного разу я змінив ДЕ ДЕ на МАЄ, це спрацювало як шарм!

  26. 39

    Я хотів би, щоб це була перша сторінка, яку я знайшов на цьому. Спробувавши безліч різних команд, це була єдина, яка працювала належним чином і з мінімальними змінами, необхідними для власної бази даних.
    Спасибі велике!

  27. 40

    Я хотів би, щоб це була перша сторінка, яку я знайшов на цьому. Спробувавши безліч різних команд, це була єдина, яка працювала належним чином і з мінімальними змінами, необхідними для власної бази даних.
    Спасибі велике!

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

    Дякую, Дугласе, SQL-запит - це саме те, що мені потрібно, і я думав, що мені доведеться написати його самому. Ви врятували мене від можливої ​​години кривої навчання довготи широти!

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

    дякую за розміщення цієї корисної статті,  
    але я чомусь хотів би запитати
    як отримати відстань між кордами всередині mysql db та кордами, вставленими користувачем у php?
    для більш чіткого опису:
    1. користувач повинен вставити [id] для вибору вказаних даних з баз даних і власних координат користувача
    2. PHP-файл отримує цільові дані (координати) за допомогою [id], а потім обчислює відстань між користувачем та цільовою точкою

    або можете просто отримати відстань від коду нижче?

    $ qry = “ВИБРАТИ *, (((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((„. $ широта. "* pi () / 180)) * cos ((` Latitude` * pi () / 180)) * cos (((". $ longitude." - "Longitude`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) як відстань ВІД `MyTable` WHERE відстань> =". $ Distance. " >>>> чи можу я “вийняти” відстань звідси?
    знову дякую,
    Тіммі С.

  41. 60

    добре, все, що я спробував, не працює. Я маю на увазі те, що у мене працює, але відстань далеко.

    Хтось міг би побачити, що не так з цим кодом?

    if (isset ($ _ POST ['submit'])) {$ z = $ _POST ['поштовий індекс']; $ r = $ _POST ['радіус']; ехо “Результати для“. $ z; $ sql = mysql_query (“ВИБЕРИТЕ ВИЗНАЧЕНИЙ m.zipcode, m.MktName, m.LocAddSt, m.LocAddCity, m.LocAddState, m.x1, m.y1, m.verified, z1.lat, z2.lon, z1. місто, z1.state ВІД mrk m, zip z1, zip z2 ДЕ m.zipcode = z1.zipcode І z2.zipcode = $ z AND (3963 * acos (усіка (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 ") або померти (mysql_error ()); while ($ 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']; $ довгота2 = $ рядок ['x1']; $ city = $ row ['city']; $ state = $ row ['state']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = відстань ($ lat1, $ lon1, $ lat2, $ lon2); $ перевірено = $ рядок ['перевірено']; if ($ verify == '1') {echo “”; echo “”. $ store. ””; echo $ dis. ”Милі (милі)”; ехо “”; } else {echo “”. $ store. ””; echo $ dis. ”Милі (милі)”; ехо “”; }}}

    мої функції.php код
    функція getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi') {$ theta = $ longitude1 - $ longitude2; $ відстань = (sin (deg2rad ($ latitude1)) * * sin (deg2rad ($ latitude2))) + (cos (deg2rad ($ latitude1)) * cos (deg2rad ($ latitude2)) * cos (deg2rad ($ theta)) ); $ відстань = acos ($ відстань); $ відстань = rad2deg ($ відстань); $ відстань = $ відстань * 60 * 1.1515; перемикач ($ одиниця) {case 'Mi': break; справа 'Km': $ відстань = $ відстань * 1.609344; } return (раунд ($ відстань, 2)); }

    Заздалегідь спасибі

  42. 61
  43. 62

    Гей, Дуглас, чудова стаття. Я знайшов ваше пояснення географічних понять і коду справді цікавим. Єдиною моєю пропозицією було б розмістити та відступити код для відображення (наприклад, Stackoverflow). Я розумію, що ви хочете заощадити простір, але звичайний інтервал / відступ коду значно полегшить мені, програмісту, читання та розбір. У всякому разі, це дрібниця. Продовжуйте велику роботу.

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

    здається швидшим (mysql 5.9) використовувати подвійну формулу в select та де:
    $ формула = “(((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((„. $ latitude. ”* Pi () / 180)) * cos ((` Latitude` * pi () / 180)) * cos ((((“. $ Longitude.” - `Longitude`) * pi () / 180)))) * 180 / пі ()) * 60 * 1.1515 * 1.609344) ”;
    $ sql = 'ВИБЕРІТЬ *,'. $ формула. ' як відстань від таблиці ДЕ '.. $ формула.' <= '. $ відстань;

  51. 71
  52. 72

    Велике спасибі за зріз цієї статті. Це дуже корисно.
    Спочатку PHP був створений як проста скриптова платформа під назвою “Персональна домашня сторінка”. В наш час PHP (скорочення від Hypertext Preprocessor) є альтернативою технології Active Server Pages (ASP) Microsoft.

    PHP - це мова з відкритим кодом на стороні сервера, яка використовується для створення динамічних веб-сторінок. Його можна вбудувати в HTML. PHP зазвичай використовується спільно з базою даних MySQL на веб-серверах Linux / UNIX. Це, мабуть, найпопулярніша мова сценаріїв.

  53. 73

    Я виявив, що вищевказане рішення не працює належним чином.
    Мені потрібно змінити на:

    $ qqq = “ВИБРАТИ *, (((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((` latt` * pi () / 180)) + cos ((”. $ широта. "* pi () / 180)) * cos ((` latt` * pi () / 180)) * cos (((". $ longitude." - `longt`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515) як відстань ВІД `реєстру`“;

  54. 75
  55. 76

    Привіт, будь ласка, мені дійсно знадобиться ваша допомога у цьому.

    Я зробив запит на отримання свого веб-сервера http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = широта $
    -2.23389 = довгота $
    і 20 = відстань, яку я хочу отримати

    Однак, використовуючи формулу, вона отримує всі рядки в моїй базі даних

    $ результати = 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) як відстань ВІД маркерів, ЩО МАЮ відстань> = “. $ Відстань));

    [{"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”: ”Піцерія на східному узбережжі Амічі”, “address”: ”790 Castro St, Mountain View, CA”, “lat”: 37.387138366699, “lng”: -122.08323669434, "відстань": 16079.175940152}, {"id": 3, "name": "Pizza Bar & Grill Kapp's", "address": "191 Castro St, Mountain View, CA", "lat": 37.393886566162, ”Lng”: - 122.07891845703, “відстань”: 16078.381373826}, {“id”: 4, “name”: ”Піца круглого столу: Маунтін-В’ю”, “адреса”: ”570 N Shoreline Blvd, Mountain View, CA”, ”Lat”: 37.402652740479, “lng”: - 122.07935333252, “distance”: 16077.420540582}, {“id”: 5, “name”: ”Піца та паста Тоні і Альби”, “address”: ”619 Escuela Ave, Mountain Вид, Каліфорнія ”,” lat ”: 37.394012451172,“ lng ”: - 122.09552764893,“ distance ”: 16078.563225154}, {“ id ”: 6,“ name ”:” Піца з дерев’яною пічкою ”,“ address ”:” 4546 El Camino Real, Лос-Альтос, Каліфорнія ”,” lat ”: 37.401725769043,“ lng ”: - 122.11464691162,“ distance ”: 16077.937560795}, {“ id ”: 7,” name ”:“ Бари та грилі ”,“ address ”:” 24 Whiteley Street, Manchester ”,“ lat ”: 53.485118865967,“ lng ”: - 2.1828699111938,“ distance ”: 8038.7620112314}]

    Я хочу отримати лише рядки з 20 милями, але це приносить всі рядки. Будь ласка, що я роблю не так

Що ви думаєте?

Цей сайт використовує Akismet для зменшення спаму. Дізнайтеся, як обробляються ваші дані коментарів.