Skip to content

Instantly share code, notes, and snippets.

@pvdb
Last active September 5, 2024 09:03
Show Gist options
  • Save pvdb/8287c3dbe74fd41722ee289c289bd3e6 to your computer and use it in GitHub Desktop.
Save pvdb/8287c3dbe74fd41722ee289c289bd3e6 to your computer and use it in GitHub Desktop.
Extract GPX tracks from Garmin FIT files
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# fit2gpx - Convert FIT files to GPX files
#
# INSTALLATION
#
# ln -s ${PWD}/fit2gpx $(brew --prefix)/bin/
# sudo ln -s ${PWD}/fit2gpx /usr/local/bin/
#
# DEPENDENCIES
#
# gem install fit4ruby
#
require 'erb'
require 'fit4ruby'
#
# 2024-06-29 18-26-24.fit - FIT file originally recorded on the device
#
# Track_2024-06-29 18.26.24.gpx - GPX file originally recorded on the device
# Track_2024-06-29 18-26-24.gpx - GPX file converted from FIT file on device
# Track_2024-06-29 18_26_24.gpx - GPX file converted from FIT file by script
#
def gpx_path_for(fit_path)
fit_file = File.basename(fit_path, '.fit')
gpx_file = if fit_file =~ /\d{4}-\d{2}-\d{2} \d{2}-\d{2}-\d{2}/
date, time = fit_file.split
converted_time = time.gsub('-', '_') # by script
"Track_#{date} #{converted_time}.gpx"
else
"Track_#{fit_file}.gpx"
end
File.join(File.dirname(fit_path), gpx_file)
end
ARGV.each do |fit_path|
next unless File.exist?(fit_path)
activity = Fit4Ruby.read(fit_path) # parse FIT file
time = Time.now # fit2gpx conversion time
name = File.basename(fit_path, 'fit') # GPX name == FIT name
gpx_path = gpx_path_for(fit_path)
gpx_template = ERB.new(DATA.read, trim_mode: '-')
File.write(gpx_path, gpx_template.result(binding)) # write GPX file
end
# That's all Folks!
__END__
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:gpxtrkx="http://www.garmin.com/xmlschemas/TrackStatsExtension/v1" xmlns:wptx1="http://www.garmin.com/xmlschemas/WaypointExtension/v1" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" creator="Oregon 750" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackStatsExtension/v1 http://www8.garmin.com/xmlschemas/TrackStatsExtension.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1 http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd">
<metadata>
<link href="http://www.garmin.com">
<text>Garmin International</text>
</link>
<time><%= time.utc.strftime('%Y-%m-%dT%H:%M:%SZ') -%></time>
</metadata>
<trk>
<name><%= name -%></name>
<trkseg>
<% activity.records.each do |record| -%>
<% lat = sprintf('%.10f', record.position_lat) -%>
<% lon = sprintf('%.10f', record.position_long) -%>
<% ele = sprintf('%.2f', record.enhanced_elevation) -%>
<trkpt lat="<%= lat %>" lon="<%= lon %>">
<ele><%= ele %></ele>
<time><%= record.timestamp.utc.strftime('%Y-%m-%dT%H:%M:%SZ') %></time>
</trkpt>
<% end -%>
</trkseg>
</trk>
</gpx>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment