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:
47
src/data/csvparser.cpp
Normal file
47
src/data/csvparser.cpp
Normal 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
22
src/data/csvparser.h
Normal 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
118
src/data/data.cpp
Normal 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
51
src/data/data.h
Normal 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
364
src/data/fitparser.cpp
Normal 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
66
src/data/fitparser.h
Normal 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
202
src/data/gpxparser.cpp
Normal 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
35
src/data/gpxparser.h
Normal 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
42
src/data/graph.h
Normal 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
251
src/data/igcparser.cpp
Normal 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
32
src/data/igcparser.h
Normal 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
505
src/data/kmlparser.cpp
Normal 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
45
src/data/kmlparser.h
Normal 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
533
src/data/nmeaparser.cpp
Normal 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
42
src/data/nmeaparser.h
Normal 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
23
src/data/parser.h
Normal 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
20
src/data/path.cpp
Normal 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
35
src/data/path.h
Normal 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
145
src/data/poi.cpp
Normal 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
57
src/data/poi.h
Normal 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
39
src/data/route.cpp
Normal 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
32
src/data/route.h
Normal 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
21
src/data/routedata.h
Normal 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
1232
src/data/rtree.h
Normal file
File diff suppressed because it is too large
Load Diff
16
src/data/str2int.cpp
Normal file
16
src/data/str2int.cpp
Normal 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
6
src/data/str2int.h
Normal 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
248
src/data/tcxparser.cpp
Normal 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
41
src/data/tcxparser.h
Normal 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
291
src/data/track.cpp
Normal 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
72
src/data/track.h
Normal 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
21
src/data/trackdata.h
Normal 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
69
src/data/trackpoint.h
Normal 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 ×tamp() 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 ×tamp) {_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
60
src/data/waypoint.h
Normal 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 ×tamp() 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 ×tamp) {_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
|
Reference in New Issue
Block a user