четверг, 1 ноября 2012 г.

Сравнение работы геосервисов Google и Yandex

    Не так давно мне пришлось заниматься проблемой определения географических координат (широта, долгота) множества точек, зная только их адрес, что предоставило хорошую возможность для изучения возможностей сервисов геолокации, предоставляемые Google и Yandex.
    Использовать сервисы геолокации оказалось не просто, а очень просто. Для доступа к функциям не нужно генерировать никаких ключей, просто формируем запрос, дергаем URL сервиса по GET, конвертируем вернувшийся JSON в java объекты и сохраняем результат в базе.
    Исходные данные примерно таковы: несколько тысяч адресов примерно следующего формата: Область, Улица, Строение или Улица, Строение и  отдельно от них Город. Все адреса находятся в России, что несколько сужает область поиска. Адреса набирались вручную и давно, и могут содержать опечатки, описки и устаревшие сведения.
    Итак, первым пробуем Google geocoding API.
Плюсы, которые я вначале не посчитал за плюсы, а понял, что это плюсы, только после того, как воспользовался аналогичным сервисом от Яндекса:
  • Все просто и удобно. Можно ограничить область поиска, можно выбрать язык результатов поиска. Не избыточный формат возвращаемых данных.
  • Правильный поиск с приемлемой точностью. Можно рассчитывать на то, что если нашелся только один адрес, это будет корректный адрес. Поиск происходит последовательно, т.е вначале ищется населенный пункт, потом улица, потом дом. Следовательно, если населенный пункт не найден, то дальше поиск не осуществляется. Если не найдена улица или дом, поиск вернет координаты населенного пункта или центра улицы соответственно, указав найденную точность в результатах.
  • Не пытается исправить ошибки в адресе, если они есть. Другими словами, не пытается подсунуть "вероятно правильный" результат.
  • Возвращает почтовый индекс, если адрес найден с точностью до дома.
Минусы:
  • Не всегда достаточно полная база, особенно по маленьким городам, что приводит к низкой точности координат (часто до города).
  •  Формат возвращаемого результата не соответствует общепринятому в России, т.е. результат возвращается в формате "Советская ул., 10, Тюмень, Тюменская область, Россия, 625003". Кончено результат также возвращается разбитый на части по административным единицам, да и этот в принципе не сложно развернуть, но все же неудобно.
  • Иногда возвращается адрес конкретного объекта, например: "Сальское Медицинское Училище, ГОУ, Кирова ул., 17, Сальск, Ростовская область, Россия, 347630" Мне в принципе не нужно знать адрес учреждения, которое находится по данному адресу.
  • Ограничение на частоту выполнения запросов. Есть ограничение на количество запросов за день (2,5 тыс) и некое ограничение на частоту запросов, из-за чего пришлось получать адреса очень медленно и с задержками.
     В результате порядка 10% адресов оказалось невозможно  распознать, в основном из-за того, что нашлось более одного адреса. Некоторые адреса были неправильно написаны, например Кольчугин вместо Кольчугино. Некоторые адреса реально не существовали, например "Улица Тестовая д 123" в городе Бобруйск. Но больше всего проблем возникало при написании адреса в виде "Большая Сухаревская пл., 16/18 стр.2". В этом случае Гугл часто возвращал более одного результата, находя к примеру в данном случае дом 16 и дом 2.
    Подумав немного, я решил уточнить результаты с помощью Яндекса. Ведь Яндекс находится в России, значит, теоретически, результаты должны быть лучше.
   Итак, Yandex geocode API.
Плюсы:
  • Находит адрес с точностью до дома даже в маленьких городах.
  • Иногда правильно исправляет опечатки.
  • Общепринятый формат вывода результата.
