В своих различных геосервисах мы используем данные OpenStreetMap (OSM). Данные в OSM заносятся, в основном, через специальные программы-редакторы, про самые популярные из которых мы писали тут и тут. На программном уровне редакторы общаются с главным сервером через OSM API. Этот API работает как на чтение, так и на запись и предоставляет лишь минимально необходимые возможности. Через него выборку можно ограничить только прямоугольником, но не произвольной полигональной областью, также нет ни фильтрации данных, ни способов их преобразования перед отправкой клиенту. Но что делать, если тебе нужно выбрать данные из OSM, отсеив много лишнего, желательно сразу показать результаты на карте, и при этом ты ни разу не программист? Тут-то на помощь и приходит Overpass API — мощный инструмент запросов к данным OSM, и Overpass Turbo — веб-приложение, предоставляющее графический интерфейс к Overpass API и дополнительные удобства при работе с запросами.

Overpass API и Overpass Turbo

Overpass API — это серверное ПО с открытым исходным кодом, созданное для обработки пространственных запросов к данным OpenStreetMap. Многие пользователи по всему миру обращаются к основному экземпляру Overpass API, работающему в домене overpass-api.de, поэтому там выставлены довольно строгие ограничения на количество запросов, время их выполнения и требуемую память. Есть и другие экземпляры Overpass API, которые либо предоставляют неполную функциональность, либо имеют заметный временной лаг в обновлении данных.  Чтобы не зависеть от чужих решений и внешних обстоятельств, наша команда Карты VK развернула собственный экземпляр Overpass API, открыла его во внешнюю сеть и пока не выставляла дополнительных ограничений. Пользуйтесь, пожалуйста:

Мы заметили, что в Safari не работает выгрузка статической картинки с картой, так что если будут проблемы, попробуйте другой браузер.

Работа с Overpass API

Сделаем простой запрос, который выбирает из базы OSM точку с идентификатором 1. Для этого введём в адресную строку браузера: https://maps.mail.ru/osm/tools/overpass/api/interpreter?data=node(1);out;

По умолчанию ответ возвращается в формате XML. На текущий момент он имеет такой вид:

<?xml version="1.0" encoding="UTF-8"?>
<osm version="0.6" generator="Overpass API 0.7.56.9 76e5016d">
<note>The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.</note>
<meta osm_base="2021-06-24T08:37:52Z"/>
  <node id="1" lat="42.7957187" lon="13.5690032">
    <tag k="man_made" v="communications_tower"/>
    <tag k="name" v="Monte Piselli - Radio Subasio 105.5 MHz"/>
    <tag k="tower:construction" v="guyed_lattice"/>
    <tag k="tower:type" v="communication"/>
  </node>
</osm>

Помимо информации о самом сервере мы видим координаты запрошенной точки и её свойства. В данном случае точкой под номером 1 в OSM обозначена башня связи где-то в Италии.

Такой способ общения с Overpass API через GET- или POST-запросы хорош для автоматизированных процессов, но не очень подходит для разовых задач и совсем не подходит для пользователей, далёких от программирования, для которых в первую очередь и разработан Overpass Turbo.

Overpass Turbo

Это веб-приложение, которое предоставляет графический интерфейс для написания Overpass-запросов и визуализации результатов, а также набор дополнительных инструментов. 

Напишем наш первый запрос в редакторе кода и нажмём кнопку «Старт».

На интерактивной карте отобразится результат запроса. Чтобы его увидеть, возможно, придётся либо подвигать карту мышкой, либо с помощью поисковой строки перейти на карту Италии, либо воспользоваться кнопкой «Перейти к данным» в виде лупы на панели инструментов. При клике по найденным объектам всплывает окно с атрибутами объекта и ссылкой на его карточку в OSM. В правом верхнем углу можно переключить вид панели результатов с «Карта» на «Данные» и посмотреть ответ в текстовом виде:

