blob: 151091ca9a25bc4e5806f320f20142c4052468e2 [file] [log] [blame]
/****************************************************************************
**
** 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 importGeoJson() method 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 the \l {Importing GeoJSON}
{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 QVariantList \a geoData, structured as described in
\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