
Мне в работе с одним из сайтов недвижимости нужно было сделать функционал который для заданной точке на карте города ищет ближайшие станции метро и рассчитывает расстояние до них. Все это должно отрабатывать на PHP скрипте которому передается через аякс адрес, который он обрабатывает и возвращает только 3 станции метро и расстояние до них (как к примеру в Яндекс картах), также рассчитывает время пешком до метро.
Список станций метрополитена
Начнем с того что нужно сделать список станций метро и проставить им координаты широты и долготы. Список станций Московского метрополитена можно получить несколькими способами. Первый способ самый простой - ручной. Находим список станций в интернете, например на официальном сайте Московского метро или в статье на википедии, и вручную перенести их в таблицу БД. Этот вариант немного однообразный и не очень интересный, поэтому есть и второй вариант.
Второй вариант использовать API сайтов которые отдают список станций метро с координатами, распарсить их и автоматически добавить в базу. Этот вариант был немного быстрее и интересней. Нужные нам данные по API из тех сайтов что я просмотрел, отдает только HeadHunter, остальные Superjob, Cian, Avito - отдают только список станций метро без координат.
Чтобы получить список всех станций для метрополитена, необходимо отправить GET запрос на указанный адрес https://api.hh.ru/metro/1, где 1 - это ID города в базе HeadHunter. Если вам нужен будет другой город смотрите в документации HH.
API возвращает список станций с группировкой по веткам метро в формате JSON, также он отдает и цвет линии, что очень удобно.
Хранить данные мы будем в MySQL в двух таблицах, станции и линии метро. Примерная структура таблиц такая:
CREATE TABLE `metro_lines` ( `id` int(11) NOT NULL auto_increment, `name` varchar(250) NOT NULL, `color` varchar(6) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; CREATE TABLE `metro` ( `id` int(11) NOT NULL auto_increment, `name` varchar(250) NOT NULL, `latitude` decimal(9,6) NOT NULL, `longitude` decimal(9,6) NOT NULL, `line_id` int(11) NOT NULL, `order` tinyint(4) NOT NULL default '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
Скрипт для импорта станций метро выглядит примерно так:
/**
* Импортируем станции метро в нашу базу
*/
public function importFromApi(){
//Ссылка на json выгрузку станций метро для города Москвы (ID:1)
$url = "https://api.hh.ru/metro/1";
// Метод getUrl делает запрос с помощью cURL
$file = $this->getUrl($url);
$file = json_decode($file, true);
// Очищаем таблицы с линиями и станциями метро
$this->pdo->prepare("TRUNCATE TABLE `metro`")->execute();
$this->pdo->prepare("TRUNCATE TABLE `metro_lines`")->execute();
foreach($file['lines'] as $lines){
// Сохраняем линию метро
$query = $this->pdo->prepare("INSERT INTO `metro_lines` SET `color` = :color, `name` = :name");
$query->execute( array(':color'=>$lines['hex_color'], ':name'=>$lines['name']));
$line_id = $this->pdo->lastInsertId();
if($line_id){
foreach($lines['stations'] as $station){
// Сохраняем станции и привязываем их к линиям метро
$query = $this->pdo->prepare("INSERT INTO `metro` SET `latitude` = :latitude, `longitude` = :longitude, `line_id` = :line_id, `name` = :name, `order` = :order");
$query->execute( array(':longitude'=>$station['lng'], ':latitude'=>$station['lat'], ':line_id'=>$line_id, ':name'=>$station['name'], ':order'=>$station['order']));
}
}
}
}
Находим координаты нужного объекта
Для того чтобы определить ближайшие станции метро нам нужно определить сначала координаты для нашего объекта, зная его адрес. Для это отправим запрос к геокодеру Яндекса.
/**
* Находим координаты объекта через API Yandex
*
* @param string $street Адрес объекта
*/
public function getLngLatStreet($street){
$url = 'https://geocode-maps.yandex.ru/1.x/?format=json&geocode='.urlencode('Москва, '.$street);
$yandex_street_response = $this->getUrl($url);
$geocodeInfo = json_decode($yandex_street_response, true);
$lngLat = $geocodeInfo['response']['GeoObjectCollection']['featureMember'][0]['GeoObject']['Point']['pos'];
// Если улица не найдено то precision == other и скорей всего координаты будут центра указанного города, в нашем случае это Москва.
if($lngLat && $geocodeInfo['response']['GeoObjectCollection']['featureMember'][0]['GeoObject']['metaDataProperty']['GeocoderMetaData']['precision'] != 'other'){
list($longitude, $latitude) = explode(" ", $lngLat);
return array('longitude'=>$longitude, 'latitude'=>$latitude);
}
return false;
}
Находим ближайшие станции метро
Дальнейшим нашим действием будет рассчитать какие станции метро находятся в круге с нужным нам радиусом точнее, для простоты расчетов, квадрате см. рис 1.

Рис. 1
Точка О - здесь находится нужный нам объект, зеленым обозначена область в которой мы будем искать станции метро. Для этого нам нужно найти координаты точек A, B, C, D зная при этом радиус AO и координаты точки О. Код нахождения координат нужных нам точек будет такой:
/**
* Нахождение координат точки по координатам другой точки и известным длине и дирекционному углу данного направления, соединяющей эти точки
*
* @param float $longitude1 Долгота первой точки
* @param float $latitude1 Широта первой точки
* @param int $distance Максимальное расстояние до метро в км
* @param int $angle Дирекционный угол
* @param string $type Если нужно только одна координата широта или высота, то указываем ее
*/
public function getPoint($longitude, $latitude, $distance=2, $angle=0, $type=null){
// Переводим км в метры
$distance = $distance * 1000;
// Длина дуги параллели в 1° на экваторе в метрах
$latMeter = 111321;
$angle = deg2rad($angle);
$dx = $distance / ($latMeter * cos(deg2rad($latitude))) * cos($angle);
$dy = $distance / $latMeter * sin($angle);
$result = array(
'longitude' => $longitude + $dx,
'latitude' => $latitude + $dy
);
// Если нам нужна только одна координата (широта или высота), то возвращаем только ее
if($type){
return $result[$type];
}
return $result;
}
Для точек A и B нам нужно найти долготу, для C и D - широту. После нахождения координат можно выбирать станции из базы:
/**
* Находим N ближайших станций метро от заданного объекта
*
* @param float $longitude Долгота заданного объекта
* @param float $latitude Широта заданного объекта
* @param int $distance Максимальное расстояние до метро в км
* @param int $limit Максимальное количество станций
*/
public function getMetro($longitude, $latitude, $distance=2, $limit=null){
$sLng = $this->getPoint($longitude, $latitude, $distance, 180, 'longitude');
$eLng = $this->getPoint($longitude, $latitude, $distance, 0, 'longitude');
$sLat = $this->getPoint($longitude, $latitude, $distance, 270, 'latitude');
$eLat = $this->getPoint($longitude, $latitude, $distance, 90, 'latitude');
/** Выбираем информацию о станции, а также цвет ветки метро */
$query = $this->pdo->prepare("SELECT m.id, m.name, m.latitude, m.longitude, l.color FROM metro as m INNER JOIN metro_lines as l ON l.id = m.line_id WHERE m.latitude BETWEEN :sLat AND :eLat AND m.longitude BETWEEN :sLng AND :eLng");
$query->execute(array(':sLat'=>$sLat, ':eLat'=>$eLat, ':sLng'=>$sLng, ':eLng'=>$eLng));
$data = $query->fetchAll(PDO::FETCH_ASSOC);
if($data){
$list = array();
foreach($data as $v){
// Рассчитываем расстояние от объекта до метро в метрах
$v['distance'] = $this->distance($longitude, $latitude,$v['longitude'],$v['latitude']);
// Если расстояние более 1000 метров то переводим результат в км. Можно вынести в отдельный метод
$v['distance_format'] = ($v['distance'] >= 1000 ? round($v['distance']/1000,1).' км' : $v['distance'].' м');
$list[$v['distance']] = $v;
}
// Сортируем по расстоянию метро
ksort($list);
if($limit){
// Если задан лимит то выводим только первые N станций
return array_slice($list, 0, $limit);
}else{
return $list;
}
}
return false;
}
Рассчитываем расстояние до метро
Метод distance для расчета расстояния между двумя точками на карте:
/**
* Рассчитываем расстояние между двумя точками на карте
*
* @param float $longitude1 Долгота первой точки
* @param float $latitude1 Широта первой точки
* @param float $longitude2 Долгота второй точки
* @param float $latitude2 Широта второй точки
*/
public function distance($longitude1, $latitude1, $longitude2, $latitude2){
// Средний радиус Земли в метрах
$earthRadius = 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));
$distance = ceil($earthRadius * $c);
return $distance;
}
И на последок функция для загрузки страницы с помощью cURL:
/**
* Подгружаем страницу по URL
*
* @param string $url Ссылка на страницу
*/
public function getUrl($url){
$browser = array (
"user_agent" => "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729)",
"language" => "en-us,en;q=0.5"
);
$ch = curl_init();
if(isset($browser)){
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"User-Agent: {$browser['user_agent']}",
"Accept-Language: {$browser['language']}"
)
);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);
$output = curl_exec($ch);
$chinfo = curl_getinfo($ch);
curl_close($ch);
if ($chinfo['http_code'] && !in_array($chinfo['http_code'],array(404,403))) {
return $output;
}
return false;
}
Пример использования
Скрипт для нахождения ближайших станций метрополитена готов. Ниже пример как его можно использовать:
// Подключаем класс нашего скрипта
include("./metro.class.php");
// Доступы к БД
$setting = array(
'username' => 'root',
'password' => '',
'database' => 'test',
'host' => 'localhost',
);
// Максимальное расстояние до метро в км.
$distance = 3;
$metroClass = new Metro($setting);
$lngLat = $metroClass->getLngLatStreet('Красная площадь, 3');
if($lngLat){
$data = $metroClass->getMetro($lngLat['longitude'], $lngLat['latitude'], $distance, 3);
var_dump($data);
}
На этом все, класс и готовый пример можете скачать внизу по ссылке. Если будут вопросы или предложения пишите в комментариях.
Frameworks.su Шпаргалка вебмастера
А как изменить путь для выгрузки станций метро СПБ
url = "https://api.hh.ru/metro/2";
В каком файле прописан этот путь?
Или придется Менять построчно Ваш файл dump?
это нужно изменить в файле metro.class.php в строке 176
Уважаемый админ, а подскажите еще, в каком файле находится ссылка url = "https://api.hh.ru/metro/1";
Т.е я сейчас пытаюсь сделать то же самое, но для Спб, нашел, что его ID=2 и как теперь правильно прописать путь, для выгрузки станций метро?
Неужели построчно менять ваш файл dump
это нужно изменить в файле metro.class.php в строке 176
Да, увидел, поменял
Мои знания в этой области минимальны, поэтому прошу простить заранее за излишнюю тупость, но файл дамп же не изменился, а соответственно и результатом обработки пока являются станции Москвы..
Как заменить теперь данные таблиц в phpmyadmin?
include("./metro.class.php");
// Укажите доступы к БД
$setting = array(
'username' => 'root',
'password' => '',
'database' => 'test',
'host' => 'localhost',
);
$metroClass = new Metro($setting);
$metroClass->importFromApi();
Да, таблички обновились, но при любой улице теперь выдает "Нет ближайших станций метро".
Менял параметр максимального расстояния, но это не помогло.(
Доброго времени суток!
Скажите пожалуйста, как нужно импортировать данные в таблицу?
Т.е. тот файл, который предназначен для импорта данных, как его внедрить, как сделать на него ссылку?
Файл в архиве это sql дамп. Его можно импортировать в mysql через phpmyadmin или adminer
Все, нашел это в своем Денвере, в phpmyadmin, большое спасибо за помощь!
А почему просто не использовать геокодер Яндекса?
Он из коробки умеет искать ближайшие метро к точке, заданной координатами: https://tech.yandex.ru/maps/doc/geocoder/desc/concepts/input_params-docpage/
Просто нужно указать kind=metro
Это одно из применений и понятное для всех, на практике мне нужно было искать ближайшие объекты, которые забитые в БД с координатами, от местоположения пользователя