{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Открытый курс по машинному обучению\n", "
Автор материала: Александровская Вера (@shlur), DS в Lamoda." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Рисуем интерактивные карты с Folium" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В питоне есть множество библиотек, с помощью которых можно рисовать и анализировать пространственную информацию (spatial analysis).\n", "\n", "Вот некоторые из них:\n", "\n", "* folium\n", "* gmaps\n", "* basemap\n", "* cartopy\n", "* geoplotlib\n", "\n", "\n", "В этом туториале пойдет речь о библиотеке Folium (https://github.com/python-visualization/folium), которая представляет собой питон-обертку над JS библиотекой Leaflet.\n", "\n", "\"Manipulate your data in Python, then visualize it in on a Leaflet map via Folium.\" (c) github\n", "\n", "Большим плюсом по сравнению с другими библиотеками является интерактивность. Карты можно зумить, исследовать, кликать на маркеры, создать сложные типы визуализации.\n", "Минусом является производительность. Создание карты с большим количеством точек может составлять минуты.\n", "\n", "К сожалению, информация по работе с библиотекой раскидана по разным сайтам и туториалам. В официальной документации информации очень мало, а в русскоязычном интернете вообще ничего нет.\n", "\n", "Будем исследовать датасет с пабами Москвы =). \n", "\n", "Датасет в виде списка пабов Москвы с их координатами выгружен с http://openstreetmap.ru" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Подготовка данных" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import json\n", "\n", "import folium\n", "import pandas as pd\n", "import requests\n", "\n", "with open('../../data/pubs.json') as json_data:\n", " d = json.load(json_data)\n", " \n", "columns = ['lat', 'lon', 'name_ru', 'opening_hours', 'website']\n", "index = range(0, len(d[\"data\"]))\n", "pubs = pd.DataFrame(columns = columns, index = index)\n", "\n", "for i in range(0, len(d[\"data\"])):\n", " pubs['lat'][i] = d[\"data\"][i][\"lat\"]\n", " pubs['lon'][i] = d[\"data\"][i][\"lon\"]\n", " pubs['opening_hours'].iloc[i] = d[\"data\"][i][\"opening_hours\"]\n", " pubs['website'][i] = d[\"data\"][i][\"website\"]\n", " pubs['name_ru'][i] = d[\"data\"][i][\"name_ru\"] " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pubs.head(3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Для центрирования карт я выбрала центральную точку Москвы\n", "\n", "kremlin = [55.750730, 37.617322]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dot density map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Самый простой вариант анализа - отобразить данные как точки (или маркеры) на карте. В popup (выноску) положим название заведения и часы работы.\n", "\n", "Карта инициализируется с помощью синтаксиса `map = folium.Map(location=kremlin, zoom_start=11)`.\n", "\n", "Добавляем маркеры `folium.Marker().add_to(map)`.\n", "\n", "Различные типы маркеров задаются функциями:\n", "\n", "- Marker (использую для визуализации ниже)\n", "- RegularPolygonMarker\n", "- CircleMarker\n", "- PolygonMarker\n", "\n", "Возможные атрибуты: \n", "\n", "- color\n", "- fill_color\n", "- weight\n", "- radius\n", "- number_of_sides (для RegularPolygonMarker)\n", "\n", "Так же в выноску можно передавать график vincent (https://github.com/wrobstory/vincent) с помощью синтаксиса: \n", "\n", "`folium.Popup().add_child(folium.Vega())`\n", "\n", "`tiles` - источник тайлов карт. Я обычно использую openstreetmaps - они идут по умолчанию." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pubs_map = folium.Map(location=kremlin, zoom_start=11)\n", "\n", "for i in range(0, len(pubs)):\n", " folium.Marker([pubs['lat'][i], pubs['lon'][i]], popup = str(pubs['name_ru'][i]) + \": \" \n", " + str(pubs['opening_hours'][i])).add_to(pubs_map)\n", " \n", "pubs_map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Cluster marker - раскраска в зависимости от плотности точек. Близкие точки сливаются в один маркер.\n", "\n", "Судя по данным, наибольшая плотность пабов в Москве - на серево-востоке от Кремля, в районе Чистых прудов." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from folium import features\n", "\n", "pubs_map = folium.Map(location=kremlin, zoom_start=12)\n", "mc = features.MarkerCluster()\n", "\n", "for i in range(0, len(pubs)):\n", " mk = features.Marker([pubs['lat'][i], pubs['lon'][i]])\n", " mc.add_child(mk)\n", " \n", "pubs_map.add_child(mc)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Heatmap" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Построим Heatmap распределения пабов по Москве" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import random\n", "\n", "import numpy as np\n", "from folium import plugins\n", "\n", "pubs_map = folium.Map(location=kremlin, zoom_start=10)\n", "\n", "data = [[x[0], x[1], 1] for x in np.array(pubs[['lat', 'lon']])]\n", "\n", "HeatMap(data, radius = 20).add_to(pubs_map)\n", "pubs_map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Линии" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Добавим на карту линии, соединяющую локации. Она создаётся с помощью folium.PolyLine, который принимает координаты соединяемых точек, и настраивается параметрами, схожими с параметрами маркеров.\n", "\n", "Здесь мы выбрали и соединили 4 заведения в Сокольниках:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sololniki = [55.791981, 37.664456]\n", "\n", "pubs_map_sokolniki = folium.Map(location=sololniki, zoom_start=13)\n", "path = []\n", "for i in [100, 101, 102, 103]:\n", " folium.Marker([pubs['lat'][i], pubs['lon'][i]], popup = str(pubs['name_ru'][i]) + \": \" \n", " + str(pubs['opening_hours'][i])).add_to(pubs_map_sokolniki)\n", " path.append([[pubs['lat'][i], pubs['lon'][i]], [pubs['lat'][i+1], pubs['lon'][i+1]]])\n", "\n", "folium.PolyLine(path[0:3], color='blue', weight=4, opacity=0.7, popup=str(i)).add_to(pubs_map_sokolniki)\n", "pubs_map_sokolniki " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Найдем и отобразим кратчайший путь через пабы Москвы :)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В заключении туториала, приведу пример отображения кратчайшего пути через пабы Москвы. Идея нагло украдена отсюда: http://www.math.uwaterloo.ca/tsp/pubs/\n", "\n", "Для нахождения кратчайшего пути использую библиотеку Google or-tools, которая включает в себя алгоритмы решения задач нахождения маршрута. Работа с ней это тема для отдельного туториала, поэтому загружаю найденное решение из внешнего файла.\n", "Пути между заведениями тут уже строятся не отрезками, соединяющими пары пабов, а следуют кратчайшему маршруту из паба в паб, подобранному с помощью http://project-osrm.org" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pickle\n", "\n", "# инициализируем карту\n", "map = folium.Map(location=kremlin, zoom_start=14)\n", "\n", "# грузим файл с данными о маршрутах\n", "file = open('../../data/tutorial_data.pickle', 'rb')\n", "routes = pickle.load(file,encoding='latin1')\n", "file.close()\n", "\n", "# Рисуем большой полилайн полного маршрута...\n", "for path in routes['paths']:\n", " folium.PolyLine(path, color='black', weight=3, opacity=0.8).add_to(map)\n", "# ...и маркеры пабов\n", "for point in routes['points']:\n", " folium.Marker(point).add_to(map)\n", "\n", "map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Have fun =)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1" } }, "nbformat": 4, "nbformat_minor": 2 }