Skip to content

Instantly share code, notes, and snippets.

@michaelsteffen
Created December 3, 2014 00:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michaelsteffen/38dc106a14115f620f4a to your computer and use it in GitHub Desktop.
Save michaelsteffen/38dc106a14115f620f4a to your computer and use it in GitHub Desktop.
Simple script to clean GPX output from a Garmin cycling computer.
# Must be run with lxml installed to process larger data sets.
# By default looks for ./RawGPX.gpx as the input file and outputs to ./CleanedGPX.gpx. But of course one could change that easily...
import gpxpy
import gpxpy.gpx
import re
def get_furthest_point_above_speed(track, start_segment_no, start_point_no, speed, shortest_length=500):
"""
Starting from a given point on a track, finds the longest subsection of the track
for which the average speed is above a given value
Parameters
----------
track: GPXTrack
Track to analyze
start_segment_no: integer
Index of the segment with the starting point
start_point_no : integer
Index of the starting point to analyze on the segment
speed : integer
Speed in m/s, we're looking for a subset of the track with average speed
above this value
shortest_length : integer
Length in meters of the shortest acceptable track -- if the average
speed of the initial distance after start_point is not at least this value,
return 0
Returns
----------
None if no segment is found, otherwise:
point: GPXTrackPoint
point in the track that marks the furthest end of the matched segment
segment_no : integer
Index of segment containint point.
point_no : integer
Index of point.
meters: integer
Distance between starting point and ending point along the track
speed: float
Average speed along the identified section of the track in m/s
"""
# Check that starting point is within bounds -- i.e. if we're beyond the last segment
# of the track, or on the last point of a segment
if (start_segment_no >= len(track.segments) or
start_point_no >= track.segments[start_segment_no].get_points_no()-1):
return None
meters = 0
seconds = 0
# First time through the loop we start at the point immediately after the starting
# point; for subsequent segments, we start at point 0
prev_point = track.segments[start_segment_no].points[start_point_no]
start = start_point_no + 1
for segment_no, segment in enumerate(track.segments[start_segment_no:], start_segment_no):
for point_no, point in enumerate(segment.points[start:],start):
seconds += point.time_difference(prev_point)
meters += point.distance_3d(prev_point) or point.distance_2d(prev_point)
if (seconds == 0 or meters/seconds < speed):
if (seconds == 0 or
meters < shortest_length or
segment_no == start_segment_no and point_no == start_point_no + 1):
return None
else:
return prev_point, prev_segment_no, prev_point_no, prev_meters, prev_meters/prev_seconds
prev_point = point
prev_point_no = point_no
prev_segment_no = segment_no
prev_meters = meters
prev_seconds = seconds
start = 0
if (meters < shortest_length):
return None
else:
return point, segment_no, point_no, meters, meters/seconds
def snip_track(track, segment_no, point_no):
"""
Shorten a GPXTrack so it ends at the specified point.
Parameters
----------
track: GPXTrack
Track to split
segment_no: integer
Index of the segment containing the new end point
point_no : integer
Index of new end point within the segment
Returns
----------
track: GPXTrack
the shortened track
"""
track.split(segment_no, point_no)
track.segments = track.segments[:segment_no+1]
return track
# ====================
def main():
in_file = open('RawGPX.gpx', 'r')
print 'Parsing GPX file . . .'
gpx_in = gpxpy.parse(in_file)
gpx_out = gpxpy.gpx.GPX()
points = 0
print 'Looking for car rides . . .'
for track_no, track in enumerate(gpx_in.tracks):
time_bounds = track.get_time_bounds()
print 'Analyzing track {0} (Start: {1}; End: {2})'.format(
track_no, time_bounds.start_time, time_bounds.end_time)
# eliminate tracks that don't follow Garmin's naming convention, b/c these are
# spurious user-uploaded routes, rather than recorded rides
if (re.match(r'\d{4}-\d{2}-\d{2}', track.name) is None):
print '--> Removing Non-Ride Track'
print ' Track Name: {0}'.format(track.name)
continue
else:
last_point = track.segments[-1].points[-1]
for point, segment_no, point_no in track.walk():
# look for segments above ~30 mph = 13.4 m/s
fast_info = get_furthest_point_above_speed(track, segment_no, point_no, speed=13.4)
if fast_info is not None:
(fast_end, fast_segment_no, fast_point_no, fast_meters, fast_speed) = fast_info
if (fast_end == last_point):
print '--> Removing Car Ride'
print ' Car Ride Start: {0}'.format(point.time)
print ' Car Ride Length: {0} km'.format(fast_meters/1000)
print ' Car Ride Avg Speed: {0} km/h'.format(fast_speed*3.6)
track = snip_track(track, segment_no, point_no)
break
points += 1
gpx_out.tracks.append(track)
# output new gpx
print 'Saving cleaned GPX file . . .'
out_file = open('CleanedGPX.gpx', 'w')
out_file.write(gpx_out.to_xml())
print 'Total Points: {0}'.format(points)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment