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

Формула Хаверсина - вычисление расстояния по большому кругу с помощью PHP или MySQL

В этом месяце я довольно много программировал на PHP и MySQL в отношении ГИС. Поискав в сети, мне было трудно найти некоторые из Географические расчеты чтобы найти расстояние между двумя точками, поэтому я хотел поделиться ими здесь.

Карта полетов в Европу с большим расстоянием по кругу

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

Это интересное начало, но оно не применимо к географии, поскольку расстояние между линиями широты и долготы не равное расстояние Кроме. По мере приближения к экватору линии широты расходятся дальше. Если вы используете какое-то простое уравнение триангуляции, оно может точно измерить расстояние в одном месте и совершенно неверно в другом из-за кривизны Земли.

Расстояние по большому кругу

Маршруты, которые проходят на большие расстояния вокруг Земли, известны как Расстояние по большому кругу. То есть… кратчайшее расстояние между двумя точками на сфере отличается от точек на плоской карте. Добавьте к этому тот факт, что линии широты и долготы не равноудалены ... и вы получите сложный расчет.

Вот фантастическое видео, объясняющее, как работают Великие круги.

Формула Хаверсина

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

Это применимо и в полете по воздуху - вы когда-нибудь смотрели на карту полетов и замечали, что они изогнуты? Это потому, что пролететь по арке между двумя точками короче, чем прямо к месту.

PHP: вычислить расстояние между двумя точками широты и долготы

В любом случае, вот формула 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, в которой я передаю долготу точки.
  • $ distance - это расстояние, на котором вы хотели бы найти все записи, меньшие или равные.
  • таблицу - это таблица ... вы захотите заменить это своим именем таблицы.
  • широта - это поле твоей широты.
  • долгота - это поле вашей долготы.

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, в которой я передаю долготу точки.
  • $ distance - это расстояние, на котором вы хотели бы найти все записи, меньшие или равные.
  • таблицу - это таблица ... вы захотите заменить это своим именем таблицы.
  • широта - это поле твоей широты.
  • долгота - это поле вашей долготы.

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

