1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2025-06-27 03:29:16 +02:00

Project structure refactoring

This commit is contained in:
2017-11-26 18:54:03 +01:00
parent 443b916301
commit 56e4c80999
182 changed files with 500 additions and 529 deletions

47
src/data/csvparser.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "csvparser.h"
bool CSVParser::parse(QFile *file, QList<TrackData> &track,
QList<RouteData> &routes, QList<Waypoint> &waypoints)
{
Q_UNUSED(track);
Q_UNUSED(routes);
bool res;
_errorLine = 1;
_errorString.clear();
while (!file->atEnd()) {
QByteArray line = file->readLine();
QList<QByteArray> list = line.split(',');
if (list.size() < 3) {
_errorString = "Parse error";
return false;
}
qreal lat = list[0].trimmed().toDouble(&res);
if (!res || (lat < -90.0 || lat > 90.0)) {
_errorString = "Invalid latitude";
return false;
}
qreal lon = list[1].trimmed().toDouble(&res);
if (!res || (lon < -180.0 || lon > 180.0)) {
_errorString = "Invalid longitude";
return false;
}
Waypoint wp(Coordinates(lon, lat));
QByteArray ba = list[2].trimmed();
QString name = QString::fromUtf8(ba.data(), ba.size());
wp.setName(name);
if (list.size() > 3) {
ba = list[3].trimmed();
wp.setDescription(QString::fromUtf8(ba.data(), ba.size()));
}
waypoints.append(wp);
_errorLine++;
}
return true;
}

22
src/data/csvparser.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef CSVPARSER_H
#define CSVPARSER_H
#include "parser.h"
class CSVParser : public Parser
{
public:
CSVParser() : _errorLine(0) {}
~CSVParser() {}
bool parse(QFile *file, QList<TrackData> &track, QList<RouteData> &routes,
QList<Waypoint> &waypoints);
QString errorString() const {return _errorString;}
int errorLine() const {return _errorLine;}
private:
QString _errorString;
int _errorLine;
};
#endif // CSVPARSER_H

118
src/data/data.cpp Normal file
View File

@ -0,0 +1,118 @@
#include <QFile>
#include <QFileInfo>
#include <QLineF>
#include "gpxparser.h"
#include "tcxparser.h"
#include "csvparser.h"
#include "kmlparser.h"
#include "fitparser.h"
#include "igcparser.h"
#include "nmeaparser.h"
#include "data.h"
static GPXParser gpx;
static TCXParser tcx;
static KMLParser kml;
static FITParser fit;
static CSVParser csv;
static IGCParser igc;
static NMEAParser nmea;
static QHash<QString, Parser*> parsers()
{
QHash<QString, Parser*> hash;
hash.insert("gpx", &gpx);
hash.insert("tcx", &tcx);
hash.insert("kml", &kml);
hash.insert("fit", &fit);
hash.insert("csv", &csv);
hash.insert("igc", &igc);
hash.insert("nmea", &nmea);
return hash;
}
QHash<QString, Parser*> Data::_parsers = parsers();
Data::~Data()
{
for (int i = 0; i < _tracks.count(); i++)
delete _tracks.at(i);
for (int i = 0; i < _routes.count(); i++)
delete _routes.at(i);
}
void Data::processData()
{
for (int i = 0; i < _trackData.count(); i++)
_tracks.append(new Track(_trackData.at(i)));
for (int i = 0; i < _routeData.count(); i++)
_routes.append(new Route(_routeData.at(i)));
}
bool Data::loadFile(const QString &fileName)
{
QFile file(fileName);
QFileInfo fi(fileName);
_errorString.clear();
_errorLine = 0;
if (!file.open(QFile::ReadOnly)) {
_errorString = qPrintable(file.errorString());
return false;
}
QHash<QString, Parser*>::iterator it;
if ((it = _parsers.find(fi.suffix().toLower())) != _parsers.end()) {
if (it.value()->parse(&file, _trackData, _routeData, _waypoints)) {
processData();
return true;
}
_errorLine = it.value()->errorLine();
_errorString = it.value()->errorString();
} else {
for (it = _parsers.begin(); it != _parsers.end(); it++) {
if (it.value()->parse(&file, _trackData, _routeData, _waypoints)) {
processData();
return true;
}
file.reset();
}
qWarning("Error loading data file: %s:\n", qPrintable(fileName));
for (it = _parsers.begin(); it != _parsers.end(); it++)
qWarning("%s: line %d: %s\n", qPrintable(it.key()),
it.value()->errorLine(), qPrintable(it.value()->errorString()));
_errorLine = 0;
_errorString = "Unknown format";
}
return false;
}
QString Data::formats()
{
return tr("Supported files (*.csv *.fit *.gpx *.igc *.kml *.nmea *.tcx)")
+ ";;" + tr("CSV files (*.csv)") + ";;" + tr("FIT files (*.fit)") + ";;"
+ tr("GPX files (*.gpx)") + ";;" + tr("IGC files (*.igc)") + ";;"
+ tr("KML files (*.kml)") + ";;" + tr("NMEA files (*.nmea)") + ";;"
+ tr("TCX files (*.tcx)") + ";;" + tr("All files (*)");
}
QStringList Data::filter()
{
QStringList filter;
QHash<QString, Parser*>::iterator it;
for (it = _parsers.begin(); it != _parsers.end(); it++)
filter << QString("*.%1").arg(it.key());
return filter;
}

51
src/data/data.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef DATA_H
#define DATA_H
#include <QVector>
#include <QList>
#include <QHash>
#include <QPointF>
#include <QString>
#include <QStringList>
#include "waypoint.h"
#include "track.h"
#include "route.h"
#include "parser.h"
class Data : public QObject
{
Q_OBJECT
public:
Data(QObject *parent = 0) : QObject(parent), _errorLine(0) {}
~Data();
bool loadFile(const QString &fileName);
const QString &errorString() const {return _errorString;}
int errorLine() const {return _errorLine;}
const QList<Track*> &tracks() const {return _tracks;}
const QList<Route*> &routes() const {return _routes;}
const QList<Waypoint> &waypoints() const {return _waypoints;}
static QString formats();
static QStringList filter();
private:
void processData();
QString _errorString;
int _errorLine;
QList<Track*> _tracks;
QList<Route*> _routes;
QList<Waypoint> _waypoints;
QList<TrackData> _trackData;
QList<RouteData> _routeData;
static QHash<QString, Parser*> _parsers;
};
#endif // DATA_H

364
src/data/fitparser.cpp Normal file
View File

