Получаем данные Open Street Map в Python

Python

Случалось ли вам работать над проектом, где были необходимы картографические данные определенной местности? Например, сколько шоссе пересекают город или сколько ресторанов расположено в заданной области?

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

Модель OpenStreetData

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

Говоря простыми словами, точки являются объектами на карте (с указанием долготы и широты), как указано на следующем изображении Индийских Ворот в Дели.

Линии соответствует упорядоченный список точек, который может соответствовать улице либо контуру дома. Ниже приведен пример NH 24, расположенного в Индии.

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

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

Использование Overpass API

Теперь давайте рассмотрим процесс загрузки данных с OSM. Для формирования обращений в Overpass API используется встроенный язык запросов.

К его использованию потребуется привыкнуть, но, к счастью, существует Overpass Turbo, разработанный Мартином Райфером, который оказывается весьма удобен для интерактивной обработки запросов прямо в браузере.

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

node["amenity"="cafe"]({{bbox}}); 
out;

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

Далее мы применяем фильтрацию по тегам, указав пару ключ — значение «amenity»=»cafe». В документации ресурса вы можете найти также много других вариантов настройки фильтра.

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

Еще один фильтр определяется {{bbox}} и соответствует ограничительной рамке, в которой мы хотим осуществить поиск. Работает эта функция только в Overpass Turbo. В иных случаях вы можете определить аналогичную ограничительную рамку с помощью (south, west, north, east) координатами широты и долготы. Выглядеть это может так:

node["amenity"="pub"]
  (53.2987342,-6.3870259,53.4105416,-6.1148829); 
out;

Это можно применить и в Overpass Turbo. Как мы видели ранее, в модели данных OSM существуют линии и отношения, имеющие общий атрибут.

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

Пример:

( node["amenity"="cafe"]({{bbox}});
  way["amenity"="cafe"]({{bbox}});
  relation["amenity"="cafe"]({{bbox}});
);
out;

Еще один способ фильтрации запросов — это по ID элемента. Вот пример запроса node(1); out;, по которому выдается главный меридиан, имеющий долготу, приближенную к нулю.

Возможна также фильтрация по области поиска, задаваемой подобным образом area[«ISO3166-1″=»GB»][admin_level=2];. Этот запрос выдаст нам область Великобритании.

Мы можем использовать этот фильтр, добавив (area) к инструкции:

area["ISO3166-1"="GB"][admin_level=2];
node["place"="city"](area);
out;

Этот запрос возвращает все города Британии. В качестве области также допустимо использование линий или отношений. В первом случае ID области получается путем прибавления 2400000000 к ID существующей OSM линии. В случае же с отношениями к их ID потребуется прибавить уже 3600000000.

Имейте в виду, что не все линии и отношения имеют такую обратную связь с областями (а именно отмеченные тегом area=no, большинство мультиполигонов и не имеющие заданного name=*).

Если мы применим отношение Великобритании к предыдущему примеру, то у нас получится следующее:

area(3600062149);
node["place"="city"](area);
out;

Мы также можем настраивать выдачу запрошенных через out данных. До этого момента мы определяли ее просто как out, но существуют и другие дополнительные значения.

Первый набор таких значений может контролировать многословность или детализацию вывода — ids, skel, body (значение по умолчанию), tags, meta и count. Все они подробно описаны в документации.

Помимо всего этого, мы можем добавлять модификаторы геокодированной информации. geom добавляет полную геометрию для каждого объекта. Это может быть очень полезно в случаях, когда возвращаются отношения или линии, не имеющие координат.

Например, запрос rel[«ISO3166-1″=»GB»][admin_level=2]; out geom; без добавления geom не вернул бы никаких координат. Значение bb добавляет только ограничительную рамку для каждой линии и отношения, а center дополнительно указывает только центр той же ограничительной рамки.

Порядок сортировки может быть настроен с помощью asc и qt, которые упорядочивают по ID, либо по индексу квадрата (quadtile), соответственно. Последний вариант оказывается существенно быстрее. В конце концов, вы можете добавлять целочисленное значение, чтобы задать максимальное число возвращаемых элементов.

Совместив все то, чему мы только что научились, можно сформировать запрос области всей сети пивоварни Biergarten в Германии.

area["ISO3166-1"="DE"][admin_level=2];( node["amenity"="biergarten"](area);
  way["amenity"="biergarten"](area);
  rel["amenity"="biergarten"](area);
);
out center;

Доступ к API в Python

Для обращения в Overpass API посредством Python нужно использовать пакет overpy в качестве обертки. Ниже приведен пример того, как можно преобразовать предыдущий код с помощью этого пакета:

import overpyapi = overpy.Overpass()
r = api.query("""
area["ISO3166-1"="DE"][admin_level=2];
(node["amenity"="biergarten"](area);
 way["amenity"="biergarten"](area);
 rel["amenity"="biergarten"](area);
);
out center;
""")coords  = []
coords += [(float(node.lon), float(node.lat)) 
           for node in r.nodes]
coords += [(float(way.center_lon), float(way.center_lat)) 
           for way in r.ways]
coords += [(float(rel.center_lon), float(rel.center_lat)) 
           for rel in r.relations]

У overpy есть одно приятное достоинство — он определяет тип содержимого ответа, а именно XML или JSON.

Специально для сайта ITWORLD.UZ. Новость взята с сайта NOP::Nuances of programming