Написание небольших, но функциональных приложений — это интересный и полезный способ научиться чему-то новому в области разработки программного обеспечения.
В руководстве рассмотрим веб-приложение на Django, объединяющее картографию с маршрутизацией: создадим веб-сайт с отображением кратчайшего маршрута между двумя точками, выбираемыми пользователем через щелчки мышью или тапы по интерактивной онлайн-карте.
Содержание:
- Схема веб-приложения.
- Пакет Folium и библиотека Leaflet.
- Технология OSRM и основы OSRM API.
- Конфигурация Django и веб-приложения.
- Выводы.
1. Схема веб-приложения
Приблизительная схема работы веб-приложения:
- Сначала посетителю веб-страницы показывается карта.
- Каждый раз, когда пользователь выбирает два места на карте через щелчок мышью, координаты отправляются на сервер.
- Затем координаты передаются по API для построения маршрутов. Ответ ожидается сервером в закодированном виде.
- После получения сервером ответ декодируется и отображается на веб-странице.
Следовательно, для разработки веб-приложения с интерактивной картой и функционалом построения географических маршрутов понадобится предварительно установить следующее программное обеспечение:
- Django, Folium, Requests, Polyline.
- Leaflet: допустимо подключение через CDN, устанавливать JavaScript-библиотеку не нужно.
Конечно, для решения задачи потребуется немного JavaScript, ведь Django — это серверная технология. Нужно запустить программу на стороне клиента, чтобы сначала получить координаты через пользовательское нажатие по карте, а затем — отправить эти координаты на Python-сервер.
2. Пакет Folium и библиотека Leaflet
Рассмотрим вышеперечисленные требования подробнее.
Folium
Folium весьма полезен в построении географических карт на Python, ведь он опирается одновременно и на сильные стороны экосистемы Python в работе с данными, и на сильные стороны библиотеки Leaflet в создании интерфейсов с интерактивными картами. Пакет работает на основе концепции первоначального манипулирования данными в Python, с последующей их визуализацией на карте Leaflet через Folium.
Folium позволяет легко визуализировать на интерактивной карте Leaflet предварительно обработанные на Python-сервере данные. Пакет содержит ряд встроенных наборов тайлов от провайдеров OpenStreetMap, Mapbox и Stamen, а также поддерживает пользовательские наборы тайлов с ключами API Mapbox или Cloudmade.
Параметры интерактивной карты поддаются тонкой настройке, такие как масштаб, слой, маркер и подобные.
За подробностями проследуйте по ссылке на документацию Folium.
Leaflet
Leaflet — это лидирующая библиотека для интерактивных карт с открытым исходным кодом, написанная на JavaScript и удобная даже на мобильных устройствах. Она весит всего 39 КБ JS, но обладает всеми возможностями картографии, которые только могут понадобиться в разработке.
Leaflet создан с учетом простоты, производительности и удобства в применении. Он эффективно работает на всех основных настольных и мобильных платформах, расширяется плагинами, располагает красивым, простым в использовании, хорошо документированным API и читабельным исходным кодом.
3. Технология OSRM и основы OSRM API
Open Source Routing Machine или OSRM — высокопроизводительный механизм маршрутизации для поиска кратчайшего пути в дорожных сетях, реализованный на C++.
OSRM сочетает сложные алгоритмы маршрутизации с открытыми и бесплатными данными дорожных сетей проекта OpenStreetMap (OSM). Вычисление кратчайшего пути в сети континентального размера может занимать до нескольких секунд, если оно выполняется без так называемой техники ускорения.
Благодаря алгоритму contraction hierarchies OSRM способен вычислить и вывести кратчайший путь между любыми двумя местами за несколько миллисекунд, при этом чистое вычисление маршрута занимает гораздо меньше времени. Большая часть усилий тратится на аннотирование маршрута и передачу геометрии по сети.
Для получения маршрутов воспользуемся OSRM API. Конечно, маршрутизацию можно выполнить на локальной машине без внешнего API, но такая реализация крайне требовательна к вычислительной мощности и памяти, поэтому выбираем стороннее API для построения маршрутов.
Давайте запросим маршрутизацию OSRM через API, перейдя по соответствующему URL: передайте координаты отправной точки и места назначения как дополнительные параметры.
'http://router.project-osrm.org/route/v1/driving/79.81039,12.00679;80.28150,13.08195?alternatives=true&geometries=polyline'
При разработке на Python для взаимодействия с API через URL доступна библиотека requests:
import requests
import json
route_url='http://router.project-osrm.org/route/v1/driving/79.81039,12.00679;80.28150,13.08195?alternatives=true&geometries=polyline'
r=requests.get(route_url)
res=r.json()
print(res)
Полученный через запрос по API ответ выглядит следующим образом:
{'code': 'Ok',
'routes': [{'distance': 163945.4,
'duration': 7840.1,
'geometry': '}ihhAoxbfNmUb|Bdv@dTewCftBm~Hb~AkjBneBc{BgLiaSpsN_hCq`@ktE{wDodI{~N{lI_{A}sD}fEceHseCs_LiiIqdYcvGkQa{BcgDzDccHauC{wa@o_VscDnIigB_wB{lJsw@koB_}Bk~B_Oe}CarHib@e_O',
'legs': [{'distance': 163945.4,
'duration': 7840.1,
'steps': [],
'summary': '',
'weight': 7915.3}],
'weight': 7915.3,
'weight_name': 'routability'},
{'distance': 149325.7,
'duration': 8733.4,
'geometry': '}ihhAoxbfN{vAbcAmlF}pAhWaeKy_XkxKmzDufEmaKmbAsnDkyC|LmjAc`NyzKi~HwrC_zGoh@qpD_~EeiAl}@ciKwAs_FaoD_mV{{B{pNkvFkjUaLa_PynDy_BspByzHqW',
'legs': [{'distance': 149325.7,
'duration': 8733.4,
'steps': [],
'summary': '',
'weight': 8733.4}],
'weight': 8733.4,
'weight_name': 'routability'}],
'waypoints': [{'distance': 177.818519,
'hint': 'sHXvg_p174MAAAAASgAAAAAAAACYAAAAAAAAAMQbo0EAAAAATAUpQgAAAABKAAAAAAAAAJgAAAAE5QAA8cvBBNc6twBWz8EEhjW3AAAATxZtVXiL',
'location': [79.809521, 12.008151],
'name': ''},
{'distance': 0.774596,
'hint': 'x11XgP___38IAAAADQAAAAoAAAAXAAAAw4KaQaeYIkHumMJBQlhaQggAAAANAAAACgAAABcAAAAE5QAAof_IBFmdxwCc_8gEXp3HAAEA7w1tVXiL',
'location': [80.281505, 13.081945],
'name': 'Muthuswamy Road'}]}
В ответе содержатся расстояние, геометрия, местоположение и другая информация. Что нам с этого?
Давайте посмотрим на геометрию, она определена примерно так:
‘}ihhAoxbfNmUb|Bdv@dTewCftBm~Hb~AkjBneBc{BgLiaSpsN_hCq`@ktE{wDodI{~N{lI_{A}sD}fEceHseCs_LiiIqdYcvGkQa{BcgDzDccHauC{wa@o_VscDnIigB_wB{lJsw@koB_}Bk~B_Oe}CarHib@e_O’
Сразу видно, что геометрия закодирована в специальном картографическом формате polylines. Чтобы получить координаты в обычном формате широты и долготы, нужно декодировать ответ.
Примените метод polyline.decode()
следующим образом:
routes = polyline.decode(res[‘routes’][0][‘geometry’])
Метод возвращает список из двух координат, lat
и lng
. Теперь довольно легко нарисовать маршрут на карте через метод folium.polyline()
.
Благодаря детальной официальной документации ознакомиться детальнее с возможностями OSRM API можно в любой момент.
4. Конфигурация Django и веб-приложения
Напишем серверное приложение для управления запросами на веб-фреймворке Python Django, ссылка на полный код указана в самом конце руководства
- Создайте новый Django-проект и новое Django-приложение:
django-admin startproject show_route
python manage.py startapp route
- Следом отредактируйте файл
settings.py
: добавьте в него путь к директориям приложений и шаблонов. - Затем создайте новый файл под названием
getroute.py
в директории приложенияroute
. В этом файле напишите функцию вызова OSRM API, принимающую в качестве параметров ширину и долготу. Помимо прочего, функция должна декодировать ответ для его возврата в виде списка.
import requests
import json
import polyline
import folium
def get_route(pickup_lon, pickup_lat, dropoff_lon, dropoff_lat):
loc = "{},{};{},{}".format(pickup_lon, pickup_lat, dropoff_lon, dropoff_lat)
url = "http://router.project-osrm.org/route/v1/driving/"
r = requests.get(url + loc)
if r.status_code!= 200:
return {}
res = r.json()
routes = polyline.decode(res['routes'][0]['geometry'])
start_point = [res['waypoints'][0]['location'][1], res['waypoints'][0]['location'][0]]
end_point = [res['waypoints'][1]['location'][1], res['waypoints'][1]['location'][0]]
distance = res['routes'][0]['distance']
out = {'route':routes,
'start_point':start_point,
'end_point':end_point,
'distance':distance
}
return out
Настал этап определения контроллера согласно паттерну Model-View-Controller, Модель-Представление-Контроллер. В Django паттерн MVC реализован через сопоставление Model-Template-View, Модель-Шаблон-Представление.
- Создайте файл представлений
views.py
, в нем определите функцию-представление с идентификаторомshowmap
. Данное представление непосредственно передает браузеру пользователя файлshowmap.html
.
import folium
from django.shortcuts import render,redirect
from . import getroute
def showmap(request):
return render(request,'showmap.html')
def showroute(request,lat1,long1,lat2,long2):
figure = folium.Figure()
lat1,long1,lat2,long2=float(lat1),float(long1),float(lat2),float(long2)
route=getroute.get_route(long1,lat1,long2,lat2)
m = folium.Map(location=[(route['start_point'][0]),
(route['start_point'][1])],
zoom_start=10)
m.add_to(figure)
folium.PolyLine(route['route'],weight=8,color='blue',opacity=0.6).add_to(m)
folium.Marker(location=route['start_point'],icon=folium.Icon(icon='play', color='green')).add_to(m)
folium.Marker(location=route['end_point'],icon=folium.Icon(icon='stop', color='red')).add_to(m)
figure.render()
context={'map':figure}
return render(request,'showroute.html',context)
- В директории шаблонов Django напишите HTML-шаблон showmap.html, не забудьте указать в нем CDN для подключения JavaScript-библиотеки Leaflet:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Map to find route</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<!-- Load Leaflet from CDN -->
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossorigin=""></script>
<!-- Load Esri Leaflet from CDN -->
<script src="https://unpkg.com/[email protected]/dist/esri-leaflet.js"
integrity="sha512-ucw7Grpc+iEQZa711gcjgMBnmd9qju1CICsRaryvX7HJklK0pGl/prxKvtHwpgm5ZHdvAil7YPxI1oWPOWK3UQ=="
crossorigin=""></script>
<!-- Load Esri Leaflet Geocoder from CDN -->
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/esri-leaflet-geocoder.css"
integrity="sha512-IM3Hs+feyi40yZhDH6kV8vQMg4Fh20s9OzInIIAc4nx7aMYMfo+IenRUekoYsHZqGkREUgx0VvlEsgm7nCDW9g=="
crossorigin="">
<script src="https://unpkg.com/[email protected]/dist/esri-leaflet-geocoder.js"
integrity="sha512-HrFUyCEtIpxZloTgEKKMq4RFYhxjJkCiF5sDxuAokklOeZ68U2NPfh4MFtyIVWlsKtVbK5GD2/JzFyAfvT5ejA=="
crossorigin=""></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<style>
body { margin:0; padding:0; }
#map { position: absolute; top:0; bottom:0; right:0; left:0; }
</style>
</head>
<body>
<div id="map"></div>
<script>
var map = L.map('map').setView([11,79], 10);
data={};
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
var gcs = L.esri.Geocoding.geocodeService();
var count=0;
map.on('click', (e)=>{
count+=1;
gcs.reverse().latlng(e.latlng).run((err, res)=>{
if(err) return;
L.marker(res.latlng).addTo(map).bindPopup(res.address.Match_addr).openPopup();
k=count.toString()
data[k+'lat']=res.latlng['lat'];
data[k+'lon']=res.latlng['lng'];
if(count==2){
const route_url='http://localhost:8000/'+data['1lat']+','+data['1lon']+','+data['2lat']+','+data['2lon'];
count=0;
window.location.replace(route_url);
}
});
});
</script>
</body>
</html>
Для просчета маршрута необходимо знать две точки местоположения, поэтому JavaScript-переменная count
настроена для ожидания двух событий щелчка мыши. Если у вас есть предложения по альтернативной реализации JavaScript части приложения, то не стесняйтесь упоминать их в комментариях!
- Файл шаблона
showroute.html
просто отобразит контекст, предоставленный бэкендом Django.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Route between the locations</title>
{{map.header.render|safe}}
</head>
<body>
{% if map %}
{{map.html.render|safe}}
<script>
{{map.script.render|safe}}
</script>
{% endif %}
</body>
</html>
- Наконец, нужно сопоставить функции представления с URL. Маршрутизация запросов веб-приложения (не имеет отношения к построению маршрутов) происходит в специальном файле
urls.py
,
в приведенном ниже примере<str:val>
предназначен для передачи строковых значений через URL.
from django.contrib import admin
from django.urls import path
from route.views import showroute,showmap
urlpatterns = [
path('admin/', admin.site.urls),
path('<str:lat1>,<str:long1>,<str:lat2>,<str:long2>',showroute,name='showroute'),
path('',showmap,name='showmap'),
]
Выводы
Оцените результат ваших трудов после выполнения всех шагов руководства!
Располагаясь в директории проекта Django, введите в командную строку следующую команду:
python manage.py runserver
- Для ознакомления с полным кодом веб-приложения перейдите по ссылке на репозиторий GitHub.
Поздравляем, вы успешно выполнили руководство! Сегодня вы реализовали только часть полноценного проекта, поэтому пока что в нем только одна функция. Как приложение — отлично подойдет для любого проекта Django с функционалом маршрутизации.
Читайте также:
- 4 совета Python Django разработчику
- Django Google SEO: поисковая оптимизация сайта на Python для индексации в Google
- Django REST Framework: REST API на Python с нуля
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Rajavel M: Django Web App for Plotting the Route between Two Points in a Map