sunnyblik (sunnyblik) wrote,
sunnyblik
sunnyblik

Category:

Находим ближайшие станции метро и расстояния до них, используя Yandex Map

Вступление:
Есть большой список организаций. Список постоянно пополняется сразу множеством организаций. Каждой нужно присвоить множество ближайших станций метро и расстояние по прямой до нее.
В этой статье я рассмотрю, как это можно сделать на php, не прибегая к js.

Задача:
Известна координата организации (можно узнать через http запрос). Также известны координаты всех станций метро (они заранее лежат в БД). Написать скрипт на php, который будет присваивать каждой организации несколько ближайших метро, а также можно расстояние до него.

Решение:
Это полезно для понимания! Почитайте базовые знания в географических координатах: https://leonid.shevtsov.me/post/chto-ty-dolzhen-znat-pro-geograficheskie-koordinaty/

1. Структура БД
Есть 3 таблицы:  1 - организация, 2 - метро, 3 - вспомогательная таблица (где я указываю id_org, id_metro, distance). Другими словами: связь многие-ко-многим через вспомогательную таблицу.
В таблице метро находятся все метро со своими координатами (долгота, широта). Да, надо заранее занести все координаты. Зато в будущем брать координаты из своей БД, и сопоставлять со своим id метро.
Координаты станций метро Москвы, правда, не всех, можно взять отсюда.
2. Выбираем из БД только ближайшие метро в определенном радиусе
Допустим у нас есть координаты организации. Также имеются координаты в БД для каждого метро, которые хранятся в таблице metro. Как выбрать с минимальными производительными затратами ближайшие метро, да еще узнать расстояния по прямой?
Для начала, рассмотрим и поймем логику, по которой будем действовать.
Возьмем самую простую систему координат и пусть точка О, с координатами (3,2)  - это организация. (Рис. 1)
Точки М - это станции метро.
Зеленый круг - это радиус, в котором будем искать ближайшие станции метро.
metror
Рис. 1

В первую очередь необходимо выбрать только те метро, которые попадают в радиус. (Для того, чтобы вычислять расстояния только до тех метро, которые попали в этот радиус, а не до всех существующих - ибо на реальной карте Москвы - их больше 180)

Применительно к рис.1:
Откладываем от точки О отрезки (радиус) по оси X (влево и вправо) и по оси Y (вверх и вниз) и получаем координаты квадрата, в котором будем искать метро.
Т.е., грубо и округленно координаты которых лежат в интервале:
по оси Х: от 0 до 6
по оси Y: от 0 до 6
Запрос к MySQL будет выглядеть примерно так:
SELECT * FROM `metro` WHERE `x` BETWEEN '0' AND '6' AND `y` BETWEEN '0' AND '6'
В результате получим список метро, которые окажутся в нужном нам квадрате
А далее - выбрать нужное количество ближайших и записать их во вспомогательную таблицу.


3. Находим расстояние между двумя точками (организацией и метро)
На данном этапе у нас есть координаты каждого метро, и собственно координаты нашей организации.
Теперь надо поочередно для каждого метро рассчитать расстояние и выбрать нужное количество ближайших.
С последним все понятно, остановлюсь на нахождении расстояния между двумя точками координат (спутниковых, они же Yandex Map, они же Google Map)

Находим расстояние между 2мя точками через формулу:
cos(d) = sin(φА)·sin(φB) + cos(φА)·cos(φB)·cos(λА − λB),
Прочитать статью про эту формулу для наилучшего понимания =)

На php это будет выглядеть так (возвращает дистанцию в м.):
function distance($longitude1, $latitude1, $longitude2, $latitude2)
{
 $earth_radius = 6372797; //средний радиус Земли в м
 
 $dLat = deg2rad($latitude2 - $latitude1);
 $dLon = deg2rad($longitude2 - $longitude1);
 
 $a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($latitude1)) * cos(deg2rad($latitude2)) * sin($dLon/2) * sin($dLon/2);
 $c = 2 * asin(sqrt($a));
 $d = $earth_radius * $c;
 
 return $d;
}







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


Используем http запрос для получения ближайших метро
Также можно использовать get запрос для получения текстового списка метро в формате XML или JSON.
Подробнее в разделе офиц. документации
Например, запрос получения ближайших метро для Марсова Поля в СПб, будет выглядеть так:
http://geocode-maps.yandex.ru/1.x/?geocode=30.331393,59.943419&kind=metro
Недостаток: мы получим названия метро, которые будем вынуждены искать в своей БД.

Полезные ссылки:
Хабр. Как работает Yandex MAP API 2.0
Формула вычисления расстояния между двух точек на Земле. Все языки


