OpenStreetMap — это большая открытая база геоданных, которую создают сами пользователи. Иногда её называют «Википедией карт», потому что любой желающий может добавить объекты.
Данные OpenStreetMap (OSM) распространяются по лицензии Open Database License (ODbL 1.0). Это означает, что их можно свободно скачивать, изменять, дополнять и даже использовать в коммерческих проектах, но с обязательным указанием источника:
© OpenStreetMap contributors
Все объекты в OSM имеют строгую структуру и описываются с помощью тегов — пар «ключ–значение», которые определяют характеристики и назначение объекта.
В этом разделе мы познакомимся со структурой данных OSM, разберёмся, как работают теги, и научимся выгружать определённые типы в пределах заданной территории.
0. Импорт библиотек¶
import osmnx as oxOSMnx (
osmnx) — библиотека Python для загрузки, анализа и визуализации данных OpenStreetMap. Она позволяет легко получать пространственные объекты по названию места или по координатам.
1. Геокодирование границ¶
Перед тем как выгружать данные из OpenStreetMap, необходимо определить территорию исследования. Один из самых простых способов — задать её по названию места.
В библиотеке OSMnx для этого используется метод .geocode_to_gdf(), который геокодирует название объекта и возвращает его границы в формате GeoDataFrame.
area_name = "Центральный район, Санкт-Петербург"
area_osm = ox.geocode_to_gdf(area_name)Посмотрим на данные на карте с помощью метода explore():
area_osm.explore(tiles='cartodbpositron')Мы получили корректные административные границы района. Это означает, что в дальнейшем мы можем использовать то же название территории для загрузки конкретных объектов или уличной сети из OpenStreetMap.
2. Получение данных из OpenStreetMap¶
2.1 Теги¶
В OpenStreetMap все объекты описываются с помощью тегов — пар «ключ–значение», которые определяют тип объекта и его характеристики.
Именно теги позволяют формировать выборку данных: мы указываем, какие объекты нас интересуют, и выгружаем их для ранее заданной территории.
Полный список тегов OSM доступен в документации: Map Features
Как задаются теги в OSMnx
В OSMnx теги передаются в виде словаря Python, где:
ключ словаря — это ключ тега в OSM;
значение — конкретное значение тега или
True, если нас интересуют все объекты с данным ключом.
Примеры задания тегов:
Все здания:
tags = {"building": True}Только кафе:
tags = {"amenity": "cafe"}Кафе, рестораны и бары:
tags = {"amenity": ["cafe", "restaurant", "bar"]}Такой подход позволяет гибко управлять выборкой и получать только те объекты, которые необходимы для анализа.
Запишем в переменную tags теги для объектов, с которыми дальше хотели бы работать.
tags = {"building": True}Теперь, когда мы разобрались с тем, как задаются теги и формируется выборка объектов, перейдём к способам их выгрузки из OpenStreetMap.
2.2. Выгрузка данных по территории¶
2.2.1 По названию места¶
Самый простой способ выгрузки данных — запрос по названию территории. Если границы объекта корректно определяются в OpenStreetMap (что мы проверили для нашего района ранее), это название можно напрямую использовать для получения интересующих нас объектов.
Такой подход удобен при работе с административными единицами — районами, городами и регионами — когда требуется быстро получить объекты, расположенные в их пределах.
osm_data = ox.features_from_place(area_name, tags)Выведем первые пять строк атрибутивной таблицы с помощью метода head():
osm_data.head()Посмотрим на данные на карте с помощью метода explore():
osm_data.explore(tiles='cartodbpositron',tooltip=None)2.2.2 По границам полигона¶
Другой способ выгрузки данных — использование заранее полученной геометрии. В этом случае в запрос передаётся не название места, а конкретный полигон, задающий границы интересующей территории.
Такой подход особенно удобен, когда границы сформированы вручную, уточнены пользователем или не совпадают с официальными административными районами.
Чтобы не загружать новые данные, воспользуемся границами района, полученными в первом разделе. Результат будет идентичен выгрузке по названию места, поскольку используется та же территория, но заданная в виде полигона.
# district_polygon = area_osm.geometry.iloc[0] # полигон района
# osm_data_polygon = ox.features_from_polygon(district_polygon, tags)
# osm_data_polygon.explore(tiles='cartodbpositron',tooltip=None)2.2.3 По Bounding Box¶
Ещё один способ задания территории — использование Bounding Box (ограничивающего прямоугольника).
Bounding Box — это минимальный прямоугольник, полностью охватывающий выбранную геометрию. Он задаётся четырьмя координатами: северной, южной, восточной и западной границами.
Получим Bounding Box на основе границ района, выгруженных в первом разделе, с помощью атрибута total_bounds и выполним экспорт данных в его пределах.
osm_data_bbox = ox.features_from_bbox(area_osm.total_bounds, tags)
# osm_data_bbox.explore(tiles='cartodbpositron',tooltip=None)При использовании Bounding Box мы получили больше данных, поскольку в выборку попадают все объекты, расположенные внутри ограничивающего прямоугольника, включая те, которые находятся за пределами исходного полигона.
3. Изучение данных¶
После выгрузки объектов из OpenStreetMap важно выполнить первичный обзор данных. Это позволяет оценить их структуру, объём и качество перед дальнейшим анализом.
3.1 Количество объектов¶
Сначала определим, сколько объектов было загружено:
len(osm_data)59293.2 Типы геометрии¶
Проверим, какие типы геометрии представлены в данных:
osm_data.geom_type.unique()array(['Point', 'Polygon', 'MultiPolygon'], dtype=object)В данных зданий могут встречаться объекты с разными типами геометрии, поскольку в OpenStreetMap один и тот же объект может быть представлен по-разному. Поэтому перед дальнейшим анализом важно определить, с какими типами геометрии мы будем работать, и при необходимости выполнить фильтрацию данных.
3.3 Атрибуты¶
Посмотрим, какие атрибуты содержит таблица и какие типы данных им соответствуют:
osm_data.dtypesgeometry geometry
building object
contact:vk object
name object
phone object
...
atm object
construction object
was:name object
grades object
note:building:year_built object
Length: 281, dtype: objectМожно заметить, что количество полей в таблице довольно велико. Это связано с тем, что OpenStreetMap хранит множество тегов, и при экспорте мы получаем практически все атрибуты, которые были добавлены пользователями при описании объектов.
Чтобы наглядно посмотреть типы данных всех атрибутов, сформируем отдельную таблицу с названиями полей и соответствующими типами данных:
dtypes_df = osm_data.dtypes.reset_index()
dtypes_df.columns = ["column", "dtype"]
dtypes_df.head()4. Обработка¶
4.1 Фильтрация по типу геометрии¶
Как мы увидели ранее, в данных зданий могут встречаться разные типы геометрии. Для большинства пространственных операций важно, чтобы в наборе данных присутствовал только один тип геометрии.
Оставим только объекты с геометрией Polygon и MultiPolygon:
osm_data = osm_data[
osm_data.geom_type.isin(["Polygon", "MultiPolygon"])
]Проверим, какие типы геометрии остались в наборе данных:
osm_data.geom_type.unique()array(['Polygon', 'MultiPolygon'], dtype=object)4.2 Проверка корректности геометрии¶
Перед дальнейшим анализом важно убедиться, что геометрии объектов являются корректными и не содержат топологических ошибок, которые могут повлиять на результаты пространственных операций.
(~osm_data.is_valid).sum()np.int64(0)Если присутствуют невалидные объекты, можно оставить только корректные:
osm_data = osm_data[osm_data.is_valid]Или исправить некорректную геометрию:
osm_data["geometry"] = osm_data.make_valid()4.3 Сохранение идентификаторов объектов¶
В данных, выгруженных из OpenStreetMap, идентификатор объекта (id) хранится не в виде обычного поля, а во втором уровне индекса GeoDataFrame. Поэтому к нему нельзя обратиться напрямую как к атрибуту таблицы.
Для удобства дальнейшей работы (фильтрации, объединения таблиц, сохранения данных) вынесем идентификатор объекта в отдельное поле.
osm_data["osm_id"] = osm_data.index.get_level_values("id")4.4 Отбор необходимых атрибутов¶
Как мы увидели ранее, количество атрибутов в наборе данных довольно велико. Однако многие из них не требуются для дальнейшей работы. Набор необходимых атрибутов зависит от того, как именно вы планируете использовать данные.
Мы оставим только три поля: osm_id, building и geometry
osm_data = osm_data[["osm_id", "building", "geometry"]]Выведем первые пять строк атрибутивной таблицы с помощью метода head():
osm_data.head()5. Сохранение данных¶
Теперь можем сохранить полученный набор пространственных данных в любом поддерживаемом формате с помощью метода to_file() библиотеки GeoPandas.
#osm_data.to_file('../data/osm_build.geojson')6. Итог¶
В этом разделе мы рассмотрели структуру данных OpenStreetMap и способы их выгрузки с помощью OSMnx.
Мы задали территорию исследования, сформировали запрос по тегам и выгрузили пространственные объекты для заданной территории, выполнили первичный обзор и предобработку данных.