1

I'm following this tutorial, and the code is supposed to iterate over multiple .gpx files to plot a chart that shows the distance grouped per month and year. It works, but the function seems to have an issue as it reads only the first .gpx file and I can't find where it fails:

import os
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import HTML
from glob import glob
import gpxpy

    #function definition
def load_run_data(gpx_path, filter=""):
    gpx_files = glob(os.path.join(gpx_path, filter + "*.gpx"))
    run_data = []
    for file_idx, gpx_file in enumerate(gpx_files):
        gpx = gpxpy.parse(open(gpx_file, 'r'))
        # Loop through tracks
        for track_idx, track in enumerate(gpx.tracks):
            track_name = track.name
            track_time = track.get_time_bounds().start_time
            track_length = track.length_3d()
            track_duration = track.get_duration()
            track_speed = track.get_moving_data().max_speed

        for seg_idx, segment in enumerate(track.segments):
            segment_length = segment.length_3d()
            for point_idx, point in enumerate(segment.points):
                run_data.append([file_idx, os.path.basename(gpx_file), track_idx, track_name,
                                 track_time, track_length, track_duration, track_speed,
                                 seg_idx, segment_length, point.time, point.latitude,
                                 point.longitude, point.elevation, segment.get_speed(point_idx)])
        return run_data


data = load_run_data(gpx_path='./gpx', filter="")
df = pd.DataFrame(data, columns=['File_Index', 'File_Name', 'Index', 'Name',
                              'Time', 'Length', 'Duration', 'Max_Speed',
                              'Segment_Index', 'Segment_Length', 'Point_Time', 'Point_Latitude',
                              'Point_Longitude', 'Point_Elevation', 'Point_Speed'])

HTML(df.head().to_html(max_cols=4))

#conversion of distance to km
cols = ['File_Index', 'Time', 'Length', 'Duration', 'Max_Speed']
tracks = df[cols].copy()
tracks['Length'] /= 1e3
tracks.drop_duplicates(inplace=True)
tracks.head()

#add year and month columns, group the values
tracks['Year'] = tracks['Time'].apply(lambda x: x.year)
tracks['Month'] = tracks['Time'].apply(lambda x: x.month)
tracks_grouped = tracks.groupby(['Year','Month'])
tracks_grouped.describe().head()

#plot the chart
figsize=(7, 3.5)
tracks_grouped = tracks.groupby(['Year', 'Month'])
ax = tracks_grouped['Length'].sum().plot(kind='bar', figsize=figsize)
xlabels = [text.get_text() for text in  ax.get_xticklabels()]
ax.set_xticklabels(xlabels, rotation=70)
_ = ax.set_ylabel('Distance (km)')
plt.show()

2
  • It is a pure Python problem: what is the result of gpx_files = glob(os.path.join(gpx_path, filter + "*.gpx"))?
    – gene
    Nov 14, 2022 at 9:43
  • @gene Well, this could go beyond my knowledge but apparently it returns a list with the .gpx files in the project folder: ['./gpx\\file_1.gpx', './gpx\\file_2.gpx', ... ]
    – HyPhens
    Nov 14, 2022 at 20:53

1 Answer 1

0
+50

Your third for block is wrongly indented (same level as the second for block) because it depends on the variable track which is defined in that second for loop. So if the second loop fails you will run into troubles: UnboundLocalError: local variable 'track' referenced before assignment. That's why you probably need to nest it deeper.

I added two prints statement for more verbosity, and because I don't have some .gpx files at hand, I set a PLOT trigger before plotting, which I personally set to False in the following code.

Same goes for the code that loop over the tracks; I don't have your .gpx file structure at hand.

But once I run the code on 3 dummy .gpx files, I got this which is printed in stdout:

Processing file 0: ./gpx/gpx_path_003.gpx...
Processing file 1: ./gpx/gpx_path_001.gpx...
Processing file 2: ./gpx/gpx_path_002.gpx...

Here's the code:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import HTML
from glob import glob
import gpxpy

#function definition
def load_run_data(gpx_path, filter=""):
    gpx_files = glob(os.path.join(gpx_path, filter + "*.gpx"))
    run_data = []
    for file_idx, gpx_file in enumerate(gpx_files):
        print(f"Processing file {file_idx}: {gpx_file}...")
        gpx = gpxpy.parse(open(gpx_file, 'r'))
        # Loop through tracks
        for track_idx, track in enumerate(gpx.tracks):
            print(f"Processing track {track_idx}: {track}...")
            track_name = track.name
            track_time = track.get_time_bounds().start_time
            track_length = track.length_3d()
            track_duration = track.get_duration()
            track_speed = track.get_moving_data().max_speed

            for seg_idx, segment in enumerate(track.segments):
                segment_length = segment.length_3d()
                for point_idx, point in enumerate(segment.points):
                    run_data.append([
                        file_idx, os.path.basename(gpx_file), track_idx, track_name,
                        track_time, track_length, track_duration, track_speed,
                        seg_idx, segment_length, point.time, point.latitude,
                        point.longitude, point.elevation, segment.get_speed(point_idx)
                    ])

    return run_data


data = load_run_data(gpx_path='./gpx', filter="")
df = pd.DataFrame(
    data,
    columns = [
        'File_Index', 'File_Name', 'Index', 'Name',
        'Time', 'Length', 'Duration', 'Max_Speed',
        'Segment_Index', 'Segment_Length', 'Point_Time', 'Point_Latitude',
        'Point_Longitude', 'Point_Elevation', 'Point_Speed'
    ]
)

HTML(df.head().to_html(max_cols=4))

#conversion of distance to km
cols = ['File_Index', 'Time', 'Length', 'Duration', 'Max_Speed']
tracks = df[cols].copy()
tracks['Length'] /= 1e3
tracks.drop_duplicates(inplace=True)
tracks.head()

#add year and month columns, group the values
tracks['Year'] = tracks['Time'].apply(lambda x: x.year)
tracks['Month'] = tracks['Time'].apply(lambda x: x.month)
tracks_grouped = tracks.groupby(['Year','Month'])
tracks_grouped.describe().head()

#plot the chart
PLOT=False
if PLOT:
    figsize=(7, 3.5)
    tracks_grouped = tracks.groupby(['Year', 'Month'])
    ax = tracks_grouped['Length'].sum().plot(kind='bar', figsize=figsize)
    xlabels = [text.get_text() for text in  ax.get_xticklabels()]
    ax.set_xticklabels(xlabels, rotation=70)
    _ = ax.set_ylabel('Distance (km)')
    plt.show()

So from this point, the rest of the code only depends on your file content.

You can also use glob.iglob if you need to iterate through many files, it may be more convenient to use.

1
  • so was the issue only wrong formatting?? I need to check better next time...
    – HyPhens
    Dec 10, 2022 at 12:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.