diff options
author | Julian Sherollari <jdotsh@gmail.com> | 2018-08-07 11:03:48 +0200 |
---|---|---|
committer | Paolo Angelelli <paolo.angelelli@qt.io> | 2019-01-18 15:08:03 +0000 |
commit | 9485871222fb3c8f5f2d058ae8c5c0ca13b0ce2c (patch) | |
tree | de99bf587b2bc885de1a04f3d01910a6572e4ff5 /src | |
parent | 1afc3744c33b67c29361e7300641c4e7212702fb (diff) | |
download | qtlocation-9485871222fb3c8f5f2d058ae8c5c0ca13b0ce2c.tar.gz |
Add QGeoJson: a GeoJSON parser
Add a Class to convert a GeoJSON document to a QVariantList ready to be
used as Model in a MapItemView. It comes with autotests, example and
detailed documentation.
[ChangeLog][QtLocation] Added a GeoJSON parser which can be used to
annotate maps with tracks, polygonal boundaries, etc.
Fixes: QTBUG-64111
Change-Id: Ib06d3902a052f69f75ae40be5c9ab56023cad916
Reviewed-by: Paolo Angelelli <paolo.angelelli@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/location/labs/qgeojson.cpp | 1242 | ||||
-rw-r--r-- | src/location/labs/qgeojson_p.h | 81 |
2 files changed, 1323 insertions, 0 deletions
diff --git a/src/location/labs/qgeojson.cpp b/src/location/labs/qgeojson.cpp new file mode 100644 index 00000000..f7f37661 --- /dev/null +++ b/src/location/labs/qgeojson.cpp @@ -0,0 +1,1242 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2018 Julian Sherollari <jdotsh@gmail.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeojson_p.h" +#include <qjsonobject.h> +#include <qjsonvalue.h> +#include <qjsonarray.h> +#include <qgeocoordinate.h> +#include <qgeocircle.h> +#include <qgeopath.h> +#include <qgeopolygon.h> +#include <qtextstream.h> + +QT_BEGIN_NAMESPACE + +/*! \class QGeoJson + \inmodule QtLocation + \since 5.13 + + QGeoJson class can be used to convert between a GeoJSON document (see the + \l {https://en.wikipedia.org/wiki/GeoJSON} {Wikipedia page}, \l + {https://tools.ietf.org/html/rfc7946} {RFC}) and a \l + {http://doc.qt.io/qt-5/qvariant.html#QVariantList-typedef} {QVariantList} + of \l QVariantMap elements ready to be used as Model in a \l MapItemView. + WARNING! This private class is part of Qt labs, thus not stable API, it is + part of the experimental components of QtLocation. Until it is promoted to + public API, it may be subject to source and binary-breaking changes. + + \section2 Importing GeoJSON + + The method \l importGeoJson accepts a \l + {http://doc.qt.io/qt-5/qjsondocument.html} {QJsonDocument} from which it + extracts a single \l {https://tools.ietf.org/html/rfc7159} {JSON} object, + since the GeoJSON RFC expects that a valid GeoJSON Document has in its root + a single JSON object. This method doesn't perform any validation on the + input. The importer returns a QVariantList containing a single QVariantMap. + This map has always at least 2 (key, value) pairs. The first one has \c + type as key, and the corresponding value is a string identifying the + GeoJSON type. This value can be one of the GeoJSON object types: \c Point, + \c MultiPoint, \c LineString, \c MultiLineString, \c Polygon, \c + MultiPolygon, \c GeometryCollection, \c FeatureCollection. The second pair + has \c data as key, and the corresponding value can be either a QGeoShape + or a list, depending on the GeoJSON type. The next section provides details + about this node. The \c Feature type is converted into the type of the + geometry contained within, with an additional (key, value) pair, where the + key is \c properties and the value is a \l QVariantMap. Thus, a feature Map + is distinguishable from the corresponding geometry, by looking for a \c + properties member. + + \section3 Structure of the data node + + For the single type geometry objects (\c Point, \c LineString, and \c + Polygon), the value corresponding to the \c data key is a QGeoShape: + + \list + \li When the type is \c Point, the data is a QGeoCircle with the point + coordinates stored in the center property. + + For example, the following GeoJSON document contains a \c Point + geometry: + + \code + { + "type" : "Point", + "data" : [60.0, 11.0] + } + \endcode + + it is converted to a QVariantMap with the following structure: + + \code + { + type : Point + data : QGeoCircle({60.000, 11.000}, -1) + } + \endcode + + \li When the type is \c LineString the data ia a QGeoPath. + + For example, the following GeoJSON document contains a \c LineString + geometry: + + \code + { + "type" : "LineString", + "coordinates" : [[13.5, 43],[10.73, 59.92]] + } + \endcode + + it is converted to a QVariantMap with the following structure: + + \code + { + type : LineString, + data : QGeoPath([{43.000, 13.500}, {59.920, 10.730}]) + } + \endcode + + \li When the type is \c Polygon, the data is a QGeoPolygon (holes are + supported). + + For example, the following GeoJSON document contains a \c Polygon + geometry: + + \code + { + "type" : "Polygon", + "coordinates" : [ + [[17.13, 51.11], + [30.54, 50.42], + [26.70, 58.36], + [17.13, 51.11]] + ], + "bbox" : [60, 60, -60, -60] + + } + \endcode + + it is converted to a QVariantMap with the following structure: + + \code + { + type : Polygon + data : QGeoPolygon([{51.110, 17.130}, {50.420,30.540}, {58.360, 26.700}, {51.110, 17.130}]) + } + \endcode + + \endlist + + For the homogeneously typed multipart geometry objects (\c MultiPoint, \c + MultiLineString, \c MultiPolygon) the value corresponding to the \c data + key is a QVariantList. Each element of the list is a QVariantMap of one of + the above listed types. The elements in this list will be all of the same + GeoJSON type: + + \list + \li When the type is \c MultiPoint, the data is a List of Points. + + For example, the following GeoJSON document contains a \c MultiPoint + geometry: + + \code + { + "type" : "MultiPoint", + "coordinates" : [ + [11,60], + [5.5,60.3], + [5.7,58.90] + ] + } + \endcode + + it is converted to a QVariantMap with the following structure: + + \code + { + type : MultiPoint + data : [ + { + type : Point + data : QGeoCircle({60.000, 11.000}, -1) + }, + { + type : Point + data : QGeoCircle({60.300, 5.500}, -1) + }, + { + type : Point + data : QGeoCircle({58.900, 5.700}, -1) + } + ] + } + \endcode + + \li When the type is \c MultiLineString, the data is a List of LineStrings. + + For example, the following GeoJSON document contains a \c MultiLineString + geometry: + + \code + { + "type" : "MultiLineString", + "coordinates" : [ + [[13.5, 43], [10.73, 59.92]], + [[9.15, 45], [-3.15, 58.90]] + ] + } + \endcode + + it is converted to a QVariantMap with the following structure: + + \code + { + type : MultiLineString + data : [ + { + type : LineString + data : QGeoPath([{45.000, 9.150}, {58.900, -3.150}]) + }, + { + type : LineString + data : QGeoPath([{43.000, 13.500}, {59.920, 10.730}]) + } + ] + } + \endcode + + \li When the type is \c MultiPolygon, the data is a List of Polygons. + + For example, the following GeoJSON document contains a \c MultiPolygon + geometry: + + \code + { + "type" : "MultiPoint", + "coordinates" : [ + [11,60], + [5.5,60.3], + [5.7,58.90] + ] + } + \endcode + + it is converted to a QVariantMap with the following structure: + + \code + { + type : MultiPoint + data : [ + { + type : Point + data : QGeoCircle({60.000, 11.000}, -1) + }, + { + type : Point + data : QGeoCircle({60.300, 5.500}, -1) + }, + { + type : Point + data : QGeoCircle({58.900, 5.700}, -1) + } + ] + } + \endcode + + \endlist + + The \c GeometryCollection is a heterogeneous composition of other geometry + types. In the resulting QVariantMap, the value of the \c data member is a + QVariantList populated by QVariantMaps of various geometries, including the + GeometryCollection itself. + + For example, the following \c GeometryCollection: + + \code + { + "type" : "GeometryCollection", + "geometries" : [ + { + "type" : "MultiPoint", + "coordinates" : [ + [11,60], [5.5,60.3], [5.7,58.90] + ] + }, + { + "type" : "MultiLineString", + "coordinates" : [ + [[13.5, 43], [10.73, 59.92]], + [[9.15, 45], [-3.15, 58.90]] + ] + }, + { + "type" : "MultiPolygon", + "coordinates" : [ + [ + [[17.13, 51.11], + [30.54, 50.42], + [26.74, 58.36], + [17.13, 51.11]] + ], + [ + [[19.84, 41.33], + [30.45, 49.26], + [17.07, 50.10], + [19.84, 41.33]] + ] + ] + } + ] + } + \endcode + + it is converted to a QVariantMap with the following structure: + + \code + { + type : GeometryCollection + data : [ + { + type : MultiPolygon + data : [ + { + type : Polygon + data : QGeoPolygon([{41.330, 19.840}, {49.260, 30.450}, {50.100, 17.070}, {41.330, 19.840}]) + } + { + type : Polygon + data : QGeoPolygon([{51.110, 17.130}, {50.420, 30.540}, {58.360, 26.740}, {51.110, 17.130}]) + } + ] + } + { + type : MultiLineString + data : [ + { + type : LineString + data : QGeoPath([{45.000, 9.150}, {58.900, -3.150}]) + } + { + type : LineString + data : QGeoPath([{43.000, 13.500}, {59.920, 10.730}]) + } + ] + } + { + type : MultiPoint + data : [ + { + type : Point + data : QGeoCircle({58.900, 5.700}, -1) + }, + { + type : Point + data : QGeoCircle({60.300, 5.500}, -1) + }, + { + type : Point + data : QGeoCircle({60.000, 11.000}, -1) + } + ] + } + ] + } + \endcode + + The \c Feature object, which consists of one of the previous geometries + together with related attributes, is structured like one of the 7 above + mentioned geometry types, plus a \c properties member. The value of this + member is a QVariantMap. The only way to distinguish a Feature from the + included geometry is to check if a \c properties node is present in the + QVariantMap. + + For example, the following \c Feature: + + \code + { + "type" : "Feature", + "id" : "Poly", + "properties" : { + "text" : "This is a Feature with a Polygon" + }, + "geometry" : { + "type" : "Polygon", + "coordinates" : [ + [[17.13, 51.11], + [30.54, 50.42], + [26.70, 58.36], + [17.13, 51.11]], + [[23.46, 54.36], + [20.52, 51.91], + [28.25, 51.50], + [26.80, 54.36], + [23.46, 54.36]] + ] + } + } + \endcode + + it is converted to a QVariantMap with the following structure: + + \code + { + type : Polygon + data : QGeoPolygon([{51.110, 17.130}, {50.420,30.540}, {58.360, 26.700}, {51.110, 17.130}]) + properties : {text : This is a Feature with a Polygon} + } + \endcode + + The \c FeatureCollection is a composition of Feature objects. The value of + the \c data member in a FeatureCollection is a QVariantList populated by + Feature type QVariantMaps. + + For example, the following \c FeatureCollection: + + \code + { + "type" : "FeatureCollection", + "features" : [ + { + "type" : "Feature", + "id" : "Poly", + "properties" : { + "text" : "This is a Feature with a Polygon" + }, + "geometry" : { + "type" : "Polygon", + "coordinates" : [ + [[17.13, 51.11], + [30.54, 50.42], + [26.70, 58.36], + [17.13, 51.11]], + [[23.46, 54.36], + [20.52, 51.91], + [28.25, 51.50], + [26.80, 54.36], + [23.46, 54.36]] + ] + } + }, + { + "type" : "Feature", + "id" : "MultiLine", + "properties" : { + "text" : "This is a Feature with a MultiLineString" + }, + "geometry" : { + "type" : "MultiLineString", + "coordinates" : [ + [[13.5, 43], [10.73, 59.92]], + [[9.15, 45], [-3.15, 58.90]] + ] + } + } + ] + } + \endcode + + it is converted to a QVariantMap with the following structure: + + \code + { + type : FeatureCollection + data : [ + { + type : MultiLineString + data : [ + { + type : LineString + data : QGeoPath([{45.000, 9.150}, {58.900, -3.150}]) + } + { + type : LineString + data : QGeoPath([{43.000, 13.500}, {59.920, 10.730}]) + } + ] + properties : {text : This is a Feature with a MultiLineString} + }, + { + type : Polygon + data : QGeoPolygon({51.110, 17.130}, {50.420, 30.540}, {58.360, 26.700}, {51.110, 17.130}) + properties : {text : This is a Feature with a Polygon} + } + ] + } + \endcode + + \section2 Exporting GeoJSON + + The exporter accepts the QVariantList returned by \l {The importer}, and + returns a JSON document. The exporter is complementary to the importer + because it executes the inverse action. + + \section2 The toString function + + The \l toString outputs, for debugging purposes, the content of a + QVariantList structured like \l importGeoJson does, to a QString using a + prettyfied format. +*/ + +static QGeoCoordinate importPosition(const QVariant &position) +{ + QGeoCoordinate returnedCoordinates; + const QVariantList positionList = position.value<QVariantList>(); + for (int i = 0; i < positionList.size(); ++i) { // Iterating Point coordinates arrays + switch (i) { + case 0: + returnedCoordinates.setLongitude(positionList.at(i).toDouble()); + break; + case 1: + returnedCoordinates.setLatitude(positionList.at(i).toDouble()); + break; + case 2: + returnedCoordinates.setAltitude(positionList.at(i).toDouble()); + break; + default: + break; + } + } + return returnedCoordinates; +} + +static QList<QGeoCoordinate> importArrayOfPositions(const QVariant &arrayOfPositions) +{ + QList <QGeoCoordinate> returnedCoordinates; + const QVariantList positionsList = arrayOfPositions.value<QVariantList>(); + QGeoCoordinate singlePosition; + for (int i = 0; i < positionsList.size(); ++i) { // Iterating the LineString coordinates nested arrays + singlePosition = importPosition((positionsList.at(i))); + returnedCoordinates.append(singlePosition); // Populating the QList of coordinates + } + return returnedCoordinates; +} + +static QList<QList<QGeoCoordinate>> importArrayOfArrayOfPositions(const QVariant &arrayOfArrayofPositions) +{ + QList<QList<QGeoCoordinate>> returnedCoordinates; + const QVariantList positionsList = arrayOfArrayofPositions.value<QVariantList>(); + QList<QGeoCoordinate> arrayOfPositions; + for (int i = 0; i < positionsList.size(); ++i) { // Iterating the Polygon coordinates nested arrays + arrayOfPositions = importArrayOfPositions((positionsList.at(i))); + returnedCoordinates << arrayOfPositions; + } + return returnedCoordinates; +} + +static QGeoCircle importPoint(const QVariantMap &inputMap) +{ + QGeoCircle returnedObject; + QGeoCoordinate center; + QVariant valueCoords = inputMap.value(QStringLiteral("coordinates")); + center = importPosition(valueCoords); + returnedObject.setCenter(center); + return returnedObject; +} + +static QGeoPath importLineString(const QVariantMap &inputMap) +{ + QGeoPath returnedObject; + QList <QGeoCoordinate> coordinatesList; + const QVariant valueCoordinates = inputMap.value(QStringLiteral("coordinates")); + coordinatesList = importArrayOfPositions(valueCoordinates); + returnedObject.setPath(coordinatesList); + return returnedObject; +} + +static QGeoPolygon importPolygon(const QVariantMap &inputMap) +{ + QGeoPolygon returnedObject; + const QVariant valueCoordinates = inputMap.value(QStringLiteral("coordinates")); + QList<QList<QGeoCoordinate>> perimeters = importArrayOfArrayOfPositions(valueCoordinates); + for (int i = 0; i < perimeters.size(); ++i) { // Import an array of QList<QGeocoordinates> + if (i == 0) + returnedObject.setPath(perimeters.at(i)); // External perimeter + else + returnedObject.addHole(perimeters.at(i)); // Inner perimeters + } + return returnedObject; +} + +static QVariantList importMultiPoint(const QVariantMap &inputMap) +{ + QVariantList returnedObject; + const QVariantList coordinatesList = inputMap.value(QStringLiteral("coordinates")).value<QVariantList>(); + QVariantMap singlePointMap; + QGeoCircle parsedPoint; + for (int i = 0; i < coordinatesList.size(); ++i) { // Iterating MultiPoint coordinates nasted arrays + parsedPoint.setCenter(importPosition(coordinatesList.at(i))); + singlePointMap.insert(QStringLiteral("type"), QStringLiteral("Point")); + singlePointMap.insert(QStringLiteral("data"), QVariant::fromValue(parsedPoint)); + returnedObject.append(QVariant::fromValue(singlePointMap)); + } + return returnedObject; +} + +static QVariantList importMultiLineString(const QVariantMap &inputMap) +{ + QVariantList returnedObject; + QGeoPath parsedLineString; + const QVariant listCoords = inputMap.value(QStringLiteral("coordinates")); + const QVariantList list = listCoords.value<QVariantList>(); + QVariantMap singleLinestringMap; + for (int i = 0; i < list.size(); ++i) { // Iterating the MultiLineString coordinates nasted arrays using importArrayOfPositions + singleLinestringMap.clear(); + const QList <QGeoCoordinate> coordinatesList = importArrayOfPositions(list.at(i)); + singleLinestringMap.insert(QStringLiteral("type"), QStringLiteral("LineString")); + parsedLineString.setPath(coordinatesList); + singleLinestringMap.insert(QStringLiteral("data"), QVariant::fromValue(parsedLineString)); + returnedObject.append(QVariant::fromValue(singleLinestringMap)); + } + return returnedObject; +} + +static QVariantList importMultiPolygon(const QVariantMap &inputMap) +{ + QVariantList returnedObject; + QGeoPolygon singlePoly; + QVariantMap singlePolygonMap; + const QVariant valueCoordinates = inputMap.value(QStringLiteral("coordinates")); + const QVariantList list = valueCoordinates.value<QVariantList>(); + for (int i = 0; i < list.size(); ++i) { // Iterating the MultiPolygon coordinates nasted arrays + singlePolygonMap.clear(); + const QList<QList<QGeoCoordinate>> coordinatesList = importArrayOfArrayOfPositions(list.at(i)); + + for (int j = 0; j < coordinatesList.size(); ++j) { + if (j == 0) + singlePoly.setPath(coordinatesList.at(j)); + else + singlePoly.addHole(coordinatesList.at(j)); + } + singlePolygonMap.insert(QStringLiteral("type"), QStringLiteral("Polygon")); + singlePolygonMap.insert(QStringLiteral("data"), QVariant::fromValue(singlePoly)); + returnedObject.append(QVariant::fromValue(singlePolygonMap)); + } + return returnedObject; +} + +static QVariantMap importGeometry(const QVariantMap &inputMap); // Function prototype for a tail recursion + +static QVariantList importGeometryCollection(const QVariantMap &inputMap) +{ + QVariantList returnedObject; + const QVariant listGeometries = inputMap.value(QStringLiteral("geometries")); + const QVariantList list = listGeometries.value<QVariantList>(); // QVariantList of heterogeneous composition of the other geometry types + for (int i = 0; i < list.size(); ++i) { + QVariantMap geometryMap = list.at(i).value<QVariantMap>(); + QVariantMap geoMap = importGeometry(geometryMap); + returnedObject.append(geoMap); + } + return returnedObject; +} + +static QVariantMap importGeometry(const QVariantMap &inputMap) +{ + QVariantMap returnedObject; + QString geometryTypes[] = { + QStringLiteral("Point"), + QStringLiteral("MultiPoint"), + QStringLiteral("LineString"), + QStringLiteral("MultiLineString"), + QStringLiteral("Polygon"), + QStringLiteral("MultiPolygon"), + QStringLiteral("GeometryCollection") + }; + enum geoTypeSwitch { + Point, + MultiPoint, + LineString, + MultiLineString, + Polygon, + MultiPolygon, + GeometryCollection + }; + for (int i = 0; i<7; ++i) { + if (inputMap.value(QStringLiteral("type")).value<QString>() == geometryTypes[i]) { + switch (i) { + case Point: { + returnedObject.insert(QStringLiteral("type"), QStringLiteral("Point")); + returnedObject.insert(QStringLiteral("data"), QVariant::fromValue(importPoint(inputMap))); + break; + } + case MultiPoint: { + returnedObject.insert(QStringLiteral("type"), QStringLiteral("MultiPoint")); + returnedObject.insert(QStringLiteral("data"), QVariant::fromValue(importMultiPoint(inputMap))); + break; + } + case LineString: { + returnedObject.insert(QStringLiteral("type"), QStringLiteral("LineString")); + returnedObject.insert(QStringLiteral("data"), QVariant::fromValue(importLineString(inputMap))); + break; + } + case MultiLineString: { + returnedObject.insert(QStringLiteral("type"), QStringLiteral("MultiLineString")); + returnedObject.insert(QStringLiteral("data"), QVariant::fromValue(importMultiLineString(inputMap))); + break; + } + case Polygon: { + returnedObject.insert(QStringLiteral("type"), QStringLiteral("Polygon")); + returnedObject.insert(QStringLiteral("data"), QVariant::fromValue(importPolygon(inputMap))); + break; + } + case MultiPolygon: { + returnedObject.insert(QStringLiteral("type"), QStringLiteral("MultiPolygon")); + returnedObject.insert(QStringLiteral("data"), QVariant::fromValue(importMultiPolygon(inputMap))); + break; + } + case GeometryCollection: { + returnedObject.insert(QStringLiteral("type"), QStringLiteral("GeometryCollection")); + returnedObject.insert(QStringLiteral("data"), QVariant::fromValue(importGeometryCollection(inputMap))); + break; + } + default: + break; + } + } + } + return returnedObject; +} + +static QVariantList importFeatureCollection(const QVariantMap &inputMap) +{ + QVariantList returnedObject; + const QVariantList featuresList = inputMap.value(QStringLiteral("features")).value<QVariantList>(); + for (int i = 0; i < featuresList.size(); ++i) { + QVariantMap inputFeatureMap = featuresList.at(i).value<QVariantMap>(); + QVariantMap singleFeatureMap = importGeometry(inputFeatureMap.value(QStringLiteral("geometry")).value<QVariantMap>()); + const QVariantMap importedProperties = inputFeatureMap.value(QStringLiteral("properties")).value<QVariantMap>(); + singleFeatureMap.insert(QStringLiteral("properties"), importedProperties); + if (inputFeatureMap.contains(QStringLiteral("id"))) { + QVariant importedId = inputFeatureMap.value(QStringLiteral("id")).value<QVariant>(); + singleFeatureMap.insert(QStringLiteral("id"), importedId); + } + returnedObject.append(singleFeatureMap); + } + return returnedObject; +} + +static QJsonValue exportPosition(const QGeoCoordinate &obtainedCoordinates) +{ + QJsonValue geoLat = obtainedCoordinates.latitude(); + QJsonValue geoLong = obtainedCoordinates.longitude(); + QJsonArray array = {geoLong, geoLat}; + QJsonValue geoAlt; + if (!qIsNaN(obtainedCoordinates.altitude())) { + geoAlt = obtainedCoordinates.altitude(); + array.append(geoAlt); + } + QJsonValue geoArray = array; + return geoArray; +} + +static QJsonValue exportArrayOfPositions(const QList<QGeoCoordinate> &obtainedCoordinatesList) +{ + QJsonValue lineCoordinates; + QJsonValue multiPosition; + QJsonArray arrayPosition; + for (int i = 0; i < obtainedCoordinatesList.size(); ++i) { + multiPosition = exportPosition(obtainedCoordinatesList.at(i)); + arrayPosition.append(multiPosition); + } + lineCoordinates = arrayPosition; + return lineCoordinates; +} + +static QJsonValue exportArrayOfArrayOfPositions(const QList<QList<QGeoCoordinate>> &obtainedCoordinates) +{ + QJsonValue lineCoordinates; + QJsonValue polyCoordinates; + QJsonArray arrayPath; + for (int i = 0; i < obtainedCoordinates.size(); ++i) { + lineCoordinates = exportArrayOfPositions(obtainedCoordinates.at(i)); + arrayPath.append(lineCoordinates); + } + polyCoordinates = arrayPath; + return polyCoordinates; +} + +static QJsonObject exportPoint(const QVariantMap &pointMap) +{ + QJsonObject parsedPoint; + QGeoCircle circle = pointMap.value(QStringLiteral("data")).value<QGeoCircle>(); + parsedPoint.insert(QStringLiteral("type"), QJsonValue(QStringLiteral("Point"))); + parsedPoint.insert(QStringLiteral("coordinates"), exportPosition(circle.center())); + return parsedPoint; +} + +static QJsonObject exportLineString(const QVariantMap &lineStringMap) +{ + QJsonObject parsedLineString; + QList <QGeoCoordinate> linestringPath = lineStringMap.value(QStringLiteral("data")).value<QGeoPath>().path(); + parsedLineString.insert(QStringLiteral("type"), QJsonValue(QStringLiteral("LineString"))); + parsedLineString.insert(QStringLiteral("coordinates"), exportArrayOfPositions(linestringPath)); + return parsedLineString; +} + +static QJsonObject exportPolygon(const QVariantMap &polygonMap) +{ + QVariant polygonVariant = polygonMap.value(QStringLiteral("data")); + QJsonObject parsedPolygon; + QJsonValue polyCoordinates; + QList<QList<QGeoCoordinate>> obtainedCoordinatesPoly; + QGeoPolygon parsedPoly = polygonVariant.value<QGeoPolygon>(); + obtainedCoordinatesPoly << parsedPoly.path(); + if (parsedPoly.holesCount()!=0) + for (int i = 0; i < parsedPoly.holesCount(); ++i) { + obtainedCoordinatesPoly << parsedPoly.holePath(i); + } + polyCoordinates = exportArrayOfArrayOfPositions(obtainedCoordinatesPoly); + parsedPolygon.insert(QStringLiteral("type"), QJsonValue(QStringLiteral("Polygon"))); + parsedPolygon.insert(QStringLiteral("coordinates"), polyCoordinates); + return parsedPolygon; +} + +static QJsonObject exportMultiPoint(const QVariantMap &multiPointMap) +{ + QJsonObject parsedMultiPoint; + QList <QGeoCoordinate> obtainedCoordinatesMP; + QVariantList multiCircleVariantList = multiPointMap.value(QStringLiteral("data")).value<QVariantList>(); + for (const QVariant &exCircleVariantMap: multiCircleVariantList) { + obtainedCoordinatesMP << exCircleVariantMap.value<QVariantMap>().value(QStringLiteral("data")).value<QGeoCircle>().center(); + } + QJsonValue multiPosition = exportArrayOfPositions(obtainedCoordinatesMP); + parsedMultiPoint.insert(QStringLiteral("type"), QJsonValue(QStringLiteral("MultiPoint"))); + parsedMultiPoint.insert(QStringLiteral("coordinates"), multiPosition); + return parsedMultiPoint; +} + +static QJsonObject exportMultiLineString(const QVariantMap &multiLineStringMap) +{ + QJsonObject parsedMultiLineString; + QList<QList<QGeoCoordinate>> extractedCoordinatesValue; + QVariant multiPathVariant = multiLineStringMap.value(QStringLiteral("data")); + QVariantList multiPathList = multiPathVariant.value<QVariantList>(); + for (int i = 0; i < multiPathList.size(); ++i) { + extractedCoordinatesValue << multiPathList.at(i).value<QVariantMap>().value(QStringLiteral("data")).value<QGeoPath>().path(); + } + QJsonValue exportedCoordinatesValue = exportArrayOfArrayOfPositions(extractedCoordinatesValue); + parsedMultiLineString.insert(QStringLiteral("type"), QJsonValue(QStringLiteral("MultiLineString"))); + parsedMultiLineString.insert(QStringLiteral("coordinates"), exportedCoordinatesValue); + return parsedMultiLineString; +} + +static QJsonObject exportMultiPolygon(const QVariantMap &multiPolygonMap) +{ + QJsonObject parsedMultiPolygon; + QJsonValue polyCoordinates; + QJsonArray parsedArrayPolygon; + QList<QList<QGeoCoordinate>> extractedCoordinatesValue; + QVariant multiPolygonVariant = multiPolygonMap.value(QStringLiteral("data")); + QVariantList multiPolygonList = multiPolygonVariant.value<QVariantList>(); + int polyHoles = 0; + int currentHole; + for (int i = 0; i < multiPolygonList.size(); ++i) { // Start parsing Polygon list + extractedCoordinatesValue << multiPolygonList.at(i).value<QVariantMap>().value(QStringLiteral("data")).value<QGeoPolygon>().path(); // Extract external polygon path + polyHoles = multiPolygonList.at(i).value<QVariantMap>().value(QStringLiteral("data")).value<QGeoPolygon>().holesCount(); + if (polyHoles) // Check if the polygon has holes + for (currentHole = 0 ; currentHole < polyHoles; currentHole++) + extractedCoordinatesValue << multiPolygonList.at(i).value<QVariantMap>().value(QStringLiteral("data")).value<QGeoPolygon>().holePath(currentHole); + polyCoordinates = exportArrayOfArrayOfPositions(extractedCoordinatesValue); // Generates QJsonDocument compatible value + parsedArrayPolygon.append(polyCoordinates); // Adds one level of nesting in coordinates + extractedCoordinatesValue.clear(); // Clears the temporary polygon linear ring storage + } + QJsonValue exportedCoordinatesNodeValue = parsedArrayPolygon; + parsedMultiPolygon.insert(QStringLiteral("type"), QJsonValue(QStringLiteral("MultiPolygon"))); + parsedMultiPolygon.insert(QStringLiteral("coordinates"), exportedCoordinatesNodeValue); + return parsedMultiPolygon; +} + +static QJsonObject exportGeometry(const QVariantMap &geometryMap); // Function prototype + +static QJsonObject exportGeometryCollection(const QVariantMap &geometryCollection) +{ + QJsonObject parsed; + QJsonObject parsedGeometry; + QJsonValue valueGeometries; + QJsonArray parsedGeometries; + QVariantList geometriesList = geometryCollection.value(QStringLiteral("data")).value<QVariantList>(); + for (int i = 0; i < geometriesList.size(); ++i) { + parsedGeometry = exportGeometry(geometriesList.at(i).value<QVariantMap>()); + valueGeometries = parsedGeometry; + parsedGeometries.append(valueGeometries); + } + QJsonValue exportedGeometriesValue = parsedGeometries; + parsed.insert(QStringLiteral("type"), QJsonValue(QStringLiteral("GeometryCollection"))); + parsed.insert(QStringLiteral("geometries"), exportedGeometriesValue); + return parsed; +} + +static QJsonObject exportGeometry(const QVariantMap &geometryMap) +{ + QJsonObject exportedGeometry; + if (geometryMap.value(QStringLiteral("type")) == QStringLiteral("Point")) + exportedGeometry = exportPoint(geometryMap); + if (geometryMap.value(QStringLiteral("type")) == QStringLiteral("MultiPoint")) + exportedGeometry = exportMultiPoint(geometryMap); + if (geometryMap.value(QStringLiteral("type")) == QStringLiteral("LineString")) + exportedGeometry = exportLineString(geometryMap); + if (geometryMap.value(QStringLiteral("type")) == QStringLiteral("MultiLineString")) + exportedGeometry = exportMultiLineString(geometryMap); + if (geometryMap.value(QStringLiteral("type")) == QStringLiteral("Polygon")) + exportedGeometry = exportPolygon(geometryMap); + if (geometryMap.value(QStringLiteral("type")) == QStringLiteral("MultiPolygon")) + exportedGeometry = exportMultiPolygon(geometryMap); + if (geometryMap.value(QStringLiteral("type")) == QStringLiteral("GeometryCollection")) + exportedGeometry = exportGeometryCollection(geometryMap); + return exportedGeometry; +} + +static QJsonObject exportFeature(const QVariantMap &featureMap) +{ + QJsonObject exportedFeature; + QJsonValue geometryNodeValue = QJsonValue(exportGeometry(featureMap)); + QJsonValue propertiesNodeValue = featureMap.value(QStringLiteral("properties")).value<QVariant>().toJsonValue(); + QJsonValue idNodeValue = featureMap.value(QStringLiteral("id")).value<QVariant>().toJsonValue(); + exportedFeature.insert(QStringLiteral("type"), QJsonValue(QStringLiteral("Feature"))); + exportedFeature.insert(QStringLiteral("geometry"), geometryNodeValue); + exportedFeature.insert(QStringLiteral("properties"), propertiesNodeValue); + exportedFeature.insert(QStringLiteral("id"), idNodeValue); + return exportedFeature; +} + +static QJsonObject exportFeatureCollection(const QVariantMap &featureCollection) +{ + QJsonObject exportedFeatureCollection; + QJsonArray featureArray; + QVariantList featureList = featureCollection.value(QStringLiteral("data")).value<QVariantList>(); + for (int i = 0; i < featureList.size(); ++i) { + featureArray.append(QJsonValue(exportFeature(featureList.at(i).value<QVariantMap>()))); + } + exportedFeatureCollection.insert(QStringLiteral("type"), QJsonValue(QStringLiteral("FeatureCollection"))); + exportedFeatureCollection.insert(QStringLiteral("features"), QJsonValue(featureArray) ); + return exportedFeatureCollection; +} + +/*! +This method imports the \a geoJson document, expected to contain valid GeoJSON +data, into a QVariantList structured like described in the section \l +{Importing GeoJSON}. + +\note This method performs no validation on the input. + +\sa exportGeoJson +*/ +QVariantList QGeoJson::importGeoJson(const QJsonDocument &geoJson) +{ + QVariantList returnedList; + QJsonObject object = geoJson.object(); // Read json object from imported doc + QVariantMap rootGeoJsonObject = object.toVariantMap(); // Extraced map using Qt's API + QString geoType[] = { + QStringLiteral("Point"), + QStringLiteral("MultiPoint"), + QStringLiteral("LineString"), + QStringLiteral("MultiLineString"), + QStringLiteral("Polygon"), + QStringLiteral("MultiPolygon"), + QStringLiteral("GeometryCollection"), + QStringLiteral("Feature"), + QStringLiteral("FeatureCollection") + }; + enum geoTypeSwitch { + Point, + MultiPoint, + LineString, + MultiLineString, + Polygon, + MultiPolygon, + GeometryCollection, + Feature, + FeatureCollection + }; + QVariantMap parsedGeoJsonMap; + + // Checking whether the JSON object has a "type" member + const QVariant keyVariant = rootGeoJsonObject.value(QStringLiteral("type")); + if (keyVariant == QVariant::Invalid) { + // Type check failed + } + QString valueType = keyVariant.value<QString>(); + + // Checking whether the "type" member has a GeoJSON admitted value + for (int i = 0; i < 9; ++i) { + if (valueType == geoType[i]) { + switch (i) { + case Point: { + QGeoCircle circle = importPoint(rootGeoJsonObject); + QVariant dataNodeValue = QVariant::fromValue(circle); + parsedGeoJsonMap.insert(QStringLiteral("type"), QStringLiteral("Point")); + parsedGeoJsonMap.insert(QStringLiteral("data"), dataNodeValue); + break; + } + case MultiPoint: { + QVariantList multiCircle = importMultiPoint(rootGeoJsonObject); + QVariant dataNodeValue = QVariant::fromValue(multiCircle); + QList <QGeoCircle> testlist; + parsedGeoJsonMap.insert(QStringLiteral("type"), QStringLiteral("MultiPoint")); + parsedGeoJsonMap.insert(QStringLiteral("data"), dataNodeValue); + break; + } + case LineString: { + QGeoPath lineString = importLineString(rootGeoJsonObject); + QVariant dataNodeValue = QVariant::fromValue(lineString); + parsedGeoJsonMap.insert(QStringLiteral("type"), QStringLiteral("LineString")); + parsedGeoJsonMap.insert(QStringLiteral("data"), dataNodeValue); + break; + } + case MultiLineString: { + QVariantList multiLineString = importMultiLineString(rootGeoJsonObject); + QVariant dataNodeValue = QVariant::fromValue(multiLineString); + parsedGeoJsonMap.insert(QStringLiteral("type"), QStringLiteral("MultiLineString")); + parsedGeoJsonMap.insert(QStringLiteral("data"), dataNodeValue); + break; + } + case Polygon: { + QGeoPolygon poly = importPolygon(rootGeoJsonObject); + QVariant dataNodeValue = QVariant::fromValue(poly); + parsedGeoJsonMap.insert(QStringLiteral("type"), QStringLiteral("Polygon")); + parsedGeoJsonMap.insert(QStringLiteral("data"), dataNodeValue); + break; + } + case MultiPolygon: { + QVariantList multiPoly = importMultiPolygon(rootGeoJsonObject); + QVariant dataNodeValue = QVariant::fromValue(multiPoly); + parsedGeoJsonMap.insert(QStringLiteral("type"), QStringLiteral("MultiPolygon")); + parsedGeoJsonMap.insert(QStringLiteral("data"), dataNodeValue); + break; + } + // List of GeoJson geometry objects + case GeometryCollection: { + QVariantList multiGeo = importGeometryCollection(rootGeoJsonObject); + QVariant dataNodeValue = QVariant::fromValue(multiGeo); + parsedGeoJsonMap.insert(QStringLiteral("type"), QStringLiteral("GeometryCollection")); + parsedGeoJsonMap.insert(QStringLiteral("data"), dataNodeValue); + break; + } + // Single GeoJson geometry object with properties + case Feature: { + parsedGeoJsonMap = importGeometry(rootGeoJsonObject.value(QStringLiteral("geometry")).value<QVariantMap>()); + QVariantMap importedProperties = rootGeoJsonObject.value(QStringLiteral("properties")).value<QVariantMap>(); + parsedGeoJsonMap.insert(QStringLiteral("properties"), importedProperties); + if (rootGeoJsonObject.contains(QStringLiteral("id"))){ + QVariant importedId = rootGeoJsonObject.value(QStringLiteral("id")).value<QVariant>(); + parsedGeoJsonMap.insert(QStringLiteral("id"), importedId); + } + break; + } + // Heterogeneous list of GeoJSON geometries with properties + case FeatureCollection: { + QVariantList featCollection = importFeatureCollection(rootGeoJsonObject); + QVariant dataNodeValue = QVariant::fromValue(featCollection); + parsedGeoJsonMap.insert(QStringLiteral("type"), QStringLiteral("FeatureCollection")); + parsedGeoJsonMap.insert(QStringLiteral("data"), dataNodeValue); + break; + } + default: + break; + } + QVariant bboxNodeValue = rootGeoJsonObject.value(QStringLiteral("bbox")); + if (bboxNodeValue != QVariant::Invalid) { + parsedGeoJsonMap.insert(QStringLiteral("bbox"), bboxNodeValue); + } + returnedList.append(parsedGeoJsonMap); + } else if (i >= 9) { + // Error + break; + } + } + return returnedList; +} + +/*! +This method exports the QVariantList \a geoData, expected to be structured like +described in the section \l {Importing GeoJSON}, to a QJsonDocument containing +the data converted to GeoJSON. + +\note This method performs no validation on the input. + +\sa importGeoJson +*/ +QJsonDocument QGeoJson::exportGeoJson(const QVariantList &geoData) +{ + QVariantMap exportMap = geoData.at(0).value<QVariantMap>(); // Extracting the QVMap + QJsonObject newObject; + QJsonDocument newDocument; + if (exportMap.contains(QStringLiteral("properties"))) { + newObject = exportFeature(exportMap); + } else { + if (exportMap.value(QStringLiteral("type")) == QStringLiteral("Point")) // Check the value corresponding to the key "Point" + newObject = exportPoint(exportMap); + if (exportMap.value(QStringLiteral("type")) == QStringLiteral("MultiPoint")) + newObject = exportMultiPoint(exportMap); + if (exportMap.value(QStringLiteral("type")) == QStringLiteral("LineString")) + newObject = exportLineString(exportMap); + if (exportMap.value(QStringLiteral("type")) == QStringLiteral("MultiLineString")) + newObject = exportMultiLineString(exportMap); + if (exportMap.value(QStringLiteral("type")) == QStringLiteral("Polygon")) + newObject = exportPolygon(exportMap); + if (exportMap.value(QStringLiteral("type")) == QStringLiteral("MultiPolygon")) + newObject = exportMultiPolygon(exportMap); + if (exportMap.value(QStringLiteral("type")) == QStringLiteral("GeometryCollection")) + newObject = exportGeometryCollection(exportMap); + if (exportMap.value(QStringLiteral("type")) == QStringLiteral("FeatureCollection")) + newObject = exportFeatureCollection(exportMap); + } + if (exportMap.contains((QStringLiteral("bbox")))) { + QJsonArray bboxArray; + QVariantList bboxList = exportMap.value(QStringLiteral("bbox")).value<QVariantList>(); + for (int i = 0; i < bboxList.size(); ++i) { + bboxArray.append(QJsonValue(bboxList.at(i).value<double>())); + } + newObject.insert(QStringLiteral("bbox"), QJsonValue(bboxArray)); + } + newDocument.setObject(newObject); + return newDocument; +} + +// Functions for toString +QTextStream &operator << (QTextStream &stream, const QGeoCoordinate &crd) +{ + stream << "{ " << QString::number(crd.latitude(), 'f', 3) << ", " + << QString::number(crd.longitude(), 'f', 3) << ", " + << QString::number(crd.altitude(), 'f', 3) << " }"; + return stream; +} + +QTextStream &operator << (QTextStream &stream, const QGeoShape &shape) +{ + switch (shape.type()) { + case QGeoShape::CircleType: { + QGeoCircle circle(shape); + stream << "QGeoCircle(" <<circle.center() << ", "<< QString::number(circle.radius()) << ")"; + break; + } + case QGeoShape::PathType: { + QGeoPath path(shape); + stream << "QGeoPath("; + for (auto c: path.path()) + stream << c << ", "; + stream << ")"; + break; + } + case QGeoShape::PolygonType: { + QGeoPolygon poly(shape); + stream << "QGeoPolygon("; + for (auto c: poly.path()) + stream << c << ", "; + stream << ")"; + break; + } + default: + stream << "QGeoShape(Unknown)"; + break; + } + return stream; +} + +static const QString sTab = QStringLiteral(" "); + +QString printQvariant(const QVariant v, int tabs = 0) { + QString sTabs; + QString res; + QTextStream stream(&res); + for (int i = 0; i< tabs; i++) { + sTabs += sTab; + } + if (v.type() == QVariant::List) { + stream << sTabs << "[\n"; + const QVariantList &l = v.toList(); + for (int i = 0; i < l.size(); ++i) + stream << printQvariant(l.at(i), tabs + 1); + stream << sTabs << "]\n"; + } else if (v.type() == QVariant::Map) { + stream << sTabs << "{\n"; + const QVariantList &l = v.toList(); + const QVariantMap &map = v.toMap(); + + // Either one or the other are valid + if (!map.keys().isEmpty()) { + // Handle type first, to easy reading + if (map.contains(QStringLiteral("type"))) { + stream << sTabs << sTab << QStringLiteral("type") << " : " + << printQvariant(map[QStringLiteral("type")], tabs + 1).remove(QRegExp(QStringLiteral("^[ ]*")));; + } + for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) { + if (iter.key() == QStringLiteral("type")) + continue; + stream << sTabs << sTab << iter.key() << " : " << printQvariant(iter.value(), tabs + 1).remove(QRegExp(QStringLiteral("^[ ]*")));; + } + } + for (int i = 0; i < l.size(); ++i) + stream << printQvariant(l.at(i), tabs + 1); + stream << sTabs << "}\n"; + } else { + stream << sTabs; + QGeoShape workigGeometry; + if ( v.canConvert<QGeoShape>()) { + workigGeometry = v.value<QGeoShape>(); + if (workigGeometry.type() == QGeoShape::CircleType) { + QGeoCircle circle = v.value<QGeoCircle>(); + stream << circle<< "\n"; + } else if (workigGeometry.type() == QGeoShape::PathType) { + QGeoPath path = v.value<QGeoPath>(); + stream << path<< "\n"; + } else if (workigGeometry.type() == QGeoShape::PolygonType) { + QGeoPolygon polygon = v.value<QGeoPolygon>(); + stream << polygon<< "\n"; + } + } else { + if (v.isNull()) + stream << "null\n"; + else + stream << v.toString() << "\n"; + } + } + return res; +} + +/*! +This method accepts the QVariantLists structured like described in section \l +{Importing GeoJSON}, and returns a string containing the same data in a +readable form. +*/ +QString QGeoJson::toString(const QVariantList &geoData) { + return printQvariant(geoData.first(), 0); +} + +QT_END_NAMESPACE diff --git a/src/location/labs/qgeojson_p.h b/src/location/labs/qgeojson_p.h new file mode 100644 index 00000000..064dfcf6 --- /dev/null +++ b/src/location/labs/qgeojson_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2018 Julian Sherollari <jdotsh@gmail.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOJSON_H +#define QGEOJSON_H + +#include <QtCore/qvariant.h> +#include <QtCore/qjsondocument.h> +#include <QtLocation/private/qlocationglobal_p.h> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QGeoJson +{ +public: + + // This method imports a GeoJSON file to a QVariantList + static QVariantList importGeoJson(const QJsonDocument &doc); + + // This method exports a GeoJSON file from a QVariantList + static QJsonDocument exportGeoJson(const QVariantList &list); + + // This method exports the content of the imported QVariantList in a + // readable format + static QString toString(const QVariantList + &importedGeoJson); + +}; + +QT_END_NAMESPACE + +#endif // QGEOJSON_H |