[PATCH 04/10] Completely rewrite the GeoJSON runner plugin for RFC7946 compliance

John Zaitseff J.Zaitseff at zap.org.au
Tue Aug 27 11:55:06 BST 2019


In particular, the plugin now handles GeoJSON files that do not contain a
FeatureCollection top-level object.  It also correctly handles
GeometryCollection objects that can nest other GeoJSON geometry objects
arbitrarily deep, by using a recursive parser.  Furthermore, altitude
values are now imported along with longitude and latitude.  Properties on
Feature objects are now handled in such a way that extending these to
cover styles is made easier.
---
 src/plugins/runner/json/JsonParser.cpp | 472 ++++++++++++++-----------
 src/plugins/runner/json/JsonParser.h   |  34 +-
 2 files changed, 287 insertions(+), 219 deletions(-)

diff --git a/src/plugins/runner/json/JsonParser.cpp b/src/plugins/runner/json/JsonParser.cpp
index 3fcc5bed5..d9c1008ec 100644
--- a/src/plugins/runner/json/JsonParser.cpp
+++ b/src/plugins/runner/json/JsonParser.cpp
@@ -1,11 +1,16 @@
 /*
- This file is part of the Marble Virtual Globe.
+    This file is part of the Marble Virtual Globe.
 
- This program is free software licensed under the GNU LGPL. You can
- find a copy of this license in LICENSE.txt in the top directory of
- the source code.
+    The JsonParser class reads in a GeoJSON document that conforms to
+    RFC7946 (including relevant errata).  Attributes are stored as OSM
+    tags.
 
- Copyright 2013 Ander Pijoan <ander.pijoan at deusto.es>
+    This program is free software licensed under the GNU LGPL. You can
+    find a copy of this license in LICENSE.txt in the top directory of
+    the source code.
+
+    Copyright 2013 Ander Pijoan <ander.pijoan at deusto.es>
+    Copyright 2019 John Zaitseff <J.Zaitseff at zap.org.au>
 */
 
 #include "JsonParser.h"
@@ -14,6 +19,7 @@
 #include "GeoDataPolygon.h"
 #include "GeoDataLinearRing.h"
 #include "GeoDataPoint.h"
+#include "GeoDataMultiGeometry.h"
 #include "MarbleDebug.h"
 #include "StyleBuilder.h"
 #include "osm/OsmPlacemarkData.h"