76 комментариев

  1. 1

    Спасибо вам большое за обмен. Это была легкая работа по копированию и вставке, и она отлично работает. Вы сэкономили мне много времени.
    FYI для всех, кто портирует на C:
    double deg2rad (двойной градус) {return deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Очень хороший постинг - работал очень хорошо - мне нужно было только изменить имя таблицы, в которой указаны широта и долгота. Это работает довольно быстро ... У меня достаточно небольшое количество широт и долгих (<400), но я думаю, что это будет хорошо масштабироваться. Хороший сайт тоже - я только что добавил его в свою учетную запись del.icio.us и буду регулярно проверять его.

  3. 4
  4. 5
  5. 8

    Я думаю, что вашему SQL нужен оператор наличия.
    вместо WHERE distance <= $ distance вам может понадобиться
    используйте 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

    Большое спасибо за сценарий, написанный на mysql, просто пришлось внести несколько незначительных изменений (ИМЕЕТ) 🙂
    Грет работа

  13. 18

    Невероятно полезно, большое спасибо! У меня были некоторые проблемы с новым «HAVING», а не с «WHERE», но как только я прочитал здесь комментарии (примерно после получаса скрежета зубами от разочарования = P), я понял, что он работает нормально. Спасибо ^ _ ^

  14. 19
  15. 20

    Имейте в виду, что подобный оператор выбора будет очень интенсивным в вычислительном отношении и, следовательно, медленным. Если у вас много таких запросов, это может довольно быстро увязнуть.

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

    Этим мы добиваемся значительного сокращения количества точек, по которым вы производите этот расчет. Если у вас есть миллион глобальных точек в базе данных, распределенных примерно равномерно, и вы хотите выполнить поиск в пределах 100 км, тогда ваш первый (быстрый) поиск будет на площади 10000 кв. Км и, вероятно, даст около 20 результатов (на основе равномерного распределения по площадь поверхности около 500 млн кв. км), что означает, что вы выполняете комплексный расчет расстояния для этого запроса 20 раз вместо миллиона раз.

    • 21

      Небольшая ошибка в этом примере… это будет в пределах 50 км (не 100), поскольку мы смотрим на «радиус» нашего… квадрата.

      • 22

        Отличный совет! На самом деле я работал с разработчиком, который написал функцию, которая вытягивает внутренний квадрат, а затем рекурсивную функцию, которая создает «квадраты» по периметру для включения и исключения оставшихся точек. Получился невероятно быстрый результат - он мог оценить миллионы точек за микросекунды.

        Мой подход, описанный выше, определенно «грубый», но способный. Еще раз спасибо!

        • 23

          Дуга,

          Я пытался использовать mysql и php, чтобы оценить, находится ли длинная точка широты внутри многоугольника. Вы знаете, публиковал ли ваш друг-разработчик какие-либо примеры того, как выполнить эту задачу. Или вы знаете какие-нибудь хорошие примеры. Заранее спасибо.

  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 сообщает мне, что расстояние не существует в виде столбца, я могу использовать порядок, я могу сделать это без WHERE, и он работает, но не с ним ...

  17. 26

    Это здорово, но как птицы летают. Было бы здорово попытаться каким-то образом включить API карт Google в это (возможно, используя дороги и т. Д.) Просто чтобы дать представление об использовании другого вида транспорта. Мне еще предстоит создать на 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
  37. 53
  38. 55
  39. 56

    Дуглас, спасибо за этот замечательный код. Я ломал голову над тем, как это сделать на моем портале сообщества GPS. Вы сэкономили мне часы.

  40. 58

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

    или можно просто дистанцироваться от кода ниже?

    $ qry = «ВЫБРАТЬ *, (((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) как расстояние ОТ `MyTable` ГДЕ distance> =«. $ Distance. » >>>> Можно отсюда дистанцию ​​«вынести»?
    еще раз спасибо,
    Тимми С

  41. 60

    хорошо, все, что я пробовал, не работает. То есть то, что у меня есть, работает, но расстояния очень большие.

    Может ли кто-нибудь увидеть, что не так с этим кодом?

    if (isset ($ _ POST ['отправлено'])) {$ z = $ _POST ['почтовый индекс']; $ r = $ _POST ['радиус']; echo «Результаты для«. $ 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. city, z1.state ОТ mrk m, zip z1, zip z2 ГДЕ m.zipcode = z1.zipcode И z2.zipcode = $ z И (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 ") или die (mysql_error ()); в то время как ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. ""; $ store = $ row ['LocAddSt']. ””; $ store. = $ row ['LocAddCity']. »,«. $ row ['LocAddState']. » «. $ Row ['почтовый индекс']; $ latitude1 = $ row ['широта']; $ longitude1 = $ row ['долгота']; $ latitude2 = $ row ['y1']; $ longitude2 = $ row ['x1']; $ city = $ row ['город']; $ state = $ row ['состояние']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = distance ($ lat1, $ lon1, $ lat2, $ lon2); $ проверено = $ строка ['проверено']; если ($ Verified == '1') {echo «»; echo «». $ store. »»; эхо $ дис. «Миля (-ы) далеко»; эхо «»; } else {echo «». $ store. »»; эхо $ дис. «Миля (-ы) далеко»; эхо «»; }}}

    мой код functions.php
    функция 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 ($ distance); $ distance = $ distance * 60 * 1.1515; переключатель ($ unit) {case 'Mi': break; case 'Km': $ distance = $ distance * 1.609344; } return (round ($ distance, 2)); }

    заранее спасибо

  42. 61
  43. 62

    Привет, Дуглас, отличная статья. Я нашел ваше объяснение географических концепций и кода действительно интересным. Единственное, что я предлагаю, - это выделить пробел и сделать отступ для кода для отображения (например, Stackoverflow). Я понимаю, что вы хотите сэкономить место, но обычные интервалы / отступы кода значительно упростят мне, как программисту, чтение и анализ. Во всяком случае, это мелочь. Продолжайте в том же духе.

  44. 64
  45. 65

    здесь при использовании с функцией мы получаем один тип расстояния ... при использовании запроса его прибывает другой тип расстояния

  46. 66
  47. 67
  48. 68
  49. 69
  50. 70

    кажется быстрее (mysql 5.9) использовать дважды формулу в выборе и где:
    $ формула = «(((acos (sin ((«. $ latitude. »* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((«. $ latitude. ”* Pi () / 180)) * cos ((` Широта` * pi () / 180)) * cos (((«. $ Longitude.» - `Долгота`) * pi () / 180)))) * 180 / пи ()) * 60 * 1.1515 * 1.609344) »;
    $ sql = 'SELECT *,'. $ formula. ' как расстояние ОТ таблицы WHERE '.. $ formula.' <= '. $ distance;

  51. 71
  52. 72

    Большое спасибо за статью. Это очень полезно.
    Изначально PHP создавался как простая платформа для написания сценариев под названием «Персональная домашняя страница». В настоящее время PHP (сокращение от Hypertext Preprocessor) является альтернативой технологии Microsoft Active Server Pages (ASP).

    PHP - это серверный язык с открытым исходным кодом, который используется для создания динамических веб-страниц. Его можно встроить в HTML. PHP обычно используется вместе с базой данных MySQL на веб-серверах Linux / UNIX. Вероятно, это самый популярный язык сценариев.

  53. 73

    Я обнаружил, что вышеуказанное решение не работает должным образом.
    Мне нужно изменить на:

    $ qqq = «ВЫБРАТЬ *, (((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) как расстояние ОТ `register`«;

  54. 75
  55. 76

    Здравствуйте, пожалуйста, мне действительно понадобится ваша помощь в этом.

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

    Однако, используя вашу формулу, он извлекает все строки в моей базе данных

    $ 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) как расстояние ОТ маркеров ИМЕЕТ расстояние> = “. $ Distance));

    [{«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 »:« Пицца на дровах орегано »,« address »:« 4546. Эль-Камино Реал, Лос-Альтос, Калифорния »,« широта »: 37.401725769043,« долг »: - 122.11464691162,« расстояние »: 16077.937560795}, {« id »: 7,« name »:« Бары и грили »,« address »:« 24 Whiteley Street, Manchester »,« lat »: 53.485118865967,« lng »: - 2.1828699111938,« distance »: 8038.7620112314}]

    Я хочу получить только строки с 20 милями, но он приносит все строки. Пожалуйста, что я делаю не так

Как вы думаете?

Этот сайт использует Akismet для уменьшения количества спама. Узнайте, как обрабатываются ваши данные комментариев.