Changeset View
Changeset View
Standalone View
Standalone View
src/plugins/runner/json/JsonParser.cpp
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
2 | This file is part of the Marble Virtual Globe. | 2 | This file is part of the Marble Virtual Globe. | ||
3 | 3 | | |||
4 | The JsonParser class reads in a GeoJSON document that conforms to | ||||
5 | RFC7946 (including relevant errata). Attributes are stored as OSM | ||||
6 | tags. | ||||
7 | | ||||
4 | This program is free software licensed under the GNU LGPL. You can | 8 | This program is free software licensed under the GNU LGPL. You can | ||
5 | find a copy of this license in LICENSE.txt in the top directory of | 9 | find a copy of this license in LICENSE.txt in the top directory of | ||
6 | the source code. | 10 | the source code. | ||
7 | 11 | | |||
8 | Copyright 2013 Ander Pijoan <ander.pijoan@deusto.es> | 12 | Copyright 2013 Ander Pijoan <ander.pijoan@deusto.es> | ||
13 | Copyright 2019 John Zaitseff <J.Zaitseff@zap.org.au> | ||||
9 | */ | 14 | */ | ||
10 | 15 | | |||
11 | #include "JsonParser.h" | 16 | #include "JsonParser.h" | ||
12 | #include "GeoDataDocument.h" | 17 | #include "GeoDataDocument.h" | ||
13 | #include "GeoDataPlacemark.h" | 18 | #include "GeoDataPlacemark.h" | ||
14 | #include "GeoDataPolygon.h" | 19 | #include "GeoDataPolygon.h" | ||
15 | #include "GeoDataLinearRing.h" | 20 | #include "GeoDataLinearRing.h" | ||
16 | #include "GeoDataPoint.h" | 21 | #include "GeoDataPoint.h" | ||
22 | #include "GeoDataMultiGeometry.h" | ||||
17 | #include "MarbleDebug.h" | 23 | #include "MarbleDebug.h" | ||
18 | #include "StyleBuilder.h" | 24 | #include "StyleBuilder.h" | ||
19 | #include "osm/OsmPlacemarkData.h" | 25 | #include "osm/OsmPlacemarkData.h" | ||
20 | 26 | | |||
21 | #include <QIODevice> | 27 | #include <QIODevice> | ||
22 | #include <QJsonDocument> | 28 | #include <QJsonDocument> | ||
23 | #include <QJsonArray> | 29 | #include <QJsonArray> | ||
24 | #include <QJsonObject> | 30 | #include <QJsonObject> | ||
Show All 14 Lines | |||||
39 | { | 45 | { | ||
40 | GeoDataDocument* document = m_document; | 46 | GeoDataDocument* document = m_document; | ||
41 | m_document = nullptr; | 47 | m_document = nullptr; | ||
42 | return document; | 48 | return document; | ||
43 | } | 49 | } | ||
44 | 50 | | |||
45 | bool JsonParser::read( QIODevice* device ) | 51 | bool JsonParser::read( QIODevice* device ) | ||
46 | { | 52 | { | ||
47 | // Assert previous document got released. | 53 | // Release the previous document if required | ||
48 | delete m_document; | 54 | delete m_document; | ||
49 | m_document = new GeoDataDocument; | 55 | m_document = new GeoDataDocument; | ||
50 | Q_ASSERT( m_document ); | 56 | Q_ASSERT( m_document ); | ||
51 | 57 | | |||
52 | // Read file data | 58 | // Read JSON file data | ||
53 | QJsonParseError error; | 59 | QJsonParseError error; | ||
54 | const QJsonDocument jsonDoc = QJsonDocument::fromJson(device->readAll(), &error); | 60 | const QJsonDocument jsonDoc = QJsonDocument::fromJson(device->readAll(), &error); | ||
55 | 61 | | |||
56 | if (jsonDoc.isNull()) { | 62 | if (jsonDoc.isNull()) { | ||
57 | qDebug() << "Error parsing GeoJSON : " << error.errorString(); | 63 | qDebug() << "Error parsing GeoJSON:" << error.errorString(); | ||
58 | return false; | 64 | return false; | ||
65 | } else if (! jsonDoc.isObject()) { | ||||
66 | qDebug() << "Invalid file, does not contain a GeoJSON object"; | ||||
67 | return false; | ||||
59 | } | 68 | } | ||
60 | 69 | | |||
61 | // Start parsing | 70 | // Valid GeoJSON documents may not always contain a FeatureCollection object with subsidiary | ||
62 | const QJsonValue featuresValue = jsonDoc.object().value(QStringLiteral("features")); | 71 | // Feature objects, or even a single Feature object: they might contain just a single geometry | ||
72 | // object. Handle such cases by creating a wrapper Feature object if required. | ||||
63 | 73 | | |||
64 | // In GeoJSON format, geometries are stored in features, so we iterate on features | 74 | const QString jsonObjectType = jsonDoc.object().value(QStringLiteral("type")).toString(); | ||
65 | if (featuresValue.isArray()) { | | |||
66 | const QJsonArray featureArray = featuresValue.toArray(); | | |||
67 | 75 | | |||
68 | // Parse each feature | 76 | if (jsonObjectType == QStringLiteral("FeatureCollection") | ||
69 | for (int featureIndex = 0; featureIndex < featureArray.size(); ++featureIndex) { | 77 | || jsonObjectType == QStringLiteral("Feature")) { | ||
70 | const QJsonObject featureObject = featureArray[featureIndex].toObject(); | | |||
71 | 78 | | |||
72 | // Check if the feature contains a geometry | 79 | // A normal GeoJSON document: parse it recursively | ||
73 | const QJsonValue geometryValue = featureObject.value(QStringLiteral("geometry")); | 80 | return parseGeoJsonTopLevel(jsonDoc.object()); | ||
74 | if (geometryValue.isObject()) { | | |||
75 | const QJsonObject geometryObject = geometryValue.toObject(); | | |||
76 | | ||||
77 | // Variables for creating the geometry | | |||
78 | QList<GeoDataGeometry*> geometryList; | | |||
79 | QList<GeoDataPlacemark*> placemarkList; | | |||
80 | | ||||
81 | // Create the different geometry types | | |||
82 | const QString geometryType = geometryObject.value(QStringLiteral("type")).toString().toUpper(); | | |||
83 | | ||||
84 | if (geometryType == QLatin1String("POLYGON")) { | | |||
85 | // Check first that there are coordinates | | |||
86 | const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates")); | | |||
87 | if (coordinatesValue.isArray()) { | | |||
88 | const QJsonArray coordinateArray = coordinatesValue.toArray(); | | |||
89 | 81 | | |||
90 | GeoDataPolygon * geom = new GeoDataPolygon( RespectLatitudeCircle | Tessellate ); | 82 | } else { | ||
83 | // Create a wrapper Feature object and parse that | ||||
91 | 84 | | |||
92 | // Coordinates first array will be the outer boundary, if there are more | 85 | QJsonObject jsonWrapper; | ||
93 | // positions those will be inner holes | 86 | QJsonObject jsonWrapperProperties; | ||
94 | for (int ringIndex = 0 ; ringIndex < coordinateArray.size(); ++ringIndex) { | | |||
95 | const QJsonArray ringArray = coordinateArray[ringIndex].toArray(); | | |||
96 | 87 | | |||
97 | GeoDataLinearRing linearRing; | 88 | jsonWrapper["type"] = QStringLiteral("Feature"); | ||
89 | jsonWrapper["geometry"] = jsonDoc.object(); | ||||
90 | jsonWrapper["properties"] = jsonWrapperProperties; | ||||
98 | 91 | | |||
99 | for (int coordinatePairIndex = 0; coordinatePairIndex < ringArray.size(); ++coordinatePairIndex) { | 92 | return parseGeoJsonTopLevel(jsonWrapper); | ||
100 | const QJsonArray coordinatePairArray = ringArray[coordinatePairIndex].toArray(); | 93 | } | ||
94 | } | ||||
101 | 95 | | |||
102 | const qreal longitude = coordinatePairArray.at(0).toDouble(); | 96 | bool JsonParser::parseGeoJsonTopLevel( const QJsonObject& jsonObject ) | ||
103 | const qreal latitude = coordinatePairArray.at(1).toDouble(); | 97 | { | ||
98 | // Every GeoJSON object must have a case-sensitive "type" member (see RFC7946 section 3) | ||||
99 | const QString jsonObjectType = jsonObject.value(QStringLiteral("type")).toString(); | ||||
104 | 100 | | |||
105 | linearRing.append( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) ); | 101 | if (jsonObjectType == QStringLiteral("FeatureCollection")) { | ||
106 | } | 102 | // Handle the FeatureCollection object, which may contain multiple Feature objects in it | ||
107 | 103 | | |||
108 | // Outer ring | 104 | const QJsonArray featureArray = jsonObject.value(QStringLiteral("features")).toArray(); | ||
109 | if (ringIndex == 0) { | 105 | for (int featureIndex = 0; featureIndex < featureArray.size(); ++featureIndex) { | ||
110 | geom->setOuterBoundary( linearRing ); | 106 | if (! parseGeoJsonTopLevel( featureArray[featureIndex].toObject() )) { | ||
111 | } | 107 | return false; | ||
112 | // Inner holes | | |||
113 | else { | | |||
114 | geom->appendInnerBoundary( linearRing ); | | |||
115 | } | 108 | } | ||
116 | } | 109 | } | ||
117 | geometryList.append( geom ); | 110 | return true; | ||
111 | | ||||
112 | } else if (jsonObjectType == QStringLiteral("Feature")) { | ||||
113 | // Handle the Feature object, which contains a single geometry object and possibly | ||||
114 | // associated properties. Note that only Feature objects can have recognised properties. | ||||
115 | | ||||
116 | QVector<GeoDataGeometry*> geometryList; // Populated by parseGeoJsonSubLevel() | ||||
117 | | ||||
118 | if (! parseGeoJsonSubLevel( jsonObject.value(QStringLiteral("geometry")).toObject(), | ||||
119 | geometryList )) { | ||||
120 | return false; | ||||
118 | } | 121 | } | ||
119 | 122 | | |||
120 | } else if (geometryType == QLatin1String("MULTIPOLYGON")) { | 123 | // Create the placemark for this feature object with appropriate geometry | ||
121 | // Check first that there are coordinates | | |||
122 | const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates")); | | |||
123 | if (coordinatesValue.isArray()) { | | |||
124 | const QJsonArray coordinateArray = coordinatesValue.toArray(); | | |||
125 | 124 | | |||
126 | for (int polygonIndex = 0; polygonIndex < coordinateArray.size(); ++polygonIndex) { | 125 | GeoDataPlacemark* placemark = new GeoDataPlacemark(); | ||
127 | const QJsonArray polygonArray = coordinateArray[polygonIndex].toArray(); | | |||
128 | 126 | | |||
129 | GeoDataPolygon * geom = new GeoDataPolygon( RespectLatitudeCircle | Tessellate ); | 127 | if (geometryList.length() < 1) { | ||
128 | // No geometries available to add to the placemark | ||||
129 | ; | ||||
130 | | ||||
131 | } else if (geometryList.length() == 1) { | ||||
132 | // Single geometry | ||||
133 | placemark->setGeometry(geometryList[0]); | ||||
134 | | ||||
135 | } else { | ||||
136 | // Multiple geometries require a GeoDataMultiGeometry class | ||||
137 | | ||||
138 | GeoDataMultiGeometry* geom = new GeoDataMultiGeometry(); | ||||
139 | for (int i = 0; i < geometryList.length(); ++i) { | ||||
140 | geom->append(geometryList[i]); | ||||
141 | } | ||||
142 | placemark->setGeometry(geom); | ||||
143 | } | ||||
130 | 144 | | |||
131 | // Coordinates first array will be the outer boundary, if there are more | 145 | // Parse any associated properties | ||
132 | // positions those will be inner holes | | |||
133 | for (int ringIndex = 0 ; ringIndex < polygonArray.size(); ++ringIndex) { | | |||
134 | const QJsonArray ringArray = polygonArray[ringIndex].toArray(); | | |||
135 | 146 | | |||
136 | GeoDataLinearRing linearRing; | 147 | const QJsonObject propertiesObject = jsonObject.value(QStringLiteral("properties")).toObject(); | ||
148 | QJsonObject::ConstIterator iter = propertiesObject.begin(); | ||||
149 | const QJsonObject::ConstIterator end = propertiesObject.end(); | ||||
137 | 150 | | |||
138 | for (int coordinatePairIndex = 0; coordinatePairIndex < ringArray.size(); ++coordinatePairIndex) { | 151 | OsmPlacemarkData osmData; | ||
139 | const QJsonArray coordinatePairArray = ringArray[coordinatePairIndex].toArray(); | | |||
140 | 152 | | |||
141 | const qreal longitude = coordinatePairArray.at(0).toDouble(); | 153 | for ( ; iter != end; ++iter) { | ||
142 | const qreal latitude = coordinatePairArray.at(1).toDouble(); | 154 | // Pass the value through QVariant to also get booleans and numbers | ||
155 | const QString propertyValue = iter.value().toVariant().toString(); | ||||
156 | const QString propertyKey = iter.key(); | ||||
143 | 157 | | |||
144 | linearRing.append( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) ); | 158 | if (iter.value().isObject() || iter.value().isArray()) { | ||
159 | qDebug() << "Skipping unsupported JSON property containing an object or array:" << propertyKey; | ||||
160 | continue; | ||||
145 | } | 161 | } | ||
146 | 162 | | |||
147 | // Outer ring | 163 | osmData.addTag(propertyKey, propertyValue); | ||
148 | if (ringIndex == 0) { | 164 | | ||
149 | geom->setOuterBoundary( linearRing ); | 165 | if (propertyKey == QStringLiteral("name")) { | ||
166 | placemark->setName(propertyValue); | ||||
150 | } | 167 | } | ||
151 | // Inner holes | | |||
152 | else { | | |||
153 | geom->appendInnerBoundary( linearRing ); | | |||
154 | } | 168 | } | ||
169 | | ||||
170 | placemark->setOsmData(osmData); | ||||
171 | placemark->setVisible(true); | ||||
172 | | ||||
173 | const GeoDataPlacemark::GeoDataVisualCategory category = | ||||
174 | StyleBuilder::determineVisualCategory(osmData); | ||||
175 | if (category != GeoDataPlacemark::None) { | ||||
176 | placemark->setVisualCategory(category); | ||||
155 | } | 177 | } | ||
156 | geometryList.append( geom ); | 178 | | ||
179 | m_document->append(placemark); | ||||
180 | return true; | ||||
181 | | ||||
182 | } else { | ||||
183 | qDebug() << "Missing FeatureCollection or Feature object in GeoJSON file"; | ||||
184 | return false; | ||||
157 | } | 185 | } | ||
158 | } | 186 | } | ||
159 | 187 | | |||
160 | } else if (geometryType == QLatin1String("LINESTRING")) { | 188 | bool JsonParser::parseGeoJsonSubLevel( const QJsonObject& jsonObject, | ||
161 | 189 | QVector<GeoDataGeometry*>& geometryList ) | |||
162 | // Check first that there are coordinates | 190 | { | ||
163 | const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates")); | 191 | // The GeoJSON object type | ||
164 | if (coordinatesValue.isArray()) { | 192 | const QString jsonObjectType = jsonObject.value(QStringLiteral("type")).toString(); | ||
165 | const QJsonArray coordinateArray = coordinatesValue.toArray(); | | |||
166 | 193 | | |||
167 | GeoDataLineString * geom = new GeoDataLineString( RespectLatitudeCircle | Tessellate ); | 194 | if (jsonObjectType == QStringLiteral("FeatureCollection") | ||
195 | || jsonObjectType == QStringLiteral("Feature")) { | ||||
168 | 196 | | |||
169 | for (int coordinatePairIndex = 0; coordinatePairIndex < coordinateArray.size(); ++coordinatePairIndex) { | 197 | qDebug() << "Cannot have FeatureCollection or Feature objects at this level of the GeoJSON file"; | ||
170 | const QJsonArray coordinatePairArray = coordinateArray[coordinatePairIndex].toArray(); | 198 | return false; | ||
171 | 199 | | |||
172 | const qreal longitude = coordinatePairArray.at(0).toDouble(); | 200 | } else if (jsonObjectType == QStringLiteral("GeometryCollection")) { | ||
173 | const qreal latitude = coordinatePairArray.at(1).toDouble(); | 201 | // Handle the GeometryCollection object, which may contain multiple geometry objects | ||
174 | 202 | | |||
175 | geom->append( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) ); | 203 | const QJsonArray geometryArray = jsonObject.value(QStringLiteral("geometries")).toArray(); | ||
204 | for (int geometryIndex = 0; geometryIndex < geometryArray.size(); ++geometryIndex) { | ||||
205 | if (! parseGeoJsonSubLevel( geometryArray[geometryIndex].toObject(), geometryList )) { | ||||
206 | return false; | ||||
176 | } | 207 | } | ||
177 | geometryList.append( geom ); | | |||
178 | } | 208 | } | ||
179 | 209 | | |||
180 | } else if (geometryType == QLatin1String("MULTILINESTRING")) { | 210 | return true; | ||
211 | } | ||||
212 | | ||||
213 | // Handle remaining GeoJSON objects, which each have a "coordinates" member (an array) | ||||
181 | 214 | | |||
182 | // Check first that there are coordinates | 215 | const QJsonArray coordinateArray = jsonObject.value(QStringLiteral("coordinates")).toArray(); | ||
183 | const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates")); | | |||
184 | if (coordinatesValue.isArray()) { | | |||
185 | const QJsonArray coordinateArray = coordinatesValue.toArray(); | | |||
186 | 216 | | |||
187 | for (int lineStringIndex = 0; lineStringIndex < coordinateArray.size(); ++lineStringIndex) { | 217 | if (jsonObjectType == QStringLiteral("Point")) { | ||
188 | const QJsonArray lineStringArray = coordinateArray[lineStringIndex].toArray(); | 218 | // A Point object has a single GeoJSON position: an array of at least two values | ||
189 | 219 | | |||
190 | GeoDataLineString * geom = new GeoDataLineString( RespectLatitudeCircle | Tessellate ); | 220 | GeoDataPoint* geom = new GeoDataPoint(); | ||
221 | const qreal lon = coordinateArray.at(0).toDouble(); | ||||
222 | const qreal lat = coordinateArray.at(1).toDouble(); | ||||
223 | const qreal alt = coordinateArray.at(2).toDouble(); // If missing, uses 0 as the default | ||||
191 | 224 | | |||
192 | for (int coordinatePairIndex = 0; coordinatePairIndex < lineStringArray.size(); ++coordinatePairIndex) { | 225 | geom->setCoordinates( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree )); | ||
193 | const QJsonArray coordinatePairArray = lineStringArray[coordinatePairIndex].toArray(); | 226 | geometryList.append(geom); | ||
194 | 227 | | |||
195 | const qreal longitude = coordinatePairArray.at(0).toDouble(); | 228 | return true; | ||
196 | const qreal latitude = coordinatePairArray.at(1).toDouble(); | | |||
197 | 229 | | |||
198 | geom->append( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) ); | 230 | } else if (jsonObjectType == QStringLiteral("MultiPoint")) { | ||
199 | } | 231 | // A MultiPoint object has an array of GeoJSON positions (ie, a two-level array) | ||
232 | | ||||
233 | for (int positionIndex = 0; positionIndex < coordinateArray.size(); ++positionIndex) { | ||||
234 | const QJsonArray positionArray = coordinateArray[positionIndex].toArray(); | ||||
235 | | ||||
236 | GeoDataPoint* geom = new GeoDataPoint(); | ||||
237 | const qreal lon = positionArray.at(0).toDouble(); | ||||
238 | const qreal lat = positionArray.at(1).toDouble(); | ||||
239 | const qreal alt = positionArray.at(2).toDouble(); | ||||
240 | | ||||
241 | geom->setCoordinates( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree )); | ||||
200 | geometryList.append( geom ); | 242 | geometryList.append(geom); | ||
201 | } | 243 | } | ||
202 | } | | |||
203 | 244 | | |||
204 | } else if (geometryType == QLatin1String("POINT")) { | 245 | return true; | ||
205 | 246 | | |||
206 | // Check first that there are coordinates | 247 | } else if (jsonObjectType == QStringLiteral("LineString")) { | ||
207 | const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates")); | 248 | // A LineString object has an array of GeoJSON positions (ie, a two-level array) | ||
208 | if (coordinatesValue.isArray()) { | | |||
209 | const QJsonArray coordinatePairArray = coordinatesValue.toArray(); | | |||
210 | 249 | | |||
211 | GeoDataPoint * geom = new GeoDataPoint(); | 250 | GeoDataLineString* geom = new GeoDataLineString( RespectLatitudeCircle | Tessellate ); | ||
212 | 251 | | |||
213 | const qreal longitude = coordinatePairArray.at(0).toDouble(); | 252 | for (int positionIndex = 0; positionIndex < coordinateArray.size(); ++positionIndex) { | ||
214 | const qreal latitude = coordinatePairArray.at(1).toDouble(); | 253 | const QJsonArray positionArray = coordinateArray[positionIndex].toArray(); | ||
215 | 254 | | |||
216 | geom->setCoordinates( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) ); | 255 | const qreal lon = positionArray.at(0).toDouble(); | ||
256 | const qreal lat = positionArray.at(1).toDouble(); | ||||
257 | const qreal alt = positionArray.at(2).toDouble(); | ||||
217 | 258 | | |||
218 | geometryList.append( geom ); | 259 | geom->append( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree )); | ||
219 | } | 260 | } | ||
220 | } else if (geometryType == QLatin1String("MULTIPOINT")) { | 261 | geometryList.append(geom); | ||
221 | 262 | | |||
222 | // Check first that there are coordinates | 263 | return true; | ||
223 | const QJsonValue coordinatesValue = geometryObject.value(QStringLiteral("coordinates")); | | |||
224 | if (coordinatesValue.isArray()) { | | |||
225 | const QJsonArray coordinateArray = coordinatesValue.toArray(); | | |||
226 | 264 | | |||
227 | for (int pointIndex = 0; pointIndex < coordinateArray.size(); ++pointIndex) { | 265 | } else if (jsonObjectType == QStringLiteral("MultiLineString")) { | ||
228 | const QJsonArray coordinatePairArray = coordinateArray[pointIndex].toArray(); | 266 | // A MultiLineString object has an array of arrays of GeoJSON positions (three-level) | ||
229 | 267 | | |||
230 | GeoDataPoint * geom = new GeoDataPoint(); | 268 | for (int lineStringIndex = 0; lineStringIndex < coordinateArray.size(); ++lineStringIndex) { | ||
269 | const QJsonArray lineStringArray = coordinateArray[lineStringIndex].toArray(); | ||||
231 | 270 | | |||
232 | const qreal longitude = coordinatePairArray.at(0).toDouble(); | 271 | GeoDataLineString* geom = new GeoDataLineString( RespectLatitudeCircle | Tessellate ); | ||
233 | const qreal latitude = coordinatePairArray.at(1).toDouble(); | | |||
234 | 272 | | |||
235 | geom->setCoordinates( GeoDataCoordinates( longitude , latitude , 0 , GeoDataCoordinates::Degree ) ); | 273 | for (int positionIndex = 0; positionIndex < lineStringArray.size(); ++positionIndex) { | ||
274 | const QJsonArray positionArray = lineStringArray[positionIndex].toArray(); | ||||
236 | 275 | | |||
237 | geometryList.append( geom ); | 276 | const qreal lon = positionArray.at(0).toDouble(); | ||
238 | } | 277 | const qreal lat = positionArray.at(1).toDouble(); | ||
278 | const qreal alt = positionArray.at(2).toDouble(); | ||||
279 | | ||||
280 | geom->append( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree )); | ||||
239 | } | 281 | } | ||
282 | geometryList.append(geom); | ||||
240 | } | 283 | } | ||
241 | 284 | | |||
285 | return true; | ||||
242 | 286 | | |||
243 | // Parse the features properties | 287 | } else if (jsonObjectType == QStringLiteral("Polygon")) { | ||
244 | const QJsonValue propertiesValue = featureObject.value(QStringLiteral("properties")); | 288 | // A Polygon object has an array of arrays of GeoJSON positions: the first array within the | ||
245 | if (!geometryList.isEmpty() && propertiesValue.isObject()) { | 289 | // top-level Polygon coordinates array is the outer boundary, following arrays are inner | ||
246 | const QJsonObject propertiesObject = propertiesValue.toObject(); | 290 | // holes (if any) | ||
247 | | ||||
248 | // First create a placemark for each geometry, there could be multi geometries | | |||
249 | // that are translated into more than one geometry/placemark | | |||
250 | for ( int numberGeometries = 0 ; numberGeometries < geometryList.length() ; numberGeometries++ ) { | | |||
251 | GeoDataPlacemark * placemark = new GeoDataPlacemark(); | | |||
252 | placemarkList.append( placemark ); | | |||
253 | } | | |||
254 | 291 | | |||
255 | OsmPlacemarkData osmData; | 292 | GeoDataPolygon* geom = new GeoDataPolygon( RespectLatitudeCircle | Tessellate ); | ||
256 | 293 | | |||
257 | QJsonObject::ConstIterator it = propertiesObject.begin(); | 294 | for (int ringIndex = 0; ringIndex < coordinateArray.size(); ++ringIndex) { | ||
258 | const QJsonObject::ConstIterator end = propertiesObject.end(); | 295 | const QJsonArray ringArray = coordinateArray[ringIndex].toArray(); | ||
259 | for ( ; it != end; ++it) { | | |||
260 | if (it.value().isObject() || it.value().isArray()) { | | |||
261 | qDebug() << "Skipping property, values of type arrays and objects not supported:" << it.key(); | | |||
262 | continue; | | |||
263 | } | | |||
264 | 296 | | |||
265 | // pass value through QVariant to also get bool & numbers | 297 | GeoDataLinearRing linearRing; | ||
266 | osmData.addTag(it.key(), it.value().toVariant().toString()); | | |||
267 | } | | |||
268 | 298 | | |||
269 | // If the property read, is the features name | 299 | for (int positionIndex = 0; positionIndex < ringArray.size(); ++positionIndex) { | ||
270 | const auto tagIter = osmData.findTag(QStringLiteral("name")); | 300 | const QJsonArray positionArray = ringArray[positionIndex].toArray(); | ||
271 | if (tagIter != osmData.tagsEnd()) { | | |||
272 | const QString& name = tagIter.value(); | | |||
273 | for (int pl = 0 ; pl < placemarkList.length(); ++pl) { | | |||
274 | placemarkList.at(pl)->setName(name); | | |||
275 | } | | |||
276 | } | | |||
277 | 301 | | |||
278 | const GeoDataPlacemark::GeoDataVisualCategory category = StyleBuilder::determineVisualCategory(osmData); | 302 | const qreal lon = positionArray.at(0).toDouble(); | ||
279 | if (category != GeoDataPlacemark::None) { | 303 | const qreal lat = positionArray.at(1).toDouble(); | ||
280 | // Add the visual category to all the placemarks | 304 | const qreal alt = positionArray.at(2).toDouble(); | ||
281 | for (int pl = 0 ; pl < placemarkList.length(); ++pl) { | 305 | | ||
282 | placemarkList.at(pl)->setVisualCategory(category); | 306 | linearRing.append( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree )); | ||
283 | placemarkList.at(pl)->setOsmData(osmData); | | |||
284 | } | 307 | } | ||
308 | | ||||
309 | if (ringIndex == 0) { | ||||
310 | // Outer boundary of the polygon | ||||
311 | geom->setOuterBoundary(linearRing); | ||||
312 | } else { | ||||
313 | geom->appendInnerBoundary(linearRing); | ||||
285 | } | 314 | } | ||
286 | } | 315 | } | ||
316 | geometryList.append(geom); | ||||
287 | 317 | | |||
288 | // Add the geometry to the document | 318 | return true; | ||
289 | if ( geometryList.length() == placemarkList.length() ) { | | |||
290 | 319 | | |||
291 | while( placemarkList.length() > 0 ) { | 320 | } else if (jsonObjectType == QStringLiteral("MultiPolygon")) { | ||
321 | // A MultiPolygon object has an array of Polygon arrays (ie, a four-level array) | ||||
292 | 322 | | |||
293 | GeoDataPlacemark * placemark = placemarkList.last(); | 323 | for (int polygonIndex = 0; polygonIndex < coordinateArray.size(); ++polygonIndex) { | ||
294 | placemarkList.pop_back(); | 324 | const QJsonArray polygonArray = coordinateArray[polygonIndex].toArray(); | ||
295 | 325 | | |||
296 | GeoDataGeometry * geom = geometryList.last(); | 326 | GeoDataPolygon* geom = new GeoDataPolygon( RespectLatitudeCircle | Tessellate ); | ||
297 | geometryList.pop_back(); | | |||
298 | 327 | | |||
299 | placemark->setGeometry( geom ); | 328 | for (int ringIndex = 0; ringIndex < polygonArray.size(); ++ringIndex) { | ||
300 | placemark->setVisible( true ); | 329 | const QJsonArray ringArray = polygonArray[ringIndex].toArray(); | ||
301 | m_document->append( placemark ); | 330 | | ||
302 | } | 331 | GeoDataLinearRing linearRing; | ||
332 | | ||||
333 | for (int positionIndex = 0; positionIndex < ringArray.size(); ++positionIndex) { | ||||
334 | const QJsonArray positionArray = ringArray[positionIndex].toArray(); | ||||
335 | | ||||
336 | const qreal lon = positionArray.at(0).toDouble(); | ||||
337 | const qreal lat = positionArray.at(1).toDouble(); | ||||
338 | const qreal alt = positionArray.at(2).toDouble(); | ||||
339 | | ||||
340 | linearRing.append( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree )); | ||||
303 | } | 341 | } | ||
304 | 342 | | |||
305 | // If geometries or placemarks missing inside the lists, delete them | 343 | if (ringIndex == 0) { | ||
306 | qDeleteAll( geometryList.begin(), geometryList.end() ); | 344 | // Outer boundary of the polygon | ||
307 | geometryList.clear(); | 345 | geom->setOuterBoundary(linearRing); | ||
308 | qDeleteAll( placemarkList.begin(), placemarkList.end() ); | 346 | } else { | ||
309 | placemarkList.clear(); | 347 | geom->appendInnerBoundary(linearRing); | ||
310 | } | 348 | } | ||
311 | } | 349 | } | ||
350 | geometryList.append(geom); | ||||
312 | } | 351 | } | ||
352 | | ||||
353 | return true; | ||||
354 | | ||||
355 | } else if (jsonObjectType == QStringLiteral("")) { | ||||
356 | // Unlocated Feature objects have a null value for "geometry" (RFC7946 section 3.2) | ||||
313 | return true; | 357 | return true; | ||
314 | } | | |||
315 | 358 | | |||
359 | } else { | ||||
360 | qDebug() << "Unknown GeoJSON object type" << jsonObjectType; | ||||
361 | return false; | ||||
362 | } | ||||
316 | } | 363 | } | ||
317 | 364 | | |||
365 | } |