paint-brush
Comment j'ai utilisé Python et Folium pour visualiser mes activités de plein airpar@lukaskrimphove
7,707 lectures
7,707 lectures

Comment j'ai utilisé Python et Folium pour visualiser mes activités de plein air

par Lukas Krimphove14m2023/09/06
Read on Terminal Reader

Trop long; Pour lire

Embarquez pour une expédition d’exploration et de cartographie ! Apprenez à donner vie à vos fichiers GPX et à créer des cartes interactives à l'aide de Folium.
featured image - Comment j'ai utilisé Python et Folium pour visualiser mes activités de plein air
Lukas Krimphove HackerNoon profile picture
0-item

Je rêve de faire une randonnée de Munich à Venise et de parcourir les magnifiques Alpes. Mais comme je dois encore vivre mon quotidien, mon voyage doit être composé de plusieurs étapes, avec des semaines, des mois, voire des années entre chaque aventure. Ce n'est pas grave, car il s'agit plus du voyage que de la destination. Cependant, j'ai toujours voulu un moyen de retracer visuellement ces chemins, de voir jusqu'où je suis arrivé et à quel point je suis proche de mon objectif. Je voulais un moyen de célébrer les progrès que j'ai réalisés et de me motiver à aller plus loin.


Heureusement, de nombreuses applications de plein air et de sport, comme Adidas Running, Komoot, Strava et autres, nous permettent gracieusement d'exporter nos activités sous forme de fichiers GPX. Cependant, il n’y a nulle part où aller avec ces fichiers GPX.


C'est là que Python et Folium entrent en jeu. Je suis récemment tombé sur Folium, une puissante bibliothèque Python permettant de créer des cartes interactives. Il peut facilement intégrer des données géographiques, telles que les fichiers GPX, et permet la personnalisation et l'exploration. En m'appuyant sur la richesse des données GPS que j'ai collectées, j'ai commencé à expérimenter avec Folium pour créer une carte qui donne vie à mes excursions en plein air.


Après quelques recherches et de nombreux tests, j'ai abouti à une carte qui me permet de revisiter mes activités outdoor passées :

Donc, si vous êtes comme moi et que vous possédez un trésor de données GPS sans aucun but, vous vous demandez peut-être comment j'en suis arrivé là. Ainsi, dans cet article, je vais vous expliquer comment donner vie à vos fichiers GPX.


Embarquons pour une expédition d'exploration et de cartographie !

Premiers pas avec Jupyter Notebook

Pour commencer cette aventure, nous utiliserons Jupyter Notebook. Pourquoi le bloc-notes Jupyter ? Il s'agit d'un fantastique environnement informatique interactif qui nous permet de combiner du code, des visualisations et du texte, ce qui le rend parfait pour expérimenter nos données et la bibliothèque Folium.


Si vous n'avez pas encore installé Jupyter Notebook, suivez les instructions sur leur site officiel . Une fois installé, vous pouvez créer un nouveau bloc-notes Jupyter et vous préparer pour votre voyage.

Analyse des fichiers GPX pour les données de suivi

Ensuite, nous avons besoin de la matière première pour notre carte : les fichiers GPX. GPX (GPS Exchange Format) est un format de fichier largement utilisé qui stocke des données de localisation, telles que la latitude, la longitude, l'altitude et l'heure, ce qui le rend idéal pour suivre les activités de plein air.


Si vous êtes un randonneur, un coureur, un cycliste ou un skieur passionné, il est probable que vous ayez déjà suivi diverses excursions à l'aide d'une application de plein air ou de sport. De nombreuses applications vous permettent d'exporter vos activités au format GPX. Alors rassemblez ces fichiers GPX et commençons !


Dans notre code Python, nous utiliserons la bibliothèque gpxpy pour analyser les fichiers GPX et extraire les données essentielles du sentier, telles que la latitude, la longitude, l'altitude, la vitesse et la distance. La fonction parse_gpx() fera tout le gros du travail à notre place :


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

Cela nous laisse avec toutes les données nécessaires d'une activité : une liste de toutes les coordonnées GPS et un Pandas DataFrame contenant toutes sortes de métriques.

Tracer des sentiers GPX sur la carte interactive

Grâce à nos données GPS désormais organisées dans un Pandas DataFrame, nous pouvons visualiser nos activités de plein air sur une carte interactive. Folium facilite cette tâche :


 ### 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' } }
  • Nous allons créer une carte centrée sur un emplacement spécifique (vous pouvez en choisir une ou laisser le code déterminer le centre en fonction de vos données).
  • Nous attribuerons des couleurs et des icônes uniques à chaque type d'activité, comme la randonnée, la course, le vélo ou le ski. Le dictionnaire ACTIVITY_TYPES nous y aidera.
 ### 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
  • Nous utiliserons les concepts Foliums FeatureGroup pour regrouper les parcours en fonction de leur nom de groupe. Cela nous permettra d'afficher et de masquer certains groupes d'activités ultérieurement.
  • Maintenant, nous allons parcourir nos données analysées et tracer chaque sentier sur la carte à l'aide des objets PolyLine et Marker de Folium. La PolyLine représentera le sentier réel, tandis que le Marker servira de point de départ pour chaque activité. Lorsque vous cliquez sur un marqueur, une fenêtre contextuelle affichera des informations pertinentes sur le sentier correspondant.

Conclusion

Au cours de ce voyage d'exploration et de cartographie, nous avons appris à utiliser Python et Folium pour transformer des fichiers GPX banals en une carte dynamique et interactive. Désormais, vous pouvez revivre vos aventures en plein air, célébrer vos progrès et rester motivé pour la prochaine étape de votre voyage.


Cependant, il existe de nombreuses façons de personnaliser, d'étendre et d'améliorer votre carte. Alors récupérez vos fichiers GPX, lancez Jupyter Notebook et laissez vos activités de plein air passées prendre vie sur la carte !

Bonne cartographie !

Et après?

Restez à l'écoute car il y a beaucoup plus à venir :


  • déployer un site Web avec votre carte à l'aide d'AWS
  • tracer des profils d'altitude et de vitesse à l'aide de Python et Plotly
  • améliorer les sentiers avec des photos prises en chemin
  • et beaucoup plus

Les références

  • Tout le code, y compris le Jupyter Notebook, se trouve sur mon GitHub .
  • En faisant mes recherches sur Folium, j'ai trouvé un excellent article de Patrick, qui a fait la même chose que j'avais prévu de faire. Son travail a constitué une excellente base pour développer ma solution, alors n'hésitez pas à le consulter.
  • Carnet Jupyter
  • Feuille
  • Pandas

Également publié ici .