From d7fd40d9d210f7a48cc37dd0a8f21e5c498b3d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Sun, 23 Oct 2016 11:09:20 +0200 Subject: [PATCH] Added support for TCX and CSV files --- gpxsee.pro | 17 ++- lang/gpxsee_cs.ts | 211 +++++++++++++++--------------- src/csvparser.cpp | 48 +++++++ src/csvparser.h | 24 ++++ src/data.cpp | 62 +++++++++ src/{gpx.h => data.h} | 21 +-- src/elevationgraph.cpp | 12 +- src/elevationgraph.h | 2 +- src/gpx.cpp | 47 ------- src/{parser.cpp => gpxparser.cpp} | 144 ++++++++++---------- src/gpxparser.h | 51 ++++++++ src/graphtab.h | 4 +- src/gui.cpp | 53 ++++---- src/gui.h | 1 + src/heartrategraph.cpp | 12 +- src/heartrategraph.h | 2 +- src/parser.h | 43 ++---- src/pathview.cpp | 14 +- src/pathview.h | 4 +- src/poi.cpp | 116 ++-------------- src/poi.h | 7 +- src/speedgraph.cpp | 14 +- src/speedgraph.h | 2 +- src/tcxparser.cpp | 181 +++++++++++++++++++++++++ src/tcxparser.h | 42 ++++++ src/temperaturegraph.cpp | 12 +- src/temperaturegraph.h | 2 +- 27 files changed, 702 insertions(+), 446 deletions(-) create mode 100644 src/csvparser.cpp create mode 100644 src/csvparser.h create mode 100644 src/data.cpp rename src/{gpx.h => data.h} (75%) delete mode 100644 src/gpx.cpp rename src/{parser.cpp => gpxparser.cpp} (72%) create mode 100644 src/gpxparser.h create mode 100644 src/tcxparser.cpp create mode 100644 src/tcxparser.h diff --git a/gpxsee.pro b/gpxsee.pro index a17e56a9..78529daf 100644 --- a/gpxsee.pro +++ b/gpxsee.pro @@ -8,8 +8,6 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += printsupport HEADERS += src/config.h \ src/icons.h \ src/gui.h \ - src/gpx.h \ - src/parser.h \ src/poi.h \ src/rtree.h \ src/ll.h \ @@ -53,11 +51,14 @@ HEADERS += src/config.h \ src/graph.h \ src/pathitem.h \ src/pathview.h \ - src/griditem.h + src/griditem.h \ + src/data.h \ + src/gpxparser.h \ + src/tcxparser.h \ + src/parser.h \ + src/csvparser.h SOURCES += src/main.cpp \ src/gui.cpp \ - src/gpx.cpp \ - src/parser.cpp \ src/poi.cpp \ src/ll.cpp \ src/axisitem.cpp \ @@ -93,7 +94,11 @@ SOURCES += src/main.cpp \ src/graphitem.cpp \ src/pathitem.cpp \ src/pathview.cpp \ - src/griditem.cpp + src/griditem.cpp \ + src/data.cpp \ + src/gpxparser.cpp \ + src/tcxparser.cpp \ + src/csvparser.cpp RESOURCES += gpxsee.qrc TRANSLATIONS = lang/gpxsee_cs.ts macx { diff --git a/lang/gpxsee_cs.ts b/lang/gpxsee_cs.ts index f7f6ddad..5a5004bb 100644 --- a/lang/gpxsee_cs.ts +++ b/lang/gpxsee_cs.ts @@ -132,10 +132,6 @@ FileSelectWidget - - Browse... - Procházet... - Select file @@ -145,260 +141,276 @@ GUI - + GPXSee is distributed under the terms of the GNU General Public License version 3. For more info about GPXSee visit the project homepage at Program GPXSee je distribuován pod podmínkami licence GNU General Public License verze 3. Pro více informací navštivte stránky programu na adrese - + Open file Otevřít soubor - + Open POI file Otevřít POI soubor - + Open Otevřít - + Quit Ukončit - - - + + + Keyboard controls Ovládací klávesy - + Close Zavřít - + Reload Znovu načíst - + Show Zobrazit - - + + File Soubor - - - + + + Data sources Zdroje dat - + Load POI file Nahrát POI soubor - + Close POI files Zavřit POI soubory - + Overlap POIs Překrývat POI - + Show POI labels Zobrazovat názvy POI - + Show POIs Zobrazit POI - + Show map Zobrazit mapu - + Clear tile cache Vymazat mezipaměť dlaždic - - - + + + Next map Následující mapa - + Show tracks Zobrazit cesty - + Show routes Zobrazit trasy - + Show waypoints Zobrazit navigační body - + Waypoint labels Názvy navigačních bodů - + Show graphs Zobrazovat grafy - + Show grid Zobrazit mřížku - + Show toolbars Zobrazovat nástrojové lišty - + Metric Metrické - + Imperial Imperiální - + Fullscreen mode Celoobrazovkový režim - + Next Následující - + Previous Předchozí - + Last Poslední - + First První - + Map Mapa - + Graph Graf - + POI POI - + POI files POI soubory - + Data Data - + Display Zobrazit - + Settings Nastavení - + Units Jednotky - + Help Nápověda - + Previous map Předchozí mapa - + + GPX files (*.gpx) Soubory GPX (*.gpx) - + + + TCX files (*.tcx) + Soubory TCX (*.tcx) + + + + Error loading data file: +%1 + Datový soubor nelze otevřít: +%1 + + + + CSV files (*.csv) Soubory CSV (*.csv) - + + All files (*) Všechny soubory (*) - - + + Date Datum - + Routes Trasy - + No GPX files loaded Nejsou načteny žádné GPX soubory - + %1 files %1 soubor @@ -407,139 +419,128 @@ - + Next file Následující soubor - + Version Verze - + Print... Tisknout... - + Export to PDF... Exportovat do PDF... - - All POI files (*.gpx *.csv) - Všechny POI soubory (*.gpx *.csv) - - - + Waypoints Navigační body - + Previous file Předchozí soubor - + Route waypoints Body tras - + First file První soubor - + Last file Poslední soubor - + Append modifier Modifikátor nahradit/přidat - + Map (tiles) source URLs are read on program startup from the following file: URL mapových zdrojů (dlaždic) jsou načteny při startu programu z následujícího souboru: - + The file format is one map entry per line, consisting of the map name and tiles URL delimited by a TAB character. The tile X and Y coordinates are replaced with $x and $y in the URL and the zoom level is replaced with $z. An example map file could look like: Formát souboru je jeden mapový záznam na řádku, kde mapový záznam sestává ze jména mapy a URL dlaždic navzájem oddělených tabulátorem. Souřadnice dlaždice jsou v URL nahrazeny řetězci $x a $y, úroven přiblížení (zoom) pak řetězcem $z. Příklad: - + To make GPXSee load a POI file automatically on startup, add the file to the following directory: POI soubory, které se mají automaticky nahrát při startu programu jsou načítány z následujícího adresáře: - - GPX files (*.gpx);;All files (*) - Soubory GPX (*.gpx);;Všechny soubory (*) + + + All supported files (*.gpx *.tcx *.csv) + Všechny podporované soubory (*.gpx *.tcx *.csv) - - + + Line: %1 Řádka: %1 - + Tracks Cesty - - + + About GPXSee O aplikaci GPXSee - + Navigation Navigace - + Map sources Mapové zdroje - + POIs POI body - - + + Distance Vzdálenost - - + + Time Čas - - + + Error Chyba - - Error loading GPX file: -%1 - Soubor GPX nelze otevřít: -%1 - - - + Error loading POI file: %1 Soubor POI nelze otevřít: diff --git a/src/csvparser.cpp b/src/csvparser.cpp new file mode 100644 index 00000000..132b3d18 --- /dev/null +++ b/src/csvparser.cpp @@ -0,0 +1,48 @@ +#include "csvparser.h" + +bool CSVParser::loadFile(QIODevice *device) +{ + bool ret; + int ln = 1; + + _errorLine = 0; + _errorString.clear(); + + while (!device->atEnd()) { + QByteArray line = device->readLine(); + QList list = line.split(','); + if (list.size() < 3) { + _errorString = "Parse error."; + _errorLine = ln; + return false; + } + + qreal lat = list[0].trimmed().toDouble(&ret); + if (!ret) { + _errorString = "Invalid latitude."; + _errorLine = ln; + return false; + } + qreal lon = list[1].trimmed().toDouble(&ret); + if (!ret) { + _errorString = "Invalid longitude."; + _errorLine = ln; + return false; + } + Waypoint wp(QPointF(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); + ln++; + } + + return true; +} diff --git a/src/csvparser.h b/src/csvparser.h new file mode 100644 index 00000000..c1b09de4 --- /dev/null +++ b/src/csvparser.h @@ -0,0 +1,24 @@ +#ifndef CSVPARSER_H +#define CSVPARSER_H + +#include "parser.h" + +class CSVParser : public Parser +{ +public: + CSVParser(QList > &tracks, + QList > &routes, QList &waypoints) + : Parser(tracks, routes, waypoints) {_errorLine = 0;} + ~CSVParser() {} + + bool loadFile(QIODevice *device); + QString errorString() const {return _errorString;} + int errorLine() const {return _errorLine;} + const char *name() const {return "CSV";} + +private: + QString _errorString; + int _errorLine; +}; + +#endif // CSVPARSER_H diff --git a/src/data.cpp b/src/data.cpp new file mode 100644 index 00000000..cb1c3165 --- /dev/null +++ b/src/data.cpp @@ -0,0 +1,62 @@ +#include +#include +#include "ll.h" +#include "gpxparser.h" +#include "tcxparser.h" +#include "csvparser.h" +#include "data.h" + + +Data::Data() : _errorLine(0) +{ + _parsers << new GPXParser(_track_data, _route_data, _waypoint_data); + _parsers << new TCXParser(_track_data, _route_data, _waypoint_data); + _parsers << new CSVParser(_track_data, _route_data, _waypoint_data); +} + +Data::~Data() +{ + for(int i = 0; i < _parsers.count(); i++) + delete _parsers.at(i); + + for (int i = 0; i < _tracks.count(); i++) + delete _tracks.at(i); + for (int i = 0; i < _routes.count(); i++) + delete _routes.at(i); +} + +bool Data::loadFile(const QString &fileName) +{ + QFile file(fileName); + + _errorString.clear(); + _errorLine = 0; + + if (!file.open(QFile::ReadOnly | QFile::Text)) { + _errorString = qPrintable(file.errorString()); + return false; + } + + for (int i = 0; i < _parsers.size(); i++) { + if (_parsers.at(i)->loadFile(&file)) { + for (int i = 0; i < _track_data.count(); i++) + _tracks.append(new Track(_track_data.at(i))); + for (int i = 0; i < _route_data.count(); i++) + _routes.append(new Route(_route_data.at(i))); + return true; + } + file.reset(); + } + + fprintf(stderr, "Error loading data file: %s:\n", qPrintable(fileName)); + for (int i = 0; i < _parsers.size(); i++) { + fprintf(stderr, "%s: line %d: %s\n", _parsers.at(i)->name(), + _parsers.at(i)->errorLine(), qPrintable(_parsers.at(i)->errorString())); + if (_parsers.at(i)->errorLine() > _errorLine) { + _errorLine = _parsers.at(i)->errorLine(); + _errorString = _parsers.at(i)->errorString(); + } + } + + return false; +} diff --git a/src/gpx.h b/src/data.h similarity index 75% rename from src/gpx.h rename to src/data.h index b1daaf8e..094c2c7f 100644 --- a/src/gpx.h +++ b/src/data.h @@ -1,5 +1,5 @@ -#ifndef GPX_H -#define GPX_H +#ifndef DATA_H +#define DATA_H #include #include @@ -8,16 +8,17 @@ #include "waypoint.h" #include "track.h" #include "route.h" -#include "parser.h" -class GPX +class Parser; + +class Data { public: - GPX(); - ~GPX(); + Data(); + ~Data(); bool loadFile(const QString &fileName); - const QString &errorString() const {return _error;} + const QString &errorString() const {return _errorString;} int errorLine() const {return _errorLine;} const QList &tracks() const {return _tracks;} @@ -25,9 +26,9 @@ public: const QList &waypoints() const {return _waypoint_data;} private: - Parser _parser; - QString _error; + QString _errorString; int _errorLine; + QList _parsers; QList _tracks; QList _routes; @@ -37,4 +38,4 @@ private: QList _waypoint_data; }; -#endif // GPX_H +#endif // DATA_H diff --git a/src/elevationgraph.cpp b/src/elevationgraph.cpp index e5b3dd09..952c3325 100644 --- a/src/elevationgraph.cpp +++ b/src/elevationgraph.cpp @@ -1,6 +1,6 @@ #include #include "config.h" -#include "gpx.h" +#include "data.h" #include "elevationgraph.h" @@ -107,14 +107,14 @@ void ElevationGraph::loadGraph(const Graph &graph, Type type, PathItem *path) GraphView::loadGraph(graph, path, type); } -void ElevationGraph::loadGPX(const GPX &gpx, const QList &paths) +void ElevationGraph::loadData(const Data &data, const QList &paths) { int p = 0; - for (int i = 0; i < gpx.tracks().count(); i++) - loadGraph(gpx.tracks().at(i)->elevation(), Track, paths.at(p++)); - for (int i = 0; i < gpx.routes().count(); i++) - loadGraph(gpx.routes().at(i)->elevation(), Route, paths.at(p++)); + for (int i = 0; i < data.tracks().count(); i++) + loadGraph(data.tracks().at(i)->elevation(), Track, paths.at(p++)); + for (int i = 0; i < data.routes().count(); i++) + loadGraph(data.routes().at(i)->elevation(), Route, paths.at(p++)); setInfo(); diff --git a/src/elevationgraph.h b/src/elevationgraph.h index 86c4f814..71e72c83 100644 --- a/src/elevationgraph.h +++ b/src/elevationgraph.h @@ -11,7 +11,7 @@ public: ElevationGraph(QWidget *parent = 0); QString label() const {return tr("Elevation");} - void loadGPX(const GPX &gpx, const QList &paths); + void loadData(const Data &data, const QList &paths); void clear(); void setUnits(enum Units units); void showTracks(bool show); diff --git a/src/gpx.cpp b/src/gpx.cpp deleted file mode 100644 index 161a8098..00000000 --- a/src/gpx.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include -#include -#include "ll.h" -#include "gpx.h" - - -GPX::GPX() : _parser(_track_data, _route_data, _waypoint_data), _errorLine(0) -{ -} - -GPX::~GPX() -{ - for (int i = 0; i < _tracks.count(); i++) - delete _tracks.at(i); - for (int i = 0; i < _routes.count(); i++) - delete _routes.at(i); -} - -bool GPX::loadFile(const QString &fileName) -{ - bool ret; - QFile file(fileName); - - - _error.clear(); - _errorLine = 0; - - if (!file.open(QFile::ReadOnly | QFile::Text)) { - _error = qPrintable(file.errorString()); - return false; - } - - ret = _parser.loadFile(&file); - file.close(); - if (ret == false) { - _error = _parser.errorString(); - _errorLine = _parser.errorLine(); - return false; - } - - for (int i = 0; i < _track_data.count(); i++) - _tracks.append(new Track(_track_data.at(i))); - for (int i = 0; i < _route_data.count(); i++) - _routes.append(new Route(_route_data.at(i))); - - return true; -} diff --git a/src/parser.cpp b/src/gpxparser.cpp similarity index 72% rename from src/parser.cpp rename to src/gpxparser.cpp index b428d409..6a17db70 100644 --- a/src/parser.cpp +++ b/src/gpxparser.cpp @@ -1,18 +1,18 @@ -#include "parser.h" +#include "gpxparser.h" -void Parser::handleTrackpointData(DataType type, const QString &value) +void GPXParser::handleTrackpointData(DataType type, const QString &value) { switch (type) { case Elevation: - _track->last().setElevation(value.toLatin1().toDouble()); + _track->last().setElevation(value.toDouble()); break; case Time: - _track->last().setTimestamp(QDateTime::fromString(value.toLatin1(), + _track->last().setTimestamp(QDateTime::fromString(value, Qt::ISODate)); break; case Geoidheight: - _track->last().setGeoidHeight(value.toLatin1().toDouble()); + _track->last().setGeoidHeight(value.toDouble()); break; case Speed: _track->last().setSpeed(value.toDouble()); @@ -28,7 +28,7 @@ void Parser::handleTrackpointData(DataType type, const QString &value) } } -void Parser::handleWaypointData(DataType type, const QString &value) +void GPXParser::handleWaypointData(DataType type, const QString &value) { switch (type) { case Name: @@ -37,22 +37,22 @@ void Parser::handleWaypointData(DataType type, const QString &value) case Description: _waypoints.last().setDescription(value); break; - case Time: - _waypoints.last().setTimestamp(QDateTime::fromString( - value.toLatin1(), Qt::ISODate)); - break; - case Elevation: - _waypoints.last().setElevation(value.toLatin1().toDouble()); - break; - case Geoidheight: - _waypoints.last().setGeoidHeight(value.toLatin1().toDouble()); - break; + case Time: + _waypoints.last().setTimestamp(QDateTime::fromString(value, + Qt::ISODate)); + break; + case Elevation: + _waypoints.last().setElevation(value.toDouble()); + break; + case Geoidheight: + _waypoints.last().setGeoidHeight(value.toDouble()); + break; default: break; } } -void Parser::handleRoutepointData(DataType type, const QString &value) +void GPXParser::handleRoutepointData(DataType type, const QString &value) { switch (type) { case Name: @@ -62,42 +62,42 @@ void Parser::handleRoutepointData(DataType type, const QString &value) _route->last().setDescription(value); break; case Time: - _route->last().setTimestamp(QDateTime::fromString( - value.toLatin1(), Qt::ISODate)); + _route->last().setTimestamp(QDateTime::fromString(value, + Qt::ISODate)); break; case Elevation: - _route->last().setElevation(value.toLatin1().toDouble()); + _route->last().setElevation(value.toDouble()); break; case Geoidheight: - _route->last().setGeoidHeight(value.toLatin1().toDouble()); + _route->last().setGeoidHeight(value.toDouble()); break; default: break; } } -void Parser::handleTrackpointAttributes(const QXmlStreamAttributes &attr) +void GPXParser::handleTrackpointAttributes(const QXmlStreamAttributes &attr) { _track->last().setCoordinates(QPointF( - attr.value("lon").toLatin1().toDouble(), - attr.value("lat").toLatin1().toDouble())); + attr.value("lon").toDouble(), + attr.value("lat").toDouble())); } -void Parser::handleRoutepointAttributes(const QXmlStreamAttributes &attr) +void GPXParser::handleRoutepointAttributes(const QXmlStreamAttributes &attr) { _route->last().setCoordinates(QPointF( - attr.value("lon").toLatin1().toDouble(), - attr.value("lat").toLatin1().toDouble())); + attr.value("lon").toDouble(), + attr.value("lat").toDouble())); } -void Parser::handleWaypointAttributes(const QXmlStreamAttributes &attr) +void GPXParser::handleWaypointAttributes(const QXmlStreamAttributes &attr) { _waypoints.last().setCoordinates(QPointF( - attr.value("lon").toLatin1().toDouble(), - attr.value("lat").toLatin1().toDouble())); + attr.value("lon").toDouble(), + attr.value("lat").toDouble())); } -void Parser::tpExtension() +void GPXParser::tpExtension() { while (_reader.readNextStartElement()) { if (_reader.name() == "hr") @@ -109,7 +109,7 @@ void Parser::tpExtension() } } -void Parser::extensions() +void GPXParser::extensions() { while (_reader.readNextStartElement()) { if (_reader.name() == "speed") @@ -125,7 +125,7 @@ void Parser::extensions() } } -void Parser::trackpointData() +void GPXParser::trackpointData() { while (_reader.readNextStartElement()) { if (_reader.name() == "ele") @@ -141,7 +141,7 @@ void Parser::trackpointData() } } -void Parser::routepointData() +void GPXParser::routepointData() { while (_reader.readNextStartElement()) { if (_reader.name() == "name") @@ -159,41 +159,7 @@ void Parser::routepointData() } } -void Parser::trackpoints() -{ - while (_reader.readNextStartElement()) { - if (_reader.name() == "trkpt") { - _track->append(Trackpoint()); - handleTrackpointAttributes(_reader.attributes()); - trackpointData(); - } else - _reader.skipCurrentElement(); - } -} - -void Parser::routepoints() -{ - while (_reader.readNextStartElement()) { - if (_reader.name() == "rtept") { - _route->append(Waypoint()); - handleRoutepointAttributes(_reader.attributes()); - routepointData(); - } else - _reader.skipCurrentElement(); - } -} - -void Parser::track() -{ - while (_reader.readNextStartElement()) { - if (_reader.name() == "trkseg") { - trackpoints(); - } else - _reader.skipCurrentElement(); - } -} - -void Parser::waypointData() +void GPXParser::waypointData() { while (_reader.readNextStartElement()) { if (_reader.name() == "name") @@ -211,7 +177,41 @@ void Parser::waypointData() } } -void Parser::gpx() +void GPXParser::trackpoints() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "trkpt") { + _track->append(Trackpoint()); + handleTrackpointAttributes(_reader.attributes()); + trackpointData(); + } else + _reader.skipCurrentElement(); + } +} + +void GPXParser::routepoints() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "rtept") { + _route->append(Waypoint()); + handleRoutepointAttributes(_reader.attributes()); + routepointData(); + } else + _reader.skipCurrentElement(); + } +} + +void GPXParser::track() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "trkseg") { + trackpoints(); + } else + _reader.skipCurrentElement(); + } +} + +void GPXParser::gpx() { while (_reader.readNextStartElement()) { if (_reader.name() == "trk") { @@ -231,7 +231,7 @@ void Parser::gpx() } } -bool Parser::parse() +bool GPXParser::parse() { if (_reader.readNextStartElement()) { if (_reader.name() == "gpx") @@ -243,7 +243,7 @@ bool Parser::parse() return !_reader.error(); } -bool Parser::loadFile(QIODevice *device) +bool GPXParser::loadFile(QIODevice *device) { _reader.clear(); _reader.setDevice(device); diff --git a/src/gpxparser.h b/src/gpxparser.h new file mode 100644 index 00000000..c6c7a437 --- /dev/null +++ b/src/gpxparser.h @@ -0,0 +1,51 @@ +#ifndef GPXPARSER_H +#define GPXPARSER_H + +#include +#include +#include "parser.h" + + +class GPXParser : public Parser +{ +public: + GPXParser(QList > &tracks, + QList > &routes, QList &waypoints) + : Parser(tracks, routes, waypoints) {_track = 0; _route = 0;} + ~GPXParser() {} + + bool loadFile(QIODevice *device); + QString errorString() const {return _reader.errorString();} + int errorLine() const {return _reader.lineNumber();} + const char *name() const {return "GPX";} + +private: + enum DataType { + Name, Description, Elevation, Time, Geoidheight, Speed, HeartRate, + Temperature + }; + + bool parse(); + void gpx(); + void track(); + void trackpoints(); + void routepoints(); + void tpExtension(); + void extensions(); + void trackpointData(); + void routepointData(); + void waypointData(); + + void handleWaypointAttributes(const QXmlStreamAttributes &attr); + void handleWaypointData(DataType type, const QString &value); + void handleTrackpointAttributes(const QXmlStreamAttributes &attr); + void handleTrackpointData(DataType type, const QString &value); + void handleRoutepointAttributes(const QXmlStreamAttributes &attr); + void handleRoutepointData(DataType type, const QString &value); + + QXmlStreamReader _reader; + QVector *_track; + QVector *_route; +}; + +#endif // GPXPARSER_H diff --git a/src/graphtab.h b/src/graphtab.h index a68c386f..96dc0151 100644 --- a/src/graphtab.h +++ b/src/graphtab.h @@ -5,7 +5,7 @@ #include "graphview.h" #include "units.h" -class GPX; +class Data; class PathItem; class GraphTab : public GraphView @@ -17,7 +17,7 @@ public: {setFrameShape(QFrame::NoFrame);} virtual QString label() const = 0; - virtual void loadGPX(const GPX &gpx, const QList &paths) = 0; + virtual void loadData(const Data &data, const QList &paths) = 0; virtual void clear() = 0; virtual void setUnits(enum Units units) = 0; virtual void showTracks(bool show) = 0; diff --git a/src/gui.cpp b/src/gui.cpp index 62580d2d..93534a7e 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -26,7 +26,7 @@ #include "icons.h" #include "keys.h" #include "settings.h" -#include "gpx.h" +#include "data.h" #include "map.h" #include "maplist.h" #include "elevationgraph.h" @@ -55,8 +55,7 @@ GUI::GUI(QWidget *parent) : QMainWindow(parent) createMenus(); createToolBars(); - _browser = new FileBrowser(this); - _browser->setFilter(QStringList("*.gpx")); + createBrowser(); QSplitter *splitter = new QSplitter(); splitter->setOrientation(Qt::Vertical); @@ -105,6 +104,14 @@ GUI::~GUI() } } +void GUI::createBrowser() +{ + QStringList filter; + filter << "*.gpx" << "*.tcx" << "*.csv"; + _browser = new FileBrowser(this); + _browser->setFilter(filter); +} + void GUI::loadMaps() { if (QFile::exists(USER_MAP_FILE)) @@ -593,7 +600,9 @@ void GUI::dataSources() void GUI::openFile() { QStringList files = QFileDialog::getOpenFileNames(this, tr("Open file"), - QString(), tr("GPX files (*.gpx);;All files (*)")); + QString(), tr("All supported files (*.gpx *.tcx *.csv)") + ";;" + + tr("GPX files (*.gpx)") + ";;" + tr("TCX files (*.tcx)") + ";;" + + tr("CSV files (*.csv)") + ";;" + tr("All files (*)")); QStringList list = files; for (QStringList::Iterator it = list.begin(); it != list.end(); it++) @@ -629,40 +638,40 @@ bool GUI::openFile(const QString &fileName) bool GUI::loadFile(const QString &fileName) { - GPX gpx; + Data data; QList paths; - if (gpx.loadFile(fileName)) { - paths = _pathView->loadGPX(gpx); + if (data.loadFile(fileName)) { + paths = _pathView->loadData(data); for (int i = 0; i < _tabs.count(); i++) - _tabs.at(i)->loadGPX(gpx, paths); + _tabs.at(i)->loadData(data, paths); updateGraphTabs(); _pathView->setHidden(false); - for (int i = 0; i < gpx.tracks().count(); i++) { - _trackDistance += gpx.tracks().at(i)->distance(); - _time += gpx.tracks().at(i)->time(); - const QDate &date = gpx.tracks().at(i)->date().date(); + for (int i = 0; i < data.tracks().count(); i++) { + _trackDistance += data.tracks().at(i)->distance(); + _time += data.tracks().at(i)->time(); + const QDate &date = data.tracks().at(i)->date().date(); if (_dateRange.first.isNull() || _dateRange.first > date) _dateRange.first = date; if (_dateRange.second.isNull() || _dateRange.second < date) _dateRange.second = date; } - _trackCount += gpx.tracks().count(); + _trackCount += data.tracks().count(); - for (int i = 0; i < gpx.routes().count(); i++) - _routeDistance += gpx.routes().at(i)->distance(); - _routeCount += gpx.routes().count(); + for (int i = 0; i < data.routes().count(); i++) + _routeDistance += data.routes().at(i)->distance(); + _routeCount += data.routes().count(); - _waypointCount += gpx.waypoints().count(); + _waypointCount += data.waypoints().count(); return true; } else { QString error = fileName + QString("\n\n") - + tr("Error loading GPX file:\n%1").arg(gpx.errorString()) + + tr("Error loading data file:\n%1").arg(data.errorString()) + QString("\n"); - if (gpx.errorLine()) - error.append(tr("Line: %1").arg(gpx.errorLine())); + if (data.errorLine()) + error.append(tr("Line: %1").arg(data.errorLine())); QMessageBox::critical(this, tr("Error"), error); return false; @@ -672,9 +681,9 @@ bool GUI::loadFile(const QString &fileName) void GUI::openPOIFile() { QStringList files = QFileDialog::getOpenFileNames(this, tr("Open POI file"), - QString(), tr("All POI files (*.gpx *.csv)") + ";;" + QString(), tr("All supported files (*.gpx *.tcx *.csv)") + ";;" + tr("GPX files (*.gpx)") + ";;" + tr("CSV files (*.csv)") + ";;" - + tr("All files (*)")); + + tr("TCX files (*.tcx)") + ";;" + tr("All files (*)")); QStringList list = files; for (QStringList::Iterator it = list.begin(); it != list.end(); it++) diff --git a/src/gui.h b/src/gui.h index 11434d04..e172f4dc 100644 --- a/src/gui.h +++ b/src/gui.h @@ -89,6 +89,7 @@ private: void createStatusBar(); void createPathView(); void createGraphTabs(); + void createBrowser(); bool openPOIFile(const QString &fileName); bool loadFile(const QString &fileName); diff --git a/src/heartrategraph.cpp b/src/heartrategraph.cpp index 41ce1fc7..a4530663 100644 --- a/src/heartrategraph.cpp +++ b/src/heartrategraph.cpp @@ -1,4 +1,4 @@ -#include "gpx.h" +#include "data.h" #include "heartrategraph.h" @@ -24,10 +24,10 @@ void HeartRateGraph::setInfo() clearInfo(); } -void HeartRateGraph::loadGPX(const GPX &gpx, const QList &paths) +void HeartRateGraph::loadData(const Data &data, const QList &paths) { - for (int i = 0; i < gpx.tracks().count(); i++) { - const Graph &graph = gpx.tracks().at(i)->heartRate(); + for (int i = 0; i < data.tracks().count(); i++) { + const Graph &graph = data.tracks().at(i)->heartRate(); qreal sum = 0, w = 0; if (graph.size() < 2) { @@ -40,12 +40,12 @@ void HeartRateGraph::loadGPX(const GPX &gpx, const QList &paths) sum += graph.at(j).y() * ds; w += ds; } - _avg.append(QPointF(gpx.tracks().at(i)->distance(), sum/w)); + _avg.append(QPointF(data.tracks().at(i)->distance(), sum/w)); GraphView::loadGraph(graph, paths.at(i)); } - for (int i = 0; i < gpx.routes().count(); i++) + for (int i = 0; i < data.routes().count(); i++) skipColor(); setInfo(); diff --git a/src/heartrategraph.h b/src/heartrategraph.h index be6962c8..0fcd46a2 100644 --- a/src/heartrategraph.h +++ b/src/heartrategraph.h @@ -11,7 +11,7 @@ public: HeartRateGraph(QWidget *parent = 0); QString label() const {return tr("Heart rate");} - void loadGPX(const GPX &gpx, const QList &paths); + void loadData(const Data &data, const QList &paths); void clear(); void setUnits(enum Units) {} void showTracks(bool show); diff --git a/src/parser.h b/src/parser.h index bfd75f70..adbd7bac 100644 --- a/src/parser.h +++ b/src/parser.h @@ -1,55 +1,30 @@ #ifndef PARSER_H #define PARSER_H -#include -#include +#include #include #include "trackpoint.h" #include "waypoint.h" +class QIODevice; class Parser { public: Parser(QList > &tracks, QList > &routes, QList &waypoints) - : _tracks(tracks), _routes(routes), _waypoints(waypoints) - {_track = 0; _route = 0;} + : _tracks(tracks), _routes(routes), _waypoints(waypoints) {} + virtual ~Parser() {} - bool loadFile(QIODevice *device); - QString errorString() const {return _reader.errorString();} - int errorLine() const {return _reader.lineNumber();} + virtual bool loadFile(QIODevice *device) = 0; + virtual QString errorString() const = 0; + virtual int errorLine() const = 0; + virtual const char *name() const = 0; -private: - enum DataType { - Name, Description, Elevation, Time, Geoidheight, Speed, HeartRate, - Temperature - }; - - bool parse(); - void gpx(); - void track(); - void trackpoints(); - void routepoints(); - void tpExtension(); - void extensions(); - void trackpointData(); - void routepointData(); - void waypointData(); - - void handleWaypointAttributes(const QXmlStreamAttributes &attr); - void handleWaypointData(DataType type, const QString &value); - void handleTrackpointAttributes(const QXmlStreamAttributes &attr); - void handleTrackpointData(DataType type, const QString &value); - void handleRoutepointAttributes(const QXmlStreamAttributes &attr); - void handleRoutepointData(DataType type, const QString &value); - - QXmlStreamReader _reader; +protected: QList > &_tracks; QList > &_routes; QList &_waypoints; - QVector *_track; - QVector *_route; }; #endif // PARSER_H diff --git a/src/pathview.cpp b/src/pathview.cpp index 4f985437..f7acbc0b 100644 --- a/src/pathview.cpp +++ b/src/pathview.cpp @@ -3,7 +3,7 @@ #include #include "ll.h" #include "poi.h" -#include "gpx.h" +#include "data.h" #include "map.h" #include "trackitem.h" #include "routeitem.h" @@ -124,17 +124,17 @@ void PathView::addWaypoints(const QList &waypoints) _scale = mapScale(_zoom); } -QList PathView::loadGPX(const GPX &gpx) +QList PathView::loadData(const Data &data) { QList paths; int zoom = _zoom; - for (int i = 0; i < gpx.tracks().count(); i++) - paths.append(addTrack(*(gpx.tracks().at(i)))); - for (int i = 0; i < gpx.routes().count(); i++) - paths.append(addRoute(*(gpx.routes().at(i)))); - addWaypoints(gpx.waypoints()); + for (int i = 0; i < data.tracks().count(); i++) + paths.append(addTrack(*(data.tracks().at(i)))); + for (int i = 0; i < data.routes().count(); i++) + paths.append(addRoute(*(data.routes().at(i)))); + addWaypoints(data.waypoints()); if (_tracks.empty() && _routes.empty() && _waypoints.empty()) return paths; diff --git a/src/pathview.h b/src/pathview.h index bf65b35c..446bff39 100644 --- a/src/pathview.h +++ b/src/pathview.h @@ -9,7 +9,7 @@ #include "palette.h" #include "waypoint.h" -class GPX; +class Data; class POI; class Map; class Track; @@ -28,7 +28,7 @@ public: PathView(QWidget *parent = 0); ~PathView(); - QList loadGPX(const GPX &gpx); + QList loadData(const Data &data); void setPOI(POI *poi); void setMap(Map *map); diff --git a/src/poi.cpp b/src/poi.cpp index 961cde11..3665c514 100644 --- a/src/poi.cpp +++ b/src/poi.cpp @@ -4,7 +4,7 @@ #include "pathitem.h" #include "waypointitem.h" #include "ll.h" -#include "gpx.h" +#include "data.h" #include "poi.h" @@ -16,119 +16,23 @@ POI::POI(QObject *parent) : QObject(parent) bool POI::loadFile(const QString &fileName) { - QString error; - int errorLine; + Data data; + FileIndex index; - _error.clear(); + _errorString.clear(); _errorLine = 0; - if (loadCSVFile(fileName)) { - emit pointsChanged(); - return true; - } else { - error = _error; - errorLine = _errorLine; - } - if (loadGPXFile(fileName)) { - emit pointsChanged(); - return true; - } - - fprintf(stderr, "Error loading POI file: %s:\n", qPrintable(fileName)); - fprintf(stderr, "CSV: line %d: %s\n", errorLine, qPrintable(error)); - fprintf(stderr, "GPX: line %d: %s\n", _errorLine, qPrintable(_error)); - - if (errorLine > _errorLine) { - _errorLine = errorLine; - _error = error; - } - - return false; -} - -bool POI::loadGPXFile(const QString &fileName) -{ - GPX gpx; - FileIndex index; - index.enabled = true; index.start = _data.size(); - if (gpx.loadFile(fileName)) { - for (int i = 0; i < gpx.waypoints().size(); i++) - _data.append(gpx.waypoints().at(i)); - index.end = _data.size() - 1; - - for (int i = index.start; i <= index.end; i++) { - const QPointF &p = _data.at(i).coordinates(); - qreal c[2]; - c[0] = p.x(); - c[1] = p.y(); - _tree.Insert(c, c, i); - } - - _files.append(fileName); - _indexes.append(index); - - return true; - } else { - _error = gpx.errorString(); - _errorLine = gpx.errorLine(); - } - - return false; -} - -bool POI::loadCSVFile(const QString &fileName) -{ - QFile file(fileName); - FileIndex index; - bool ret; - int ln = 1; - - index.enabled = true; - index.start = _data.size(); - - if (!file.open(QFile::ReadOnly | QFile::Text)) { - _error = qPrintable(file.errorString()); + if (!data.loadFile(fileName)) { + _errorString = data.errorString(); + _errorLine = data.errorLine(); return false; } - while (!file.atEnd()) { - QByteArray line = file.readLine(); - QList list = line.split(','); - if (list.size() < 3) { - _error = "Parse error."; - _errorLine = ln; - return false; - } - - qreal lat = list[0].trimmed().toDouble(&ret); - if (!ret) { - _error = "Invalid latitude."; - _errorLine = ln; - return false; - } - qreal lon = list[1].trimmed().toDouble(&ret); - if (!ret) { - _error = "Invalid longitude."; - _errorLine = ln; - return false; - } - Waypoint wp(QPointF(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())); - } - - _data.append(wp); - ln++; - } + 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++) { @@ -142,6 +46,8 @@ bool POI::loadCSVFile(const QString &fileName) _files.append(fileName); _indexes.append(index); + emit pointsChanged(); + return true; } diff --git a/src/poi.h b/src/poi.h index 73f6fcda..c36fafb3 100644 --- a/src/poi.h +++ b/src/poi.h @@ -19,7 +19,7 @@ public: POI(QObject *parent = 0); bool loadFile(const QString &fileName); - const QString &errorString() const {return _error;} + const QString &errorString() const {return _errorString;} int errorLine() const {return _errorLine;} qreal radius() const {return _radius;} @@ -44,9 +44,6 @@ private: bool enabled; } FileIndex; - bool loadCSVFile(const QString &fileName); - bool loadGPXFile(const QString &fileName); - POITree _tree; QVector _data; QStringList _files; @@ -54,7 +51,7 @@ private: qreal _radius; - QString _error; + QString _errorString; int _errorLine; }; diff --git a/src/speedgraph.cpp b/src/speedgraph.cpp index 2b365a32..70aaef2a 100644 --- a/src/speedgraph.cpp +++ b/src/speedgraph.cpp @@ -1,5 +1,5 @@ #include "config.h" -#include "gpx.h" +#include "data.h" #include "speedgraph.h" @@ -25,22 +25,22 @@ void SpeedGraph::setInfo() clearInfo(); } -void SpeedGraph::loadGPX(const GPX &gpx, const QList &paths) +void SpeedGraph::loadData(const Data &data, const QList &paths) { - for (int i = 0; i < gpx.tracks().count(); i++) { - const Graph &graph = gpx.tracks().at(i)->speed(); + for (int i = 0; i < data.tracks().count(); i++) { + const Graph &graph = data.tracks().at(i)->speed(); if (graph.size() < 2) { skipColor(); continue; } - _avg.append(QPointF(gpx.tracks().at(i)->distance(), - gpx.tracks().at(i)->distance() / gpx.tracks().at(i)->time())); + _avg.append(QPointF(data.tracks().at(i)->distance(), + data.tracks().at(i)->distance() / data.tracks().at(i)->time())); GraphView::loadGraph(graph, paths.at(i)); } - for (int i = 0; i < gpx.routes().count(); i++) + for (int i = 0; i < data.routes().count(); i++) skipColor(); setInfo(); diff --git a/src/speedgraph.h b/src/speedgraph.h index 8d12df1a..3e763b4c 100644 --- a/src/speedgraph.h +++ b/src/speedgraph.h @@ -12,7 +12,7 @@ public: SpeedGraph(QWidget *parent = 0); QString label() const {return tr("Speed");} - void loadGPX(const GPX &gpx, const QList &paths); + void loadData(const Data &data, const QList &paths); void clear(); void setUnits(enum Units units); void showTracks(bool show); diff --git a/src/tcxparser.cpp b/src/tcxparser.cpp new file mode 100644 index 00000000..0a13c273 --- /dev/null +++ b/src/tcxparser.cpp @@ -0,0 +1,181 @@ +#include "tcxparser.h" + + +QPointF TCXParser::position() +{ + QPointF pos; + + while (_reader.readNextStartElement()) { + if (_reader.name() == "LatitudeDegrees") + pos.setY(_reader.readElementText().toDouble()); + else if (_reader.name() == "LongitudeDegrees") + pos.setX(_reader.readElementText().toDouble()); + else + _reader.skipCurrentElement(); + } + + return pos; +} + +void TCXParser::trackpointData() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "Position") + _track->last().setCoordinates(position()); + else if (_reader.name() == "AltitudeMeters") + _track->last().setElevation(_reader.readElementText().toDouble()); + else if (_reader.name() == "Time") + _track->last().setTimestamp(QDateTime::fromString( + _reader.readElementText(), Qt::ISODate)); + else if (_reader.name() == "HeartRateBpm") + _track->last().setHeartRate(_reader.readElementText().toDouble()); + else + _reader.skipCurrentElement(); + } +} + +void TCXParser::routepointData() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "Position") + _route->last().setCoordinates(position()); + else if (_reader.name() == "AltitudeMeters") + _route->last().setElevation(_reader.readElementText().toDouble()); + else if (_reader.name() == "Time") + _route->last().setTimestamp(QDateTime::fromString( + _reader.readElementText(), Qt::ISODate)); + else + _reader.skipCurrentElement(); + } +} + +void TCXParser::waypointData() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "Position") + _waypoints.last().setCoordinates(position()); + else if (_reader.name() == "Name") + _waypoints.last().setName(_reader.readElementText()); + else if (_reader.name() == "Notes") + _waypoints.last().setDescription(_reader.readElementText()); + else if (_reader.name() == "AltitudeMeters") + _waypoints.last().setElevation( + _reader.readElementText().toDouble()); + else if (_reader.name() == "Time") + _waypoints.last().setTimestamp(QDateTime::fromString( + _reader.readElementText(), Qt::ISODate)); + else + _reader.skipCurrentElement(); + } +} + +void TCXParser::trackpoints() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "Trackpoint") { + _track->append(Trackpoint()); + trackpointData(); + } else + _reader.skipCurrentElement(); + } +} + +void TCXParser::routepoints() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "Trackpoint") { + _route->append(Waypoint()); + routepointData(); + } else + _reader.skipCurrentElement(); + } +} + +void TCXParser::lap() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "Track") { + _tracks.append(QVector()); + _track = &_tracks.back(); + trackpoints(); + } else + _reader.skipCurrentElement(); + } +} + +void TCXParser::course() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "Track") { + _routes.append(QVector()); + _route = &_routes.back(); + routepoints(); + } else if (_reader.name() == "CoursePoint") { + _waypoints.append(Waypoint()); + waypointData(); + } else + _reader.skipCurrentElement(); + } +} + +void TCXParser::activity() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "Lap") + lap(); + else + _reader.skipCurrentElement(); + } +} + +void TCXParser::courses() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "Course") + course(); + else + _reader.skipCurrentElement(); + } +} + +void TCXParser::activities() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "Activity") + activity(); + else + _reader.skipCurrentElement(); + } +} + +void TCXParser::tcx() +{ + while (_reader.readNextStartElement()) { + if (_reader.name() == "Courses") + courses(); + else if (_reader.name() == "Activities") + activities(); + else + _reader.skipCurrentElement(); + } +} + +bool TCXParser::parse() +{ + if (_reader.readNextStartElement()) { + if (_reader.name() == "TrainingCenterDatabase") + tcx(); + else + _reader.raiseError("Not a TCX file."); + } + + return !_reader.error(); +} + +bool TCXParser::loadFile(QIODevice *device) +{ + _reader.clear(); + _reader.setDevice(device); + + return parse(); +} diff --git a/src/tcxparser.h b/src/tcxparser.h new file mode 100644 index 00000000..3dc0f846 --- /dev/null +++ b/src/tcxparser.h @@ -0,0 +1,42 @@ +#ifndef TCXPARSER_H +#define TCXPARSER_H + +#include +#include +#include "parser.h" + + +class TCXParser : public Parser +{ +public: + TCXParser(QList > &tracks, + QList > &routes, QList &waypoints) + : Parser(tracks, routes, waypoints) {_track = 0; _route = 0;} + ~TCXParser() {} + + bool loadFile(QIODevice *device); + QString errorString() const {return _reader.errorString();} + int errorLine() const {return _reader.lineNumber();} + const char *name() const {return "TCX";} + +private: + bool parse(); + void tcx(); + void courses(); + void activities(); + void course(); + void activity(); + void lap(); + void trackpoints(); + void routepoints(); + void trackpointData(); + void routepointData(); + void waypointData(); + QPointF position(); + + QXmlStreamReader _reader; + QVector *_track; + QVector *_route; +}; + +#endif // TCXPARSER_H diff --git a/src/temperaturegraph.cpp b/src/temperaturegraph.cpp index a54cfb32..e315da92 100644 --- a/src/temperaturegraph.cpp +++ b/src/temperaturegraph.cpp @@ -1,4 +1,4 @@ -#include "gpx.h" +#include "data.h" #include "temperaturegraph.h" @@ -26,10 +26,10 @@ void TemperatureGraph::setInfo() clearInfo(); } -void TemperatureGraph::loadGPX(const GPX &gpx, const QList &paths) +void TemperatureGraph::loadData(const Data &data, const QList &paths) { - for (int i = 0; i < gpx.tracks().count(); i++) { - const Graph &graph = gpx.tracks().at(i)->temperature(); + for (int i = 0; i < data.tracks().count(); i++) { + const Graph &graph = data.tracks().at(i)->temperature(); qreal sum = 0, w = 0; if (graph.size() < 2) { @@ -42,12 +42,12 @@ void TemperatureGraph::loadGPX(const GPX &gpx, const QList &paths) sum += graph.at(j).y() * ds; w += ds; } - _avg.append(QPointF(gpx.tracks().at(i)->distance(), sum/w)); + _avg.append(QPointF(data.tracks().at(i)->distance(), sum/w)); GraphView::loadGraph(graph, paths.at(i)); } - for (int i = 0; i < gpx.routes().count(); i++) + for (int i = 0; i < data.routes().count(); i++) skipColor(); setInfo(); diff --git a/src/temperaturegraph.h b/src/temperaturegraph.h index be85990e..5636b03d 100644 --- a/src/temperaturegraph.h +++ b/src/temperaturegraph.h @@ -11,7 +11,7 @@ public: TemperatureGraph(QWidget *parent = 0); QString label() const {return tr("Temperature");} - void loadGPX(const GPX &gpx, const QList &paths); + void loadData(const Data &data, const QList &paths); void clear(); void setUnits(enum Units units); void showTracks(bool show);