diff options
Diffstat (limited to 'src/plugins')
22 files changed, 977 insertions, 736 deletions
diff --git a/src/plugins/geoservices/esri/geotiledmappingmanagerengine_esri.cpp b/src/plugins/geoservices/esri/geotiledmappingmanagerengine_esri.cpp index 396d0f57..abcb3779 100644 --- a/src/plugins/geoservices/esri/geotiledmappingmanagerengine_esri.cpp +++ b/src/plugins/geoservices/esri/geotiledmappingmanagerengine_esri.cpp @@ -127,6 +127,75 @@ GeoTiledMappingManagerEngineEsri::GeoTiledMappingManagerEngineEsri(const QVarian setTileFetcher(tileFetcher); + /* TILE CACHE */ + QString cacheDirectory; + if (parameters.contains(QStringLiteral("esri.mapping.cache.directory"))) { + cacheDirectory = parameters.value(QStringLiteral("esri.mapping.cache.directory")).toString(); + } else { + // managerName() is not yet set, we have to hardcode the plugin name below + cacheDirectory = QAbstractGeoTileCache::baseLocationCacheDirectory() + QLatin1String("esri"); + } + QGeoFileTileCache *tileCache = new QGeoFileTileCache(cacheDirectory); + + /* + * Disk cache setup -- defaults to ByteSize (old behavior) + */ + if (parameters.contains(QStringLiteral("esri.mapping.cache.disk.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("esri.mapping.cache.disk.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyDisk(QGeoFileTileCache::Unitary); + } else { + tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize); + } + if (parameters.contains(QStringLiteral("esri.mapping.cache.disk.size"))) { + bool ok = false; + int cacheSize = parameters.value(QStringLiteral("esri.mapping.cache.disk.size")).toString().toInt(&ok); + if (ok) + tileCache->setMaxDiskUsage(cacheSize); + } + + /* + * Memory cache setup -- defaults to ByteSize (old behavior) + */ + if (parameters.contains(QStringLiteral("esri.mapping.cache.memory.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("esri.mapping.cache.memory.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyMemory(QGeoFileTileCache::Unitary); + } else { + tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize); + } + if (parameters.contains(QStringLiteral("esri.mapping.cache.memory.size"))) { + bool ok = false; + int cacheSize = parameters.value(QStringLiteral("esri.mapping.cache.memory.size")).toString().toInt(&ok); + if (ok) + tileCache->setMaxMemoryUsage(cacheSize); + } + + /* + * Texture cache setup -- defaults to ByteSize (old behavior) + */ + if (parameters.contains(QStringLiteral("esri.mapping.cache.texture.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("esri.mapping.cache.texture.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyTexture(QGeoFileTileCache::Unitary); + } else { + tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize); + } + if (parameters.contains(QStringLiteral("esri.mapping.cache.texture.size"))) { + bool ok = false; + int cacheSize = parameters.value(QStringLiteral("esri.mapping.cache.texture.size")).toString().toInt(&ok); + if (ok) + tileCache->setExtraTextureUsage(cacheSize); + } + + + setTileCache(tileCache); *error = QGeoServiceProvider::NoError; errorString->clear(); } diff --git a/src/plugins/geoservices/mapbox/qgeofiletilecachemapbox.cpp b/src/plugins/geoservices/mapbox/qgeofiletilecachemapbox.cpp index 6ce41a2b..8cc3622b 100644 --- a/src/plugins/geoservices/mapbox/qgeofiletilecachemapbox.cpp +++ b/src/plugins/geoservices/mapbox/qgeofiletilecachemapbox.cpp @@ -87,10 +87,10 @@ QGeoTileSpec QGeoFileTileCacheMapbox::filenameToTileSpec(const QString &filename { QStringList parts = filename.split('.'); - if (parts.length() != 2) + if (parts.length() != 3) return QGeoTileSpec(); - QString name = parts.at(0); + QString name = parts.at(0) + parts.at(1); QStringList fields = name.split('-'); int length = fields.length(); diff --git a/src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.cpp b/src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.cpp index 3dccca4b..5404eb30 100644 --- a/src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.cpp +++ b/src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.cpp @@ -149,26 +149,46 @@ QGeoTiledMappingManagerEngineMapbox::QGeoTiledMappingManagerEngineMapbox(const Q m_cacheDirectory = QAbstractGeoTileCache::baseLocationCacheDirectory() + QLatin1String("mapbox"); } - // The Mapbox free plan allows for 6000 tiles to be stored for offline uses - // As of 2016.06.15, according to https://www.mapbox.com/help/mobile-offline/ , - // this translates into 45-315 MiB, depending on the map and the area. - // Setting a default limit of 300MiB, which can be overridden via parameters, if - // the plan allows for more data to be stored offline. - // NOTE: - // It is illegal to violate Mapbox Terms of Service, setting a limit that exceeds - // what the plan the token belongs to allows. - QGeoFileTileCache *tileCache = new QGeoFileTileCacheMapbox(mapTypes, scaleFactor, m_cacheDirectory); + /* + * Disk cache setup -- defaults to Unitary since: + * + * The Mapbox free plan allows for 6000 tiles to be stored for offline uses, + * As of 2016.06.15, according to https://www.mapbox.com/help/mobile-offline/ . + * Thus defaulting to Unitary strategy, and setting 6000 tiles as default cache disk size + */ + if (parameters.contains(QStringLiteral("mapbox.mapping.cache.disk.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("mapbox.mapping.cache.disk.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyDisk(QGeoFileTileCache::Unitary); + } else { + tileCache->setCostStrategyDisk(QGeoFileTileCache::Unitary); + } if (parameters.contains(QStringLiteral("mapbox.mapping.cache.disk.size"))) { bool ok = false; int cacheSize = parameters.value(QStringLiteral("mapbox.mapping.cache.disk.size")).toString().toInt(&ok); if (ok) tileCache->setMaxDiskUsage(cacheSize); } else { - tileCache->setMaxDiskUsage(300 * 1024 * 1024); + if (tileCache->costStrategyDisk() == QGeoFileTileCache::Unitary) + tileCache->setMaxDiskUsage(6000); // The maximum allowed with the free tier } + /* + * Memory cache setup -- defaults to ByteSize (old behavior) + */ + if (parameters.contains(QStringLiteral("mapbox.mapping.cache.memory.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("mapbox.mapping.cache.memory.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyMemory(QGeoFileTileCache::Unitary); + } else { + tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize); + } if (parameters.contains(QStringLiteral("mapbox.mapping.cache.memory.size"))) { bool ok = false; int cacheSize = parameters.value(QStringLiteral("mapbox.mapping.cache.memory.size")).toString().toInt(&ok); @@ -176,6 +196,18 @@ QGeoTiledMappingManagerEngineMapbox::QGeoTiledMappingManagerEngineMapbox(const Q tileCache->setMaxMemoryUsage(cacheSize); } + /* + * Texture cache setup -- defaults to ByteSize (old behavior) + */ + if (parameters.contains(QStringLiteral("mapbox.mapping.cache.texture.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("mapbox.mapping.cache.texture.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyTexture(QGeoFileTileCache::Unitary); + } else { + tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize); + } if (parameters.contains(QStringLiteral("mapbox.mapping.cache.texture.size"))) { bool ok = false; int cacheSize = parameters.value(QStringLiteral("mapbox.mapping.cache.texture.size")).toString().toInt(&ok); @@ -183,6 +215,7 @@ QGeoTiledMappingManagerEngineMapbox::QGeoTiledMappingManagerEngineMapbox(const Q tileCache->setExtraTextureUsage(cacheSize); } + setTileCache(tileCache); *error = QGeoServiceProvider::NoError; diff --git a/src/plugins/geoservices/nokia/nokia.pro b/src/plugins/geoservices/nokia/nokia.pro index 77499e79..c60bc7af 100644 --- a/src/plugins/geoservices/nokia/nokia.pro +++ b/src/plugins/geoservices/nokia/nokia.pro @@ -4,7 +4,7 @@ QT += location-private positioning-private network HEADERS += \ qgeocodereply_nokia.h \ - qgeocodexmlparser.h \ + qgeocodejsonparser.h \ qgeocodingmanagerengine_nokia.h \ qgeotiledmappingmanagerengine_nokia.h \ qgeotilefetcher_nokia.h \ @@ -26,7 +26,7 @@ HEADERS += \ SOURCES += \ qgeocodereply_nokia.cpp \ - qgeocodexmlparser.cpp \ + qgeocodejsonparser.cpp \ qgeocodingmanagerengine_nokia.cpp \ qgeotiledmappingmanagerengine_nokia.cpp \ qgeotilefetcher_nokia.cpp \ diff --git a/src/plugins/geoservices/nokia/qgeocodejsonparser.cpp b/src/plugins/geoservices/nokia/qgeocodejsonparser.cpp new file mode 100644 index 00000000..128f7fd2 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeocodejsonparser.cpp @@ -0,0 +1,413 @@ +/**************************************************************************** +** +** 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 <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; + } + + 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<QGeoLocation> *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 diff --git a/src/plugins/geoservices/nokia/qgeocodexmlparser.h b/src/plugins/geoservices/nokia/qgeocodejsonparser.h index 83a4b598..03251775 100644 --- a/src/plugins/geoservices/nokia/qgeocodexmlparser.h +++ b/src/plugins/geoservices/nokia/qgeocodejsonparser.h @@ -34,31 +34,26 @@ ** ****************************************************************************/ -#ifndef QGEOCODEXMLPARSER_H -#define QGEOCODEXMLPARSER_H +#ifndef QGEOCODEJSONPARSER_H +#define QGEOCODEJSONPARSER_H + +#include <QtPositioning/QGeoShape> +#include <QtPositioning/QGeoLocation> #include <QtCore/QObject> #include <QtCore/QRunnable> +#include <QtCore/QJsonDocument> +#include <QtCore/QByteArray> #include <QtCore/QString> #include <QtCore/QList> -#include <QtPositioning/QGeoShape> QT_BEGIN_NAMESPACE -class QGeoLocation; -class QGeoAddress; -class QGeoRectangle; -class QGeoCoordinate; -class QXmlStreamReader; - -class QGeoCodeXmlParser : public QObject, public QRunnable +class QGeoCodeJsonParser : public QObject, public QRunnable { Q_OBJECT public: - QGeoCodeXmlParser(); - ~QGeoCodeXmlParser(); - void setBounds(const QGeoShape &bounds); void parse(const QByteArray &data); void run(); @@ -68,17 +63,9 @@ signals: void error(const QString &errorString); private: - bool parseRootElement(); - bool parsePlace(QGeoLocation *location); - bool parseLocation(QGeoLocation *location); - bool parseAddress(QGeoAddress *address); - bool parseBoundingBox(QGeoRectangle *bounds); - bool parseCoordinate(QGeoCoordinate *coordinate, const QString &elementName); - - QGeoShape m_bounds; + QJsonDocument m_document; QByteArray m_data; - QXmlStreamReader *m_reader; - + QGeoShape m_bounds; QList<QGeoLocation> m_results; QString m_errorString; }; diff --git a/src/plugins/geoservices/nokia/qgeocodereply_nokia.cpp b/src/plugins/geoservices/nokia/qgeocodereply_nokia.cpp index 53a28652..0fb6eb2a 100644 --- a/src/plugins/geoservices/nokia/qgeocodereply_nokia.cpp +++ b/src/plugins/geoservices/nokia/qgeocodereply_nokia.cpp @@ -35,7 +35,7 @@ ****************************************************************************/ #include "qgeocodereply_nokia.h" -#include "qgeocodexmlparser.h" +#include "qgeocodejsonparser.h" #include "qgeoerror_messages.h" #include <QtPositioning/QGeoShape> @@ -45,9 +45,15 @@ Q_DECLARE_METATYPE(QList<QGeoLocation>) QT_BEGIN_NAMESPACE +// manualBoundsRequired will be true if the parser has to manually +// check if a given result lies within the viewport bounds, +// and false if the bounds information was able to be supplied +// to the server in the request (so it should not return any +// out-of-bounds results). QGeoCodeReplyNokia::QGeoCodeReplyNokia(QNetworkReply *reply, int limit, int offset, - const QGeoShape &viewport, QObject *parent) -: QGeoCodeReply(parent), m_parsing(false) + const QGeoShape &viewport, bool manualBoundsRequired, + QObject *parent) +: QGeoCodeReply(parent), m_parsing(false), m_manualBoundsRequired(manualBoundsRequired) { if (!reply) { setError(UnknownError, QStringLiteral("Null reply")); @@ -80,8 +86,9 @@ void QGeoCodeReplyNokia::networkFinished() if (reply->error() != QNetworkReply::NoError) return; - QGeoCodeXmlParser *parser = new QGeoCodeXmlParser; - parser->setBounds(viewport()); + QGeoCodeJsonParser *parser = new QGeoCodeJsonParser; // QRunnable, autoDelete = true. + if (m_manualBoundsRequired) + parser->setBounds(viewport()); connect(parser, SIGNAL(results(QList<QGeoLocation>)), this, SLOT(appendResults(QList<QGeoLocation>))); connect(parser, SIGNAL(error(QString)), this, SLOT(parseError(QString))); diff --git a/src/plugins/geoservices/nokia/qgeocodereply_nokia.h b/src/plugins/geoservices/nokia/qgeocodereply_nokia.h index 43730403..90443ebc 100644 --- a/src/plugins/geoservices/nokia/qgeocodereply_nokia.h +++ b/src/plugins/geoservices/nokia/qgeocodereply_nokia.h @@ -46,7 +46,7 @@ class QGeoCodeReplyNokia : public QGeoCodeReply { Q_OBJECT public: - QGeoCodeReplyNokia(QNetworkReply *reply, int limit, int offset, const QGeoShape &viewport, QObject *parent = 0); + QGeoCodeReplyNokia(QNetworkReply *reply, int limit, int offset, const QGeoShape &viewport, bool manualBoundsRequired, QObject *parent = 0); ~QGeoCodeReplyNokia(); private Q_SLOTS: @@ -57,6 +57,7 @@ private Q_SLOTS: private: bool m_parsing; + bool m_manualBoundsRequired; }; QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeocodexmlparser.cpp b/src/plugins/geoservices/nokia/qgeocodexmlparser.cpp deleted file mode 100644 index 89738869..00000000 --- a/src/plugins/geoservices/nokia/qgeocodexmlparser.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/**************************************************************************** -** -** 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 "qgeocodexmlparser.h" - -#include <QtCore/QXmlStreamReader> -#include <QtCore/QThreadPool> -#include <QtPositioning/QGeoLocation> -#include <QtPositioning/QGeoAddress> -#include <QtPositioning/QGeoCoordinate> -#include <QtPositioning/QGeoRectangle> - -QT_BEGIN_NAMESPACE - -QGeoCodeXmlParser::QGeoCodeXmlParser() -{ -} - -QGeoCodeXmlParser::~QGeoCodeXmlParser() -{ -} - -void QGeoCodeXmlParser::setBounds(const QGeoShape &bounds) -{ - m_bounds = bounds; -} - -void QGeoCodeXmlParser::parse(const QByteArray &data) -{ - m_data = data; - QThreadPool::globalInstance()->start(this); -} - -void QGeoCodeXmlParser::run() -{ - m_reader = new QXmlStreamReader(m_data); - - if (!parseRootElement()) - emit error(m_reader->errorString()); - else - emit results(m_results); - - delete m_reader; - m_reader = 0; -} - -bool QGeoCodeXmlParser::parseRootElement() -{ - /* - <xsd:element name="places"> - <xsd:complexType> - <xsd:sequence> - <xsd:element minOccurs="0" maxOccurs="unbounded" name="place" type="gc:Place"/> - </xsd:sequence> - <xsd:attribute name="resultCode" type="gc:ResultCodes"/> - <xsd:attribute name="resultDescription" type="xsd:string"/> - <xsd:attribute name="resultsTotal" type="xsd:nonNegativeInteger"/> - </xsd:complexType> - </xsd:element> - - <xsd:simpleType name="ResultCodes"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="OK"/> - <xsd:enumeration value="FAILED"/> - </xsd:restriction> - </xsd:simpleType> - */ - - if (m_reader->readNextStartElement()) { - if (m_reader->name() == "places") { - if (m_reader->attributes().hasAttribute("resultCode")) { - QStringRef result = m_reader->attributes().value("resultCode"); - if (result == "FAILED") { - QString resultDesc = m_reader->attributes().value("resultDescription").toString(); - if (resultDesc.isEmpty()) - resultDesc = "The attribute \"resultCode\" of the element \"places\" indicates that the request failed."; - - m_reader->raiseError(resultDesc); - - return false; - } else if (result != "OK") { - m_reader->raiseError(QString("The attribute \"resultCode\" of the element \"places\" has an unknown value (value was %1).").arg(result.toString())); - return false; - } - } - - while (m_reader->readNextStartElement()) { - if (m_reader->name() == "place") { - QGeoLocation location; - - if (!parsePlace(&location)) - return false; - - if (!m_bounds.isValid() || m_bounds.contains(location.coordinate())) - m_results.append(location); - } else { - m_reader->raiseError(QString("The element \"places\" did not expect a child element named \"%1\".").arg(m_reader->name().toString())); - return false; - } - } - } else { - m_reader->raiseError(QString("The root element is expected to have the name \"places\" (root element was named \"%1\").").arg(m_reader->name().toString())); - return false; - } - } else { - m_reader->raiseError("Expected a root element named \"places\" (no root element found)."); - return false; - } - - if (m_reader->readNextStartElement()) { - m_reader->raiseError(QString("A single root element named \"places\" was expected (second root element was named \"%1\")").arg(m_reader->name().toString())); - return false; - } - - return true; -} - - -//Note: the term Place here is semi-confusing since -// the xml 'place' is actually a location ie coord + address -bool QGeoCodeXmlParser::parsePlace(QGeoLocation *location) -{ - /* - <xsd:complexType name="Place"> - <xsd:all> - <xsd:element name="location" type="gc:Location"/> - <xsd:element minOccurs="0" name="address" type="gc:Address"/> - <xsd:element minOccurs="0" name="alternatives" type="gc:Alternatives"/> - </xsd:all> - <xsd:attribute name="title" type="xsd:string" use="required"/> - <xsd:attribute name="language" type="gc:LanguageCode" use="required"/> - </xsd:complexType> - - <xsd:simpleType name="LanguageCode"> - <xsd:restriction base="xsd:string"> - <xsd:length value="3"/> - </xsd:restriction> - </xsd:simpleType> - */ - - Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "place"); - - if (!m_reader->attributes().hasAttribute("title")) { - m_reader->raiseError("The element \"place\" did not have the required attribute \"title\"."); - return false; - } - - if (!m_reader->attributes().hasAttribute("language")) { - //m_reader->raiseError("The element \"place\" did not have the required attribute \"language\"."); - //return false; - } else { - QString lang = m_reader->attributes().value("language").toString(); - - if (lang.length() != 3) { - m_reader->raiseError(QString("The attribute \"language\" of the element \"place\" was not of length 3 (length was %1).").arg(lang.length())); - return false; - } - } - - bool parsedLocation = false; - bool parsedAddress = false; - bool parsedAlternatives = false; - - while (m_reader->readNextStartElement()) { - QString name = m_reader->name().toString(); - if (name == "location") { - if (parsedLocation) { - m_reader->raiseError("The element \"place\" has multiple child elements named \"location\" (exactly one expected)"); - return false; - } - - if (!parseLocation(location)) - return false; - - parsedLocation = true; - } else if (name == "address") { - if (parsedAddress) { - m_reader->raiseError("The element \"place\" has multiple child elements named \"address\" (at most one expected)"); - return false; - } - - QGeoAddress address; - if (!parseAddress(&address)) - return false; - else - location->setAddress(address); - - location->setAddress(address); - - parsedAddress = true; - } else if (name == "alternatives") { - if (parsedAlternatives) { - m_reader->raiseError("The element \"place\" has multiple child elements named \"alternatives\" (at most one expected)"); - return false; - } - - // skip alternatives for now - // need to work out if we have a use for them at all - // and how to store them if we get them - m_reader->skipCurrentElement(); - - parsedAlternatives = true; - } else { - m_reader->raiseError(QString("The element \"place\" did not expect a child element named \"%1\".").arg(m_reader->name().toString())); - return false; - } - } - - if (!parsedLocation) { - m_reader->raiseError("The element \"place\" has no child elements named \"location\" (exactly one expected)"); - return false; - } - - return true; -} - -//Note: the term Place here is semi-confusing since -// the xml 'location' is actually a parital location i.e coord -// as opposed to coord + address -bool QGeoCodeXmlParser::parseLocation(QGeoLocation *location) -{ - /* - <xsd:complexType name="Location"> - <xsd:all> - <xsd:element name="position" type="gc:GeoCoord"/> - <xsd:element minOccurs="0" name="boundingBox" type="gc:GeoBox"/> - </xsd:all> - </xsd:complexType> - - <xsd:complexType name="GeoBox"> - <xsd:sequence> - <xsd:element name="topLeft" type="gc:GeoCoord"/> - <xsd:element name="bottomRight" type="gc:GeoCoord"/> - </xsd:sequence> - </xsd:complexType> - */ - - Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "location"); - - bool parsedPosition = false; - bool parsedBounds = false; - - while (m_reader->readNextStartElement()) { - QString name = m_reader->name().toString(); - if (name == "position") { - if (parsedPosition) { - m_reader->raiseError("The element \"location\" has multiple child elements named \"position\" (exactly one expected)"); - return false; - } - - QGeoCoordinate coord; - if (!parseCoordinate(&coord, "position")) - return false; - - location->setCoordinate(coord); - - parsedPosition = true; - } else if (name == "boundingBox") { - if (parsedBounds) { - m_reader->raiseError("The element \"location\" has multiple child elements named \"boundingBox\" (at most one expected)"); - return false; - } - - QGeoRectangle bounds; - - if (!parseBoundingBox(&bounds)) - return false; - - location->setBoundingBox(bounds); - - parsedBounds = true; - } else { - m_reader->raiseError(QString("The element \"location\" did not expect a child element named \"%1\".").arg(m_reader->name().toString())); - return false; - } - } - - if (!parsedPosition) { - m_reader->raiseError("The element \"location\" has no child elements named \"position\" (exactly one expected)"); - return false; - } - - return true; -} - -bool QGeoCodeXmlParser::parseAddress(QGeoAddress *address) -{ - /* - <xsd:complexType name="Address"> - <xsd:sequence> - <xsd:element minOccurs="0" maxOccurs="1" name="country" type="xsd:string"/> - <xsd:element minOccurs="0" maxOccurs="1" name="countryCode" type="gc:CountryCode"/> - <xsd:element minOccurs="0" maxOccurs="1" name="state" type="xsd:string"/> - <xsd:element minOccurs="0" maxOccurs="1" name="county" type="xsd:string"/> - <xsd:element minOccurs="0" maxOccurs="1" name="city" type="xsd:string"/> - <xsd:element minOccurs="0" maxOccurs="1" name="district" type="xsd:string"/> - <xsd:element minOccurs="0" maxOccurs="1" name="thoroughfare" type="gc:Thoroughfare"/> - <xsd:element minOccurs="0" maxOccurs="1" name="postCode" type="xsd:string"/> - </xsd:sequence> - <xsd:attribute name="type" type="xsd:string"/> - </xsd:complexType> - - <xsd:simpleType name="CountryCode"> - <xsd:restriction base="xsd:string"> - <xsd:length value="3" fixed="true"/> - </xsd:restriction> - </xsd:simpleType> - - <xsd:complexType name="Thoroughfare"> - <xsd:sequence> - <xsd:element minOccurs="0" name="name" type="xsd:string"/> - <xsd:element minOccurs="0" name="number" type="xsd:string"/> - </xsd:sequence> - </xsd:complexType> - */ - - Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "address"); - - // currently ignoring the type of the address - - if (!m_reader->readNextStartElement()) - return true; - - if (m_reader->name() == "country") { - address->setCountry(m_reader->readElementText()); - if (!m_reader->readNextStartElement()) - return true; - } - - if (m_reader->name() == "countryCode") { - address->setCountryCode(m_reader->readElementText()); - - if (address->countryCode().length() != 3) { - m_reader->raiseError(QString("The text of the element \"countryCode\" was not of length 3 (length was %1).").arg(address->countryCode().length())); - return false; - } - - if (!m_reader->readNextStartElement()) - return true; - } - - if (m_reader->name() == "state") { - address->setState(m_reader->readElementText()); - if (!m_reader->readNextStartElement()) - return true; - } - - if (m_reader->name() == "county") { - address->setCounty(m_reader->readElementText()); - if (!m_reader->readNextStartElement()) - return true; - } - - if (m_reader->name() == "city") { - address->setCity(m_reader->readElementText()); - if (!m_reader->readNextStartElement()) - return true; - } - - if (m_reader->name() == "district") { - address->setDistrict(m_reader->readElementText()); - if (!m_reader->readNextStartElement()) - return true; - } - - bool inThoroughfare = false; - - if (m_reader->name() == "thoroughfare") { - inThoroughfare = m_reader->readNextStartElement(); - - if (inThoroughfare && (m_reader->name() == "name")) { - address->setStreet(m_reader->readElementText()); - if (!m_reader->readNextStartElement()) - inThoroughfare = false; - } - - if (inThoroughfare && (m_reader->name() == "number")) { - address->setStreet(m_reader->readElementText() + ' ' + address->street()); - if (!m_reader->readNextStartElement()) - inThoroughfare = false; - } - - if (inThoroughfare) { - m_reader->raiseError(QString("The element \"thoroughFare\" did not expect the child element \"%1\" at this point (unknown child element or child element out of order).").arg(m_reader->name().toString())); - return false; - } - - if (!m_reader->readNextStartElement()) - return true; - } - - if (m_reader->name() == "postCode") { - address->setPostalCode(m_reader->readElementText()); - if (!m_reader->readNextStartElement()) - return true; - } - - m_reader->raiseError(QString("The element \"address\" did not expect the child element \"%1\" at this point (unknown child element or child element out of order).").arg(m_reader->name().toString())); - return false; -} - -bool QGeoCodeXmlParser::parseBoundingBox(QGeoRectangle *bounds) -{ - /* - <xsd:complexType name="GeoBox"> - <xsd:sequence> - <xsd:element name="topLeft" type="gc:GeoCoord"/> - <xsd:element name="bottomRight" type="gc:GeoCoord"/> - </xsd:sequence> - </xsd:complexType> - */ - - Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "boundingBox"); - - if (!m_reader->readNextStartElement()) { - m_reader->raiseError("The element \"boundingBox\" was expected to have 2 child elements (0 found)"); - return false; - } - - QGeoCoordinate nw; - - if (m_reader->name() == "topLeft") { - if (!parseCoordinate(&nw, "topLeft")) - return false; - } else { - m_reader->raiseError(QString("The element \"boundingBox\" expected this child element to be named \"topLeft\" (found an element named \"%1\")").arg(m_reader->name().toString())); - return false; - } - - if (!m_reader->readNextStartElement()) { - m_reader->raiseError("The element \"boundingBox\" was expected to have 2 child elements (1 found)"); - return false; - } - - QGeoCoordinate se; - - if (m_reader->name() == "bottomRight") { - if (!parseCoordinate(&se, "bottomRight")) - return false; - } else { - m_reader->raiseError(QString("The element \"boundingBox\" expected this child element to be named \"bottomRight\" (found an element named \"%1\")").arg(m_reader->name().toString())); - return false; - } - - if (m_reader->readNextStartElement()) { - m_reader->raiseError("The element \"boundingBox\" was expected to have 2 child elements (more than 2 found)"); - return false; - } - - *bounds = QGeoRectangle(nw, se); - - return true; -} - -bool QGeoCodeXmlParser::parseCoordinate(QGeoCoordinate *coordinate, const QString &elementName) -{ - /* - <xsd:complexType name="GeoCoord"> - <xsd:sequence> - <xsd:element name="latitude" type="gc:Latitude"/> - <xsd:element name="longitude" type="gc:Longitude"/> - </xsd:sequence> - </xsd:complexType> - - <xsd:simpleType name="Latitude"> - <xsd:restriction base="xsd:float"> - <xsd:minInclusive value="-90.0"/> - <xsd:maxInclusive value="90.0"/> - </xsd:restriction> - </xsd:simpleType> - - <xsd:simpleType name="Longitude"> - <xsd:restriction base="xsd:float"> - <xsd:minInclusive value="-180.0"/> - <xsd:maxInclusive value="180.0"/> - </xsd:restriction> - </xsd:simpleType> - */ - - Q_ASSERT(m_reader->isStartElement() && m_reader->name() == elementName); - - if (!m_reader->readNextStartElement()) { - m_reader->raiseError(QString("The element \"%1\" was expected to have 2 child elements (0 found)").arg(elementName)); - return false; - } - - if (m_reader->name() == "latitude") { - bool ok = false; - QString s = m_reader->readElementText(); - double lat = s.toDouble(&ok); - - if (!ok) { - m_reader->raiseError(QString("The element \"latitude\" expected a value convertable to type float (value was \"%1\")").arg(s)); - return false; - } - - if (lat < -90.0 || 90.0 < lat) { - m_reader->raiseError(QString("The element \"latitude\" expected a value between -90.0 and 90.0 inclusive (value was %1)").arg(lat)); - return false; - } - - coordinate->setLatitude(lat); - } else { - m_reader->raiseError(QString("The element \"%1\" expected this child element to be named \"latitude\" (found an element named \"%2\")").arg(elementName).arg(m_reader->name().toString())); - } - - if (!m_reader->readNextStartElement()) { - m_reader->raiseError(QString("The element \"%1\" was expected to have 2 child elements (1 found)").arg(elementName)); - return false; - } - - if (m_reader->name() == "longitude") { - bool ok = false; - QString s = m_reader->readElementText(); - double lng = s.toDouble(&ok); - - if (!ok) { - m_reader->raiseError(QString("The element \"longitude\" expected a value convertable to type float (value was \"%1\")").arg(s)); - return false; - } - - if (lng < -180.0 || 180.0 < lng) { - m_reader->raiseError(QString("The element \"longitude\" expected a value between -180.0 and 180.0 inclusive (value was %1)").arg(lng)); - return false; - } - - coordinate->setLongitude(lng); - } else { - m_reader->raiseError(QString("The element \"%1\" expected this child element to be named \"longitude\" (found an element named \"%2\")").arg(elementName).arg(m_reader->name().toString())); - } - - if (m_reader->readNextStartElement()) { - m_reader->raiseError(QString("The element \"%1\" was expected to have 2 child elements (more than 2 found)").arg(elementName)); - return false; - } - - return true; -} - -QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.cpp b/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.cpp index 2e0eae42..b3c74a63 100644 --- a/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.cpp +++ b/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.cpp @@ -41,8 +41,12 @@ #include "qgeouriprovider.h" #include "uri_constants.h" -#include <qgeoaddress.h> -#include <qgeocoordinate.h> +#include <QtPositioning/QGeoAddress> +#include <QtPositioning/QGeoCoordinate> +#include <QtPositioning/QGeoCircle> +#include <QtPositioning/QGeoRectangle> +#include <QtPositioning/QGeoShape> + #include <QUrl> #include <QMap> #include <QStringList> @@ -56,7 +60,8 @@ QGeoCodingManagerEngineNokia::QGeoCodingManagerEngineNokia( QString *errorString) : QGeoCodingManagerEngine(parameters) , m_networkManager(networkManager) - , m_uriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.geocoding.host"), GEOCODING_HOST, GEOCODING_HOST_CN)) + , m_uriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.geocoding.host"), GEOCODING_HOST)) + , m_reverseGeocodingUriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.reversegeocoding.host"), REVERSE_GEOCODING_HOST)) { Q_ASSERT(networkManager); m_networkManager->setParent(this); @@ -81,7 +86,7 @@ QString QGeoCodingManagerEngineNokia::getAuthenticationString() const QString authenticationString; if (!m_token.isEmpty() && !m_applicationId.isEmpty()) { - authenticationString += "?token="; + authenticationString += "?app_code="; authenticationString += m_token; authenticationString += "&app_id="; @@ -95,15 +100,43 @@ QString QGeoCodingManagerEngineNokia::getAuthenticationString() const QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(const QGeoAddress &address, const QGeoShape &bounds) { - QString requestString = "http://"; + QString requestString = "https://"; requestString += m_uriProvider->getCurrentHost(); - requestString += "/geocoder/gc/2.0"; + requestString += "/6.2/geocode.json"; requestString += getAuthenticationString(); + requestString += "&gen=9"; - requestString += "&lg="; + requestString += "&language="; requestString += languageToMarc(locale().language()); + bool manualBoundsRequired = false; + if (bounds.type() == QGeoShape::RectangleType) { + QGeoRectangle rect(bounds); + if (rect.isValid()) { + requestString += "&bbox="; + requestString += trimDouble(rect.topLeft().latitude()); + requestString += ","; + requestString += trimDouble(rect.topLeft().longitude()); + requestString += ";"; + requestString += trimDouble(rect.bottomRight().latitude()); + requestString += ","; + requestString += trimDouble(rect.bottomRight().longitude()); + } + } else if (bounds.type() == QGeoShape::CircleType) { + QGeoCircle circ(bounds); + if (circ.isValid()) { + requestString += "?prox="; + requestString += trimDouble(circ.center().latitude()); + requestString += ","; + requestString += trimDouble(circ.center().longitude()); + requestString += ","; + requestString += trimDouble(circ.radius()); + } + } else { + manualBoundsRequired = true; + } + if (address.country().isEmpty()) { QStringList parts; @@ -119,8 +152,8 @@ QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(const QGeoAddress &address, if (!address.street().isEmpty()) parts << address.street(); - requestString += "&obloc="; - requestString += parts.join(" "); + requestString += "&searchtext="; + requestString += parts.join("+").replace(' ', '+'); } else { requestString += "&country="; requestString += address.country(); @@ -136,7 +169,7 @@ QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(const QGeoAddress &address, } if (!address.postalCode().isEmpty()) { - requestString += "&zip="; + requestString += "&postalcode="; requestString += address.postalCode(); } @@ -146,39 +179,7 @@ QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(const QGeoAddress &address, } } - - // TODO? - // street number has been removed from QGeoAddress - // do we need to try to split it out from QGeoAddress::street - // in order to geocode properly - - // Old code: -// if (!address.streetNumber().isEmpty()) { -// requestString += "&number="; -// requestString += address.streetNumber(); -// } - - return geocode(requestString, bounds); -} - -QGeoCodeReply *QGeoCodingManagerEngineNokia::reverseGeocode(const QGeoCoordinate &coordinate, - const QGeoShape &bounds) -{ - QString requestString = "http://"; - requestString += m_uriProvider->getCurrentHost(); - requestString += "/geocoder/rgc/2.0"; - - requestString += getAuthenticationString(); - - requestString += "&long="; - requestString += trimDouble(coordinate.longitude()); - requestString += "&lat="; - requestString += trimDouble(coordinate.latitude()); - - requestString += "&lg="; - requestString += languageToMarc(locale().language()); - - return geocode(requestString, bounds); + return geocode(requestString, bounds, manualBoundsRequired); } QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(const QString &address, @@ -186,52 +187,119 @@ QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(const QString &address, int offset, const QGeoShape &bounds) { - QString requestString = "http://"; + QString requestString = "https://"; requestString += m_uriProvider->getCurrentHost(); - requestString += "/geocoder/gc/2.0"; + requestString += "/6.2/geocode.json"; requestString += getAuthenticationString(); + requestString += "&gen=9"; - requestString += "&lg="; + requestString += "&language="; requestString += languageToMarc(locale().language()); - requestString += "&obloc="; - requestString += address; + requestString += "&searchtext="; + requestString += QString(address).replace(' ', '+'); if (limit > 0) { - requestString += "&total="; + requestString += "&maxresults="; requestString += QString::number(limit); } - if (offset > 0) { - requestString += "&offset="; - requestString += QString::number(offset); + // We cannot do this precisely, since HERE doesn't allow + // precise result-set offset to be supplied; instead, it + // returns "pages" of results at a time. + // So, we tell HERE which page of results we want, and the + // client has to filter out duplicates if they changed + // the limit param since the last call. + requestString += "&pageinformation="; + requestString += QString::number(offset/limit); } - return geocode(requestString, bounds, limit, offset); + bool manualBoundsRequired = false; + if (bounds.type() == QGeoShape::RectangleType) { + QGeoRectangle rect(bounds); + if (rect.isValid()) { + requestString += "&bbox="; + requestString += trimDouble(rect.topLeft().latitude()); + requestString += ","; + requestString += trimDouble(rect.topLeft().longitude()); + requestString += ";"; + requestString += trimDouble(rect.bottomRight().latitude()); + requestString += ","; + requestString += trimDouble(rect.bottomRight().longitude()); + } + } else if (bounds.type() == QGeoShape::CircleType) { + QGeoCircle circ(bounds); + if (circ.isValid()) { + requestString += "?prox="; + requestString += trimDouble(circ.center().latitude()); + requestString += ","; + requestString += trimDouble(circ.center().longitude()); + requestString += ","; + requestString += trimDouble(circ.radius()); + } + } else { + manualBoundsRequired = true; + } + + return geocode(requestString, bounds, manualBoundsRequired, limit, offset); } QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(QString requestString, const QGeoShape &bounds, + bool manualBoundsRequired, int limit, int offset) { - QNetworkReply *networkReply = m_networkManager->get(QNetworkRequest(QUrl(requestString))); - QGeoCodeReplyNokia *reply = new QGeoCodeReplyNokia(networkReply, limit, offset, bounds, this); + QGeoCodeReplyNokia *reply = new QGeoCodeReplyNokia( + m_networkManager->get(QNetworkRequest(QUrl(requestString))), + limit, offset, bounds, manualBoundsRequired, this); - connect(reply, - SIGNAL(finished()), - this, - SLOT(placesFinished())); + connect(reply, &QGeoCodeReplyNokia::finished, + this, &QGeoCodingManagerEngineNokia::placesFinished); - connect(reply, - SIGNAL(error(QGeoCodeReply::Error,QString)), - this, - SLOT(placesError(QGeoCodeReply::Error,QString))); + connect(reply, static_cast<void (QGeoCodeReply::*)(QGeoCodeReply::Error, const QString &)>(&QGeoCodeReplyNokia::error), + this, &QGeoCodingManagerEngineNokia::placesError); return reply; } +QGeoCodeReply *QGeoCodingManagerEngineNokia::reverseGeocode(const QGeoCoordinate &coordinate, + const QGeoShape &bounds) +{ + QString requestString = "https://"; + requestString += m_reverseGeocodingUriProvider->getCurrentHost(); + requestString += "/6.2/reversegeocode.json"; + + requestString += getAuthenticationString(); + requestString += "&gen=9"; + + requestString += "&mode=retrieveAddresses"; + + requestString += "&prox="; + requestString += trimDouble(coordinate.latitude()); + requestString += ","; + requestString += trimDouble(coordinate.longitude()); + + bool manualBoundsRequired = false; + if (bounds.type() == QGeoShape::CircleType) { + QGeoCircle circ(bounds); + if (circ.isValid() && circ.center() == coordinate) { + requestString += ","; + requestString += trimDouble(circ.radius()); + } else { + manualBoundsRequired = true; + } + } else { + manualBoundsRequired = true; + } + + requestString += "&language="; + requestString += languageToMarc(locale().language()); + + return geocode(requestString, bounds, manualBoundsRequired); +} + QString QGeoCodingManagerEngineNokia::trimDouble(double degree, int decimalDigits) { QString sDegree = QString::number(degree, 'g', decimalDigits); diff --git a/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.h b/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.h index baa91907..9e1564aa 100644 --- a/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.h +++ b/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.h @@ -75,14 +75,15 @@ private Q_SLOTS: private: static QString trimDouble(double degree, int decimalDigits = 10); - QGeoCodeReply *geocode(QString requestString, const QGeoShape &bounds, int limit = -1, int offset = 0); + QGeoCodeReply *geocode(QString requestString, const QGeoShape &bounds, bool manualBoundsRequired = true, int limit = -1, int offset = 0); QString languageToMarc(QLocale::Language language); QString getAuthenticationString() const; QGeoNetworkAccessManager *m_networkManager; + QGeoUriProvider *m_uriProvider; + QGeoUriProvider *m_reverseGeocodingUriProvider; QString m_token; QString m_applicationId; - QGeoUriProvider *m_uriProvider; }; QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeotiledmap_nokia.cpp b/src/plugins/geoservices/nokia/qgeotiledmap_nokia.cpp index d83ad0f9..5179fff4 100644 --- a/src/plugins/geoservices/nokia/qgeotiledmap_nokia.cpp +++ b/src/plugins/geoservices/nokia/qgeotiledmap_nokia.cpp @@ -72,13 +72,13 @@ void QGeoTiledMapNokia::evaluateCopyrights(const QSet<QGeoTileSpec> &visibleTile const QString copyrightsString = m_engine->evaluateCopyrightsText(activeMapType(), cameraData().zoomLevel(), visibleTiles); - if (width() > 0 && height() > 0 && ((copyrightsString.isNull() && m_copyrightsSlab.isNull()) || copyrightsString != m_lastCopyrightsString)) { + if (viewportWidth() > 0 && viewportHeight() > 0 && ((copyrightsString.isNull() && m_copyrightsSlab.isNull()) || copyrightsString != m_lastCopyrightsString)) { QFont font("Sans Serif"); font.setPixelSize(fontSize); font.setStyleHint(QFont::SansSerif); font.setWeight(QFont::Bold); - QRect textBounds = QFontMetrics(font).boundingRect(0, 0, width(), height(), Qt::AlignBottom | Qt::AlignLeft | Qt::TextWordWrap, copyrightsString); + QRect textBounds = QFontMetrics(font).boundingRect(0, 0, viewportWidth(), viewportHeight(), Qt::AlignBottom | Qt::AlignLeft | Qt::TextWordWrap, copyrightsString); m_copyrightsSlab = QImage(m_logo.width() + textBounds.width() + spaceToLogo + blurRate * 2, qMax(m_logo.height(), textBounds.height() + blurRate * 2), diff --git a/src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.cpp b/src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.cpp index 8fcaba6a..6548aa2b 100644 --- a/src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.cpp +++ b/src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.cpp @@ -105,9 +105,9 @@ QGeoTiledMappingManagerEngineNokia::QGeoTiledMappingManagerEngineNokia( QGeoTileFetcherNokia *fetcher = new QGeoTileFetcherNokia(parameters, networkManager, this, tileSize(), ppi); setTileFetcher(fetcher); + /* TILE CACHE */ // TODO: do this in a plugin-neutral way so that other tiled map plugins // don't need this boilerplate or hardcode plugin name - if (parameters.contains(QStringLiteral("here.mapping.cache.directory"))) { m_cacheDirectory = parameters.value(QStringLiteral("here.mapping.cache.directory")).toString(); } else { @@ -116,8 +116,19 @@ QGeoTiledMappingManagerEngineNokia::QGeoTiledMappingManagerEngineNokia( } QGeoFileTileCache *tileCache = new QGeoFileTileCacheNokia(ppi, m_cacheDirectory); - setTileCache(tileCache); + /* + * Disk cache setup -- defaults to ByteSize (old behavior) + */ + if (parameters.contains(QStringLiteral("here.mapping.cache.disk.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("here.mapping.cache.disk.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyDisk(QGeoFileTileCache::Unitary); + } else { + tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize); + } if (parameters.contains(QStringLiteral("here.mapping.cache.disk.size"))) { bool ok = false; int cacheSize = parameters.value(QStringLiteral("here.mapping.cache.disk.size")).toString().toInt(&ok); @@ -125,6 +136,18 @@ QGeoTiledMappingManagerEngineNokia::QGeoTiledMappingManagerEngineNokia( tileCache->setMaxDiskUsage(cacheSize); } + /* + * Memory cache setup -- defaults to ByteSize (old behavior) + */ + if (parameters.contains(QStringLiteral("here.mapping.cache.memory.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("here.mapping.cache.memory.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyMemory(QGeoFileTileCache::Unitary); + } else { + tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize); + } if (parameters.contains(QStringLiteral("here.mapping.cache.memory.size"))) { bool ok = false; int cacheSize = parameters.value(QStringLiteral("here.mapping.cache.memory.size")).toString().toInt(&ok); @@ -132,6 +155,18 @@ QGeoTiledMappingManagerEngineNokia::QGeoTiledMappingManagerEngineNokia( tileCache->setMaxMemoryUsage(cacheSize); } + /* + * Texture cache setup -- defaults to ByteSize (old behavior) + */ + if (parameters.contains(QStringLiteral("here.mapping.cache.texture.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("here.mapping.cache.texture.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyTexture(QGeoFileTileCache::Unitary); + } else { + tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize); + } if (parameters.contains(QStringLiteral("here.mapping.cache.texture.size"))) { bool ok = false; int cacheSize = parameters.value(QStringLiteral("here.mapping.cache.texture.size")).toString().toInt(&ok); @@ -139,6 +174,7 @@ QGeoTiledMappingManagerEngineNokia::QGeoTiledMappingManagerEngineNokia( tileCache->setExtraTextureUsage(cacheSize); } + setTileCache(tileCache); populateMapSchemes(); loadMapVersion(); QMetaObject::invokeMethod(fetcher, "fetchCopyrightsData", Qt::QueuedConnection); diff --git a/src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.cpp b/src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.cpp index 6dcb28da..67b7e70f 100644 --- a/src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.cpp +++ b/src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.cpp @@ -200,7 +200,7 @@ QPlaceManagerEngineNokiaV2::QPlaceManagerEngineNokiaV2( QString *errorString) : QPlaceManagerEngine(parameters) , m_manager(networkManager) - , m_uriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.places.host"), PLACES_HOST, PLACES_HOST_CN)) + , m_uriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.places.host"), PLACES_HOST)) { Q_ASSERT(networkManager); m_manager->setParent(this); diff --git a/src/plugins/geoservices/nokia/uri_constants.cpp b/src/plugins/geoservices/nokia/uri_constants.cpp index 8a075328..8db47beb 100644 --- a/src/plugins/geoservices/nokia/uri_constants.cpp +++ b/src/plugins/geoservices/nokia/uri_constants.cpp @@ -38,10 +38,9 @@ QT_BEGIN_NAMESPACE const QString ROUTING_HOST = QLatin1String("route.api.here.com"); -const QString GEOCODING_HOST = QLatin1String("loc.desktop.maps.svc.ovi.com"); -const QString GEOCODING_HOST_CN = QLatin1String("pr.geo.maps.svc.nokia.com.cn"); +const QString GEOCODING_HOST = QLatin1String("geocoder.api.here.com"); +const QString REVERSE_GEOCODING_HOST = QLatin1String("reverse.geocoder.api.here.com"); const QString PLACES_HOST = QLatin1String("places.api.here.com"); -const QString PLACES_HOST_CN = QLatin1String("places.nlp.nokia.com.cn"); const QString MAP_TILES_HOST = QLatin1String("1-4.base.maps.api.here.com"); const QString MAP_TILES_HOST_AERIAL = QLatin1String("1-4.aerial.maps.api.here.com"); diff --git a/src/plugins/geoservices/nokia/uri_constants.h b/src/plugins/geoservices/nokia/uri_constants.h index 151a4aa4..b2133fe3 100644 --- a/src/plugins/geoservices/nokia/uri_constants.h +++ b/src/plugins/geoservices/nokia/uri_constants.h @@ -43,9 +43,8 @@ QT_BEGIN_NAMESPACE extern const QString ROUTING_HOST; extern const QString GEOCODING_HOST; -extern const QString GEOCODING_HOST_CN; +extern const QString REVERSE_GEOCODING_HOST; extern const QString PLACES_HOST; -extern const QString PLACES_HOST_CN; extern const QString MAP_TILES_HOST; extern const QString MAP_TILES_HOST_AERIAL; diff --git a/src/plugins/geoservices/osm/qgeofiletilecacheosm.cpp b/src/plugins/geoservices/osm/qgeofiletilecacheosm.cpp index 5028a82b..a563cced 100644 --- a/src/plugins/geoservices/osm/qgeofiletilecacheosm.cpp +++ b/src/plugins/geoservices/osm/qgeofiletilecacheosm.cpp @@ -45,13 +45,16 @@ QT_BEGIN_NAMESPACE -QGeoFileTileCacheOsm::QGeoFileTileCacheOsm(const QVector<QGeoTileProviderOsm *> &providers, const QString &offlineDirectory, const QString &directory, QObject *parent) -: QGeoFileTileCache(directory, parent), m_offlineDirectory(offlineDirectory), - m_requestCancel(0), m_providers(providers) +QGeoFileTileCacheOsm::QGeoFileTileCacheOsm(const QVector<QGeoTileProviderOsm *> &providers, + const QString &offlineDirectory, + const QString &directory, + QObject *parent) +: QGeoFileTileCache(directory, parent), m_offlineDirectory(offlineDirectory), m_requestCancel(0), m_providers(providers) { m_highDpi.resize(providers.size()); for (int i = 0; i < providers.size(); i++) { m_highDpi[i] = providers[i]->isHighDpi(); + m_mapIdFutures[providers[i]->mapType().mapId()].isFinished(); // To construct a default future for this mapId connect(providers[i], &QGeoTileProviderOsm::resolutionFinished, this, &QGeoFileTileCacheOsm::onProviderResolutionFinished); connect(providers[i], &QGeoTileProviderOsm::resolutionError, this, &QGeoFileTileCacheOsm::onProviderResolutionFinished); } @@ -61,6 +64,8 @@ QGeoFileTileCacheOsm::~QGeoFileTileCacheOsm() { m_requestCancel = 1; m_future.waitForFinished(); + for (const QGeoTileProviderOsm *p : m_providers) + m_mapIdFutures[p->mapType().mapId()].waitForFinished(); } QSharedPointer<QGeoTileTexture> QGeoFileTileCacheOsm::get(const QGeoTileSpec &spec) @@ -75,9 +80,10 @@ QSharedPointer<QGeoTileTexture> QGeoFileTileCacheOsm::get(const QGeoTileSpec &sp void QGeoFileTileCacheOsm::onProviderResolutionFinished(const QGeoTileProviderOsm *provider) { + clearObsoleteTiles(provider); Q_UNUSED(provider) for (int i = 0; i < m_providers.size(); i++) { - if (m_providers[i]->isHighDpi() != m_highDpi[i]) { + if (m_providers[i]->isHighDpi() != m_highDpi[i]) { // e.g., HiDpi was requested but only LoDpi is available int mapId = m_providers[i]->mapType().mapId(); m_highDpi[i] = m_providers[i]->isHighDpi(); @@ -85,17 +91,60 @@ void QGeoFileTileCacheOsm::onProviderResolutionFinished(const QGeoTileProviderOs dropTiles(mapId); loadTiles(mapId); + // reload offline registry for mapId i + m_mapIdFutures[mapId] = QtConcurrent::run(this, &QGeoFileTileCacheOsm::initOfflineRegistry, mapId); + // send signal to clear scene in all maps created through this provider that use the reloaded tiles emit mapDataUpdated(mapId); } } } +// On resolution error the provider is removed ONLY if there is no enabled hardcoded fallback. +// Hardcoded fallbacks also have a timestamp, that can get updated with Qt releases. +void QGeoFileTileCacheOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error) +{ + Q_UNUSED(error) + clearObsoleteTiles(provider); // this still removes tiles who happen to be older than qgeotileproviderosm.cpp defaultTs +} + void QGeoFileTileCacheOsm::init() { + if (directory_.isEmpty()) + directory_ = baseLocationCacheDirectory(); + QDir::root().mkpath(directory_); + + // find max mapId + int max = 0; + for (auto p: m_providers) + if (p->mapType().mapId() > max) + max = p->mapType().mapId(); + // Create a mapId to maxTimestamp LUT.. + m_maxMapIdTimestamps.resize(max+1); // initializes to invalid QDateTime + + // .. by finding the newest file in each tileset (tileset = mapId). + QDir dir(directory_); + QStringList formats; + formats << QLatin1String("*.*"); + QStringList files = dir.entryList(formats, QDir::Files); + + for (const QString &tileFileName : files) { + QGeoTileSpec spec = filenameToTileSpec(tileFileName); + if (spec.zoom() == -1) + continue; + QFileInfo fi(dir.filePath(tileFileName)); + if (fi.lastModified() > m_maxMapIdTimestamps[spec.mapId()]) + m_maxMapIdTimestamps[spec.mapId()] = fi.lastModified(); + } + + // Base class ::init() QGeoFileTileCache::init(); + + for (QGeoTileProviderOsm * p: m_providers) + clearObsoleteTiles(p); + if (!m_offlineDirectory.isEmpty()) - m_future = QtConcurrent::run(this, &QGeoFileTileCacheOsm::initOfflineRegistry); + m_future = QtConcurrent::run(this, &QGeoFileTileCacheOsm::initOfflineRegistry, -1); } QSharedPointer<QGeoTileTexture> QGeoFileTileCacheOsm::getFromOfflineStorage(const QGeoTileSpec &spec) @@ -159,7 +208,7 @@ void QGeoFileTileCacheOsm::loadTiles(int mapId) } } -void QGeoFileTileCacheOsm::initOfflineRegistry() +void QGeoFileTileCacheOsm::initOfflineRegistry(int mapId) { // Dealing with duplicates: picking the newest QMap<QString, QPair<QString, QDateTime> > fileDates; // key is filename, value is <filepath, lastmodified> @@ -173,11 +222,33 @@ void QGeoFileTileCacheOsm::initOfflineRegistry() return; } + // Clear the content of the index. Entirely (at startup), or selectively (when a provider resolution changes the highDpi status). + if (mapId < 0) { + storageLock.lock(); + m_tilespecToOfflineFilepath.clear(); + storageLock.unlock(); + } else { + QList<QGeoTileSpec> toRemove; + for (auto i = m_tilespecToOfflineFilepath.constBegin(); i != m_tilespecToOfflineFilepath.constEnd(); ++i) { + if (i.key().mapId() == mapId) + toRemove.append(i.key()); + } + storageLock.lock(); + for (const auto &i : toRemove) + m_tilespecToOfflineFilepath.remove(i); + storageLock.unlock(); + } + if (m_requestCancel) + return; + + // Fill the index entirely or selectively int count = 0; - for (auto i= fileDates.begin(); i != fileDates.end(); ++i) { + for (auto i= fileDates.constBegin(); i != fileDates.constEnd(); ++i) { QGeoTileSpec spec = filenameToTileSpec(i.key()); if (spec.zoom() == -1) continue; + if (mapId >= 0 && spec.mapId() != mapId) // if mapId != -1, pick up only those files with that mapId. + continue; count++; storageLock.lock(); m_tilespecToOfflineFilepath[spec] = i.value().first; @@ -185,7 +256,7 @@ void QGeoFileTileCacheOsm::initOfflineRegistry() if (m_requestCancel) return; } - qWarning() << "OSM Offline tiles: "<<count; + //qInfo() << "OSM plugin has found and is using "<< count <<" offline tiles"; } QString QGeoFileTileCacheOsm::tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, const QString &directory) const @@ -264,4 +335,26 @@ QGeoTileSpec QGeoFileTileCacheOsm::filenameToTileSpec(const QString &filename) c numbers.at(4)); } +void QGeoFileTileCacheOsm::clearObsoleteTiles(const QGeoTileProviderOsm *p) +{ + // process initialized providers, and connect the others + + if (p->isResolved()) { + if (m_maxMapIdTimestamps[p->mapType().mapId()].isValid() && // there are tiles in the cache + p->timestamp() > m_maxMapIdTimestamps[p->mapType().mapId()]) { // and they are older than the provider + qInfo() << "provider for " << p->mapType().name() << " timestamp: " << p->timestamp() + << " -- data last modified: " << m_maxMapIdTimestamps[p->mapType().mapId()] << ". Clearing."; + clearMapId(p->mapType().mapId()); + m_maxMapIdTimestamps[p->mapType().mapId()] = p->timestamp(); // don't do it again. + } + } else { + connect(p, &QGeoTileProviderOsm::resolutionFinished, + this, &QGeoFileTileCacheOsm::onProviderResolutionFinished); +#if 0 // If resolution fails, better not try to remove anything. Beside, on error, resolutionFinished is also emitted. + connect(p, &QGeoTileProviderOsm::resolutionError, + this, &QGeoFileTileCacheOsm::onProviderResolutionError); +#endif + } +} + QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeofiletilecacheosm.h b/src/plugins/geoservices/osm/qgeofiletilecacheosm.h index 52d57747..d26cad4a 100644 --- a/src/plugins/geoservices/osm/qgeofiletilecacheosm.h +++ b/src/plugins/geoservices/osm/qgeofiletilecacheosm.h @@ -49,7 +49,10 @@ class QGeoFileTileCacheOsm : public QGeoFileTileCache { Q_OBJECT public: - QGeoFileTileCacheOsm(const QVector<QGeoTileProviderOsm *> &providers, const QString &offlineDirectory = QString(), const QString &directory = QString(), QObject *parent = 0); + QGeoFileTileCacheOsm(const QVector<QGeoTileProviderOsm *> &providers, + const QString &offlineDirectory = QString(), + const QString &directory = QString(), + QObject *parent = 0); ~QGeoFileTileCacheOsm(); QSharedPointer<QGeoTileTexture> get(const QGeoTileSpec &spec) Q_DECL_OVERRIDE; @@ -59,6 +62,7 @@ Q_SIGNALS: protected Q_SLOTS: void onProviderResolutionFinished(const QGeoTileProviderOsm *provider); + void onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error); protected: void init() Q_DECL_OVERRIDE; @@ -68,15 +72,18 @@ protected: void dropTiles(int mapId); void loadTiles(int mapId); - void initOfflineRegistry(); + void initOfflineRegistry(int mapId = -1); + void clearObsoleteTiles(const QGeoTileProviderOsm *p); QString m_offlineDirectory; QHash<QGeoTileSpec, QString> m_tilespecToOfflineFilepath; QAtomicInt m_requestCancel; QFuture<void> m_future; + QMap<int, QFuture<void>> m_mapIdFutures; QMutex storageLock; QVector<QGeoTileProviderOsm *> m_providers; QVector<bool> m_highDpi; + QVector<QDateTime> m_maxMapIdTimestamps; }; QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp b/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp index f8e18de9..ff79c261 100644 --- a/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp +++ b/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp @@ -70,7 +70,10 @@ QGeoTiledMappingManagerEngineOsm::QGeoTiledMappingManagerEngineOsm(const QVarian customAddress = QStringLiteral("http://") + customAddress; if (customAddress[customAddress.length()-1] != QLatin1Char('/')) customAddress += QLatin1Char('/'); - domain = customAddress; + if (QUrl(customAddress).isValid()) + domain = customAddress; + else + qWarning() << "Invalid custom providers repository address: " << customAddress; } bool highdpi = false; @@ -105,37 +108,50 @@ QGeoTiledMappingManagerEngineOsm::QGeoTiledMappingManagerEngineOsm(const QVarian providers_terrain.push_back(new TileProvider(domain + "terrain")); providers_hiking.push_back(new TileProvider(domain + "hiking")); // Backups + const QDateTime defaultTs = QDateTime::fromString(QStringLiteral("2016-06-01T00:00:00"), Qt::ISODate); providers_street.push_back( new TileProvider(QStringLiteral("http://c.tile.openstreetmap.org/%z/%x/%y.png"), QStringLiteral("png"), QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap.org</a>"), QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"))); - // No available satellite backup + providers_street.back()->setTimestamp(defaultTs); + + // No available open access satellite backup with satisfactory level of details at the present. + providers_cycle.push_back( new TileProvider(QStringLiteral("http://c.tile.opencyclemap.org/cycle/%z/%x/%y.png"), QStringLiteral("png"), QStringLiteral("<a href='http://www.thunderforest.com/'>Thunderforest</a>"), QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"))); + providers_cycle.back()->setTimestamp(defaultTs); + providers_transit.push_back( new TileProvider(QStringLiteral("http://c.tile2.opencyclemap.org/transport/%z/%x/%y.png"), QStringLiteral("png"), QStringLiteral("<a href='http://www.thunderforest.com/'>Thunderforest</a>"), QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"))); + providers_transit.back()->setTimestamp(defaultTs); + providers_nighttransit.push_back( new TileProvider(QStringLiteral("http://a.tile.thunderforest.com/transport-dark/%z/%x/%y.png"), QStringLiteral("png"), QStringLiteral("<a href='http://www.thunderforest.com/'>Thunderforest</a>"), QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors")) ); + providers_nighttransit.back()->setTimestamp(defaultTs); + providers_terrain.push_back( new TileProvider(QStringLiteral("http://a.tile.thunderforest.com/landscape/%z/%x/%y.png"), QStringLiteral("png"), QStringLiteral("<a href='http://www.thunderforest.com/'>Thunderforest</a>"), QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"))); + providers_terrain.back()->setTimestamp(defaultTs); + providers_hiking.push_back( new TileProvider(QStringLiteral("http://a.tile.thunderforest.com/outdoors/%z/%x/%y.png"), QStringLiteral("png"), QStringLiteral("<a href='http://www.thunderforest.com/'>Thunderforest</a>"), QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"))); + providers_hiking.back()->setTimestamp(defaultTs); /* QGeoTileProviderOsms setup */ @@ -208,15 +224,8 @@ QGeoTiledMappingManagerEngineOsm::QGeoTiledMappingManagerEngineOsm(const QVarian } updateMapTypes(); - QGeoTileFetcherOsm *tileFetcher = new QGeoTileFetcherOsm(m_providers, nm, this); - if (parameters.contains(QStringLiteral("osm.useragent"))) { - const QByteArray ua = parameters.value(QStringLiteral("osm.useragent")).toString().toLatin1(); - tileFetcher->setUserAgent(ua); - } - - - setTileFetcher(tileFetcher); + /* TILE CACHE */ if (parameters.contains(QStringLiteral("osm.mapping.cache.directory"))) { m_cacheDirectory = parameters.value(QStringLiteral("osm.mapping.cache.directory")).toString(); } else { @@ -225,19 +234,38 @@ QGeoTiledMappingManagerEngineOsm::QGeoTiledMappingManagerEngineOsm(const QVarian } if (parameters.contains(QStringLiteral("osm.mapping.offline.directory"))) m_offlineDirectory = parameters.value(QStringLiteral("osm.mapping.offline.directory")).toString(); - - QAbstractGeoTileCache *tileCache = new QGeoFileTileCacheOsm(m_providers, m_offlineDirectory, m_cacheDirectory); - - // 50mb of disk cache by default to minimize n. of accesses to public OSM servers - tileCache->setMaxDiskUsage(50 * 1024 * 1024); - + QGeoFileTileCacheOsm *tileCache = new QGeoFileTileCacheOsm(m_providers, m_offlineDirectory, m_cacheDirectory); + + /* + * Disk cache setup -- defaults to ByteSize (old behavior) + */ + if (parameters.contains(QStringLiteral("osm.mapping.cache.disk.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("osm.mapping.cache.disk.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyDisk(QGeoFileTileCache::Unitary); + } else { + tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize); + } if (parameters.contains(QStringLiteral("osm.mapping.cache.disk.size"))) { bool ok = false; int cacheSize = parameters.value(QStringLiteral("osm.mapping.cache.disk.size")).toString().toInt(&ok); if (ok) tileCache->setMaxDiskUsage(cacheSize); + } + + /* + * Memory cache setup -- defaults to ByteSize (old behavior) + */ + if (parameters.contains(QStringLiteral("osm.mapping.cache.memory.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("osm.mapping.cache.memory.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyMemory(QGeoFileTileCache::Unitary); } else { - tileCache->setMaxDiskUsage(100 * 1024 * 1024); + tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize); } if (parameters.contains(QStringLiteral("osm.mapping.cache.memory.size"))) { bool ok = false; @@ -245,14 +273,38 @@ QGeoTiledMappingManagerEngineOsm::QGeoTiledMappingManagerEngineOsm(const QVarian if (ok) tileCache->setMaxMemoryUsage(cacheSize); } + + /* + * Texture cache setup -- defaults to ByteSize (old behavior) + */ + if (parameters.contains(QStringLiteral("osm.mapping.cache.texture.cost_strategy"))) { + QString cacheStrategy = parameters.value(QStringLiteral("osm.mapping.cache.texture.cost_strategy")).toString().toLower(); + if (cacheStrategy == QLatin1String("bytesize")) + tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize); + else + tileCache->setCostStrategyTexture(QGeoFileTileCache::Unitary); + } else { + tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize); + } if (parameters.contains(QStringLiteral("osm.mapping.cache.texture.size"))) { bool ok = false; int cacheSize = parameters.value(QStringLiteral("osm.mapping.cache.texture.size")).toString().toInt(&ok); if (ok) tileCache->setExtraTextureUsage(cacheSize); } + + setTileCache(tileCache); + + /* TILE FETCHER */ + QGeoTileFetcherOsm *tileFetcher = new QGeoTileFetcherOsm(m_providers, nm, this); + if (parameters.contains(QStringLiteral("osm.useragent"))) { + const QByteArray ua = parameters.value(QStringLiteral("osm.useragent")).toString().toLatin1(); + tileFetcher->setUserAgent(ua); + } + setTileFetcher(tileFetcher); + *error = QGeoServiceProvider::NoError; errorString->clear(); } diff --git a/src/plugins/geoservices/osm/qgeotileproviderosm.cpp b/src/plugins/geoservices/osm/qgeotileproviderosm.cpp index 0d99c828..1989c44f 100644 --- a/src/plugins/geoservices/osm/qgeotileproviderosm.cpp +++ b/src/plugins/geoservices/osm/qgeotileproviderosm.cpp @@ -43,6 +43,7 @@ QT_BEGIN_NAMESPACE static const int maxValidZoom = 30; +static const QDateTime defaultTs = QDateTime::fromString(QStringLiteral("2016-06-01T00:00:00"), Qt::ISODate); QGeoTileProviderOsm::QGeoTileProviderOsm(QNetworkAccessManager *nm, const QGeoMapType &mapType, @@ -120,6 +121,13 @@ bool QGeoTileProviderOsm::isHighDpi() const return m_provider->isHighDpi(); } +const QDateTime QGeoTileProviderOsm::timestamp() const +{ + if (!m_provider) + return QDateTime(); + return m_provider->timestamp(); +} + const QGeoMapType &QGeoTileProviderOsm::mapType() const { return m_mapType; @@ -190,8 +198,10 @@ void QGeoTileProviderOsm::onResolutionError(TileProvider *provider) m_provider = p; if (!p->isValid()) { m_status = Idle; -// m_status = Resolving; -// p->resolveProvider(); +#if 0 // leaving triggering the retry to the tile fetcher, instead of constantly spinning it in here. + m_status = Resolving; + p->resolveProvider(); +#endif emit resolutionRequired(); } break; @@ -204,7 +214,9 @@ void QGeoTileProviderOsm::onResolutionError(TileProvider *provider) emit resolutionFinished(this); } else { // still not resolved. But network error is recoverable. m_status = Idle; - //m_provider->resolveProvider(); +#if 0 // leaving triggering the retry to the tile fetcher + m_provider->resolveProvider(); +#endif } } @@ -226,7 +238,7 @@ void QGeoTileProviderOsm::addProvider(TileProvider *provider) /* - QGeoTileProviderOsm::TileProvder + Class TileProvder */ static void sort2(int &a, int &b) @@ -238,13 +250,13 @@ static void sort2(int &a, int &b) } } -TileProvider::TileProvider() : m_status(Invalid), m_nm(nullptr), m_highDpi(false) +TileProvider::TileProvider() : m_status(Invalid), m_nm(nullptr), m_timestamp(defaultTs), m_highDpi(false) { } TileProvider::TileProvider(const QUrl &urlRedirector, bool highDpi) -: m_status(Idle), m_urlRedirector(urlRedirector), m_nm(nullptr), m_highDpi(highDpi) +: m_status(Idle), m_urlRedirector(urlRedirector), m_nm(nullptr), m_timestamp(defaultTs), m_highDpi(highDpi) { if (!m_urlRedirector.isValid()) m_status = Invalid; @@ -259,7 +271,7 @@ TileProvider::TileProvider(const QString &urlTemplate, int maximumZoomLevel) : m_status(Invalid), m_nm(nullptr), m_urlTemplate(urlTemplate), m_format(format), m_copyRightMap(copyRightMap), m_copyRightData(copyRightData), - m_minimumZoomLevel(minimumZoomLevel), m_maximumZoomLevel(maximumZoomLevel), m_highDpi(highDpi) + m_minimumZoomLevel(minimumZoomLevel), m_maximumZoomLevel(maximumZoomLevel), m_timestamp(defaultTs), m_highDpi(highDpi) { setupProvider(); } @@ -352,6 +364,7 @@ void TileProvider::onNetworkReplyFinished() * "StyleCopyRight" : "<copyright>", (optional) * "MinimumZoomLevel" : <minimumZoomLevel>, (optional) * "MaximumZoomLevel" : <maximumZoomLevel>, (optional) + * "Timestamp" : <timestamp>, (optional) * } * * Enabled is optional, and allows to temporarily disable a tile provider if it becomes @@ -361,27 +374,31 @@ void TileProvider::onNetworkReplyFinished() * requests to the providers, if they do not support the specific ZL. Default is 0 and 20, * respectively. * - * <server address template> is required, and is the tile url template, with %x, %y and %z as + * UrlTemplate is required, and is the tile url template, with %x, %y and %z as * placeholders for the actual parameters. * Example: * http://localhost:8080/maps/%z/%x/%y.png * - * <image format> is required, and is the format of the tile. + * ImageFormat is required, and is the format of the tile. * Examples: * "png", "jpg" * - * <MapCopyRight> is required and is the string that will be displayed in the "Map (c)" part + * MapCopyRight is required and is the string that will be displayed in the "Map (c)" part * of the on-screen copyright notice. Can be an empty string. * Example: * "<a href='http://www.mapquest.com/'>MapQuest</a>" * - * <DataCopyRight> is required and is the string that will be displayed in the "Data (c)" part + * DataCopyRight is required and is the string that will be displayed in the "Data (c)" part * of the on-screen copyright notice. Can be an empty string. * Example: * "<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors" * - * <StyleCopyRight> is optional and is the string that will be displayed in the optional "Style (c)" part + * StyleCopyRight is optional and is the string that will be displayed in the optional "Style (c)" part * of the on-screen copyright notice. + * + * Timestamp is optional, and if set will cause QtLocation to clear the content of the cache older + * than this timestamp. The purpose is to prevent mixing tiles from different providers in the cache + * upon provider change. The value must be a string in ISO 8601 format (see Qt::ISODate) */ QJsonParseError error; @@ -435,6 +452,10 @@ void TileProvider::onNetworkReplyFinished() if (maxZoom.isDouble()) m_maximumZoomLevel = qBound(0, int(maxZoom.toDouble()), maxValidZoom); + const QJsonValue ts = json.value(QLatin1String("Timestamp")); + if (ts.isString()) + m_timestamp = QDateTime::fromString(ts.toString(), Qt::ISODate); + setupProvider(); if (isValid()) { QObject::disconnect(errorEmitterConnection); @@ -553,6 +574,11 @@ int TileProvider::maximumZoomLevel() const return m_maximumZoomLevel; } +const QDateTime &TileProvider::timestamp() const +{ + return m_timestamp; +} + bool TileProvider::isHighDpi() const { return m_highDpi; @@ -563,6 +589,11 @@ void TileProvider::setStyleCopyRight(const QString ©right) m_copyRightStyle = copyright; } +void TileProvider::setTimestamp(const QDateTime ×tamp) +{ + m_timestamp = timestamp; +} + QUrl TileProvider::tileAddress(int x, int y, int z) const { if (z < m_minimumZoomLevel || z > m_maximumZoomLevel) @@ -591,7 +622,3 @@ TileProvider::Status TileProvider::status() const QT_END_NAMESPACE - - - - diff --git a/src/plugins/geoservices/osm/qgeotileproviderosm.h b/src/plugins/geoservices/osm/qgeotileproviderosm.h index cea832f0..b8647244 100644 --- a/src/plugins/geoservices/osm/qgeotileproviderosm.h +++ b/src/plugins/geoservices/osm/qgeotileproviderosm.h @@ -48,6 +48,7 @@ #include <algorithm> #include <QtCore/QJsonDocument> #include <QtCore/QJsonObject> +#include <QDateTime> QT_BEGIN_NAMESPACE @@ -90,11 +91,13 @@ public: inline QString format() const; inline int minimumZoomLevel() const; inline int maximumZoomLevel() const; + inline const QDateTime ×tamp() const; inline bool isHighDpi() const; QUrl tileAddress(int x, int y, int z) const; // Optional properties, not needed to construct a provider void setStyleCopyRight(const QString ©right); + void setTimestamp(const QDateTime ×tamp); Status m_status; QUrl m_urlRedirector; // The URL from where to fetch the URL template in case of a provider to resolve. @@ -108,6 +111,7 @@ public: QString m_urlSuffix; int m_minimumZoomLevel; int m_maximumZoomLevel; + QDateTime m_timestamp; bool m_highDpi; int paramsLUT[3]; //Lookup table to handle possibly shuffled x,y,z @@ -152,6 +156,7 @@ public: const QGeoMapType &mapType() const; bool isValid() const; bool isResolved() const; + const QDateTime timestamp() const; Q_SIGNALS: void resolutionFinished(const QGeoTileProviderOsm *provider); diff --git a/src/plugins/position/winrt/qgeopositioninfosource_winrt.cpp b/src/plugins/position/winrt/qgeopositioninfosource_winrt.cpp index a1cda7f6..a8e5e201 100644 --- a/src/plugins/position/winrt/qgeopositioninfosource_winrt.cpp +++ b/src/plugins/position/winrt/qgeopositioninfosource_winrt.cpp @@ -343,7 +343,7 @@ void QGeoPositionInfoSourceWinRT::virtualPositionUpdate() // We can only do this if we received a valid position before if (d->lastPosition.isValid()) { QGeoPositionInfo sent = d->lastPosition; - sent.setTimestamp(QDateTime::currentDateTime()); + sent.setTimestamp(sent.timestamp().addMSecs(updateInterval())); d->lastPosition = sent; emit positionUpdated(sent); } @@ -498,7 +498,24 @@ HRESULT QGeoPositionInfoSourceWinRT::onPositionChanged(IGeolocator *locator, IPo currentInfo.setAttribute(QGeoPositionInfo::Direction, value); } - currentInfo.setTimestamp(QDateTime::currentDateTime()); + DateTime dateTime; + hr = coord->get_Timestamp(&dateTime); + + if (dateTime.UniversalTime > 0) { + ULARGE_INTEGER uLarge; + uLarge.QuadPart = dateTime.UniversalTime; + FILETIME fileTime; + fileTime.dwHighDateTime = uLarge.HighPart; + fileTime.dwLowDateTime = uLarge.LowPart; + SYSTEMTIME systemTime; + if (FileTimeToSystemTime(&fileTime, &systemTime)) { + currentInfo.setTimestamp(QDateTime(QDate(systemTime.wYear, systemTime.wMonth, + systemTime.wDay), + QTime(systemTime.wHour, systemTime.wMinute, + systemTime.wSecond, systemTime.wMilliseconds), + Qt::UTC)); + } + } emit nativePositionUpdate(currentInfo); |