@@ -44,274 +50,316 @@ GeoDataDocument *JsonParser::releaseDocument()
 
 bool JsonParser::read( QIODevice* device )
 {
-    // Assert previous document got released.
+    // Release the previous document if required
     delete m_document;
     m_document = new GeoDataDocument;
     Q_ASSERT( m_document );
 
-    // Read file data
+    // Read JSON file data
     QJsonParseError error;
     const QJsonDocument jsonDoc = QJsonDocument::fromJson(device->readAll(), &error);
 
     if (jsonDoc.isNull()) {
-        qDebug() << "Error parsing GeoJSON : " << error.errorString();
+        qDebug() << "Error parsing GeoJSON:" << error.errorString();
+        return false;
+    } else if (! jsonDoc.isObject()) {
+        qDebug() << "Invalid file, does not contain a GeoJSON object";
         return false;
     }
 
-    // Start parsing
-    const QJsonValue featuresValue = jsonDoc.object().value(QStringLiteral("features"));
+    // Valid GeoJSON documents may not always contain a FeatureCollection object with subsidiary
+    // Feature objects, or even a single Feature object: they might contain just a single geometry
+    // object.  Handle such cases by creating a wrapper Feature object if required.
+
+    const QString jsonObjectType = jsonDoc.object().value(QStringLiteral("type")).toString();
+
+    if (jsonObjectType == QStringLiteral("FeatureCollection")
+        || jsonObjectType == QStringLiteral("Feature")) {
+
+        // A normal GeoJSON document: parse it recursively
+        return parseGeoJsonTopLevel(jsonDoc.object());
+
+    } else {
+        // Create a wrapper Feature object and parse that
+
+        QJsonObject jsonWrapper;
+        QJsonObject jsonWrapperProperties;
+
+        jsonWrapper["type"] = QStringLiteral("Feature");
+        jsonWrapper["geometry"] = jsonDoc.object();
+        jsonWrapper["properties"] = jsonWrapperProperties;
+
+        return parseGeoJsonTopLevel(jsonWrapper);
+    }
+}
+
+bool JsonParser::parseGeoJsonTopLevel( const QJsonObject& jsonObject )
+{
+    // Every GeoJSON object must have a case-sensitive "type" member (see RFC7946 section 3)
+    const QString jsonObjectType = jsonObject.value(QStringLiteral("type")).toString();
 
-    // In GeoJSON format, geometries are stored in features, so we iterate on features
-    if (featuresValue.isArray()) {
-        const QJsonArray featureArray = featuresValue.toArray();
+    if (jsonObjectType == QStringLiteral("FeatureCollection")) {
+        // Handle the FeatureCollection object, which may contain multiple Feature objects in it
 
-        // Parse each feature
+        const QJsonArray featureArray = jsonObject.value(QStringLiteral("features")).toArray();
         for (int featureIndex = 0; featureIndex < featureArray.size(); ++featureIndex) {
-            const QJsonObject featureObject = featureArray[featureIndex].toObject();
+            if (! parseGeoJsonTopLevel( featureArray[featureIndex].toObject() )) {
+                return false;
+            }
+        }
+        return true;
+
+    } else if (jsonObjectType == QStringLiteral("Feature")) {
+        // Handle the Feature object, which contains a single geometry object and possibly
+        // associated properties.  Note that only Feature objects can have recognised properties.
 
-            // Check if the feature contains a geometry
-            const QJsonValue geometryValue = featureObject.value(QStringLiteral("geometry"));
-            if (geometryValue.isObject()) {
-                const QJsonObject geometryObject = geometryValue.toObject();
+        QVector<GeoDataGeometry*> geometryList;     // Populated by parseGeoJsonSubLevel()
 
-                // Variables for creating the geometry
-                QList<GeoDataGeometry*> geometryList;
-                QList<GeoDataPlacemark*> placemarkList;
+        if (! parseGeoJsonSubLevel( jsonObject.value(QStringLiteral("geometry")).toObject(),
+                geometryList )) {
+            return false;
+        }
 
-                // Create the different geometry types
-                const QString geometryType = geometryObject.value(QStringLiteral("type")).toString().toUpper();
+        // Create the placemark for this feature object with appropriate geometry
 
-                if (geometryType == QLatin1String("POLYGON")) {
-                    // Check first that there are coordinates
-                    const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates"));
-                    if (coordinatesValue.isArray()) {
-                        const QJsonArray coordinateArray = coordinatesValue.toArray();
+        GeoDataPlacemark* placemark = new GeoDataPlacemark();
 
-                        GeoDataPolygon * geom = new GeoDataPolygon( RespectLatitudeCircle | Tessellate );
+        if (geometryList.length() < 1) {
+            // No geometries available to add to the placemark
+            ;
 
-                        // Coordinates first array will be the outer boundary, if there are more
-                        // positions those will be inner holes
-                        for (int ringIndex = 0 ; ringIndex < coordinateArray.size(); ++ringIndex) {
-                            const QJsonArray ringArray = coordinateArray[ringIndex].toArray();
+        } else if (geometryList.length() == 1) {
+            // Single geometry
+            placemark->setGeometry(geometryList[0]);
 
-                            GeoDataLinearRing linearRing;
+        } else {
+            // Multiple geometries require a GeoDataMultiGeometry class
 
-                            for (int coordinatePairIndex = 0; coordinatePairIndex < ringArray.size(); ++coordinatePairIndex) {
-                                const QJsonArray coordinatePairArray = ringArray[coordinatePairIndex].toArray();
+            GeoDataMultiGeometry* geom = new GeoDataMultiGeometry();
+            for (int i = 0; i < geometryList.length(); ++i) {
+                geom->append(geometryList[i]);
+            }
+            placemark->setGeometry(geom);
+        }
 
-                                const qreal longitude = coordinatePairArray.at(0).toDouble();
-                                const qreal latitude = coordinatePairArray.at(1).toDouble();
+        // Parse any associated properties
 
-                                linearRing.append( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) );
-                            }
+        const QJsonObject propertiesObject = jsonObject.value(QStringLiteral("properties")).toObject();
+        QJsonObject::ConstIterator iter = propertiesObject.begin();
+	const QJsonObject::ConstIterator end = propertiesObject.end();
 
-                            // Outer ring
-                            if (ringIndex == 0) {
-                                geom->setOuterBoundary( linearRing );
-                            }
-                            // Inner holes
-                            else {
-                                geom->appendInnerBoundary( linearRing );
-                            }
-                        }
-                        geometryList.append( geom );
-                    }
+	OsmPlacemarkData osmData;
 
-                } else if (geometryType == QLatin1String("MULTIPOLYGON")) {
-                    // Check first that there are coordinates
-                    const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates"));
-                    if (coordinatesValue.isArray()) {
-                        const QJsonArray coordinateArray = coordinatesValue.toArray();
+	for ( ; iter != end; ++iter) {
+	    // Pass the value through QVariant to also get booleans and numbers
+	    const QString propertyValue = iter.value().toVariant().toString();
+	    const QString propertyKey = iter.key();
 
-                        for (int polygonIndex = 0; polygonIndex < coordinateArray.size(); ++polygonIndex) {
-                            const QJsonArray polygonArray = coordinateArray[polygonIndex].toArray();
+	    if (iter.value().isObject() || iter.value().isArray()) {
+		qDebug() << "Skipping unsupported JSON property containing an object or array:" << propertyKey;
+		continue;
+	    }
 
-                            GeoDataPolygon * geom = new GeoDataPolygon( RespectLatitudeCircle | Tessellate );
+	    osmData.addTag(propertyKey, propertyValue);
 
-                            // Coordinates first array will be the outer boundary, if there are more
-                            // positions those will be inner holes
-                            for (int ringIndex = 0 ; ringIndex < polygonArray.size(); ++ringIndex) {
-                                const QJsonArray ringArray = polygonArray[ringIndex].toArray();
+	    if (propertyKey == QStringLiteral("name")) {
+		placemark->setName(propertyValue);
+	    }
+	}
 
-                                GeoDataLinearRing linearRing;
+	placemark->setOsmData(osmData);
+	placemark->setVisible(true);
 
-                                for (int coordinatePairIndex = 0; coordinatePairIndex < ringArray.size(); ++coordinatePairIndex) {
-                                    const QJsonArray coordinatePairArray = ringArray[coordinatePairIndex].toArray();
+	const GeoDataPlacemark::GeoDataVisualCategory category =
+	    StyleBuilder::determineVisualCategory(osmData);
+	if (category != GeoDataPlacemark::None) {
+	    placemark->setVisualCategory(category);
+	}
 
-                                    const qreal longitude = coordinatePairArray.at(0).toDouble();
-                                    const qreal latitude = coordinatePairArray.at(1).toDouble();
+	m_document->append(placemark);
+	return true;
 
-                                    linearRing.append( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) );
-                                }
+    } else {
+	qDebug() << "Missing FeatureCollection or Feature object in GeoJSON file";
+	return false;
+    }
+}
 
-                                // Outer ring
-                                if (ringIndex == 0) {
-                                    geom->setOuterBoundary( linearRing );
-                                }
-                                // Inner holes
-                                else {
-                                    geom->appendInnerBoundary( linearRing );
-                                }
-                            }
-                            geometryList.append( geom );
-                        }
-                    }
+bool JsonParser::parseGeoJsonSubLevel( const QJsonObject& jsonObject,
+				       QVector<GeoDataGeometry*>& geometryList )
+{
+    // The GeoJSON object type
+    const QString jsonObjectType = jsonObject.value(QStringLiteral("type")).toString();
 
-                } else if (geometryType == QLatin1String("LINESTRING")) {
+    if (jsonObjectType == QStringLiteral("FeatureCollection")
+	|| jsonObjectType == QStringLiteral("Feature")) {
 
-                    // Check first that there are coordinates
-                    const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates"));
-                    if (coordinatesValue.isArray()) {
-                        const QJsonArray coordinateArray = coordinatesValue.toArray();
+	qDebug() << "Cannot have FeatureCollection or Feature objects at this level of the GeoJSON file";
+	return false;
 
-                        GeoDataLineString * geom = new GeoDataLineString( RespectLatitudeCircle | Tessellate );
+    } else if (jsonObjectType == QStringLiteral("GeometryCollection")) {
+	// Handle the GeometryCollection object, which may contain multiple geometry objects
 
-                        for (int coordinatePairIndex = 0; coordinatePairIndex < coordinateArray.size(); ++coordinatePairIndex) {
-                            const QJsonArray coordinatePairArray = coordinateArray[coordinatePairIndex].toArray();
+	const QJsonArray geometryArray = jsonObject.value(QStringLiteral("geometries")).toArray();
+	for (int geometryIndex = 0; geometryIndex < geometryArray.size(); ++geometryIndex) {
+	    if (! parseGeoJsonSubLevel( geometryArray[geometryIndex].toObject(), geometryList )) {
+		return false;
+	    }
+	}
 
-                            const qreal longitude = coordinatePairArray.at(0).toDouble();
-                            const qreal latitude = coordinatePairArray.at(1).toDouble();
+	return true;
+    }
 
-                            geom->append( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) );
-                        }
-                        geometryList.append( geom );
-                    }
+    // Handle remaining GeoJSON objects, which each have a "coordinates" member (an array)
 
-                } else if (geometryType == QLatin1String("MULTILINESTRING")) {
+    const QJsonArray coordinateArray = jsonObject.value(QStringLiteral("coordinates")).toArray();
 
-                    // Check first that there are coordinates
-                    const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates"));
-                    if (coordinatesValue.isArray()) {
-                        const QJsonArray coordinateArray = coordinatesValue.toArray();
-
-                        for (int lineStringIndex = 0; lineStringIndex < coordinateArray.size(); ++lineStringIndex) {
-                            const QJsonArray lineStringArray = coordinateArray[lineStringIndex].toArray();
+    if (jsonObjectType == QStringLiteral("Point")) {
+	// A Point object has a single GeoJSON position: an array of at least two values
 
-                            GeoDataLineString * geom = new GeoDataLineString( RespectLatitudeCircle | Tessellate );
+	GeoDataPoint* geom = new GeoDataPoint();
+	const qreal lon = coordinateArray.at(0).toDouble();
+	const qreal lat = coordinateArray.at(1).toDouble();
+	const qreal alt = coordinateArray.at(2).toDouble();	// If missing, uses 0 as the default
 
-                            for (int coordinatePairIndex = 0; coordinatePairIndex < lineStringArray.size(); ++coordinatePairIndex) {
-                                const QJsonArray coordinatePairArray = lineStringArray[coordinatePairIndex].toArray();
+	geom->setCoordinates( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree ));
+	geometryList.append(geom);
 
-                                const qreal longitude = coordinatePairArray.at(0).toDouble();
-                                const qreal latitude = coordinatePairArray.at(1).toDouble();
+	return true;
 
-                                geom->append( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) );
-                            }
-                            geometryList.append( geom );
-                        }
-                    }
-
-                } else if (geometryType == QLatin1String("POINT")) {
-
-                    // Check first that there are coordinates
-                    const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates"));
-                    if (coordinatesValue.isArray()) {
-                        const QJsonArray coordinatePairArray = coordinatesValue.toArray();
-
-                        GeoDataPoint * geom = new GeoDataPoint();
-
-                        const qreal longitude = coordinatePairArray.at(0).toDouble();
-                        const qreal latitude = coordinatePairArray.at(1).toDouble();
+    } else if (jsonObjectType == QStringLiteral("MultiPoint")) {
+	// A MultiPoint object has an array of GeoJSON positions (ie, a two-level array)
 
-                        geom->setCoordinates( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) );
+	for (int positionIndex = 0; positionIndex < coordinateArray.size(); ++positionIndex) {
+	    const QJsonArray positionArray = coordinateArray[positionIndex].toArray();
 
-                        geometryList.append( geom );
-                    }
-                } else if (geometryType == QLatin1String("MULTIPOINT")) {
+	    GeoDataPoint* geom = new GeoDataPoint();
+	    const qreal lon = positionArray.at(0).toDouble();
+	    const qreal lat = positionArray.at(1).toDouble();
+	    const qreal alt = positionArray.at(2).toDouble();
 
-                    // Check first that there are coordinates
-                    const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates"));
-                    if (coordinatesValue.isArray()) {
-                        const QJsonArray coordinateArray = coordinatesValue.toArray();
+	    geom->setCoordinates( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree ));
+	    geometryList.append(geom);
+	}
 