@ -0,0 +1,364 @@
#include <cstring>
#include <QtEndian>
#include "common/staticassert.h"
#include "fitparser.h"
const quint32 FIT_MAGIC = 0x5449462E; // .FIT
#define RECORD_MESSAGE 20
#define TIMESTAMP_FIELD 253
FITParser::FITParser()
{
memset(_defs, 0, sizeof(_defs));
_device = 0;
_endian = 0;
_timestamp = 0;
_len = 0;
}
void FITParser::clearDefinitions()
{
for (int i = 0; i < 16; i++) {
if (_defs[i].fields)
delete[] _defs[i].fields;
if (_defs[i].devFields)
delete[] _defs[i].devFields;
}
memset(_defs, 0, sizeof(_defs));
}
void FITParser::warning(const char *text) const
{
const QFile *file = static_cast<QFile *>(_device);
qWarning("%s:%d: %s\n", qPrintable(file->fileName()), _len, text);
}
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;
def->fields = 0;
}
if (def->devFields) {
delete[] def->devFields;
def->devFields = 0;
}
// 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->globalId))
return false;
// number of records
if (!readValue(def->numFields))
return false;
// definition records
def->fields = new Field[def->numFields];
for (i = 0; i < def->numFields; i++) {
STATIC_ASSERT(sizeof(def->fields[i]) == 3);
if (!readData((char*)&(def->fields[i]), sizeof(def->fields[i])))
return false;
_len -= sizeof(def->fields[i]);
}
// developer definition records
if (header & 0x20) {
if (!readValue(def->numDevFields))
return false;
def->devFields = new Field[def->numDevFields];
for (i = 0; i < def->numDevFields; i++) {
STATIC_ASSERT(sizeof(def->devFields[i]) == 3);
if (!readData((char*)&(def->devFields[i]),
sizeof(def->devFields[i])))
return false;
_len -= sizeof(def->devFields[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(TrackData &track, MessageDefinition *def,
quint8 offset)
{
Field *field;
quint32 timestamp = _timestamp + offset;
quint32 val;
Trackpoint trackpoint;
int i;
if (!def->fields && !def->devFields) {
_errorString = "Undefined data message";
return false;
}
_endian = def->endian;
for (i = 0; i < def->numFields; i++) {
field = &def->fields[i];
if (!readField(field, val))
return false;
if (field->id == TIMESTAMP_FIELD)
_timestamp = timestamp = val;
else if (def->globalId == RECORD_MESSAGE) {
switch (field->id) {
case 0:
if (val != 0x7fffffff)
trackpoint.rcoordinates().setLat(
((qint32)val / (double)0x7fffffff) * 180);
break;
case 1:
if (val != 0x7fffffff)
trackpoint.rcoordinates().setLon(
((qint32)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 4:
if (val != 0xff)
trackpoint.setCadence(val);
break;
case 6:
if (val != 0xffff)
trackpoint.setSpeed(val / 1000.0f);
break;
case 7:
if (val != 0xffff)
trackpoint.setPower(val);
break;
case 13:
if (val != 0x7f)
trackpoint.setTemperature((qint8)val);
break;
default:
break;
}
}
}
for (i = 0; i < def->numDevFields; i++) {
field = &def->devFields[i];
if (!readField(field, val))
return false;
}
if (def->globalId == RECORD_MESSAGE) {
if (trackpoint.coordinates().isValid()) {
trackpoint.setTimestamp(QDateTime::fromTime_t(timestamp
+ 631065600));
track.append(trackpoint);
} else {
if (trackpoint.coordinates().isNull())
warning("Missing coordinates");
else {
_errorString = "Invalid coordinates";
return false;
}
}
}
return true;
}
bool FITParser::parseDataMessage(TrackData &track, quint8 header)
{
int local_id = header & 0xf;
MessageDefinition* def = &_defs[local_id];
return parseData(track, def, 0);
}
bool FITParser::parseCompressedMessage(TrackData &track, quint8 header)
{
int local_id = (header >> 5) & 3;
MessageDefinition* def = &_defs[local_id];
return parseData(track, def, header & 0x1f);
}
bool FITParser::parseRecord(TrackData &track)
{
quint8 header;
if (!readValue(header))
return false;
if (header & 0x80)
return parseCompressedMessage(track, header);
else if (header & 0x40)
return parseDefinitionMessage(header);
else
return parseDataMessage(track, header);
}
bool FITParser::parseHeader()
{
FileHeader hdr;
quint16 crc;
qint64 len;
STATIC_ASSERT(sizeof(hdr) == 12);
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 = qFromLittleEndian(hdr.dataSize);
if (hdr.headerSize > sizeof(hdr))
if (!readData((char *)&crc, sizeof(crc)))
return false;
return true;
}
bool FITParser::parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints)
{
Q_UNUSED(routes);
Q_UNUSED(waypoints);
bool ret = true;
_device = file;
_endian = 0;
_timestamp = 0;
if (!parseHeader())
return false;
tracks.append(TrackData());
TrackData &track = tracks.last();
while (_len)
if ((ret = parseRecord(track)) == false)
break;
clearDefinitions();
return ret;
}

66
src/data/fitparser.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef FITPARSER_H
#define FITPARSER_H
#include "parser.h"
class FITParser : public Parser
{
public:
FITParser();
~FITParser() {}
bool parse(QFile *file, QList<TrackData> &tracks, QList<RouteData> &routes,
QList<Waypoint> &waypoints);
QString errorString() const {return _errorString;}
int errorLine() const {return 0;}
private:
struct FileHeader {
quint8 headerSize;
quint8 protocolVersion;
quint16 profileVersion;
quint32 dataSize;
quint32 magic;
};
struct Field {
quint8 id;
quint8 size;
quint8 type;
};
struct MessageDefinition {
quint8 endian;
quint16 globalId;
quint8 numFields;
Field *fields;
quint8 numDevFields;
Field *devFields;
};
void warning(const char *text) const;
void clearDefinitions();
bool readData(char *data, size_t size);
template<class T> bool readValue(T &val);
bool skipValue(size_t size);
bool parseHeader();
bool parseRecord(TrackData &track);
bool parseDefinitionMessage(quint8 header);
bool parseCompressedMessage(TrackData &track, quint8 header);
bool parseDataMessage(TrackData &track, quint8 header);
bool parseData(TrackData &track, 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

202
src/data/gpxparser.cpp Normal file
View File

@ -0,0 +1,202 @@
#include "gpxparser.h"
qreal GPXParser::number()
{
bool res;
qreal ret = _reader.readElementText().toDouble(&res);
if (!res)
_reader.raiseError(QString("Invalid %1").arg(
_reader.name().toString()));
return ret;
}
QDateTime GPXParser::time()
{
QDateTime d = QDateTime::fromString(_reader.readElementText(),
Qt::ISODate);
if (!d.isValid())
_reader.raiseError(QString("Invalid %1").arg(
_reader.name().toString()));
return d;
}
Coordinates GPXParser::coordinates()
{
bool res;
qreal lon, lat;
const QXmlStreamAttributes &attr = _reader.attributes();
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
lon = attr.value("lon").toString().toDouble(&res);
#else // QT_VERSION < 5
lon = attr.value("lon").toDouble(&res);
#endif // QT_VERSION < 5
if (!res || (lon < -180.0 || lon > 180.0)) {
_reader.raiseError("Invalid longitude");
return Coordinates();
}
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
lat = attr.value("lat").toString().toDouble(&res);
#else // QT_VERSION < 5
lat = attr.value("lat").toDouble(&res);
#endif // QT_VERSION < 5
if (!res || (lat < -90.0 || lat > 90.0)) {
_reader.raiseError("Invalid latitude");
return Coordinates();
}
return Coordinates(lon, lat);
}
void GPXParser::tpExtension(Trackpoint &trackpoint)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "hr")
trackpoint.setHeartRate(number());
else if (_reader.name() == "atemp")
trackpoint.setTemperature(number());
else
_reader.skipCurrentElement();
}
}
void GPXParser::extensions(Trackpoint &trackpoint)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "speed")
trackpoint.setSpeed(number());
else if (_reader.name() == "hr" || _reader.name() == "heartrate")
trackpoint.setHeartRate(number());
else if (_reader.name() == "temp")
trackpoint.setTemperature(number());
else if (_reader.name() == "cadence")
trackpoint.setCadence(number());
else if (_reader.name() == "power")
trackpoint.setPower(number());
else if (_reader.name() == "TrackPointExtension")
tpExtension(trackpoint);
else
_reader.skipCurrentElement();
}
}
void GPXParser::trackpointData(Trackpoint &trackpoint)
{
qreal gh = NAN;
while (_reader.readNextStartElement()) {
if (_reader.name() == "ele")
trackpoint.setElevation(number());
else if (_reader.name() == "time")
trackpoint.setTimestamp(time());
else if (_reader.name() == "geoidheight")
gh = number();
else if (_reader.name() == "extensions")
extensions(trackpoint);
else
_reader.skipCurrentElement();
}
if (!std::isnan(gh) && !std::isnan(trackpoint.elevation()))
trackpoint.setElevation(trackpoint.elevation() - gh);
}
void GPXParser::waypointData(Waypoint &waypoint)
{
qreal gh = NAN;
while (_reader.readNextStartElement()) {
if (_reader.name() == "name")
waypoint.setName(_reader.readElementText());
else if (_reader.name() == "desc")
waypoint.setDescription(_reader.readElementText());
else if (_reader.name() == "ele")
waypoint.setElevation(number());
else if (_reader.name() == "geoidheight")
gh = number();
else if (_reader.name() == "time")
waypoint.setTimestamp(time());
else
_reader.skipCurrentElement();
}
if (!std::isnan(gh) && !std::isnan(waypoint.elevation()))
waypoint.setElevation(waypoint.elevation() - gh);
}
void GPXParser::trackpoints(TrackData &track)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "trkpt") {
track.append(Trackpoint(coordinates()));
trackpointData(track.last());
} else
_reader.skipCurrentElement();
}
}
void GPXParser::routepoints(RouteData &route)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "rtept") {
route.append(Waypoint(coordinates()));
waypointData(route.last());
} else if (_reader.name() == "name")
route.setName(_reader.readElementText());
else if (_reader.name() == "desc")
route.setDescription(_reader.readElementText());
else
_reader.skipCurrentElement();
}
}
void GPXParser::track(TrackData &track)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "trkseg")
trackpoints(track);
else if (_reader.name() == "name")
track.setName(_reader.readElementText());
else if (_reader.name() == "desc")
track.setDescription(_reader.readElementText());
else
_reader.skipCurrentElement();
}
}
void GPXParser::gpx(QList<TrackData> &tracks, QList<RouteData> &routes,
QList<Waypoint> &waypoints)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "trk") {
tracks.append(TrackData());
track(tracks.back());
} else if (_reader.name() == "rte") {
routes.append(RouteData());
routepoints(routes.back());
} else if (_reader.name() == "wpt") {
waypoints.append(Waypoint(coordinates()));
waypointData(waypoints.last());
} else
_reader.skipCurrentElement();
}
}
bool GPXParser::parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints)
{
_reader.clear();
_reader.setDevice(file);
if (_reader.readNextStartElement()) {
if (_reader.name() == "gpx")
gpx(tracks, routes, waypoints);
else
_reader.raiseError("Not a GPX file");
}
return !_reader.error();
}

35
src/data/gpxparser.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef GPXPARSER_H
#define GPXPARSER_H
#include <QXmlStreamReader>
#include "parser.h"
class GPXParser : public Parser
{
public:
~GPXParser() {}
bool parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints);
QString errorString() const {return _reader.errorString();}
int errorLine() const {return _reader.lineNumber();}
private:
void gpx(QList<TrackData> &tracks, QList<RouteData> &routes,
QList<Waypoint> &waypoints);
void track(TrackData &track);
void trackpoints(TrackData &track);
void routepoints(RouteData &route);
void tpExtension(Trackpoint &trackpoint);
void extensions(Trackpoint &trackpoint);
void trackpointData(Trackpoint &trackpoint);
void waypointData(Waypoint &waypoint);
qreal number();
QDateTime time();
Coordinates coordinates();
QXmlStreamReader _reader;
};
#endif // GPXPARSER_H

42
src/data/graph.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef GRAPH_H
#define GRAPH_H
#include <QVector>
#include <QDebug>
#include <cmath>
enum GraphType {Distance, Time};
class GraphPoint
{
public:
GraphPoint(qreal s = NAN, qreal t = NAN, qreal y = NAN)
: _s(s), _t(t), _y(y) {}
qreal s() const {return _s;}
qreal t() const {return _t;}
qreal y() const {return _y;}
qreal x(GraphType type) const {return (type == Distance) ? _s : _t;}
void setS(qreal s) {_s = s;}
void setT(qreal t) {_t = t;}
void setY(qreal y) {_y = y;}
private:
qreal _s;
qreal _t;
qreal _y;
};
Q_DECLARE_TYPEINFO(GraphPoint, Q_PRIMITIVE_TYPE);
inline QDebug operator<<(QDebug dbg, const GraphPoint &point)
{
dbg.nospace() << "GraphPoint(" << point.s() << ", " << point.t() << ", "
<< point.y() << ")";
return dbg.space();
}
typedef QVector<GraphPoint> Graph;
#endif // GRAPH_H

