QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GeoJsonHelper.cc
Go to the documentation of this file.
1#include "GeoJsonHelper.h"
2#include "JsonParsing.h"
4
5#include <QtCore/QFile>
6#include <QtCore/QJsonArray>
7#include <QtCore/QJsonValue>
8#include <QtCore/QCoreApplication>
9#include <QtCore/QVariantMap>
10#include <QtLocation/private/qgeojson_p.h>
11#include <QtPositioning/QGeoCoordinate>
12#include <QtPositioning/QGeoPath>
13#include <QtPositioning/QGeoPolygon>
14
15QGC_LOGGING_CATEGORY(GeoJsonHelperLog, "Utilities.GeoJsonHelper")
16
18{
19 QJsonDocument _loadFile(const QString &filePath, QString &errorString);
20 QVariantList _extractShapeValues(const QVariantList &values);
21 void _extractShapeValuesRecursive(const QVariant &value, QVariantList &shapes, int depth = 0);
22
23 constexpr int _maxRecursionDepth = 32;
24 constexpr const char *_errorPrefix = QT_TRANSLATE_NOOP("GeoJsonHelper", "GeoJson file load failed. %1");
25}
26
27void GeoJsonHelper::_extractShapeValuesRecursive(const QVariant &value, QVariantList &shapes, int depth)
28{
29 if (depth >= _maxRecursionDepth) {
30 return;
31 }
32
33 if (value.canConvert<QGeoPolygon>() || value.canConvert<QGeoPath>() || value.canConvert<QGeoShape>()) {
34 (void) shapes.append(value);
35 }
36
37 if (value.typeId() == QMetaType::QVariantList) {
38 const QVariantList children = value.toList();
39 for (const QVariant &child : children) {
40 _extractShapeValuesRecursive(child, shapes, depth + 1);
41 }
42 } else if (value.typeId() == QMetaType::QVariantMap) {
43 const QVariantMap map = value.toMap();
44 for (auto it = map.cbegin(); it != map.cend(); ++it) {
45 _extractShapeValuesRecursive(it.value(), shapes, depth + 1);
46 }
47 }
48}
49
50QVariantList GeoJsonHelper::_extractShapeValues(const QVariantList &values)
51{
52 QVariantList shapes;
53 for (const QVariant &value : values) {
54 _extractShapeValuesRecursive(value, shapes);
55 }
56 return shapes;
57}
58
59QJsonDocument GeoJsonHelper::_loadFile(const QString &filePath, QString &errorString)
60{
61 errorString.clear();
62
63 QFile file(filePath);
64 if (!file.exists()) {
65 errorString = QCoreApplication::translate("GeoJsonHelper", _errorPrefix).arg(
66 QCoreApplication::translate("GeoJson", "File not found: %1").arg(filePath));
67 return QJsonDocument();
68 }
69
70 if (!file.open(QIODevice::ReadOnly)) {
71 errorString = QCoreApplication::translate("GeoJsonHelper", _errorPrefix).arg(
72 QCoreApplication::translate("GeoJson", "Unable to open file: %1 error: %2")
73 .arg(filePath, file.errorString()));
74 return QJsonDocument();
75 }
76
77 QJsonDocument jsonDoc;
78 const QByteArray bytes = file.readAll();
79 if (!JsonParsing::isJsonFile(bytes, jsonDoc, errorString)) {
80 errorString = QCoreApplication::translate("GeoJsonHelper", _errorPrefix).arg(errorString);
81 }
82
83 return jsonDoc;
84}
85
87{
88 using ShapeType = ShapeFileHelper::ShapeType;
89
90 const QJsonDocument jsonDoc = GeoJsonHelper::_loadFile(filePath, errorString);
91 if (!errorString.isEmpty()) {
92 return ShapeType::Error;
93 }
94
95 const QVariantList imported = QGeoJson::importGeoJson(jsonDoc);
96 const QVariantList shapes = _extractShapeValues(imported);
97 if (shapes.isEmpty()) {
98 errorString = QCoreApplication::translate("GeoJsonHelper", _errorPrefix).arg(
99 QCoreApplication::translate("GeoJson", "No shapes found in GeoJson file."));
100 return ShapeType::Error;
101 }
102
103 for (const QVariant &shapeVar : shapes) {
104 if (shapeVar.canConvert<QGeoPolygon>()) {
105 return ShapeType::Polygon;
106 }
107 if (shapeVar.canConvert<QGeoPath>()) {
108 return ShapeType::Polyline;
109 }
110 if (shapeVar.canConvert<QGeoShape>()) {
111 const QGeoShape shape = shapeVar.value<QGeoShape>();
112 if (shape.type() == QGeoShape::PolygonType) {
113 return ShapeType::Polygon;
114 }
115 if (shape.type() == QGeoShape::PathType) {
116 return ShapeType::Polyline;
117 }
118 }
119 }
120
121 errorString = QCoreApplication::translate("GeoJsonHelper", _errorPrefix).arg(
122 QCoreApplication::translate("GeoJson", "No supported type found in GeoJson file."));
123 return ShapeType::Error;
124}
125
126bool GeoJsonHelper::loadPolygonFromFile(const QString &filePath, QList<QGeoCoordinate> &vertices, QString &errorString)
127{
128 errorString.clear();
129 vertices.clear();
130
131 const QJsonDocument jsonDoc = GeoJsonHelper::_loadFile(filePath, errorString);
132 if (!errorString.isEmpty()) {
133 return false;
134 }
135
136 const QVariantList imported = QGeoJson::importGeoJson(jsonDoc);
137 const QVariantList shapes = _extractShapeValues(imported);
138 if (shapes.isEmpty()) {
139 errorString = QCoreApplication::translate("GeoJsonHelper", _errorPrefix).arg(
140 QCoreApplication::translate("GeoJson", "No polygon data found in GeoJson file."));
141 return false;
142 }
143
144 for (const QVariant &shapeVar : shapes) {
145 if (shapeVar.canConvert<QGeoPolygon>()) {
146 const QGeoPolygon poly = shapeVar.value<QGeoPolygon>();
147 vertices = poly.perimeter();
148 return true;
149 }
150 if (shapeVar.canConvert<QGeoShape>()) {
151 const QGeoShape shape = shapeVar.value<QGeoShape>();
152 if (shape.type() == QGeoShape::PolygonType) {
153 const QGeoPolygon poly(shape);
154 vertices = poly.perimeter();
155 if (!vertices.isEmpty()) {
156 return true;
157 }
158 }
159 }
160 }
161
162 errorString = QCoreApplication::translate("GeoJsonHelper", _errorPrefix).arg(
163 QCoreApplication::translate("GeoJson", "No polygon found in GeoJson file."));
164 return false;
165}
166
167bool GeoJsonHelper::loadPolylineFromFile(const QString &filePath, QList<QGeoCoordinate> &coords, QString &errorString)
168{
169 errorString.clear();
170 coords.clear();
171
172 const QJsonDocument jsonDoc = GeoJsonHelper::_loadFile(filePath, errorString);
173 if (!errorString.isEmpty()) {
174 return false;
175 }
176
177 const QVariantList imported = QGeoJson::importGeoJson(jsonDoc);
178 const QVariantList shapes = _extractShapeValues(imported);
179 if (shapes.isEmpty()) {
180 errorString = QCoreApplication::translate("GeoJsonHelper", _errorPrefix).arg(
181 QCoreApplication::translate("GeoJson", "No polyline data found in GeoJson file."));
182 return false;
183 }
184
185 for (const QVariant &shapeVar : shapes) {
186 if (shapeVar.canConvert<QGeoPath>()) {
187 const QGeoPath path = shapeVar.value<QGeoPath>();
188 coords = path.path();
189 return true;
190 }
191 if (shapeVar.canConvert<QGeoShape>()) {
192 const QGeoShape shape = shapeVar.value<QGeoShape>();
193 if (shape.type() == QGeoShape::PathType) {
194 const QGeoPath path(shape);
195 coords = path.path();
196 if (!coords.isEmpty()) {
197 return true;
198 }
199 }
200 }
201 }
202
203 errorString = QCoreApplication::translate("GeoJsonHelper", _errorPrefix).arg(
204 QCoreApplication::translate("GeoJson", "No polyline found in GeoJson file."));
205 return false;
206}
207
208bool GeoJsonHelper::loadGeoJsonCoordinate(const QJsonValue &jsonValue, bool altitudeRequired, QGeoCoordinate &coordinate, QString &errorString)
209{
210 if (!jsonValue.isArray()) {
211 errorString = QCoreApplication::translate("GeoJsonHelper", "value for coordinate is not array");
212 return false;
213 }
214
215 const QJsonArray coordinateArray = jsonValue.toArray();
216 const int requiredCount = altitudeRequired ? 3 : 2;
217 if (coordinateArray.count() != requiredCount) {
218 errorString = QCoreApplication::translate("GeoJsonHelper", "Coordinate array must contain %1 values").arg(requiredCount);
219 return false;
220 }
221
222 for (const QJsonValue &coordinateValue : coordinateArray) {
223 if ((coordinateValue.type() != QJsonValue::Double) && (coordinateValue.type() != QJsonValue::Null)) {
225 QCoreApplication::translate("GeoJsonHelper", "Coordinate array may only contain double values, found: %1").arg(coordinateValue.type());
226 return false;
227 }
228 }
229
230 // GeoJSON ordering is [lon, lat, alt] (RFC 7946).
231 coordinate = QGeoCoordinate(coordinateArray[1].toDouble(), coordinateArray[0].toDouble());
232 if (altitudeRequired) {
233 coordinate.setAltitude(JsonParsing::possibleNaNJsonValue(coordinateArray[2]));
234 }
235
236 return true;
237}
238
239void GeoJsonHelper::saveGeoJsonCoordinate(const QGeoCoordinate &coordinate, bool writeAltitude, QJsonValue &jsonValue)
240{
241 QJsonArray coordinateArray;
242 coordinateArray << coordinate.longitude() << coordinate.latitude();
243 if (writeAltitude) {
244 coordinateArray << coordinate.altitude();
245 }
246 jsonValue = QJsonValue(coordinateArray);
247}
248
249bool GeoJsonHelper::loadGeoCoordinate(const QJsonValue &jsonValue, bool altitudeRequired, QGeoCoordinate &coordinate,
250 QString &errorString)
251{
252 if (!jsonValue.isArray()) {
253 errorString = QCoreApplication::translate("GeoJsonHelper", "value for coordinate is not array");
254 return false;
255 }
256
257 const QJsonArray coordinateArray = jsonValue.toArray();
258 const int requiredCount = altitudeRequired ? 3 : 2;
259 if (coordinateArray.count() != requiredCount) {
260 errorString = QCoreApplication::translate("GeoJsonHelper", "Coordinate array must contain %1 values").arg(requiredCount);
261 return false;
262 }
263
264 for (const QJsonValue &coordinateValue : coordinateArray) {
265 if ((coordinateValue.type() != QJsonValue::Double) && (coordinateValue.type() != QJsonValue::Null)) {
267 QCoreApplication::translate("GeoJsonHelper", "Coordinate array may only contain double values, found: %1").arg(coordinateValue.type());
268 return false;
269 }
270 }
271
272 coordinate = QGeoCoordinate(
273 JsonParsing::possibleNaNJsonValue(coordinateArray[0]),
274 JsonParsing::possibleNaNJsonValue(coordinateArray[1]));
275
276 if (altitudeRequired) {
277 coordinate.setAltitude(JsonParsing::possibleNaNJsonValue(coordinateArray[2]));
278 }
279
280 return true;
281}
282
283void GeoJsonHelper::saveGeoCoordinate(const QGeoCoordinate &coordinate, bool writeAltitude, QJsonValue &jsonValue)
284{
285 QJsonArray coordinateArray;
286 coordinateArray << coordinate.latitude() << coordinate.longitude();
287
288 if (writeAltitude) {
289 coordinateArray << coordinate.altitude();
290 }
291
292 jsonValue = QJsonValue(coordinateArray);
293}
294
295bool GeoJsonHelper::loadGeoCoordinateArray(const QJsonValue &jsonValue, bool altitudeRequired,
296 QVariantList &rgVarPoints, QString &errorString)
297{
298 if (!jsonValue.isArray()) {
299 errorString = QCoreApplication::translate("GeoJsonHelper", "value for coordinate array is not array");
300 return false;
301 }
302
303 const QJsonArray rgJsonPoints = jsonValue.toArray();
304
305 rgVarPoints.clear();
306 for (const QJsonValue &point : rgJsonPoints) {
307 QGeoCoordinate coordinate;
308 if (!loadGeoCoordinate(point, altitudeRequired, coordinate, errorString)) {
309 return false;
310 }
311 rgVarPoints.append(QVariant::fromValue(coordinate));
312 }
313
314 return true;
315}
316
317bool GeoJsonHelper::loadGeoCoordinateArray(const QJsonValue &jsonValue, bool altitudeRequired,
318 QList<QGeoCoordinate> &rgPoints, QString &errorString)
319{
320 QVariantList rgVarPoints;
321
322 if (!loadGeoCoordinateArray(jsonValue, altitudeRequired, rgVarPoints, errorString)) {
323 return false;
324 }
325
326 rgPoints.clear();
327 for (const QVariant &point : rgVarPoints) {
328 rgPoints.append(point.value<QGeoCoordinate>());
329 }
330
331 return true;
332}
333
334void GeoJsonHelper::saveGeoCoordinateArray(const QVariantList &rgVarPoints, bool writeAltitude, QJsonValue &jsonValue)
335{
336 QJsonArray rgJsonPoints;
337 for (const QVariant &point : rgVarPoints) {
338 QJsonValue jsonPoint;
339 saveGeoCoordinate(point.value<QGeoCoordinate>(), writeAltitude, jsonPoint);
340 rgJsonPoints.append(jsonPoint);
341 }
342
343 jsonValue = rgJsonPoints;
344}
345
346void GeoJsonHelper::saveGeoCoordinateArray(const QList<QGeoCoordinate> &rgPoints, bool writeAltitude,
347 QJsonValue &jsonValue)
348{
349 QVariantList rgVarPoints;
350 for (const QGeoCoordinate &coord : rgPoints) {
351 rgVarPoints.append(QVariant::fromValue(coord));
352 }
353
354 saveGeoCoordinateArray(rgVarPoints, writeAltitude, jsonValue);
355}
QString errorString
#define QGC_LOGGING_CATEGORY(name, categoryStr)
bool loadGeoCoordinateArray(const QJsonValue &jsonValue, bool altitudeRequired, QVariantList &rgVarPoints, QString &errorString)
Loads a list of QGeoCoordinates (QGC plan format) from a json array.
void saveGeoCoordinateArray(const QVariantList &rgVarPoints, bool writeAltitude, QJsonValue &jsonValue)
Saves a list of QGeoCoordinates (QGC plan format) to a json array.
QVariantList _extractShapeValues(const QVariantList &values)
constexpr const char * _errorPrefix
bool loadGeoCoordinate(const QJsonValue &jsonValue, bool altitudeRequired, QGeoCoordinate &coordinate, QString &errorString)
ShapeFileHelper::ShapeType determineShapeType(const QString &filePath, QString &errorString)
bool loadPolylineFromFile(const QString &filePath, QList< QGeoCoordinate > &coords, QString &errorString)
bool loadGeoJsonCoordinate(const QJsonValue &jsonValue, bool altitudeRequired, QGeoCoordinate &coordinate, QString &errorString)
returned error string if load failure
void saveGeoJsonCoordinate(const QGeoCoordinate &coordinate, bool writeAltitude, QJsonValue &jsonValue)
json value to save to
void saveGeoCoordinate(const QGeoCoordinate &coordinate, bool writeAltitude, QJsonValue &jsonValue)
Saves a QGeoCoordinate as [lat, lon, alt] array (QGC plan format).
QJsonDocument _loadFile(const QString &filePath, QString &errorString)
bool loadPolygonFromFile(const QString &filePath, QList< QGeoCoordinate > &vertices, QString &errorString)
constexpr int _maxRecursionDepth
void _extractShapeValuesRecursive(const QVariant &value, QVariantList &shapes, int depth=0)
bool isJsonFile(const QByteArray &bytes, QJsonDocument &jsonDoc, QString &errorString)
Determines whether an in-memory byte buffer contains parseable JSON content.
double possibleNaNJsonValue(const QJsonValue &value)
Returns NaN if the value is null, or the value converted to double otherwise.