-                        for (int pointIndex = 0; pointIndex < coordinateArray.size(); ++pointIndex) {
-                            const QJsonArray coordinatePairArray = coordinateArray[pointIndex].toArray();
+	return true;
 
-                            GeoDataPoint * geom = new GeoDataPoint();
+    } else if (jsonObjectType == QStringLiteral("LineString")) {
+	// A LineString object has an array of GeoJSON positions (ie, a two-level array)
 
-                            const qreal longitude = coordinatePairArray.at(0).toDouble();
-                            const qreal latitude = coordinatePairArray.at(1).toDouble();
+	GeoDataLineString* geom = new GeoDataLineString( RespectLatitudeCircle | Tessellate );
 
-                            geom->setCoordinates( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) );
+	for (int positionIndex = 0; positionIndex < coordinateArray.size(); ++positionIndex) {
+	    const QJsonArray positionArray = coordinateArray[positionIndex].toArray();
 
-                            geometryList.append( geom );
-                        }
-                    }
-                }
-
-
-                // Parse the features properties
-                const QJsonValue propertiesValue = featureObject.value(QStringLiteral("properties"));
-                if (!geometryList.isEmpty() && propertiesValue.isObject()) {
-                    const QJsonObject propertiesObject = propertiesValue.toObject();
-
-                    // First create a placemark for each geometry, there could be multi geometries
-                    // that are translated into more than one geometry/placemark
-                    for ( int numberGeometries = 0 ; numberGeometries < geometryList.length() ; numberGeometries++ ) {
-                        GeoDataPlacemark * placemark = new GeoDataPlacemark();
-                        placemarkList.append( placemark );
-                    }
+	    const qreal lon = positionArray.at(0).toDouble();
+	    const qreal lat = positionArray.at(1).toDouble();
+	    const qreal alt = positionArray.at(2).toDouble();
 
-                    OsmPlacemarkData osmData;
+	    geom->append( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree ));
+	}
+	geometryList.append(geom);
 
-                    QJsonObject::ConstIterator it = propertiesObject.begin();
-                    const QJsonObject::ConstIterator end = propertiesObject.end();
-                    for ( ; it != end; ++it) {
-                        if (it.value().isObject() || it.value().isArray()) {
-                            qDebug() << "Skipping property, values of type arrays and objects not supported:" << it.key();
-                            continue;
-                        }
+	return true;
 
-                        // pass value through QVariant to also get bool & numbers
-                        osmData.addTag(it.key(), it.value().toVariant().toString());
-                    }
+    } else if (jsonObjectType == QStringLiteral("MultiLineString")) {
+	// A MultiLineString object has an array of arrays of GeoJSON positions (three-level)
 
-                    // If the property read, is the features name
-                    const auto tagIter = osmData.findTag(QStringLiteral("name"));
-                    if (tagIter != osmData.tagsEnd()) {
-                        const QString& name = tagIter.value();
-                        for (int pl = 0 ; pl < placemarkList.length(); ++pl) {
-                            placemarkList.at(pl)->setName(name);
-                        }
-                    }
+	for (int lineStringIndex = 0; lineStringIndex < coordinateArray.size(); ++lineStringIndex) {
+	    const QJsonArray lineStringArray = coordinateArray[lineStringIndex].toArray();
 
-                    const GeoDataPlacemark::GeoDataVisualCategory category = StyleBuilder::determineVisualCategory(osmData);
-                    if (category != GeoDataPlacemark::None) {
-                        // Add the visual category to all the placemarks
-                        for (int pl = 0 ; pl < placemarkList.length(); ++pl) {
-                            placemarkList.at(pl)->setVisualCategory(category);
-                            placemarkList.at(pl)->setOsmData(osmData);
-                        }
-                    }
-                }
+	    GeoDataLineString* geom = new GeoDataLineString( RespectLatitudeCircle | Tessellate );
 
-                // Add the geometry to the document
-                if ( geometryList.length() == placemarkList.length() ) {
-
-                    while( placemarkList.length() > 0 ) {
-
-                        GeoDataPlacemark * placemark = placemarkList.last();
-                        placemarkList.pop_back();
-
-                        GeoDataGeometry * geom = geometryList.last();
-                        geometryList.pop_back();
-
-                        placemark->setGeometry( geom );
-                        placemark->setVisible( true );
-                        m_document->append( placemark );
-                    }
-                }
-
-                // If geometries or placemarks missing inside the lists, delete them
-                qDeleteAll( geometryList.begin(), geometryList.end() );
-                geometryList.clear();
-                qDeleteAll( placemarkList.begin(), placemarkList.end() );
-                placemarkList.clear();
-            }
-        }
+	    for (int positionIndex = 0; positionIndex < lineStringArray.size(); ++positionIndex) {
+		const QJsonArray positionArray = lineStringArray[positionIndex].toArray();
+
+		const qreal lon = positionArray.at(0).toDouble();
+		const qreal lat = positionArray.at(1).toDouble();
+		const qreal alt = positionArray.at(2).toDouble();
+
+		geom->append( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree ));
+	    }
+	    geometryList.append(geom);
+	}
+
+	return true;
+
+    } else if (jsonObjectType == QStringLiteral("Polygon")) {
+	// A Polygon object has an array of arrays of GeoJSON positions: the first array within the
+	// top-level Polygon coordinates array is the outer boundary, following arrays are inner
+	// holes (if any)
+
+	GeoDataPolygon* geom = new GeoDataPolygon( RespectLatitudeCircle | Tessellate );
+
+	for (int ringIndex = 0; ringIndex < coordinateArray.size(); ++ringIndex) {
+	    const QJsonArray ringArray = coordinateArray[ringIndex].toArray();
+
+	    GeoDataLinearRing linearRing;
+
+	    for (int positionIndex = 0; positionIndex < ringArray.size(); ++positionIndex) {
+		const QJsonArray positionArray = ringArray[positionIndex].toArray();
+
+		const qreal lon = positionArray.at(0).toDouble();
+		const qreal lat = positionArray.at(1).toDouble();
+		const qreal alt = positionArray.at(2).toDouble();
+
+		linearRing.append( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree ));
+	    }
+
+	    if (ringIndex == 0) {
+		// Outer boundary of the polygon
+		geom->setOuterBoundary(linearRing);
+	    } else {
+		geom->appendInnerBoundary(linearRing);
+	    }
+	}
+	geometryList.append(geom);
+
+	return true;
+
+    } else if (jsonObjectType == QStringLiteral("MultiPolygon")) {
+	// A MultiPolygon object has an array of Polygon arrays (ie, a four-level array)
+
+	for (int polygonIndex = 0; polygonIndex < coordinateArray.size(); ++polygonIndex) {
+	    const QJsonArray polygonArray = coordinateArray[polygonIndex].toArray();
+
+	    GeoDataPolygon* geom = new GeoDataPolygon( RespectLatitudeCircle | Tessellate );
+
+	    for (int ringIndex = 0; ringIndex < polygonArray.size(); ++ringIndex) {
+		const QJsonArray ringArray = polygonArray[ringIndex].toArray();
+
+		GeoDataLinearRing linearRing;
+
+		for (int positionIndex = 0; positionIndex < ringArray.size(); ++positionIndex) {
+		    const QJsonArray positionArray = ringArray[positionIndex].toArray();
+
+		    const qreal lon = positionArray.at(0).toDouble();
+		    const qreal lat = positionArray.at(1).toDouble();
+		    const qreal alt = positionArray.at(2).toDouble();
+
+		    linearRing.append( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree ));
+		}
+
+		if (ringIndex == 0) {
+		    // Outer boundary of the polygon
+		    geom->setOuterBoundary(linearRing);
+		} else {
+		    geom->appendInnerBoundary(linearRing);
+		}
+	    }
+	    geometryList.append(geom);
+	}
+
+	return true;
+
+    } else if (jsonObjectType == QStringLiteral("")) {
+	// Unlocated Feature objects have a null value for "geometry" (RFC7946 section 3.2)
+	return true;
+
+    } else {
+	qDebug() << "Unknown GeoJSON object type" << jsonObjectType;
+	return false;
     }
