Визуализация интерактивных карт

Визуализация интерактивных карт

@ai_machinelearning_big_data

Часто возникает проблема визуализации данных за несколько периодов времени. Те, кто уже пытался создавать карты знают, что эту проблему можно решить с помощью разных слоёв на карте.

Для визуализации интерактивных карт рассмотрим библиотеку - Folium.

Folium — это мощная библиотека визуализации данных в Python, которая была создана в первую очередь для того, чтобы помочь людям визуализировать гео-пространственные данные.

Folium - это библиотека с открытым исходным кодом, созданная на основе возможностей Datawrangling экосистемы.

С помощью Folium можно создать карту любого местоположения в мире, если вы знаете его значения широты и долготы.

Также можете создать карту и наложить маркеры, а также кластеры маркеров поверх карты для крутых и очень интересных визуализаций.

Для интерактивной визуальной аналитики - библиотеку Folium сначала нужно установить. В терминале прописываем:

pip install folium

Теперь можно импортировать библиотеку Folium.

import folium

# Import folium MarkerCluster plugin
from folium.plugins import MarkerCluster
# Import folium MousePosition plugin
from folium.plugins import MousePosition
# Import folium DivIcon plugin
from folium.features import DivIcon

print('Folium installed and imported!')

Folium installed and imported!

Создание карты мира с помощью Folium довольно просто. Вы просто вызываете функцию карты - folium.Map(), и это все.

# define the world map
world_map = folium.Map()

# display world map
world_map
Карта мира (увеличенный масштаб)

Что действительно интересно в картах, созданных Folium, так это то, что они являются интерактивными, поэтому можно увеличить или уменьшить масштаб после визуализации карты.

Карта мира

Стиль карты по умолчанию - OpenStreetMap (открытая карта улиц), которая показывает вид улицы области при масштабировании и показывает границы стран мира при масштабировании.

Карта мира, Стиль карты - OpenStreetMap (открытая карта улиц)

Теперь давайте создадим карту мира вокруг России и назовем ее - russia_map . Для этого мы передаем значения широты и долготы России с помощью параметра location (местоположения) и с Folium вы можете установить начальный уровень масштабирования с помощью параметра zoom start.

russia_map = folium.Map(
    location = [64.6863136, 97.7453061],    # широта и долгота России
    zoom_start = 4
)

# display russia map
russia_map
Карта мира вокруг России

zoom start - это начальный уровень масштабирования, т.к. легко можно изменить уровень масштабирования после отображения карты путем увеличения или уменьшения масштаба.

Еще одна удивительная особенность Folium заключается в том, что можно создавать различные стили карт с помощью параметра tiles

Давайте создадим стиль 'Stamen Toner’ карты мира вокруг России. Это высококонтрастные черно-белые карты.

Этот стиль отлично подходит для визуализации и изучения речных меандров (изгибы русла реки) и прибрежных зон.

russia_map = folium.Map(
    location = [64.6863136, 97.7453061],   # широта и долгота России
    zoom_start = 4,
    tiles = 'Stamen Toner'
)

# display russia map
russia_map
Стиль 'Stamen Toner’ карты мира вокруг России
Стиль 'Stamen Toner’ карты мира вокруг России (увеличенный масштаб)

Другой стиль - 'Stamen Terrain’ (тиснение местности). Этот стиль отлично подходит для визуализации затенения холмов и природных цветов растительности.

russia_map = folium.Map(
    location = [64.6863136, 97.7453061],      # широта и долгота России
    zoom_start = 4,
    tiles = 'Stamen Terrain'
)

# display russia map
russia_map
Стиль 'Stamen Terrain’ карты мира вокруг России

Это карты с затенением холмов и цветами естественной растительности. Они демонстрируют усовершенствованную маркировку и обобщение линий дорог с двусторонним движением.

Стиль 'Stamen Terrain’ карты мира вокруг России (увеличенный масштаб)

Как накладывать маркеры поверх карты для интересных визуализаций?

Давайте посмотрим, как мы можем добавить красный круговой знак в центр Санкт-Петербурга. Для этого нам нужно создать так называемую группу объектов под названием Saint_Petersburg.

Эта группа объектов пуста, а это значит, что дальше — нужно создать так называемые дочерние элементы (child) и добавлять их в группу объектов.

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

И как только мы закончим добавление дочерних элементов (child) в группу объектов, мы добавим выбранную группу на карту -

