Pull to refresh

Geocoding with PHP and the Google Maps API

Reading time 12 min
Views 37K
Original author: Quentin Zervaas
Большинство приложений в интернете сейчас начинает встраивать карты местности.
Работе с картами, используя Google Map API, посвящена данная статья.

Далее описываются принципы работы с Google API, реализованы наипростейшие классы используя SimpleXML (многим может показаться, что код чрезвычайно прост). Термины geocoding и geocoder в статье оставлены на английском языке.


Введение

Geocoding — это процесс нахождения широты и долготы по введенному адресу. Google предоставляет бесплатный доступ к разработанному инструментарию, доступ к которому можно получить используя как JavaScript API, так и вебсервис… В этой статье показывается как получить доступ к вебсервису geocoder, использую PHP.
Существует большое количество приложений для определения широты и долготы, многие из них бесплатны. Вот некоторые примеры как можно их использовать:
  • Отрисовка адреса, введенного пользователем на карте
  • Использование широты и долготы для вычислений (например нахождения расстояния)
  • Проверка адреса при заказе в интернет-магазине

Когда вы используете Google Maps для отображения карт на сайте, самый лёгкий путь использовать JavaScript API, однако это не всегда является оптимальным решением.
Например, если ваше приложение записывает координаты по адресу, который вводит пользователь, данное решение не будет работать для тех пользователей, у которых отключен JavaScript. Для предотвращения такой ситуации мы можем использовать веб-сервис для перенаправления всех запросов к geocoder на сервер.
В данной статье будут разработаны некоторые классы, которые помогут получить быстрый доступ к geocoder.

Начало работы с Google Maps

Вся документация по использованию Google Maps API находится по этому адресу
Первое что необходимо сделать для использования Google Maps API это создать API ключ для вашего сайта. По данному ключу будут идентифицированы все ваши запросы к этому API. Вы можете получить данный ключ, согласившись с правилами использования и введя адрес сайта на странице
В настоящее время максимальное количество запросов к geocoder ограничено и составляет 15000 в день. Но вы можете кэшировать ответы geocoder.
Получив ключ, вы можете обращаться к geocoder. Документацию по использованию geocoder можно найти по адресу

Осуществление запросов к Geocoder

Когда вы осуществляете запрос к geocoder, вы можете указать в каком формате вы желаете получить ответ. Возможные варианты форматов: JSON, XML, KML и CSV. В данной статье описаны манипуляции с ответами в XML.
KML (расшифровка Keyhole Markup Data) это XML формат, разработанный для использования программой Google Earth. Данные ответов в KML и XML от geocoder идентичны.
Несмотря на то, что JSON используется обычно с JavaScript, мы можем также использовать его в своем приложении. Для работы с JSOM, используя PHP, посмотрите следующие функции (начиная с PHP 5.2.0). В данной статье для обработки XML данных от geocoder используется расширение SimpleXML
Для осуществления запроса к geocoder должен быть послан HTTP-запрос по адресу maps.google.com/maps/geo. Ниже описаны параметры запроса:
  • q – адрес, координаты которого надо определить
  • key – ключ Google Maps API
  • output – формат результата (json, xml, kml или csv)

Например, для того чтобы получить координаты Белого дома (расположенного по адресу 1600 Pennsylvania Avenue, Washington, DC), необходимо составить следующий запрос: maps.google.com/maps/geo?q=1600+pennsylvania+ave+washington+dc&output=xml&key=123456
Замените 123456 на свой ключ
Строгость формата строки, которую вы посылаете в запросе, не критична, однако не должно быть двусмысленности

Разбор ответа Geocoder

После отправки запроса к geocoder, вы получаете ответ в том формате, который указали. Например, если указанный адрес это адрес Белового Дома, то мы получим в ответ следующий XML-документ:



