From 9789982626514050d2c691a5a05b66ac79faef29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Mon, 31 Oct 2016 22:59:08 +0100 Subject: [PATCH] Added support for Garmin FIT file format. --- gpxsee.pro | 6 +- src/coordinates.h | 4 +- src/data.cpp | 5 +- src/fitparser.cpp | 287 ++++++++++++++++++++++++++++++++++++++++++++++ src/fitparser.h | 62 ++++++++++ src/graphview.h | 2 - src/gui.cpp | 10 +- src/trackpoint.h | 1 + 8 files changed, 365 insertions(+), 12 deletions(-) create mode 100644 src/fitparser.cpp create mode 100644 src/fitparser.h diff --git a/gpxsee.pro b/gpxsee.pro index c5fc7500..99b72e87 100644 --- a/gpxsee.pro +++ b/gpxsee.pro @@ -62,7 +62,8 @@ HEADERS += src/config.h \ src/wgs84.h \ src/kmlparser.h \ src/trackdata.h \ - src/routedata.h + src/routedata.h \ + src/fitparser.h SOURCES += src/main.cpp \ src/gui.cpp \ src/poi.cpp \ @@ -105,7 +106,8 @@ SOURCES += src/main.cpp \ src/tcxparser.cpp \ src/csvparser.cpp \ src/coordinates.cpp \ - src/kmlparser.cpp + src/kmlparser.cpp \ + src/fitparser.cpp RESOURCES += gpxsee.qrc TRANSLATIONS = lang/gpxsee_cs.ts macx { diff --git a/src/coordinates.h b/src/coordinates.h index 9f09973b..b9bc3f93 100644 --- a/src/coordinates.h +++ b/src/coordinates.h @@ -12,8 +12,8 @@ public: Coordinates(const Coordinates &c) {_lon = c._lon; _lat = c._lat;} Coordinates(qreal lon, qreal lat) {_lon = lon; _lat = lat;} - qreal &rLon() {return _lon;} - qreal &rLat() {return _lat;} + qreal &rlon() {return _lon;} + qreal &rlat() {return _lat;} void setLon(qreal lon) {_lon = lon;} void setLat(qreal lat) {_lat = lat;} qreal lon() const {return _lon;} diff --git a/src/data.cpp b/src/data.cpp index e5f75f8a..3b5b0e91 100644 --- a/src/data.cpp +++ b/src/data.cpp @@ -5,6 +5,7 @@ #include "tcxparser.h" #include "csvparser.h" #include "kmlparser.h" +#include "fitparser.h" #include "data.h" @@ -16,6 +17,8 @@ Data::Data() : _errorLine(0) _waypointData)); _parsers.insert("kml", new KMLParser(_trackData, _routeData, _waypointData)); + _parsers.insert("fit", new FITParser(_trackData, _routeData, + _waypointData)); _parsers.insert("csv", new CSVParser(_trackData, _routeData, _waypointData)); } @@ -50,7 +53,7 @@ bool Data::loadFile(const QString &fileName) _errorString.clear(); _errorLine = 0; - if (!file.open(QFile::ReadOnly | QFile::Text)) { + if (!file.open(QFile::ReadOnly)) { _errorString = qPrintable(file.errorString()); return false; } diff --git a/src/fitparser.cpp b/src/fitparser.cpp new file mode 100644 index 00000000..6fcb9b5c --- /dev/null +++ b/src/fitparser.cpp @@ -0,0 +1,287 @@ +#include +#include +#include "fitparser.h" + + +const quint32 FIT_MAGIC = 0x5449462E; // .FIT + + +FITParser::FITParser(QList &tracks, QList &routes, + QList &waypoints) : Parser(tracks, routes, waypoints) +{ + memset(_defs, 0, sizeof(_defs)); +} + +FITParser::~FITParser() +{ + for (int i = 0; i < 16; i++) + if (_defs[i].fields) + delete[] _defs[i].fields; +} + +bool FITParser::readData(char *data, size_t size) +{ + qint64 n; + + n = _device->read(data, size); + if (n < 0) { + _errorString = "I/O error"; + return false; + } else if ((size_t)n < size) { + _errorString = "Premature end of data"; + return false; + } + + return true; +} + +template bool FITParser::readValue(T &val) +{ + T data; + + if (!readData((char*)&data, sizeof(T))) + return false; + + _len -= sizeof(T); + + if (sizeof(T) > 1) { + if (_endian) + val = qFromBigEndian(data); + else + val = qFromLittleEndian(data); + } else + val = data; + + return true; +} + +bool FITParser::skipValue(size_t size) +{ + size_t i; + quint8 val; + + for (i = 0; i < size; i++) + if (!readValue(val)) + return false; + + return true; +} + +bool FITParser::parseDefinitionMessage(quint8 header) +{ + int local_id = header & 0x0f; + MessageDefinition* def = &_defs[local_id]; + quint8 i; + + + if (def->fields) + delete[] def->fields; + + // reserved/unused + if (!readValue(i)) + return false; + + // endianness + if (!readValue(def->endian)) + return false; + if (def->endian > 1) { + _errorString = "Bad endian field"; + return false; + } + _endian = def->endian; + + // global message number + if (!readValue(def->global_id)) + return false; + + // number of records + if (!readValue(def->num_fields)) + return false; + if (def->num_fields == 0) { + def->fields = 0; + return true; + } + + // definition records + def->fields = new Field[def->num_fields]; + for (i = 0; i < def->num_fields; i++) { + if (!readData((char*)&(def->fields[i]), sizeof(def->fields[i]))) + return false; + _len -= sizeof(def->fields[i]); + } + + return true; +} + +bool FITParser::readField(Field* f, quint32 &val) +{ + quint8 v8 = (quint8)-1; + quint16 v16 = (quint16)-1; + bool ret; + + val = (quint32)-1; + + switch (f->type) { + case 1: // sint8 + case 2: // uint8 + if (f->size == 1) { + ret = readValue(v8); + val = v8; + } else + ret = skipValue(f->size); + break; + case 0x83: // sint16 + case 0x84: // uint16 + if (f->size == 2) { + ret = readValue(v16); + val = v16; + } else + ret = skipValue(f->size); + break; + case 0x85: // sint32 + case 0x86: // uint32 + if (f->size == 4) + ret = readValue(val); + else + ret = skipValue(f->size); + break; + default: + ret = skipValue(f->size); + break; + } + + return ret; +} + +bool FITParser::parseData(MessageDefinition *def, quint8 offset) +{ + Field *field; + quint32 timestamp = _timestamp + offset; + quint32 val; + Trackpoint trackpoint; + int i; + + + _endian = def->endian; + + for (i = 0; i < def->num_fields; i++) { + field = &def->fields[i]; + if (!readField(field, val)) + return false; + + if (field->id == 253) + _timestamp = timestamp = val; + else if (def->global_id == 20) { + switch (field->id) { + case 0: + if (val != 0x7fffffff) + trackpoint.rcoordinates().setLat( + (val / (double)0x7fffffff) * 180); + break; + case 1: + if (val != 0x7fffffff) + trackpoint.rcoordinates().setLon( + (val / (double)0x7fffffff) * 180); + break; + case 2: + if (val != 0xffff) + trackpoint.setElevation((val / 5.0) - 500); + break; + case 3: + if (val != 0xff) + trackpoint.setHeartRate(val); + break; + case 6: + if (val != 0xffff) + trackpoint.setSpeed(val / 1000.0f); + break; + case 13: + if (val != 0x7f) + trackpoint.setTemperature(val); + break; + default: + break; + + } + } + } + + if (def->global_id == 20 && trackpoint.coordinates().isValid()) { + trackpoint.setTimestamp(QDateTime::fromTime_t(timestamp + 631065600)); + _tracks.last().append(trackpoint); + } + + return true; +} + +bool FITParser::parseDataMessage(quint8 header) +{ + int local_id = header & 0x1f; + MessageDefinition* def = &_defs[local_id]; + return parseData(def, 0); +} + +bool FITParser::parseCompressedMessage(quint8 header) +{ + int local_id = (header >> 5) & 3; + MessageDefinition* def = &_defs[local_id]; + return parseData(def, header & 0x1f); +} + +bool FITParser::parseRecord() +{ + quint8 header; + + if (!readValue(header)) + return false; + + if (header & 0x80) + return parseCompressedMessage(header); + else if (header & 0x40) + return parseDefinitionMessage(header); + else + return parseDataMessage(header); +} + +bool FITParser::parseHeader() +{ + FileHeader hdr; + quint16 crc; + qint64 len; + + len = _device->read((char*)&hdr, sizeof(hdr)); + if (len < 0) { + _errorString = "I/O error"; + return false; + } else if ((size_t)len < sizeof(hdr) + || hdr.magic != qToLittleEndian(FIT_MAGIC)) { + _errorString = "Not a FIT file"; + return false; + } + + _len = hdr.data_size; + + if (hdr.header_size > 12) + if (!readData((char *)&crc, sizeof(crc))) + return false; + + return true; +} + +bool FITParser::loadFile(QFile *file) +{ + _device = file; + _endian = 0; + _timestamp = 0; + + if (!parseHeader()) + return false; + + _tracks.append(TrackData()); + + while (_len) + if (!parseRecord()) + return false; + + return true; +} diff --git a/src/fitparser.h b/src/fitparser.h new file mode 100644 index 00000000..f3772cd5 --- /dev/null +++ b/src/fitparser.h @@ -0,0 +1,62 @@ +#ifndef FITPARSER_H +#define FITPARSER_H + +#include "parser.h" + +class FITParser : public Parser +{ +public: + FITParser(QList &tracks, QList &routes, + QList &waypoints); + ~FITParser(); + + bool loadFile(QFile *file); + QString errorString() const {return _errorString;} + int errorLine() const {return 0;} + +private: + typedef struct { + quint8 header_size; + quint8 protocol_version; + quint16 profile_version; + quint32 data_size; + quint32 magic; + } FileHeader; + + typedef struct { + quint8 id; + quint8 size; + quint8 type; + } Field; + + typedef struct { + quint8 endian; + quint16 global_id; + quint8 num_fields; + Field *fields; + } MessageDefinition; + + + bool readData(char *data, size_t size); + template bool readValue(T &val); + bool skipValue(size_t size); + + bool parseHeader(); + bool parseRecord(); + bool parseDefinitionMessage(quint8 header); + bool parseCompressedMessage(quint8 header); + bool parseDataMessage(quint8 header); + bool parseData(MessageDefinition *def, quint8 offset); + bool readField(Field *f, quint32 &val); + + + QIODevice *_device; + QString _errorString; + + quint32 _len; + quint8 _endian; + quint32 _timestamp; + MessageDefinition _defs[16]; +}; + +#endif // FITPARSER_H diff --git a/src/graphview.h b/src/graphview.h index 1fad15d6..691586a5 100644 --- a/src/graphview.h +++ b/src/graphview.h @@ -2,7 +2,6 @@ #define GRAPHVIEW_H #include -#include #include #include #include "palette.h" @@ -18,7 +17,6 @@ class GraphItem; class PathItem; class GridItem; - class GraphView : public QGraphicsView { Q_OBJECT diff --git a/src/gui.cpp b/src/gui.cpp index bd14a00c..313cc1c3 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -107,7 +107,7 @@ GUI::~GUI() void GUI::createBrowser() { QStringList filter; - filter << "*.gpx" << "*.tcx" << "*.kml" << "*.csv"; + filter << "*.gpx" << "*.tcx" << "*.kml" << "*.fit" << "*.csv"; _browser = new FileBrowser(this); _browser->setFilter(filter); } @@ -600,10 +600,10 @@ void GUI::dataSources() void GUI::openFile() { QStringList files = QFileDialog::getOpenFileNames(this, tr("Open file"), - QString(), tr("All supported files (*.gpx *.tcx *.kml *.csv)") + ";;" + QString(), tr("Supported files (*.gpx *.tcx *.kml *.csv *.fit)") + ";;" + tr("GPX files (*.gpx)") + ";;" + tr("TCX files (*.tcx)") + ";;" + tr("CSV files (*.csv)") + ";;" + tr("KML files (*.kml)") + ";;" - + tr("All files (*)")); + + tr("FIT files (*.fit)") + ";;" + tr("All files (*)")); QStringList list = files; for (QStringList::Iterator it = list.begin(); it != list.end(); it++) @@ -684,10 +684,10 @@ bool GUI::loadFile(const QString &fileName) void GUI::openPOIFile() { QStringList files = QFileDialog::getOpenFileNames(this, tr("Open POI file"), - QString(), tr("All supported files (*.gpx *.tcx *.kml *.csv)") + ";;" + QString(), tr("Supported files (*.gpx *.tcx *.kml *.csv *.fit)") + ";;" + tr("GPX files (*.gpx)") + ";;" + tr("CSV files (*.csv)") + ";;" + tr("TCX files (*.tcx)") + ";;" + tr("KML files (*.kml)") + ";;" - + tr("All files (*)")); + + tr("FIT files (*.fit)") + ";;" + tr("All files (*)")); QStringList list = files; for (QStringList::Iterator it = list.begin(); it != list.end(); it++) diff --git a/src/trackpoint.h b/src/trackpoint.h index 9f9df632..1a015756 100644 --- a/src/trackpoint.h +++ b/src/trackpoint.h @@ -15,6 +15,7 @@ public: {_elevation = NAN; _speed = NAN; _heartRate = NAN; _temperature = NAN;} const Coordinates &coordinates() const {return _coordinates;} + Coordinates &rcoordinates() {return _coordinates;} const QDateTime ×tamp() const {return _timestamp;} qreal elevation() const {return _elevation;} qreal speed() const {return _speed;}