251
src/data/igcparser.cpp Normal file
View File

@ -0,0 +1,251 @@
#include <cstring>
#include "str2int.h"
#include "igcparser.h"
static bool readLat(const char *data, qreal &lat)
{
int d = str2int(data, 2);
int mi = str2int(data + 2, 2);
int mf = str2int(data + 4, 3);
if (d < 0 || mi < 0 || mf < 0)
return false;
if (!(data[7] == 'N' || data[7] == 'S'))
return false;
lat = d + (((qreal)mi + (qreal)mf/1000) / 60);
if (lat > 90)
return false;
if (data[7] == 'S')
lat = -lat;
return true;
}
static bool readLon(const char *data, qreal &lon)
{
int d = str2int(data, 3);
int mi = str2int(data + 3, 2);
int mf = str2int(data + 5, 3);
if (d < 0 || mi < 0 || mf < 0)
return false;
if (!(data[8] == 'E' || data[8] == 'W'))
return false;
lon = d + (((qreal)mi + (qreal)mf/1000) / 60);
if (lon > 180)
return false;
if (data[8] == 'W')
lon = -lon;
return true;
}
static bool readAltitude(const char *data, qreal &ele)
{
int p;
if (!(data[0] == 'A' || data[0] == 'V'))
return false;
if (data[1] == '-')
p = str2int(data + 2, 4);
else
p = str2int(data + 1, 5);
int g = str2int(data + 6, 5);
if (p < 0 || g < 0)
return false;
if (data[0] == 'A')
ele = (qreal)g;
else
ele = NAN;
return true;
}
static bool readTimestamp(const char *data, QTime &time)
{
int h = str2int(data, 2);
int m = str2int(data + 2, 2);
int s = str2int(data + 4, 2);
if (h < 0 || m < 0 || s < 0)
return false;
time = QTime(h, m, s);
if (!time.isValid())
return false;
return true;
}
static bool readARecord(const char *line, qint64 len)
{
if (len < 9 || line[0] != 'A')
return false;
for (int i = 1; i < 7; i++)
if (!::isprint(line[i]))
return false;
return true;
}
bool IGCParser::readHRecord(const char *line, int len)
{
if (len < 10 || ::strncmp(line, "HFDTE", 5))
return true;
int d = str2int(line + 5, 2);
int m = str2int(line + 7, 2);
int y = str2int(line + 9, 2);
if (y < 0 || m < 0 || d < 0) {
_errorString = "Invalid date header format";
return false;
}
_date = QDate(y + 2000 < QDate::currentDate().year() ? 2000 + y : 1900 + y,
m, d);
if (!_date.isValid()) {
_errorString = "Invalid date";
return false;
}
return true;
}
bool IGCParser::readBRecord(TrackData &track, const char *line, int len)
{
qreal lat, lon, ele;
QTime time;
if (len < 35)
return false;
if (!readTimestamp(line + 1, time)) {
_errorString = "Invalid timestamp";
return false;
}
if (!readLat(line + 7, lat)) {
_errorString = "Invalid latitude";
return false;
}
if (!readLon(line + 15, lon)) {
_errorString = "Invalid longitude";
return false;
}
if (!readAltitude(line + 24, ele)) {
_errorString = "Invalid altitude";
return false;
}
if (time < _time)
_date = _date.addDays(1);
_time = time;
Trackpoint t(Coordinates(lon, lat));
t.setTimestamp(QDateTime(_date, _time, Qt::UTC));
t.setElevation(ele);
track.append(t);
return true;
}
bool IGCParser::readCRecord(RouteData &route, const char *line, int len)
{
qreal lat, lon;
if (len < 18)
return false;
if (!readLat(line + 1, lat)) {
_errorString = "Invalid latitude";
return false;
}
if (!readLon(line + 9, lon)) {
_errorString = "Invalid longitude";
return false;
}
if (!(lat == 0 && lon == 0)) {
QByteArray ba(line + 18, len - 19);
Waypoint w(Coordinates(lon, lat));
w.setName(QString(ba.trimmed()));
route.append(w);
}
return true;
}
bool IGCParser::parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints)
{
Q_UNUSED(waypoints);
qint64 len;
char line[76 + 2 + 1 + 1];
bool route = false, track = false;
_errorLine = 1;
_errorString.clear();
while (!file->atEnd()) {
len = file->readLine(line, sizeof(line));
if (len < 0) {
_errorString = "I/O error";
return false;
} else if (len > (qint64)sizeof(line) - 1) {
_errorString = "Line limit exceeded";
return false;
}
if (_errorLine == 1) {
if (!readARecord(line, len)) {
_errorString = "Invalid/missing A record";
return false;
}
} else {
if (line[0] == 'H') {
if (!readHRecord(line, len))
return false;
} else if (line[0] == 'C') {
if (route) {
if (!readCRecord(routes.last() ,line, len))
return false;
} else {
route = true;
routes.append(RouteData());
}
} else if (line[0] == 'B') {
if (_date.isNull()) {
_errorString = "Missing date header";
return false;
}
if (!track) {
tracks.append(TrackData());
_time = QTime(0, 0);
track = true;
}
if (!readBRecord(tracks.last(), line, len))
return false;
}
}
_errorLine++;
}
return true;
}

32
src/data/igcparser.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef IGCPARSER_H
#define IGCPARSER_H
#include <QDate>
#include <QTime>
#include "parser.h"
class IGCParser : public Parser
{
public:
IGCParser() : _errorLine(0) {}
~IGCParser() {}
bool parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints);
QString errorString() const {return _errorString;}
int errorLine() const {return _errorLine;}
private:
bool readHRecord(const char *line, int len);
bool readBRecord(TrackData &track, const char *line, int len);
bool readCRecord(RouteData &route, const char *line, int len);
int _errorLine;
QString _errorString;
QDate _date;
QTime _time;
};
#endif // IGCPARSER_H

505
src/data/kmlparser.cpp Normal file
View File

