paint-brush
Wie ich Python und Folium verwendet habe, um meine Outdoor-Aktivitäten zu visualisierenvon@lukaskrimphove
6,856 Lesungen
6,856 Lesungen

Wie ich Python und Folium verwendet habe, um meine Outdoor-Aktivitäten zu visualisieren

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

Zu lang; Lesen

Begeben Sie sich auf eine Entdeckungs- und Kartierungsexpedition! Erfahren Sie, wie Sie Ihren GPX-Dateien Leben einhauchen und mit Folium interaktive Karten erstellen.
featured image - Wie ich Python und Folium verwendet habe, um meine Outdoor-Aktivitäten zu visualisieren
Lukas Krimphove HackerNoon profile picture
0-item

Ich träume davon, von München nach Venedig zu wandern und über die wunderschönen Alpen zu wandern. Aber da ich immer noch meinen Alltag leben muss, muss meine Reise aus mehreren Etappen bestehen, mit Wochen, Monaten oder sogar Jahren zwischen den einzelnen Abenteuern. Das ist in Ordnung, denn es geht mehr um die Reise als um das Ziel. Ich wollte jedoch immer eine Möglichkeit haben, diese Wege visuell nachzuvollziehen, um zu sehen, wie weit ich gekommen bin und wie nah ich meinem Ziel bin. Ich wollte eine Möglichkeit finden, die Fortschritte, die ich gemacht habe, zu feiern und mich selbst zu motivieren, die Reise weiter voranzutreiben.


Glücklicherweise erlauben uns viele Outdoor- und Sport-Apps wie Adidas Running, Komoot, Strava und andere, unsere Aktivitäten als GPX-Dateien zu exportieren. Mit diesen GPX-Dateien kann man jedoch nirgendwo hingehen.


Hier kommen Python und Folium ins Spiel. Ich bin kürzlich auf Folium gestoßen, eine leistungsstarke Python-Bibliothek zum Erstellen interaktiver Karten. Es kann problemlos geografische Daten wie GPX-Dateien integrieren und ermöglicht eine individuelle Anpassung und Erkundung. Basierend auf der Fülle an GPS-Daten, die ich gesammelt habe, begann ich mit Folium zu experimentieren, um eine Karte zu erstellen, die meine Outdoor-Ausflüge zum Leben erweckt.


Nach einigen Recherchen und vielen Tests habe ich eine Karte erstellt, die es mir ermöglicht, meine früheren Outdoor-Aktivitäten noch einmal Revue passieren zu lassen:

Wenn Sie also so sind wie ich und über einen Schatz an GPS-Daten ohne Zweck verfügen, fragen Sie sich vielleicht, wie ich dorthin gekommen bin. In diesem Artikel erkläre ich Ihnen, wie Sie Ihren GPX-Dateien Leben einhauchen.


Begeben wir uns auf eine Entdeckungs- und Kartierungsexpedition!

Erste Schritte mit Jupyter Notebook

Um dieses Abenteuer zu beginnen, verwenden wir Jupyter Notebook. Warum Jupyter Notebook? Es handelt sich um eine fantastische interaktive Computerumgebung, die es uns ermöglicht, Code, Visualisierungen und Text zu kombinieren, was sie perfekt zum Experimentieren mit unseren Daten und der Folium-Bibliothek macht.


Wenn Sie Jupyter Notebook noch nicht installiert haben, befolgen Sie die Anweisungen auf der offiziellen Website . Nach der Installation können Sie ein neues Jupyter-Notebook erstellen und sich auf Ihre Reise vorbereiten.

Parsen von GPX-Dateien für Trail-Daten

Als nächstes benötigen wir das Rohmaterial für unsere Karte – die GPX-Dateien. GPX (GPS Exchange Format) ist ein weit verbreitetes Dateiformat, das Standortdaten wie Breitengrad, Längengrad, Höhe und Zeit speichert und sich daher ideal für die Verfolgung von Outdoor-Aktivitäten eignet.