Модель данных OpenStreetMap

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

  • точка (node);

  • линия (way);

  • отношение (relation).

Точка — базовый элемент в структуре данных OSM, обладающий параметрами «широта» и «долгота». Линия — это последовательность точек. Если первая точка линии совпадает с последней, получается замкнутая линия. Замкнутая линия, в зависимости от интерпретации, может обозначать полигон как объект с конкретной площадью. Отношение группирует более простые сущности в более сложные. Как частный случай, несколько линий могут быть объединены в отношение типа «мультиполигон» — объект с площадью, который может содержать несколько контуров, в том числе «дырки».

Объекту любого типа могут быть присвоены теги. В них хранится описание атрибутов объекта в виде пар «ключ-значение». Примером тега может служить пара man_made=communications_tower, как в нашем запросе выше.

Теперь рассмотрим, как запрашивать данные.

Overpass QL

Overpass API понимает два языка запросов — Overpass XML и Overpass QL (Overpass Query Language). Overpass QL является императивным процедурным языком программирования с Си-подобным синтаксисом. Он более краткий и выразительный, чем его XML-аналог, поэтому будем использовать его.

Операторы

Запрос на Overpass QL состоит из операторов. Каждый оператор заканчивается точкой с запятой. Операторы выполняются друг за другом, формируя новые наборы данных, при этом каждый последующий оператор может использовать результаты предыдущих. Посмотрим ещё раз на наш первый запрос:

node(1);
out;

В первой строке написан оператор выборки (query statement). Из всего набора данных OSM он отбирает точку с номером 1 и запоминает её. Оператор out выводит этот результат, по умолчанию в формате XML.

Фильтры

Попробуем что-то более практичное. Выберем магазины Дикси в некотором районе Москвы:

node[shop][name="Дикси"](55.7263,37.6503,55.7816,37.7864);
out;

Разберём оператор выборки. Он состоит из спецификатора типов выбираемых объектов, за которым следует один или несколько фильтров. Тип node означает, что нам вернутся только объекты, которые закартированы как точки. [shop] — это фильтр по тегу. Он отбирает только те объекты, у которых есть тег с ключом shop вне зависимости от значения. Следующий фильтр тоже по тегу, но в данном случае останутся только объекты, у которых тег name имеет указанное значение. Далее идёт фильтр по области, заданной координатами ограничивающего прямоугольника (bounding box, кратко — bbox). Заметим, что все строковые литералы, содержащие символы помимо буквенно-цифровых, в том числе нелатинские буквы, нужно заключать в двойные или одинарные кавычки.

Если забыть ограничить область выборки, то сервер начнёт проверять точки по всему миру, что, скорее всего, закончится неудачей из-за ограничения на время выполнения запроса или память. С другой стороны, вручную задавать координаты области выборки не кажется дружественным по отношению к пользователю. Благо, в Overpass Turbo реализованы расширенные Overpass-запросы посредством двойных фигурных скобок. Так, выражение {{bbox}} перед отправкой в Overpass API автоматически заменится на координаты либо видимой области карты, либо области, выделенной с помощью инструмента «Выбрать прямоугольник вручную».

node[shop][name="Дикси"]({{bbox}});
out;

Фильтр по объектам, у которых отсутствует определённый тег, реализуется с помощью операции отрицания «!». К примеру, запросим магазины, у которых не указано время работы:

node[shop][name="Дикси"][!opening_hours]({{bbox}});
out;

Типы выбираемых объектов

Различные спецификаторы типа позволяют выбирать только точки (node), только линии (way), только отношения (relation или rel), или лишь некоторые типы. К примеру, nwr соответствует любому из трёх типов, а nw — точкам и линиям. Поскольку магазин может быть обозначен в OSM и как точка, и как замкнутая линия (например, по контуру здания), и как отношение типа мультиполигон, то стоит переписать наш запрос магазинов со спецификатором типа nwr:

