From 1c4eb8d398dd4e70a2b45be148765b041c88f061 Mon Sep 17 00:00:00 2001 From: Jochen Peters Date: Fri, 26 Jun 2020 15:43:05 +0200 Subject: [PATCH 1/4] added basic GPX output (work in progress) --- scripts/fitdump | 89 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/scripts/fitdump b/scripts/fitdump index 2af0f20..cd4b0cb 100755 --- a/scripts/fitdump +++ b/scripts/fitdump @@ -35,10 +35,90 @@ def format_message(num, message, options): return "".join(s) +def get_gpxdat(records, src=None, trkname=None): + gpxdat = { + 'time': None, + 'src': src, + 'trkname': trkname, + 'trkpts': [], + } + + for message in records: + + if message.name == 'event': + start_event_type = False + tstamp = None + for field_data in message: + if field_data.name == 'event_type' and field_data.value == 'start': + start_event_type = True + elif field_data.name == 'timestamp': + tstamp = field_data.value + if start_event_type and tstamp is not None: + gpxdat['time'] = tstamp.strftime('%Y-%m-%dT%H:%M:%S') + + elif message.name == 'record': + trkpt = { + 'lat': None, + 'lon': None, + 'ele': None, + 'time': None, + 'speed': None, # in m/s + } + for field_data in message: + if field_data.name == 'position_lat': + trkpt['lat'] = field_data.value + elif field_data.name == 'position_long': + trkpt['lon'] = field_data.value + elif field_data.name == 'enhanced_altitude': + trkpt['ele'] = field_data.value + elif field_data.name == 'timestamp': + trkpt['time'] = field_data.value.strftime('%Y-%m-%dT%H:%M:%S') + elif field_data.name == 'enhanced_speed': + trkpt['speed'] = field_data.value / 3.6 # convert to m/s + gpxdat['trkpts'].append(trkpt) + + return gpxdat + + +def format_gpx(gpxdat): + s = [] + # header + s.append('\n') + s.append('\n') + s.append('\n') + if gpxdat['time'] is not None: + s.append('\n') + if gpxdat['src'] is not None: + s.append('' + gpxdat['src'] + '\n') + s.append('\n') + s.append('\n') + if gpxdat['trkname'] is not None: + s.append('' + gpxdat['trkname'] + '\n') + s.append('\n') + + # track points + for dat in gpxdat['trkpts']: + if dat['lat'] is not None and dat['lon'] is not None: + s.append('\n') + if dat['ele'] is not None: + s.append('' + str(dat['ele']) + '\n') + if dat['time'] is not None: + s.append('\n') + if dat['speed'] is not None: + s.append('' + str(dat['speed']) + '\n') + s.append('\n') + + # close tags + s.append('\n') + s.append('\n') + s.append('\n') + + return s + + def parse_args(args=None): parser = argparse.ArgumentParser( - description='Dump .FIT files to various formats', - epilog='python-fitparse version %s' % fitparse.__version__, + description='Dump .FIT files to various formats', epilog='python-fitparse version %s' % fitparse.__version__, ) parser.add_argument('-v', '--verbose', action='count', default=0) parser.add_argument( @@ -47,7 +127,7 @@ def parse_args(args=None): ) parser.add_argument( # TODO: csv - '-t', '--type', choices=('readable', 'json'), default='readable', + '-t', '--type', choices=('readable', 'json', 'gpx'), default='readable', help='File type to output. (DEFAULT: %(default)s)', ) parser.add_argument( @@ -113,6 +193,9 @@ def main(args=None): elif options.type == "readable": options.output.writelines(format_message(n, record, options) for n, record in enumerate(records, 1)) + elif options.type == "gpx": + gpxdat = get_gpxdat(records) + options.output.writelines(format_gpx(gpxdat)) finally: try: options.output.close() From 33750a3f011cf652a1ac521f806d8f8e1ffa54bf Mon Sep 17 00:00:00 2001 From: Jochen Peters Date: Sun, 28 Jun 2020 12:35:03 +0200 Subject: [PATCH 2/4] added basic GPX output --- scripts/fitdump | 166 ++++++++++++++++++++++++------------------------ 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/scripts/fitdump b/scripts/fitdump index cd4b0cb..5e680e1 100755 --- a/scripts/fitdump +++ b/scripts/fitdump @@ -7,6 +7,7 @@ import datetime import json import sys import types +import os # Python 2 compat try: @@ -35,87 +36,6 @@ def format_message(num, message, options): return "".join(s) -def get_gpxdat(records, src=None, trkname=None): - gpxdat = { - 'time': None, - 'src': src, - 'trkname': trkname, - 'trkpts': [], - } - - for message in records: - - if message.name == 'event': - start_event_type = False - tstamp = None - for field_data in message: - if field_data.name == 'event_type' and field_data.value == 'start': - start_event_type = True - elif field_data.name == 'timestamp': - tstamp = field_data.value - if start_event_type and tstamp is not None: - gpxdat['time'] = tstamp.strftime('%Y-%m-%dT%H:%M:%S') - - elif message.name == 'record': - trkpt = { - 'lat': None, - 'lon': None, - 'ele': None, - 'time': None, - 'speed': None, # in m/s - } - for field_data in message: - if field_data.name == 'position_lat': - trkpt['lat'] = field_data.value - elif field_data.name == 'position_long': - trkpt['lon'] = field_data.value - elif field_data.name == 'enhanced_altitude': - trkpt['ele'] = field_data.value - elif field_data.name == 'timestamp': - trkpt['time'] = field_data.value.strftime('%Y-%m-%dT%H:%M:%S') - elif field_data.name == 'enhanced_speed': - trkpt['speed'] = field_data.value / 3.6 # convert to m/s - gpxdat['trkpts'].append(trkpt) - - return gpxdat - - -def format_gpx(gpxdat): - s = [] - # header - s.append('\n') - s.append('\n') - s.append('\n') - if gpxdat['time'] is not None: - s.append('\n') - if gpxdat['src'] is not None: - s.append('' + gpxdat['src'] + '\n') - s.append('\n') - s.append('\n') - if gpxdat['trkname'] is not None: - s.append('' + gpxdat['trkname'] + '\n') - s.append('\n') - - # track points - for dat in gpxdat['trkpts']: - if dat['lat'] is not None and dat['lon'] is not None: - s.append('\n') - if dat['ele'] is not None: - s.append('' + str(dat['ele']) + '\n') - if dat['time'] is not None: - s.append('\n') - if dat['speed'] is not None: - s.append('' + str(dat['speed']) + '\n') - s.append('\n') - - # close tags - s.append('\n') - s.append('\n') - s.append('\n') - - return s - - def parse_args(args=None): parser = argparse.ArgumentParser( description='Dump .FIT files to various formats', epilog='python-fitparse version %s' % fitparse.__version__, @@ -173,6 +93,85 @@ class RecordJSONEncoder(json.JSONEncoder): return super(RecordJSONEncoder, self).default(obj) +def get_gpsdat(records, src=None, trkname=None): + gpsdat = { + 'time': None, # in ISO 8601 format + 'src': src, + 'trkname': trkname, + 'trkpts': [], + } + + for message in records: + + if message.name == 'file_id': + for field_data in message: + if field_data.name == 'time_created' and type(field_data.value) == datetime.datetime: + gpsdat['time'] = field_data.value.strftime('%Y-%m-%dT%H:%M:%SZ') + + elif message.name == 'record': + trkpt = { + 'lat': None, # in decimal degrees + 'lon': None, # in decimal degrees + 'ele': None, # in m + 'time': None, # in ISO 8601 format + 'speed': None, # in m/s + } + for field_data in message: + if field_data.name == 'position_lat': + trkpt['lat'] = field_data.value + elif field_data.name == 'position_long': + trkpt['lon'] = field_data.value + elif field_data.name == 'enhanced_altitude': + trkpt['ele'] = field_data.value + elif field_data.name == 'timestamp' and type(field_data.value) == datetime.datetime: + trkpt['time'] = field_data.value.strftime('%Y-%m-%dT%H:%M:%SZ') + elif field_data.name == 'enhanced_speed' and type(field_data.value) == float: + if field_data.units == 'm/s': + trkpt['speed'] = field_data.value + elif field_data.units == 'km/h': + trkpt['speed'] = field_data.value / 3.6 # convert to m/s + gpsdat['trkpts'].append(trkpt) + + return gpsdat + + +def format_gpx(gpsdat): + s = [] + + # header + open tags + s.append('\n') + s.append('\n') + s.append('\n') + if gpsdat['time'] is not None: + s.append('\n') + if gpsdat['src'] is not None: + s.append('' + gpsdat['src'] + '\n') + s.append('\n') + s.append('\n') + if gpsdat['trkname'] is not None: + s.append('' + gpsdat['trkname'] + '\n') + s.append('\n') + + # track points + for dat in gpsdat['trkpts']: + if dat['lat'] is not None and dat['lon'] is not None: + s.append('\n') + if dat['ele'] is not None: + s.append('' + str(dat['ele']) + '\n') + if dat['time'] is not None: + s.append('\n') + if dat['speed'] is not None: + s.append('' + str(dat['speed']) + '\n') + s.append('\n') + + # close tags + s.append('\n') + s.append('\n') + s.append('\n') + + return s + + def main(args=None): options = parse_args(args) @@ -194,8 +193,9 @@ def main(args=None): options.output.writelines(format_message(n, record, options) for n, record in enumerate(records, 1)) elif options.type == "gpx": - gpxdat = get_gpxdat(records) - options.output.writelines(format_gpx(gpxdat)) + src_filename = os.path.basename(options.infile.name) + gpsdat = get_gpsdat(records, src=src_filename, trkname=src_filename) + options.output.writelines(format_gpx(gpsdat)) finally: try: options.output.close() From 52be8c047bf1eb3431d9aa4e5ce7430a1c6084cc Mon Sep 17 00:00:00 2001 From: Jochen Peters Date: Sun, 28 Jun 2020 12:42:18 +0200 Subject: [PATCH 3/4] added basic GPX output --- scripts/fitdump | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/fitdump b/scripts/fitdump index 5e680e1..5d4d138 100755 --- a/scripts/fitdump +++ b/scripts/fitdump @@ -38,7 +38,8 @@ def format_message(num, message, options): def parse_args(args=None): parser = argparse.ArgumentParser( - description='Dump .FIT files to various formats', epilog='python-fitparse version %s' % fitparse.__version__, + description='Dump .FIT files to various formats', + epilog='python-fitparse version %s' % fitparse.__version__, ) parser.add_argument('-v', '--verbose', action='count', default=0) parser.add_argument( From 86edac3d33f14c888ba6a34bebfe766f6473566d Mon Sep 17 00:00:00 2001 From: Carey Metcalfe Date: Fri, 25 Dec 2020 13:34:17 -0500 Subject: [PATCH 4/4] Changes to GPX fitdump --- scripts/fitdump | 154 +++++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 75 deletions(-) diff --git a/scripts/fitdump b/scripts/fitdump index 5d4d138..5e4a4b4 100755 --- a/scripts/fitdump +++ b/scripts/fitdump @@ -4,10 +4,11 @@ from __future__ import print_function import argparse import codecs import datetime +import itertools import json +import os.path import sys import types -import os # Python 2 compat try: @@ -94,83 +95,84 @@ class RecordJSONEncoder(json.JSONEncoder): return super(RecordJSONEncoder, self).default(obj) -def get_gpsdat(records, src=None, trkname=None): - gpsdat = { - 'time': None, # in ISO 8601 format - 'src': src, - 'trkname': trkname, - 'trkpts': [], - } +def generate_gpx(records, filename=None): + # TODO: Use xml.etree.ElementTree ? - for message in records: + GPX_TIME_FMT = "%Y-%m-%dT%H:%M:%SZ" # ISO 8601 format - if message.name == 'file_id': - for field_data in message: - if field_data.name == 'time_created' and type(field_data.value) == datetime.datetime: - gpsdat['time'] = field_data.value.strftime('%Y-%m-%dT%H:%M:%SZ') - - elif message.name == 'record': - trkpt = { - 'lat': None, # in decimal degrees - 'lon': None, # in decimal degrees - 'ele': None, # in m - 'time': None, # in ISO 8601 format - 'speed': None, # in m/s - } - for field_data in message: - if field_data.name == 'position_lat': - trkpt['lat'] = field_data.value - elif field_data.name == 'position_long': - trkpt['lon'] = field_data.value - elif field_data.name == 'enhanced_altitude': - trkpt['ele'] = field_data.value - elif field_data.name == 'timestamp' and type(field_data.value) == datetime.datetime: - trkpt['time'] = field_data.value.strftime('%Y-%m-%dT%H:%M:%SZ') - elif field_data.name == 'enhanced_speed' and type(field_data.value) == float: - if field_data.units == 'm/s': - trkpt['speed'] = field_data.value - elif field_data.units == 'km/h': - trkpt['speed'] = field_data.value / 3.6 # convert to m/s - gpsdat['trkpts'].append(trkpt) - - return gpsdat - - -def format_gpx(gpsdat): - s = [] + records = iter(records) # header + open tags - s.append('\n') - s.append('\n') - s.append('\n') - if gpsdat['time'] is not None: - s.append('\n') - if gpsdat['src'] is not None: - s.append('' + gpsdat['src'] + '\n') - s.append('\n') - s.append('\n') - if gpsdat['trkname'] is not None: - s.append('' + gpsdat['trkname'] + '\n') - s.append('\n') + yield '\n' + yield '\n' + yield ' \n' + + # file creation time (if a file_id record exists) + first_record = [] + for message in records: + if message.name == "file_id": + for field_data in message: + if field_data.name == "time_created" and type(field_data.value) == datetime.datetime: + yield ' \n'.format(field_data.value.strftime(GPX_TIME_FMT)) + break + else: + # No time found in the fields, check next record + continue + break + elif message.name == "record": + first_record.append(message) + break + + if filename: + yield ' {}\n'.format(filename) + + yield ' \n' + yield ' \n' + + if filename: + yield ' {}\n'.format(filename) + + yield ' \n' # track points - for dat in gpsdat['trkpts']: - if dat['lat'] is not None and dat['lon'] is not None: - s.append('\n') - if dat['ele'] is not None: - s.append('' + str(dat['ele']) + '\n') - if dat['time'] is not None: - s.append('\n') - if dat['speed'] is not None: - s.append('' + str(dat['speed']) + '\n') - s.append('\n') + for message in itertools.chain(first_record, records): + if message.name != "record": + continue - # close tags - s.append('\n') - s.append('\n') - s.append('\n') + trkpt = {} - return s + # TODO: support more data types (heart rate, cadence, etc) + for field_data in message: + if field_data.name == "position_lat": + # Units are decimal degrees + trkpt["lat"] = field_data.value + elif field_data.name == "position_long": + # Units are decimal degrees + trkpt["lon"] = field_data.value + elif field_data.name == "enhanced_altitude": + # Units are m + trkpt["ele"] = field_data.value + elif field_data.name == "timestamp" and type(field_data.value) == datetime.datetime: + trkpt["time"] = field_data.value.strftime(GPX_TIME_FMT) + elif field_data.name == "enhanced_speed" and type(field_data.value) == float: + # convert from km/h to m/s + trkpt["speed"] = field_data.value / 3.6 + + # Add trackpoint + if "lat" in trkpt and "lon" in trkpt: + yield ' \n'.format(**trkpt) + if "ele" in trkpt: + yield ' {ele}\n'.format(**trkpt) + if "time" in trkpt: + yield ' \n'.format(**trkpt) + if "speed" in trkpt: + yield ' {speed}\n'.format(**trkpt) + yield ' \n' + + # close tags + yield ' \n' + yield ' \n' + yield '\n' def main(args=None): @@ -191,12 +193,14 @@ def main(args=None): if options.type == "json": json.dump(records, fp=options.output, cls=RecordJSONEncoder) elif options.type == "readable": - options.output.writelines(format_message(n, record, options) - for n, record in enumerate(records, 1)) + options.output.writelines( + format_message(n, record, options) for n, record in enumerate(records, 1) + ) elif options.type == "gpx": - src_filename = os.path.basename(options.infile.name) - gpsdat = get_gpsdat(records, src=src_filename, trkname=src_filename) - options.output.writelines(format_gpx(gpsdat)) + filename = getattr(options.infile, "name") + if filename: + filename = os.path.basename(filename) + options.output.writelines(generate_gpx(records, filename)) finally: try: options.output.close()