paint-brush
Как я использовал Python и Folium для визуализации своих занятий на свежем воздухек@lukaskrimphove
7,707 чтения
7,707 чтения

Как я использовал Python и Folium для визуализации своих занятий на свежем воздухе

к Lukas Krimphove14m2023/09/06
Read on Terminal Reader
Read this story w/o Javascript

Слишком долго; Читать

Отправляйтесь в исследовательскую и картографическую экспедицию! Узнайте, как вдохнуть жизнь в файлы GPX и создавать интерактивные карты с помощью Folium.
featured image - Как я использовал Python и Folium для визуализации своих занятий на свежем воздухе
Lukas Krimphove HackerNoon profile picture
0-item

Я мечтаю совершить поход из Мюнхена в Венецию и совершить путешествие по прекрасным Альпам. Но поскольку мне все еще приходится жить своей повседневной жизнью, мое путешествие должно состоять из нескольких этапов, с неделями, месяцами или даже годами между каждым приключением. Это нормально, поскольку речь идет больше о путешествии, чем о пункте назначения. Однако мне всегда хотелось иметь возможность визуально проследить эти пути, чтобы увидеть, как далеко я продвинулся и насколько близок к своей цели. Мне нужен был способ отпраздновать достигнутый мной прогресс и мотивировать себя двигаться дальше.


К счастью, многие приложения для активного отдыха и спорта, такие как Adidas Running, Komoot, Strava и другие, любезно позволяют нам экспортировать нашу деятельность в файлы GPX. Однако эти файлы GPX некуда девать.


Именно здесь в игру вступают Python и Folium . Недавно я наткнулся на Folium, мощную библиотеку Python для создания интерактивных карт. Он может легко включать географические данные, такие как файлы GPX, и позволяет настраивать и исследовать. Опираясь на собранные мною данные GPS, я начал экспериментировать с Folium, чтобы создать карту, которая оживит мои экскурсии на свежем воздухе.


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

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


Давайте отправимся в исследовательскую и картографическую экспедицию!

Начало работы с блокнотом Jupyter

Чтобы начать это приключение, мы будем использовать Jupyter Notebook. Почему блокнот Jupyter? Это фантастическая интерактивная вычислительная среда, которая позволяет нам комбинировать код, визуализацию и текст, что делает ее идеальной для экспериментов с нашими данными и библиотекой Folium.


Если вы еще не установили Jupyter Notebook, следуйте инструкциям на их официальном сайте . После установки вы можете создать новый блокнот Jupyter и подготовиться к путешествию.

Анализ файлов GPX на предмет данных следа

Далее нам понадобится исходный материал для нашей карты — файлы GPX. GPX (формат обмена GPS) — это широко используемый формат файлов, в котором хранятся данные о местоположении, такие как широта, долгота, высота над уровнем моря и время, что делает его идеальным для отслеживания активности на свежем воздухе.


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


В нашем коде Python мы будем использовать библиотеку gpxpy для анализа файлов GPX и извлечения основных данных следа, таких как широта, долгота, высота, скорость и расстояние. Функция parse_gpx() сделает за нас всю тяжелую работу:


 ### READ GPX FILES import gpxpy import pandas as pd import numpy as np import haversine as hs from pathlib import Path def parse_gpx(file_path): # parse gpx file to pandas dataframe gpx = gpxpy.parse(open(file_path), version='1.0') data = [] points = [] for track in gpx.tracks: for segment in track.segments: for point_idx, point in enumerate(segment.points): points.append(tuple([point.latitude, point.longitude])) # calculate distances between points if point_idx == 0: distance = np.nan else: distance = hs.haversine( point1=points[point_idx-1], point2=points[point_idx], unit=hs.Unit.METERS ) data.append([point.longitude, point.latitude,point.elevation, point.time, segment.get_speed(point_idx), distance]) columns = ['Longitude', 'Latitude', 'Elevation', 'Time', 'Speed', 'Distance'] gpx_df = pd.DataFrame(data, columns=columns) return points, gpx_df activities = {} frames = [] for activity_type in ACTIVITY_TYPES: activities[activity_type] = {} pathlist = Path(activity_type).glob('**/*.gpx') for path in pathlist: # exclude hidden directories and files # will lead to performance issues if there are lots of hidden files if any(part.startswith('.') for part in path.parts): continue activity_group = path.parts[1] activity_name = path.parts[2] if activity_group not in activities[activity_type]: activities[activity_type][activity_group] = [] points, gpx_df = parse_gpx(path) gpx_df['Elevation_Diff'] = np.round(gpx_df['Elevation'].diff(), 2) gpx_df['Cum_Elevation'] = np.round(gpx_df['Elevation_Diff'].cumsum(), 2) gpx_df['Cum_Distance'] = np.round(gpx_df['Distance'].cumsum(), 2) gpx_df['Gradient'] = np.round(gpx_df['Elevation_Diff'] / gpx_df['Distance'] * 100, 1) activities[activity_type][activity_group].append({ 'name': activity_name.replace('.gpx', '').replace('_', ' '), 'points': points, 'gpx_df': gpx_df }) frames.append(gpx_df) df = pd.concat(frames) df