В данном ответе выделим 3 важных элемента:
  • name: Строка адреса, которую мы передаем geocoder
  • Status: Статус ответа, по которому мы можем легко понять что произошло. Далее описание различных возможных состояний:
    • 200: Successful request (хотя бы одно место по введенной строке найдено)
    • 400: Bad request (сервер не понял запрос)
    • 500: Server error (Внутренняя ошибка)
    • 601: Missing query. Отсутствует параметр q или передана пустая строка
    • 602: Unknown address. Ничего не найдено по введенной строке
    • 603: Unavailable address. Переданный адрес отсутствует или не может быть показан из-за юридических или контрактных соображений
    • 610: Неверный ключ Google Maps API
    • 620: Сделано слишком много запросов, используя данный ключ
  • Placemark: Если поле Status равно 200, в возвращенном XML присутствует хотя бы один элемент Placemark, который содержит информацию о переданной строке.

Первый полезный элемент, который включается в каждый элемент placemark это отформатированный адрес address. Это очень важно, так как пользователи по разному могут вводить одинаковые адреса. Если посмотреть на пример выше, то можно увидеть, что была введена для поиска строка «1600 pennsylvania ave washington dc», а в качестве ответа получена «1600 Pennsylvania Ave NW, Washington, DC 20006, USA».
Следующий элемент это AddressDetails, который использует Extensible Address Language (xAL) для предоставления информации. У данного элемента есть атрибут Accuracy, который указывает степень точности определения местоположения. Возможные значения атрибута Accuracy:
  • 0: Unknown
  • 1: Country
  • 2: Region
  • 3: Sub-region
  • 4: Town
  • 5: Postcode
  • 6: Street
  • 7: Intersection
  • 8: Address

В примере выше значение атрибута равно 8, что указывает на самый высокий уровень точности.
Другие элементы внутри элемента AddressDetails описывают части найденного места в соответствии со спецификацией языка xAL.
Каждый элемент placemark содержит элемент Point. Этот элемент содержит данные разделенные запятой. Первый элемент — долгота; второй — широта; третий аргумент – высота над уровнем моря (не используется).

Доступ к данным Geocoder используя SimpleXML
Для разбора полученного XML мы будем использовать SimpleXML, расширение которое делает работу с XML в PHP достаточно легкой. SimpleXML встроен в PHP, начиная с PHP 5.
Для прочтения данных XML, необходимо инициализировать объект SimpleXMLElement, передав в его конструктор строку XML, которую мы хотим разобрать.
$str = «XML-строка»; // sample XML file
$xml = new SimpleXMLElement($str);

Создав объект класса SimpleXMLElement, мы можем получить доступ ко всем элементам XML-строки, как будто это обыкновенные свойства объекта. Например, можно получить досуп к статусу ответа, используя следующий код:
$xml = new SimpleXMLElement($geocoderResponse);
echo $xml->Response->Status->code;

Надо отметить, что тип $xml->Response->Status->code — SimpleXMLElement. Поэтому, необходимо для использования выполнить приведение типов к строке или числу.
$xml = new SimpleXMLElement($geocoderResponse);
$status = (int) $xml->Response->Status->code;
if ($status == 200) {...}

Сейчас мы знает как читать данные XML, но некоторые элементы содержат атрибуты. Следующий пример показывает как получить доступ к таким атрибутам.
$xml = new SimpleXMLElement($geocoderResponse);
foreach ($xml->Response->Placemark as $placemark) {
    $accuracy = (int) $placemark->AddressDetails['Accuracy'];
    ($accuracy == 8) {...}
}


Создание класса Geocoder

В данной статье будет рассмотрена реализация решения длоя использвоания информации от Google geocoder. В процессе будут рассмотрено написание трех классов:
  • Geocoder: Этот класс будет сипользован для работы с geocoder (направления запросов, получения и разбора ответов).
  • Placemark: Этот класс будет содержать места, возвращенные geocoder.
  • Point: В данном классе будут находится координаты найденных мест.