Вторая часть статьи
Собственно, на свой вопрос я дал ответ в первой части. Во второй я рассмотрю дополнительный весьма полезный и интересный материал, но он больше предназначен для наилучшего понимания данной темы.

2ой способ нахождения расстояния между двумя точками спутниковых координат (или между двумя точками Yandex Map / Google Map)
Когда я рассматривал этот способ, я еще не знал формулы и кода, которые написал в первой части. Эти 2 способа работают почти одинаково. Разница вычислений составляет всего 2 метра. И для данного случая это совершенно не существенно.
Найдем расстояние от точки О до М3. (Рис. 2)
metro
Рис. 2

Вспоминаем теорему Пифагора (квадрат гипотенузы равен сумме квадратов катетов). Т.к. координаты всех точек нам известны - то легко находим длину С (расстояние от метро до организации).

Гладко было на бумаге, да забыли про овраги
А если точнее,  то координаты в Yandex Map (да и в Google Map) у нас указаны по долготе и широте в градусах, а следовательно, необходимо переводить градусы широты и долготы в метры, используя коэффициенты. Коэффициенты отличаются в зависимости от широты
Необходимые коэффициенты можно посмотреть в таблице "Длина градуса широты и долготы".

Таблица: длина градуса широты и долготы
Эту таблицу я не нашел в интернете  - поэтому нашел ее в в книге: Министерство Обороны Союза ССР. Главное управление навигации и океанографии. Мореходные таблицы. 1976 г. =)
таблица


Рассмотрим код для окончательного понимания как этим пользоваться:
Функция возвращает расстояние в метрах между объектами
public function distance2($lng1, $lat1, $lng2, $lat2)
{
 define('LNG_SPB', 55801); //градус долготы в метрах для СПБ
 define('LAT_SPB', 111414); //градус широты в метрах для СПБ
 define('LNG_MOSCOW', 63995); //градус долготы в метрах для Москвы
 define('LAT_MOSCOW', 111325); //градус широты в метрах для Москвы
 
 $diffLng = abs($lng2 - $lng1); // разница долготы по модулю
 $diffLat = abs($lat2 - $lat1); // разница широты по модулю
 
 $diffLngM = $diffLng * LNG_SPB; //расстояние в метрах по долготе
 $diffLatM = $diffLat * LAT_SPB; //расстояние в метрах по широте
 
 return $distance = sqrt($diffLngM*$diffLngM + $diffLatM*$diffLatM);
} 






Проверяем первый и второй способ
Для проверки, измеряем расстояние первым и вторым способом между м. "Невский проспект": 30.327144,59.935387 и м. "Пл. Восстания": 30.360704,59.931639. Координаты написаны в порядке (долгота, широта).
function distance() возвратит: 1916.0274249762 (м.)
function distance2() возвратит: 1918.6737626891 (м.)
А если на карте вручную кликнуть на эти станции метро (берем погрешность клика) - то расстояние будет: 1916 м.
distance

Пишите комментарии и задавайте вопросы, если они есть =)


UPDATE 27.07.2017:
КОД НА MySQL:
Ищем организации в радиусе  = 7 км.
SELECT *,
111.414 * DEGREES(ACOS(COS(RADIANS(59.901346)) * COS(RADIANS(X(coordinates))) *
COS(RADIANS(30.355807 - Y(coordinates))) + SIN(RADIANS(59.901346)) * SIN(RADIANS(X(coordinates))))) AS distance_in_km
FROM cemeteries
WHERE MBRContains( LINESTRING(POINT(59.901346 - 0.009 * 3 , 30.355807 - 0.01792 * 6371),
POINT(59.901346 + 0.009 * 3 , 30.355807 + 0.01792 * 3 )), coordinates )
AND ACOS(COS(RADIANS(59.901346)) * COS(RADIANS(X(coordinates))) * COS(RADIANS(Y(coordinates)) - RADIANS(30.355807))
+ SIN(RADIANS(59.901346)) * SIN(radians(X(coordinates)))) <= 3 / 6371




http://stackoverflow.com/questions/6919661/select-within-20-kilometers-based-on-latitude-longitude - Рабочий пример c POINT на mysql!!!!!
http://stackoverflow.com/questions/24370975/find-distance-between-two-points-using-latitude-and-longitude-in-mysql - расстояние к км между двумя точками/ Рабочий пример с POINT
https://www.frameworks.su/article/rasstoyanie_do_bligayshih_stantsii_metro - хорошая статья с применением php и без POINT
Tags: yandexmap
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 1 comment