красный круговой знак наложен на верхней части карты и добавлен в центр города Санкт-Петербурга.

# generate map of Russia  (составить карту россии)
russia_map = folium.Map(
    location = [64.6863136, 97.7453061],    # широта и долгота России
    zoom_start = 4
)

# add a red marker to Saint Petersburg
# create a feature group (создать группу функций)
saint_petersburg = folium.map.FeatureGroup()

# style the feature group (стиль группы объектов)
saint_petersburg.add_child(
    folium.features.CircleMarker(
        [59.938732, 30.316229], radius = 5,  # широта и долгота Санкт-Петербурга
        color = 'red', fill_color = 'Red'
    )
)

# add the feature group to the map  (добавить группу объектов на карту)
russia_map.add_child(saint_petersburg)

# display russia map
russia_map
красный круговой знак наложен на верхней части карты и добавлен в центр города Санкт-Петербурга.

Давайте, теперь подпишем красный круговой знак: - 'Санкт-Петербург, в разговорной речи - Пи́тер, сокр. - СПб’. Для этого мы просто используем функцию маркера и параметр всплывающего окна, чтобы передать любой текст, который мы хотим добавить к этому маркеру.

# generate map of Russia
russia_map = folium.Map(
    location = [64.6863136, 97.7453061],    # широта и долгота России
    zoom_start = 4
)

# add a red marker to Saint Petersburg
# create a feature group
saint_petersburg = folium.map.FeatureGroup()

# style the feature group
saint_petersburg.add_child(
    folium.features.CircleMarker(
        [59.938732, 30.316229], radius = 5,    # широта и долгота Санкт-Петербурга
        color = 'red', fill_color = 'Red'
    )
)

# add the feature group to the map
russia_map.add_child(saint_petersburg)

# label the Marker (пометить маркер)
folium.Marker([59.938732, 30.316229],         # широта и долгота Санкт-Петербурга
popup = 'Санкт-Петербург, в разговорной речи - Пи́тер, сокр.- СПб').add_to(russia_map)

# display russia map
russia_map
подпись 'Санкт-Петербург, в разговорной речи - Пи́тер, сокр. - СПб’ к маркеру красный круговой знак
подпись 'Санкт-Петербург, в разговорной речи - Пи́тер, сокр. - СПб’ к маркеру красный круговой знак (увеличенный масштаб), cтиль карты - OpenStreetMap

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

# Add Mouse Position to get the coordinate (Lat, Long) for a mouse over on the map
formatter = "function(num) {return L.Util.formatNum(num, 5);};"
mouse_position = MousePosition(
    position='topright',
    separator=' Long: ',
    empty_string='NaN',
    lng_first=False,
    num_digits=20,
    prefix='Lat:',
    lat_formatter=formatter,
    lng_formatter=formatter,
)

russia_map.add_child(mouse_position)
russia_map
MousePosition (координаты показаны в верхнем левом углу)

Можно рассчитать расстояние между двумя точками на карте на основе их значений широты и долготы, используя следующий метод:

from math import sin, cos, sqrt, atan2, radians

def calculate_distance(lat1, lon1, lat2, lon2):
    # approximate radius of earth in km
    R = 6373.0

    lat1 = radians(lat1)
    lon1 = radians(lon1)
    lat2 = radians(lat2)
    lon2 = radians(lon2)

    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
    c = 2 * atan2(sqrt(a), sqrt(1 - a))

    distance = R * c
    return distance

Отметим точку на Дворцовом мосту с помощью MousePosition и вычислим расстояние от нашего красного круглого маркера до Дворцового моста.

#Distance to Palace Bridge  Дворцовый мост
coordinates = [
    [59.93876, 30.31623],           # координаты красного круглого маркера
    [59.94022, 30.30931]]           # координаты дворцового моста

lines=folium.PolyLine(locations=coordinates, weight=1)
russia_map.add_child(lines)
distance = calculate_distance(coordinates[0][0], coordinates[0][1],
                              coordinates[1][0], coordinates[1][1])
distance_circle = folium.Marker(
    [59.94022,30.30931],
    icon=DivIcon(
        icon_size=(20,20),
        icon_anchor=(0,0),
        html='<div style="font-size: 12; 
      color:#252526;"><b>%s</b></div>' % "{:10.2f} KM".format(distance),
        )
    )
russia_map.add_child(distance_circle)
russia_map
0.42 км - расстояние от нашего красного круглого маркера до Дворцового моста

Отметим точку на Литейном мосту с помощью MousePosition и вычислим расстояние от нашего красного круглого маркера до Литейного моста.

#Distance to Foundry bridge   Литейный мост
coordinates = [
    [59.93876, 30.31623],       # координаты красного круглого маркера
    [59.94981, 30.34906]]       # координаты литейного моста

lines=folium.PolyLine(locations=coordinates, weight=1)
russia_map.add_child(lines)
distance = calculate_distance(coordinates[0][0], coordinates[0][1],
                              coordinates[1][0], coordinates[1][1])
distance_circle = folium.Marker(
    [59.94981,30.34906],
    icon=DivIcon(
        icon_size=(20,20),
        icon_anchor=(0,0),
        html='<div style="font-size: 12; 
      color:#252526;"><b>%s</b></div>' % "{:10.2f} KM".format(distance),
        )
    )
russia_map.add_child(distance_circle)
russia_map
2.20 км - расстояние от нашего красного круглого маркера до Литейного моста.


Если заглянуть в код, то можно увидеть, что именно строка folium.LayerControl().add_to(m) позволяет добавить на карту возможность переключения между слоями, но об этом чуть позже.

Предположу, что есть некоторые данные о количестве плохих и хороших отзывов для каждой из точек сети ресторанов ХХХ. Нужно понять, какие оценки получают рестораны сети от гостей, посетивших эти заведения. Сделаю карту, на которой нарисую несколько точек и раскрашу по цветам, в зависимости от оценки, где зелёный цвет – отлично, жёлтый – хорошо, все остальные красным цветом.

В таблице обязательно должны присутствовать данные о ресторанах: координаты, оценка, идентификатор. Дополнительную информацию, такую как адрес, директор и другую информацию можно вынести в подсказки (tooltip или popup, подробнее о них можно почитать в других публикациях).

Подгружу данные.

data_01=pd.read_excel('test.xlsx')

Создам карту и нанесу на неё точки:

map = folium.Map(location=[55.000,57.000], zoom_start = 4, tiles = ‘CartoDB position’)
lat  = data[‘LAT’]
lon = data[‘LON’]
name = data[‘name’]
mes = data[‘mes’]
for lat, lon, name, mes in zip(lat, lon, name, mes):
 marker = folium.CircleMarker( location = [lat, lon], 
    tooltip = (‘Доля положительных отзывов:’+ mes+’.’+name) 
    fill_opacity = 0.6, 
    radius = 7,
    fill_color = color_change(mes),
    color = ‘mes’).add_to(layer)

Получаю карту, на которой можно видеть точки, окрашенные в определенный цвет:

Choropleth. Несколько карт в одной.

Данная карта уже отражает нужную информацию, но её можно сделать более читабельной с помощью создания карты Chrolopleth, тогда можно будет понимать ситуацию не только по конкретным точкам, но и по региону в целом.

Существует несколько способов создания таких карт: функции Chrolopleth и GeoJson.

Создам карту и подгружу данные о границах некоторых регионов России:

map = folium.Map(location=[55.000,57.000], zoom_start = 4, tiles = ‘CartoDB position’)
url = “formap.geojson”
df = geopandas.read_file(url)

Создать choropleth можно двумя способами:

Первый способ.

Choropleth = folium.Choropleth(
  data = regions_color,
  columns=[“cartodb_id”, “mes”],
  key_on=’feature.properties.cartodb_id’,
  fill_opacity=0.9,
  line_opacity=0.7,
  geo_data=regions,  
  fill_color = ‘YlGn’,
  name = ‘Choropleth2’).add_to(m) 

Где regions – это датасет с границами регионов, regions_color — датасет с данными по отзывам.

Обязательно должен быть уникальный идентификатор региона в обоих файлах.

Второй способ.

itog = folium.GeoJson(
  full_merge,
  style_function=lambda x: {
   “fillColor”: colormap(x[‘properties’][“compl”])
   if  x[‘properties’][“compl”] is not None
   else “transparent”,
   “color”: “black”,  
   “fillOpacity”: 0.7},
  tooltip=tooltip,
  name=”ИТОГ”).add_to(map)

Full_merge файл, где по идентификатору региона объединены два моих файла с данными.

