Как наладить поиск адреса по координатам (и где взять нужный справочник)



    Весной мы добавили в API DaData.ru фичу «Обратное геокодирование», она же «Адрес по координатам». Название намекает: метод принимает геокоординаты и отдает данные об адресе.

    Солидный продукт с той же функциональностью предлагает «Яндекс» — он называется «Геокодер». Но сервис «Яндекса» бесплатен только для открытых некоммерческих проектов. Стандартный же тариф — от 120 000 ₽ в год — подходит не всем.

    Мы подумали — если сделать бесплатную или недорогую альтернативу «Геокодеру», разработчики наверняка скажут спасибо. И сделали. В статье расскажу, как устроен «Адрес по координатам»: как мы наладили поиск, собрали справочник и упаковали в готовый метод.

    Где берем данные и чем ищем адрес


    Подступаясь к задаче, мы изучили готовые решения: где взять справочник координат с адресами и как потом искать по этому справочнику географические объекты. Оказалось, за нужными инструментами даже не придется далеко ходить.

    Адресные объекты берем в ФИАС — Федеральной информационной адресной системе. Это самый полный из открытых и официальных адресных справочников. Подробно о нем мы уже писали на «Хабре», а сейчас важны четыре факта:

    • в ФИАС хранятся все адресные объекты страны, от регионов и ниже — до зданий и дополнительных территорий;
    • справочник свободно доступен в форматах DBF и XML;
    • ФИАС не идеален — в нем отсутствуют десятки тысяч домов и множество улиц, особенно новых;
    • каждому адресному объекту в справочнике соответствует уникальный ID — ФИАС-код. Код объекта иногда меняют, но для нашей статьи это не так важно.

    Адресные объекты, загруженные из ФИАС вместе с ID, — основа нашего справочника для обратного геокодирования.

    Координаты загружаем из OpenStreetMap (OSM). OSM — проект со свободной лицензией: энтузиасты собирают координаты всевозможных объектов и выкладывают для всех желающих.

    Если по-простому, OSM — это набор точек, линий и полигонов на карте. У каждого объекта свое описание, тип и набор координат. Данные OSM для России лежат по адресам needgeo.com, osm.sbin.ru/osm_dump/ и osmosis.svimik.com/latest/.


    Список источников публикуют на специальной странице в «Вики» проекта

    Выгрузки состоят из PBF-файлов — этот формат используют вместо XML как более компактный. Превратить PBF в OSM XML ничего не стоит, с этим справится куча одобренных сообществом утилит.

    Для собственного справочника мы берем адресные объекты из ФИАС, а затем ищем их координаты в OSM. Если нашли, сохраняем объединенные данные. Получается такое пересечение ФИАС и OSM.

    И все это замечательно, но есть одна проблема: с качеством данных в OSM дела обстоят непросто. Координаты объектов часто не соответствуют реальности. Например, полигоны для регионов и районов адекватны. А для городов и ниже — уже не очень.


    Полигоны — это многоугольники, ограничивающие площади на карте. Они состоят из связанного набора точек с координатами. Полигонами обозначают границы регионов, районов, городов и даже зданий

    Основная работа, и с большим отрывом — собрать из OSM адекватные данные и отсеять брак. Задача настолько объемная, что я отвел под нее в статье отдельный раздел.

    Дома, которых нет в ФИАС, загружаем тоже из OSM. Выше я уже говорил, что в ФИАС отсутствуют десятки тысяч домов. Это даже не проблема, а просто реальность, фон. Поэтому мы пополняем свой справочник домами из OSM. Но только теми, для которых в ФИАС существует улица. У пришедших из OSM зданий нет ФИАС ID, поэтому мы идентифицируем их как ФИАС-код родителя + номер дома.

    По справочнику ищем с помощью прекрасного Lucene — нашего многолетнего помощника. За наводку спасибо сведущему индийцу, написавшему пост Indexing Geographical Data With Lucene (хорошее дополнение — материал A dive into spatial search algorithms — о k-d-деревьях, на которых построен алгоритм поиска).

    Как только мы узнали об у́дали Lucene, проблема с поиском решилась почти сама. Делов осталось — пройтись наждачкой.

    1. Загрузили в Lucene свой справочник координат и адресов, получили поисковый индекс. Для легкости убрали из него почти все, оставив лишь ID адресов и координаты.
    2. Наладили поиск по индексу: на вход — координаты, на выход — ID найденных адресных объектов. Другой информации поиск не возвращает, поскольку индекс мы донельзя скукожили.
    3. Насытили выдачу, загружая из «большого» ФИАС данные по найденным ID. Добавляем много всего, от нужного всем адреса одной строкой до признака столицы региона у городов.
    4. Придумали, как сортировать и отдавать полученные объекты.

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

    Как собрали базу координат и адресов


    Для начала выложу багаж: прочитав статью, быстро сделать подобный справочник не получится. Мы собираем его с 2014 года, постоянно дополняя. Об этом чертовски длинном пути я и расскажу.

    Самое сложное при составлении справочника — перебрать кординаты, которые пришли из OSM. На старте мы выверяли их как могли, в том числе руками. Главная цель тогда — получить опорные точки в крупных городах и сделать из них эталонный справочник. Теперь, когда таких точек много, проверять новые данные вручную почти не приходится. За раз мы добавляем в эталонный справочник 200 000–300 000 адресов с координатами, и вот как это делаем.

    Формируем из OSM-тегов полные адреса́. В OSM-выгрузках составные части адресов разбросаны по разным тегам:

    • addr:city — д. Булатниково;
    • addr:street — Центральная улица;
    • addr:housenumber — 103.

    Пробегаем по тегам и собираем из них полный адрес: д. Булатниково, Центральная улица, 103.

    Прогоняем каждый новый адрес через API стандартизации «Дадаты». Сервис приводит адреса к единому формату «Как в ФИАС»:

    • исправляет опечатки;
    • расшифровывает сокращения вроде «НиНо» и «Мск»;
    • меняет старые названия на новые;
    • находит по индексу пропущенный в адресе город;
    • определяет ФИАС-код.

    Адреса́ приходят от API чистыми, хоть сейчас шли письмо или бандероль.
    До стандартизации После
    д. Булатниково, Центральная улица, 103

    142718, Московская обл, Ленинский р-н, с Булатниково, ул Центральная, д 103.


    ФИАС-код — a8b6a52f-e96d-4ec3-a0ff-641013ab0445

    Стандартизованные дома, улицы и населенные пункты мы храним как одну точку. Для улицы и населенного пункта эта точка — центр. В итоге все адресные объекты лежат в одной таблице, внутри — адрес, ФИАС ID, широта и долгота.
    Адрес ФИАС ID Широта Долгота
    142718, Московская обл, Ленинский р-н, с Булатниково, ул Центральная, д 103 a8b6a52f-e96d-4ec3-a0ff-641013ab0445 55.558773 37.667103
    119034, г Москва, пер Турчанинов, д 6 стр 2 8c925e61-9173-48b3-999e-dc85c86d89e7 55.737096 37.597190
    Разбираем адреса, которые «Дадата» не стандартизовала. Адреса́, которые не получилось сопоставить с ФИАС, сервис помечает флажком. Их проверяем вручную, вариантов здесь несколько.

    1. Адрес пришел не в положенных тегах выгрузки OSM, а черт знает где. Встречали и вовсе не заполненные адресные теги, и город в теге улицы, и еще много всего.
    2. В OSM лежит экзотический объект вроде детской площадки, вузовского футбольного поля или вовсе кладбища. В ФИАС ничего такого нет, да и для наших целей эти результаты не годятся. Такие объекты просто отсеиваем.
    3. Ошибка — и не ошибка вовсе. Например, из OSM пришел район города, которого в ФИАС нет. Или в ОSМ объект находится в населенном пункте, а в ФИАС этот населенный пункт присоединили к городу и удалили. Тогда мы допиливаем алгоритм под загруженные данные и запускаем снова.


    Распарсили выгрузку, а там — путаница в тегах

    Проверяем, насколько адекватны загруженные координаты. Для этого специальной утилитой смотрим, попадают ли координаты нового объекта в полигон родительского региона или района. Если адрес сообщает, что объект находится в Омской области, будь добр по координатам попадать в ее полигон. Вхождения в город не требуем — не все города точно освещены в OSM, для многих данные не обновляют.

    Эталонные полигоны мы загружаем из OSM и храним как есть — в формате GeoJSON. Чтобы выбрать, к какому полигону примерить точку, смотрим в отдельную таблицу. В ней мы сопоставили префиксы КЛАДР-кодов и ID полигонов: находишь для адреса КЛАДР-код и видишь, какой полигон выбрать.


    КЛАДР-код — это уникальный идентификатор, который использовали еще до появления ФИАС. Находить этот код для адреса умеет миллион сервисов

    Утилита разрешает объекту отстоять от положенного полигона на 1 700 метров. Это правило добавили из-за шоссе, которые часто выходят за границы региона. Но расстояние больше 1 700 метров — признак ошибки, так говорит статистика.

    На этом для городов и улиц проверка заканчивается.

    Еще раз, построже, проверяем загруженные координаты домов. В дело снова вступает упомянутая утилита, и вот что она делает.

    1. Берет адрес нового дома и находит для него соседей в эталонном справочнике.
    2. По координатам считает расстояние между непроверенным новым домом и надежными соседними.


    Соседей найти несложно: 1. Берем новый дом и находим ФИАС ID родителя. 2. Выбираем из эталонного справочника дома́, у родителей которых тот же ФИАС ID

    Проверку проходят только дома, которые удалены от надежных коллег не более чем на 150 метров. Причем каждый новый одобренный дом мы учитываем при разборе следующих. Вот как это работает.

    Допустим, в эталонном справочнике хранятся дома № 1, 2 и 3 по улице Коммунаров. В новых данных пришли дома № 5, 6 и 7 по той же улице. Судя по координатам, новые дома стоят рядом. Утилита видит, что дом № 5 находится рядом с домами № 1, 2 и 3 и добавляет его в эталонный справочник. Значит, дома № 6 и 7 тоже проходят проверку.

    А дальше решается судьба пришедших из OSM данных:

    • дома, прошедшие обе проверки: на полигоны и на соседей, добавляем в эталонный справочник;
    • если объект не попадает в полигон, новые данные не подходят. То же самое, если расстояние между домом и соседями слишком велико;
    • дома, у которых нет соседей, мы пока откладываем. Они лежат в отдельной базе, когда-нибудь разберем.

    Делим прошедшие проверку объекты на две части. Они пойдут в разные таблички нашего эталонного справочника.


    В первой таблице — все объекты c ФИАС ID до домов: регионы, населенные пункты, улицы. Во второй — дома́ и ссылка на родителя из первой таблицы

    Две таблицы нужны, чтобы назначить ключи отсутствующим в ФИАС домам. У них нет собственного ФИАС-кода, поэтому делаем вот как:

    • в одну таблицу собираем адресные объекты до дома, у каждого из них свой ФИАС-код;
    • во вторую — только дома́, при этом ссылаясь на родителя в первой таблице.

    В итоге здания без ФИАС-кода мы идентифицируем по ключу ФИАС ID родителя + номер дома.

    Справочник готов, осталось протестировать. За ночь прогоняем сервис по функциональным тестам и тестируем производительность. Скорость проверяем на Москве, запрашивая все дома в радиусе трех километров. Чтобы уж наверняка. Конечно, обложили все автотестами.

    Главное после обновления — чтобы не стало хуже.

    Обратное геокодирование глазами пользователя


    На вход метод принимает три параметра: координаты, количество результатов и радиус поиска. Радиус по умолчанию — 100 метров, максимальный — километр. Точное значение задают в настройках.

    curl -X POST \
      -H "Content-Type: application/json" \
      -H "Accept: application/json" \
      -H "Authorization: Token ${API_KEY}" \
      -d '{ "lat": 55.878, "lon": 37.653, "radius_meters": 50 }' \
      https://suggestions.dadata.ru/suggestions/api/4_1/rs/geolocate/address

    Обратно метод возвращает найденные объекты: дома, улицы и населенные пункты. При этом сортирует их по убыванию точности.

    1. Дома́.
    2. Улицы.
    3. Населенные пункты.
    4. Города́.

    Затем сортирует еще раз — по расстоянию от заданных координат. Если метод нашел четыре дома и улицу, сначала встанут дома́ в порядке удаленности от заданной точки. За ними — улица.

    После всех этих рокировок метод наконец-то возвращает объекты, которые нашел.

    {
        "suggestions": [
            {
                "value": "г Москва, ул Сухонская, д 11",
                "unrestricted_value": "г Москва, ул Сухонская, д 11",
                "data": {...}
            },
            {
                "value": "г Москва, ул Сухонская, д 11А",
                "unrestricted_value": "г Москва, ул Сухонская, д 11А",
                "data": {...}
            }
        ]
    } 

    Внутри — много разного о найденных объектах: строки с полным и сокращенным адресом, актуальное и устаревшие названия, почтовый индекс, ФИАС-код родительского объекта и так далее.


    Все данные, которые отдает метод — в документации

    Покрытие по координатам для разных регионов разное, вот так с домами:

    • Москва — 96%,
    • Санкт-Петербург — 88%,
    • другие города-миллионники — 74%,
    • остальная Россия — 47%.

    А вот это — покрытие по улицам:

    • Москва — 92%,
    • Санкт-Петербург — 79%,
    • другие города-миллионники — 75%,
    • остальная Россия — 67%.

    По городам не считали — в масштабах России зыбок даже сам факт принадлежности к гордому званию города. Например, Ярославская обл, Пошехонский р-н, с/о Федорковский — это город, согласно официальному справочнику ФИАС. А по факту, да и по адресу — сельский округ. Физически сельский округ напоминает объединение нескольких деревень в большую кляксу. Сложно не только определить центр, но даже найти населенный пункт на карте.

    Уже думаем, что добавить к методу: разрешить фильтрацию по типам объектов, возвращать расстояния до заданной точки, еще что-нибудь. Следим за спросом и решаем, вкладывать ли силы.

    А в остальном все уже на проде. До 10 000 запросов в сутки — бесплатно, больше — по подписке от 5 000 ₽ в год. Если нужны адреса по координатам для коммерческого проекта, а «Геокодер» слишком дорог — попробуйте API «Дадаты».

    Оригинал статьи опубликован в блоге HFLabs.
    HFLabs
    91,79
    Качество и интеграция клиентских данных
    Поделиться публикацией

    Комментарии 32

      0

      А есть возможность по ФИАС-коду запросить координаты?

      +2

      Спасибо! Как раз искал решение по обратному геокодингу, чтоб можно было радиус задавать — нужно находить ближайший к точке адрес.

        0
        Все верно, это к нам ;)

        А не поделитесь, как будете использовать?
          +1

          У нас первый в РФ сервис по упрощённой регистрации разрешений на полеты беспилотников. Все автоматизировано, нужно лишь за день до полеты тыкнуть на карту где будешь летать.
          Координаты нужны для органов, которые отвечают за организацию воздушного движения, а вот для силовиков нужен уже примерный адрес взлета, чтоб любой ппсник мог проверить )

            0
            Ого! Честно говоря, думал, будет что-то более приземленное :) Было бы очень интересно про это почитать, конечно.
        +1
        Спасибо за интересные мысли. Только я не очень понял, как в искомый радиус попадают не точечные, а протяженные объекты, например длинные улицы, если в базу (и в индекс) попадает только центральная точка? Например, улица длиной в 1000 метров, в базу попадет ее центральная точка. И если указать искомые координаты края улицы с радиусом 100 метров, то как люцент найдет эту улицу, если в индексе хранится только ее центр? Ведь радиус поиска центр улицы не захватит.
          0
          Спасибо, что подняли интересную тему! :)

          Вы все правильно описали с протяженной улицей и точкой на краю — если радиус невелик, поиск эту улицу не найдет. Зато найдет дома на улице — они почти всегда есть, потому что мы отдаем приоритет детальным объектам.

          Но у разработчиков уже пошла волна от комментария — задумались, может, нужно что-то допилить в поиске. Так что спасибо за хороший вопрос — заставил подумать.
            +1
            Мне в голову пока приходит тупое лобовое решение (в рамках вашей модели данных) — с неким шагом (например 100 метров) расставить фейковые объекты-пустышки, которые по сути будут ссылками на улицу. Правда, может достаточно много таких объектов получиться, но всё зависит от шага. Зато по сути в программе почти ничего менять не надо.
              0
              Передал ребятам, спасибо!
                +1
                А можно для протяженных объектов строить упрощенную геометрию и хранить её для детальной фильтрации.
                Ещё можно строить индекс на основе дерева квадрантов, при этом протяженные объекты закреплять за теми квадрантами, в которые объект помещается полностью.
                Можно индекс на основе того же дерева квадрантов строить для первичного грубого поиска, тогда все протяженные объекты можно «рендерить» на грубую растровую карту в виде битовых «примесей» к пикселям, на которые объект грубо накладывается.
                Огромное количество разных способов оптимизации этой задачи.
            +1
            А публичную кадастровую карту или другие данные Росреестра не пробовали для этой задачи дополнительно использовать? Можно брать адреса и объектов капитального строительства, и земельных участков. Данные есть сильно не всегда (зависит от региона и от того, насколько свежая застройка), но когда они есть — их можно почти всегда считать достоверными.
              0
              Нет, даже в голову не приходило! Смотрели прежде всего на ресурсы, которые рождены для адресов и координат :)

              Спасибо за наводку, передал ребятам. Посмотрим, что есть у Росреестра, это интересно.
                +1
                Там, насколько я понимаю (нюансов не расскажу, т.к. сам пока с этой темой сталкивался только по касательной) есть свои фокусы с API. Как минимум показателен тот факт, что размещённая на самой публичной кадастровой карте ссылка на описание их веб-сервисов (https://pkk5.rosreestr.ru/help/pkk5_services.docx) выдаёт ошибку 404.
                Но если получится с этим разобраться разумными силами — то как раз по всякой пригородной сельской местности, где участки и дома активно покупаются/продаются, застраиваются и пр., т.е. в ЕГРН новые данные попадают, можно сильно улучшить процент покрытия.
                  0
                  Ничего страшного, разберемся! Вот с чем у нас есть опыт, так это с изучением всевозможных открытых и приоткрытых источников :) Главное, что наводку дали — спасибо!
              0
              Сейчас у вашего обратного геокодера проблема с точками в местах, где нет никаких населенных пунктов. Например:
              {
              "lat": 59.276284,
              "lon": 29.936941
              }

              При этом OSM nominatim неплохо с такими координатами справляется. Не собираетесь расширять базу?
                0
                Базу расширяем постоянно, но в основном внутри населенных пунктов. Мы мало смотрим за пределы обжитых территорий, потому что делаем сервис прежде всего для транспорта и логистики внутри городов, поселков, деревень — для курьеров, такси и так далее.

                Ваша же точка — где-то на шоссе. 83-й километр, домов поблизости нет. Письмо не пошлешь, курьера не отправишь :)
                +1
                Отличная работа!
                И спасибо, что так подробно рассказываете технические подробности.
                  0
                  Спасибо! Делимся по возможности.
                  0
                  Основная работа, и с большим отрывом — собрать из OSM адекватные данные и отсеять брак. Задача настолько объемная, что я отвел под нее в статье отдельный раздел.
                  Не подскажите — а Вы каким-то образом делитесь найденными ошибками в OSM с самим сообществом OSM? Было бы неплохо. Я понимаю что коммерческая тайна и всё такое — но участники OSM стараются в том числе и для Вас, неплохо что-то возвращать.
                    0
                    Честно говоря, нет, не делимся. Дело не в коммерческой тайне, этого как раз совершенно не жалко. Непонятно, как технически делиться, в каком качестве мы будем выступать и так далее
                      +2
                      Можно спросить тут у обитателей по порядку популярности и актуальности статей с OSM: Zverik, vics001, JediPhilosopher, Sildream. Если никто не ответит(а ребята занятые) — можно скинуть демо информацию в чат t.me/ruosm с найдеными ошибками и спросить готов ли кто-то в таком формате(который у вас есть) собирать данные и вносить исправления в OSM. Не забыть сказать что Вы уже данные для себя почистили и отдаёте их из благодарности, потому что некоторые мапперы иногда немного ревностно относятся к тому, что кто-то даёт «какие-то» данные для внесения и могут «настойчиво» попросить самим всё вносить). Но обижаться на них не стоит, они делают хорошее дело.
                        0
                        Ого, спасибо большое! Отнесу это ребятам, которые работают на земле.
                          +1
                          Да проще зайти в чат, хотя сразу скажу, что ruosm крайне перегружен всякой информацией и иногда и там информация не доходит до нужных людей. Лучше всего, конечно, сходить на какой-нибудь локальный митап OSM, сделать презентацию, а заодно познакомится с людьми.

                          Насколько, я понимаю, вы валидируете данные и исправляете их у себя, вы вполне можете открыть валидатор OSM и рассказать другим осмерам и возможно люди будут поглядывать и исправлять ошибки. Дела в OSM не делаются быстро, но если начать сегодня через год вполне можно найти своих пользователей. Не забывайте, что в OSM есть свои тулы для исправления ошибок OSM Notes, OSMCha, MapRoulette, StreetComplete…
                          +1
                          Я сам в случае обнаружения каких-то ошибок просто иду и в OSM это правлю. Поскольку я работаю в основном с пешеходной инфраструктурой, то обычно я добавляю какие-то недостающие тротуары и пешеходные переходы. Или вон в Китай когда ездил — отрисовал в OSM квартал и парк вокруг отеля, не очень подробно, но лучше чем ничего.
                      +1
                      Я сам в случае обнаружения каких-то ошибок просто иду и в OSM это правлю. Поскольку я работаю в основном с пешеходной инфраструктурой, то обычно я добавляю какие-то недостающие тротуары и пешеходные переходы. Или вон в Китай когда ездил — отрисовал в OSM квартал и парк вокруг отеля, не очень подробно, но лучше чем ничего.

                      Насчет правок адресов — это наверное адски много ручной работы. При этом OSM везде большими буквами пишет что автоматическое пакетное редактирование карты — плохая идея и приносит больше проблем чем пользы, а вручную такое исправлять слишком долго.
                        0
                        Вы совершенно правы насчет ручных правок — мы такое просто не осилим при своих объемах.
                        +1
                        А ваш этот сервис локально в закрытом контуре можно запустить? Дорого получится?
                        Не думали продавать аппаратные решения в виде сертифицированных по чем только можно маленьких железных серверов с вашей дадатой внутри? Купил поставил и знай себе только заливай обновления время от времени.
                          0
                          Да, кроме обычной версии предлагаем и «коробочную» — обычно покупают банки и прочие ребята с суровыми безопасниками. Для адресов стоит 450 000 ₽ за один промышленный сервер и один тестовый. Если интересно подробнее, вот описание в нашей базе знаний.

                          Продаем только софтовую часть. О том, чтобы пойти дальше и упаковывать в «коробку» сразу железо, — не думали. Насколько понимаю, у крупных компаний свои поставщики, у нас никто серверы не просит. По конфигурации, конечно, спрашивают — без проблем консультируем.
                          0
                          А вы наоборот, геокодирование не делаете? По адресу возвращать геокоординаты, было бы полезно.
                            0
                            Делаем, причем научились гораздо раньше. Вот описание метода, стоит от 11 500 ₽ в год dadata.ru/api/suggest/address.
                            0
                            Для собственного справочника мы берем адресные объекты из ФИАС, а затем ищем их координаты в OSM. Если нашли, сохраняем объединенные данные. Получается такое пересечение ФИАС и OSM.

                            Вы конечно же выложили объединённую базу как того требует лицензия ODbL, по которой распространяется OSM, вот только я её что-то не нашёл.

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое