From 17c791d753954ee59736a8e30d4c95edc4bb3a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Thu, 10 Nov 2016 00:08:11 +0100 Subject: [PATCH] Added support for IGC files --- gpxsee.pro | 6 +- src/data.cpp | 3 + src/gui.cpp | 9 +- src/igcparser.cpp | 204 ++++++++++++++++++++++++++++++++++++++++++++++ src/igcparser.h | 31 +++++++ 5 files changed, 247 insertions(+), 6 deletions(-) create mode 100644 src/igcparser.cpp create mode 100644 src/igcparser.h diff --git a/gpxsee.pro b/gpxsee.pro index 909cc156..5ebeef96 100644 --- a/gpxsee.pro +++ b/gpxsee.pro @@ -68,7 +68,8 @@ HEADERS += src/config.h \ src/path.h \ src/assert.h \ src/cadencegraph.h \ - src/powergraph.h + src/powergraph.h \ + src/igcparser.h SOURCES += src/main.cpp \ src/gui.cpp \ src/poi.cpp \ @@ -116,7 +117,8 @@ SOURCES += src/main.cpp \ src/format.cpp \ src/graph.cpp \ src/cadencegraph.cpp \ - src/powergraph.cpp + src/powergraph.cpp \ + src/igcparser.cpp RESOURCES += gpxsee.qrc TRANSLATIONS = lang/gpxsee_cs.ts macx { diff --git a/src/data.cpp b/src/data.cpp index 3b5b0e91..1a719465 100644 --- a/src/data.cpp +++ b/src/data.cpp @@ -6,6 +6,7 @@ #include "csvparser.h" #include "kmlparser.h" #include "fitparser.h" +#include "igcparser.h" #include "data.h" @@ -21,6 +22,8 @@ Data::Data() : _errorLine(0) _waypointData)); _parsers.insert("csv", new CSVParser(_trackData, _routeData, _waypointData)); + _parsers.insert("igc", new IGCParser(_trackData, _routeData, + _waypointData)); } Data::~Data() diff --git a/src/gui.cpp b/src/gui.cpp index 10854b15..b609c581 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -108,16 +108,17 @@ GUI::~GUI() const QString GUI::fileFormats() const { - return tr("Supported files (*.csv *.fit *.gpx *.kml *.tcx)") + ";;" + return tr("Supported files (*.csv *.fit *.gpx *.igc *.kml *.tcx)") + ";;" + tr("CSV files (*.csv)") + ";;" + tr("FIT files (*.fit)") + ";;" - + tr("GPX files (*.gpx)") + ";;" + tr("KML files (*.kml)") + ";;" - + tr("TCX files (*.tcx)") + ";;" + tr("All files (*)"); + + tr("GPX files (*.gpx)") + ";;" + tr("IGC files (*.igc)") + ";;" + + tr("KML files (*.kml)") + ";;" + tr("TCX files (*.tcx)") + ";;" + + tr("All files (*)"); } void GUI::createBrowser() { QStringList filter; - filter << "*.gpx" << "*.tcx" << "*.kml" << "*.fit" << "*.csv"; + filter << "*.gpx" << "*.tcx" << "*.kml" << "*.fit" << "*.csv" << "*.igc"; _browser = new FileBrowser(this); _browser->setFilter(filter); } diff --git a/src/igcparser.cpp b/src/igcparser.cpp new file mode 100644 index 00000000..81e5748e --- /dev/null +++ b/src/igcparser.cpp @@ -0,0 +1,204 @@ +#include +#include +#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 *line, qreal &lat) +{ + int d = str2int(line + 7, 2); + int mi = str2int(line + 9, 2); + int mf = str2int(line + 11, 3); + if (d < 0 || mi < 0 || mf < 0) + return false; + + if (!(line[14] == 'N' || line[14] == 'S')) + return false; + + lat = d + (((qreal)mi + (qreal)mf/1000) / 60); + if (lat > 90) + return false; + + if (line[14] == 'S') + lat = -lat; + + return true; +} + +static bool readLon(const char *line, qreal &lon) +{ + int d = str2int(line + 15, 3); + int mi = str2int(line + 18, 2); + int mf = str2int(line + 20, 3); + if (d < 0 || mi < 0 || mf < 0) + return false; + + if (!(line[23] == 'E' || line[23] == 'W')) + return false; + + lon = d + (((qreal)mi + (qreal)mf/1000) / 60); + if (lon > 180) + return false; + + if (line[23] == 'W') + lon = -lon; + + return true; +} + +static bool readAltitude(const char *line, qreal &ele) +{ + int p; + + if (!(line[24] == 'A' || line[24] == 'V')) + return false; + + if (line[25] == '-') + p = str2int(line + 26, 4); + else + p = str2int(line + 25, 5); + + int g = str2int(line + 30, 5); + if (p < 0 || g < 0) + return false; + + if (line[24] == 'A') + ele = (qreal)g; + else + ele = NAN; + + return true; +} + +bool IGCParser::readDate(const char *line) +{ + 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"; + return false; + } + + _date = QDate(2000 + y, m, d); + + return true; +} + +bool IGCParser::readRecord(const char *line) +{ + qreal lat, lon, ele; + QDateTime timestamp; + + + if (_date.isNull()) { + _errorString = "Date header missing"; + return false; + } + + int h = str2int(line + 1, 2); + int m = str2int(line + 3, 2); + int s = str2int(line + 5, 2); + + if (h <0 || m < 0 || s < 0) { + _errorString = "Invalid timestamp"; + return false; + } + QTime time = QTime(h, m, s); + if (time < _time) + _date.addDays(1); + _time = time; + timestamp = QDateTime(_date, _time, Qt::UTC); + + if (!readLat(line, lat)) { + _errorString = "Invalid latitude"; + return false; + } + if (!readLon(line, lon)) { + _errorString = "Invalid longitude"; + return false; + } + + if (!readAltitude(line, ele)) { + _errorString = "Invalid altitude"; + return false; + } + + Trackpoint t(Coordinates(lon, lat)); + t.setTimestamp(timestamp); + t.setElevation(ele); + _tracks.last().append(t); + + return true; +} + +bool IGCParser::loadFile(QFile *file) +{ + qint64 len; + char line[76 + 2 + 1 + 1]; + + _errorLine = 1; + _errorString.clear(); + + _tracks.append(TrackData()); + _time = QTime(0, 0); + + // Read the initial A record + if ((len = file->readLine(line, sizeof(line))) < 0) { + _errorString = "I/O error"; + return false; + } else { + if (len < 9 || len > (qint64)sizeof(line) - 1 || line[0] != 'A') { + _errorString = "Not a IGC file"; + return false; + } + for (int i = 1; i < 7; i++) { + if (!::isprint(line[i])) { + _errorString = "Not a IGC file"; + return false; + } + } + } + + _errorLine++; + + // Read header (H) records and data (B) records + while ((len = file->readLine(line, sizeof(line))) > 0) { + if (len < 0) { + _errorString = "I/O error"; + return false; + } + if (len > (qint64)sizeof(line) - 1) { + _errorString = "Line limit exceeded"; + return false; + } + + if (line[0] == 'B') { + if (len > 35) + if (!readRecord(line)) + return false; + } else if (line[0] == 'H') { + if (len > 10 && !::strncmp(line + 1, "FDTE", 4)) + if (!readDate(line)) + return false; + } + + _errorLine++; + } + + return true; +} diff --git a/src/igcparser.h b/src/igcparser.h new file mode 100644 index 00000000..0964cb77 --- /dev/null +++ b/src/igcparser.h @@ -0,0 +1,31 @@ +#ifndef IGCPARSER_H +#define IGCPARSER_H + +#include +#include +#include "parser.h" + + +class IGCParser : public Parser +{ +public: + IGCParser(QList &tracks, QList &routes, + QList &waypoints) : Parser(tracks, routes, waypoints) {} + ~IGCParser() {} + + bool loadFile(QFile *file); + QString errorString() const {return _errorString;} + int errorLine() const {return _errorLine;} + +private: + bool readDate(const char *line); + bool readRecord(const char *line); + + int _errorLine; + QString _errorString; + + QDate _date; + QTime _time; +}; + +#endif // IGCPARSER_H