@ -0,0 +1,505 @@
#include "kmlparser.h"
qreal KMLParser::number()
{
bool res;
qreal ret = _reader.readElementText().toDouble(&res);
if (!res)
_reader.raiseError(QString("Invalid %1").arg(
_reader.name().toString()));
return ret;
}
QDateTime KMLParser::time()
{
QDateTime d = QDateTime::fromString(_reader.readElementText(),
Qt::ISODate);
if (!d.isValid())
_reader.raiseError(QString("Invalid %1").arg(
_reader.name().toString()));
return d;
}
bool KMLParser::coord(Trackpoint &trackpoint)
{
QString data = _reader.readElementText();
const QChar *sp, *ep, *cp, *vp;
int c = 0;
qreal val[3];
bool res;
sp = data.constData();
ep = sp + data.size();
for (cp = sp; cp < ep; cp++)
if (!cp->isSpace())
break;
for (vp = cp; cp <= ep; cp++) {
if (cp->isSpace() || cp->isNull()) {
if (c > 2)
return false;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
val[c] = QString(vp, cp - vp).toDouble(&res);
#else // QT_VERSION < 5
val[c] = QStringRef(&data, vp - sp, cp - vp).toDouble(&res);
#endif // QT_VERSION < 5
if (!res)
return false;
if (c == 1) {
trackpoint.setCoordinates(Coordinates(val[0], val[1]));
if (!trackpoint.coordinates().isValid())
return false;
} else if (c == 2)
trackpoint.setElevation(val[2]);
while (cp->isSpace())
cp++;
vp = cp;
c++;
}
}
return true;
}
bool KMLParser::pointCoordinates(Waypoint &waypoint)
{
QString data = _reader.readElementText();
const QChar *sp, *ep, *cp, *vp;
int c = 0;
qreal val[3];
bool res;
sp = data.constData();
ep = sp + data.size();
for (cp = sp; cp < ep; cp++)
if (!cp->isSpace())
break;
for (vp = cp; cp <= ep; cp++) {
if (*cp == ',') {
if (c > 1)
return false;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
val[c] = QString(vp, cp - vp).toDouble(&res);
#else // QT_VERSION < 5
val[c] = QStringRef(&data, vp - sp, cp - vp).toDouble(&res);
#endif // QT_VERSION < 5
if (!res)
return false;
c++;
vp = cp + 1;
} else if (cp->isSpace() || cp->isNull()) {
if (c < 1)
return false;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
val[c] = QString(vp, cp - vp).toDouble(&res);
#else // QT_VERSION < 5
val[c] = QStringRef(&data, vp - sp, cp - vp).toDouble(&res);
#endif // QT_VERSION < 5
if (!res)
return false;
waypoint.setCoordinates(Coordinates(val[0], val[1]));
if (!waypoint.coordinates().isValid())
return false;
if (c == 2)
waypoint.setElevation(val[2]);
while (cp->isSpace())
cp++;
c = 3;
}
}
return true;
}
bool KMLParser::lineCoordinates(TrackData &track)
{
QString data = _reader.readElementText();
const QChar *sp, *ep, *cp, *vp;
int c = 0;
qreal val[3];
bool res;
sp = data.constData();
ep = sp + data.size();
for (cp = sp; cp < ep; cp++)
if (!cp->isSpace())
break;
for (vp = cp; cp <= ep; cp++) {
if (*cp == ',') {
if (c > 1)
return false;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
val[c] = QString(vp, cp - vp).toDouble(&res);
#else // QT_VERSION < 5
val[c] = QStringRef(&data, vp - sp, cp - vp).toDouble(&res);
#endif // QT_VERSION < 5
if (!res)
return false;
c++;
vp = cp + 1;
} else if (cp->isSpace() || cp->isNull()) {
if (c < 1 || c > 2)
return false;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
val[c] = QString(vp, cp - vp).toDouble(&res);
#else // QT_VERSION < 5
val[c] = QStringRef(&data, vp - sp, cp - vp).toDouble(&res);
#endif // QT_VERSION < 5
if (!res)
return false;
track.append(Trackpoint(Coordinates(val[0], val[1])));
if (!track.last().coordinates().isValid())
return false;
if (c == 2)
track.last().setElevation(val[2]);
while (cp->isSpace())
cp++;
c = 0;
vp = cp;
}
}
return true;
}
QDateTime KMLParser::timeStamp()
{
QDateTime ts;
while (_reader.readNextStartElement()) {
if (_reader.name() == "when")
ts = time();
else
_reader.skipCurrentElement();
}
return ts;
}
void KMLParser::lineString(TrackData &track)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "coordinates") {
if (!lineCoordinates(track))
_reader.raiseError("Invalid coordinates");
} else
_reader.skipCurrentElement();
}
}
void KMLParser::point(Waypoint &waypoint)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "coordinates") {
if (!pointCoordinates(waypoint))
_reader.raiseError("Invalid coordinates");
} else
_reader.skipCurrentElement();
}
if (waypoint.coordinates().isNull())
_reader.raiseError("Missing Point coordinates");
}
void KMLParser::heartRate(TrackData &track, int start)
{
int i = start;
const char error[] = "Heartrate data count mismatch";
while (_reader.readNextStartElement()) {
if (_reader.name() == "value") {
if (i < track.size())
track[i++].setHeartRate(number());
else {
_reader.raiseError(error);
return;
}
} else
_reader.skipCurrentElement();
}
if (i != track.size())
_reader.raiseError(error);
}
void KMLParser::cadence(TrackData &track, int start)
{
int i = start;
const char error[] = "Cadence data count mismatch";
while (_reader.readNextStartElement()) {
if (_reader.name() == "value") {
if (i < track.size())
track[i++].setCadence(number());
else {
_reader.raiseError(error);
return;
}
} else
_reader.skipCurrentElement();
}
if (i != track.size())
_reader.raiseError(error);
}
void KMLParser::speed(TrackData &track, int start)
{
int i = start;
const char error[] = "Speed data count mismatch";
while (_reader.readNextStartElement()) {
if (_reader.name() == "value") {
if (i < track.size())
track[i++].setSpeed(number());
else {
_reader.raiseError(error);
return;
}
} else
_reader.skipCurrentElement();
}
if (i != track.size())
_reader.raiseError(error);
}
void KMLParser::temperature(TrackData &track, int start)
{
int i = start;
const char error[] = "Temperature data count mismatch";
while (_reader.readNextStartElement()) {
if (_reader.name() == "value") {
if (i < track.size())
track[i++].setTemperature(number());
else {
_reader.raiseError(error);
return;
}
} else
_reader.skipCurrentElement();
}
if (i != track.size())
_reader.raiseError(error);
}
void KMLParser::schemaData(TrackData &track, int start)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "SimpleArrayData") {
QXmlStreamAttributes attr = _reader.attributes();
QStringRef name = attr.value("name");
if (name == "Heartrate")
heartRate(track, start);
else if (name == "Cadence")
cadence(track, start);
else if (name == "Speed")
speed(track, start);
else if (name == "Temperature")
temperature(track, start);
else
_reader.skipCurrentElement();
} else
_reader.skipCurrentElement();
}
}
void KMLParser::extendedData(TrackData &track, int start)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "SchemaData")
schemaData(track, start);
else
_reader.skipCurrentElement();
}
}
void KMLParser::track(TrackData &track)
{
const char error[] = "gx:coord/when element count mismatch";
int first = track.size();
int i = first;
while (_reader.readNextStartElement()) {
if (_reader.name() == "when") {
track.append(Trackpoint());
track.last().setTimestamp(time());
} else if (_reader.name() == "coord") {
if (i == track.size()) {
_reader.raiseError(error);
return;
} else if (!coord(track[i])) {
_reader.raiseError("Invalid coordinates");
return;
}
i++;
} else if (_reader.name() == "ExtendedData")
extendedData(track, first);
else
_reader.skipCurrentElement();
}
if (i != track.size())
_reader.raiseError(error);
}
void KMLParser::multiTrack(TrackData &t)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Track")
track(t);
else
_reader.skipCurrentElement();
}
}
void KMLParser::multiGeometry(QList<TrackData> &tracks,
QList<Waypoint> &waypoints, const QString &name, const QString &desc,
const QDateTime timestamp)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Point") {
waypoints.append(Waypoint());
Waypoint &w = waypoints.last();
w.setName(name);
w.setDescription(desc);
w.setTimestamp(timestamp);
point(w);
} else if (_reader.name() == "LineString") {
tracks.append(TrackData());
TrackData &t = tracks.last();
t.setName(name);
t.setDescription(desc);
lineString(t);
} else
_reader.skipCurrentElement();
}
}
void KMLParser::placemark(QList<TrackData> &tracks, QList<Waypoint> &waypoints)
{
QString name, desc;
QDateTime timestamp;
while (_reader.readNextStartElement()) {
if (_reader.name() == "name")
name = _reader.readElementText();
else if (_reader.name() == "description")
desc = _reader.readElementText();
else if (_reader.name() == "TimeStamp")
timestamp = timeStamp();
else if (_reader.name() == "MultiGeometry")
multiGeometry(tracks, waypoints, name, desc, timestamp);
else if (_reader.name() == "Point") {
waypoints.append(Waypoint());
Waypoint &w = waypoints.last();
w.setName(name);
w.setDescription(desc);
w.setTimestamp(timestamp);
point(w);
} else if (_reader.name() == "LineString"
|| _reader.name() == "LinearRing") {
tracks.append(TrackData());
TrackData &t = tracks.last();
t.setName(name);
t.setDescription(desc);
lineString(t);
} else if (_reader.name() == "Track") {
tracks.append(TrackData());
TrackData &t = tracks.last();
t.setName(name);
t.setDescription(desc);
track(t);
} else if (_reader.name() == "MultiTrack") {
tracks.append(TrackData());
TrackData &t = tracks.last();
t.setName(name);
t.setDescription(desc);
multiTrack(t);
} else
_reader.skipCurrentElement();
}
}
void KMLParser::folder(QList<TrackData> &tracks, QList<Waypoint> &waypoints)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Placemark")
placemark(tracks, waypoints);
else if (_reader.name() == "Folder")
folder(tracks, waypoints);
else
_reader.skipCurrentElement();
}
}
void KMLParser::document(QList<TrackData> &tracks, QList<Waypoint> &waypoints)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Placemark")
placemark(tracks, waypoints);
else if (_reader.name() == "Folder")
folder(tracks, waypoints);
else
_reader.skipCurrentElement();
}
}
void KMLParser::kml(QList<TrackData> &tracks, QList<Waypoint> &waypoints)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Document")
document(tracks, waypoints);
else if (_reader.name() == "Placemark")
placemark(tracks, waypoints);
else if (_reader.name() == "Folder")
folder(tracks, waypoints);
else
_reader.skipCurrentElement();
}
}
bool KMLParser::parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints)
{
Q_UNUSED(routes);
_reader.clear();
_reader.setDevice(file);
if (_reader.readNextStartElement()) {
if (_reader.name() == "kml")
kml(tracks, waypoints);
else
_reader.raiseError("Not a KML file");
}
return !_reader.error();
}

45
src/data/kmlparser.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef KMLPARSER_H
#define KMLPARSER_H
#include <QXmlStreamReader>
#include <QDateTime>
#include "parser.h"
class KMLParser : public Parser
{
public:
~KMLParser() {}
bool parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints);
QString errorString() const {return _reader.errorString();}
int errorLine() const {return _reader.lineNumber();}
private:
void kml(QList<TrackData> &tracks, QList<Waypoint> &waypoints);
void document(QList<TrackData> &tracks, QList<Waypoint> &waypoints);
void folder(QList<TrackData> &tracks, QList<Waypoint> &waypoints);
void placemark(QList<TrackData> &tracks, QList<Waypoint> &waypoints);
void multiGeometry(QList<TrackData> &tracks, QList<Waypoint> &waypoints,
const QString &name, const QString &desc, const QDateTime timestamp);
void track(TrackData &track);
void multiTrack(TrackData &t);
void lineString(TrackData &track);
void point(Waypoint &waypoint);
bool pointCoordinates(Waypoint &waypoint);
bool lineCoordinates(TrackData &track);
bool coord(Trackpoint &trackpoint);
void extendedData(TrackData &track, int start);
void schemaData(TrackData &track, int start);
void heartRate(TrackData &track, int start);
void cadence(TrackData &track, int start);
void speed(TrackData &track, int start);
void temperature(TrackData &track, int start);
QDateTime timeStamp();
qreal number();
QDateTime time();
QXmlStreamReader _reader;
};
#endif // KMLPARSER_H

533
src/data/nmeaparser.cpp Normal file
View File