nwr[shop][name="Дикси"]({{bbox}});
out;

В таком виде Overpass API вернёт данные об объектах всех типов, но Overpass Turbo отобразит на карте только магазины-точки. Дело в том, что в ответе сервера для каждой линии будет передан лишь список идентификаторов точек, но не будет координат самих точек. Аналогично, отношения в ответе будут содержать ссылки на члены-линии и члены-точки, но самих членов не будет в ответе. Один из способов исправить это — добавить параметр geom в оператор вывода:

nwr[shop][name="Дикси"]({{bbox}});
out geom;

Теперь отобразятся все магазины — и в виде точек, и в виде контуров:

Регулярные выражения в фильтрах

С Дикси всё ясно. Но если мы попытаемся найти все магазины Пятёрочки:

nwr[shop][name="Пятёрочка"]({{bbox}});
out geom;

то можем их недосчитаться, так как название может быть написано через «е». В таких случаях поможет сравнение с регулярным выражением:

nwr[shop][name~"Пят(е|ё)рочка"]({{bbox}});
out geom;

Также для нечёткого поиска полезен флаг i, чтобы совпадения искались без учёта регистра символов:

nwr[shop][name~"пят(е|ё)рочка",i]({{bbox}});
out geom;

Наборы данных

Операторы записывают результат в так называемые наборы данных, которые затем передаются на вход последующим операторам. Если набор не указан явно, то используется набор данных по умолчанию, который имеет имя «._».  На самом деле, в предыдущем запросе оператор выборки сохранил результат в набор данных ._, а оператор вывода принял его на вход. То есть происходило на самом деле следующее:

nwr[shop][name~"пят(е|ё)рочка",i]({{bbox}}) -> ._;
._ out geom;

Мы можем сохранить результат оператора в именованный набор и далее использовать это имя:

nwr[shop][name~"пят(е|ё)рочка",i]({{bbox}}) -> .shops;
.shops out geom;

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

Поиск в окрестности

Найдём все кафе, находящиеся в радиусе 500 м от офиса VK:

nwr["addr:street"="Ленинградский проспект"]
   ["addr:housenumber"="39 с79"]
   ({{bbox}}) -> .office;
nwr[amenity=cafe](around.office:500);
out geom;

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

nwr["addr:street"="Ленинградский проспект"]
   ["addr:housenumber"="39 с79"]
   ({{bbox}});
nwr[amenity=cafe](around:500);
out geom;

Операторы рекурсии

Иногда нужно по линии получить все её узлы или, наоборот, по точкам найти содержащие их линии. Тогда не обойтись без операторов рекурсии. Вот запрос, который возвращает все пешеходные переходы на определённой улице:

way[name="улица Черняховского"]({{bbox}});
>;
node._[highway=crossing];
out;

Рассмотрим подробнее каждый из операторов. В первой строке оператор выбирает все линии с именем «улица Черняховского», попавшие в область поиска, и помещает их в набор данных ._. Во второй строке написан оператор рекурсивного спуска. Если его применить к входному набору линий, он поместит в выходной набор все узлы этих линий. В этом примере входной и выходной наборы рекурсивного оператора совпадают, в наборе по умолчанию все линии заменяются их узлами. В третьем операторе используется ещё один вид фильтра — фильтр по набору данных. Он выбирает точки, но только те, которые присутствуют в наборе по умолчанию, и применяет к ним фильтр по тегу, тоже перезаписывая набор данных своим результатом. Четвёртый оператор выводит набор ._, в котором содержится ответ.

Следующий пример: в какие автобусные маршруты входит остановка «Улица Усиевича», которая находится в окрестности 20 м от точки (55.8034694, 37.5353442)?

node[highway=bus_stop]
    (around:20.0, 55.8034694, 37.5353442);
<;
relation._[type=route][route=bus];
out geom;

