Распространённая задача программистов в работе с геопространственными данными — отобразить маршруты между различными точками. Решением, которое может понадобиться в разработке веб-сайта, делимся к старту курса по Fullstack-разработке на Python.
Вы освоите:
Геокодирование местоположения.
Нахождение маршрутов между двумя точками.
Установка OSMnx
OSMnx — это пакет Python для загрузки геопространственных данных OpenStreetMap, моделирования, проектирования, визуализации и анализа реальных уличных сетей и другой геометрии геопространства.
Установка OSMnx немного хитрая: до обычных pip/conda install нужно выполнить следующие шаги:
$ conda config --prepend channels conda-forge
$ conda install osmnx
Если pip/conda install не работает, выполните pip install osmnx
.
Вы получите готовый к работе OSMnx.
Нахождение самого быстрого маршрута
Теперь, чтобы найти самый короткий маршрут между двумя точками, можно воспользоваться пакетом NetworkX вместе с OSMnx. NetworkX — пакет Python для создания, манипулирования, а также изучения структуры, динамики и функций сложных сетей.
Следующий фрагмент кода находит самый быстрый путь между двумя точками в Сан-Франциско:
import osmnx as ox
import networkx as nx
ox.config(log_console=True, use_cache=True)
# define the start and end locations in latlng
start_latlng = (37.78497,-122.43327)
end_latlng = (37.78071,-122.41445)
# location where you want to find your route
place = 'San Francisco, California, United States'
# find shortest route based on the mode of travel
mode = 'walk' # 'drive', 'bike', 'walk'
# find shortest path based on distance or time
optimizer = 'time' # 'length','time'
# create graph from OSM within the boundaries of some
# geocodable place(s)
graph = ox.graph_from_place(place, network_type = mode)
# find the nearest node to the start location
orig_node = ox.get_nearest_node(graph, start_latlng)
# find the nearest node to the end location
dest_node = ox.get_nearest_node(graph, end_latlng)
# find the shortest path
shortest_route = nx.shortest_path(graph, orig_node,dest_node,
weight=optimizer)
Метод нахождения кратчайшего пути по умолчанию — алгоритм Дейкстры, строка dijkstra. Изменить его на bellman-ford можно, изменив параметр method в shortest_path(). Сейчас переменная shortest_route сейчас содержит коллекцию пути, позволяющую за кратчайшее время пешком пройти из одной точки в другую:
[5287124093,
65314192,
258759765,
65314189,
5429032435,
65303568,
65292734,
65303566,
2220968863,
4014319583,
65303561,
65303560,
4759501665,
65303559,
258758548,
4759501667,
65303556,
65303554,
65281835,
65303553,
65303552,
65314163,
65334128,
65317951,
65333826,
65362158,
65362154,
5429620634,
65308268,
4064226224,
7240837048,
65352325,
7240837026,
7240837027]
Отрисовка путей
Очевидно, список путей нам не очень поможет. Более осмысленный способ интерпретации результата — отрисовка путей с помощью функции plot_route_folium():
shortest_route_map = ox.plot_route_folium(graph, shortest_route)
shortest_route_map
Эта функция возвращает карту folium (folium.folium.Map). В Jupyter Notebook она выглядит так:
Набор тайлов по умолчанию — cartodbpositron. Если нужно изменить его, установите аргумент tiles в соответствующее значение. Следующий фрагмент кода покажет карту с набором тайлов openstreetmap:
shortest_route_map = ox.plot_route_folium(graph, shortest_route,
tiles='openstreetmap')
shortest_route_map
Ниже отображён набор openstreetmap:
Если нужно позволить пользователям выбирать предпочтения во время выполнения, воспользуйтесь этим фрагментом кода:
import folium
folium.TileLayer('openstreetmap').add_to(shortest_route_map)
folium.TileLayer('Stamen Terrain').add_to(shortest_route_map)
folium.TileLayer('Stamen Toner').add_to(shortest_route_map)
folium.TileLayer('Stamen Water Color').add_to(shortest_route_map)
folium.TileLayer('cartodbpositron').add_to(shortest_route_map)
folium.TileLayer('cartodbdark_matter').add_to(shortest_route_map)
folium.LayerControl().add_to(shortest_route_map)
shortest_route_map
Теперь пользователь может выбирать набор тайлов:
Изменения типа перемещения и оптимизатор
Помимо поиска кратчайшего пути пешехода можно найти кратчайший путь для водителя:
# find shortest route based on the mode of travel
mode = 'drive' # 'drive', 'bike', 'walk'
# find shortest path based on distance or time
optimizer = 'time' # 'length','time'
Вот он:
А что с велосипедом?
# find shortest route based on the mode of travel
mode = 'bike' # 'drive', 'bike', 'walk'
# find shortest path based on distance or time
optimizer = 'time' # 'length','time'
Вот самый быстрый путь на велосипеде:
Можно найти не только самый быстрый путь, но и самый короткий:
# find shortest route based on the mode of travel
mode = 'bike' # 'drive', 'bike', 'walk'
# find shortest path based on distance or time
optimizer = 'time' # 'length','time'
Вот соответствующий код для велосипедов:
Другие варианты комбинирования оставляю вам.
Геокодирование местоположения
В поиске маршрута не очень удобно определять широту и долготу, если только их уже нет в вашем наборе данных. Проще может быть другой подход: дать расположениям удобные названия. Сделать это можно с помощью геокодирования, используя модуль geopy.
Геокодирование — это преобразование адреса в координаты этого адреса. Обратное геокодирование — это преобразование пары координат в удобный адрес.
Чтобы установить geopy, выполните в терминале эту команду:
$ pip install geopy
Следующий фрагмент кода создаёт экземпляр класса геокодера Nominatim для данных OpenStreetMap. В коде вызывается метод geocode(), чтобы закодировать расположение Golden Gate Bridge. При помощи геокодирования можно извлечь широту и долготу расположения:
from geopy.geocoders import Nominatim
locator = Nominatim(user_agent = "myapp")
location = locator.geocode("Golden Gate Bridge")
print(location.latitude, location.longitude)
# 37.8303213 -122.4797496
print(location.point)
# 37 49m 49.1567s N, 122 28m 47.0986s W
print(type(location.point))
# <class 'geopy.point.Point'>
Проверить результат можно здесь. В поисковую строку введите широту и долготу:
Изменим оригинальный код, чтобы геокодировать начальную и конечную точки:
import osmnx as ox
import networkx as nx
from geopy.geocoders import Nominatim
ox.config(log_console=True, use_cache=True)
locator = Nominatim(user_agent = "myapp")
# define the start and end locations in latlng
# start_latlng = (37.78497,-122.43327)
# end_latlng = (37.78071,-122.41445)
start_location = "Hilton San Francisco Union Square"
end_location = "Golden Gateway Tennis & Swim Club"
# stores the start and end points as geopy.point.Point objects
start_latlng = locator.geocode(start_location).point
end_latlng = locator.geocode(end_location).point
# location where you want to find your route
place = 'San Francisco, California, United States'
# find shortest route based on the mode of travel
mode = 'bike' # 'drive', 'bike', 'walk'
# find shortest path based on distance or time
optimizer = 'length' # 'length','time'
# create graph from OSM within the boundaries of some
# geocodable place(s)
graph = ox.graph_from_place(place, network_type = mode)
# find the nearest node to the start location
orig_node = ox.get_nearest_node(graph, start_latlng)
# find the nearest node to the end location
dest_node = ox.get_nearest_node(graph, end_latlng)
...
Заметьте, что функция get_nearest_node() принимает и кортеж широта — долгота, и объект geopy.point.Point. Вот самая короткая дистанция на велосипеде от отеля Hilton и до Golden Gateway Tennis & Swim Club в Сан-Франциско:
Отображение маркеров начальной и конечной точек
Карта станет понятнее, если отображать маркеры, указывающие начальную и конечную точки маршрутов. Чтобы отобразить маркер со всплывающим окном, можно воспользоваться классом Marker, о котором я рассказывал в предыдущей статье. Код ниже отображает два маркера: зелёный означает начальную точку, красный — конечную:
import folium
# Marker class only accepts coordinates in tuple form
start_latlng = (start_latlng[0],start_latlng[1])
end_latlng = (end_latlng[0],end_latlng[1])
start_marker = folium.Marker(
location = start_latlng,
popup = start_location,
icon = folium.Icon(color='green'))
end_marker = folium.Marker(
location = end_latlng,
popup = end_location,
icon = folium.Icon(color='red'))
# add the circle marker to the map
start_marker.add_to(shortest_route_map)
end_marker.add_to(shortest_route_map)
shortest_route_map
Класс Marker принимает координаты только в форме кортежа. А значит, чтобы кортеж содержал широту и долготу, нужно изменить start_lating и end_lating. Вот два маркера, показывающих начало и конец пути:
Отрисовка статичного графа
Ранее мы воспользовались plot_route_folium(), чтобы показать кратчайший путь на карте folium:
shortest_route_map = ox.plot_route_folium(graph, shortest_route)
shortest_route_map
Есть ещё одна интересная функция — plot_graph_route(), которая вместо вывода интерактивной карты чертит статический граф. Это полезно, когда вам нужно изображение с маршрутом между двумя точками. Следующий код выводит статичный граф для точек из предыдущего раздела статьи:
import osmnx as ox
import networkx as nx
ox.config(log_console=True, use_cache=True)
graph = ox.graph_from_place(place, network_type = mode)
orig_node = ox.get_nearest_node(graph, start_latlng)
dest_node = ox.get_nearest_node(graph, end_latlng)
shortest_route = nx.shortest_path(graph, orig_node, dest_node,
weight=optimizer)
fig, ax = ox.plot_graph_route(graph, shortest_route, save=True)
Вы увидите такой вывод:
Резюме
Надеюсь, эта статья вдохновит вас и вы используете её, чтобы создавать маршруты по достопримечательностям. Возможно, вам захочется позволить пользователям вводить их текущие местоположения и прокладывать маршруты до места назначения.
Ссылки
[1] Boeing, G. 2017. OSMnx: New Methods for Acquiring, Constructing, Analyzing, and Visualizing Complex Street Networks. Computers, Environment and Urban Systems 65, 126–139. doi:10.1016/j.compenvurbsys.2017.05.004
А мы поможем вам освоить надёжную профессию в IT с самого начала или развить ваши навыки:
Выбрать другую востребованную профессию.
Краткий каталог курсов и профессий
Data Science и Machine Learning
Python, веб-разработка
Мобильная разработка
Java и C#
От основ — в глубину
А также
gnome2_terminal_is_best
Думал, увижу алгоритм расчёта путей, на основе массива данных, а тут просто про использование очередной библиотеки. Куда делось настоящее программирование?
AigizK
Да давно уже все алгоритмы реализованы. Используйте готовые библиотеки /sarkazm_off
gnome2_terminal_is_best
Согласен, но на собеседованиях, очень любят спрашивать про настоящее программирование, когда на практике, придётся только с фреймворками работать, где всё уже есть в коробке.
RandomProgrammer
"Python" в тегах явно намекает на использование библиотеки)
gnome2_terminal_is_best
Ну, не только же с библиотеками работать? Хотя это тренд для 90% случаев.