paint-brush
Como usei Python e Folium para visualizar minhas atividades ao ar livrepor@lukaskrimphove
7,707 leituras
7,707 leituras

Como usei Python e Folium para visualizar minhas atividades ao ar livre

por Lukas Krimphove14m2023/09/06
Read on Terminal Reader

Muito longo; Para ler

Embarque em uma expedição de exploração e mapeamento! Aprenda como dar vida aos seus arquivos GPX e criar mapas interativos usando o Folium.
featured image - Como usei Python e Folium para visualizar minhas atividades ao ar livre
Lukas Krimphove HackerNoon profile picture
0-item

Sonho em caminhar de Munique a Veneza e percorrer os lindos Alpes. Mas como ainda tenho de viver a minha vida quotidiana, a minha viagem tem de consistir em múltiplas etapas, com semanas, meses ou mesmo anos entre cada aventura. Tudo bem, pois se trata mais da jornada do que do destino. Porém, sempre quis uma forma de refazer visualmente esses caminhos, para ver até onde cheguei e quão perto estou de meu objetivo. Eu queria uma forma de comemorar o progresso que fiz e de me motivar para levar a jornada adiante.


Felizmente, muitos aplicativos de esportes e atividades ao ar livre, como Adidas Running, Komoot, Strava e outros, nos permitem exportar nossas atividades como arquivos GPX. No entanto, não há para onde ir com esses arquivos GPX.


É aí que o Python e o Folium entram em ação. Recentemente me deparei com o Folium, uma poderosa biblioteca Python para a criação de mapas interativos. Ele pode incorporar facilmente dados geográficos, como arquivos GPX, e permite personalização e exploração. Com base na riqueza de dados de GPS que reuni, comecei a fazer experiências com o Folium para criar um mapa que dê vida às minhas excursões ao ar livre.


Depois de alguma pesquisa e muitos testes, criei um mapa que me permite revisitar minhas atividades anteriores ao ar livre:

Então, se você é como eu e tem um tesouro de dados de GPS sem qualquer propósito, você pode se perguntar como cheguei lá. Portanto, neste artigo, explicarei como dar vida aos seus arquivos GPX.


Vamos embarcar numa expedição de exploração e mapeamento!

Primeiros passos com o Jupyter Notebook

Para começar esta aventura, usaremos o Jupyter Notebook. Por que Jupyter Notebook? É um ambiente de computação interativo fantástico que nos permite combinar código, visualizações e texto, tornando-o perfeito para fazer experiências com nossos dados e com a biblioteca Folium.


Se você ainda não instalou o Jupyter Notebook, siga as instruções no site oficial . Depois de instalado, você pode criar um novo Jupyter Notebook e se preparar para sua jornada.

Analisando arquivos GPX para dados de trilha

Em seguida, precisamos da matéria-prima para o nosso mapa – os arquivos GPX. GPX (GPS Exchange Format) é um formato de arquivo amplamente utilizado que armazena dados de localização, como latitude, longitude, elevação e hora, tornando-o ideal para rastrear atividades ao ar livre.


Se você é um caminhante, corredor, ciclista ou esquiador ávido, é provável que já tenha rastreado várias excursões usando um aplicativo ao ar livre ou esportivo. Muitos desses aplicativos permitem exportar suas atividades no formato GPX. Então reúna esses arquivos GPX e vamos começar!


Em nosso código Python, usaremos a biblioteca gpxpy para analisar os arquivos GPX e extrair os dados essenciais da trilha, como latitude, longitude, elevação, velocidade e distância. A função parse_gpx() fará todo o trabalho pesado para nós:


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

Isso nos deixa com todos os dados necessários de uma atividade: uma lista de todas as coordenadas GPS e um DataFrame do Pandas contendo todos os tipos de métricas.

Traçando trilhas GPX no mapa interativo

Com nossos dados de GPS agora organizados em um Pandas DataFrame, podemos visualizar nossas atividades ao ar livre em um mapa interativo. Folium torna essa tarefa muito fácil:


 ### 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' } }
  • Criaremos um mapa centralizado em um local específico (você pode escolher um ou deixar o código determinar o centro com base em seus dados).
  • Atribuíremos cores e ícones exclusivos a cada tipo de atividade, como caminhada, corrida, ciclismo ou esqui. O dicionário ACTIVITY_TYPES nos ajudará nisso.
 ### 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
  • Usaremos os conceitos do Foliums FeatureGroup para agrupar as trilhas com base no nome do grupo. Isso nos permitirá mostrar e ocultar determinados grupos de atividades posteriormente.
  • Agora, iremos iterar nossos dados analisados e traçar cada trilha no mapa usando os objetos PolyLine e Marker do Folium. A PolyLine representará a trilha real, enquanto o Marcador atuará como ponto de partida para cada atividade. Ao clicar em um marcador, um pop-up exibirá informações relevantes sobre a trilha correspondente.

Conclusão

Nesta jornada de exploração e mapeamento, aprendemos como usar Python e Folium para transformar arquivos GPX comuns em um mapa dinâmico e interativo. Agora você pode reviver suas aventuras ao ar livre, comemorar seu progresso e manter-se motivado para a próxima etapa de sua jornada.


No entanto, existem muitas maneiras de personalizar, ampliar e melhorar seu mapa. Então pegue seus arquivos GPX, abra o Jupyter Notebook e deixe suas atividades ao ar livre anteriores ganharem vida no mapa!

Bom mapeamento!

Qual é o próximo?

Fique ligado porque tem muito mais por vir:


  • implantando um site com seu mapa usando AWS
  • plotando perfis de elevação e velocidade usando Python e Plotly
  • aprimorando trilhas com fotos tiradas no caminho
  • e muito mais

Referências

  • Todo o código, incluindo o Jupyter Notebook, está no meu GitHub .
  • Ao fazer minha pesquisa no Folium, encontrei um ótimo artigo de Patrick, que fez a mesma coisa que eu planejei fazer. Seu trabalho foi uma excelente base para desenvolver minha solução, então dê uma olhada.
  • Caderno Jupyter
  • Fólio
  • Pandas

Também publicado aqui .