В первом операторе выборки применяется вариант фильтра по окрестности с указанием радиуса и центра этой окрестности. Во второй строке вызывается оператор рекурсивного подъёма. За полным описанием его действия советую обратиться к документации, а конкретно в нашем примере этот оператор заменит точки из входного набора на линии и отношения, содержащие эти точки. Третий оператор оставляет только отношения с тегами type=route и route=bus. Результатом можно насладиться на картинке ниже.

Пересечение, объединение и разность

Над наборами данных можно выполнять операции из теории множеств.

Пересечение реализуется через многократное применение фильтра по набору данных. То есть имея наборы a и b, объекты, входящие в оба набора, можно получить, например, оператором nwr.a.b -> .c;.

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

(
  nwr[amenity=cafe]({{bbox}});
  nwr[amenity=restaurant]({{bbox}});
);
out geom;

В простых случаях разность наборов данных можно реализовать с помощью комбинации фильтров, поэтому для демонстрации этой операции нам потребуется более сложный пример. Магазины, занимающие всё здание, часто рисуют контуром, на который наносят теги здания и магазина. Найдём такие здания-магазины Дикси, на которых не указан главный вход (конечно же, чтобы отредактировать OSM и исправить этот недостаток):

(
  way[building][shop][name="Дикси"]({{bbox}});
  -
  (node[entrance=main]({{bbox}}); <;);
);
out;

Оператор во второй строке выбирает все линии, являющиеся одновременно и зданиями, и магазинами Дикси. В четвёртой строке мы получаем объединение всех точек, обозначающих главный вход, и линий (строго говоря, ещё и отношений), в которые входят эти точки. Разность этих наборов данных и даст здания магазинов без отмеченного главного входа. С помощью именованных наборов можно написать более интуитивно понятный вариант:

way[building][shop][name="Дикси"]({{bbox}}) -> .all_dixy_buildings;
node[entrance=main]({{bbox}}) -> .all_entrances;
.all_entrances < -> .ways_with_entrances;
(.all_dixy_buildings; - .ways_with_entrances;);
out geom;

В третьей строке оператор рекурсивного подъёма получает на вход подготовленные предыдущим оператором точки и сохраняет содержащие их линии и отношения в другой именованный набор.

Области

Помимо трёх базовых типов сущностей (точка, линия, отношение) Overpass API вводит ещё один — область (area). Области генерируются из замкнутых линий и мультиполигонов, по которым, скорее всего, потребуется делать пространственные запросы. В частности, области порождаются границами стран, регионов и населённых пунктов. Объекты типа area также можно сохранять в именованные наборы, а затем использовать в фильтрах по области. Например, все магазины Дикси в Москве:

area[name="Москва"];
nwr[shop][name="Дикси"](area);
out geom;

Запрос областей может вернуть и более одной сущности, поэтому чтобы исключить области, обозначающие здание гостиницы с именем «Москва» и т.п., нужно задать более специфичные фильтры. Также для ясности используем имена для наборов данных:

area[place=city][name="Москва"] -> .moscow;
nwr[shop][name="Дикси"](area.moscow);
out geom;

Геокодированные области

Ещё одно расширение Overpass Turbo — выражение {{geocodeArea}} — позволяет выбирать наиболее подходящие с точки зрения геокодирования области по их именам, а страны — ещё и по двухбуквенным кодам. Наш предыдущий пример будет выглядеть следующим образом:

{{geocodeArea:"Москва"}} -> .moscow;
nwr[shop][name="Дикси"](area.moscow);
out geom;

Оператор настроек

Оператор настроек позволяет управлять некоторыми параметрами запроса, в частности, изменить формат выходных данных на CSV или GeoJSON, попытаться увеличить таймаут или требования к памяти. Кроме того, можно задать глобальное ограничение на прямоугольную область отсечения, чтобы не повторять его в каждом последующем операторе.

