[Python, Data Mining, OpenStreetMap, Открытые данные] Как найти количество всех букв на всех знаках вида «въезд в город Х» в стране? Точный способ ответить на такие вопросы
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Недавно в рамках одного собеседования мне понадобилось решить задачу, условие которой приведено ниже:
У лучшего в мире управляющего по имени Пенультимо родилась очередная гениальнейшая идея, peализовать которую вам и предстоит. Он верит, что поток туристов на Исла-де-Эдукадос повысится, если он сможет рассказать всему миру, как же много замечательных дорожных знаков с длинными надписями eсть у них на острове. Вам предлагается придумать алгоритм, позволяющий подсчитать суммарное количество букв на всех знаках «Въезд в город Х» на острове, а затем применить полученные знания для подсчёта аналогичной метрики для Республики Беларусь. Обратите внимание язык, используемый для обозначения населённых пунктов, а также тот факт, что въездов в город может быть несколько. Пенультимо также приветствует инициативность, так что можете исследовать этот вопрос для отдельных областей, провести сравнение с количеством людей, проживающих в области, а также провести любые другие исследования, которые покажутся Вам интересными.
Под катом покажу точное решение этой и других похожих задач, например: «Сколько АЗС находится в пределах Москвы?»
Общий метод решения
Если взглянуть на карту OpenStreetMap, то на ум сразу приходит следующая идея: а давайте для каждого города получим его границы и дороги, находящиеся внутри его границ, а потом найдем их пересечения, на которых будут стоять знаки! Как будем искать пересечения: берем отрезок границы, потом отрезок дороги и смотрим, пересекаются ли они (типичная геометрическая задача). И так пока не кончатся все отрезки и города.
Про архитектуру данных OSM
SPL
Вообще, есть три главных компонента: узлы, линии и отношения
Каждый элемент имеет свой ID, а также свои теги.
- Узел — это просто точка на карте, имеющая кроме ID также широту и долготу
- Линия — это отсортированный список узлов, который представляет контур здания или отдельную улицу
- Отношение — это список, состоящий из узлов, линий или других отношений, представляющий логические или географические связи между объектами на карте
OverPass
OverPass — Это API для получения данных из OpenStreetMap. Оно имеет свой язык составления запросов, про него подробно можно почитать В этой статье.
Для того чтобы было проще и удобнее составлять запросы, есть инструмент Overpass-turbo, где результат выполнения запроса можно посмотреть в удобном и интерактивном виде.
Использование OverPass API в Python
Для обработки данных из OSM в Питоне можно использовать пакет Overpy в качестве оболочки.
Для отправки запросов и получения данных нужно проделать следующее:
import overpy
api = overpy.Overpass()
Data = api.query("""
*ваш запрос*
""")
где в переменной(?) Data и содержится все, что выдал нам сервер.
Как обработать эти данные? Предположим, что мы ввели следующий запрос на получение границ Минска:
relation["type"="boundary"]["boundary"="administrative"]["name:be"="Мінск"];
//Дословно: отношение вида административная граница города Минска
>; out skel qt;
На выходе имеем файл XML (можно выбрать Json), имеющий следующую структуру:
<*шапка файла*>
<далее идет подобное перечисление всех узлов>
<node id="277218521" lat="53.8605688" lon="27.3946601"/>
<node id="4623647835" lat="53.8603938" lon="27.3966685"/>
<node id="4713906615" lat="53.8605343" lon="27.3998220"/>
<node id="4713906616" lat="53.8605398" lon="27.3966820"/>
<node id="4713906617" lat="53.8605986" lon="27.3947987"/>
<node id="277050633" lat="53.8463790" lon="27.4431241"/>
<node id="277050634" lat="53.8455797" lon="27.4452681"/>
<node id="4713906607" lat="53.8460017" lon="27.4439797"/>
<далее указаны пути и ID узлов, из которых они состоят>
<way id="572768148">
<nd ref="5502433452"/>
<nd ref="277218520"/>
<nd ref="4713906620"/>
<nd ref="277218521"/>
<nd ref="4713906617"/>
<nd ref="4623647835"/>
<nd ref="4713906616"/>
</way>
<way id="29079842">
<nd ref="277212682"/>
<nd ref="277051005"/>
<nd ref="4739822889"/>
<nd ref="4739822888"/>
<nd ref="4739845423"/>
<nd ref="4739845422"/>
<nd ref="4739845421"/>
</way>
Давайте получим некоторые данные:
import overpy
api = overpy.Overpass()
Data = api.query("""
relation["type"="boundary"]["boundary"="administrative"]["name:be"="Мінск"];
>; out skel qt;
""")
Xa=Data.ways[0].nodes[0].lon #получаем долготу первого узла первой линии
Ya=Data.ways[0].nodes[0].lat #получаем широту
Xb=Data.ways[0].nodes[1].lon
Yb=Data.ways[0].nodes[1].lat
NodeID=Data.ways[0]._node_ids[0] #получаем ID первого узла первой линии
print(len(Data.nodes)) #получаем общее количество узлов
print(NodeID)
print(Xa,Ya)
print(Xb,Yb)
С точки зрения работы с OpenStreetMap в питоне, это всё, что понадобится, чтобы достать данные.
Перейдем непосредственно к задаче
Для ее решения написан код на Питоне, увидеть его можно под спойлером. Просьба сильно не ругать за качество кода, это первый такой большой проект на нем.
Заголовок спойлера
SPL
import overpy
###########################
def line_intersection(line1, line2): #функция поиска пересечений
ax1 = line1[0][0]
ay1 = line1[0][1]
ax2 = line1[1][0]
ay2 = line1[1][1]
bx1 = line2[0][0]
by1 = line2[0][1]
bx2 = line2[1][0]
by2 = line2[1][1]
v1 = (bx2 - bx1) * (ay1 - by1) - (by2 - by1) * (ax1 - bx1)
v2 = (bx2 - bx1) * (ay2 - by1) - (by2 - by1) * (ax2 - bx1)
v3 = (ax2 - ax1) * (by1 - ay1) - (ay2 - ay1) * (bx1 - ax1)
v4 = (ax2 - ax1) * (by2 - ay1) - (ay2 - ay1) * (bx2 - ax1)
return (v1 * v2 < 0) & (v3 * v4 < 0)
#######################################
citytmp = []
city = []
Borderway = []
Roadway = []
Total = 0
A = [0, 0]
B = [0, 0]
C = [0, 0]
D = [0, 0]
amount = 0
progressbar = 0
ReadyData = open('Готовые данные.txt','w')
with open("Города Беларуси.txt", "r", encoding='utf8') as file:
for i in range(115):
citytmp.append(file.readline())
citytmp = [line.rstrip() for line in citytmp]
for i in range(115):
city.append('"' + citytmp[i] + '"')
city[0]='"Дзісна"'
api = overpy.Overpass()
for number in range(0,115):#главный цикл обработки, перебирает города
borderstring = """(
relation["type"="boundary"]["boundary"="administrative"]["name:be"=""" + city[number] + """][place=town];
relation["type"="boundary"]["boundary"="administrative"]["name:be"=""" + city[number] + """][place=city];
);
>; out skel qt;"""
roadstring = """(
area[place=town]["name:be"=""" + city[number] + """];
way["highway"][highway!=service]["highway"!="footway"]["highway"!="track"]["highway"!="path"]
["highway"!="cycleway"]["highway"!="pedestrian"]["highway"!="steps"]["highway"!="residential"](area);
area[place=city]["name:be"=""" + city[number] + """];
way["highway"][highway!=service]["highway"!="footway"]["highway"!="track"]["highway"!="path"]
["highway"!="cycleway"]["highway"!="pedestrian"]["highway"!="steps"]["highway"!="residential"](area);
);
out body; >; out skel qt;"""
print('Getting data about', city[number],'...')
road = api.query(roadstring)
border = api.query(borderstring)
print('got data!, city:', city[number]) #получаем данные
for w in range(len(border.ways)): #перебирает линии границ
for i in range(len(border.ways[w]._node_ids)):#перебирает узлы линий границ
progressbar = i / len(border.ways[w]._node_ids) * 100
print(progressbar, "%;", w, "of", len(border.ways), "parts ready; city-", city[number])
A[0] = border.ways[w].nodes[i].lon
A[1] = border.ways[w].nodes[i].lat
if i == len(border.ways[w]._node_ids) - 1:
break
B[0] = border.ways[w].nodes[i+1].lon
B[1] = border.ways[w].nodes[i+1].lat
for j in range(len(road.ways)):
for k in range(len(road.ways[j]._node_ids)):
C[0] = road.ways[j].nodes[k].lon
C[1] = road.ways[j].nodes[k].lat
if k == len(road.ways[j]._node_ids) - 1:
break
D[0] = road.ways[j].nodes[k+1].lon
D[1] = road.ways[j].nodes[k+1].lat
if line_intersection((A, B), (C, D)) == 1:
amount += 1
print(road.ways[j]._node_ids[k])
print(amount)
Total += amount * len(city[number])
ReadyData.write(str(city[number]))
ReadyData.write(str(amount))
ReadyData.write('\n')
amount = 0
print('Total', Total) #и вывод всего
Заметки по коду
Я достаточно долго составлял запрос, подбирая разные типы дорог чтобы было меньше считать и чтобы не пропустить знаки. В итоговом запросе просто убраны те дороги, на которых нет знаков, например residential, service, footway, track и т. п.
Список городов я спарсил с википедии и сохранил в формате.тхт
Код выполняется достаточно долго, у меня даже один раз возникло желание переписать его на С++, но решил оставить так как есть. У меня он выполнялся два дня, все из-за проблем с диктатурой белорусским интернетом и перегруженностью сервера OverPass. Чтобы решить вторую проблему, нужно составить один запрос ко всем городам, но я еще не придумал как это нормально сделать.
Мой ответ на задачу
SPL
18981 буква
Что хочу сказать насчет правильности цифры: все упирается в качество данных самой OSM, то есть там есть места где, например, одну дорогу пересекает две линии границы, или где-нибудь на развязке граница проведена чуть-чуть не так, и в результате имеем лишнее/недостающее пересечение. Но это особенность конкретно этой не имеющей практического смысла задачи, в остальном OSM — сила.
Вторая задача
Сейчас давайте посчитаем количество заправок в пределах Москвы:
area[name="Москва"];
(
node["amenity"="fuel"](area);
way["amenity"="fuel"](area);
relation["amenity"="fuel"](area);
);
out body;
>;
out skel qt;
Код
SPL
import overpy
api = overpy.Overpass()
Data = api.query("""
area[name="Москва"];
(
node["amenity"="fuel"](area);
way["amenity"="fuel"](area);
relation["amenity"="fuel"](area);
);
out body;
>;
out skel qt;
""")
print(len(Data.nodes)) #получаем общее количество узлов
Результат — 489 заправок:
===========
Источник:
habr.com
===========
Похожие новости:
- [Управление персоналом, Карьера в IT-индустрии] Бесплатные образовательные курсы: бэкенд-разработка
- [Python, Финансы в IT] Оптимизация инвестиционного портфеля по методу Марковица
- [Машинное обучение] Новая библиотека для уменьшения размерности данных ITMO_FS — зачем она нужна и как устроена
- [Ненормальное программирование, Python, Программирование, Обработка изображений, Машинное обучение] Склеиваем несколько фотографий в одну длинную с помощью машинного обучения
- [Python, Программирование, Изучение языков] Почему Python — плохой выбор для первого языка программирования?
- [Data Mining, Big Data, Терминология IT] К чему прилагаются Business Intelligence (BI) — системы для бизнес-анализа?
- [Разработка веб-сайтов, Python, Управление разработкой] Исправь код, продай техническую фигню, покрути рулетку на Russian Python Week 2020
- [Тестирование IT-систем, Python, Тестирование мобильных приложений] Передача динамических объектов от setup к тестовой функции в py.test
- [Python, Node.JS, Серверное администрирование, TypeScript, Игры и игровые приставки] Пишем матчмейкинг для Доты 2014 года
- [Анализ и проектирование систем, Data Mining, Управление проектами, Исследования и прогнозы в IT] Бизнес-процессы на прокачку: как Process Intelligence помогает компаниям определить, что, где и когда автоматизировать
Теги для поиска: #_python, #_data_mining, #_openstreetmap, #_otkrytye_dannye (Открытые данные), #_tegi_nikto_ne_chitaet (теги никто не читает), #_python, #_overpass, #_overpy, #_openstreetmap, #_osm, #_python, #_data_mining, #_openstreetmap, #_otkrytye_dannye (
Открытые данные
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:57
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Недавно в рамках одного собеседования мне понадобилось решить задачу, условие которой приведено ниже: У лучшего в мире управляющего по имени Пенультимо родилась очередная гениальнейшая идея, peализовать которую вам и предстоит. Он верит, что поток туристов на Исла-де-Эдукадос повысится, если он сможет рассказать всему миру, как же много замечательных дорожных знаков с длинными надписями eсть у них на острове. Вам предлагается придумать алгоритм, позволяющий подсчитать суммарное количество букв на всех знаках «Въезд в город Х» на острове, а затем применить полученные знания для подсчёта аналогичной метрики для Республики Беларусь. Обратите внимание язык, используемый для обозначения населённых пунктов, а также тот факт, что въездов в город может быть несколько. Пенультимо также приветствует инициативность, так что можете исследовать этот вопрос для отдельных областей, провести сравнение с количеством людей, проживающих в области, а также провести любые другие исследования, которые покажутся Вам интересными.
Под катом покажу точное решение этой и других похожих задач, например: «Сколько АЗС находится в пределах Москвы?» Общий метод решения Если взглянуть на карту OpenStreetMap, то на ум сразу приходит следующая идея: а давайте для каждого города получим его границы и дороги, находящиеся внутри его границ, а потом найдем их пересечения, на которых будут стоять знаки! Как будем искать пересечения: берем отрезок границы, потом отрезок дороги и смотрим, пересекаются ли они (типичная геометрическая задача). И так пока не кончатся все отрезки и города. Про архитектуру данных OSMSPLВообще, есть три главных компонента: узлы, линии и отношения
Каждый элемент имеет свой ID, а также свои теги.
OverPass OverPass — Это API для получения данных из OpenStreetMap. Оно имеет свой язык составления запросов, про него подробно можно почитать В этой статье. Для того чтобы было проще и удобнее составлять запросы, есть инструмент Overpass-turbo, где результат выполнения запроса можно посмотреть в удобном и интерактивном виде. Использование OverPass API в Python Для обработки данных из OSM в Питоне можно использовать пакет Overpy в качестве оболочки. Для отправки запросов и получения данных нужно проделать следующее: import overpy
api = overpy.Overpass() Data = api.query(""" *ваш запрос* """) где в переменной(?) Data и содержится все, что выдал нам сервер. Как обработать эти данные? Предположим, что мы ввели следующий запрос на получение границ Минска: relation["type"="boundary"]["boundary"="administrative"]["name:be"="Мінск"];
//Дословно: отношение вида административная граница города Минска >; out skel qt; На выходе имеем файл XML (можно выбрать Json), имеющий следующую структуру: <*шапка файла*>
<далее идет подобное перечисление всех узлов> <node id="277218521" lat="53.8605688" lon="27.3946601"/> <node id="4623647835" lat="53.8603938" lon="27.3966685"/> <node id="4713906615" lat="53.8605343" lon="27.3998220"/> <node id="4713906616" lat="53.8605398" lon="27.3966820"/> <node id="4713906617" lat="53.8605986" lon="27.3947987"/> <node id="277050633" lat="53.8463790" lon="27.4431241"/> <node id="277050634" lat="53.8455797" lon="27.4452681"/> <node id="4713906607" lat="53.8460017" lon="27.4439797"/> <далее указаны пути и ID узлов, из которых они состоят> <way id="572768148"> <nd ref="5502433452"/> <nd ref="277218520"/> <nd ref="4713906620"/> <nd ref="277218521"/> <nd ref="4713906617"/> <nd ref="4623647835"/> <nd ref="4713906616"/> </way> <way id="29079842"> <nd ref="277212682"/> <nd ref="277051005"/> <nd ref="4739822889"/> <nd ref="4739822888"/> <nd ref="4739845423"/> <nd ref="4739845422"/> <nd ref="4739845421"/> </way> Давайте получим некоторые данные: import overpy
api = overpy.Overpass() Data = api.query(""" relation["type"="boundary"]["boundary"="administrative"]["name:be"="Мінск"]; >; out skel qt; """) Xa=Data.ways[0].nodes[0].lon #получаем долготу первого узла первой линии Ya=Data.ways[0].nodes[0].lat #получаем широту Xb=Data.ways[0].nodes[1].lon Yb=Data.ways[0].nodes[1].lat NodeID=Data.ways[0]._node_ids[0] #получаем ID первого узла первой линии print(len(Data.nodes)) #получаем общее количество узлов print(NodeID) print(Xa,Ya) print(Xb,Yb) С точки зрения работы с OpenStreetMap в питоне, это всё, что понадобится, чтобы достать данные. Перейдем непосредственно к задаче Для ее решения написан код на Питоне, увидеть его можно под спойлером. Просьба сильно не ругать за качество кода, это первый такой большой проект на нем. Заголовок спойлераSPLimport overpy
########################### def line_intersection(line1, line2): #функция поиска пересечений ax1 = line1[0][0] ay1 = line1[0][1] ax2 = line1[1][0] ay2 = line1[1][1] bx1 = line2[0][0] by1 = line2[0][1] bx2 = line2[1][0] by2 = line2[1][1] v1 = (bx2 - bx1) * (ay1 - by1) - (by2 - by1) * (ax1 - bx1) v2 = (bx2 - bx1) * (ay2 - by1) - (by2 - by1) * (ax2 - bx1) v3 = (ax2 - ax1) * (by1 - ay1) - (ay2 - ay1) * (bx1 - ax1) v4 = (ax2 - ax1) * (by2 - ay1) - (ay2 - ay1) * (bx2 - ax1) return (v1 * v2 < 0) & (v3 * v4 < 0) ####################################### citytmp = [] city = [] Borderway = [] Roadway = [] Total = 0 A = [0, 0] B = [0, 0] C = [0, 0] D = [0, 0] amount = 0 progressbar = 0 ReadyData = open('Готовые данные.txt','w') with open("Города Беларуси.txt", "r", encoding='utf8') as file: for i in range(115): citytmp.append(file.readline()) citytmp = [line.rstrip() for line in citytmp] for i in range(115): city.append('"' + citytmp[i] + '"') city[0]='"Дзісна"' api = overpy.Overpass() for number in range(0,115):#главный цикл обработки, перебирает города borderstring = """( relation["type"="boundary"]["boundary"="administrative"]["name:be"=""" + city[number] + """][place=town]; relation["type"="boundary"]["boundary"="administrative"]["name:be"=""" + city[number] + """][place=city]; ); >; out skel qt;""" roadstring = """( area[place=town]["name:be"=""" + city[number] + """]; way["highway"][highway!=service]["highway"!="footway"]["highway"!="track"]["highway"!="path"] ["highway"!="cycleway"]["highway"!="pedestrian"]["highway"!="steps"]["highway"!="residential"](area); area[place=city]["name:be"=""" + city[number] + """]; way["highway"][highway!=service]["highway"!="footway"]["highway"!="track"]["highway"!="path"] ["highway"!="cycleway"]["highway"!="pedestrian"]["highway"!="steps"]["highway"!="residential"](area); ); out body; >; out skel qt;""" print('Getting data about', city[number],'...') road = api.query(roadstring) border = api.query(borderstring) print('got data!, city:', city[number]) #получаем данные for w in range(len(border.ways)): #перебирает линии границ for i in range(len(border.ways[w]._node_ids)):#перебирает узлы линий границ progressbar = i / len(border.ways[w]._node_ids) * 100 print(progressbar, "%;", w, "of", len(border.ways), "parts ready; city-", city[number]) A[0] = border.ways[w].nodes[i].lon A[1] = border.ways[w].nodes[i].lat if i == len(border.ways[w]._node_ids) - 1: break B[0] = border.ways[w].nodes[i+1].lon B[1] = border.ways[w].nodes[i+1].lat for j in range(len(road.ways)): for k in range(len(road.ways[j]._node_ids)): C[0] = road.ways[j].nodes[k].lon C[1] = road.ways[j].nodes[k].lat if k == len(road.ways[j]._node_ids) - 1: break D[0] = road.ways[j].nodes[k+1].lon D[1] = road.ways[j].nodes[k+1].lat if line_intersection((A, B), (C, D)) == 1: amount += 1 print(road.ways[j]._node_ids[k]) print(amount) Total += amount * len(city[number]) ReadyData.write(str(city[number])) ReadyData.write(str(amount)) ReadyData.write('\n') amount = 0 print('Total', Total) #и вывод всего Заметки по коду Я достаточно долго составлял запрос, подбирая разные типы дорог чтобы было меньше считать и чтобы не пропустить знаки. В итоговом запросе просто убраны те дороги, на которых нет знаков, например residential, service, footway, track и т. п. Список городов я спарсил с википедии и сохранил в формате.тхт Код выполняется достаточно долго, у меня даже один раз возникло желание переписать его на С++, но решил оставить так как есть. У меня он выполнялся два дня, все из-за проблем с диктатурой белорусским интернетом и перегруженностью сервера OverPass. Чтобы решить вторую проблему, нужно составить один запрос ко всем городам, но я еще не придумал как это нормально сделать. Мой ответ на задачуSPL18981 буква
Что хочу сказать насчет правильности цифры: все упирается в качество данных самой OSM, то есть там есть места где, например, одну дорогу пересекает две линии границы, или где-нибудь на развязке граница проведена чуть-чуть не так, и в результате имеем лишнее/недостающее пересечение. Но это особенность конкретно этой не имеющей практического смысла задачи, в остальном OSM — сила. Вторая задача Сейчас давайте посчитаем количество заправок в пределах Москвы: area[name="Москва"];
( node["amenity"="fuel"](area); way["amenity"="fuel"](area); relation["amenity"="fuel"](area); ); out body; >; out skel qt; КодSPLimport overpy
api = overpy.Overpass() Data = api.query(""" area[name="Москва"]; ( node["amenity"="fuel"](area); way["amenity"="fuel"](area); relation["amenity"="fuel"](area); ); out body; >; out skel qt; """) print(len(Data.nodes)) #получаем общее количество узлов Результат — 489 заправок: =========== Источник: habr.com =========== Похожие новости:
Открытые данные ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:57
Часовой пояс: UTC + 5