-    return true;
 }
 
 }
-
diff --git a/src/plugins/runner/json/JsonParser.h b/src/plugins/runner/json/JsonParser.h
index a9c758841..69fda8c73 100644
--- a/src/plugins/runner/json/JsonParser.h
+++ b/src/plugins/runner/json/JsonParser.h
@@ -1,21 +1,26 @@
 /*
- This file is part of the Marble Virtual Globe.
+    This file is part of the Marble Virtual Globe.
 
- This program is free software licensed under the GNU LGPL. You can
- find a copy of this license in LICENSE.txt in the top directory of
- the source code.
+    This program is free software licensed under the GNU LGPL. You can
+    find a copy of this license in LICENSE.txt in the top directory of
+    the source code.
 
- Copyright 2013 Ander Pijoan <ander.pijoan at deusto.es>
+    Copyright 2013 Ander Pijoan <ander.pijoan at deusto.es>
+    Copyright 2019 John Zaitseff <J.Zaitseff at zap.org.au>
 */
 
 #ifndef MARBLE_JSONPARSER_H
 #define MARBLE_JSONPARSER_H
 
 class QIODevice;
+class QJsonObject;
+
+#include <QVector>
 
 namespace Marble {
 
 class GeoDataDocument;
+class GeoDataGeometry;
 
 class JsonParser
 {
@@ -24,8 +29,8 @@ public:
     ~JsonParser();
 
     /**
-     * @brief parse the json file
-     * @return true if the parsed has been successful
+     * @brief parse the GeoJSON file
+     * @return true if parsing of the file was successful
      */
     bool read(QIODevice*);
 
@@ -39,6 +44,21 @@ public:
 private:
 
     GeoDataDocument* m_document;
+
+    /**
+     * @brief parse a top-level GeoJSON object (FeatureCollection or Feature)
+     * @param jsonObject  the object to parse
+     * @return true if parsing of the top-level object was successful
+     */
+    bool parseGeoJsonTopLevel(const QJsonObject&);
+
+    /**
+      * @brief parse a sub-level GeoJSON object
+      * @param jsonObject    the object to parse
+      * @param geometryList  a list of geometries pass back to the caller
+      * @return true if parsing of the object was successful
+      */
+    bool parseGeoJsonSubLevel(const QJsonObject&, QVector<GeoDataGeometry*>&);
 };
 
 }
-- 
2.20.1



More information about the Marble-devel mailing list