Найдём все кафе и рестораны в интересующей нас области в формате CSV, выводя только координаты, название и время работы, при этом увеличив допустимое время запроса с трёх минут (180 секунд — значение по умолчанию) до десяти:

[out:csv(::lat, ::lon, name, opening_hours)][bbox:{{bbox}}][timeout:600];
(
    nwr[amenity=cafe];
    nwr[amenity=restaurant];
);
out geom;

Здесь ::lat и ::lon — одни из нескольких допустимых специальных полей, не являющихся тегами. В интерфейсе Overpass Turbo результат в формате CSV можно увидеть только на вкладке «Данные», на карте он не отображается.

Ретроспективные запросы

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

[out:xml][date:"2019-01-01T00:000:00Z"];
nwr[amenity=restaurant]({{bbox}});
out geom;

Запрос за временной промежуток покажет объекты, которые были созданы или изменены за это время:

[diff:"2018-01-01T00:00:00Z","2021-01-01T00:00:00Z"];
way[shop][name="Дикси"]({{bbox}});
out;

Overpass Turbo и MapCSS

Пусть вишенкой на многослойном торте возможностей Overpass Turbo станет запрос, раскрашивающий рестораны в разные цвета в зависимости от времени работы: зелёным — круглосуточные, красным — для которых время работы не указано, фиолетовым — остальные.

nwr[amenity=restaurant]({{bbox}});
out geom;

{{style:

node, area
{ color:gray; fill-color:gray; }

node[opening_hours=24/7],
area[opening_hours=24/7]
{ color:green; fill-color:green; }

node[opening_hours][opening_hours!=24/7],
area[opening_hours][opening_hours!=24/7]
{ color:purple; fill-color:purple; }

node[!opening_hours],
area[!opening_hours]
{ color:red; fill-color:red; }

}}

Здесь используется язык определения стилей MapCSS. Для объектов с различными тегами задаётся свой стиль отображения.

Прочие возможности

Язык Overpass QL предоставляет множество операторов, фильтров и настроек, которых мы не касались здесь: ограничение области выборки произвольным полигоном, различные варианты рекурсивных операторов, циклы по объектам и тегам, агрегирующие операторы и пр. За дополнительной информацией обращайтесь к User manual и Overpass QL language reference.

Стоит упомянуть и такие возможности Overpass Turbo, как:

  • Помощник — способен сформировать простой запрос на основе фразы на английском языке, описывающей намерения пользователя.

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

  • Сохранение изображения с картой в формате PNG.

  • Экспорт результатов запроса в различных форматах, включая XML, GeoJSON, KML и др.

  • Возможность сохранить запрос локально или в облаке и загрузить сохранённый запрос в редактор.

В заключение

Проект OpenStreetMap замечателен не только свободными геоданными, собранными тысячами энтузиастов, но и разнообразием приложений по обработке и анализу этих данных, часто также свободных в плане открытости исходного кода. Мы рассмотрели связку Overpass API + Overpass Turbo — мощнейший инструмент по доступу и визуализации данных OSM. Также для удобства всех, кто работает с данными OSM, мы сделали ещё одну точку входа. Пока что у неё нет ограничений на количество подключений или объём памяти. Но по статистике нагрузки на серверы, в случае введения ограничений пропускная способность нашей точки входа будет выше, чем у других подобных решений.

Комментарии (4)


  1. pfg21
    01.12.2021 17:32

    отлично !!


  1. dbf
    02.12.2021 09:40

    В своих различных геосервисах мы используем данные OpenStreetMap (OSM).

    Можете рассказать в каких? Это что-то внутреннее?


    1. alexey_zakharenkov Автор
      06.12.2021 16:34

      Наши собственные геосервисы используются в большинстве проектов VK, а также в продуктах наших партнёров. Описание публичных сервисов можно найти в документации на https://help.mail.ru/maps


      1. freeExec
        07.12.2021 17:24

        А демо карт больше не будет работать?
        https://demo.maps.mail.ru/