@ -0,0 +1,533 @@
#include <cstring>
#include "str2int.h"
#include "nmeaparser.h"
static bool validSentence(const char *line, int len)
{
const char *lp;
if (len < 12 || line[0] != '$')
return false;
for (lp = line + len - 1; lp > line + 3; lp--)
if (!::isspace(*lp))
break;
if (*(lp-2) != '*' || !::isalnum(*(lp-1)) || !::isalnum(*(lp)))
return false;
return true;
}
static bool readFloat(const char *data, int len, qreal &f)
{
bool ok;
f = QString(QByteArray::fromRawData(data, len)).toFloat(&ok);
return ok;
}
bool NMEAParser::readAltitude(const char *data, int len, qreal &ele)
{
if (!len) {
ele = NAN;
return true;
}
if (!readFloat(data, len, ele)) {
_errorString = "Invalid altitude";
return false;
}
return true;
}
bool NMEAParser::readGeoidHeight(const char *data, int len, qreal &gh)
{
if (!len) {
gh = 0;
return true;
}
if (!readFloat(data, len, gh)) {
_errorString = "Invalid geoid height";
return false;
}
return true;
}
bool NMEAParser::readTime(const char *data, int len, QTime &time)
{
int h, m, s, ms = 0;
if (!len) {
time = QTime();
return true;
}
if (len < 6)
goto error;
h = str2int(data, 2);
m = str2int(data + 2, 2);
s = str2int(data + 4, 2);
if (h < 0 || m < 0 || s < 0)
goto error;
if (len > 7 && data[6] == '.') {
if ((ms = str2int(data + 7, len - 7)) < 0)
goto error;
}
time = QTime(h, m, s, ms);
if (!time.isValid())
goto error;
return true;
error:
_errorString = "Invalid time";
return false;
}
bool NMEAParser::readDate(const char *data, int len, QDate &date)
{
int y, m, d;
if (!len) {
date = QDate();
return true;
}
if (len < 6)
goto error;
d = str2int(data, 2);
m = str2int(data + 2, 2);
y = str2int(data + 4, len - 4);
if (d < 0 || m < 0 || y < 0)
goto error;
if (len - 4 == 2)
date = QDate(y + 2000 < QDate::currentDate().year()
? 2000 + y : 1900 + y, m, d);
else
date = QDate(y, m, d);
if (!date.isValid())
goto error;
return true;
error:
_errorString = "Invalid date";
return false;
}
bool NMEAParser::readLat(const char *data, int len, qreal &lat)
{
int d, mi;
qreal mf;
bool ok;
if (!len) {
lat = NAN;
return true;
}
if (len < 7 || data[4] != '.')
goto error;
d = str2int(data, 2);
mi = str2int(data + 2, 2);
mf = QString(QByteArray::fromRawData(data + 4, len - 4)).toFloat(&ok);
if (d < 0 || mi < 0 || !ok)
goto error;
lat = d + (((qreal)mi + mf) / 60.0);
if (lat > 90)
goto error;
return true;
error:
_errorString = "Invalid ltitude";
return false;
}
bool NMEAParser::readNS(const char *data, int len, qreal &lat)
{
if (!len) {
lat = NAN;
return true;
}
if (len != 1 || !(*data == 'N' || *data == 'S')) {
_errorString = "Invalid N/S value";
return false;
}
if (*data == 'S')
lat = -lat;
return true;
}
bool NMEAParser::readLon(const char *data, int len, qreal &lon)
{
int d, mi;
qreal mf;
bool ok;
if (!len) {
lon = NAN;
return true;
}
if (len < 8 || data[5] != '.')
goto error;
d = str2int(data, 3);
mi = str2int(data + 3, 2);
mf = QString(QByteArray::fromRawData(data + 5, len - 5)).toFloat(&ok);
if (d < 0 || mi < 0 || !ok)
goto error;
lon = d + (((qreal)mi + mf) / 60.0);
if (lon > 180)
goto error;
return true;
error:
_errorString = "Invalid longitude";
return false;
}
bool NMEAParser::readEW(const char *data, int len, qreal &lon)
{
if (!len) {
lon = NAN;
return true;
}
if (len != 1 || !(*data == 'E' || *data == 'W')) {
_errorString = "Invalid E/W value";
return false;
}
if (*data == 'W')
lon = -lon;
return true;
}
bool NMEAParser::readRMC(TrackData &track, const char *line, int len)
{
int col = 1;
const char *vp = line;
qreal lat, lon;
QTime time;
QDate date;
bool valid = true;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 1:
if (!readTime(vp, lp - vp, time))
return false;
break;
case 2:
if (*vp != 'A')
valid = false;
break;
case 3:
if (!readLat(vp, lp - vp, lat))
return false;
break;
case 4:
if (!readNS(vp, lp - vp, lat))
return false;
break;
case 5:
if (!readLon(vp, lp - vp, lon))
return false;
break;
case 6:
if (!readEW(vp, lp - vp, lon))
return false;
break;
case 9:
if (!readDate(vp, lp - vp, date))
return false;
break;
}
col++;
vp = lp + 1;
}
}
if (col < 9) {
_errorString = "Invalid RMC sentence";
return false;
}
if (!date.isNull()) {
if (_date.isNull() && !_time.isNull() && !track.isEmpty())
track.last().setTimestamp(QDateTime(date, _time, Qt::UTC));
_date = date;
}
Coordinates c(lon, lat);
if (valid && !_GGA && c.isValid()) {
Trackpoint t(c);
if (!_date.isNull() && !time.isNull())
t.setTimestamp(QDateTime(_date, time, Qt::UTC));
track.append(t);
}
return true;
}
bool NMEAParser::readGGA(TrackData &track, const char *line, int len)
{
int col = 1;
const char *vp = line;
qreal lat, lon, ele, gh;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 1:
if (!readTime(vp, lp - vp, _time))
return false;
break;
case 2:
if (!readLat(vp, lp - vp, lat))
return false;
break;
case 3:
if (!readNS(vp, lp - vp, lat))
return false;
break;
case 4:
if (!readLon(vp, lp - vp, lon))
return false;
break;
case 5:
if (!readEW(vp, lp - vp, lon))
return false;
break;
case 9:
if (!readAltitude(vp, lp - vp, ele))
return false;
break;
case 10:
if ((lp - vp) && !((lp - vp) == 1 && *vp == 'M')) {
_errorString = "Invalid altitude units";
return false;
}
break;
case 11:
if (!readGeoidHeight(vp, lp - vp, gh))
return false;
break;
case 12:
if ((lp - vp) && !((lp - vp) == 1 && *vp == 'M')) {
_errorString = "Invalid geoid height units";
return false;
}
break;
}
col++;
vp = lp + 1;
}
}
if (col < 12) {
_errorString = "Invalid GGA sentence";
return false;
}
Coordinates c(lon, lat);
if (c.isValid()) {
Trackpoint t(c);
if (!(_time.isNull() || _date.isNull()))
t.setTimestamp(QDateTime(_date, _time, Qt::UTC));
if (!std::isnan(ele))
t.setElevation(ele - gh);
track.append(t);
_GGA = true;
}
return true;
}
bool NMEAParser::readWPL(QList<Waypoint> &waypoints, const char *line, int len)
{
int col = 1;
const char *vp = line;
qreal lat, lon;
QString name;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 1:
if (!readLat(vp, lp - vp, lat))
return false;
break;
case 2:
if (!readNS(vp, lp - vp, lat))
return false;
break;
case 3:
if (!readLon(vp, lp - vp, lon))
return false;
break;
case 4:
if (!readEW(vp, lp - vp, lon))
return false;
break;
case 5:
name = QString(QByteArray(vp, lp - vp));
break;
}
col++;
vp = lp + 1;
}
}
if (col < 4) {
_errorString = "Invalid WPL sentence";
return false;
}
Coordinates c(lon, lat);
if (c.isValid()) {
Waypoint w(c);
w.setName(name);
waypoints.append(w);
}
return true;
}
bool NMEAParser::readZDA(const char *line, int len)
{
int col = 1;
const char *vp = line;
int d, m, y;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 2:
if (!(lp - vp))
return true;
if ((d = str2int(vp, lp - vp)) < 0) {
_errorString = "Invalid day";
return false;
}
break;
case 3:
if (!(lp - vp))
return true;
if ((m = str2int(vp, lp - vp)) < 0) {
_errorString = "Invalid month";
return false;
}
break;
case 4:
if (!(lp - vp))
return true;
if ((y = str2int(vp, lp - vp)) < 0) {
_errorString = "Invalid year";
return false;
}
break;
}
col++;
vp = lp + 1;
}
}
if (col < 4) {
_errorString = "Invalid ZDA sentence";
return false;
}
_date = QDate(y, m, d);
if (!_date.isValid()) {
_errorString = "Invalid date";
return false;
}
return true;
}
bool NMEAParser::parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints)
{
Q_UNUSED(routes);
qint64 len;
char line[80 + 2 + 1 + 1];
_errorLine = 1;
_errorString.clear();
_date = QDate();
_time = QTime();
_GGA = false;
tracks.append(TrackData());
TrackData &track = tracks.last();
while (!file->atEnd()) {
len = file->readLine(line, sizeof(line));
if (len < 0) {
_errorString = "I/O error";
return false;
} else if (len > (qint64)sizeof(line) - 1) {
_errorString = "Line limit exceeded";
return false;
}
if (validSentence(line, len)) {
if (!memcmp(line + 3, "RMC,", 4)) {
if (!readRMC(track, line + 7, len - 7))
return false;
} else if (!memcmp(line + 3, "GGA,", 4)) {
if (!readGGA(track, line + 7, len - 7))
return false;
} else if (!memcmp(line + 3, "WPL,", 4)) {
if (!readWPL(waypoints, line + 7, len - 7))
return false;
} else if (!memcmp(line + 3, "ZDA,", 4)) {
if (!readZDA(line + 7, len - 7))
return false;
}
}
_errorLine++;
}
if (!tracks.last().size() && !waypoints.size()) {
_errorString = "No usable NMEA sentence found";
return false;
}
return true;
}

42
src/data/nmeaparser.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef NMEAPARSER_H
#define NMEAPARSER_H
#include <QDate>
#include "parser.h"
class NMEAParser : public Parser
{
public:
NMEAParser() : _errorLine(0), _GGA(false) {}
~NMEAParser() {}
bool parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints);
QString errorString() const {return _errorString;}
int errorLine() const {return _errorLine;}
private:
bool readEW(const char *data, int len, qreal &lon);
bool readLon(const char *data, int len, qreal &lon);
bool readNS(const char *data, int len, qreal &lat);
bool readLat(const char *data, int len, qreal &lat);
bool readDate(const char *data, int len, QDate &date);
bool readTime(const char *data, int len, QTime &time);
bool readAltitude(const char *data, int len, qreal &ele);
bool readGeoidHeight(const char *data, int len, qreal &gh);
bool readRMC(TrackData &track, const char *line, int len);
bool readGGA(TrackData &track, const char *line, int len);
bool readWPL(QList<Waypoint> &waypoints, const char *line, int len);
bool readZDA(const char *line, int len);
int _errorLine;
QString _errorString;
QDate _date;
QTime _time;
bool _GGA;
};
#endif // NMEAPARSER_H