Приступим к реализации класса Geocoder. Этот класс будет содержать метод lookup(), входными данными которого будет являтся адрес для поиска, в качестве результат возвращаться будет массив из элементов класса Placecmark с найденными местами.
Код ниже показывает часть реализации класса. Сначала определяется адрес geocoder. Мы определяем необходимое количество констант, соответствующих статусу возврата. Использование констант делает код более читаемым и поддерживаемым. Для консистентности мы используем те же константы, что определены для JavaScript API
class Geocoder
{
    public static $url = 'http://maps.google.com/maps/geo';
    const G_GEO_SUCCESS = 200;
    const G_GEO_BAD_REQUEST = 400;
    const G_GEO_SERVER_ERROR = 500;
    const G_GEO_MISSING_QUERY = 601;
    const G_GEO_MISSING_ADDRESS = 601;
    const G_GEO_UNKNOWN_ADDRESS = 602;
    const G_GEO_UNAVAILABLE_ADDRESS = 603;
    const G_GEO_UNKNOWN_DIRECTIONS = 604;
    const G_GEO_BAD_KEY = 610;
    const G_GEO_TOO_MANY_QUERIES = 620;
    protected $_apiKey;
    public function __construct($key)
    {
        $this->_apiKey = $key;
    }
}

Как было описано выше, мы реализуем метод lookup(), возвращающий массив объектов типа Placemark. Для получения ответа от geocoder мы напишем метод под названием performRequest().
Метод performRequest() будет принимать строку адреса в качестве первого аргумента и тип возвращаемых данных в качестве второго аргумента.
Этот метод используем функции Curl для осуществления HTTP-запросов. Для получения ответов от сервера параметр CURLOPT_RETURNTRANSFER должен быть установлен в true.
class Geocoder
    {
    public function performRequest($search, $output = 'xml')
    {
        $url = sprintf("%s?q=%s&output=%s&key=%s&oe=utf-8", self::$url, urlencode($search), $output,$this->_apiKey);
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        curl_close($ch);
        return $response;
    }
}

Сейчас мы вплотную займемся реализацией метода lookup() В начале мы получим ответ от сервера используя метод performRequest(), создадим объект класса SimpleXMLELement. Далее мы прочитаем возвращенный сервером статус запроса. Сингналом о нормальном запросе будет являться статус G_GEO_SUCCESS, G_GEO_UNKNOWN_ADDRESS или G_GEO_UNAVAILABLE_ADDRESS. Последние два значения покажут что не найдено указанных мест, в данном случае будет возвращен пустой массив.
Если код статуса G_GEO_SUCCESS, мы в цикле будем обрабатывать найденные места и создадим для каждого объекты типа Placemark… Мы напишем метод FromSimpleXml() для создания объекта класса Placemark.
Для всех других кодов статуса мы будем создавать исключение.
class Geocoder
{
    public function lookup($search)
    {
        $response = $this->performRequest($search, «xml»);
        $xml=new SimpleXMLElement($response);
        $status = (int) $xml->Response->Status->code;

        switch ($status)
        {
            case self::G_GEO_SUCCESS:
                require_once(«Placemark.php»);
                $placemarks = array();
                foreach ($xml->Response->Placemark as $placemark)
                    $placemarks[] = Placemark::FromSimpleXml($placemark);
                return $placemarks;
            case self::G_GEO_UNKNOWN_ADDRESS:
            case self::G_GEO_UNAVAILABLE_ADDRESS:
                return array();
            default:
                 throw new Exception(sprintf(«Google Geo error %d occurred”, $status));
        }
    }
}

Это полный код класса Geocoder. Если вы хотите использовать функцию performRequest(), вы можете это сделать, однако сейчас необходимо реализовать класс Placemark до использования метода lookup().
require_once(«Geocoder.php»);
$address = «1600 Pennsylvania Ave Washington DC»;
$geocoder = new Geocoder('your key');
$xml = $geocoder->performRequest($address, «xml»);
$json = $geocoder->performRequest($address, «json»);
$csv = $geocoder->performRequest($address, «csv»);


