1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2025-03-15 19:27:45 +01:00
GPXSee/src/data/geojsonparser.cpp

657 lines
17 KiB
C++
Raw Normal View History

2019-01-25 22:18:21 +01:00
#include <QJsonDocument>
#include <QJsonArray>
#include "map/crs.h"
2019-01-25 22:18:21 +01:00
#include "geojsonparser.h"
#define MARKER_SIZE_MEDIUM 12
#define MARKER_SIZE_SMALL 8
#define MARKER_SIZE_LARGE 16
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);
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));
}
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();
}
track.setStyle(LineStyle(color, width, Qt::SolidLine));
}
static void setWaypointProperties(Waypoint &waypoint,
const QJsonValue &properties)
{
QColor color(0x7e, 0x7e, 0x7e);
int size = MARKER_SIZE_MEDIUM;
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));
}
static bool isWS(char c)
{
return (c == 0x20 || c == 0x09 || c == 0x0A || c == 0x0D) ? true : false;
}
static bool isJSONObject(QFile *file)
{
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
static Coordinates coordinates(const QJsonArray &data, const Projection &proj)
{
if (data.count() >= 2 && data.at(0).isDouble() && data.at(1).isDouble())
return proj.xy2ll(PointD(data.at(0).toDouble(), data.at(1).toDouble()));
else
return Coordinates();
}
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;
}
bool GeoJSONParser::point(const QJsonObject &object, const Projection &parent,
const QJsonValue &properties, Waypoint &waypoint)
2019-01-25 22:18:21 +01:00
{
if (!object.contains("coordinates")) {
_errorString = "Missing Point coordinates array";
2019-01-25 22:18:21 +01:00
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;
2019-01-25 22:18:21 +01:00
Coordinates c(::coordinates(coordinates, proj.isNull() ? parent : proj));
if (!c.isValid()) {
_errorString = "Invalid Point coordinates";
return false;
}
waypoint.setCoordinates(c);
2019-01-25 22:18:21 +01:00
if (coordinates.count() == 3 && coordinates.at(2).isDouble())
waypoint.setElevation(coordinates.at(2).toDouble());
setWaypointProperties(waypoint, properties);
2019-01-25 22:18:21 +01:00
return true;
}
bool GeoJSONParser::multiPoint(const QJsonObject &object,
const Projection &parent, const QJsonValue &properties,
QVector<Waypoint> &waypoints)
2019-01-25 22:18:21 +01:00
{
if (!object.contains("coordinates")) {
_errorString = "Missing MultiPoint 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;
2019-01-25 22:18:21 +01:00
for (int i = 0; i < coordinates.size(); i++) {
if (!coordinates.at(i).isArray()) {
_errorString = "Invalid MultiPoint data";
2019-01-25 22:18:21 +01:00
return false;
} else {
waypoints.resize(waypoints.size() + 1);
QJsonArray data(coordinates.at(i).toArray());
Coordinates c(::coordinates(data, proj.isNull() ? parent : proj));
if (!c.isValid()) {
_errorString = "Invalid MultiPoint coordinates";
2019-01-25 22:18:21 +01:00
return false;
}
waypoints.last().setCoordinates(c);
if (data.count() == 3 && data.at(2).isDouble())
waypoints.last().setElevation(data.at(2).toDouble());
setWaypointProperties(waypoints.last(), properties);
2019-01-25 22:18:21 +01:00
}
}
return true;
}
bool GeoJSONParser::lineString(const QJsonObject &object,
const Projection &parent, const QJsonValue &properties, TrackData &track)
2019-01-25 22:18:21 +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;
track.append(SegmentData());
2019-01-25 22:18:21 +01:00
for (int i = 0; i < coordinates.size(); i++) {
if (!coordinates.at(i).isArray()) {
_errorString = "Invalid LineString data";
return false;
}
QJsonArray data(coordinates.at(i).toArray());
Coordinates c(::coordinates(data, proj.isNull() ? parent : proj));
if (!c.isValid()) {
2019-01-31 01:46:53 +01:00
_errorString = "Invalid LineString coordinates";
2019-01-25 22:18:21 +01:00
return false;
}
Trackpoint t(c);
if (data.count() == 3 && data.at(2).isDouble())
t.setElevation(data.at(2).toDouble());
track.last().append(t);
2019-01-25 22:18:21 +01:00
}
setTrackProperties(track, properties);
2019-02-11 23:28:08 +01:00
return true;
}
bool GeoJSONParser::multiLineString(const QJsonObject &object,
const Projection &parent, const QJsonValue &properties, TrackData &track)
2019-01-25 22:18:21 +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;
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()) {
_errorString = "Invalid MultiLineString data";
2019-01-25 22:18:21 +01:00
return false;
} else {
2019-02-11 23:28:08 +01:00
track.append(SegmentData());
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());
Coordinates c(::coordinates(data, proj.isNull() ? parent : proj));
if (!c.isValid()) {
_errorString = "Invalid MultiLineString coordinates";
return false;
}
Trackpoint t(c);
if (data.count() == 3 && data.at(2).isDouble())
t.setElevation(data.at(2).toDouble());
track.last().append(t);
}
2019-01-25 22:18:21 +01:00
}
}
setTrackProperties(track, properties);
2019-01-25 22:18:21 +01:00
return true;
}
bool GeoJSONParser::polygon(const QJsonObject &object, const Projection &parent,
const QJsonValue &properties, Area &area)
2019-01-31 01:46:53 +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;
::Polygon pg;
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;
}
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++) {
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());
Coordinates c(::coordinates(point, proj.isNull() ? parent : proj));
if (!c.isValid()) {
2019-01-31 01:46:53 +01:00
_errorString = "Invalid Polygon linear ring coordinates";
return false;
}
data.append(c);
2019-01-31 01:46:53 +01:00
}
2021-04-10 15:27:40 +02:00
pg.append(data);
2019-01-31 01:46:53 +01:00
}
area.append(pg);
setAreaProperties(area, properties);
2019-01-31 01:46:53 +01:00
return true;
2019-01-31 01:46:53 +01:00
}
bool GeoJSONParser::multiPolygon(const QJsonObject &object,
const Projection &parent, const QJsonValue &properties, Area &area)
2019-01-31 01:46:53 +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;
2019-01-31 01:46:53 +01:00
for (int i = 0; i < coordinates.size(); i++) {
if (!coordinates.at(i).isArray()) {
_errorString = "Invalid MultiPolygon data";
2019-01-31 01:46:53 +01:00
return false;
} else {
::Polygon pg;
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());
Coordinates c(::coordinates(point, proj.isNull() ? parent : proj));
if (!c.isValid()) {
_errorString = "Invalid MultiPolygon linear ring coordinates";
return false;
}
data.append(c);
}
pg.append(data);
}
area.append(pg);
2019-01-31 01:46:53 +01:00
}
}
setAreaProperties(area, properties);
2019-01-31 01:46:53 +01:00
return true;
}
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
{
if (!object.contains("geometries") || !object["geometries"].isArray()) {
2019-01-25 22:18:21 +01:00
_errorString = "Invalid/missing GeometryCollection geometries array";
return false;
}
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());
2019-01-25 22:18:21 +01:00
switch (type(geometry)) {
case Point:
waypoints.resize(waypoints.size() + 1);
if (!point(geometry, proj.isNull() ? parent : proj, properties,
waypoints.last()))
2019-01-25 22:18:21 +01:00
return false;
break;
case MultiPoint:
if (!multiPoint(geometry, proj.isNull() ? parent : proj,
properties, waypoints))
2019-01-25 22:18:21 +01:00
return false;
break;
case LineString:
tracks.append(TrackData());
if (!lineString(geometry, proj.isNull() ? parent : proj,
properties, tracks.last()))
2019-01-25 22:18:21 +01:00
return false;
break;
case MultiLineString:
2019-02-11 23:28:08 +01:00
tracks.append(TrackData());
if (!multiLineString(geometry, proj.isNull() ? parent : proj,
properties, tracks.last()))
2019-01-25 22:18:21 +01:00
return false;
break;
case Polygon:
2019-01-31 01:46:53 +01:00
areas.append(Area());
if (!polygon(geometry, proj.isNull() ? parent : proj, properties,
areas.last()))
2019-01-31 01:46:53 +01:00
return false;
break;
2019-01-25 22:18:21 +01:00
case MultiPolygon:
2019-01-31 01:46:53 +01:00
areas.append(Area());
if (!multiPolygon(geometry, proj.isNull() ? parent : proj,
properties, areas.last()))
2019-01-31 01:46:53 +01:00
return false;
2019-01-25 22:18:21 +01:00
break;
case GeometryCollection:
if (!geometryCollection(geometry, proj.isNull() ? parent : proj,
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;
}
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
{
if (!object.contains("geometry") || !object["geometry"].isObject()) {
_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:
waypoints.resize(waypoints.size() + 1);
return point(geometry, proj.isNull() ? parent : proj, properties,
waypoints.last());
2019-01-25 22:18:21 +01:00
case MultiPoint:
return multiPoint(geometry, proj.isNull() ? parent : proj,
properties, waypoints);
2019-01-25 22:18:21 +01:00
case LineString:
tracks.append(TrackData());
return lineString(geometry, proj.isNull() ? parent : proj,
properties, tracks.last());
2019-01-25 22:18:21 +01:00
case MultiLineString:
2019-02-11 23:28:08 +01:00
tracks.append(TrackData());
return multiLineString(geometry, proj.isNull() ? parent : proj,
properties, tracks.last());
2019-01-25 22:18:21 +01:00
case GeometryCollection:
return geometryCollection(geometry, proj.isNull() ? parent : proj,
properties, tracks, areas, waypoints);
2019-01-25 22:18:21 +01:00
case Polygon:
2019-01-31 01:46:53 +01:00
areas.append(Area());
return polygon(geometry, proj.isNull() ? parent : proj, properties,
areas.last());
2019-01-25 22:18:21 +01:00
case MultiPolygon:
2019-01-31 01:46:53 +01:00
areas.append(Area());
return multiPolygon(geometry, proj.isNull() ? parent : proj,
properties, areas.last());
2019-01-25 22:18:21 +01:00
default:
_errorString = geometry["type"].toString()
+ ": invalid/missing Feature geometry";
return false;
}
}
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
{
if (!object.contains("features") || !object["features"].isArray()) {
2019-01-25 22:18:21 +01:00
_errorString = "Invalid/missing FeatureCollection features array";
return false;
}
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++)
if (!feature(features.at(i).toObject(), proj.isNull() ? parent : proj,
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);
if (!isJSONObject(file)) {
_errorString = "Not a JSON file";
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()) {
_errorString = "JSON parse error: " + error.errorString() + " ["
+ QString::number(error.offset) + "]";
return false;
}
QJsonObject object(doc.object());
Projection proj(GCS::WGS84());
2019-01-25 22:18:21 +01:00
switch (type(object)) {
2019-01-25 22:18:21 +01:00
case Point:
waypoints.resize(waypoints.size() + 1);
return point(object, proj, QJsonValue(), waypoints.last());
2019-01-25 22:18:21 +01:00
case MultiPoint:
return multiPoint(object, proj, QJsonValue(), waypoints);
2019-01-25 22:18:21 +01:00
case LineString:
tracks.append(TrackData());
return lineString(object, proj, QJsonValue(), tracks.last());
2019-01-25 22:18:21 +01:00
case MultiLineString:
2019-02-11 23:28:08 +01:00
tracks.append(TrackData());
return multiLineString(object, proj, QJsonValue(), tracks.last());
2019-01-25 22:18:21 +01:00
case GeometryCollection:
return geometryCollection(object, proj, QJsonValue(), tracks, areas,
waypoints);
2019-01-25 22:18:21 +01:00
case Feature:
return feature(object, proj, tracks, areas, waypoints);
2019-01-25 22:18:21 +01:00
case FeatureCollection:
return featureCollection(object, proj, tracks, areas, waypoints);
2019-01-25 22:18:21 +01:00
case Polygon:
2019-01-31 01:46:53 +01:00
areas.append(Area());
return polygon(object, proj, QJsonValue(), areas.last());
2019-01-25 22:18:21 +01:00
case MultiPolygon:
2019-01-31 01:46:53 +01:00
areas.append(Area());
return multiPolygon(object, proj, QJsonValue(), areas.last());
2019-01-25 22:18:21 +01:00
case Unknown:
if (object["type"].toString().isNull())
2019-01-25 22:18:21 +01:00
_errorString = "Not a GeoJSON file";
else
_errorString = object["type"].toString()
2019-01-25 22:18:21 +01:00
+ ": unknown GeoJSON object";
return false;
}
return true;
}