23
src/data/parser.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef PARSER_H
#define PARSER_H
#include <QString>
#include <QList>
#include <QFile>
#include "trackdata.h"
#include "routedata.h"
#include "waypoint.h"
class Parser
{
public:
virtual ~Parser() {}
virtual bool parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints) = 0;
virtual QString errorString() const = 0;
virtual int errorLine() const = 0;
};
#endif // PARSER_H

20
src/data/path.cpp Normal file
View File

@ -0,0 +1,20 @@
#include "path.h"
RectC Path::boundingRect() const
{
if (size() < 2)
return RectC();
RectC ret(first().coordinates(), first().coordinates());
for (int i = 1; i < size(); i++)
ret.unite(at(i).coordinates());
return ret;
}
QDebug operator<<(QDebug dbg, const PathPoint &point)
{
dbg.nospace() << "PathPoint(" << point.distance() << ", "
<< point.coordinates() << ")";
return dbg.space();
}

35
src/data/path.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef PATH_H
#define PATH_H
#include <QVector>
#include <QRectF>
#include "common/coordinates.h"
#include "common/rectc.h"
class PathPoint
{
public:
PathPoint() :
_coordinates(Coordinates()), _distance(NAN) {}
PathPoint(const Coordinates &coordinates, qreal distance)
: _coordinates(coordinates), _distance(distance) {}
const Coordinates &coordinates() const {return _coordinates;}
qreal distance() const {return _distance;}
private:
Coordinates _coordinates;
qreal _distance;
};
Q_DECLARE_TYPEINFO(PathPoint, Q_PRIMITIVE_TYPE);
QDebug operator<<(QDebug dbg, const PathPoint &point);
class Path : public QVector<PathPoint>
{
public:
RectC boundingRect() const;
};
#endif // PATH_H

145
src/data/poi.cpp Normal file
View File

@ -0,0 +1,145 @@
#include <QFile>
#include "data.h"
#include "poi.h"
POI::POI(QObject *parent) : QObject(parent)
{
_errorLine = 0;
_radius = 1000;
}
bool POI::loadFile(const QString &fileName)
{
Data data;
FileIndex index;
_errorString.clear();
_errorLine = 0;
index.enabled = true;
index.start = _data.size();
if (!data.loadFile(fileName)) {
_errorString = data.errorString();
_errorLine = data.errorLine();
return false;
}
for (int i = 0; i < data.waypoints().size(); i++)
_data.append(data.waypoints().at(i));
index.end = _data.size() - 1;
for (int i = index.start; i <= index.end; i++) {
const Coordinates &p = _data.at(i).coordinates();
qreal c[2];
c[0] = p.lon();
c[1] = p.lat();
_tree.Insert(c, c, i);
}
_files.append(fileName);
_indexes.append(index);
emit pointsChanged();
return true;
}
static bool cb(size_t data, void* context)
{
QSet<int> *set = (QSet<int>*) context;
set->insert((int)data);
return true;
}
QList<Waypoint> POI::points(const Path &path) const
{
QList<Waypoint> ret;
QSet<int> set;
qreal min[2], max[2];
QSet<int>::const_iterator it;
for (int i = 0; i < path.count(); i++) {
const Coordinates &c = path.at(i).coordinates();
QPair<Coordinates, Coordinates> br = c.boundingRect(_radius);
min[0] = br.first.lon();
min[1] = br.first.lat();
max[0] = br.second.lon();
max[1] = br.second.lat();
_tree.Search(min, max, cb, &set);
}
for (it = set.constBegin(); it != set.constEnd(); ++it)
ret.append(_data.at(*it));
return ret;
}
QList<Waypoint> POI::points(const Waypoint &point) const
{
QList<Waypoint> ret;
QSet<int> set;
qreal min[2], max[2];
QSet<int>::const_iterator it;
const Coordinates &c = point.coordinates();
QPair<Coordinates, Coordinates> br = c.boundingRect(_radius);
min[0] = br.first.lon();
min[1] = br.first.lat();
max[0] = br.second.lon();
max[1] = br.second.lat();
_tree.Search(min, max, cb, &set);
for (it = set.constBegin(); it != set.constEnd(); ++it)
ret.append(_data.at(*it));
return ret;
}
void POI::enableFile(const QString &fileName, bool enable)
{
int i;
i = _files.indexOf(fileName);
Q_ASSERT(i >= 0);
_indexes[i].enabled = enable;
_tree.RemoveAll();
for (int i = 0; i < _indexes.count(); i++) {
FileIndex idx = _indexes.at(i);
if (!idx.enabled)
continue;
for (int j = idx.start; j <= idx.end; j++) {
const Coordinates &p = _data.at(j).coordinates();
qreal c[2];
c[0] = p.lon();
c[1] = p.lat();
_tree.Insert(c, c, j);
}
}
emit pointsChanged();
}
void POI::clear()
{
_tree.RemoveAll();
_data.clear();
_files.clear();
_indexes.clear();
emit pointsChanged();
}
void POI::setRadius(unsigned radius)
{
_radius = radius;
emit pointsChanged();
}

57
src/data/poi.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef POI_H
#define POI_H
#include <QList>
#include <QPointF>
#include <QString>
#include <QStringList>
#include "rtree.h"
#include "waypoint.h"
#include "path.h"
class WaypointItem;
class POI : public QObject
{
Q_OBJECT
public:
POI(QObject *parent = 0);
bool loadFile(const QString &fileName);
const QString &errorString() const {return _errorString;}
int errorLine() const {return _errorLine;}
unsigned radius() const {return _radius;}
void setRadius(unsigned radius);
QList<Waypoint> points(const Path &path) const;
QList<Waypoint> points(const Waypoint &point) const;
const QStringList &files() const {return _files;}
void enableFile(const QString &fileName, bool enable);
void clear();
signals:
void pointsChanged();
private:
typedef RTree<size_t, qreal, 2> POITree;
struct FileIndex {
int start;
int end;
bool enabled;
};
POITree _tree;
QVector<Waypoint> _data;
QStringList _files;
QList<FileIndex> _indexes;
unsigned _radius;
QString _errorString;
int _errorLine;
};
#endif // POI_H

39
src/data/route.cpp Normal file
View File

@ -0,0 +1,39 @@
#include "route.h"
Route::Route(const RouteData &data) : _data(data)
{
qreal dist = 0;
_distance.append(dist);
for (int i = 1; i < data.count(); i++) {
dist += data.at(i).coordinates().distanceTo(data.at(i-1).coordinates());
_distance.append(dist);
}
}
Path Route::path() const
{
Path ret;
for (int i = 0; i < _data.size(); i++)
ret.append(PathPoint(_data.at(i).coordinates(), _distance.at(i)));
return ret;
}
Graph Route::elevation() const
{
Graph graph;
for (int i = 0; i < _data.size(); i++)
if (_data.at(i).hasElevation())
graph.append(GraphPoint(_distance.at(i), NAN,
_data.at(i).elevation()));
return graph;
}
qreal Route::distance() const
{
return (_distance.isEmpty()) ? 0 : _distance.last();
}

32
src/data/route.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef ROUTE_H
#define ROUTE_H
#include <QVector>
#include "routedata.h"
#include "graph.h"
#include "path.h"
class Route
{
public:
Route(const RouteData &data);
Path path() const;
const QVector<Waypoint> &waypoints() const {return _data;}
Graph elevation() const;
qreal distance() const;
const QString &name() const {return _data.name();}
const QString &description() const {return _data.description();}
bool isNull() const {return (_data.count() < 2);}
private:
const RouteData &_data;
QVector<qreal> _distance;
};
#endif // ROUTE_H

21
src/data/routedata.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef ROUTEDATA_H
#define ROUTEDATA_H
#include <QVector>
#include <QString>
#include "waypoint.h"
class RouteData : public QVector<Waypoint>
{
public:
const QString& name() const {return _name;}
const QString& description() const {return _desc;}
void setName(const QString &name) {_name = name;}
void setDescription(const QString &desc) {_desc = desc;}
private:
QString _name;
QString _desc;
};
#endif // ROUTEDATA_H

1232
src/data/rtree.h Normal file

File diff suppressed because it is too large Load Diff

16
src/data/str2int.cpp Normal file
View File

@ -0,0 +1,16 @@
#include <cctype>
#include "str2int.h"
int str2int(const char *str, int len)
{
int res = 0;
for (const char *sp = str; sp < str + len; sp++) {
if (::isdigit(*sp))
res = res * 10 + *sp - '0';
else
return -1;
}
return res;
}

6
src/data/str2int.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef MISC_H
#define MISC_H
int str2int(const char *str, int len);
#endif // MISC_H

248
src/data/tcxparser.cpp Normal file
View File

