2019-01-25 22:18:21 +01:00
|
|
|
#include <QJsonDocument>
|
|
|
|
#include <QJsonArray>
|
2024-03-09 17:51:43 +01:00
|
|
|
#include "map/crs.h"
|
2019-01-25 22:18:21 +01:00
|
|
|
#include "geojsonparser.h"
|
|
|
|
|
2022-09-23 02:34:18 +02:00
|
|
|
#define MARKER_SIZE_MEDIUM 12
|
|
|
|
#define MARKER_SIZE_SMALL 8
|
|
|
|
#define MARKER_SIZE_LARGE 16
|
|
|
|
|
2024-03-11 23:09:38 +01:00
|
|
|
#define PROJ(object, parent) \
|
|
|
|
((object).isNull() ? (parent) : (object))
|
|
|
|
|
2024-03-12 21:30:15 +01:00
|
|
|
/*
|
|
|
|
* Mapbox Simple Style
|
|
|
|
* https://github.com/mapbox/simplestyle-spec
|
|
|
|
*/
|
2022-09-23 02:34:18 +02:00
|
|
|
static int markerSize(const QString &str)
|
|
|
|
{
|
|
|
|
if (str == "small")
|
|
|
|
return MARKER_SIZE_SMALL;
|
|
|
|
else if (str == "medium")
|
|
|
|
return MARKER_SIZE_MEDIUM;
|
|
|
|
else if (str == "large")
|
|
|
|
return MARKER_SIZE_LARGE;
|
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setAreaProperties(Area &area, const QJsonValue &properties)
|
|
|
|
{
|
|
|
|
QColor strokeColor(0x55, 0x55, 0x55);
|
2022-09-23 21:36:02 +02:00
|
|
|
QColor fillColor(0x55, 0x55, 0x55, 0x99);
|
2022-09-23 02:34:18 +02:00
|
|
|
double strokeWidth = 2;
|
|
|
|
|
2022-09-23 21:36:02 +02:00
|
|
|
if (properties.isObject()) {
|
|
|
|
QJsonObject o(properties.toObject());
|
|
|
|
|
|
|
|
if (o["name"].isString())
|
|
|
|
area.setName(o["name"].toString());
|
|
|
|
if (o["title"].isString())
|
|
|
|
area.setName(o["title"].toString());
|
|
|
|
if (o["description"].isString())
|
|
|
|
area.setDescription(o["description"].toString());
|
|
|
|
|
|
|
|
if (o["stroke"].isString())
|
|
|
|
strokeColor = QColor(o["stroke"].toString());
|
|
|
|
if (o["stroke-opacity"].isDouble())
|
|
|
|
strokeColor.setAlphaF(o["stroke-opacity"].toDouble());
|
|
|
|
if (o["stroke-width"].isDouble())
|
|
|
|
strokeWidth = o["stroke-width"].toDouble();
|
|
|
|
if (o["fill"].isString())
|
|
|
|
fillColor = QColor(o["fill"].toString());
|
|
|
|
if (o["fill-opacity"].isDouble())
|
|
|
|
fillColor.setAlphaF(o["fill-opacity"].toDouble());
|
|
|
|
else
|
2023-02-25 11:06:14 +01:00
|
|
|
fillColor.setAlphaF(0.6f);
|
2022-09-23 21:36:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
area.setStyle(PolygonStyle(fillColor, strokeColor, strokeWidth));
|
2022-09-23 02:34:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void setTrackProperties(TrackData &track, const QJsonValue &properties)
|
|
|
|
{
|
|
|
|
QColor color(0x55, 0x55, 0x55);
|
|
|
|
double width = 2;
|
|
|
|
|
2022-09-23 21:36:02 +02:00
|
|
|
if (properties.isObject()) {
|
|
|
|
QJsonObject o(properties.toObject());
|
|
|
|
|
|
|
|
if (o["name"].isString())
|
|
|
|
track.setName(o["name"].toString());
|
|
|
|
if (o["title"].isString())
|
|
|
|
track.setName(o["title"].toString());
|
|
|
|
if (o["description"].isString())
|
|
|
|
track.setDescription(o["description"].toString());
|
|
|
|
|
|
|
|
if (o["stroke"].isString())
|
|
|
|
color = QColor(o["stroke"].toString());
|
|
|
|
if (o["stroke-opacity"].isDouble())
|
|
|
|
color.setAlphaF(o["stroke-opacity"].toDouble());
|
|
|
|
if (o["stroke-width"].isDouble())
|
|
|
|
width = o["stroke-width"].toDouble();
|
|
|
|
}
|
2022-09-23 02:34:18 +02:00
|
|
|
|
2022-10-07 21:54:39 +02:00
|
|
|
track.setStyle(LineStyle(color, width, Qt::SolidLine));
|
2022-09-23 02:34:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void setWaypointProperties(Waypoint &waypoint,
|
|
|
|
const QJsonValue &properties)
|
|
|
|
{
|
|
|
|
QColor color(0x7e, 0x7e, 0x7e);
|
|
|
|
int size = MARKER_SIZE_MEDIUM;
|
|
|
|
|
2023-02-15 01:40:28 +01:00
|
|
|
if (properties.isObject()) {
|
2022-09-23 21:36:02 +02:00
|
|
|
QJsonObject o(properties.toObject());
|
|
|
|
|
|
|
|
if (o["name"].isString())
|
|
|
|
waypoint.setName(o["name"].toString());
|
|
|
|
if (o["title"].isString())
|
|
|
|
waypoint.setName(o["title"].toString());
|
|
|
|
if (o["description"].isString())
|
|
|
|
waypoint.setDescription(o["description"].toString());
|
|
|
|
|
|
|
|
if (o["marker-color"].isString())
|
|
|
|
color = QColor(o["marker-color"].toString());
|
|
|
|
if (o["marker-symbol"].isString())
|
|
|
|
waypoint.setSymbol(o["marker-symbol"].toString());
|
|
|
|
if (o["marker-size"].isString())
|
|
|
|
size = markerSize(o["marker-size"].toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
waypoint.setStyle(PointStyle(color, size));
|
2022-09-23 02:34:18 +02:00
|
|
|
}
|
|
|
|
|
2024-03-12 21:30:15 +01:00
|
|
|
/*
|
|
|
|
* Mapbox Coordinate Properties
|
|
|
|
* https://github.com/mapbox/geojson-coordinate-properties
|
|
|
|
*/
|
|
|
|
static QDateTime timestamp(const QJsonValue &data)
|
|
|
|
{
|
|
|
|
if (data.isString())
|
|
|
|
return QDateTime::fromString(data.toString(), Qt::ISODate);
|
|
|
|
else if (data.isDouble())
|
|
|
|
return QDateTime::fromMSecsSinceEpoch((qint64)data.toDouble());
|
|
|
|
else
|
|
|
|
return QDateTime();
|
|
|
|
}
|
|
|
|
|
|
|
|
static double hr(const QJsonValue &data)
|
|
|
|
{
|
|
|
|
return data.isDouble() ? data.toDouble() : NAN;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setSegmentProperties(SegmentData &segment, int segno,
|
|
|
|
const QJsonValue &properties)
|
|
|
|
{
|
|
|
|
if (properties.isObject()) {
|
|
|
|
QJsonObject o(properties.toObject());
|
|
|
|
|
|
|
|
if (o["coordinateProperties"].isObject()) {
|
|
|
|
QJsonObject cp(o["coordinateProperties"].toObject());
|
|
|
|
if (cp["times"].isArray()) {
|
|
|
|
QJsonArray times(cp["times"].toArray());
|
|
|
|
|
|
|
|
if (segno >= 0) {
|
|
|
|
if (times.size() > segno) {
|
|
|
|
QJsonArray seg(times.at(segno).toArray());
|
|
|
|
if (seg.size() == segment.size()) {
|
|
|
|
for (int i = 0; i < seg.size(); i++)
|
|
|
|
segment[i].setTimestamp(timestamp(seg.at(i)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (times.size() == segment.size()) {
|
|
|
|
for (int i = 0; i < times.size(); i++)
|
|
|
|
segment[i].setTimestamp(timestamp(times.at(i)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cp["heart"].isArray()) {
|
|
|
|
QJsonArray heart(cp["heart"].toArray());
|
|
|
|
|
|
|
|
if (segno >= 0) {
|
|
|
|
if (heart.size() > segno) {
|
|
|
|
QJsonArray seg(heart.at(segno).toArray());
|
|
|
|
if (seg.size() == segment.size()) {
|
|
|
|
for (int i = 0; i < seg.size(); i++)
|
|
|
|
segment[i].setHeartRate(hr(seg.at(i)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (heart.size() == segment.size()) {
|
|
|
|
for (int i = 0; i < heart.size(); i++)
|
|
|
|
segment[i].setHeartRate(hr(heart.at(i)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-02-14 00:49:41 +01:00
|
|
|
static bool isWS(char c)
|
|
|
|
{
|
|
|
|
return (c == 0x20 || c == 0x09 || c == 0x0A || c == 0x0D) ? true : false;
|
|
|
|
}
|
|
|
|
|
2024-03-11 23:09:38 +01:00
|
|
|
static bool possiblyJSONObject(QFile *file)
|
2023-02-14 00:49:41 +01:00
|
|
|
{
|
|
|
|
char c;
|
|
|
|
|
|
|
|
while (file->getChar(&c)) {
|
|
|
|
if (isWS(c))
|
|
|
|
continue;
|
|
|
|
else if (c == '{')
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2019-01-25 22:18:21 +01:00
|
|
|
|
2024-03-11 23:09:38 +01:00
|
|
|
bool GeoJSONParser::a2c(const QJsonArray &data, const Projection &proj,
|
|
|
|
Coordinates &c)
|
2024-03-09 17:51:43 +01:00
|
|
|
{
|
2024-03-11 23:09:38 +01:00
|
|
|
c = (data.count() >= 2 && data.at(0).isDouble() && data.at(1).isDouble())
|
|
|
|
? proj.xy2ll(PointD(data.at(0).toDouble(), data.at(1).toDouble()))
|
|
|
|
: Coordinates();
|
|
|
|
|
|
|
|
if (c.isValid())
|
|
|
|
return true;
|
|
|
|
else {
|
|
|
|
QJsonDocument doc(QJsonDocument::fromVariant(data.toVariantList()));
|
|
|
|
_errorString = QString("%1: invalid coordinates")
|
|
|
|
.arg(QString::fromUtf8(doc.toJson(QJsonDocument::Compact)));
|
|
|
|
return false;
|
|
|
|
}
|
2024-03-09 17:51:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool GeoJSONParser::crs(const QJsonObject &object, Projection &proj)
|
|
|
|
{
|
|
|
|
if (!object.contains("crs"))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
QJsonObject crsObj(object["crs"].toObject());
|
|
|
|
if (crsObj["type"].toString() != "name" || !crsObj.contains("properties")) {
|
|
|
|
_errorString = "Invalid crs object";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
QString str(crsObj["properties"].toObject()["name"].toString());
|
|
|
|
proj = CRS::projection(str);
|
|
|
|
|
|
|
|
if (proj.isValid())
|
|
|
|
return true;
|
|
|
|
else {
|
|
|
|
_errorString = QString("%1: unknown CRS").arg(str);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-31 01:46:53 +01:00
|
|
|
GeoJSONParser::Type GeoJSONParser::type(const QJsonObject &json)
|
2019-01-25 22:18:21 +01:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
bool GeoJSONParser::point(const QJsonObject &object, const Projection &parent,
|
2024-03-10 07:46:50 +01:00
|
|
|
const QJsonValue &properties, QVector<Waypoint> &waypoints)
|
2019-01-25 22:18:21 +01:00
|
|
|
{
|
2024-03-09 17:51:43 +01:00
|
|
|
if (!object.contains("coordinates")) {
|
|
|
|
_errorString = "Missing Point coordinates array";
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
|
|
|
}
|
2024-03-09 17:51:43 +01:00
|
|
|
if (object["coordinates"].isNull())
|
|
|
|
return true;
|
|
|
|
QJsonArray coordinates(object["coordinates"].toArray());
|
|
|
|
if (coordinates.isEmpty())
|
|
|
|
return true;
|
|
|
|
Projection proj;
|
|
|
|
if (!crs(object, proj))
|
|
|
|
return false;
|
2019-01-25 22:18:21 +01:00
|
|
|
|
2024-03-11 23:09:38 +01:00
|
|
|
Coordinates c;
|
|
|
|
if (!a2c(coordinates, PROJ(proj, parent), c))
|
2024-03-09 17:51:43 +01:00
|
|
|
return false;
|
2022-09-23 02:34:18 +02:00
|
|
|
|
2024-03-10 07:46:50 +01:00
|
|
|
Waypoint waypoint(c);
|
2019-01-25 22:18:21 +01:00
|
|
|
if (coordinates.count() == 3 && coordinates.at(2).isDouble())
|
|
|
|
waypoint.setElevation(coordinates.at(2).toDouble());
|
2024-03-09 17:51:43 +01:00
|
|
|
setWaypointProperties(waypoint, properties);
|
2024-03-10 07:46:50 +01:00
|
|
|
waypoints.append(waypoint);
|
2024-03-09 17:51:43 +01:00
|
|
|
|
2019-01-25 22:18:21 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
bool GeoJSONParser::multiPoint(const QJsonObject &object,
|
|
|
|
const Projection &parent, const QJsonValue &properties,
|
|
|
|
QVector<Waypoint> &waypoints)
|
2019-01-25 22:18:21 +01:00
|
|
|
{
|
2024-03-09 17:51:43 +01:00
|
|
|
if (!object.contains("coordinates")) {
|
|
|
|
_errorString = "Missing MultiPoint coordinates array";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (object["coordinates"].isNull())
|
|
|
|
return true;
|
|
|
|
QJsonArray coordinates(object["coordinates"].toArray());
|
|
|
|
Projection proj;
|
|
|
|
if (!crs(object, proj))
|
|
|
|
return false;
|
2024-03-11 23:09:38 +01:00
|
|
|
Coordinates c;
|
2024-03-09 17:51:43 +01:00
|
|
|
|
2019-01-25 22:18:21 +01:00
|
|
|
for (int i = 0; i < coordinates.size(); i++) {
|
|
|
|
if (!coordinates.at(i).isArray()) {
|
2024-03-09 17:51:43 +01:00
|
|
|
_errorString = "Invalid MultiPoint data";
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
|
|
|
} else {
|
2024-03-09 17:51:43 +01:00
|
|
|
QJsonArray data(coordinates.at(i).toArray());
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!a2c(data, PROJ(proj, parent), c))
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
2024-03-09 17:51:43 +01:00
|
|
|
|
2024-03-10 07:46:50 +01:00
|
|
|
Waypoint waypoint(c);
|
2024-03-09 17:51:43 +01:00
|
|
|
if (data.count() == 3 && data.at(2).isDouble())
|
2024-03-10 07:46:50 +01:00
|
|
|
waypoint.setElevation(data.at(2).toDouble());
|
|
|
|
setWaypointProperties(waypoint, properties);
|
|
|
|
waypoints.append(waypoint);
|
2019-01-25 22:18:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
bool GeoJSONParser::lineString(const QJsonObject &object,
|
2024-03-10 07:46:50 +01:00
|
|
|
const Projection &parent, const QJsonValue &properties,
|
|
|
|
QList<TrackData> &tracks)
|
2019-01-25 22:18:21 +01:00
|
|
|
{
|
2024-03-09 17:51:43 +01:00
|
|
|
if (!object.contains("coordinates")) {
|
|
|
|
_errorString = "Missing LineString coordinates array";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (object["coordinates"].isNull())
|
|
|
|
return true;
|
|
|
|
QJsonArray coordinates(object["coordinates"].toArray());
|
|
|
|
if (coordinates.isEmpty())
|
|
|
|
return true;
|
|
|
|
Projection proj;
|
|
|
|
if (!crs(object, proj))
|
|
|
|
return false;
|
2024-03-11 23:09:38 +01:00
|
|
|
SegmentData segment;
|
|
|
|
Coordinates c;
|
2024-03-09 17:51:43 +01:00
|
|
|
|
2019-01-25 22:18:21 +01:00
|
|
|
for (int i = 0; i < coordinates.size(); i++) {
|
2024-03-09 17:51:43 +01:00
|
|
|
if (!coordinates.at(i).isArray()) {
|
|
|
|
_errorString = "Invalid LineString data";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonArray data(coordinates.at(i).toArray());
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!a2c(data, PROJ(proj, parent), c))
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
Trackpoint t(c);
|
|
|
|
if (data.count() == 3 && data.at(2).isDouble())
|
|
|
|
t.setElevation(data.at(2).toDouble());
|
2024-03-11 23:09:38 +01:00
|
|
|
segment.append(t);
|
2019-01-25 22:18:21 +01:00
|
|
|
}
|
|
|
|
|
2024-03-12 21:30:15 +01:00
|
|
|
setSegmentProperties(segment, -1, properties);
|
2024-03-11 23:09:38 +01:00
|
|
|
TrackData track(segment);
|
2022-09-23 02:34:18 +02:00
|
|
|
setTrackProperties(track, properties);
|
2024-03-10 07:46:50 +01:00
|
|
|
tracks.append(track);
|
2019-02-11 23:28:08 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
bool GeoJSONParser::multiLineString(const QJsonObject &object,
|
2024-03-10 07:46:50 +01:00
|
|
|
const Projection &parent, const QJsonValue &properties,
|
|
|
|
QList<TrackData> &tracks)
|
2019-01-25 22:18:21 +01:00
|
|
|
{
|
2024-03-09 17:51:43 +01:00
|
|
|
if (!object.contains("coordinates")) {
|
|
|
|
_errorString = "Missing MultiLineString coordinates array";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (object["coordinates"].isNull())
|
|
|
|
return true;
|
|
|
|
QJsonArray coordinates(object["coordinates"].toArray());
|
|
|
|
if (coordinates.isEmpty())
|
|
|
|
return true;
|
|
|
|
Projection proj;
|
|
|
|
if (!crs(object, proj))
|
|
|
|
return false;
|
2024-03-10 07:46:50 +01:00
|
|
|
TrackData track;
|
2024-03-11 23:09:38 +01:00
|
|
|
Coordinates c;
|
2019-02-11 23:28:08 +01:00
|
|
|
|
2019-01-25 22:18:21 +01:00
|
|
|
for (int i = 0; i < coordinates.size(); i++) {
|
|
|
|
if (!coordinates.at(i).isArray()) {
|
2024-03-09 17:51:43 +01:00
|
|
|
_errorString = "Invalid MultiLineString data";
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
|
|
|
} else {
|
2024-03-12 21:30:15 +01:00
|
|
|
SegmentData segment;
|
2024-03-09 17:51:43 +01:00
|
|
|
|
|
|
|
QJsonArray ls(coordinates.at(i).toArray());
|
|
|
|
for (int j = 0; j < ls.size(); j++) {
|
|
|
|
if (!ls.at(j).isArray()) {
|
|
|
|
_errorString = "Invalid MultiLineString LineString data";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonArray data(ls.at(j).toArray());
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!a2c(data, PROJ(proj, parent), c))
|
2024-03-09 17:51:43 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
Trackpoint t(c);
|
|
|
|
if (data.count() == 3 && data.at(2).isDouble())
|
|
|
|
t.setElevation(data.at(2).toDouble());
|
2024-03-12 21:30:15 +01:00
|
|
|
segment.append(t);
|
2024-03-09 17:51:43 +01:00
|
|
|
}
|
2024-03-10 07:46:50 +01:00
|
|
|
|
2024-03-12 21:30:15 +01:00
|
|
|
setSegmentProperties(segment, track.size(), properties);
|
|
|
|
track.append(segment);
|
2019-01-25 22:18:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
setTrackProperties(track, properties);
|
2024-03-10 07:46:50 +01:00
|
|
|
tracks.append(track);
|
2024-03-09 17:51:43 +01:00
|
|
|
|
2019-01-25 22:18:21 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
bool GeoJSONParser::polygon(const QJsonObject &object, const Projection &parent,
|
2024-03-10 07:46:50 +01:00
|
|
|
const QJsonValue &properties, QList<Area> &areas)
|
2019-01-31 01:46:53 +01:00
|
|
|
{
|
2024-03-09 17:51:43 +01:00
|
|
|
if (!object.contains("coordinates")) {
|
|
|
|
_errorString = "Missing Polygon coordinates array";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (object["coordinates"].isNull())
|
|
|
|
return true;
|
|
|
|
QJsonArray coordinates(object["coordinates"].toArray());
|
|
|
|
if (coordinates.isEmpty())
|
|
|
|
return true;
|
|
|
|
Projection proj;
|
|
|
|
if (!crs(object, proj))
|
|
|
|
return false;
|
2024-03-11 23:09:38 +01:00
|
|
|
::Polygon poly;
|
|
|
|
Coordinates c;
|
2024-03-09 17:51:43 +01:00
|
|
|
|
2019-01-31 01:46:53 +01:00
|
|
|
for (int i = 0; i < coordinates.size(); i++) {
|
|
|
|
if (!coordinates.at(i).isArray()) {
|
|
|
|
_errorString = "Invalid Polygon linear ring";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
QJsonArray lr(coordinates.at(i).toArray());
|
2021-04-10 15:27:40 +02:00
|
|
|
QVector<Coordinates> data;
|
2019-01-31 01:46:53 +01:00
|
|
|
|
|
|
|
for (int j = 0; j < lr.size(); j++) {
|
2024-03-09 17:51:43 +01:00
|
|
|
if (!lr.at(j).isArray()) {
|
|
|
|
_errorString = "Invalid Polygon linear ring data";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-31 01:46:53 +01:00
|
|
|
QJsonArray point(lr.at(j).toArray());
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!a2c(point, PROJ(proj, parent), c))
|
2019-01-31 01:46:53 +01:00
|
|
|
return false;
|
2024-03-11 23:09:38 +01:00
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
data.append(c);
|
2019-01-31 01:46:53 +01:00
|
|
|
}
|
2021-04-10 15:27:40 +02:00
|
|
|
|
2024-03-11 23:09:38 +01:00
|
|
|
poly.append(data);
|
2019-01-31 01:46:53 +01:00
|
|
|
}
|
|
|
|
|
2024-03-11 23:09:38 +01:00
|
|
|
Area area(poly);
|
2022-09-23 02:34:18 +02:00
|
|
|
setAreaProperties(area, properties);
|
2024-03-10 07:46:50 +01:00
|
|
|
areas.append(area);
|
2019-01-31 01:46:53 +01:00
|
|
|
|
2021-03-07 11:58:21 +01:00
|
|
|
return true;
|
2019-01-31 01:46:53 +01:00
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
bool GeoJSONParser::multiPolygon(const QJsonObject &object,
|
2024-03-10 07:46:50 +01:00
|
|
|
const Projection &parent, const QJsonValue &properties, QList<Area> &areas)
|
2019-01-31 01:46:53 +01:00
|
|
|
{
|
2024-03-09 17:51:43 +01:00
|
|
|
if (!object.contains("coordinates")) {
|
|
|
|
_errorString = "Missing MultiPolygon coordinates array";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (object["coordinates"].isNull())
|
|
|
|
return true;
|
|
|
|
QJsonArray coordinates(object["coordinates"].toArray());
|
|
|
|
if (coordinates.isEmpty())
|
|
|
|
return true;
|
|
|
|
Projection proj;
|
|
|
|
if (!crs(object, proj))
|
|
|
|
return false;
|
2024-03-10 07:46:50 +01:00
|
|
|
Area area;
|
2024-03-11 23:09:38 +01:00
|
|
|
Coordinates c;
|
2019-01-31 01:46:53 +01:00
|
|
|
|
|
|
|
for (int i = 0; i < coordinates.size(); i++) {
|
|
|
|
if (!coordinates.at(i).isArray()) {
|
2024-03-09 17:51:43 +01:00
|
|
|
_errorString = "Invalid MultiPolygon data";
|
2019-01-31 01:46:53 +01:00
|
|
|
return false;
|
|
|
|
} else {
|
2024-03-11 23:09:38 +01:00
|
|
|
::Polygon poly;
|
2024-03-09 17:51:43 +01:00
|
|
|
|
|
|
|
QJsonArray polygon(coordinates.at(i).toArray());
|
|
|
|
for (int j = 0; j < polygon.size(); j++) {
|
|
|
|
if (!polygon.at(j).isArray()) {
|
|
|
|
_errorString = "Invalid MultiPolygon linear ring";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonArray lr(polygon.at(j).toArray());
|
|
|
|
QVector<Coordinates> data;
|
|
|
|
|
|
|
|
for (int k = 0; k < lr.size(); k++) {
|
|
|
|
if (!lr.at(k).isArray()) {
|
|
|
|
_errorString = "Invalid MultiPolygon linear ring data";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonArray point(lr.at(k).toArray());
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!a2c(point, PROJ(proj, parent), c))
|
2024-03-09 17:51:43 +01:00
|
|
|
return false;
|
2024-03-11 23:09:38 +01:00
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
data.append(c);
|
|
|
|
}
|
|
|
|
|
2024-03-11 23:09:38 +01:00
|
|
|
poly.append(data);
|
2024-03-09 17:51:43 +01:00
|
|
|
}
|
|
|
|
|
2024-03-11 23:09:38 +01:00
|
|
|
area.append(poly);
|
2019-01-31 01:46:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
setAreaProperties(area, properties);
|
2024-03-10 07:46:50 +01:00
|
|
|
areas.append(area);
|
2024-03-09 17:51:43 +01:00
|
|
|
|
2019-01-31 01:46:53 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
bool GeoJSONParser::geometryCollection(const QJsonObject &object,
|
|
|
|
const Projection &parent, const QJsonValue &properties,
|
|
|
|
QList<TrackData> &tracks, QList<Area> &areas, QVector<Waypoint> &waypoints)
|
2019-01-25 22:18:21 +01:00
|
|
|
{
|
2024-03-12 21:30:15 +01:00
|
|
|
if (!object["geometries"].isArray()) {
|
2019-01-25 22:18:21 +01:00
|
|
|
_errorString = "Invalid/missing GeometryCollection geometries array";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
QJsonArray geometries(object["geometries"].toArray());
|
|
|
|
Projection proj;
|
|
|
|
if (!crs(object, proj))
|
|
|
|
return false;
|
|
|
|
|
2019-01-25 22:18:21 +01:00
|
|
|
for (int i = 0; i < geometries.size(); i++) {
|
|
|
|
QJsonObject geometry(geometries.at(i).toObject());
|
2024-03-09 17:51:43 +01:00
|
|
|
|
2019-01-25 22:18:21 +01:00
|
|
|
switch (type(geometry)) {
|
|
|
|
case Point:
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!point(geometry, PROJ(proj, parent), properties,
|
2024-03-10 07:46:50 +01:00
|
|
|
waypoints))
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
case MultiPoint:
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!multiPoint(geometry, PROJ(proj, parent), properties,
|
|
|
|
waypoints))
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
case LineString:
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!lineString(geometry, PROJ(proj, parent), properties,
|
|
|
|
tracks))
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
case MultiLineString:
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!multiLineString(geometry, PROJ(proj, parent), properties,
|
|
|
|
tracks))
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
case Polygon:
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!polygon(geometry, PROJ(proj, parent), properties, areas))
|
2019-01-31 01:46:53 +01:00
|
|
|
return false;
|
|
|
|
break;
|
2019-01-25 22:18:21 +01:00
|
|
|
case MultiPolygon:
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!multiPolygon(geometry, PROJ(proj, parent), properties,
|
|
|
|
areas))
|
2019-01-31 01:46:53 +01:00
|
|
|
return false;
|
2019-01-25 22:18:21 +01:00
|
|
|
break;
|
|
|
|
case GeometryCollection:
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!geometryCollection(geometry, PROJ(proj, parent),
|
2024-03-09 17:51:43 +01:00
|
|
|
properties, tracks, areas, waypoints))
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
_errorString = geometry["type"].toString()
|
|
|
|
+ ": invalid/missing geometry type";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
bool GeoJSONParser::feature(const QJsonObject &object, const Projection &parent,
|
|
|
|
QList<TrackData> &tracks, QList<Area> &areas, QVector<Waypoint> &waypoints)
|
2019-01-25 22:18:21 +01:00
|
|
|
{
|
2024-03-27 09:07:39 +01:00
|
|
|
if (object["geometry"].isNull())
|
|
|
|
return true;
|
2024-03-12 21:30:15 +01:00
|
|
|
if (!object["geometry"].isObject()) {
|
2024-03-09 17:51:43 +01:00
|
|
|
_errorString = "Invalid/missing Feature geometry object";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonValue properties(object["properties"]);
|
|
|
|
QJsonObject geometry(object["geometry"].toObject());
|
|
|
|
Projection proj;
|
|
|
|
if (!crs(object, proj))
|
|
|
|
return false;
|
2019-01-25 22:18:21 +01:00
|
|
|
|
|
|
|
switch (type(geometry)) {
|
|
|
|
case Point:
|
2024-03-11 23:09:38 +01:00
|
|
|
return point(geometry, PROJ(proj, parent), properties, waypoints);
|
2019-01-25 22:18:21 +01:00
|
|
|
case MultiPoint:
|
2024-03-11 23:09:38 +01:00
|
|
|
return multiPoint(geometry, PROJ(proj, parent), properties,
|
|
|
|
waypoints);
|
2019-01-25 22:18:21 +01:00
|
|
|
case LineString:
|
2024-03-11 23:09:38 +01:00
|
|
|
return lineString(geometry, PROJ(proj, parent), properties, tracks);
|
2019-01-25 22:18:21 +01:00
|
|
|
case MultiLineString:
|
2024-03-11 23:09:38 +01:00
|
|
|
return multiLineString(geometry, PROJ(proj, parent), properties,
|
|
|
|
tracks);
|
2019-01-25 22:18:21 +01:00
|
|
|
case GeometryCollection:
|
2024-03-11 23:09:38 +01:00
|
|
|
return geometryCollection(geometry, PROJ(proj, parent), properties,
|
|
|
|
tracks, areas, waypoints);
|
2019-01-25 22:18:21 +01:00
|
|
|
case Polygon:
|
2024-03-11 23:09:38 +01:00
|
|
|
return polygon(geometry, PROJ(proj, parent), properties, areas);
|
2019-01-25 22:18:21 +01:00
|
|
|
case MultiPolygon:
|
2024-03-11 23:09:38 +01:00
|
|
|
return multiPolygon(geometry, PROJ(proj, parent), properties, areas);
|
2019-01-25 22:18:21 +01:00
|
|
|
default:
|
|
|
|
_errorString = geometry["type"].toString()
|
|
|
|
+ ": invalid/missing Feature geometry";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
bool GeoJSONParser::featureCollection(const QJsonObject &object,
|
|
|
|
const Projection &parent, QList<TrackData> &tracks, QList<Area> &areas,
|
2019-01-31 01:46:53 +01:00
|
|
|
QVector<Waypoint> &waypoints)
|
2019-01-25 22:18:21 +01:00
|
|
|
{
|
2024-03-12 21:30:15 +01:00
|
|
|
if (!object["features"].isArray()) {
|
2019-01-25 22:18:21 +01:00
|
|
|
_errorString = "Invalid/missing FeatureCollection features array";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
QJsonArray features(object["features"].toArray());
|
|
|
|
Projection proj;
|
|
|
|
if (!crs(object, proj))
|
|
|
|
return false;
|
|
|
|
|
2019-01-25 22:18:21 +01:00
|
|
|
for (int i = 0; i < features.size(); i++)
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!feature(features.at(i).toObject(), PROJ(proj, parent), tracks,
|
|
|
|
areas, waypoints))
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool GeoJSONParser::parse(QFile *file, QList<TrackData> &tracks,
|
2019-01-31 01:46:53 +01:00
|
|
|
QList<RouteData> &routes, QList<Area> &areas, QVector<Waypoint> &waypoints)
|
2019-01-25 22:18:21 +01:00
|
|
|
{
|
|
|
|
Q_UNUSED(routes);
|
2023-02-14 00:49:41 +01:00
|
|
|
|
2024-03-11 23:09:38 +01:00
|
|
|
if (!possiblyJSONObject(file)) {
|
|
|
|
_errorString = "Not a GeoJSON file";
|
2023-02-14 00:49:41 +01:00
|
|
|
return false;
|
|
|
|
} else
|
|
|
|
file->reset();
|
|
|
|
|
2019-01-25 22:18:21 +01:00
|
|
|
QJsonParseError error;
|
|
|
|
QJsonDocument doc(QJsonDocument::fromJson(file->readAll(), &error));
|
|
|
|
|
|
|
|
if (doc.isNull()) {
|
2024-03-11 23:09:38 +01:00
|
|
|
_errorString = QString("JSON parse error on offset %1: %2")
|
|
|
|
.arg(QString::number(error.offset), error.errorString());
|
2019-01-25 22:18:21 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
QJsonObject object(doc.object());
|
|
|
|
Projection proj(GCS::WGS84());
|
2019-01-25 22:18:21 +01:00
|
|
|
|
2024-03-09 17:51:43 +01:00
|
|
|
switch (type(object)) {
|
2019-01-25 22:18:21 +01:00
|
|
|
case Point:
|
2024-03-10 07:46:50 +01:00
|
|
|
return point(object, proj, QJsonValue(), waypoints);
|
2019-01-25 22:18:21 +01:00
|
|
|
case MultiPoint:
|
2024-03-09 17:51:43 +01:00
|
|
|
return multiPoint(object, proj, QJsonValue(), waypoints);
|
2019-01-25 22:18:21 +01:00
|
|
|
case LineString:
|
2024-03-10 07:46:50 +01:00
|
|
|
return lineString(object, proj, QJsonValue(), tracks);
|
2019-01-25 22:18:21 +01:00
|
|
|
case MultiLineString:
|
2024-03-10 07:46:50 +01:00
|
|
|
return multiLineString(object, proj, QJsonValue(), tracks);
|
2019-01-25 22:18:21 +01:00
|
|
|
case GeometryCollection:
|
2024-03-09 17:51:43 +01:00
|
|
|
return geometryCollection(object, proj, QJsonValue(), tracks, areas,
|
|
|
|
waypoints);
|
2019-01-25 22:18:21 +01:00
|
|
|
case Feature:
|
2024-03-09 17:51:43 +01:00
|
|
|
return feature(object, proj, tracks, areas, waypoints);
|
2019-01-25 22:18:21 +01:00
|
|
|
case FeatureCollection:
|
2024-03-09 17:51:43 +01:00
|
|
|
return featureCollection(object, proj, tracks, areas, waypoints);
|
2019-01-25 22:18:21 +01:00
|
|
|
case Polygon:
|
2024-03-10 07:46:50 +01:00
|
|
|
return polygon(object, proj, QJsonValue(), areas);
|
2019-01-25 22:18:21 +01:00
|
|
|
case MultiPolygon:
|
2024-03-10 07:46:50 +01:00
|
|
|
return multiPolygon(object, proj, QJsonValue(), areas);
|
2019-01-25 22:18:21 +01:00
|
|
|
case Unknown:
|
2024-03-09 17:51:43 +01:00
|
|
|
if (object["type"].toString().isNull())
|
2019-01-25 22:18:21 +01:00
|
|
|
_errorString = "Not a GeoJSON file";
|
|
|
|
else
|
2024-03-09 17:51:43 +01:00
|
|
|
_errorString = object["type"].toString()
|
2019-01-25 22:18:21 +01:00
|
|
|
+ ": unknown GeoJSON object";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|