mirror of
https://github.com/tumic0/GPXSee.git
synced 2025-02-17 16:20:48 +01:00
Added support for Garmin FIT file format.
This commit is contained in:
parent
eb03fe6ead
commit
9789982626
@ -62,7 +62,8 @@ HEADERS += src/config.h \
|
|||||||
src/wgs84.h \
|
src/wgs84.h \
|
||||||
src/kmlparser.h \
|
src/kmlparser.h \
|
||||||
src/trackdata.h \
|
src/trackdata.h \
|
||||||
src/routedata.h
|
src/routedata.h \
|
||||||
|
src/fitparser.h
|
||||||
SOURCES += src/main.cpp \
|
SOURCES += src/main.cpp \
|
||||||
src/gui.cpp \
|
src/gui.cpp \
|
||||||
src/poi.cpp \
|
src/poi.cpp \
|
||||||
@ -105,7 +106,8 @@ SOURCES += src/main.cpp \
|
|||||||
src/tcxparser.cpp \
|
src/tcxparser.cpp \
|
||||||
src/csvparser.cpp \
|
src/csvparser.cpp \
|
||||||
src/coordinates.cpp \
|
src/coordinates.cpp \
|
||||||
src/kmlparser.cpp
|
src/kmlparser.cpp \
|
||||||
|
src/fitparser.cpp
|
||||||
RESOURCES += gpxsee.qrc
|
RESOURCES += gpxsee.qrc
|
||||||
TRANSLATIONS = lang/gpxsee_cs.ts
|
TRANSLATIONS = lang/gpxsee_cs.ts
|
||||||
macx {
|
macx {
|
||||||
|
@ -12,8 +12,8 @@ public:
|
|||||||
Coordinates(const Coordinates &c) {_lon = c._lon; _lat = c._lat;}
|
Coordinates(const Coordinates &c) {_lon = c._lon; _lat = c._lat;}
|
||||||
Coordinates(qreal lon, qreal lat) {_lon = lon; _lat = lat;}
|
Coordinates(qreal lon, qreal lat) {_lon = lon; _lat = lat;}
|
||||||
|
|
||||||
qreal &rLon() {return _lon;}
|
qreal &rlon() {return _lon;}
|
||||||
qreal &rLat() {return _lat;}
|
qreal &rlat() {return _lat;}
|
||||||
void setLon(qreal lon) {_lon = lon;}
|
void setLon(qreal lon) {_lon = lon;}
|
||||||
void setLat(qreal lat) {_lat = lat;}
|
void setLat(qreal lat) {_lat = lat;}
|
||||||
qreal lon() const {return _lon;}
|
qreal lon() const {return _lon;}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "tcxparser.h"
|
#include "tcxparser.h"
|
||||||
#include "csvparser.h"
|
#include "csvparser.h"
|
||||||
#include "kmlparser.h"
|
#include "kmlparser.h"
|
||||||
|
#include "fitparser.h"
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
|
|
||||||
|
|
||||||
@ -16,6 +17,8 @@ Data::Data() : _errorLine(0)
|
|||||||
_waypointData));
|
_waypointData));
|
||||||
_parsers.insert("kml", new KMLParser(_trackData, _routeData,
|
_parsers.insert("kml", new KMLParser(_trackData, _routeData,
|
||||||
_waypointData));
|
_waypointData));
|
||||||
|
_parsers.insert("fit", new FITParser(_trackData, _routeData,
|
||||||
|
_waypointData));
|
||||||
_parsers.insert("csv", new CSVParser(_trackData, _routeData,
|
_parsers.insert("csv", new CSVParser(_trackData, _routeData,
|
||||||
_waypointData));
|
_waypointData));
|
||||||
}
|
}
|
||||||
@ -50,7 +53,7 @@ bool Data::loadFile(const QString &fileName)
|
|||||||
_errorString.clear();
|
_errorString.clear();
|
||||||
_errorLine = 0;
|
_errorLine = 0;
|
||||||
|
|
||||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
if (!file.open(QFile::ReadOnly)) {
|
||||||
_errorString = qPrintable(file.errorString());
|
_errorString = qPrintable(file.errorString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
287
src/fitparser.cpp
Normal file
287
src/fitparser.cpp
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
#include <cstring>
|
||||||
|
#include <QtEndian>
|
||||||
|
#include "fitparser.h"
|
||||||
|
|
||||||
|
|
||||||
|
const quint32 FIT_MAGIC = 0x5449462E; // .FIT
|
||||||
|
|
||||||
|
|
||||||
|
FITParser::FITParser(QList<TrackData> &tracks, QList<RouteData> &routes,
|
||||||
|
QList<Waypoint> &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<class T> 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;
|
||||||
|
}
|
62
src/fitparser.h
Normal file
62
src/fitparser.h
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#ifndef FITPARSER_H
|
||||||
|
#define FITPARSER_H
|
||||||
|
|
||||||
|
#include "parser.h"
|
||||||
|
|
||||||
|
class FITParser : public Parser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FITParser(QList<TrackData> &tracks, QList<RouteData> &routes,
|
||||||
|
QList<Waypoint> &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<class T> 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
|
@ -2,7 +2,6 @@
|
|||||||
#define GRAPHVIEW_H
|
#define GRAPHVIEW_H
|
||||||
|
|
||||||
#include <QGraphicsView>
|
#include <QGraphicsView>
|
||||||
#include <QVector>
|
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include "palette.h"
|
#include "palette.h"
|
||||||
@ -18,7 +17,6 @@ class GraphItem;
|
|||||||
class PathItem;
|
class PathItem;
|
||||||
class GridItem;
|
class GridItem;
|
||||||
|
|
||||||
|
|
||||||
class GraphView : public QGraphicsView
|
class GraphView : public QGraphicsView
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
10
src/gui.cpp
10
src/gui.cpp
@ -107,7 +107,7 @@ GUI::~GUI()
|
|||||||
void GUI::createBrowser()
|
void GUI::createBrowser()
|
||||||
{
|
{
|
||||||
QStringList filter;
|
QStringList filter;
|
||||||
filter << "*.gpx" << "*.tcx" << "*.kml" << "*.csv";
|
filter << "*.gpx" << "*.tcx" << "*.kml" << "*.fit" << "*.csv";
|
||||||
_browser = new FileBrowser(this);
|
_browser = new FileBrowser(this);
|
||||||
_browser->setFilter(filter);
|
_browser->setFilter(filter);
|
||||||
}
|
}
|
||||||
@ -600,10 +600,10 @@ void GUI::dataSources()
|
|||||||
void GUI::openFile()
|
void GUI::openFile()
|
||||||
{
|
{
|
||||||
QStringList files = QFileDialog::getOpenFileNames(this, tr("Open file"),
|
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("GPX files (*.gpx)") + ";;" + tr("TCX files (*.tcx)") + ";;"
|
||||||
+ tr("CSV files (*.csv)") + ";;" + tr("KML files (*.kml)") + ";;"
|
+ tr("CSV files (*.csv)") + ";;" + tr("KML files (*.kml)") + ";;"
|
||||||
+ tr("All files (*)"));
|
+ tr("FIT files (*.fit)") + ";;" + tr("All files (*)"));
|
||||||
QStringList list = files;
|
QStringList list = files;
|
||||||
|
|
||||||
for (QStringList::Iterator it = list.begin(); it != list.end(); it++)
|
for (QStringList::Iterator it = list.begin(); it != list.end(); it++)
|
||||||
@ -684,10 +684,10 @@ bool GUI::loadFile(const QString &fileName)
|
|||||||
void GUI::openPOIFile()
|
void GUI::openPOIFile()
|
||||||
{
|
{
|
||||||
QStringList files = QFileDialog::getOpenFileNames(this, tr("Open POI file"),
|
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("GPX files (*.gpx)") + ";;" + tr("CSV files (*.csv)") + ";;"
|
||||||
+ tr("TCX files (*.tcx)") + ";;" + tr("KML files (*.kml)") + ";;"
|
+ tr("TCX files (*.tcx)") + ";;" + tr("KML files (*.kml)") + ";;"
|
||||||
+ tr("All files (*)"));
|
+ tr("FIT files (*.fit)") + ";;" + tr("All files (*)"));
|
||||||
QStringList list = files;
|
QStringList list = files;
|
||||||
|
|
||||||
for (QStringList::Iterator it = list.begin(); it != list.end(); it++)
|
for (QStringList::Iterator it = list.begin(); it != list.end(); it++)
|
||||||
|
@ -15,6 +15,7 @@ public:
|
|||||||
{_elevation = NAN; _speed = NAN; _heartRate = NAN; _temperature = NAN;}
|
{_elevation = NAN; _speed = NAN; _heartRate = NAN; _temperature = NAN;}
|
||||||
|
|
||||||
const Coordinates &coordinates() const {return _coordinates;}
|
const Coordinates &coordinates() const {return _coordinates;}
|
||||||
|
Coordinates &rcoordinates() {return _coordinates;}
|
||||||
const QDateTime ×tamp() const {return _timestamp;}
|
const QDateTime ×tamp() const {return _timestamp;}
|
||||||
qreal elevation() const {return _elevation;}
|
qreal elevation() const {return _elevation;}
|
||||||
qreal speed() const {return _speed;}
|
qreal speed() const {return _speed;}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user