Создание класса Placemark

Сейчас мы реализуем класс Placemark, который будет использован для хранения информации о местоположении. Предпочтительно конечно сохранять все части адреса возвращенные от geocoder, мы ограничимся сохранением трех элементов:
  • Широта и долгота места (используя класс Point)
  • Форматированный адрес, возвращенный от geocoder
  • Точность определения места

Для реализации этого класса мы напишем функции установки и получения значений (функции setPoint(), getPoint(), setAddress(), getAddress(), setAccuracy() и getAccuracy()). Мы также реализуем статический метод FromSimpleXml(), который используется в классе Geocoder.
В классе Placemark мы определим константы отвечающие за точность определения местоположения. Например, для определения определена ли только страна необходимо использовать следующее
if ($placemark->getAccuracy() == Placemark::ACCURACY_COUNTRY) {… }

Реализация.
class Placemark
{
    const ACCURACY_UNKNOWN = 0;
    const ACCURACY_COUNTRY = 1;
    const ACCURACY_REGION = 2;
    const ACCURACY_SUBREGION = 3;
    const ACCURACY_TOWN = 4;
    const ACCURACY_POSTCODE = 5;
    const ACCURACY_STREET = 6;
    const ACCURACY_INTERSECTION = 7;
    const ACCURACY_ADDRESS = 8;
    protected $_point;
    protected $_address;
    protected $_accuracy;
}

Google Maps API не определяет именованные константы для точности определения.
Далее идет определение так называемых сеттеров и геттеров, я пропускаю комментарии автора.
class Placemark
{
    // other code
    public function setAddress($address)
    {
        $this->_address = (string) $address;
    }
    public function getAddress()
    {
        return $this->_address;
    }
    public function __toString()
    {
        return $this->getAddress();
    }
    public function setPoint(Point $point)
    {
        $this->_point = $point;
    }
    public function getPoint()
    {
        return $this->_point;
    }
    public function setAccuracy($accuracy)
    {
        $this->_accuracy = (int) $accuracy;
    }
    public function getAccuracy()
    {
        return $this->_accuracy;
    }
    // other code
}

В конце мы реализуем метод FromSimpleXml(), который является методом-фабрикой для создания объектов типа Placemark на основе объекта SimpleXMLElement.
В примере, описанном ниже используется класс Point и его метод Create(), они будут описаны ниже.
class Placemark
{
    // other code
    public static function FromSimpleXml($xml)
    {
        require_once('Point.php');
        $point = Point::Create($xml->Point->coordinates);
        $placemark = new self;
        $placemark->setPoint($point);
        $placemark->setAddress($xml->address);
        $placemark->setAccuracy($xml->AddressDetails['Accuracy']);
        return $placemark;
    }
}


Создание класса Point

Последний класс, который должен быть реализован для полноценного использования, — это класс Point. Этот класс хранит широту и долготу. Эти данные передаются в конструкторе, также присутствуют методы получения значений широты и долготы.
class Point
{
    protected $_lat;
    protected $_lng;
    public function __construct($latitude, $longitude)
    {
        $this->_lat = $latitude;
        $this->_lng = $longitude;
    }
    public function getLatitude()
    {
        return $this->_lat;
    }
    public function getLongitude()
    {
        return $this->_lng;
    }
    public static function Create($str)
    {
        list($longitude, $latitude, $elevation) = explode(',', $str, 3);
        return new self($latitude, $longitude);
    }
}


Заключение

В данной статье рассмотрен простейший алгоритм работы с geocoder, используя Google Maps. Также вы можете использовать указанные класса вместе с JavaScript, но надо также не забывать, что не у всех может быть включен JavaScript.
Также не забывайте, что кэширование ответов geocoder позволит вам снизить нагрузку и увеличить производительность в будущем.

Ссылки
Tags:
Hubs:
+54
Comments 53
Comments Comments 53

Articles