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!
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.
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.
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' } }
### 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
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!
Bleiben Sie dran, denn es kommt noch viel mehr:
Auch hier veröffentlicht.