Минусы:
  • Неправильный, избыточный поиск. Назовем его "боязнь ничего не найти". Вернуть любой более-менее похожий результат. Иногда это срабатывает, например, уже упоминавшийся населенный пункт Кольчугин во Владимирской области был правильно исправлен на Кольчугино, но иногда приводит к тому, что возвращается просто неверный результат, совершенно в другом городе. Например, "Смоленская обл., Рудня, ул. Киреева, 66" превращается в "Россия, Смоленская область, Смоленск, улица Кирова".
  • При указании точного адреса может вернуться более одного результата. Чтобы не ходить далеко, возьмем пример, приведенный на сайте самого Яндекса. "ул. Тверская, дом 7". Данный запрос вернет 5 результатов. Помимо ожидаемой Тверской улицы, сервис вернет также 4 Тверские-Ямские улицы. При этом у всех (кроме одной) будет точность до дома. Какой логикой руководствовались создатели сервиса, мне непонятно. Убедиться своими глазами.
  • Избыточность в описании результатов. Может быть создатели сервиса от Яндекса считают, что это придает солидности, мне же кажется, что это просто добавляет ненужной работы и программисту, и сетевому оборудованию. Количество различных java объектов, необходимых для разбора ответа от Яндекса в 2 раза больше, чем для Гугла.
Как результат: сервис от Гугла предоставляет недостаточную точность результатов в небольших городах, а сервисом от Яндекса пользоваться можно только в том случае, если проводится ручная проверка результатов.

Код для работы с сервисом Яндекса:
HttpClient client = prepareHttpClient();

        String street = "ул Тверская, д 7. ";
        String region = "МОСКВА";

        String address = String.format("%s, %s", region, street);


        URI uri = new URI(
                "http",
                "geocode-maps.yandex.ru",
                "/1.x/",
                "format=json&lang=ru-RU&geocode=" + address,
                null);


        HttpGet request = new HttpGet(uri);
        HttpResponse resp = client.execute(request);
        Assert.assertEquals(200, resp.getStatusLine().getStatusCode());

        String encoding = "UTF-8";
        String str = new String(IOUtils.toByteArray(resp.getEntity().getContent()), encoding);
        System.out.println(str);

        Gson gson = new Gson();

        YandexApiResponse res = gson.fromJson(str, YandexApiResponse.class);



и для Гугла:

       HttpClient client = prepareHttpClient();

        String street = "ул Тверская, д. 7";
        String region = "МОСКВА";

        String address = String.format("%s ,%s", region, street);

        URI uri = new URI(
                "http",
                "maps.googleapis.com",
                "/maps/api/geocode/json",
                "sensor=false&language=ru&region=ru&address=" + address,
                null);

        HttpGet request = new HttpGet(uri);
        HttpResponse resp = client.execute(request);
        Assert.assertEquals(200, resp.getStatusLine().getStatusCode());

        String encoding = "UTF-8";
        String str = new String(IOUtils.toByteArray(resp.getEntity().getContent()), encoding);
        System.out.println(str);

        Gson gson = new Gson();

        GoogleApiResponse res = gson.fromJson(str, GoogleApiResponse.class);


Функция для подготовки HttpClient с поддержкой gzip

private DefaultHttpClient prepareHttpClient() {
        DefaultHttpClient client = new DefaultHttpClient();
        client.addRequestInterceptor(new HttpRequestInterceptor() {

            public void process(
                    final HttpRequest request,
                    final HttpContext context) throws HttpException, IOException {
                if (!request.containsHeader("Accept-Encoding")) {
                    request.addHeader("Accept-Encoding", "gzip");
                }
            }

        });

        client.addResponseInterceptor(new HttpResponseInterceptor() {

            public void process(
                    final HttpResponse response,
                    final HttpContext context) throws HttpException, IOException {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    Header ceheader = entity.getContentEncoding();
                    if (ceheader != null) {
                        HeaderElement[] codecs = ceheader.getElements();
                        for (int i = 0; i < codecs.length; i++) {
                            if (codecs[i].getName().equalsIgnoreCase("gzip")) {
                                response.setEntity(
                                        new GzipDecompressingEntity(response.getEntity()));
                                return;
                            }
                        }
                    }
                }
            }

        });
        return client;
    } 

    Реализация GoogleApiResponse и YandexApiResponse тривиальна - это обычные бины, с именами свойств, совпадающими с таковыми в JSON ответе сервиса.      

Комментариев нет:

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