diff options
46 files changed, 1387 insertions, 838 deletions
diff --git a/dist/changes-5.7.1 b/dist/changes-5.7.1 new file mode 100644 index 00000000..2d111d5b --- /dev/null +++ b/dist/changes-5.7.1 @@ -0,0 +1,45 @@ +Qt 5.7.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.7.0. + +Qt 5.7.1 contains a merge from Qt 5.6.2 and all changes in Qt 5.6.2 are +also in Qt 5.7.1. For more see changes-5.6.2. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.7 series is binary compatible with the 5.6.x series. +Applications compiled for 5.6 will continue to run with 5.7. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtLocation +---------- + +- [QTBUG-55535] Fixed map polygon not working correctly when zooming. +- [QTBUG-55964] Fixed rendering of polylines and polygons at the edge + of the map. +- [QTBUG-52610] Fixed incorrect map polyline drawing. +- [QTBUG-52076] Fixed crashes on tessellation of self-intersecting + GeoPolygons. +- [QTBUG-52030] Fixed that map's center and zoom were not initialized when + used in a layout. +- [QTBUG-52514] Fixed not working MapPolygon inside Repeater. +- [QTBUG-53128] Fixed map objects moving incorrectly on map resize. +- [QTBUG-52075] Fixed losing map item focus while dragging. + +QtPositioning +------------- + +- [QTBUG-54506] Fixed unneeded dependency on Activity for Android Platform. diff --git a/src/imports/location/qdeclarativecirclemapitem.cpp b/src/imports/location/qdeclarativecirclemapitem.cpp index 5e33a1c5..c440664c 100644 --- a/src/imports/location/qdeclarativecirclemapitem.cpp +++ b/src/imports/location/qdeclarativecirclemapitem.cpp @@ -145,7 +145,7 @@ void QGeoMapCircleGeometry::updateScreenPointsInvert(const QGeoMap &map) if (!screenDirty_) return; - if (map.width() == 0 || map.height() == 0) { + if (map.viewportWidth() == 0 || map.viewportHeight() == 0) { clear(); return; } @@ -177,7 +177,7 @@ void QGeoMapCircleGeometry::updateScreenPointsInvert(const QGeoMap &map) geoDistance += 360.0; double mapWidth = 360.0 / geoDistance; - qreal leftOffset = origin.x() - (map.width()/2.0 - mapWidth/2.0) - firstPointOffset_.x(); + qreal leftOffset = origin.x() - (map.viewportWidth()/2.0 - mapWidth/2.0) - firstPointOffset_.x(); qreal topOffset = origin.y() - (midPoint.y() - mapWidth/2.0) - firstPointOffset_.y(); QPainterPath ppiBorder; ppiBorder.moveTo(QPointF(-leftOffset, -topOffset)); @@ -610,7 +610,7 @@ void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList<QGeoCoordinat if ( geoDistance < 0 ) geoDistance += 360; qreal mapWidth = 360.0 / geoDistance; - mapWidth = qMin(static_cast<int>(mapWidth), map()->width()); + mapWidth = qMin(static_cast<int>(mapWidth), map()->viewportWidth()); QDoubleVector2D prev = map()->coordinateToItemPosition(path.at(0), false); // find the points in path where wrapping occurs for (int i = 1; i <= path.count(); ++i) { diff --git a/src/imports/location/qdeclarativegeomap.cpp b/src/imports/location/qdeclarativegeomap.cpp index 49d6ddd1..d432e776 100644 --- a/src/imports/location/qdeclarativegeomap.cpp +++ b/src/imports/location/qdeclarativegeomap.cpp @@ -306,7 +306,7 @@ void QDeclarativeGeoMap::initialize() // try to keep center change signal in the end bool centerHasChanged = false; - setMinimumZoomLevel(m_map->minimumZoomAtMapSize(width(), height())); + setMinimumZoomLevel(m_map->minimumZoomAtViewportSize(width(), height())); // set latitude bundary check m_maximumViewportLatitude = m_map->maximumCenterLatitudeAtZoom(m_cameraData.zoomLevel()); @@ -573,7 +573,7 @@ void QDeclarativeGeoMap::mappingManagerInitialized() // after this has been called at least once, after creation. if (!m_initialized && width() > 0 && height() > 0) { - m_map->setSize(QSize(width(), height())); + m_map->setViewportSize(QSize(width(), height())); initialize(); } @@ -634,7 +634,7 @@ void QDeclarativeGeoMap::setMinimumZoomLevel(qreal minimumZoomLevel) if (m_map) { minimumZoomLevel = qBound(qreal(m_map->cameraCapabilities().minimumZoomLevel()), minimumZoomLevel, maximumZoomLevel()); - double minimumViewportZoomLevel = m_map->minimumZoomAtMapSize(width(),height()); + double minimumViewportZoomLevel = m_map->minimumZoomAtViewportSize(width(),height()); if (minimumZoomLevel < minimumViewportZoomLevel) minimumZoomLevel = minimumViewportZoomLevel; } @@ -907,6 +907,9 @@ void QDeclarativeGeoMap::fitViewportToGeoShape() QGeoRectangle rect = m_region; topLeft = rect.topLeft(); bottomRight = rect.bottomRight(); + if (bottomRight.longitude() < topLeft.longitude()) + bottomRight.setLongitude(bottomRight.longitude() + 360.0); + break; } case QGeoShape::CircleType: @@ -1054,8 +1057,8 @@ void QDeclarativeGeoMap::pan(int dx, int dy) if (dx == 0 && dy == 0) return; QGeoCoordinate coord = m_map->itemPositionToCoordinate( - QDoubleVector2D(m_map->width() / 2 + dx, - m_map->height() / 2 + dy)); + QDoubleVector2D(m_map->viewportWidth() / 2 + dx, + m_map->viewportHeight() / 2 + dy)); setCenter(coord); } @@ -1415,12 +1418,12 @@ void QDeclarativeGeoMap::geometryChanged(const QRectF &newGeometry, const QRectF if (!m_map || !newGeometry.size().isValid()) return; - m_map->setSize(newGeometry.size().toSize()); + m_map->setViewportSize(newGeometry.size().toSize()); if (!m_initialized) { initialize(); } else { - setMinimumZoomLevel(m_map->minimumZoomAtMapSize(newGeometry.width(), newGeometry.height())); + setMinimumZoomLevel(m_map->minimumZoomAtViewportSize(newGeometry.width(), newGeometry.height())); // Update the center latitudinal threshold double maximumCenterLatitudeAtZoom = m_map->maximumCenterLatitudeAtZoom(m_cameraData.zoomLevel()); diff --git a/src/imports/location/qdeclarativepolygonmapitem.cpp b/src/imports/location/qdeclarativepolygonmapitem.cpp index 50dce35f..41196770 100644 --- a/src/imports/location/qdeclarativepolygonmapitem.cpp +++ b/src/imports/location/qdeclarativepolygonmapitem.cpp @@ -208,7 +208,7 @@ void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map) if (!screenDirty_) return; - if (map.width() == 0 || map.height() == 0) { + if (map.viewportWidth() == 0 || map.viewportHeight() == 0) { clear(); return; } @@ -217,7 +217,7 @@ void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map) // Create the viewport rect in the same coordinate system // as the actual points - QRectF viewport(0, 0, map.width(), map.height()); + QRectF viewport(0, 0, map.viewportWidth(), map.viewportHeight()); viewport.translate(-1 * origin.toPointF()); QPainterPath vpPath; diff --git a/src/imports/location/qdeclarativepolylinemapitem.cpp b/src/imports/location/qdeclarativepolylinemapitem.cpp index c4150d2a..2153b036 100644 --- a/src/imports/location/qdeclarativepolylinemapitem.cpp +++ b/src/imports/location/qdeclarativepolylinemapitem.cpp @@ -200,9 +200,9 @@ void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map, srcPointTypes_.clear(); srcPointTypes_.reserve(path.size()); - QDoubleVector2D origin, lastPoint, lastAddedPoint; + QDoubleVector2D origin, lastAddedPoint; - const double mapWidthHalf = map.width()/2.0; + const double mapWidthHalf = map.viewportWidth()/2.0; double unwrapBelowX = 0; if (preserveGeometry_) unwrapBelowX = map.coordinateToItemPosition(geoLeftBound_, false).x(); @@ -263,8 +263,6 @@ void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map, lastAddedPoint = point; } } - - lastPoint = point; } sourceBounds_ = QRectF(QPointF(minX, minY), QPointF(maxX, maxY)); @@ -402,7 +400,7 @@ void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map, // Create the viewport rect in the same coordinate system // as the actual points - QRectF viewport(0, 0, map.width(), map.height()); + QRectF viewport(0, 0, map.viewportWidth(), map.viewportHeight()); viewport.adjust(-strokeWidth, -strokeWidth, strokeWidth, strokeWidth); viewport.translate(-1 * origin); diff --git a/src/imports/location/qgeomapitemgeometry.cpp b/src/imports/location/qgeomapitemgeometry.cpp index c8168f67..a22be0af 100644 --- a/src/imports/location/qgeomapitemgeometry.cpp +++ b/src/imports/location/qgeomapitemgeometry.cpp @@ -125,7 +125,7 @@ double QGeoMapItemGeometry::geoDistanceToScreenWidth(const QGeoMap &map, // Do not wrap around half the globe Q_ASSERT(!qFuzzyCompare(fromCoord.longitude(), toCoord.longitude())); - QGeoCoordinate mapMid = map.itemPositionToCoordinate(QDoubleVector2D(map.width()/2.0, 0)); + QGeoCoordinate mapMid = map.itemPositionToCoordinate(QDoubleVector2D(map.viewportWidth()/2.0, 0)); double halfGeoDist = toCoord.longitude() - fromCoord.longitude(); if (toCoord.longitude() < fromCoord.longitude()) halfGeoDist += 360; @@ -133,7 +133,7 @@ double QGeoMapItemGeometry::geoDistanceToScreenWidth(const QGeoMap &map, QGeoCoordinate geoDelta = QGeoCoordinate(0, QLocationUtils::wrapLong(mapMid.longitude() + halfGeoDist)); QDoubleVector2D halfScreenDist = map.coordinateToItemPosition(geoDelta, false) - - QDoubleVector2D(map.width()/2.0, 0); + - QDoubleVector2D(map.viewportWidth()/2.0, 0); return halfScreenDist.x() * 2.0; } diff --git a/src/imports/location/qquickgeomapgesturearea.cpp b/src/imports/location/qquickgeomapgesturearea.cpp index c115ce49..3d881ae2 100644 --- a/src/imports/location/qquickgeomapgesturearea.cpp +++ b/src/imports/location/qquickgeomapgesturearea.cpp @@ -702,7 +702,7 @@ void QQuickGeoMapGestureArea::handleWheelEvent(QWheelEvent *event) { qreal dx = postZoomPoint.x() - preZoomPoint.x(); qreal dy = postZoomPoint.y() - preZoomPoint.y(); - QPointF mapCenterPoint(m_map->width() / 2.0 + dx, m_map->height() / 2.0 + dy); + QPointF mapCenterPoint(m_map->viewportWidth() / 2.0 + dx, m_map->viewportHeight() / 2.0 + dy); QGeoCoordinate mapCenterCoordinate = m_map->itemPositionToCoordinate(QDoubleVector2D(mapCenterPoint), false); m_declarativeMap->setCenter(mapCenterCoordinate); @@ -1149,8 +1149,8 @@ void QQuickGeoMapGestureArea::updatePan() int dx = static_cast<int>(m_sceneCenter.x() - startPoint.x()); int dy = static_cast<int>(m_sceneCenter.y() - startPoint.y()); QPointF mapCenterPoint; - mapCenterPoint.setY(m_map->height() / 2.0 - dy); - mapCenterPoint.setX(m_map->width() / 2.0 - dx); + mapCenterPoint.setY(m_map->viewportHeight() / 2.0 - dy); + mapCenterPoint.setX(m_map->viewportWidth() / 2.0 - dx); QGeoCoordinate animationStartCoordinate = m_map->itemPositionToCoordinate(QDoubleVector2D(mapCenterPoint), false); m_declarativeMap->setCenter(animationStartCoordinate); } diff --git a/src/location/doc/src/plugins/esri.qdoc b/src/location/doc/src/plugins/esri.qdoc index 06b0c2c1..6ac65910 100644 --- a/src/location/doc/src/plugins/esri.qdoc +++ b/src/location/doc/src/plugins/esri.qdoc @@ -52,7 +52,25 @@ The use of these services is governed by the \l {http://www.esri.com/legal/terms Data is provided by \l {http://www.esri.com/data/find-data}{many different content providers}. -The Esri geo services plugin can be loaded by using the plugin key "esri". +The Esri geoservices plugin can be loaded by using the plugin key "esri". + +To use the services provided by the Esri platform, a subscription is required. There are two kinds of subscriptions: + +\list + \li \l {https://developers.arcgis.com/plans/}{Developer Subscription} + \li \l {http://www.arcgis.com/features/plans/pricing.html}{ArcGIS Online Subscription} +\endlist + +The Developer subscription offers a free-of-charge option for developing and testing your applications. +With this, your applications can go to production under the following conditions: + +\list + \li The app can request up to one million maps per month, and one million geocodes per month. + \li The app does not directly generate revenue. That is, end users must be able to obtain it and to use it for free. +\endlist + +If the above conditions cannot be met, the ArcGIS Online subscription is the correct choice, as it +gives applications full access to the ArcGIS platform. \section1 Parameters @@ -67,7 +85,8 @@ The Esri geo services plugin can be loaded by using the plugin key "esri". \li Security token for using routing services only. Mapping and geocoding services do not require a token. \endtable -To use the routing services hosted on ArcGIS Online with the Esri plugin, a token is required. You can \l{https://developers.arcgis.com/authentication/accessing-arcgis-online-services/#registering-your-application}{obtain a token for testing purposes}, +To use the routing services hosted on ArcGIS Online with the Esri plugin, a token is required. +You can \l{https://developers.arcgis.com/authentication/accessing-arcgis-online-services/#registering-your-application}{obtain a token for testing purposes}, or you can sign up for an \l {http://www.arcgis.com/features/plans/pricing.html}{ArcGIS Online subscription}. \section2 Optional parameters @@ -85,6 +104,53 @@ or you can sign up for an \l {http://www.arcgis.com/features/plans/pricing.html} \row \li esri.mapping.maximumZoomLevel \li The maximum level [double] at which the map is displayed +\row + \li esri.mapping.cache.directory + \li Absolute path to map tile cache directory used as network disk cache. + + The default place for the cache is \c{QtLocation/here} directory in + \l {QStandardPaths::writableLocation()} {QStandardPaths::writableLocation}(\l{QStandardPaths::GenericCacheLocation}). + On systems that have no concept of a shared cache, the application-specific + \l{QStandardPaths::CacheLocation} is used instead. +\row + \li esri.mapping.cache.disk.cost_strategy + \li The cost strategy to use to cache map tiles on disk. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b esri.mapping.cache.disk.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b bytesize. +\row + \li esri.mapping.cache.disk.size + \li Disk cache size for map tiles. The default size of the cache is 50 MiB when \b bytesize is the cost + strategy for this cache, or 1000 tiles, when \b unitary is the cost strategy. +\row + \li esri.mapping.cache.memory.cost_strategy + \li The cost strategy to use to cache map tiles in memory. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b esri.mapping.cache.memory.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b bytesize. +\row + \li esri.mapping.cache.memory.size + \li Memory cache size for map tiles. The default size of the cache is 3 MiB when \b bytesize is the cost + strategy for this cache, or 100 tiles, when \b unitary is the cost strategy. +\row + \li esri.mapping.cache.texture.cost_strategy + \li The cost strategy to use to cache decompressed map tiles in memory. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b esri.mapping.cache.texture.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b bytesize. +\row + \li esri.mapping.cache.texture.size + \li Texture cache size for map tiles. The default size of the cache is 6 MiB when \b bytesize is the cost + strategy for this cache, or 30 tiles, when \b unitary is the cost strategy. + Note that the texture cache has a hard minimum size which depends on the size of the map viewport + (it must contain enough data to display the tiles currently visible on the display). + This value is the amount of cache to be used in addition to the bare minimum. \endtable \section2 Directions language diff --git a/src/location/doc/src/plugins/mapbox.qdoc b/src/location/doc/src/plugins/mapbox.qdoc index bc67822a..dfa83713 100644 --- a/src/location/doc/src/plugins/mapbox.qdoc +++ b/src/location/doc/src/plugins/mapbox.qdoc @@ -92,18 +92,49 @@ The following table lists optional parameters that can be passed to the Mapbox p The default place for the cache is \c{QtLocation/mapbox} directory in \l {QStandardPaths::writableLocation()} {QStandardPaths::writableLocation}(\l{QStandardPaths::GenericCacheLocation}). On systems that have no concept of a shared cache, the application-specific \l{QStandardPaths::CacheLocation} is used instead. \row + \li mapbox.mapping.cache.disk.cost_strategy + \li The cost strategy to use to cache map tiles on disk. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b mapbox.mapping.cache.disk.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b unitary. +\row \li mapbox.mapping.cache.disk.size - \li Map tile disk cache size in bytes. Default size of the cache is 300MB. - Note that this is the maximum amount of data that the Mapbox free plan allows to cache. + \li Disk cache size for map tiles. + The default size of this cache is 6000 if \b unitary is used as cost strategy, + or 50 MiB, if \b bytesize is used as cost strategy. + Note that 6000 is the maximum amount of tiles that the Mapbox free plan allows to cache. Make sure to comply with Mapbox Terms of Service before increasing this value. \row + \li mapbox.mapping.cache.memory.cost_strategy + \li The cost strategy to use to cache map tiles in memory. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b mapbox.mapping.cache.memory.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b bytesize. +\row \li mapbox.mapping.cache.memory.size - \li Map tile memory cache size in bytes. Default size of the cache is 3MB. + \li Memory cache size for map tiles. + The Default size of this cache is 100 if \b unitary is used as cost strategy, or + 3 MiB, if \b bytesize is used as cost strategy. +\row + \li mapbox.mapping.cache.texture.cost_strategy + \li The cost strategy to use to cache decompressed map tiles in memory. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b mapbox.mapping.cache.texture.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b bytesize. \row \li mapbox.mapping.cache.texture.size - \li Map tile texture cache size in bytes. Default size of the cache is 6MB. - Note that the texture cache has a hard minimum size which depends on the size of the map viewport - (it must contain enough data to display the tiles currently visible on the display). - This value is the amount of cache to be used in addition to the bare minimum. + \li Texture cache size for map tiles. + The Default size of this cache is 30 if \b unitary is used as cost strategy, or + 6 MiB, if \b bytesize is used as cost strategy. + Note that the texture cache has a hard minimum size which depends on the size of the map + viewport (it must contain enough data to display the tiles currently visible on the + display). + This value is the amount of tiles to be cached in addition to the bare minimum. \endtable */ diff --git a/src/location/doc/src/plugins/nokia.qdoc b/src/location/doc/src/plugins/nokia.qdoc index 91a9fe9a..7e2fa512 100644 --- a/src/location/doc/src/plugins/nokia.qdoc +++ b/src/location/doc/src/plugins/nokia.qdoc @@ -96,14 +96,44 @@ a prefix. On systems that have no concept of a shared cache, the application-specific \l{QStandardPaths::CacheLocation} is used instead. \row + \li here.mapping.cache.disk.cost_strategy + \li The cost strategy to use to cache map tiles on disk. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b here.mapping.cache.disk.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b bytesize. +\row \li here.mapping.cache.disk.size - \li Map tile disk cache size in bytes. Default size of the cache is 20MB. + \li Disk cache size for map tiles. The default size of the cache is 50 MiB when \b bytesize is the cost + strategy for this cache, or 1000 tiles, when \b unitary is the cost strategy. +\row + \li here.mapping.cache.memory.cost_strategy + \li The cost strategy to use to cache map tiles in memory. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b here.mapping.cache.memory.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b bytesize. \row \li here.mapping.cache.memory.size - \li Map tile memory cache size in bytes. Default size of the cache is 3MB. + \li Memory cache size for map tiles. The default size of the cache is 3 MiB when \b bytesize is the cost + strategy for this cache, or 100 tiles, when \b unitary is the cost strategy. +\row + \li here.mapping.cache.texture.cost_strategy + \li The cost strategy to use to cache decompressed map tiles in memory. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b here.mapping.cache.texture.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b bytesize. \row - \li here.mapping.cache.texture.size - \li Map tile texture cache size in bytes. Default size of the cache is 6MB. Note that the texture cache has a hard minimum size which depends on the size of the map viewport (it must contain enough data to display the tiles currently visible on the display). This value is the amount of cache to be used in addition to the bare minimum. + \li osm.mapping.cache.texture.size + \li Texture cache size for map tiles. The default size of the cache is 6 MiB when \b bytesize is the cost + strategy for this cache, or 30 tiles, when \b unitary is the cost strategy. + Note that the texture cache has a hard minimum size which depends on the size of the map viewport + (it must contain enough data to display the tiles currently visible on the display). + This value is the amount of cache to be used in addition to the bare minimum. \row \li here.mapping.highdpi_tiles \li Whether or not to request high dpi tiles. Valid values are \b true and \b false. The default value is \b false. diff --git a/src/location/doc/src/plugins/osm.qdoc b/src/location/doc/src/plugins/osm.qdoc index c0ae48a7..bb520601 100644 --- a/src/location/doc/src/plugins/osm.qdoc +++ b/src/location/doc/src/plugins/osm.qdoc @@ -135,14 +135,44 @@ a prefix. There is no default value, and if this property is not set, no directory will be indexed and only the network disk cache will be used to reduce network usage or to act as an offline storage for the currently cached tiles. \row + \li osm.mapping.cache.disk.cost_strategy + \li The cost strategy to use to cache map tiles on disk. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b osm.mapping.cache.disk.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b bytesize. +\row \li osm.mapping.cache.disk.size - \li Map tile disk cache size in bytes. Default size of the cache is 100MB. + \li Disk cache size for map tiles. The default size of the cache is 50 MiB when \b bytesize is the cost + strategy for this cache, or 1000 tiles, when \b unitary is the cost strategy. +\row + \li osm.mapping.cache.memory.cost_strategy + \li The cost strategy to use to cache map tiles in memory. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b osm.mapping.cache.memory.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b bytesize. \row \li osm.mapping.cache.memory.size - \li Map tile memory cache size in bytes. Default size of the cache is 3MB. + \li Memory cache size for map tiles. The default size of the cache is 3 MiB when \b bytesize is the cost + strategy for this cache, or 100 tiles, when \b unitary is the cost strategy. +\row + \li osm.mapping.cache.texture.cost_strategy + \li The cost strategy to use to cache decompressed map tiles in memory. + Valid values are \b bytesize and \b unitary. + Using \b bytesize, the related size parameter (\b osm.mapping.cache.texture.size) will + be interpreted as bytes. + Using \b unitary, they will be interpreted as number of tiles. + The default value for this parameter is \b bytesize. \row \li osm.mapping.cache.texture.size - \li Map tile texture cache size in bytes. Default size of the cache is 6MB. Note that the texture cache has a hard minimum size which depends on the size of the map viewport (it must contain enough data to display the tiles currently visible on the display). This value is the amount of cache to be used in addition to the bare minimum. + \li Texture cache size for map tiles. The default size of the cache is 6 MiB when \b bytesize is the cost + strategy for this cache, or 30 tiles, when \b unitary is the cost strategy. + Note that the texture cache has a hard minimum size which depends on the size of the map viewport + (it must contain enough data to display the tiles currently visible on the display). + This value is the amount of cache to be used in addition to the bare minimum. \endtable diff --git a/src/location/maps/qabstractgeotilecache_p.h b/src/location/maps/qabstractgeotilecache_p.h index 8cd303ee..484bb8a5 100644 --- a/src/location/maps/qabstractgeotilecache_p.h +++ b/src/location/maps/qabstractgeotilecache_p.h @@ -87,6 +87,10 @@ class Q_LOCATION_EXPORT QAbstractGeoTileCache : public QObject { Q_OBJECT public: + enum CostStrategy { + Unitary, + ByteSize + }; virtual ~QAbstractGeoTileCache(); virtual void setMaxDiskUsage(int diskUsage); @@ -103,6 +107,12 @@ public: virtual int minTextureUsage() const = 0; virtual int textureUsage() const = 0; virtual void clearAll() = 0; + virtual void setCostStrategyDisk(CostStrategy costStrategy) = 0; + virtual CostStrategy costStrategyDisk() const = 0; + virtual void setCostStrategyMemory(CostStrategy costStrategy) = 0; + virtual CostStrategy costStrategyMemory() const = 0; + virtual void setCostStrategyTexture(CostStrategy costStrategy) = 0; + virtual CostStrategy costStrategyTexture() const = 0; virtual QSharedPointer<QGeoTileTexture> get(const QGeoTileSpec &spec) = 0; diff --git a/src/location/maps/qcache3q_p.h b/src/location/maps/qcache3q_p.h index 4348e14b..148c1f8b 100644 --- a/src/location/maps/qcache3q_p.h +++ b/src/location/maps/qcache3q_p.h @@ -145,7 +145,7 @@ private: QHash<Key, Node *> lookup_; public: - explicit QCache3Q(int maxCost = 100, int minRecent = -1, int maxOldPopular = -1); + explicit QCache3Q(int maxCost = 0, int minRecent = -1, int maxOldPopular = -1); inline ~QCache3Q() { clear(); delete q1_; delete q2_; delete q3_; delete q1_evicted_; } inline int maxCost() const { return maxCost_; } @@ -161,7 +161,7 @@ public: QSharedPointer<T> object(const Key &key) const; QSharedPointer<T> operator[](const Key &key) const; - void remove(const Key &key); + void remove(const Key &key, bool force = false); QList<Key> keys() const; void printStats(); @@ -398,7 +398,7 @@ void QCache3Q<Key,T,EvPolicy>::rebalance() } else { Node *n = q2_->l; unlink(n); - if (n->pop > (q2_->pop / q2_->size)) { + if (q2_->size && n->pop > (q2_->pop / q2_->size)) { link_front(n, q3_); } else { EvPolicy::aboutToBeEvicted(n->k, n->v); @@ -411,14 +411,14 @@ void QCache3Q<Key,T,EvPolicy>::rebalance() } template <class Key, class T, class EvPolicy> -void QCache3Q<Key,T,EvPolicy>::remove(const Key &key) +void QCache3Q<Key,T,EvPolicy>::remove(const Key &key, bool force) { if (!lookup_.contains(key)) { return; } Node *n = lookup_[key]; unlink(n); - if (n->q != q1_evicted_) + if (n->q != q1_evicted_ && !force) EvPolicy::aboutToBeRemoved(n->k, n->v); lookup_.remove(key); delete n; @@ -427,10 +427,7 @@ void QCache3Q<Key,T,EvPolicy>::remove(const Key &key) template <class Key, class T, class EvPolicy> QList<Key> QCache3Q<Key,T,EvPolicy>::keys() const { - QList<Key> keys; - for (auto i = lookup_.constBegin(); i != lookup_.constEnd(); ++i) - keys.append(i.key()); - return keys; + return lookup_.keys(); } template <class Key, class T, class EvPolicy> diff --git a/src/location/maps/qgeofiletilecache.cpp b/src/location/maps/qgeofiletilecache.cpp index 6e8fa856..49e9dfff 100644 --- a/src/location/maps/qgeofiletilecache.cpp +++ b/src/location/maps/qgeofiletilecache.cpp @@ -87,6 +87,7 @@ QGeoCachedTileDisk::~QGeoCachedTileDisk() QGeoFileTileCache::QGeoFileTileCache(const QString &directory, QObject *parent) : QAbstractGeoTileCache(parent), directory_(directory), minTextureUsage_(0), extraTextureUsage_(0) + ,costStrategyDisk_(ByteSize), costStrategyMemory_(ByteSize), costStrategyTexture_(ByteSize) { } @@ -119,9 +120,26 @@ void QGeoFileTileCache::init() QDir::root().mkpath(directory_); // default values - setMaxDiskUsage(20 * 1024 * 1024); - setMaxMemoryUsage(3 * 1024 * 1024); - setExtraTextureUsage(6 * 1024 * 1024); + if (!diskCache_.maxCost()) { // If setMaxDiskUsage has not been called yet + if (costStrategyDisk_ == ByteSize) + setMaxDiskUsage(50 * 1024 * 1024); + else + setMaxDiskUsage(1000); + } + + if (!memoryCache_.maxCost()) { // If setMaxMemoryUsage has not been called yet + if (costStrategyMemory_ == ByteSize) + setMaxMemoryUsage(3 * 1024 * 1024); + else + setMaxMemoryUsage(100); + } + + if (!textureCache_.maxCost()) { // If setExtraTextureUsage has not been called yet + if (costStrategyTexture_ == ByteSize) + setExtraTextureUsage(6 * 1024 * 1024); + else + setExtraTextureUsage(30); // byte size of texture is >> compressed image, hence unitary cost should be lower + } loadTiles(); } @@ -160,7 +178,11 @@ void QGeoFileTileCache::loadTiles() QFileInfo fi(tileDisk->filename); specs.append(spec); queue.append(tileDisk); - costs.append(fi.size()); + if (costStrategyDisk_ == ByteSize) + costs.append(fi.size()); + else + costs.append(1); + } } @@ -284,6 +306,64 @@ void QGeoFileTileCache::clearAll() } } +void QGeoFileTileCache::clearMapId(const int mapId) +{ + for (const QGeoTileSpec &k : diskCache_.keys()) + if (k.mapId() == mapId) + diskCache_.remove(k, true); + for (const QGeoTileSpec &k : memoryCache_.keys()) + if (k.mapId() == mapId) + memoryCache_.remove(k); + for (const QGeoTileSpec &k : textureCache_.keys()) + if (k.mapId() == mapId) + textureCache_.remove(k); + + // TODO: It seems the cache leaves residues, like some tiles do not get picked up. + // After the above calls, files that shouldnt be left behind are still on disk. + // Do an additional pass and make sure what has to be deleted gets deleted. + QDir dir(directory_); + QStringList formats; + formats << QLatin1String("*.*"); + QStringList files = dir.entryList(formats, QDir::Files); + qWarning() << "Old tile data detected. Cache eviction left out "<< files.size() << "tiles"; + for (const QString &tileFileName : files) { + QGeoTileSpec spec = filenameToTileSpec(tileFileName); + if (spec.mapId() != mapId) + continue; + QFile::remove(dir.filePath(tileFileName)); + } +} + +void QGeoFileTileCache::setCostStrategyDisk(QAbstractGeoTileCache::CostStrategy costStrategy) +{ + costStrategyDisk_ = costStrategy; +} + +QAbstractGeoTileCache::CostStrategy QGeoFileTileCache::costStrategyDisk() const +{ + return costStrategyDisk_; +} + +void QGeoFileTileCache::setCostStrategyMemory(QAbstractGeoTileCache::CostStrategy costStrategy) +{ + costStrategyMemory_ = costStrategy; +} + +QAbstractGeoTileCache::CostStrategy QGeoFileTileCache::costStrategyMemory() const +{ + return costStrategyMemory_; +} + +void QGeoFileTileCache::setCostStrategyTexture(QAbstractGeoTileCache::CostStrategy costStrategy) +{ + costStrategyTexture_ = costStrategy; +} + +QAbstractGeoTileCache::CostStrategy QGeoFileTileCache::costStrategyTexture() const +{ + return costStrategyTexture_; +} + QSharedPointer<QGeoTileTexture> QGeoFileTileCache::get(const QGeoTileSpec &spec) { QSharedPointer<QGeoTileTexture> tt = getFromMemory(spec); @@ -335,9 +415,12 @@ QSharedPointer<QGeoCachedTileDisk> QGeoFileTileCache::addToDiskCache(const QGeoT td->filename = filename; td->cache = this; - QFileInfo fi(filename); - int diskCost = fi.size(); - diskCache_.insert(spec, td, diskCost); + int cost = 1; + if (costStrategyDisk_ == ByteSize) { + QFileInfo fi(filename); + cost = fi.size(); + } + diskCache_.insert(spec, td, cost); return td; } @@ -349,7 +432,9 @@ QSharedPointer<QGeoCachedTileMemory> QGeoFileTileCache::addToMemoryCache(const Q tm->bytes = bytes; tm->format = format; - int cost = bytes.size(); + int cost = 1; + if (costStrategyMemory_ == ByteSize) + cost = bytes.size(); memoryCache_.insert(spec, tm, cost); return tm; @@ -361,8 +446,10 @@ QSharedPointer<QGeoTileTexture> QGeoFileTileCache::addToTextureCache(const QGeoT tt->spec = spec; tt->image = image; - int textureCost = image.width() * image.height() * image.depth() / 8; - textureCache_.insert(spec, tt, textureCost); + int cost = 1; + if (costStrategyTexture_ == ByteSize) + cost = image.width() * image.height() * image.depth() / 8; + textureCache_.insert(spec, tt, cost); return tt; } diff --git a/src/location/maps/qgeofiletilecache_p.h b/src/location/maps/qgeofiletilecache_p.h index fc023bc7..7d6df9fd 100644 --- a/src/location/maps/qgeofiletilecache_p.h +++ b/src/location/maps/qgeofiletilecache_p.h @@ -117,6 +117,14 @@ public: int minTextureUsage() const Q_DECL_OVERRIDE; int textureUsage() const Q_DECL_OVERRIDE; void clearAll() Q_DECL_OVERRIDE; + void clearMapId(const int mapId); + void setCostStrategyDisk(CostStrategy costStrategy) Q_DECL_OVERRIDE; + CostStrategy costStrategyDisk() const Q_DECL_OVERRIDE; + void setCostStrategyMemory(CostStrategy costStrategy) Q_DECL_OVERRIDE; + CostStrategy costStrategyMemory() const Q_DECL_OVERRIDE; + void setCostStrategyTexture(CostStrategy costStrategy) Q_DECL_OVERRIDE; + CostStrategy costStrategyTexture() const Q_DECL_OVERRIDE; + QSharedPointer<QGeoTileTexture> get(const QGeoTileSpec &spec) Q_DECL_OVERRIDE; @@ -153,6 +161,9 @@ protected: int minTextureUsage_; int extraTextureUsage_; + CostStrategy costStrategyDisk_; + CostStrategy costStrategyMemory_; + CostStrategy costStrategyTexture_; }; QT_END_NAMESPACE diff --git a/src/location/maps/qgeomap.cpp b/src/location/maps/qgeomap.cpp index f8361ee3..ac6d661b 100644 --- a/src/location/maps/qgeomap.cpp +++ b/src/location/maps/qgeomap.cpp @@ -51,31 +51,31 @@ QGeoMap::~QGeoMap() { } -void QGeoMap::setSize(const QSize& size) +void QGeoMap::setViewportSize(const QSize& size) { Q_D(QGeoMap); - if (size == d->m_size) + if (size == d->m_viewportSize) return; - d->m_size = size; - d->changeMapSize(size); + d->m_viewportSize = size; + d->changeViewportSize(size); } -QSize QGeoMap::size() const +QSize QGeoMap::viewportSize() const { Q_D(const QGeoMap); - return d->m_size; + return d->m_viewportSize; } -int QGeoMap::width() const +int QGeoMap::viewportWidth() const { Q_D(const QGeoMap); - return d->m_size.width(); + return d->m_viewportSize.width(); } -int QGeoMap::height() const +int QGeoMap::viewportHeight() const { Q_D(const QGeoMap); - return d->m_size.height(); + return d->m_viewportSize.height(); } void QGeoMap::setCameraData(const QGeoCameraData &cameraData) diff --git a/src/location/maps/qgeomap_p.h b/src/location/maps/qgeomap_p.h index 6a482e4f..7a12820f 100644 --- a/src/location/maps/qgeomap_p.h +++ b/src/location/maps/qgeomap_p.h @@ -47,8 +47,8 @@ // We mean it. // -#include "qgeocameradata_p.h" -#include "qgeomaptype_p.h" +#include <QtLocation/private/qgeocameradata_p.h> +#include <QtLocation/private/qgeomaptype_p.h> #include <QtCore/QObject> #include <QtPositioning/private/qdoublevector2d_p.h> @@ -71,10 +71,11 @@ class Q_LOCATION_EXPORT QGeoMap : public QObject public: virtual ~QGeoMap(); - void setSize(const QSize& size); - QSize size() const; - int width() const; - int height() const; + // Sets the display size + void setViewportSize(const QSize& viewportSize); + QSize viewportSize() const; + int viewportWidth() const; + int viewportHeight() const; QGeoCameraData cameraData() const; @@ -85,7 +86,7 @@ public: virtual QGeoCoordinate itemPositionToCoordinate(const QDoubleVector2D &pos, bool clipToViewport = true) const = 0; virtual QDoubleVector2D coordinateToItemPosition(const QGeoCoordinate &coordinate, bool clipToViewport = true) const = 0; - virtual double minimumZoomAtMapSize(int width, int height) const = 0; + virtual double minimumZoomAtViewportSize(int viewportWidth, int viewportHeight) const = 0; virtual double maximumCenterLatitudeAtZoom(double zoomLevel) const = 0; virtual QDoubleVector2D referenceCoordinateToItemPosition(const QGeoCoordinate &coordinate) const = 0; diff --git a/src/location/maps/qgeomap_p_p.h b/src/location/maps/qgeomap_p_p.h index 6347d0f7..91938903 100644 --- a/src/location/maps/qgeomap_p_p.h +++ b/src/location/maps/qgeomap_p_p.h @@ -71,13 +71,15 @@ public: protected: /* Hooks into the actual map implementations */ - virtual void changeMapSize(const QSize &size) = 0; // called by QGeoMap::setSize() - virtual void changeCameraData(const QGeoCameraData &oldCameraData) = 0; // called by QGeoMap::setCameraData() - virtual void changeActiveMapType(const QGeoMapType mapType) = 0; // called by QGeoMap::setActiveMapType() virtual void addParameter(QGeoMapParameter *param); virtual void removeParameter(QGeoMapParameter *param); - QSize m_size; + virtual void changeViewportSize(const QSize &size) = 0; // called by QGeoMap::setSize() + virtual void changeCameraData(const QGeoCameraData &oldCameraData) = 0; // called by QGeoMap::setCameraData() + virtual void changeActiveMapType(const QGeoMapType mapType) = 0; // called by QGeoMap::setActiveMapType() + +protected: + QSize m_viewportSize; QPointer<QGeoMappingManagerEngine> m_engine; QGeoCameraData m_cameraData; QGeoMapType m_activeMapType; diff --git a/src/location/maps/qgeorouteparser_p_p.h b/src/location/maps/qgeorouteparser_p_p.h index 7bf41f87..63c773eb 100644 --- a/src/location/maps/qgeorouteparser_p_p.h +++ b/src/location/maps/qgeorouteparser_p_p.h @@ -37,6 +37,17 @@ #ifndef QGEOROUTEPARSER_P_P_H #define QGEOROUTEPARSER_P_P_H +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + #include <QtCore/private/qobject_p.h> #include <QtCore/QUrl> #include <QtLocation/qgeoroutereply.h> diff --git a/src/location/maps/qgeotiledmap.cpp b/src/location/maps/qgeotiledmap.cpp index 2cf815af..a3cad550 100644 --- a/src/location/maps/qgeotiledmap.cpp +++ b/src/location/maps/qgeotiledmap.cpp @@ -153,8 +153,8 @@ QGeoCoordinate QGeoTiledMap::itemPositionToCoordinate(const QDoubleVector2D &pos { Q_D(const QGeoTiledMap); if (clipToViewport) { - int w = width(); - int h = height(); + int w = viewportWidth(); + int h = viewportHeight(); if ((pos.x() < 0) || (w < pos.x()) || (pos.y() < 0) || (h < pos.y())) return QGeoCoordinate(); @@ -169,8 +169,8 @@ QDoubleVector2D QGeoTiledMap::coordinateToItemPosition(const QGeoCoordinate &coo QDoubleVector2D pos = d->coordinateToItemPosition(coordinate); if (clipToViewport) { - int w = width(); - int h = height(); + int w = viewportWidth(); + int h = viewportHeight(); double x = pos.x(); double y = pos.y(); if ((x < 0.0) || (x > w) || (y < 0) || (y > h) || qIsNaN(x) || qIsNaN(y)) @@ -182,7 +182,7 @@ QDoubleVector2D QGeoTiledMap::coordinateToItemPosition(const QGeoCoordinate &coo // This method returns the minimum zoom level that this specific qgeomap type allows // at a given canvas size (width,height) and for a given tile size (usually 256). -double QGeoTiledMap::minimumZoomAtMapSize(int width, int height) const +double QGeoTiledMap::minimumZoomAtViewportSize(int width, int height) const { Q_D(const QGeoTiledMap); double maxSize = qMax(width,height); @@ -203,7 +203,7 @@ double QGeoTiledMap::maximumCenterLatitudeAtZoom(double zoomLevel) const mapEdgeSize *= d->m_visibleTiles->tileSize(); // At init time weird things happen - int clampedWindowHeight = (height() > mapEdgeSize) ? mapEdgeSize : height(); + int clampedWindowHeight = (viewportHeight() > mapEdgeSize) ? mapEdgeSize : viewportHeight(); // Use the window height divided by 2 as the topmost allowed center, with respect to the map size in pixels double mercatorTopmost = (clampedWindowHeight * 0.5) / mapEdgeSize ; @@ -386,7 +386,7 @@ void QGeoTiledMapPrivate::clearScene() updateScene(); } -void QGeoTiledMapPrivate::changeMapSize(const QSize& size) +void QGeoTiledMapPrivate::changeViewportSize(const QSize& size) { Q_Q(QGeoTiledMap); diff --git a/src/location/maps/qgeotiledmap_p.h b/src/location/maps/qgeotiledmap_p.h index ff5f88a6..b148b59c 100644 --- a/src/location/maps/qgeotiledmap_p.h +++ b/src/location/maps/qgeotiledmap_p.h @@ -86,7 +86,7 @@ public: QGeoCoordinate itemPositionToCoordinate(const QDoubleVector2D &pos, bool clipToViewport = true) const Q_DECL_OVERRIDE; QDoubleVector2D coordinateToItemPosition(const QGeoCoordinate &coordinate, bool clipToViewport = true) const Q_DECL_OVERRIDE; - double minimumZoomAtMapSize(int width, int height) const Q_DECL_OVERRIDE; + double minimumZoomAtViewportSize(int viewportWidth, int viewportHeight) const Q_DECL_OVERRIDE; double maximumCenterLatitudeAtZoom(double zoomLevel) const Q_DECL_OVERRIDE; diff --git a/src/location/maps/qgeotiledmap_p_p.h b/src/location/maps/qgeotiledmap_p_p.h index f0744df6..72873d77 100644 --- a/src/location/maps/qgeotiledmap_p_p.h +++ b/src/location/maps/qgeotiledmap_p_p.h @@ -84,7 +84,7 @@ public: QGeoMapType activeMapType(); protected: - void changeMapSize(const QSize& size) Q_DECL_OVERRIDE; + void changeViewportSize(const QSize& size) Q_DECL_OVERRIDE; void changeCameraData(const QGeoCameraData &cameraData) Q_DECL_OVERRIDE; void changeActiveMapType(const QGeoMapType mapType) Q_DECL_OVERRIDE; void changeTileVersion(int version); 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); diff --git a/sync.profile b/sync.profile index fa9885a4..2a80bd03 100644 --- a/sync.profile +++ b/sync.profile @@ -4,17 +4,3 @@ ); %moduleheaders = ( # restrict the module headers to those found in relative path ); -# Module dependencies. -# Every module that is required to build this module should have one entry. -# Each of the module version specifiers can take one of the following values: -# - A specific Git revision. -# - any git symbolic ref resolvable from the module's repository (e.g. "refs/heads/master" to track master branch) -# - an empty string to use the same branch under test (dependencies will become "refs/heads/master" if we are in the master branch) -# -%dependencies = ( - "qtbase" => "", - "qtxmlpatterns" => "", - "qtdeclarative" => "", - "qtquickcontrols" => "", - "qtserialport" => "", -); diff --git a/tests/auto/qgeotiledmap/tst_qgeotiledmap.cpp b/tests/auto/qgeotiledmap/tst_qgeotiledmap.cpp index e7026cff..cc2672b2 100644 --- a/tests/auto/qgeotiledmap/tst_qgeotiledmap.cpp +++ b/tests/auto/qgeotiledmap/tst_qgeotiledmap.cpp @@ -109,7 +109,7 @@ void tst_QGeoTiledMap::initTestCase() QVERIFY2(provider->error() == QGeoServiceProvider::NoError, "Could not load plugin: " + provider->errorString().toLatin1()); m_map.reset(static_cast<QGeoTiledMapTest*>(mappingManager->createMap(this))); QVERIFY(m_map); - m_map->setSize(QSize(16, 16)); + m_map->setViewportSize(QSize(16, 16)); m_fetcher = static_cast<QGeoTileFetcherTest*>(m_map->m_engine->tileFetcher()); m_tilesCounter.reset(new FetchTileCounter()); connect(m_fetcher, SIGNAL(tileFetched(const QGeoTileSpec&)), m_tilesCounter.data(), SLOT(tileFetched(const QGeoTileSpec&))); |