Revision control

++ // Copyright (C) 2015 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qgeocodejsonparser.h"
++
++ #include <QtPositioning/QGeoShape>
++ #include <QtPositioning/QGeoRectangle>
++ #include <QtPositioning/QGeoAddress>
++ #include <QtPositioning/QGeoCoordinate>
++
++ #include <QtCore/QThreadPool>
++ #include <QtCore/QJsonObject>
++ #include <QtCore/QJsonArray>
++ #include <QtCore/QJsonParseError>
++ #include <QtCore/QVariantMap>
++
++ #include <QtDebug>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace {
++
++ /*
++ Checks that the given Location object contains the information
++ we need and is not malformed in any way. We expect a Location
++ object of the following form:
++
++ "Location": {
++ "Address": {
++ "AdditionalData": [
++ {
++ "key": "CountryName",
++ "value": "Australia"
++ },
++ {
++ "key": "StateName",
++ "value": "New South Wales"
++ }
++ ],
++ "City": "Sydney",
++ "Country": "AUS",
++ "District": "Casula",
++ "Label": "Casula, Sydney, NSW, Australia",
++ "PostalCode": "2170",
++ "State": "NSW"
++ },
++ "DisplayPosition": {
++ "Latitude": -33.949509999999997,
++ "Longitude": 150.90386000000001
++ },
++ "LocationId": "NT_5UQ89lKoiI4DIYbOrIR0-D",
++ "LocationType": "area",
++ "MapReference": {
++ "CityId": "1469266800",
++ "CountryId": "1469256839",
++ "DistrictId": "1469267758",
++ "MapId": "NXAM16130",
++ "MapReleaseDate": "2016-10-05",
++ "MapVersion": "Q1/2016",
++ "ReferenceId": "868383156",
++ "SideOfStreet": "neither",
++ "StateId": "1469256831"
++ },
++ "MapView": {
++ "BottomRight": {
++ "Latitude": -33.966839999999998,
++ "Longitude": 150.91875999999999
++ },
++ "TopLeft": {
++ "Latitude": -33.937440000000002,
++ "Longitude": 150.87457000000001
++ }
++ }
++ }
++
++ */
++ bool checkLocation(const QJsonObject &loc, QString *errorString)
++ {
++ QJsonObject::const_iterator ait = loc.constFind(QLatin1String("Address"));
++ if (ait == loc.constEnd()) {
++ *errorString = QLatin1String("Expected Address element within Location object");
++ return false;
++ } else if (!ait.value().isObject()) {
++ *errorString = QLatin1String("Expected Address object within Location object");
++ return false;
++ }
++
++ QJsonObject::const_iterator dpit = loc.constFind(QLatin1String("DisplayPosition"));
++ if (dpit == loc.constEnd()) {
++ *errorString = QLatin1String("Expected DisplayPosition element within Location object");
++ return false;
++ } else if (!dpit.value().isObject()) {
++ *errorString = QLatin1String("Expected DisplayPosition object within Location object");
++ return false;
++ }
++ QJsonObject displayPosition = dpit.value().toObject();
++ QJsonObject::const_iterator latit = displayPosition.constFind(QLatin1String("Latitude"));
++ if (latit == displayPosition.constEnd()) {
++ *errorString = QLatin1String("Expected Latitude element within Location.DisplayPosition object");
++ return false;
++ } else if (!latit.value().isDouble()) {
++ *errorString = QLatin1String("Expected Latitude double within Location.DisplayPosition object");
++ return false;
++ }
++ QJsonObject::const_iterator lonit = displayPosition.constFind(QLatin1String("Longitude"));
++ if (lonit == displayPosition.constEnd()) {
++ *errorString = QLatin1String("Expected Longitude element within Location.DisplayPosition object");
++ return false;
++ } else if (!lonit.value().isDouble()) {
++ *errorString = QLatin1String("Expected Longitude double within Location.DisplayPosition object");
++ return false;
++ }
++
++ QJsonObject::const_iterator mvit = loc.constFind(QLatin1String("MapView"));
++ if (mvit == loc.constEnd()) {
++ *errorString = QLatin1String("Expected MapView element within Location object");
++ return false;
++ } else if (!mvit.value().isObject()) {
++ *errorString = QLatin1String("Expected MapView object within Location object");
++ return false;
++ }
++ QJsonObject mapView = mvit.value().toObject();
++ QJsonObject::const_iterator brit = mapView.constFind(QLatin1String("BottomRight"));
++ if (brit == mapView.constEnd()) {
++ *errorString = QLatin1String("Expected BottomRight element within Location.MapView object");
++ return false;
++ } else if (!brit.value().isObject()) {
++ *errorString = QLatin1String("Expected BottomRight object within Location.MapView object");
++ return false;
++ }
++ QJsonObject bottomRight = brit.value().toObject();
++ QJsonObject::const_iterator brlatit = bottomRight.constFind(QLatin1String("Latitude"));
++ if (brlatit == bottomRight.constEnd()) {
++ *errorString = QLatin1String("Expected Latitude element within Location.MapView.BottomRight object");
++ return false;
++ } else if (!brlatit.value().isDouble()) {
++ *errorString = QLatin1String("Expected Latitude double within Location.MapView.BottomRight object");
++ return false;
++ }
++ QJsonObject::const_iterator brlonit = bottomRight.constFind(QLatin1String("Longitude"));
++ if (brlonit == bottomRight.constEnd()) {
++ *errorString = QLatin1String("Expected Longitude element within Location.MapView.BottomRight object");
++ return false;
++ } else if (!brlonit.value().isDouble()) {
++ *errorString = QLatin1String("Expected Longitude double within Location.MapView.BottomRight object");
++ return false;
++ }
++ QJsonObject::const_iterator tlit = mapView.constFind(QLatin1String("TopLeft"));
++ if (tlit == mapView.constEnd()) {
++ *errorString = QLatin1String("Expected TopLeft element within Location.MapView object");
++ return false;
++ } else if (!tlit.value().isObject()) {
++ *errorString = QLatin1String("Expected TopLeft object within Location.MapView object");
++ return false;
++ }
++ QJsonObject topLeft = tlit.value().toObject();
++ QJsonObject::const_iterator tllatit = topLeft.constFind(QLatin1String("Latitude"));
++ if (tllatit == topLeft.constEnd()) {
++ *errorString = QLatin1String("Expected Latitude element within Location.MapView.TopLeft object");
++ return false;
++ } else if (!tllatit.value().isDouble()) {
++ *errorString = QLatin1String("Expected Latitude double within Location.MapView.TopLeft object");
++ return false;
++ }
++ QJsonObject::const_iterator tllonit = topLeft.constFind(QLatin1String("Longitude"));
++ if (tllonit == bottomRight.constEnd()) {
++ *errorString = QLatin1String("Expected Longitude element within Location.MapView.TopLeft object");
++ return false;
++ } else if (!tllonit.value().isDouble()) {
++ *errorString = QLatin1String("Expected Longitude double within Location.MapView.TopLeft object");
++ return false;
++ }
++
++ return true;
++ }
++
++ /*
++ Checks that the given document contains the required information
++ and is not malformed in any way. We expect a document like the
++ following:
++
++ {
++ "Response": {
++ "MetaInfo": {
++ "Timestamp": "2016-10-18T08:42:04.369+0000"
++ },
++ "View": [
++ {
++ "ViewId": 0,
++ "_type": "SearchResultsViewType",
++ "Result": [
++ {
++ "Direction": 72.099999999999994,
++ "Distance": -1885.2,
++ "Location": {
++ // OMITTED FOR BREVITY
++ },
++ "MatchLevel": "district",
++ "MatchQuality": {
++ "City": 1,
++ "Country": 1,
++ "District": 1,
++ "PostalCode": 1,
++ "State": 1
++ },
++ "Relevance": 1
++ }
++ ]
++ }
++ ]
++ }
++ }
++ */
++ bool checkDocument(const QJsonDocument &doc, QString *errorString)
++ {
++ if (!doc.isObject()) {
++ *errorString = QLatin1String("Expected JSON document containing object");
++ return false;
++ }
++
++ QJsonObject rootObject = doc.object();
++ QJsonObject::const_iterator it = rootObject.constFind(QLatin1String("Response"));
++ if (it == rootObject.constEnd()) {
++ *errorString = QLatin1String("Expected Response element within root object");
++ return false;
++ } else if (!it.value().isObject()) {
++ *errorString = QLatin1String("Expected Response object within root object");
++ return false;
++ }
++
++ QJsonObject response = it.value().toObject();
++ QJsonObject::const_iterator rit = response.constFind(QLatin1String("View"));
++ if (rit == response.constEnd()) {
++ *errorString = QLatin1String("Expected View element within Response object");
++ return false;
++ } else if (!rit.value().isArray()) {
++ *errorString = QLatin1String("Expected View array within Response object");
++ return false;
++ }
++
++ const QJsonArray view = rit.value().toArray();
++ for (const QJsonValueConstRef viewElement : view) {
++ if (!viewElement.isObject()) {
++ *errorString = QLatin1String("Expected View array element to be object");
++ return false;
++ }
++
++ QJsonObject viewObject = viewElement.toObject();
++ QJsonObject::const_iterator voit = viewObject.constFind(QLatin1String("Result"));
++ if (voit == viewObject.constEnd()) {
++ *errorString = QLatin1String("Expected Result element within View array object element");
++ return false;
++ } else if (!voit.value().isArray()) {
++ *errorString = QLatin1String("Expected Result array within View array object element");
++ return false;
++ }
++
++ const QJsonArray result = voit.value().toArray();
++ for (const QJsonValueConstRef resultElement : result) {
++ if (!resultElement.isObject()) {
++ *errorString = QLatin1String("Expected Result array element to be object");
++ return false;
++ }
++
++ QJsonObject resultObject = resultElement.toObject();
++ QJsonObject::const_iterator roit = resultObject.constFind("Location");
++ if (roit == resultObject.constEnd()) {
++ *errorString = QLatin1String("Expected Location element in Result array element object");
++ return false;
++ } else if (!roit.value().isObject()) {
++ *errorString = QLatin1String("Expected Location object in Result array element object");
++ return false;
++ }
++
++ QJsonObject location = roit.value().toObject();
++ if (!checkLocation(location, errorString)) {
++ return false;
++ }
++ }
++ }
++
++ return true;
++ }
++
++ bool parseLocation(const QJsonObject &obj, const QGeoShape &bounds, QGeoLocation *loc)
++ {
++ QJsonObject displayPosition = obj.value("DisplayPosition").toObject();
++ QGeoCoordinate coordinate = QGeoCoordinate(displayPosition.value("Latitude").toDouble(), displayPosition.value("Longitude").toDouble());
++ if (bounds.isValid() && !bounds.contains(coordinate)) {
++ // manual bounds check failed, location can be omitted from results.
++ return false;
++ }
++
++ QGeoAddress address;
++ QJsonObject addr = obj.value("Address").toObject();
++ address.setCountryCode(addr.value("Country").toString());
++ address.setState(addr.value("State").toString());
++ address.setCounty(addr.value("County").toString());
++ address.setCity(addr.value("City").toString());
++ address.setDistrict(addr.value("District").toString());
++ QString houseNumber = addr.value("HouseNumber").toString();
++ QString street = addr.value("Street").toString();
++ address.setStreet(houseNumber.isEmpty() ? street : QString("%1 %2").arg(houseNumber, street));
++ address.setPostalCode(addr.value("PostalCode").toString());
++ QString label = addr.value("Label").toString().trimmed();
++ if (!label.isEmpty()) {
++ address.setText(label);
++ }
++ const QJsonArray additionalData = addr.value("AdditionalData").toArray();
++ for (const QJsonValueConstRef adv : additionalData) {
++ if (adv.isObject()) {
++ const QJsonObject &ado(adv.toObject());
++ if (ado.value("key").toString() == QLatin1String("CountryName")) {
++ address.setCountry(ado.value("value").toString());
++ }
++ }
++ }
++
++ QGeoRectangle boundingBox;
++ QJsonObject mapView = obj.value("MapView").toObject();
++ QJsonObject bottomRight = mapView.value("BottomRight").toObject();
++ QJsonObject topLeft = mapView.value("TopLeft").toObject();
++ boundingBox.setBottomRight(QGeoCoordinate(bottomRight.value("Latitude").toDouble(), bottomRight.value("Longitude").toDouble()));
++ boundingBox.setTopLeft(QGeoCoordinate(topLeft.value("Latitude").toDouble(), topLeft.value("Longitude").toDouble()));
++
++ loc->setAddress(address);
++ loc->setCoordinate(coordinate);
++ loc->setBoundingShape(boundingBox);
++
++ return true;
++ }
++
++ void parseDocument(const QJsonDocument &doc, const QGeoShape &bounds, QList<QGeoLocation> *locs)
++ {
++ QJsonArray view = doc.object().value("Response").toObject().value("View").toArray();
++ for (const QJsonValueRef viewElement : view) {
++ QJsonArray result = viewElement.toObject().value("Result").toArray();
++ for (const QJsonValueRef resultElement : result) {
++ QGeoLocation location;
++ if (parseLocation(resultElement.toObject().value("Location").toObject(), bounds, &location)) {
++ locs->append(location);
++ }
++ }
++ }
++ }
++
++ } // namespace
++
++ void QGeoCodeJsonParser::setBounds(const QGeoShape &bounds)
++ {
++ m_bounds = bounds;
++ }
++
++ void QGeoCodeJsonParser::parse(const QByteArray &data)
++ {
++ m_data = data;
++ QThreadPool::globalInstance()->start(this);
++ }
++
++ void QGeoCodeJsonParser::run()
++ {
++ // parse the document.
++ QJsonParseError perror;
++ m_document = QJsonDocument::fromJson(m_data, &perror);
++ if (perror.error != QJsonParseError::NoError) {
++ m_errorString = perror.errorString();
++ } else {
++ // ensure that the response is valid and contains the information we need.
++ if (checkDocument(m_document, &m_errorString)) {
++ // extract the location results from the response.
++ parseDocument(m_document, m_bounds, &m_results);
++ emit results(m_results);
++ return;
++ }
++ }
++
++ emit errorOccurred(m_errorString);
++ }
++
++ QT_END_NAMESPACE