В результате у нас остаются все необходимые данные о деятельности: список всех координат GPS и кадр данных Pandas, содержащий все виды показателей.

Нанесение маршрутов GPX на интерактивную карту

Теперь, когда наши данные GPS организованы в фрейме данных Pandas, мы можем визуализировать наши занятия на свежем воздухе на интерактивной карте. Folium упрощает эту задачу:


 ### CONFIG # LOCATION = None LOCATION = [48.13743, 11.57549] # latitude, longitude ZOOM_START = 10 ACTIVITY_TYPES = { 'Hiking': { 'icon': 'person-hiking', 'color': 'green' }, 'Running': { 'icon': 'person-running', 'color': 'orange' }, 'Biking': { 'icon': 'person-biking', 'color': 'red' }, 'Skiing': { 'icon': 'person-skiing', 'color': 'blue' } }
  • Мы создадим карту с центром в определенном месте (вы можете выбрать одно или позволить коду определить центр на основе ваших данных).
  • Мы назначим уникальные цвета и значки для каждого типа занятий, например пешего туризма, бега, езды на велосипеде или катания на лыжах. В этом нам поможет словарь ACTIVITY_TYPES.
 ### READ GPX FILES import gpxpy import pandas as pd import numpy as np import haversine as hs from pathlib import Path def parse_gpx(file_path): # parse gpx file to pandas dataframe gpx = gpxpy.parse(open(file_path), version='1.0') data = [] points = [] for track in gpx.tracks: for segment in track.segments: for point_idx, point in enumerate(segment.points): points.append(tuple([point.latitude, point.longitude])) # calculate distances between points if point_idx == 0: distance = np.nan else: distance = hs.haversine( point1=points[point_idx-1], point2=points[point_idx], unit=hs.Unit.METERS ) data.append([point.longitude, point.latitude,point.elevation, point.time, segment.get_speed(point_idx), distance]) columns = ['Longitude', 'Latitude', 'Elevation', 'Time', 'Speed', 'Distance'] gpx_df = pd.DataFrame(data, columns=columns) return points, gpx_df activities = {} frames = [] for activity_type in ACTIVITY_TYPES: activities[activity_type] = {} pathlist = Path(activity_type).glob('**/*.gpx') for path in pathlist: # exclude hidden directories and files # will lead to performance issues if there are lots of hidden files if any(part.startswith('.') for part in path.parts): continue activity_group = path.parts[1] activity_name = path.parts[2] if activity_group not in activities[activity_type]: activities[activity_type][activity_group] = [] points, gpx_df = parse_gpx(path) gpx_df['Elevation_Diff'] = np.round(gpx_df['Elevation'].diff(), 2) gpx_df['Cum_Elevation'] = np.round(gpx_df['Elevation_Diff'].cumsum(), 2) gpx_df['Cum_Distance'] = np.round(gpx_df['Distance'].cumsum(), 2) gpx_df['Gradient'] = np.round(gpx_df['Elevation_Diff'] / gpx_df['Distance'] * 100, 1) activities[activity_type][activity_group].append({ 'name': activity_name.replace('.gpx', '').replace('_', ' '), 'points': points, 'gpx_df': gpx_df }) frames.append(gpx_df) df = pd.concat(frames) df That leaves us with all the necessary data of an activity: a list of all the GPS coordinates and a Pandas DataFrame containing all kinds of metrics. Plotting GPX Trails on the Interactive Map With our GPS data now organized in a Pandas DataFrame, we can visualize our outdoor activities on an interactive map. Folium makes this task a breeze: ### CONFIG # LOCATION = None LOCATION = [48.13743, 11.57549] # latitude, longitude ZOOM_START = 10 ACTIVITY_TYPES = { 'Hiking': { 'icon': 'person-hiking', 'color': 'green' }, 'Running': { 'icon': 'person-running', 'color': 'orange' }, 'Biking': { 'icon': 'person-biking', 'color': 'red' }, 'Skiing': { 'icon': 'person-skiing', 'color': 'blue' } } We'll create a map centered at a specific location (you can choose one or let the code determine the center based on your data). We'll assign unique colors and icons to each activity type, such as hiking, running, biking, or skiing. The ACTIVITY_TYPES dictionary will help us with this. ### CREATE MAP import folium from folium import plugins as folium_plugins if LOCATION: location = LOCATION else: location=[df.Latitude.mean(), df.Longitude.mean()] map = folium.Map(location=location, zoom_start=ZOOM_START, tiles=None) folium.TileLayer('OpenStreetMap', name='OpenStreet Map').add_to(map) folium.TileLayer('Stamen Terrain', name='Stamen Terrain').add_to(map) ### MAP TRAILS def timedelta_formatter(td): td_sec = td.seconds hour_count, rem = divmod(td_sec, 3600) hour_count += td.days * 24 minute_count, second_count = divmod(rem, 60) return f'{hour_count}h, {minute_count}min, {second_count}s' def create_activity_popup(activity): df = activity['gpx_df'] attributes = { 'Date': { 'value': df['Time'][df.index[0]].strftime("%m/%d/%Y"), 'icon': 'calendar' }, 'Start': { 'value': df['Time'][df.index[0]].strftime("%H:%M:%S"), 'icon': 'clock' }, 'End': { 'value': df['Time'][df.index[-1]].strftime("%H:%M:%S"), 'icon': 'flag-checkered' }, 'Duration': { 'value': timedelta_formatter(df['Time'][df.index[-1]]-df['Time'][df.index[0]]), 'icon': 'stopwatch' }, 'Distance': { 'value': f"{np.round(df['Cum_Distance'][df.index[-1]] / 1000, 2)} km", 'icon': 'arrows-left-right' }, 'Average Speed': { 'value': f'{np.round(df.Speed.mean() * 3.6, 2)} km/h', 'icon': 'gauge-high' }, 'Max. Elevation': { 'value': f'{np.round(df.Elevation.max(), 2)} m', 'icon': 'mountain' }, 'Uphill': { 'value': f"{np.round(df[df['Elevation_Diff']>0]['Elevation_Diff'].sum(), 2)} m", 'icon': 'arrow-trend-up' }, 'Downhill': { 'value': f"{np.round(abs(df[df['Elevation_Diff']<0]['Elevation_Diff'].sum()), 2)} m", 'icon': 'arrow-trend-down' }, } html = f"<h4>{activity['name'].upper()}</h4>" for attribute in attributes: html += f'<i class="fa-solid fa-{attributes[attribute]["icon"]}" title="{attribute}"> {attributes[attribute]["value"]}</i></br>' return folium.Popup(html, max_width=300) feature_groups = {} for activity_type in activities: color = ACTIVITY_TYPES[activity_type]['color'] icon = ACTIVITY_TYPES[activity_type]['icon'] for activity_group in activities[activity_type]: # create and store feature groups # this allows different activity types in the same feature group if activity_group not in feature_groups: # create new feature group fg = folium.FeatureGroup(name=activity_group, show=True) feature_groups[activity_group] = fg map.add_child(fg) else: # use existing fg = feature_groups[activity_group] for activity in activities[activity_type][activity_group]: # create line on map points = activity['points'] line = folium.PolyLine(points, color=color, weight=4.5, opacity=.5) fg.add_child(line) # create marker marker = folium.Marker(points[0], popup=create_activity_popup(activity), icon=folium.Icon(color=color, icon_color='white', icon=icon, prefix='fa')) fg.add_child(marker) map.add_child(folium.LayerControl(position='bottomright')) folium_plugins.Fullscreen(position='topright').add_to(map) map
  • Мы будем использовать концепции Foliums FeatureGroup для группировки следов по имени их группы. Это позволит нам позже показывать и скрывать определенные группы действий.
  • Теперь мы пройдемся по нашим проанализированным данным и нанесем каждый след на карту, используя объекты Folium PolyLine и Marker. Полилиния будет представлять собой фактический маршрут, а маркер будет служить отправной точкой для каждого действия. Когда вы нажимаете на маркер, во всплывающем окне отображается соответствующая информация о соответствующем маршруте.

Заключение

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


Однако существует множество способов настроить, расширить и улучшить вашу карту. Так что возьмите файлы GPX, запустите Jupyter Notebook и позвольте своим прошлым занятиям на свежем воздухе оживить на карте!

Удачного картографирования!

Что дальше?

Оставайтесь с нами, потому что впереди еще много интересного:


  • развертывание веб-сайта с вашей картой с помощью AWS
  • построение профилей высоты и скорости с использованием Python и Plotly
  • улучшение троп с помощью фотографий, сделанных в одну сторону
  • и многое другое

Рекомендации


Также опубликовано здесь .