From ecb82952f641dc3c126d929f056a81b7c7cd3488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Thu, 15 Aug 2019 21:27:55 +0200 Subject: [PATCH] Added support for SeeYou CUP files --- gpxsee.pro | 8 +- src/data/csv.cpp | 66 +++++++++++++++++ src/data/csv.h | 22 ++++++ src/data/csvparser.cpp | 39 +++++----- src/data/cupparser.cpp | 163 +++++++++++++++++++++++++++++++++++++++++ src/data/cupparser.h | 26 +++++++ src/data/data.cpp | 4 + 7 files changed, 305 insertions(+), 23 deletions(-) create mode 100644 src/data/csv.cpp create mode 100644 src/data/csv.h create mode 100644 src/data/cupparser.cpp create mode 100644 src/data/cupparser.h diff --git a/gpxsee.pro b/gpxsee.pro index 140e232c..6b6b299c 100644 --- a/gpxsee.pro +++ b/gpxsee.pro @@ -181,7 +181,9 @@ HEADERS += src/common/config.h \ src/GUI/limitedcombobox.h \ src/GUI/pathtickitem.h \ src/map/IMG/textitem.h \ - src/map/IMG/label.h + src/map/IMG/label.h \ + src/data/csv.h \ + src/data/cupparser.h SOURCES += src/main.cpp \ src/common/coordinates.cpp \ src/common/rectc.cpp \ @@ -312,7 +314,9 @@ SOURCES += src/main.cpp \ src/map/IMG/style.cpp \ src/map/IMG/netfile.cpp \ src/GUI/pathtickitem.cpp \ - src/map/IMG/textitem.cpp + src/map/IMG/textitem.cpp \ + src/data/csv.cpp \ + src/data/cupparser.cpp greaterThan(QT_MAJOR_VERSION, 4) { HEADERS += src/data/geojsonparser.h diff --git a/src/data/csv.cpp b/src/data/csv.cpp new file mode 100644 index 00000000..25ebad24 --- /dev/null +++ b/src/data/csv.cpp @@ -0,0 +1,66 @@ +#include "csv.h" + +/* +RFC 4180 parser with the following enchancements: + - allows an arbitrary delimiter + - allows LF line ends in addition to CRLF line ends +*/ + +bool CSV::readEntry(QStringList &list) +{ + int state = 0; + char c; + QByteArray field; + + while (_device->getChar(&c)) { + switch (state) { + case 0: + if (c == '\r') + state = 3; + else if (c == '\n') { + list.append(field); + _line++; + return true; + } else if (c == _delimiter) { + list.append(field); + field.clear(); + } else if (c == '"') { + if (!field.isEmpty()) + return false; + state = 1; + } else + field.append(c); + break; + case 1: + if (c == '"') + state = 2; + else { + field.append(c); + if (c == '\n') + _line++; + } + break; + case 2: + if (c == '"') { + field.append('"'); + state = 1; + } else if (c == _delimiter || c == '\r' || c == '\n') { + _device->ungetChar(c); + state = 0; + } else + return false; + break; + case 3: + if (c == '\n') { + _device->ungetChar(c); + state = 0; + } else + return false; + break; + } + } + + list.append(field); + + return (state == 0); +} diff --git a/src/data/csv.h b/src/data/csv.h new file mode 100644 index 00000000..fa947b2a --- /dev/null +++ b/src/data/csv.h @@ -0,0 +1,22 @@ +#ifndef CSV_H +#define CSV_H + +#include + +class CSV +{ +public: + CSV(QIODevice *device, char delimiter = ',') + : _device(device), _delimiter(delimiter), _line(1) {} + + bool readEntry(QStringList &list); + bool atEnd() const {return _device->atEnd();} + int line() const {return _line;} + +private: + QIODevice *_device; + char _delimiter; + int _line; +}; + +#endif // CSV_H diff --git a/src/data/csvparser.cpp b/src/data/csvparser.cpp index ed8f00d9..6d89dbfb 100644 --- a/src/data/csvparser.cpp +++ b/src/data/csvparser.cpp @@ -1,3 +1,4 @@ +#include "csv.h" #include "csvparser.h" bool CSVParser::parse(QFile *file, QList &tracks, @@ -7,42 +8,38 @@ bool CSVParser::parse(QFile *file, QList &tracks, Q_UNUSED(tracks); Q_UNUSED(routes); Q_UNUSED(polygons); - bool res; + CSV csv(file); + QStringList entry; + bool ok; - _errorLine = 1; - _errorString.clear(); - while (!file->atEnd()) { - QByteArray line = file->readLine(); - QList list = line.split(','); - if (list.size() < 3) { + while (!csv.atEnd()) { + if (!csv.readEntry(entry) || entry.size() < 3) { _errorString = "Parse error"; + _errorLine = csv.line(); return false; } - qreal lon = list[0].trimmed().toDouble(&res); - if (!res || (lon < -180.0 || lon > 180.0)) { + double lon = entry.at(0).trimmed().toDouble(&ok); + if (!ok || (lon < -180.0 || lon > 180.0)) { _errorString = "Invalid longitude"; + _errorLine = csv.line(); return false; } - qreal lat = list[1].trimmed().toDouble(&res); - if (!res || (lat < -90.0 || lat > 90.0)) { + double lat = entry.at(1).trimmed().toDouble(&ok); + if (!ok || (lat < -90.0 || lat > 90.0)) { _errorString = "Invalid latitude"; + _errorLine = csv.line(); 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())); - } + wp.setName(entry.at(2).trimmed()); + if (entry.size() > 3) + wp.setDescription(entry.at(3).trimmed()); waypoints.append(wp); - _errorLine++; + + entry.clear(); } return true; diff --git a/src/data/cupparser.cpp b/src/data/cupparser.cpp new file mode 100644 index 00000000..79a9228c --- /dev/null +++ b/src/data/cupparser.cpp @@ -0,0 +1,163 @@ +#include +#include "csv.h" +#include "cupparser.h" + + +enum SegmentType { + Header, Waypoints, Tasks +}; + +static double latitude(const QString &str) +{ + bool ok; + + if (str.length() != 9) + return NAN; + int deg = str.left(2).toInt(&ok); + if (!ok || deg > 90) + return NAN; + double min = str.mid(2, 6).toDouble(&ok); + if (!ok || min > 60) + return NAN; + + double dd = deg + min/60.0; + return (str.right(1) == 'S') ? -dd : dd; +} + +static double longitude(const QString &str) +{ + bool ok; + + if (str.length() != 10) + return NAN; + int deg = str.left(3).toInt(&ok); + if (!ok || deg > 180) + return NAN; + double min = str.mid(3, 6).toDouble(&ok); + if (!ok || min > 60) + return NAN; + + double dd = deg + min/60.0; + return (str.right(1) == 'W') ? -dd : dd; +} + +static double elevation(const QString &str) +{ + bool ok; + double ele; + + if (str.right(2) == "ft") + ele = str.left(str.length() - 2).toDouble(&ok) * 0.3048; + else if (str.right(1) == "m") + ele = str.left(str.length() - 1).toDouble(&ok); + else + return NAN; + + return ok ? ele : NAN; +} + + +bool CUPParser::waypoint(const QStringList &entry, QVector &waypoints, + QMap &turnpoints) +{ + if (entry.size() < 11) { + _errorString = "Invalid number of fields"; + return false; + } + + double lon = longitude(entry.at(4)); + if (std::isnan(lon)) { + _errorString = "Invalid longitude"; + return false; + } + double lat = latitude(entry.at(3)); + if (std::isnan(lat)) { + _errorString = "Invalid latitude"; + return false; + } + + Waypoint wp(Coordinates(lon, lat)); + wp.setName(entry.at(0)); + wp.setDescription(entry.at(10)); + wp.setElevation(elevation(entry.at(5))); + waypoints.append(wp); + + turnpoints.insert(wp.name(), wp.coordinates()); + + return true; +} + +bool CUPParser::task(const QStringList &entry, QList &routes, + const QMap &turnpoints) +{ + if (entry.size() < 2) { + _errorString = "Invalid number of fields"; + return false; + } + + RouteData r; + r.setName(entry.at(0)); + for (int i = 1; i < entry.size(); i++) { + if (!turnpoints.contains(entry.at(i))) { + _errorString = entry.at(i) + ": unknown turnpoint"; + return false; + } + + Waypoint w(turnpoints[entry.at(i)]); + w.setName(entry.at(i)); + r.append(w); + } + + routes.append(r); + + return true; +} + +bool CUPParser::parse(QFile *file, QList &tracks, + QList &routes, QList &polygons, + QVector &waypoints) +{ + Q_UNUSED(tracks); + Q_UNUSED(polygons); + CSV csv(file); + SegmentType st = Header; + QStringList entry; + QMap turnpoints; + + + while (!csv.atEnd()) { + if (!csv.readEntry(entry)) { + _errorString = "CSV parse error"; + _errorLine = csv.line(); + return false; + } + + if (st == Header) { + st = Waypoints; + if (entry.size() >= 11 && entry.at(3) == "lat" + && entry.at(4) == "lon") { + entry.clear(); + continue; + } + } else if (st == Waypoints && entry.size() == 1 + && entry.at(0) == "-----Related Tasks-----") { + st = Tasks; + entry.clear(); + continue; + } + + if (st == Waypoints) { + if (!waypoint(entry, waypoints, turnpoints)) + return false; + } else if (st == Tasks) { + if (entry.at(0) != "Options" && entry.at(0) != "ObsZone" + && !task(entry, routes, turnpoints)) + return false; + } + + entry.clear(); + _errorLine = csv.line(); + } + + return true; +} diff --git a/src/data/cupparser.h b/src/data/cupparser.h new file mode 100644 index 00000000..cd2c1752 --- /dev/null +++ b/src/data/cupparser.h @@ -0,0 +1,26 @@ +#ifndef CUPPARSER_H +#define CUPPARSER_H + +#include "parser.h" + +class CUPParser : public Parser +{ +public: + CUPParser() : _errorLine(0) {} + + bool parse(QFile *file, QList &tracks, QList &routes, + QList &polygons, QVector &waypoints); + QString errorString() const {return _errorString;} + int errorLine() const {return _errorLine;} + +private: + bool waypoint(const QStringList &entry, QVector &waypoints, + QMap &turnpoints); + bool task(const QStringList &entry, QList &routes, + const QMap &turnpoints); + + QString _errorString; + int _errorLine; +}; + +#endif // CUPPARSER_H diff --git a/src/data/data.cpp b/src/data/data.cpp index 98b0c19d..684eab42 100644 --- a/src/data/data.cpp +++ b/src/data/data.cpp @@ -17,6 +17,7 @@ #include "geojsonparser.h" #endif // ENABLE_GEOJSON #include "exifparser.h" +#include "cupparser.h" #include "dem.h" #include "data.h" @@ -37,6 +38,7 @@ static SLFParser slf; static GeoJSONParser geojson; #endif // ENABLE_GEOJSON static EXIFParser exif; +static CUPParser cup; static QHash parsers() { @@ -60,6 +62,7 @@ static QHash parsers() #endif // ENABLE_GEOJSON hash.insert("jpeg", &exif); hash.insert("jpg", &exif); + hash.insert("cup", &cup); return hash; } @@ -147,6 +150,7 @@ QString Data::formats() return qApp->translate("Data", "Supported files") + " (" + supported + ");;" + qApp->translate("Data", "CSV files") + " (*.csv);;" + + qApp->translate("Data", "CUP files") + " (*.cup);;" + qApp->translate("Data", "FIT files") + " (*.fit);;" #ifdef ENABLE_GEOJSON + qApp->translate("Data", "GeoJSON files") + " (*.geojson *.json);;"