/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtLocation module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL3$ ** 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 http://www.qt.io/terms-conditions. For further ** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qgeocodejsonparser.h" #include #include #include #include #include #include #include #include #include #include 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; } QJsonArray view = rit.value().toArray(); Q_FOREACH (const QJsonValue &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; } QJsonArray result = voit.value().toArray(); Q_FOREACH (const QJsonValue &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); } QJsonArray additionalData = addr.value("AdditionalData").toArray(); Q_FOREACH (const QJsonValue &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->setBoundingBox(boundingBox); return true; } void parseDocument(const QJsonDocument &doc, const QGeoShape &bounds, QList *locs) { QJsonArray view = doc.object().value("Response").toObject().value("View").toArray(); Q_FOREACH (const QJsonValue &viewElement, view) { QJsonArray result = viewElement.toObject().value("Result").toArray(); Q_FOREACH (const QJsonValue &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 error(m_errorString); } QT_END_NAMESPACE