@ -0,0 +1,248 @@
#include "tcxparser.h"
void TCXParser::warning(const char *text) const
{
const QFile *file = static_cast<QFile *>(_reader.device());
qWarning("%s:%lld: %s\n", qPrintable(file->fileName()),
_reader.lineNumber(), text);
}
qreal TCXParser::number()
{
bool res;
qreal ret = _reader.readElementText().toDouble(&res);
if (!res)
_reader.raiseError(QString("Invalid %1").arg(
_reader.name().toString()));
return ret;
}
QDateTime TCXParser::time()
{
QDateTime d = QDateTime::fromString(_reader.readElementText(),
Qt::ISODate);
if (!d.isValid())
_reader.raiseError(QString("Invalid %1").arg(
_reader.name().toString()));
return d;
}
Coordinates TCXParser::position()
{
Coordinates pos;
qreal val;
bool res;
while (_reader.readNextStartElement()) {
if (_reader.name() == "LatitudeDegrees") {
val = _reader.readElementText().toDouble(&res);
if (!res || (val < -90.0 || val > 90.0))
_reader.raiseError("Invalid LatitudeDegrees");
else
pos.setLat(val);
} else if (_reader.name() == "LongitudeDegrees") {
val = _reader.readElementText().toDouble(&res);
if (!res || (val < -180.0 || val > 180.0))
_reader.raiseError("Invalid LongitudeDegrees");
else
pos.setLon(val);
} else
_reader.skipCurrentElement();
}
return pos;
}
void TCXParser::heartRateBpm(Trackpoint &trackpoint)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Value")
trackpoint.setHeartRate(number());
else
_reader.skipCurrentElement();
}
}
void TCXParser::extensions(Trackpoint &trackpoint)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "RunCadence")
trackpoint.setCadence(number());
else if (_reader.name() == "Watts")
trackpoint.setPower(number());
else
_reader.skipCurrentElement();
}
}
void TCXParser::trackpointData(Trackpoint &trackpoint)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Position")
trackpoint.setCoordinates(position());
else if (_reader.name() == "AltitudeMeters")
trackpoint.setElevation(number());
else if (_reader.name() == "Time")
trackpoint.setTimestamp(time());
else if (_reader.name() == "HeartRateBpm")
heartRateBpm(trackpoint);
else if (_reader.name() == "Cadence")
trackpoint.setCadence(number());
else if (_reader.name() == "Extensions")
extensions(trackpoint);
else
_reader.skipCurrentElement();
}
}
void TCXParser::waypointData(Waypoint &waypoint)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Position")
waypoint.setCoordinates(position());
else if (_reader.name() == "Name")
waypoint.setName(_reader.readElementText());
else if (_reader.name() == "Notes")
waypoint.setDescription(_reader.readElementText());
else if (_reader.name() == "AltitudeMeters")
waypoint.setElevation(number());
else if (_reader.name() == "Time")
waypoint.setTimestamp(time());
else
_reader.skipCurrentElement();
}
}
void TCXParser::trackpoints(TrackData &track)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Trackpoint") {
Trackpoint t;
trackpointData(t);
if (t.coordinates().isValid())
track.append(t);
else
warning("Missing Trackpoint coordinates");
} else
_reader.skipCurrentElement();
}
}
void TCXParser::lap(TrackData &track)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Track")
trackpoints(track);
else
_reader.skipCurrentElement();
}
}
void TCXParser::course(QList<Waypoint> &waypoints, TrackData &track)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Track")
trackpoints(track);
else if (_reader.name() == "Name")
track.setName(_reader.readElementText());
else if (_reader.name() == "Notes")
track.setDescription(_reader.readElementText());
else if (_reader.name() == "CoursePoint") {
Waypoint w;
waypointData(w);
if (w.coordinates().isValid())
waypoints.append(w);
else
warning("Missing Trackpoint coordinates");
} else
_reader.skipCurrentElement();
}
}
void TCXParser::activity(TrackData &track)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Lap")
lap(track);
else
_reader.skipCurrentElement();
}
}
void TCXParser::courses(QList<TrackData> &tracks, QList<Waypoint> &waypoints)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Course") {
tracks.append(TrackData());
course(waypoints, tracks.back());
} else
_reader.skipCurrentElement();
}
}
void TCXParser::sport(QList<TrackData> &tracks)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Activity") {
tracks.append(TrackData());
activity(tracks.back());
} else
_reader.skipCurrentElement();
}
}
void TCXParser::multiSportSession(QList<TrackData> &tracks)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "FirstSport" || _reader.name() == "NextSport")
sport(tracks);
else
_reader.skipCurrentElement();
}
}
void TCXParser::activities(QList<TrackData> &tracks)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Activity") {
tracks.append(TrackData());
activity(tracks.back());
} else if (_reader.name() == "MultiSportSession")
multiSportSession(tracks);
else
_reader.skipCurrentElement();
}
}
void TCXParser::tcx(QList<TrackData> &tracks, QList<Waypoint> &waypoints)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Courses")
courses(tracks, waypoints);
else if (_reader.name() == "Activities")
activities(tracks);
else
_reader.skipCurrentElement();
}
}
bool TCXParser::parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints)
{
Q_UNUSED(routes);
_reader.clear();
_reader.setDevice(file);
if (_reader.readNextStartElement()) {
if (_reader.name() == "TrainingCenterDatabase")
tcx(tracks, waypoints);
else
_reader.raiseError("Not a TCX file");
}
return !_reader.error();
}

41
src/data/tcxparser.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef TCXPARSER_H
#define TCXPARSER_H
#include <QXmlStreamReader>
#include "parser.h"
class TCXParser : public Parser
{
public:
~TCXParser() {}
bool parse(QFile *file, QList<TrackData> &tracks,
QList<RouteData> &routes, QList<Waypoint> &waypoints);
QString errorString() const {return _reader.errorString();}
int errorLine() const {return _reader.lineNumber();}
private:
void tcx(QList<TrackData> &tracks, QList<Waypoint> &waypoints);
void courses(QList<TrackData> &tracks, QList<Waypoint> &waypoints);
void activities(QList<TrackData> &tracks);
void multiSportSession(QList<TrackData> &tracks);
void sport(QList<TrackData> &tracks);
void course(QList<Waypoint> &waypoints, TrackData &track);
void activity(TrackData &track);
void lap(TrackData &track);
void trackpoints(TrackData &track);
void trackpointData(Trackpoint &trackpoint);
void waypointData(Waypoint &waypoint);
void extensions(Trackpoint &trackpoint);
void heartRateBpm(Trackpoint &trackpoint);
Coordinates position();
qreal number();
QDateTime time();
void warning(const char *text) const;
QXmlStreamReader _reader;
};
#endif // TCXPARSER_H

291
src/data/track.cpp Normal file
View File

@ -0,0 +1,291 @@
#include "track.h"
#define OUTLIER_WINDOW 21
int Track::_elevationWindow = 3;
int Track::_speedWindow = 5;
int Track::_heartRateWindow = 3;
int Track::_cadenceWindow = 3;
int Track::_powerWindow = 3;
qreal Track::_pauseSpeed = 0.5;
int Track::_pauseInterval = 10;
bool Track::_outlierEliminate = true;
static qreal median(QVector<qreal> v)
{
qSort(v.begin(), v.end());
return v.at(v.size() / 2);
}
static qreal MAD(QVector<qreal> v, qreal m)
{
for (int i = 0; i < v.size(); i++)
v[i] = qAbs(v.at(i) - m);
qSort(v.begin(), v.end());
return v.at(v.size() / 2);
}
static QSet<int> eliminate(const QVector<qreal> &v, int window)
{
QSet<int> rm;
qreal m, M;
if (v.size() < window)
return rm;
for (int i = window/2; i < v.size() - window/2; i++) {
m = median(v.mid(i - window/2, window));
M = MAD(v.mid(i - window/2, window), m);
if (qAbs((0.6745 * (v.at(i) - m)) / M) > 3.5)
rm.insert(i);
}
return rm;
}
static Graph filter(const Graph &g, int window)
{
if (g.size() < window)
return Graph();
if (window < 2)
return Graph(g);
qreal acc = 0;
Graph ret(g.size());
for (int i = 0; i < window; i++)
acc += g.at(i).y();
for (int i = 0; i <= window/2; i++)
ret[i] = GraphPoint(g.at(i).s(), g.at(i).t(), acc/window);
for (int i = window/2 + 1; i < g.size() - window/2; i++) {
acc += g.at(i + window/2).y() - g.at(i - (window/2 + 1)).y();
ret[i] = GraphPoint(g.at(i).s(), g.at(i).t(), acc/window);
}
for (int i = g.size() - window/2; i < g.size(); i++)
ret[i] = GraphPoint(g.at(i).s(), g.at(i).t(), acc/window);
return ret;
}
Track::Track(const TrackData &data) : _data(data)
{
qreal dt, ds, total;
_time.append(0);
_distance.append(0);
_speed.append(0);
for (int i = 1; i < data.count(); i++) {
ds = data.at(i).coordinates().distanceTo(data.at(i-1).coordinates());
_distance.append(ds);
if (data.first().hasTimestamp() && data.at(i).hasTimestamp())
_time.append(_data.first().timestamp().msecsTo(
_data.at(i).timestamp()) / 1000.0);
else
_time.append(NAN);
if (std::isnan(_time.at(i)) || std::isnan(_time.at(i-1)))
_speed.append(NAN);
else {
dt = _time.at(i) - _time.at(i-1);
if (!dt) {
_speed.append(_speed.at(i-1));
continue;
}
_speed.append(ds / dt);
}
}
_pause = 0;
for (int i = 1; i < data.count(); i++) {
if (_time.at(i) > _time.at(i-1) + _pauseInterval
&& _speed.at(i) < _pauseSpeed) {
_pause += _time.at(i) - _time.at(i-1);
_stop.insert(i-1);
_stop.insert(i);
}
}
if (_outlierEliminate)
_outliers = eliminate(_speed, OUTLIER_WINDOW);
QSet<int>::const_iterator it;
for (it = _stop.constBegin(); it != _stop.constEnd(); ++it)
_outliers.remove(*it);
total = 0;
for (int i = 0; i < _data.size(); i++) {
if (_outliers.contains(i))
continue;
if (!discardStopPoint(i))
total += _distance.at(i);
_distance[i] = total;
}
}
Graph Track::elevation() const
{
Graph raw;
for (int i = 0; i < _data.size(); i++)
if (_data.at(i).hasElevation() && !_outliers.contains(i))
raw.append(GraphPoint(_distance.at(i), _time.at(i),
_data.at(i).elevation()));
return filter(raw, _elevationWindow);
}
Graph Track::speed() const
{
Graph raw, filtered;
qreal v;
QSet<int> stop;
for (int i = 0; i < _data.size(); i++) {
if (_stop.contains(i) && (!std::isnan(_speed.at(i))
|| _data.at(i).hasSpeed())) {
v = 0;
stop.insert(raw.size());
} else if (_data.at(i).hasSpeed() && !_outliers.contains(i))
v = _data.at(i).speed();
else if (!std::isnan(_speed.at(i)) && !_outliers.contains(i))
v = _speed.at(i);
else
continue;
raw.append(GraphPoint(_distance.at(i), _time.at(i), v));
}
filtered = filter(raw, _speedWindow);
QSet<int>::const_iterator it;
for (it = stop.constBegin(); it != stop.constEnd(); ++it)
filtered[*it].setY(0);
return filtered;
}
Graph Track::heartRate() const
{
Graph raw;
for (int i = 0; i < _data.count(); i++)
if (_data.at(i).hasHeartRate() && !_outliers.contains(i))
raw.append(GraphPoint(_distance.at(i), _time.at(i),
_data.at(i).heartRate()));
return filter(raw, _heartRateWindow);
}
Graph Track::temperature() const
{
Graph raw;
for (int i = 0; i < _data.size(); i++)
if (_data.at(i).hasTemperature() && !_outliers.contains(i))
raw.append(GraphPoint(_distance.at(i), _time.at(i),
_data.at(i).temperature()));
return raw;
}
Graph Track::cadence() const
{
Graph raw, filtered;
QSet<int> stop;
qreal c;
for (int i = 0; i < _data.size(); i++) {
if (_data.at(i).hasCadence() && _stop.contains(i)) {
c = 0;
stop.insert(raw.size());
} else if (_data.at(i).hasCadence() && !_outliers.contains(i))
c = _data.at(i).cadence();
else
continue;
raw.append(GraphPoint(_distance.at(i), _time.at(i), c));
}
filtered = filter(raw, _cadenceWindow);
QSet<int>::const_iterator it;
for (it = stop.constBegin(); it != stop.constEnd(); ++it)
filtered[*it].setY(0);
return filtered;
}
Graph Track::power() const
{
Graph raw, filtered;
QSet<int> stop;
qreal p;
for (int i = 0; i < _data.size(); i++) {
if (_data.at(i).hasPower() && _stop.contains(i)) {
p = 0;
stop.insert(raw.size());
} else if (_data.at(i).hasPower() && !_outliers.contains(i))
p = _data.at(i).power();
else
continue;
raw.append(GraphPoint(_distance.at(i), _time.at(i), p));
}
filtered = filter(raw, _powerWindow);
QSet<int>::const_iterator it;
for (it = stop.constBegin(); it != stop.constEnd(); ++it)
filtered[*it].setY(0);
return filtered;
}
qreal Track::distance() const
{
return _distance.isEmpty() ? 0 : _distance.last();
}
qreal Track::time() const
{
return (_data.size() < 2) ? 0 :
(_data.first().timestamp().msecsTo(_data.last().timestamp()) / 1000.0);
}
qreal Track::movingTime() const
{
return (time() - _pause);
}
QDateTime Track::date() const
{
return (_data.size()) ? _data.first().timestamp() : QDateTime();
}
Path Track::path() const
{
Path ret;
for (int i = 0; i < _data.size(); i++)
if (!_outliers.contains(i) && !discardStopPoint(i))
ret.append(PathPoint(_data.at(i).coordinates(), _distance.at(i)));
return ret;
}
bool Track::discardStopPoint(int i) const
{
return (_stop.contains(i) && i > 0 && _stop.contains(i-1)
&& i < _data.size() - 1 && _stop.contains(i+1));
}

