diff --git a/gpxsee.pro b/gpxsee.pro index 334c3721..c4179c05 100644 --- a/gpxsee.pro +++ b/gpxsee.pro @@ -261,6 +261,12 @@ SOURCES += src/main.cpp \ src/data/dem.cpp \ src/map/polarstereographic.cpp \ src/map/rectd.cpp + +greaterThan(QT_MAJOR_VERSION, 4) { + HEADERS += src/data/geojsonparser.h + SOURCES += src/data/geojsonparser.cpp +} + RESOURCES += gpxsee.qrc TRANSLATIONS = lang/gpxsee_en.ts \ lang/gpxsee_cs.ts \ diff --git a/src/data/data.cpp b/src/data/data.cpp index 43692ecb..62794919 100644 --- a/src/data/data.cpp +++ b/src/data/data.cpp @@ -12,6 +12,9 @@ #include "oziparsers.h" #include "locparser.h" #include "slfparser.h" +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +#include "geojsonparser.h" +#endif // QT 5 #include "dem.h" #include "data.h" @@ -28,6 +31,9 @@ static WPTParser wpt; static RTEParser rte; static LOCParser loc; static SLFParser slf; +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +static GeoJSONParser geojson; +#endif // QT 5 static QHash parsers() { @@ -45,6 +51,10 @@ static QHash parsers() hash.insert("rte", &rte); hash.insert("loc", &loc); hash.insert("slf", &slf); +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + hash.insert("json", &geojson); + hash.insert("geojson", &geojson); +#endif // QT 5 return hash; } @@ -123,11 +133,22 @@ Data::Data(const QString &fileName, bool poi) QString Data::formats() { + QStringList l(filter()); + qSort(l); + QString supported; + for (int i = 0; i < l.size(); i++) { + supported += l.at(i); + if (i != l.size() - 1) + supported += " "; + } + return - qApp->translate("Data", "Supported files") - + " (*.csv *.fit *.gpx *.igc *.kml *.loc *.nmea *.plt *.rte *.slf *.tcx *.wpt);;" + qApp->translate("Data", "Supported files") + " (" + supported + ");;" + qApp->translate("Data", "CSV files") + " (*.csv);;" + qApp->translate("Data", "FIT files") + " (*.fit);;" +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + + qApp->translate("Data", "GeoJSON files") + " (*.geojson *.json);;" +#endif // QT5 + qApp->translate("Data", "GPX files") + " (*.gpx);;" + qApp->translate("Data", "IGC files") + " (*.igc);;" + qApp->translate("Data", "KML files") + " (*.kml);;" diff --git a/src/data/geojsonparser.cpp b/src/data/geojsonparser.cpp new file mode 100644 index 00000000..efbdf045 --- /dev/null +++ b/src/data/geojsonparser.cpp @@ -0,0 +1,277 @@ +#include +#include +#include "geojsonparser.h" + + +enum Type { + Unknown, + Point, + LineString, + MultiPoint, + Polygon, + MultiLineString, + MultiPolygon, + GeometryCollection, + Feature, + FeatureCollection +}; + +static Type type(const QJsonObject &json) +{ + QString str(json["type"].toString()); + + if (str == "Point") + return Point; + else if (str == "MultiPoint") + return MultiPoint; + else if (str == "LineString") + return LineString; + else if (str == "MultiLineString") + return MultiLineString; + else if (str == "Polygon") + return Polygon; + else if (str == "MultiPolygon") + return MultiPolygon; + else if (str == "GeometryCollection") + return GeometryCollection; + else if (str == "Feature") + return Feature; + else if (str == "FeatureCollection") + return FeatureCollection; + else + return Unknown; +} + +bool GeoJSONParser::point(const QJsonArray &coordinates, Waypoint &waypoint, + const QJsonObject &properties) +{ + if (coordinates.count() < 2 || !coordinates.at(0).isDouble() + || !coordinates.at(1).isDouble()) { + _errorString = "Invalid Point Coordinates"; + return false; + } + + waypoint.setCoordinates(Coordinates(coordinates.at(0).toDouble(), + coordinates.at(1).toDouble())); + if (coordinates.count() == 3 && coordinates.at(2).isDouble()) + waypoint.setElevation(coordinates.at(2).toDouble()); + if (properties.contains("title") && properties["title"].isString()) + waypoint.setName(properties["title"].toString()); + if (properties.contains("description") + && properties["description"].isString()) + waypoint.setDescription(properties["description"].toString()); + + return true; +} + +bool GeoJSONParser::multiPoint(const QJsonArray &coordinates, + QVector &waypoints, const QJsonObject &properties) +{ + for (int i = 0; i < coordinates.size(); i++) { + if (!coordinates.at(i).isArray()) { + _errorString = "Invalid MultiPoint Coordinates"; + return false; + } else { + waypoints.resize(waypoints.size() + 1); + if (!point(coordinates.at(i).toArray(), waypoints.last(), properties)) + return false; + } + } + + return true; +} + +bool GeoJSONParser::lineString(const QJsonArray &coordinates, TrackData &track, + const QJsonObject &properties) +{ + if (properties.contains("title") && properties["title"].isString()) + track.setName(properties["title"].toString()); + if (properties.contains("description") + && properties["description"].isString()) + track.setDescription(properties["description"].toString()); + + for (int i = 0; i < coordinates.size(); i++) { + QJsonArray point(coordinates.at(i).toArray()); + if (point.count() < 2 || !point.at(0).isDouble() + || !point.at(1).isDouble()) { + _errorString = "Invalid LineString Coordinates"; + return false; + } + + Trackpoint t(Coordinates(point.at(0).toDouble(), + point.at(1).toDouble())); + if (point.count() == 3 && point.at(2).isDouble()) + t.setElevation(point.at(2).toDouble()); + track.append(t); + } + + return true; +} + +bool GeoJSONParser::multiLineString(const QJsonArray &coordinates, + QList &tracks, const QJsonObject &properties) +{ + for (int i = 0; i < coordinates.size(); i++) { + if (!coordinates.at(i).isArray()) { + _errorString = "Invalid MultiLineString Coordinates"; + return false; + } else { + tracks.append(TrackData()); + if (!lineString(coordinates.at(i).toArray(), tracks.last(), + properties)) + return false; + } + } + + return true; +} + +bool GeoJSONParser::geometryCollection(const QJsonObject &json, + QList &tracks, QVector &waypoints, + const QJsonObject &properties) +{ + if (!json.contains("geometries") || !json["geometries"].isArray()) { + _errorString = "Invalid/missing GeometryCollection geometries array"; + return false; + } + + QJsonArray geometries(json["geometries"].toArray()); + for (int i = 0; i < geometries.size(); i++) { + QJsonObject geometry(geometries.at(i).toObject()); + switch (type(geometry)) { + case Point: + waypoints.resize(waypoints.size() + 1); + if (!point(geometry["coordinates"].toArray(), waypoints.last(), + properties)) + return false; + break; + case MultiPoint: + if (!multiPoint(geometry["coordinates"].toArray(), waypoints, + properties)) + return false; + break; + case LineString: + tracks.append(TrackData()); + if (!lineString(geometry["coordinates"].toArray(), + tracks.last(), properties)) + return false; + break; + case MultiLineString: + if (!multiLineString(geometry["coordinates"].toArray(), tracks, + properties)) + return false; + break; + case Polygon: + case MultiPolygon: + break; + case GeometryCollection: + if (!geometryCollection(geometry, tracks, waypoints, + properties)) + return false; + break; + default: + _errorString = geometry["type"].toString() + + ": invalid/missing geometry type"; + return false; + } + } + + return true; +} + +bool GeoJSONParser::feature(const QJsonObject &json, QList &tracks, + QVector &waypoints) +{ + QJsonObject properties(json["properties"].toObject()); + QJsonObject geometry(json["geometry"].toObject()); + + switch (type(geometry)) { + case Point: + waypoints.resize(waypoints.size() + 1); + return point(geometry["coordinates"].toArray(), waypoints.last(), + properties); + case MultiPoint: + return multiPoint(geometry["coordinates"].toArray(), waypoints, + properties); + case LineString: + tracks.append(TrackData()); + return lineString(geometry["coordinates"].toArray(), tracks.last(), + properties); + case MultiLineString: + return multiLineString(geometry["coordinates"].toArray(), tracks, + properties); + case GeometryCollection: + return geometryCollection(geometry, tracks, waypoints); + case Polygon: + case MultiPolygon: + return true; + default: + _errorString = geometry["type"].toString() + + ": invalid/missing Feature geometry"; + return false; + } +} + +bool GeoJSONParser::featureCollection(const QJsonObject &json, + QList &tracks, QVector &waypoints) +{ + if (!json.contains("features") || !json["features"].isArray()) { + _errorString = "Invalid/missing FeatureCollection features array"; + return false; + } + + QJsonArray features(json["features"].toArray()); + for (int i = 0; i < features.size(); i++) + if (!feature(features.at(i).toObject(), tracks, waypoints)) + return false; + + return true; +} + + +bool GeoJSONParser::parse(QFile *file, QList &tracks, + QList &routes, QVector &waypoints) +{ + Q_UNUSED(routes); + QJsonParseError error; + QJsonDocument doc(QJsonDocument::fromJson(file->readAll(), &error)); + + if (doc.isNull()) { + _errorString = "JSON parse error: " + error.errorString() + " [" + + QString::number(error.offset) + "]"; + return false; + } + + QJsonObject json(doc.object()); + + switch (type(json)) { + case Point: + waypoints.resize(waypoints.size() + 1); + return point(json["coordinates"].toArray(), waypoints.last()); + case MultiPoint: + return multiPoint(json["coordinates"].toArray(), waypoints); + case LineString: + tracks.append(TrackData()); + return lineString(json["coordinates"].toArray(), tracks.last()); + case MultiLineString: + return multiLineString(json["coordinates"].toArray(), tracks); + case GeometryCollection: + return geometryCollection(json, tracks, waypoints); + case Feature: + return feature(json, tracks, waypoints); + case FeatureCollection: + return featureCollection(json, tracks, waypoints); + case Polygon: + case MultiPolygon: + return true; + case Unknown: + if (json["type"].toString().isNull()) + _errorString = "Not a GeoJSON file"; + else + _errorString = json["type"].toString() + + ": unknown GeoJSON object"; + return false; + } + + return true; +} diff --git a/src/data/geojsonparser.h b/src/data/geojsonparser.h new file mode 100644 index 00000000..765fd0af --- /dev/null +++ b/src/data/geojsonparser.h @@ -0,0 +1,37 @@ +#ifndef GEOJSONPARSER_H +#define GEOJSONPARSER_H + +#include +#include "parser.h" + +class QJsonObject; +class QJsonArray; + +class GeoJSONParser : public Parser +{ +public: + bool parse(QFile *file, QList &tracks, QList &routes, + QVector &waypoints); + QString errorString() const {return _errorString;} + int errorLine() const {return 0;} + +private: + bool point(const QJsonArray &coordinates, Waypoint &waypoint, + const QJsonObject &properties = QJsonObject()); + bool multiPoint(const QJsonArray &coordinates, + QVector &waypoints, const QJsonObject &properties = QJsonObject()); + bool lineString(const QJsonArray &coordinates, TrackData &track, + const QJsonObject &properties = QJsonObject()); + bool multiLineString(const QJsonArray &coordinates, + QList &tracks, const QJsonObject &properties = QJsonObject()); + bool geometryCollection(const QJsonObject &json, QList &tracks, + QVector &waypoints, const QJsonObject &properties = QJsonObject()); + bool feature(const QJsonObject &json, QList &tracks, + QVector &waypoints); + bool featureCollection(const QJsonObject &json, QList &tracks, + QVector &waypoints); + + QString _errorString; +}; + +#endif // GEOJSONPARSER_H