Вычислить или запросить расстояние по большому кругу между точками широты и долготы, используя формулу гаверсинуса (примеры PHP, Python, MySQL, MSSQL)

Формула гаверсинуса — расстояние по большому кругу — PHP, Python, 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)); 
}

Переменные:

  • $широта1 – переменная для широты вашего первого местоположения.
  • $Долгота1 – переменная для долготы вашего первого местоположения
  • $широта2 – переменная для широты вашего второго местоположения.
  • $Долгота2 – переменная для долготы вашего второго местоположения.
  • $единица - значение по умолчанию миль. Это может быть обновлено или передано как километров.

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

В любом случае, вот формула Python для расчета расстояния между двумя точками (вместе с преобразованием мили в километр), округленная до двух знаков после запятой. Спасибо моему сыну Биллу Карру, специалисту по данным в OpenINSIGHTS, для кода.

from numpy import sin, cos, arccos, pi, round

def rad2deg(radians):
    degrees = radians * 180 / pi
    return degrees

def deg2rad(degrees):
    radians = degrees * pi / 180
    return radians

def getDistanceBetweenPointsNew(latitude1, longitude1, latitude2, longitude2, unit = 'miles'):
    
    theta = longitude1 - longitude2
    
    distance = 60 * 1.1515 * rad2deg(
        arccos(
            (sin(deg2rad(latitude1)) * sin(deg2rad(latitude2))) + 
            (cos(deg2rad(latitude1)) * cos(deg2rad(latitude2)) * cos(deg2rad(theta)))
        )
    )
    
    if unit == 'miles':
        return round(distance, 2)
    if unit == 'kilometers':
        return round(distance * 1.609344, 2)

Переменные:

  • широта1 – переменная для вашего первого местоположения широта.
  • долгота1 – переменная для вашего первого местоположения долгота
  • широта2 – переменная для вашего второго местоположения широта.
  • долгота2 – переменная для вашего второго местоположения долгота.
  • Ед. изм - значение по умолчанию миль. Это может быть обновлено или передано как километров.

MySQL: получение всех записей в диапазоне путем вычисления расстояния в милях с использованием широты и долготы

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

MySQL: получение всех записей в диапазоне путем вычисления расстояния в километрах с использованием широты и долготы

А вот 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 мест по всей Северной Америке, и он отлично сработал.

Географическое расстояние Microsoft SQL Server: STDistance

Если вы используете Microsoft SQL Server, они предлагают свои собственные функции, STРасстояние для вычисления расстояния между двумя точками с использованием типа данных Geography.

DECLARE @g geography;  
DECLARE @h geography;  
SET @g = geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656)', 4326);  
SET @h = geography::STGeomFromText('POINT(-122.34900 47.65100)', 4326);  
SELECT @g.STDistance(@h);  

Советую Манашу Саху, вице-президенту и архитектору Highbridge.

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

  1. 1

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

  2. 2

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

  3. 4
  4. 5
  5. 8

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

    Невероятно полезно, большое спасибо! У меня были некоторые проблемы с новым «ИМЕТЬ», а не «ГДЕ», но как только я прочитал здесь комментарии (примерно после получаса скрипа зубами от разочарования =P), у меня все заработало. Спасибо ^_^

  14. 19
  15. 20

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

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

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

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

  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

      Спасибо Георгий. Я продолжал получать столбец «расстояние», не найденный. Как только я изменил WHERE на HAVING, это сработало как шарм!

  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
  40. 58

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

    или можно просто получить расстояние из приведенного ниже кода?

    $qry = "SELECT *,(((acos(sin((".$latitude."*pi()/180)) * sin((`Latitude`*pi()/180))+cos((". $широта.»*pi()/180)) * cos((`Широта`*pi()/180)) * cos(((“.$longitude.”- `Долгота`)*pi()/180) )))*180/pi())*60*1.1515*1.609344) как расстояние ОТ `MyTable` ГДЕ расстояние >= «.$расстояние». >>>>Могу ли я «вынести» расстояние отсюда?
    еще раз спасибо,
    Тимми С

  41. 60

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

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

    if(isset($_POST['отправлено'])){ $z = $_POST['zipcode']; $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. город, 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()); в то время как ($ row = mysql_fetch_array ( $ sql )) { $ store1 = $ row ['MktName']. $store = $row['LocAddSt'].””; $store .= $row['LocAddCity'].", ".$row['LocAddState']." «.$row['zipcode']; $широта1 = $строка['широта']; $longitude1 = $строка['длина']; $широта2 = $строка['y1']; $longitude2 = $строка['x1']; $город = $строка['город']; $state = $строка['состояние']; $dis = getnew($широта1, $долгота1, $широта2, $долгота2, $единица = 'Ми'); // $dis = расстояние($lat1, $lon1, $lat2, $lon2); $проверено = $строка['проверено']; if($verified == '1'){ эхо ""; эхо "".$store."; эхо $dis . «в милях»; эхо «»; } else { echo "".$store."; эхо $dis . «в милях»; эхо «»; } }}

    мой код functions.php
    function getnew($latitude1, $longitude1, $latitude2, $longitude2, $unit = 'Mi') { $theta = $longitude1 – $longitude2; $distance = (sin(deg2rad($latitude1)) * sin(deg2rad($latitude2))) + (cos(deg2rad($latitude1)) * cos(deg2rad($широта2)) * cos(deg2rad($theta)) ); $расстояние = acos($расстояние); $расстояние = rad2deg($расстояние); $расстояние = $расстояние * 60 * 1.1515; switch($unit) { case 'Mi': break; случай «км»: $расстояние = $расстояние * 1.609344; } возврат (раунд($расстояние,2)); }

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

  42. 61
  43. 62

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

  44. 64
  45. 65

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

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

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

  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) как расстояние ОТ `регистра` “;

  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((“.$широта.”*pi()/180)) * cos((широта*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, «расстояние»: 16079.175940152}, {«id»: 3, «имя»: «Kapp's Pizza Bar & Grill», «адрес»: «191 Castro St, Mountain View, CA», «широта»: 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, «имя»: «Tony & Alba's Pizza & Pasta», «address»: «619 Escuela Ave, Mountain Вид, Калифорния», «широта»: 37.394012451172, «длина»: -122.09552764893, «расстояние»: 16078.563225154}, {«id»: 6, «имя»: «Пицца на дровах с орегано», «адрес»: «4546 Эль-Камино-Реал, Лос-Альтос, Калифорния», «широта»: 37.401725769043, «длина»: -122.11464691162, «расстояние»: 16077.937560795}, {« id»: 7», «name»: «The Bars and Grills», «address»: «24 Whiteley Street, Manchester», «lat»: 53.485118865967, «lng»:-2.1828699111938, «distance»: 8038.7620112314}]

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

  56. 77

    Я ищу аналогичный запрос, но немного активизировался - короче говоря, это группировать все координаты в пределах 2 миль от каждой координаты, а затем подсчитывать, сколько координат в каждой группе, и выводить только одну группу с наибольшим количеством координат - даже если у вас есть более одной группы среди групп с наибольшим количеством координат — просто выведите случайную группу из групп с одинаковым наибольшим числом —

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

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