DIY geocoding service

Создание сервиса геокодирования с использованием Python и Elasticsearch.

photography from unsplash

В этой статье мы рассмотрим пример построения простого сервиса по прямому геокодированию на территории города Москвы. Это руководство по разработке сервиса не претендует на единственно верное решение и в основном преследует цель познакомить читателя с технологией.

Для разработки сервиса мы будем использовать (3.8 или выше версии) и установочный пакет (msi) версии 7.9.0. Сервис разворачивается на Windows 10.

Прямое геокодирование — это задача получения координат географической точки по её адресу, то есть при введении адреса сервис возвращает место в системе координат WGS84 (EPSG:4326).

В качестве исходной базы данных для геокодирования мы будем использовать открытые данные из сети интернет (например сервис data.mos.ru, а именно или данные из ). Вы можете использовать любой другой источник данных, доступный вам, и содержащий адрес и координаты зданий.

Перед началом необходимо установить Python (настроить окружение и установить необходимые библиотеки) и Elasticsearch. Проверить правильность установки Elasticsearch можно перейдя по ссылке в браузере: , в ответ вы получите:

{
"name" : "YOUR NAME",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "kuO-N2pM9FSVKQ88XoYp_w",
"version" : {
"number" : "7.9.0",
"build_flavor" : "unknown",
"build_type" : "unknown",
"build_hash" : "a479a2a67fce0389512d6a9361301778b92dff667",
"build_date" : "2020-08-31T21:36:48.204330Z",
"build_snapshot" : false,
"lucene_version" : "8.6.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}

Для загрузки данных в Elasticsearch нам необходимо обработать данные из открытых источников, определиться с системой координат в которой будут возвращаться результаты геокодирования (на примере OSM).

import geopandas as gpd
import pandas as pd
import shapely

# читаем файл с полигонами зданий из OSM (возможна реализация
# загрузки данных через Python с использованием библиотеки OSMnx)
# Система координат остается неизменной EPSG:4326
gdf_osm = gpd.read_file('building-polygon.shp', scr='EPSG:4326')
# собираем адрес
gdf_osm['adres'] = gdf_osm.A_STRT +' '+ gdf_osm.A_HSNMBR
# выбираем не пустые записи в поле adres
gdf_osm = gdf_osm[~gdf_osm['adres'].isnull()]
# сбрасываем индекс
gdf_osm.reset_index(drop=True, inplace=True)
# выбираем поля с адресом и геометрией, наш геокодер будет
# возвращать контур здания (полигон) в ответ на запрос.
gdf_osm = gdf_osm[['adres', 'geometry']]
# преобразовываем геометрию в строку
gdf_osm['geom'] = gdf_osm.geometry.apply(lambda x: shapely.wkt.dumps(x))
# создаем датафрейм для загрузки в Elasticsearch
df = pd.DataFrame(gdf_osm[['adres', 'geom']])
# добавляем колонку eventId для корректной загрузки
# (в других версиях библиотеки espandas могут быть другие имена
# обязательных полей)
df['eventId'] = df.index

Теперь создаем подключение к Elasticsearch и загружаем данные.

Для загрузки данных в базу будем использовать библиотеку Espandas, в зависимости от версии необходимо будет поправить код библиотеки, по этой ссылке описано решение проблемы:

from espandas import Espandas

# назначаем индекс и тип документов
INDEX = 'geo'
TYPE = 'geos'

# создаем 'подключение'
esp = Espandas()

# записываем данные в Elasticsearch
esp.es_write(df, INDEX, TYPE)

В зависимости от количества данных время записи в БД может варьироваться.

Проведем тест на примере адреса: “улица МОСФИЛЬМОВСКАЯ дом 8

from elasticsearch import Elasticsearch
from pandas.io.json import json_normalize

# создаем 'подключение'
es = Elasticsearch('http://localhost:9200')

# адрес для теста
content = 'улица МОСФИЛЬМОВСКАЯ дом 8'

# запрос к базе:
# 'geo' - ранее указанный индекс
# 'adres' - поле по которому осуществляется поиск
# "fuzziness" - алгоритм сопоставления адресов
search_res = es.search(index='geo',
body={
'query': {
'match': {
'adres': {
'query': content,
"fuzziness": "AUTO"
},
}
}})

# ТОП-10 результатов преобразованные в датафрейм
df_res = json_normalize(search_res['hits']['hits'])

Результат работы поисковика:

Наиболее подходящий ответ находится в первой строке (ответ возвращается отсортированным по колонке “_score”, которая указывает на степень совпадения адреса подаваемого в запрос и адреса из базы данных, более подробно о составлении запроса по ).

Как можно улучшить результаты поиска:

1. Добавлять новые адресные базы с геопривязкой.

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

3. Использование специализированных библиотек для нормализации адреса (pullenti_wrapper, natasha и т.д.)

4. Отказаться от Elasticsearch и реализовать свой алгоритм сравнения ( fuzzywuzzy, levenshtein и т.д.).

Для созданию сервиса (локального) по геокодированию адресов, мы будем использовать библиотеку . Подробно останавливаться на этом шаге не будем, приведем лишь один из рабочих вариантов реализации такого приложения. Тестовый набор данных расположен по и содержит 20 адресов для проверки работоспособности этого решения.

import pandas as pd
import streamlit as st
from elasticsearch import Elasticsearch
from pandas.io.json import json_normalize


# ini
es = Elasticsearch('http://localhost:9200')

# Functions
def elastic_search(content):
"""Функция отправляет запрос в базу и возвращает адрес и его геометрию"""
search_res = es.search(index='geo',
body={
'query': {
'match': {
'adres': {
'query': content,
"fuzziness": "AUTO"
},
}
}})
df = json_normalize(search_res['hits']['hits'])
if len(df) !=0:

return df['_source.adres'][0], df['_source.geom'][0]




def generate_csv(df, name):
"""Функция сохраняет файл по указанному пути"""
df.to_csv(f'{name}', encoding='cp1251')


# Streamlit app
st.set_page_config(page_title='geocoding_service', layout="wide", initial_sidebar_state="expanded")
st.sidebar.title('Геокодирование адресов')
st.sidebar.write('Для корректной работы программы подготовьте файл с расширением *.csv, где имя колонки с геокодируемыми адресами - ADRES')
data = st.sidebar.file_uploader("Загрузите файл с расширением csv, выберите разделитель и кодировку файла", type=([".csv"]))

try:
sep = st.sidebar.selectbox('Выберите разделитель ', ['', ";", ",","\t"])
enc = st.sidebar.selectbox('Выберите кодировку ', ['', "cp1251", "utf8"])
df = pd.read_csv(data, delimiter=sep, encoding=enc)
try:
st.write('Процесс геокодирования запущен')
if len(df) != 0:
df['source.adress'] = None
df['_source.geom'] = None
# Отправляем запрос на геокодирование для каждого адреса из нашего списка
df['lst'] = df['ADRES'].apply(lambda x: elastic_search(str(x)))
df[['source.adress', '_source.geom']] = pd.DataFrame(df.lst.tolist(), index=df.index)
del df['lst']

st.write('Результат')
st.table(df)

name = st.sidebar.text_input('Введите путь и имя файла: ', r'C:\Users\YOUR_NAME\Downloads\имя файла.csv')

if st.sidebar.button('Сохранить результат'):
generate_csv(df, name)
st.sidebar.write('Файл сохранен по адресу: ', f'{name}')
except:
st.title('Попробуйте изменить разделитель и кодировку файла.')

except:
st.stop()

Создайте файл с кодом представленным выше и расширением .py. Для запуска приложения необходимо в командной строке ввести следующую команду:

streamlit run \path\to\file\name_your_file.py

Если все выполнили правильно в окне браузера откроется вкладка geocoding_service с вашим приложением.

Демонстрация работы приложения

После выполнения процедуры геокодирования сохраните файл на своем компьютере и перейдите в настольное приложение QGIS, для того чтобы просмотреть результат геокодирования. Для этого в меню выберите Слой >Добавить слой>Добавить слой из текста с разделителями. В открывшемся меню установите значения как на скриншоте:

Результат геокодирования на карте в ПО QGIS:

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

--

--

Data analysis. Geoinformation technologies. Geodata visualization. Urban planning.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Urban Materials

Data analysis. Geoinformation technologies. Geodata visualization. Urban planning.