Попробую построить карты на основе данных за 2022 год.

По такой карте можно понять, в каком регионе гости не удовлетворены обслуживанием, а значит стоит обратить особое внимание.

Есть небольшой минус в первом случае — невозможно самостоятельно выбрать 3 цвета и необходимо использовать системные настройки цветов.

Во втором случае создаю палитру цветов. Вот таким образом:

Colormap = branca.colormap.LinerColormap(
 Vmin=4.00,
 Vmax=5.00,
 caption = “Выполнение плана”,
 colors = [“red”, “yellow”, “green”])

Задам максимальное и минимальное значение самостоятельно. То есть всё, что равно максимальному значению и больше, окрашивается в зелёный, значения попадающие в промежуток – жёлтым и все остальные – красным. Удобство такого способа состоит в том, что можно самостоятельно делать настройки цвета. Выбирать при каких обстоятельствах появляется тот или иной цвет, а также назначить даже розовый или чёрный, если нужно как-то по-особенному выделить.

Можно заметить, что карты похожи, однако, минус функции choropleth не только в ограничениях по палитре цветов, но и в том, что при попытке создания нескольких слоёв появляется несколько подсказок сверху, что значительно ухудшает внешний вид, ведь достаточно одной.

Воспользовавшись функцией GeoJson, сделаю несколько слоёв раскрасив регионы России и получу результат:

Справа есть панель, с помощью которой буду переключать слои, выбирая нужный год. Каждый год – это отдельная choropleth карта, но в данном случае, я объединила их в одну.

Слои создам таким образом:

y2022= folium.GeoJson(
  full_merge,
  style_function=lambda x: {
   “fillColor”: colormap(x[‘properties’][“compl”])
   if  x[‘properties’][“compl”] is not None
   else “transparent”,
   “color”: “black”,  
   “fillOpacity”: 0.7},
  tooltip=tooltip,
  name=”2022”).add_to(map)
for lat, lon, name, mes in zip(lat, lon, name, mes):
 marker = folium.CircleMarker( location = [lat, lon], 
    tooltip = (‘Доля положительных отзывов:’+ mes+’.’+name) 
    fill_opacity = 0.6, 
    radius = 7,
    fill_color = color_change(mes),
    color = ‘mes’).add_to(y2022)
colormap.add_to(map)
folium.LayerControl(collapsed=False).add_to(map)

Слой – это непосредственно geojson функция, которая создает карту choropleth, далее добавляю точки на этот слой. Также наношу на карту подсказку и добавляю панель, которая будет переключать слои.

DualMap – что это за карта и зачем нужна?

Допустим, человеку необходимо знать и данные по территории и данные по конкретной точке. Очевидный вариант решения – две карты, так как если сделать все на одной, то это будет нечитабельно.

Однако, две карты могут отражаться на одной html странице, именно такую возможность предоставляем dualMap функция. Попробую сделать на моих данных:

  1. Создам основную DualMap карту m
  2. Далее, если нужно что-то добавить на одну карту – пишу add_to(m.m1), если хочу добавить на вторую — add_to(m.m2).

Если написать просто add_to(m), то данные добавятся на обе карты, и они будут полностью идентичными.

  1. Создаю карту и подтягиваю необходимые данные:
map = folium.DualMap(location=[55.000,57.000], zoom_start = 4, tiles = ‘CartoDB position’)
url = “formap.geojson”
df = geopandas.read_file(url)

Добавляю необходимые слои на обе карты.

layer= folium.GeoJson(
  full_merge,
  style_function=lambda x: {
   “fillColor”: colormap(x[‘properties’][“compl”])
   if  x[‘properties’][“compl”] is not None
   else “transparent”,
   “color”: “black”,  
   “fillOpacity”: 0.7},
  tooltip=tooltip,
  name=”2022”).add_to(m.m1)

И получаю следующее:

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

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

Существует множество функций, и каждый найдёт то, что ему по душе. Конечно же, библиотека folium имеет некоторые ограничения. Вспомнить, например, ту же функцию choropleth, но их можно обходить при помощи других функций.

Кроме того, даже на этом возможности folium карт не заканчиваются. Можно также добавить текст (заголовок, описание, общая статистка, но придется использовать html и css код). Или в подсказки можно вставить графики (использовать pandas). Вариантов масса, осталось только попробовать.



Report Page