From 535361dadaf329801aeeeab581f1dc4d922f73f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Wed, 16 Nov 2016 23:54:15 +0100 Subject: [PATCH] Added support for NMEA files --- gpxsee.pro | 6 +- src/data.cpp | 3 + src/gui.cpp | 11 +- src/igcparser.cpp | 24 +-- src/igcparser.h | 6 +- src/misc.cpp | 15 ++ src/misc.h | 1 + src/nmeaparser.cpp | 420 +++++++++++++++++++++++++++++++++++++++++++++ src/nmeaparser.h | 33 ++++ 9 files changed, 491 insertions(+), 28 deletions(-) create mode 100644 src/nmeaparser.cpp create mode 100644 src/nmeaparser.h diff --git a/gpxsee.pro b/gpxsee.pro index af3e9e1b..b53784a4 100644 --- a/gpxsee.pro +++ b/gpxsee.pro @@ -69,7 +69,8 @@ HEADERS += src/config.h \ src/assert.h \ src/cadencegraph.h \ src/powergraph.h \ - src/igcparser.h + src/igcparser.h \ + src/nmeaparser.h SOURCES += src/main.cpp \ src/gui.cpp \ src/poi.cpp \ @@ -119,7 +120,8 @@ SOURCES += src/main.cpp \ src/cadencegraph.cpp \ src/powergraph.cpp \ src/igcparser.cpp \ - src/path.cpp + src/path.cpp \ + src/nmeaparser.cpp RESOURCES += gpxsee.qrc TRANSLATIONS = lang/gpxsee_cs.ts macx { diff --git a/src/data.cpp b/src/data.cpp index 1a719465..404ce171 100644 --- a/src/data.cpp +++ b/src/data.cpp @@ -7,6 +7,7 @@ #include "kmlparser.h" #include "fitparser.h" #include "igcparser.h" +#include "nmeaparser.h" #include "data.h" @@ -24,6 +25,8 @@ Data::Data() : _errorLine(0) _waypointData)); _parsers.insert("igc", new IGCParser(_trackData, _routeData, _waypointData)); + _parsers.insert("nmea", new NMEAParser(_trackData, _routeData, + _waypointData)); } Data::~Data() diff --git a/src/gui.cpp b/src/gui.cpp index b609c581..d3972569 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -108,17 +108,18 @@ GUI::~GUI() const QString GUI::fileFormats() const { - return tr("Supported files (*.csv *.fit *.gpx *.igc *.kml *.tcx)") + ";;" - + tr("CSV files (*.csv)") + ";;" + tr("FIT files (*.fit)") + ";;" + 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("TCX files (*.tcx)") + ";;" - + tr("All files (*)"); + + tr("KML files (*.kml)") + ";;" + tr("NMEA files (*.nmea)") + ";;" + + tr("TCX files (*.tcx)") + ";;" + tr("All files (*)"); } void GUI::createBrowser() { QStringList filter; - filter << "*.gpx" << "*.tcx" << "*.kml" << "*.fit" << "*.csv" << "*.igc"; + filter << "*.gpx" << "*.tcx" << "*.kml" << "*.fit" << "*.csv" << "*.igc" + << "*.nmea"; _browser = new FileBrowser(this); _browser->setFilter(filter); } diff --git a/src/igcparser.cpp b/src/igcparser.cpp index 265bc9e1..0aba808e 100644 --- a/src/igcparser.cpp +++ b/src/igcparser.cpp @@ -1,22 +1,8 @@ -#include #include +#include "misc.h" #include "igcparser.h" -static int str2int(const char *str, size_t 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; -} - static bool readLat(const char *data, qreal &lat) { int d = str2int(data, 2); @@ -93,6 +79,8 @@ static bool readTimestamp(const char *data, QTime &time) return false; time = QTime(h, m, s); + if (!time.isValid()) + return false; return true; } @@ -108,7 +96,7 @@ static bool readARecord(const char *line, qint64 len) return true; } -bool IGCParser::readHRecord(const char *line, qint64 len) +bool IGCParser::readHRecord(const char *line, int len) { if (len < 10 || ::strncmp(line, "HFDTE", 5)) return true; @@ -131,7 +119,7 @@ bool IGCParser::readHRecord(const char *line, qint64 len) return true; } -bool IGCParser::readBRecord(const char *line, qint64 len) +bool IGCParser::readBRecord(const char *line, int len) { qreal lat, lon, ele; QTime time; @@ -172,7 +160,7 @@ bool IGCParser::readBRecord(const char *line, qint64 len) return true; } -bool IGCParser::readCRecord(const char *line, qint64 len) +bool IGCParser::readCRecord(const char *line, int len) { qreal lat, lon; diff --git a/src/igcparser.h b/src/igcparser.h index 13a41f9e..a9be1abf 100644 --- a/src/igcparser.h +++ b/src/igcparser.h @@ -19,9 +19,9 @@ public: int errorLine() const {return _errorLine;} private: - bool readHRecord(const char *line, qint64 len); - bool readBRecord(const char *line, qint64 len); - bool readCRecord(const char *line, qint64 len); + bool readHRecord(const char *line, int len); + bool readBRecord(const char *line, int len); + bool readCRecord(const char *line, int len); int _errorLine; QString _errorString; diff --git a/src/misc.cpp b/src/misc.cpp index 14dd0f2c..4f5d90ef 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -1,4 +1,5 @@ #include +#include #include "misc.h" @@ -33,3 +34,17 @@ double niceNum(double x, int round) return nf * pow(10.0, expv); } + +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; +} diff --git a/src/misc.h b/src/misc.h index b0d3aaaa..67a86139 100644 --- a/src/misc.h +++ b/src/misc.h @@ -2,5 +2,6 @@ #define MISC_H double niceNum(double x, int round); +int str2int(const char *str, int len); #endif // MISC_H diff --git a/src/nmeaparser.cpp b/src/nmeaparser.cpp new file mode 100644 index 00000000..f099057a --- /dev/null +++ b/src/nmeaparser.cpp @@ -0,0 +1,420 @@ +#include +#include "misc.h" +#include "nmeaparser.h" + + +static bool readTime(const char *data, int len, QTime &time) +{ + int h, m, s ,ms; + + if (len < 9) + return false; + + h = str2int(data, 2); + m = str2int(data + 2, 2); + s = str2int(data + 4, 2); + ms = str2int(data + 7, len - 7); + if (h < 0 || m < 0 || s < 0 || ms < 0 || data[6] != '.') + return false; + + time = QTime(h, m, s, ms); + if (!time.isValid()) + return false; + + return true; +} + +static bool readDate(const char *data, int len, QDate &date) +{ + int y, m, d; + + if (len != 6) + return false; + + d = str2int(data, 2); + m = str2int(data + 2, 2); + y = str2int(data + 4, 2); + if (d < 0 || m < 0 || y < 0) + return false; + + date = QDate(2000 + y, m, d); + if (!date.isValid()) + return false; + + return true; +} + +static bool readLat(const char *data, int len, qreal &lat) +{ + int d, mi; + qreal mf; + bool ok; + + if (len < 7 || data[4] != '.') + return false; + + 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) + return false; + + lat = d + (((qreal)mi + mf) / 60.0); + if (lat > 90) + return false; + + return true; +} + +static bool readLon(const char *data, int len, qreal &lon) +{ + int d, mi; + qreal mf; + bool ok; + + if (len < 8 || data[5] != '.') + return false; + + 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) + return false; + + lon = d + (((qreal)mi + mf) / 60.0); + if (lon > 180) + 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::readRMC(const char *line, int len) +{ + int col = 1; + const char *vp = line; + qreal lat, lon; + QTime time; + 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)) { + _errorString = "Invalid time"; + return false; + } + break; + case 2: + if (*vp != 'A') + valid = false; + break; + case 3: + if (!readLat(vp, lp - vp, lat)) { + _errorString = "Invalid latitude"; + return false; + } + break; + case 4: + if (!(*vp == 'N' || *vp == 'S')) { + _errorString = "Invalid latitude N|S"; + return false; + } + if (*lp == 'S') + lat = -lat; + break; + case 5: + if (!readLon(vp, lp - vp, lon)) { + _errorString = "Invalid longitude"; + return false; + } + break; + case 6: + if (!(*vp == 'E' || *vp == 'W')) { + _errorString = "Invalid latitude E|W"; + return false; + } + if (*vp == 'W') + lon = -lon; + break; + case 9: + if (!readDate(vp, lp - vp, _date)) { + _errorString = "Invalid date"; + return false; + } + break; + } + + col++; + vp = lp + 1; + } + } + + if (col < 9) { + _errorString = "Invalid RMC sentence"; + return false; + } + + if (valid && !_GGA) { + Trackpoint t(Coordinates(lon, lat)); + t.setTimestamp(QDateTime(_date, time, Qt::UTC)); + _tracks.last().append(t); + } + + return true; +} + +bool NMEAParser::readGGA(const char *line, int len) +{ + int col = 1; + const char *vp = line; + qreal lat, lon, ele, gh; + QTime time; + + for (const char *lp = line; lp < line + len; lp++) { + if (*lp == ',' || *lp == '*') { + switch (col) { + case 1: + if (!readTime(vp, lp - vp, time)) { + _errorString = "Invalid time"; + return false; + } + break; + case 2: + if (!readLat(vp, lp - vp, lat)) { + _errorString = "Invalid latitude"; + return false; + } + break; + case 3: + if (!(*vp == 'N' || *vp == 'S')) { + _errorString = "Invalid latitude N|S"; + return false; + } + if (*vp == 'S') + lat = -lat; + break; + case 4: + if (!readLon(vp, lp - vp, lon)) { + _errorString = "Invalid longitude"; + return false; + } + break; + case 5: + if (!(*vp == 'E' || *vp == 'W')) { + _errorString = "Invalid latitude E|W"; + return false; + } + if (*vp == 'W') + lon = -lon; + break; + case 9: + if ((lp - vp) && !readFloat(vp, lp - vp, ele)) { + _errorString = "Invalid altitude"; + return false; + } + break; + case 10: + if ((lp - vp) && *vp != 'M') { + _errorString = "Invalid altitude units"; + return false; + } + break; + case 11: + if ((lp - vp) && !readFloat(vp, lp - vp, gh)) { + _errorString = "Invalid geoid height"; + return false; + } + break; + case 12: + if ((lp - vp) && *vp != 'M') { + _errorString = "Invalid geoid height units"; + return false; + } + break; + } + + col++; + vp = lp + 1; + } + } + + if (col < 12) { + _errorString = "Invalid GGA sentence"; + return false; + } + + _GGA = true; + + Trackpoint t(Coordinates(lon, lat)); + t.setTimestamp(QDateTime(_date, time, Qt::UTC)); + t.setElevation(ele - gh); + _tracks.last().append(t); + + return true; +} + +bool NMEAParser::readWPL(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)) { + _errorString = "Invalid latitude"; + return false; + } + break; + case 2: + if (!(*vp == 'N' || *vp == 'S')) { + _errorString = "Invalid latitude N|S"; + return false; + } + if (*vp == 'S') + lat = -lat; + break; + case 3: + if (!readLon(vp, lp - vp, lon)) { + _errorString = "Invalid longitude"; + return false; + } + break; + case 4: + if (!(*vp == 'E' || *vp == 'W')) { + _errorString = "Invalid latitude E|W"; + return false; + } + if (*vp == 'W') + lon = -lon; + break; + case 5: + name = QString(QByteArray(vp, lp - vp)); + break; + } + + col++; + vp = lp + 1; + } + } + + if (col < 4) { + _errorString = "Invalid WPL sentence"; + return false; + } + + Waypoint w(Coordinates(lon, lat)); + 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 ((d = str2int(vp, lp - vp)) < 0) { + _errorString = "Invalid day"; + return false; + } + break; + case 3: + if ((m = str2int(vp, lp - vp)) < 0) { + _errorString = "Invalid month"; + return false; + } + break; + case 4: + 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::loadFile(QFile *file) +{ + qint64 len; + char line[80 + 2 + 1 + 1]; + + + _errorLine = 1; + _errorString.clear(); + + _GGA = false; + _tracks.append(TrackData()); + + 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 (len < 7 || line[0] != '$') { + _errorString = "Format error"; + return false; + } + + if (!memcmp(line + 3, "RMC,", 4)) { + if (!readRMC(line + 7, len)) + return false; + } else if (!memcmp(line + 3, "GGA,", 4)) { + if (!readGGA(line + 7, len)) + return false; + } else if (!memcmp(line + 3, "WPL,", 4)) { + if (!readWPL(line + 7, len)) + return false; + } else if (!memcmp(line + 3, "ZDA,", 4)) { + if (!readZDA(line + 7, len)) + return false; + } + + _errorLine++; + } + + return true; +} diff --git a/src/nmeaparser.h b/src/nmeaparser.h new file mode 100644 index 00000000..64a661ae --- /dev/null +++ b/src/nmeaparser.h @@ -0,0 +1,33 @@ +#ifndef NMEAPARSER_H +#define NMEAPARSER_H + +#include +#include "parser.h" + + +class NMEAParser : public Parser +{ +public: + NMEAParser(QList &tracks, QList &routes, + QList &waypoints) : Parser(tracks, routes, waypoints) + {_errorLine = 0; _GGA = false;} + ~NMEAParser() {} + + bool loadFile(QFile *file); + QString errorString() const {return _errorString;} + int errorLine() const {return _errorLine;} + +private: + bool readRMC(const char *line, int len); + bool readGGA(const char *line, int len); + bool readWPL(const char *line, int len); + bool readZDA(const char *line, int len); + + int _errorLine; + QString _errorString; + + QDate _date; + bool _GGA; +}; + +#endif // NMEAPARSER_H