72
src/data/track.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef TRACK_H
#define TRACK_H
#include <QVector>
#include <QSet>
#include <QDateTime>
#include "trackdata.h"
#include "graph.h"
#include "path.h"
class Track
{
public:
Track(const TrackData &data);
Path path() const;
Graph elevation() const;
Graph speed() const;
Graph heartRate() const;
Graph temperature() const;
Graph cadence() const;
Graph power() const;
qreal distance() const;
qreal time() const;
qreal movingTime() const;
QDateTime date() const;
const QString &name() const {return _data.name();}
const QString &description() const {return _data.description();}
bool isNull() const {return (_data.size() < 2);}
static void setElevationFilter(int window) {_elevationWindow = window;}
static void setSpeedFilter(int window) {_speedWindow = window;}
static void setHeartRateFilter(int window) {_heartRateWindow = window;}
static void setCadenceFilter(int window) {_cadenceWindow = window;}
static void setPowerFilter(int window) {_powerWindow = window;}
static void setPauseSpeed(qreal speed) {_pauseSpeed = speed;}
static void setPauseInterval(int interval) {_pauseInterval = interval;}
static void setOutlierElimination(bool eliminate)
{_outlierEliminate = eliminate;}
private:
bool discardStopPoint(int i) const;
const TrackData &_data;
QVector<qreal> _distance;
QVector<qreal> _time;
QVector<qreal> _speed;
QSet<int> _outliers;
QSet<int> _stop;
qreal _pause;
static bool _outlierEliminate;
static int _elevationWindow;
static int _speedWindow;
static int _heartRateWindow;
static int _cadenceWindow;
static int _powerWindow;
static qreal _pauseSpeed;
static int _pauseInterval;
};
#endif // TRACK_H

21
src/data/trackdata.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef TRACKDATA_H
#define TRACKDATA_H
#include <QVector>
#include <QString>
#include "trackpoint.h"
class TrackData : public QVector<Trackpoint>
{
public:
const QString& name() const {return _name;}
const QString& description() const {return _desc;}
void setName(const QString &name) {_name = name;}
void setDescription(const QString &desc) {_desc = desc;}
private:
QString _name;
QString _desc;
};
#endif // TRACKDATA_H

69
src/data/trackpoint.h Normal file
View File

@ -0,0 +1,69 @@
#ifndef TRACKPOINT_H
#define TRACKPOINT_H
#include <QDateTime>
#include <QDebug>
#include <cmath>
#include "common/coordinates.h"
class Trackpoint
{
public:
Trackpoint()
{_elevation = NAN; _speed = NAN; _heartRate = NAN; _temperature = NAN;
_cadence = NAN; _power = NAN;}
Trackpoint(const Coordinates &coordinates) : _coordinates(coordinates)
{_elevation = NAN; _speed = NAN; _heartRate = NAN; _temperature = NAN;
_cadence = NAN; _power = NAN;}
const Coordinates &coordinates() const {return _coordinates;}
Coordinates &rcoordinates() {return _coordinates;}
const QDateTime &timestamp() const {return _timestamp;}
qreal elevation() const {return _elevation;}
qreal speed() const {return _speed;}
qreal heartRate() const {return _heartRate;}
qreal temperature() const {return _temperature;}
qreal cadence() const {return _cadence;}
qreal power() const {return _power;}
void setCoordinates(const Coordinates &coordinates)
{_coordinates = coordinates;}
void setTimestamp(const QDateTime &timestamp) {_timestamp = timestamp;}
void setElevation(qreal elevation) {_elevation = elevation;}
void setSpeed(qreal speed) {_speed = speed;}
void setHeartRate(qreal heartRate) {_heartRate = heartRate;}
void setTemperature(qreal temperature) {_temperature = temperature;}
void setCadence(qreal cadence) {_cadence = cadence;}
void setPower(qreal power) {_power = power;}
bool hasTimestamp() const {return !_timestamp.isNull();}
bool hasElevation() const {return !std::isnan(_elevation);}
bool hasSpeed() const {return !std::isnan(_speed);}
bool hasHeartRate() const {return !std::isnan(_heartRate);}
bool hasTemperature() const {return !std::isnan(_temperature);}
bool hasCadence() const {return !std::isnan(_cadence);}
bool hasPower() const {return !std::isnan(_power);}
private:
Coordinates _coordinates;
QDateTime _timestamp;
qreal _elevation;
qreal _speed;
qreal _heartRate;
qreal _temperature;
qreal _cadence;
qreal _power;
};
Q_DECLARE_TYPEINFO(Trackpoint, Q_MOVABLE_TYPE);
inline QDebug operator<<(QDebug dbg, const Trackpoint &trackpoint)
{
dbg.nospace() << "Trackpoint(" << trackpoint.coordinates() << ", "
<< trackpoint.timestamp() << ", " << trackpoint.elevation() << ", "
<< trackpoint.speed() << ", " << trackpoint.heartRate() << ", "
<< trackpoint.temperature() << ")";
return dbg.space();
}
#endif // TRACKPOINT_H

60
src/data/waypoint.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef WAYPOINT_H
#define WAYPOINT_H
#include <QString>
#include <QDateTime>
#include <QHash>
#include <QDebug>
#include <cmath>
#include "common/coordinates.h"
class Waypoint
{
public:
Waypoint() {_elevation = NAN;}
Waypoint(const Coordinates &coordinates) : _coordinates(coordinates)
{_elevation = NAN;}
const Coordinates &coordinates() const {return _coordinates;}
const QString &name() const {return _name;}
const QString &description() const {return _description;}
const QDateTime &timestamp() const {return _timestamp;}
qreal elevation() const {return _elevation;}
void setCoordinates(const Coordinates &coordinates)
{_coordinates = coordinates;}
void setName(const QString &name) {_name = name;}
void setDescription(const QString &description)
{_description = description;}
void setTimestamp(const QDateTime &timestamp) {_timestamp = timestamp;}
void setElevation(qreal elevation) {_elevation = elevation;}
bool hasElevation() const {return !std::isnan(_elevation);}
bool operator==(const Waypoint &other) const
{return this->_name == other._name
&& this->_coordinates == other._coordinates;}
private:
Coordinates _coordinates;
QString _name;
QString _description;
QDateTime _timestamp;
qreal _elevation;
};
inline uint qHash(const Waypoint &key)
{
return ::qHash(key.name());
}
inline QDebug operator<<(QDebug dbg, const Waypoint &waypoint)
{
dbg.nospace() << "Waypoint(" << waypoint.coordinates() << ", "
<< waypoint.name() << ", " << waypoint.description() << ")";
return dbg.space();
}
Q_DECLARE_TYPEINFO(Waypoint, Q_MOVABLE_TYPE);
#endif // WAYPOINT_H