Frameworks.suFrameworks.su Шпаргалка вебмастера

  • Главная
  • Framework Kohana
  • PHP
  • Javascript
  • CSS
  • Сервисы
    • Генератор паролей
  • Контакты
Главная / PHP / Находим расстояние до ближайших станций метро

Находим расстояние до ближайших станций метро

17.02.2017 12 102981

Находим расстояние до ближайших станций метро

Мне в работе с одним из сайтов недвижимости нужно было сделать функционал который для заданной точке на карте города ищет ближайшие станции метро и рассчитывает расстояние до них. Все это должно отрабатывать на 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);
}

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

Скачать рабочий пример

Теги:
  • API
  • Yandex

Комментарии

  1. Sasha
    Sasha
    02.04.2018 17:44

    А как изменить путь для выгрузки станций метро СПБ
    url = "https://api.hh.ru/metro/2";
    В каком файле прописан этот путь?
    Или придется Менять построчно Ваш файл dump?

    Ответить
    • Администратор
      Администратор
      02.04.2018 17:54

      это нужно изменить в файле metro.class.php в строке 176

      Ответить
    • Alexandr
      Alexandr
      02.04.2018 17:40

      Уважаемый админ, а подскажите еще, в каком файле находится ссылка url = "https://api.hh.ru/metro/1";
      Т.е я сейчас пытаюсь сделать то же самое, но для Спб, нашел, что его ID=2 и как теперь правильно прописать путь, для выгрузки станций метро?
      Неужели построчно менять ваш файл dump

      Ответить
      • Администратор
        Администратор
        02.04.2018 17:53

        это нужно изменить в файле metro.class.php в строке 176

        Ответить
        • Alexandr
          Alexandr
          02.04.2018 18:07

          Да, увидел, поменял
          Мои знания в этой области минимальны, поэтому прошу простить заранее за излишнюю тупость, но файл дамп же не изменился, а соответственно и результатом обработки пока являются станции Москвы..
          Как заменить теперь данные таблиц в phpmyadmin?

          Ответить
          • Администратор
            Администратор
            02.04.2018 18:14

            include("./metro.class.php");

            // Укажите доступы к БД
            $setting = array(
            'username' => 'root',
            'password' => '',
            'database' => 'test',
            'host' => 'localhost',
            );

            $metroClass = new Metro($setting);
            $metroClass->importFromApi();

            Ответить
            • Alexandr
              Alexandr
              02.04.2018 21:44

              Да, таблички обновились, но при любой улице теперь выдает "Нет ближайших станций метро".
              Менял параметр максимального расстояния, но это не помогло.(

              Ответить
      • Alexandr
        Alexandr
        02.04.2018 17:08

        Доброго времени суток!
        Скажите пожалуйста, как нужно импортировать данные в таблицу?
        Т.е. тот файл, который предназначен для импорта данных, как его внедрить, как сделать на него ссылку?

        Ответить
        • Администратор
          Администратор
          02.04.2018 17:12

          Файл в архиве это sql дамп. Его можно импортировать в mysql через phpmyadmin или adminer

          Ответить
          • Alexandr
            Alexandr
            02.04.2018 17:30

            Все, нашел это в своем Денвере, в phpmyadmin, большое спасибо за помощь!

            Ответить
        • Ирен
          Ирен
          06.12.2017 15:58

          А почему просто не использовать геокодер Яндекса?
          Он из коробки умеет искать ближайшие метро к точке, заданной координатами: https://tech.yandex.ru/maps/doc/geocoder/desc/concepts/input_params-docpage/
          Просто нужно указать kind=metro

          Ответить
          • Администратор
            Администратор
            06.12.2017 16:03

            Это одно из применений и понятное для всех, на практике мне нужно было искать ближайшие объекты, которые забитые в БД с координатами, от местоположения пользователя

            Ответить

          Оставить комментарий Отмена

          *

          *

          *

          *

          Категории

          • PHP
            • Framework Kohana
          • Javascript
          • CSS
          • Администрирование

          Теги

          Framework Kohana Пример jQuery Уроки CRON Установка и настройка Backup CSS3 Валидация API Bash Cache Captcha i18n Linux

          Авторизация

          • Забыли пароль?
          • Регистрация

          Популярные статьи

          • Находим расстояние до ближайших станций метро

            Находим расстояние до ближайших станций метро

            17.02.2017 102982
          • Регистрация и авторизация пользователей. Модуль Auth в Kohana 3.3.x

            Регистрация и авторизация пользователей. Модуль Auth в Kohana 3.3.x

            02.07.2014 44646
          • Собственная система лайков на PHP и JQuery

            Собственная система лайков на PHP и JQuery

            06.04.2015 33241
          • Javascript — сумма прописью

            Javascript — сумма прописью

            07.07.2014 23090
          • Cross-domain ajax с помощью jQuery

            Cross-domain ajax с помощью jQuery

            24.04.2015 19964
          Copyright © 2014-2025 Frameworks.su. Все права защищены.