Wenn Sie ein begeisterter Wanderer, Läufer, Radfahrer oder Skifahrer sind, ist die Wahrscheinlichkeit groß, dass Sie bereits verschiedene Ausflüge mit einer Outdoor- oder Sport-App getrackt haben. Viele dieser Apps ermöglichen Ihnen den Export Ihrer Aktivitäten im GPX-Format. Sammeln Sie also die GPX-Dateien und legen Sie los!


In unserem Python-Code verwenden wir die gpxpy-Bibliothek, um die GPX-Dateien zu analysieren und die wesentlichen Wegdaten wie Breitengrad, Längengrad, Höhe, Geschwindigkeit und Entfernung zu extrahieren. Die Funktion parse_gpx() erledigt die ganze schwere Arbeit für uns:


 ### 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

Damit haben wir alle notwendigen Daten einer Aktivität: eine Liste aller GPS-Koordinaten und einen Pandas DataFrame, der alle Arten von Metriken enthält.

Einzeichnen von GPX-Trails auf der interaktiven Karte

Da unsere GPS-Daten jetzt in einem Pandas DataFrame organisiert sind, können wir unsere Outdoor-Aktivitäten auf einer interaktiven Karte visualisieren. Folium macht diese Aufgabe zum Kinderspiel:


 ### 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' } }
  • Wir erstellen eine Karte, deren Mittelpunkt ein bestimmter Ort ist (Sie können einen auswählen oder den Mittelpunkt anhand Ihrer Daten vom Code bestimmen lassen).
  • Wir weisen jeder Aktivitätsart, wie Wandern, Laufen, Radfahren oder Skifahren, eindeutige Farben und Symbole zu. Dabei hilft uns das Wörterbuch 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
  • Wir verwenden Foliums FeatureGroup-Konzepte, um die Trails basierend auf ihrem Gruppennamen zu gruppieren. Dadurch können wir später bestimmte Aktivitätsgruppen ein- und ausblenden.
  • Jetzt durchlaufen wir unsere analysierten Daten und zeichnen jeden Weg auf der Karte mithilfe der PolyLine- und Marker-Objekte von Folium auf. Die Polylinie stellt den eigentlichen Weg dar, während die Markierung als Ausgangspunkt für jede Aktivität dient. Wenn Sie auf eine Markierung klicken, werden in einem Popup relevante Informationen zum entsprechenden Weg angezeigt.

Abschluss

Auf dieser Reise der Erkundung und Kartierung haben wir gelernt, wie man mit Python und Folium alltägliche GPX-Dateien in eine dynamische und interaktive Karte umwandelt. Jetzt können Sie Ihre Outdoor-Abenteuer noch einmal erleben, Ihre Fortschritte feiern und für die nächste Etappe Ihrer Reise motiviert bleiben.


Es gibt jedoch viele Möglichkeiten, Ihre Karte anzupassen, zu erweitern und zu verbessern. Schnappen Sie sich also Ihre GPX-Dateien, starten Sie Jupyter Notebook und lassen Sie Ihre vergangenen Outdoor-Aktivitäten auf der Karte zum Leben erwachen!

Viel Spaß beim Kartieren!

Was kommt als nächstes?

Bleiben Sie dran, denn es kommt noch viel mehr:


  • Bereitstellen einer Website mit Ihrer Karte mithilfe von AWS
  • Zeichnen von Höhen- und Geschwindigkeitsprofilen mit Python und Plotly
  • Verschönern Sie Wanderwege mit Bildern, die unterwegs aufgenommen wurden
  • und vieles mehr

Verweise

  • Der gesamte Code, einschließlich des Jupyter-Notebooks, befindet sich auf meinem GitHub .
  • Bei meiner Recherche zu Folium bin ich auf einen tollen Artikel von Patrick gestoßen, der das Gleiche tat, was ich vorhatte. Seine Arbeit war eine großartige Grundlage, um auf meiner Lösung aufzubauen, also schauen Sie sich sie bitte an.
  • Jupyter-Notizbuch
  • Folium
  • Pandas

Auch hier veröffentlicht.