diff options
Diffstat (limited to 'src/plugins/geoservices/osm')
18 files changed, 1283 insertions, 366 deletions
diff --git a/src/plugins/geoservices/osm/osm.pro b/src/plugins/geoservices/osm/osm.pro index 56f4cb33..86a62744 100644 --- a/src/plugins/geoservices/osm/osm.pro +++ b/src/plugins/geoservices/osm/osm.pro @@ -1,6 +1,6 @@ TARGET = qtgeoservices_osm -QT += location-private positioning-private network +QT += location-private positioning-private network concurrent HEADERS += \ qgeoserviceproviderpluginosm.h \ @@ -15,6 +15,7 @@ HEADERS += \ qplacesearchreplyosm.h \ qplacecategoriesreplyosm.h \ qgeotiledmaposm.h \ + qgeofiletilecacheosm.h \ qgeotileproviderosm.h SOURCES += \ @@ -30,6 +31,7 @@ SOURCES += \ qplacesearchreplyosm.cpp \ qplacecategoriesreplyosm.cpp \ qgeotiledmaposm.cpp \ + qgeofiletilecacheosm.cpp \ qgeotileproviderosm.cpp diff --git a/src/plugins/geoservices/osm/providers/5.8/cycle b/src/plugins/geoservices/osm/providers/5.8/cycle new file mode 100644 index 00000000..5e37aab2 --- /dev/null +++ b/src/plugins/geoservices/osm/providers/5.8/cycle @@ -0,0 +1,8 @@ +{ + "UrlTemplate" : "http://a.tile.thunderforest.com/cycle/%z/%x/%y.png", + "ImageFormat" : "png", + "QImageFormat" : "Indexed8", + "ID" : "thf-cycle", + "MapCopyRight" : "<a href='http://www.thunderforest.com/'>Thunderforest</a>", + "DataCopyRight" : "<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors" +} diff --git a/src/plugins/geoservices/osm/providers/5.8/hiking b/src/plugins/geoservices/osm/providers/5.8/hiking new file mode 100644 index 00000000..1bb182e4 --- /dev/null +++ b/src/plugins/geoservices/osm/providers/5.8/hiking @@ -0,0 +1,9 @@ +{ + "UrlTemplate" : "http://b.tiles.wmflabs.org/hikebike/%z/%x/%y.png", + "ImageFormat" : "png", + "QImageFormat" : "Indexed8", + "ID" : "wmf-hike", + "MaximumZoomLevel" : 18, + "MapCopyRight" : "<a href='https://wikimediafoundation.org/wiki/Terms_of_Use'>WikiMedia Foundation</a>", + "DataCopyRight" : "<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors" +} diff --git a/src/plugins/geoservices/osm/providers/5.8/night-transit b/src/plugins/geoservices/osm/providers/5.8/night-transit new file mode 100644 index 00000000..988a096a --- /dev/null +++ b/src/plugins/geoservices/osm/providers/5.8/night-transit @@ -0,0 +1,9 @@ +{ + "UrlTemplate" : "http://a.tile.thunderforest.com/transport-dark/%z/%x/%y.png", + "ImageFormat" : "png", + "QImageFormat" : "Indexed8", + "ID" : "thf-nighttransit", + "MaximumZoomLevel" : 19, + "MapCopyRight" : "<a href='http://www.thunderforest.com/'>Thunderforest</a>", + "DataCopyRight" : "<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors" +} diff --git a/src/plugins/geoservices/osm/providers/5.8/satellite b/src/plugins/geoservices/osm/providers/5.8/satellite new file mode 100644 index 00000000..5c48a077 --- /dev/null +++ b/src/plugins/geoservices/osm/providers/5.8/satellite @@ -0,0 +1,10 @@ +{ + "Enabled" : false, + "UrlTemplate" : "http://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/%z/%y/%x", + "ImageFormat" : "jpg", + "QImageFormat" : "RGB888", + "ID" : "usgs-l7", + "MaximumZoomLevel" : 8, + "MapCopyRight" : "<a href='http://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer'>USGS The National Map: Orthoimagery</a>", + "DataCopyRight" : "<a href='http://landsat.gsfc.nasa.gov/?page_id=2339'>USGS/NASA Landsat</a>" +} diff --git a/src/plugins/geoservices/osm/providers/5.8/street b/src/plugins/geoservices/osm/providers/5.8/street new file mode 100644 index 00000000..b3bccf1d --- /dev/null +++ b/src/plugins/geoservices/osm/providers/5.8/street @@ -0,0 +1,10 @@ +{ + "UrlTemplate" : "http://korona.geog.uni-heidelberg.de/tiles/roads/x=%x&y=%y&z=%z", + "ImageFormat" : "jpg", + "QImageFormat" : "Indexed8", + "ID" : "oms-street", + "MaximumZoomLevel" : 20, + "MapCopyRight" : "<a href='http://giscience.uni-hd.de/'>GIScience Research Group @ University of Heidelberg</a>", + "StyleCopyRight" : "<a href='http://www.geog.uni-heidelberg.de/personen/gis_rylov.html'>Maxim Rylov</a>", + "DataCopyRight" : "<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors" +} diff --git a/src/plugins/geoservices/osm/providers/5.8/street-hires b/src/plugins/geoservices/osm/providers/5.8/street-hires new file mode 100644 index 00000000..9819f619 --- /dev/null +++ b/src/plugins/geoservices/osm/providers/5.8/street-hires @@ -0,0 +1,9 @@ +{ + "UrlTemplate" : "https://maps.wikimedia.org/osm-intl/%z/%x/%y@2x.png", + "ImageFormat" : "png", + "QImageFormat" : "Indexed8", + "MaximumZoomLevel" : 18, + "ID" : "wmf-intl-2x", + "MapCopyRight" : "<a href='https://wikimediafoundation.org/wiki/Terms_of_Use'>WikiMedia Foundation</a>", + "DataCopyRight" : "<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors" +} diff --git a/src/plugins/geoservices/osm/providers/5.8/terrain b/src/plugins/geoservices/osm/providers/5.8/terrain new file mode 100644 index 00000000..7fc6636c --- /dev/null +++ b/src/plugins/geoservices/osm/providers/5.8/terrain @@ -0,0 +1,9 @@ +{ + "UrlTemplate" : "http://a.tile.thunderforest.com/landscape/%z/%x/%y.png", + "ImageFormat" : "png", + "QImageFormat" : "Indexed8", + "ID" : "thf-landsc", + "MaximumZoomLevel" : 19, + "MapCopyRight" : "<a href='http://www.thunderforest.com/'>Thunderforest</a>", + "DataCopyRight" : "<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors" +} diff --git a/src/plugins/geoservices/osm/providers/5.8/transit b/src/plugins/geoservices/osm/providers/5.8/transit new file mode 100644 index 00000000..ebf87b05 --- /dev/null +++ b/src/plugins/geoservices/osm/providers/5.8/transit @@ -0,0 +1,9 @@ +{ + "UrlTemplate" : "http://a.tile.thunderforest.com/transport/%z/%x/%y.png", + "ImageFormat" : "png", + "QImageFormat" : "Indexed8", + "ID" : "thf-transit", + "MaximumZoomLevel" : 19, + "MapCopyRight" : "<a href='http://www.thunderforest.com/'>Thunderforest</a>", + "DataCopyRight" : "<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors" +} diff --git a/src/plugins/geoservices/osm/qgeofiletilecacheosm.cpp b/src/plugins/geoservices/osm/qgeofiletilecacheosm.cpp new file mode 100644 index 00000000..a563cced --- /dev/null +++ b/src/plugins/geoservices/osm/qgeofiletilecacheosm.cpp @@ -0,0 +1,360 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qgeofiletilecacheosm.h" +#include <QtLocation/private/qgeotilespec_p.h> +#include <QDir> +#include <QDirIterator> +#include <QPair> +#include <QDateTime> +#include <QtConcurrent> +#include <QThread> + +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) +{ + 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); + } +} + +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) +{ + QSharedPointer<QGeoTileTexture> tt = getFromMemory(spec); + if (tt) + return tt; + if ((tt = getFromOfflineStorage(spec))) + return tt; + return getFromDisk(spec); +} + +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]) { // e.g., HiDpi was requested but only LoDpi is available + int mapId = m_providers[i]->mapType().mapId(); + m_highDpi[i] = m_providers[i]->isHighDpi(); + + // reload cache for mapId i + 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, -1); +} + +QSharedPointer<QGeoTileTexture> QGeoFileTileCacheOsm::getFromOfflineStorage(const QGeoTileSpec &spec) +{ + QMutexLocker locker(&storageLock); + if (m_tilespecToOfflineFilepath.contains(spec)) { + QFile file(m_tilespecToOfflineFilepath[spec]); + file.open(QIODevice::ReadOnly); + QByteArray bytes = file.readAll(); + file.close(); + locker.unlock(); + + QImage image; + if (!image.loadFromData(bytes)) { + handleError(spec, QLatin1String("Problem with tile image")); + return QSharedPointer<QGeoTileTexture>(0); + } + + addToMemoryCache(spec, bytes, QString()); + QSharedPointer<QGeoTileTexture> tt = addToTextureCache(spec, image); + if (tt) + return tt; + } + + return QSharedPointer<QGeoTileTexture>(); +} + +void QGeoFileTileCacheOsm::dropTiles(int mapId) +{ + QList<QGeoTileSpec> keys; + keys = textureCache_.keys(); + for (const QGeoTileSpec &k : keys) + if (k.mapId() == mapId) + textureCache_.remove(k); + + keys = memoryCache_.keys(); + for (const QGeoTileSpec &k : keys) + if (k.mapId() == mapId) + memoryCache_.remove(k); + + keys = diskCache_.keys(); + for (const QGeoTileSpec &k : keys) + if (k.mapId() == mapId) + diskCache_.remove(k); +} + +void QGeoFileTileCacheOsm::loadTiles(int mapId) +{ + QStringList formats; + formats << QLatin1String("*.*"); + + QDir dir(directory_); + QStringList files = dir.entryList(formats, QDir::Files); + + for (int i = 0; i < files.size(); ++i) { + QGeoTileSpec spec = filenameToTileSpec(files.at(i)); + if (spec.zoom() == -1 || spec.mapId() != mapId) + continue; + QString filename = dir.filePath(files.at(i)); + addToDiskCache(spec, filename); + } +} + +void QGeoFileTileCacheOsm::initOfflineRegistry(int mapId) +{ + // Dealing with duplicates: picking the newest + QMap<QString, QPair<QString, QDateTime> > fileDates; // key is filename, value is <filepath, lastmodified> + QDirIterator it(m_offlineDirectory, QStringList() << "*.*", QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks ); + while (it.hasNext()) { + const QString &path = it.next(); + QFileInfo f(path); + if (!fileDates.contains(f.fileName()) || fileDates[f.fileName()].second < f.lastModified()) + fileDates[f.fileName()] = QPair<QString, QDateTime>(f.filePath(), f.lastModified()); + if (m_requestCancel) + 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.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; + storageLock.unlock(); + if (m_requestCancel) + return; + } + //qInfo() << "OSM plugin has found and is using "<< count <<" offline tiles"; +} + +QString QGeoFileTileCacheOsm::tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, const QString &directory) const +{ + int providerId = spec.mapId() - 1; + if (providerId < 0 || providerId >= m_providers.size()) + return QString(); + QString filename = spec.plugin(); + filename += QLatin1String("-"); + filename += (m_providers[providerId]->isHighDpi()) ? QLatin1Char('h') : QLatin1Char('l'); + filename += QLatin1String("-"); + filename += QString::number(spec.mapId()); + filename += QLatin1String("-"); + filename += QString::number(spec.zoom()); + filename += QLatin1String("-"); + filename += QString::number(spec.x()); + filename += QLatin1String("-"); + filename += QString::number(spec.y()); + + //Append version if real version number to ensure backwards compatibility and eviction of old tiles + if (spec.version() != -1) { + filename += QLatin1String("-"); + filename += QString::number(spec.version()); + } + + filename += QLatin1String("."); + filename += format; + + QDir dir = QDir(directory); + + return dir.filePath(filename); +} + +QGeoTileSpec QGeoFileTileCacheOsm::filenameToTileSpec(const QString &filename) const +{ + QGeoTileSpec emptySpec; + + QStringList parts = filename.split('.'); + + if (parts.length() != 2) + return emptySpec; + + QString name = parts.at(0); + QStringList fields = name.split('-'); + + int length = fields.length(); + if (length != 6 && length != 7) + return emptySpec; + + QList<int> numbers; + + bool ok = false; + for (int i = 2; i < length; ++i) { + ok = false; + int value = fields.at(i).toInt(&ok); + if (!ok) + return emptySpec; + numbers.append(value); + } + + bool highDpi = m_providers[numbers.at(0) - 1]->isHighDpi(); + if (highDpi && fields.at(1) != QLatin1Char('h')) + return emptySpec; + else if (!highDpi && fields.at(1) != QLatin1Char('l')) + return emptySpec; + + //File name without version, append default + if (numbers.length() < 5) + numbers.append(-1); + + return QGeoTileSpec(fields.at(0), + numbers.at(0), + numbers.at(1), + numbers.at(2), + numbers.at(3), + 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 new file mode 100644 index 00000000..d26cad4a --- /dev/null +++ b/src/plugins/geoservices/osm/qgeofiletilecacheosm.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QGEOFILETILECACHEOSM_H +#define QGEOFILETILECACHEOSM_H + +#include "qgeotileproviderosm.h" +#include <QtLocation/private/qgeofiletilecache_p.h> +#include <QHash> +#include <QtConcurrent> +#include <qatomic.h> + +QT_BEGIN_NAMESPACE + +class QGeoFileTileCacheOsm : public QGeoFileTileCache +{ + Q_OBJECT +public: + 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; + +Q_SIGNALS: + void mapDataUpdated(int mapId); + +protected Q_SLOTS: + void onProviderResolutionFinished(const QGeoTileProviderOsm *provider); + void onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error); + +protected: + void init() Q_DECL_OVERRIDE; + QString tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, const QString &directory) const Q_DECL_OVERRIDE; + QGeoTileSpec filenameToTileSpec(const QString &filename) const Q_DECL_OVERRIDE; + QSharedPointer<QGeoTileTexture> getFromOfflineStorage(const QGeoTileSpec &spec); + void dropTiles(int mapId); + void loadTiles(int mapId); + + 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 + +#endif // QGEOFILETILECACHEOSM_H diff --git a/src/plugins/geoservices/osm/qgeotiledmaposm.cpp b/src/plugins/geoservices/osm/qgeotiledmaposm.cpp index f16e602a..d94a40a6 100644 --- a/src/plugins/geoservices/osm/qgeotiledmaposm.cpp +++ b/src/plugins/geoservices/osm/qgeotiledmaposm.cpp @@ -71,10 +71,13 @@ void QGeoTiledMapOsm::evaluateCopyrights(const QSet<QGeoTileSpec> &visibleTiles) return; int providerId = tile.mapId() - 1; - if (providerId < 0 || providerId >= m_engine->providers().size() || !m_engine->providers().at(providerId)->isValid()) + if (providerId < 0 || providerId >= m_engine->providers().size()) return; m_mapId = tile.mapId(); + if (!m_engine->providers().at(providerId)->isValid()) + return; + onProviderDataUpdated(m_engine->providers().at(providerId)); } diff --git a/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp b/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp index fba177f4..ff79c261 100644 --- a/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp +++ b/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp @@ -40,7 +40,7 @@ #include "qgeotiledmappingmanagerengineosm.h" #include "qgeotilefetcherosm.h" #include "qgeotiledmaposm.h" -#include "qgeotileproviderosm.h" +#include "qgeofiletilecacheosm.h" #include <QtLocation/private/qgeocameracapabilities_p.h> #include <QtLocation/private/qgeomaptype_p.h> @@ -62,77 +62,120 @@ QGeoTiledMappingManagerEngineOsm::QGeoTiledMappingManagerEngineOsm(const QVarian setTileSize(QSize(256, 256)); QNetworkAccessManager *nm = new QNetworkAccessManager(); - QString domain = QStringLiteral("http://maps-redirect.qt.io/osm/5.6/"); + QString domain = QStringLiteral("http://maps-redirect.qt.io/osm/5.8/"); if (parameters.contains(QStringLiteral("osm.mapping.providersrepository.address"))) { QString customAddress = parameters.value(QStringLiteral("osm.mapping.providersrepository.address")).toString(); - // Allowing some malformed addresses ( containing the suffix "/osm/5.6/" + // Allowing some malformed addresses if (customAddress.indexOf(QStringLiteral(":")) < 0) // defaulting to http:// if no prefix is found 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; } - m_providers.push_back( - new QGeoTileProviderOsm(domain + "street", - nm, + bool highdpi = false; + if (parameters.contains(QStringLiteral("osm.mapping.highdpi_tiles"))) { + const QString param = parameters.value(QStringLiteral("osm.mapping.highdpi_tiles")).toString().toLower(); + if (param == "true") + highdpi = true; + } + + /* TileProviders setup */ + QVector<TileProvider *> providers_street; + QVector<TileProvider *> providers_satellite; + QVector<TileProvider *> providers_cycle; + QVector<TileProvider *> providers_transit; + QVector<TileProvider *> providers_nighttransit; + QVector<TileProvider *> providers_terrain; + QVector<TileProvider *> providers_hiking; + if (highdpi) { + providers_street.push_back(new TileProvider(domain + "street-hires", true)); + providers_satellite.push_back(new TileProvider(domain + "satellite-hires", true)); + providers_cycle.push_back(new TileProvider(domain + "cycle-hires", true)); + providers_transit.push_back(new TileProvider(domain + "transit-hires", true)); + providers_nighttransit.push_back(new TileProvider(domain + "night-transit-hires", true)); + providers_terrain.push_back(new TileProvider(domain + "terrain-hires", true)); + providers_hiking.push_back(new TileProvider(domain + "hiking-hires", true)); + } + providers_street.push_back(new TileProvider(domain + "street")); + providers_satellite.push_back(new TileProvider(domain + "satellite")); + providers_cycle.push_back(new TileProvider(domain + "cycle")); + providers_transit.push_back(new TileProvider(domain + "transit")); + providers_nighttransit.push_back(new TileProvider(domain + "night-transit")); + 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"))); + 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 */ + m_providers.push_back( new QGeoTileProviderOsm( nm, QGeoMapType(QGeoMapType::StreetMap, tr("Street Map"), tr("Street map view in daylight mode"), false, false, 1), - QGeoTileProviderOsm::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") - ))); - m_providers.push_back( - new QGeoTileProviderOsm(domain + "satellite", - nm, + providers_street )); + m_providers.push_back( new QGeoTileProviderOsm( nm, QGeoMapType(QGeoMapType::SatelliteMapDay, tr("Satellite Map"), tr("Satellite map view in daylight mode"), false, false, 2), - QGeoTileProviderOsm::TileProvider() - )); - m_providers.push_back( - new QGeoTileProviderOsm(domain + "cycle", - nm, + providers_satellite )); + m_providers.push_back( new QGeoTileProviderOsm( nm, QGeoMapType(QGeoMapType::CycleMap, tr("Cycle Map"), tr("Cycle map view in daylight mode"), false, false, 3), - QGeoTileProviderOsm::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") - ))); - m_providers.push_back( - new QGeoTileProviderOsm(domain + "transit", - nm, + providers_cycle )); + m_providers.push_back( new QGeoTileProviderOsm( nm, QGeoMapType(QGeoMapType::TransitMap, tr("Transit Map"), tr("Public transit map view in daylight mode"), false, false, 4), - QGeoTileProviderOsm::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") - ))); - m_providers.push_back( - new QGeoTileProviderOsm(domain + "night-transit", - nm, + providers_transit )); + m_providers.push_back( new QGeoTileProviderOsm( nm, QGeoMapType(QGeoMapType::TransitMap, tr("Night Transit Map"), tr("Public transit map view in night mode"), false, true, 5), - QGeoTileProviderOsm::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") - ))); - m_providers.push_back( - new QGeoTileProviderOsm(domain + "terrain", - nm, + providers_nighttransit )); + m_providers.push_back( new QGeoTileProviderOsm( nm, QGeoMapType(QGeoMapType::TerrainMap, tr("Terrain Map"), tr("Terrain map view"), false, false, 6), - QGeoTileProviderOsm::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") - ))); - m_providers.push_back( - new QGeoTileProviderOsm(domain + "hiking", - nm, + providers_terrain )); + m_providers.push_back( new QGeoTileProviderOsm( nm, QGeoMapType(QGeoMapType::PedestrianMap, tr("Hiking Map"), tr("Hiking map view"), false, false, 7), - QGeoTileProviderOsm::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 )); if (parameters.contains(QStringLiteral("osm.mapping.custom.host")) || parameters.contains(QStringLiteral("osm.mapping.host"))) { @@ -154,14 +197,13 @@ QGeoTiledMappingManagerEngineOsm::QGeoTiledMappingManagerEngineOsm(const QVarian m_customCopyright = parameters.value(QStringLiteral("osm.mapping.copyright")).toString(); m_providers.push_back( - new QGeoTileProviderOsm("", - nm, + new QGeoTileProviderOsm( nm, QGeoMapType(QGeoMapType::CustomMap, tr("Custom URL Map"), tr("Custom url map view set via urlprefix parameter"), false, false, 8), - QGeoTileProviderOsm::TileProvider(tmsServer + QStringLiteral("%z/%x/%y.png"), + { new TileProvider(tmsServer + QStringLiteral("%z/%x/%y.png"), QStringLiteral("png"), mapCopyright, - dataCopyright - ))); + dataCopyright) } + )); m_providers.last()->disableRedirection(); } @@ -182,20 +224,87 @@ QGeoTiledMappingManagerEngineOsm::QGeoTiledMappingManagerEngineOsm(const QVarian } updateMapTypes(); + + /* TILE CACHE */ + if (parameters.contains(QStringLiteral("osm.mapping.cache.directory"))) { + m_cacheDirectory = parameters.value(QStringLiteral("osm.mapping.cache.directory")).toString(); + } else { + // managerName() is not yet set, we have to hardcode the plugin name below + m_cacheDirectory = QAbstractGeoTileCache::baseLocationCacheDirectory() + QLatin1String("osm"); + } + if (parameters.contains(QStringLiteral("osm.mapping.offline.directory"))) + m_offlineDirectory = parameters.value(QStringLiteral("osm.mapping.offline.directory")).toString(); + 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->setCostStrategyMemory(QGeoFileTileCache::ByteSize); + } + if (parameters.contains(QStringLiteral("osm.mapping.cache.memory.size"))) { + bool ok = false; + int cacheSize = parameters.value(QStringLiteral("osm.mapping.cache.memory.size")).toString().toInt(&ok); + 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); - QAbstractGeoTileCache *tileCache = new QGeoFileTileCache(QAbstractGeoTileCache::baseCacheDirectory() + QStringLiteral("osm")); - // 50mb of disk cache by default to minimize n. of accesses to public OSM servers - tileCache->setMaxDiskUsage(50 * 1024 * 1024); - setTileCache(tileCache); - *error = QGeoServiceProvider::NoError; errorString->clear(); } @@ -206,7 +315,10 @@ QGeoTiledMappingManagerEngineOsm::~QGeoTiledMappingManagerEngineOsm() QGeoMap *QGeoTiledMappingManagerEngineOsm::createMap() { - return new QGeoTiledMapOsm(this); + QGeoTiledMap *map = new QGeoTiledMapOsm(this); + connect(qobject_cast<QGeoFileTileCacheOsm *>(tileCache()), &QGeoFileTileCacheOsm::mapDataUpdated + , map, &QGeoTiledMap::clearScene); + return map; } const QVector<QGeoTileProviderOsm *> &QGeoTiledMappingManagerEngineOsm::providers() @@ -226,9 +338,8 @@ void QGeoTiledMappingManagerEngineOsm::onProviderResolutionFinished(const QGeoTi updateMapTypes(); } -void QGeoTiledMappingManagerEngineOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error) +void QGeoTiledMappingManagerEngineOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider) { - Q_UNUSED(error) if (!provider->isResolved()) return; updateMapTypes(); diff --git a/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.h b/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.h index 247e4377..b1f0a13c 100644 --- a/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.h +++ b/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.h @@ -65,7 +65,7 @@ public: protected Q_SLOTS: void onProviderResolutionFinished(const QGeoTileProviderOsm *provider); - void onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error); + void onProviderResolutionError(const QGeoTileProviderOsm *provider); protected: void updateMapTypes(); @@ -73,6 +73,8 @@ protected: private: QVector<QGeoTileProviderOsm *> m_providers; QString m_customCopyright; + QString m_cacheDirectory; + QString m_offlineDirectory; }; QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeotilefetcherosm.cpp b/src/plugins/geoservices/osm/qgeotilefetcherosm.cpp index 98621411..f7c25d61 100644 --- a/src/plugins/geoservices/osm/qgeotilefetcherosm.cpp +++ b/src/plugins/geoservices/osm/qgeotilefetcherosm.cpp @@ -43,6 +43,7 @@ #include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkRequest> #include <QtLocation/private/qgeotilespec_p.h> +#include <QtLocation/private/qgeotilefetcher_p_p.h> QT_BEGIN_NAMESPACE @@ -55,10 +56,30 @@ static bool providersResolved(const QVector<QGeoTileProviderOsm *> &providers) return true; } +class QGeoTileFetcherOsmPrivate : public QGeoTileFetcherPrivate +{ + Q_DECLARE_PUBLIC(QGeoTileFetcherOsm) +public: + QGeoTileFetcherOsmPrivate(); + virtual ~QGeoTileFetcherOsmPrivate(); + +private: + Q_DISABLE_COPY(QGeoTileFetcherOsmPrivate) +}; + +QGeoTileFetcherOsmPrivate::QGeoTileFetcherOsmPrivate() : QGeoTileFetcherPrivate() +{ +} + +QGeoTileFetcherOsmPrivate::~QGeoTileFetcherOsmPrivate() +{ +} + + QGeoTileFetcherOsm::QGeoTileFetcherOsm(const QVector<QGeoTileProviderOsm *> &providers, QNetworkAccessManager *nm, QObject *parent) -: QGeoTileFetcher(parent), m_userAgent("Qt Location based application"), +: QGeoTileFetcher(*new QGeoTileFetcherOsmPrivate(), parent), m_userAgent("Qt Location based application"), m_providers(providers), m_nm(nm), m_ready(true) { m_nm->setParent(this); @@ -69,6 +90,8 @@ QGeoTileFetcherOsm::QGeoTileFetcherOsm(const QVector<QGeoTileProviderOsm *> &pro this, &QGeoTileFetcherOsm::onProviderResolutionFinished); connect(provider, &QGeoTileProviderOsm::resolutionError, this, &QGeoTileFetcherOsm::onProviderResolutionError); + connect(provider, &QGeoTileProviderOsm::resolutionRequired, + this, &QGeoTileFetcherOsm::restartTimer, Qt::QueuedConnection); provider->resolveProvider(); } } @@ -100,9 +123,8 @@ void QGeoTileFetcherOsm::onProviderResolutionFinished(const QGeoTileProviderOsm emit providerDataUpdated(provider); } -void QGeoTileFetcherOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error) +void QGeoTileFetcherOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider) { - Q_UNUSED(error) if ((m_ready = providersResolved(m_providers))) { qWarning("QGeoTileFetcherOsm: all providers resolved"); readyUpdated(); @@ -110,6 +132,14 @@ void QGeoTileFetcherOsm::onProviderResolutionError(const QGeoTileProviderOsm *pr emit providerDataUpdated(provider); } +void QGeoTileFetcherOsm::restartTimer() +{ + Q_D(QGeoTileFetcherOsm); + + if (!d->queue_.isEmpty()) + d->timer_.start(0, this); +} + QGeoTiledMapReply *QGeoTileFetcherOsm::getTileImage(const QGeoTileSpec &spec) { int id = spec.mapId(); @@ -122,6 +152,9 @@ QGeoTiledMapReply *QGeoTileFetcherOsm::getTileImage(const QGeoTileSpec &spec) } id -= 1; // TODO: make OSM map ids start from 0. + if (spec.zoom() > m_providers[id]->maximumZoomLevel() || spec.zoom() < m_providers[id]->minimumZoomLevel()) + return Q_NULLPTR; + const QUrl url = m_providers[id]->tileAddress(spec.x(), spec.y(), spec.zoom()); QNetworkRequest request; request.setHeader(QNetworkRequest::UserAgentHeader, m_userAgent); diff --git a/src/plugins/geoservices/osm/qgeotilefetcherosm.h b/src/plugins/geoservices/osm/qgeotilefetcherosm.h index 8d69cc56..a7b89bad 100644 --- a/src/plugins/geoservices/osm/qgeotilefetcherosm.h +++ b/src/plugins/geoservices/osm/qgeotilefetcherosm.h @@ -47,10 +47,12 @@ QT_BEGIN_NAMESPACE class QNetworkAccessManager; +class QGeoTileFetcherOsmPrivate; class QGeoTileFetcherOsm : public QGeoTileFetcher { Q_OBJECT + Q_DECLARE_PRIVATE(QGeoTileFetcherOsm) friend class QGeoMapReplyOsm; friend class QGeoTiledMappingManagerEngineOsm; @@ -69,10 +71,11 @@ protected: protected Q_SLOTS: void onProviderResolutionFinished(const QGeoTileProviderOsm *provider); - void onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error); + void onProviderResolutionError(const QGeoTileProviderOsm *provider); + void restartTimer(); private: - QGeoTiledMapReply *getTileImage(const QGeoTileSpec &spec); + QGeoTiledMapReply *getTileImage(const QGeoTileSpec &spec) Q_DECL_OVERRIDE; void readyUpdated(); QByteArray m_userAgent; diff --git a/src/plugins/geoservices/osm/qgeotileproviderosm.cpp b/src/plugins/geoservices/osm/qgeotileproviderosm.cpp index 3d46a425..1989c44f 100644 --- a/src/plugins/geoservices/osm/qgeotileproviderosm.cpp +++ b/src/plugins/geoservices/osm/qgeotileproviderosm.cpp @@ -43,115 +43,89 @@ QT_BEGIN_NAMESPACE static const int maxValidZoom = 30; +static const QDateTime defaultTs = QDateTime::fromString(QStringLiteral("2016-06-01T00:00:00"), Qt::ISODate); -QGeoTileProviderOsm::QGeoTileProviderOsm(const QString &urlRedir, - QNetworkAccessManager *nm, - const QGeoMapType &mapType, - const QGeoTileProviderOsm::TileProvider &providerFallback) - : m_nm(nm), m_urlRedirector(urlRedir), - m_providerFallback(providerFallback), - m_mapType(mapType), m_status(Idle) +QGeoTileProviderOsm::QGeoTileProviderOsm(QNetworkAccessManager *nm, + const QGeoMapType &mapType, + const QVector<TileProvider *> &providers) +: m_nm(nm), m_provider(nullptr), m_mapType(mapType), m_status(Idle) { - if (!m_urlRedirector.isValid()) - disableRedirection(); + for (int i = 0; i < providers.size(); ++i) { + TileProvider *p = providers[i]; + if (!m_provider) + m_providerId = i; + addProvider(p); + } + + if (!m_provider || m_provider->isValid()) + m_status = Resolved; } QGeoTileProviderOsm::~QGeoTileProviderOsm() { - } -void QGeoTileProviderOsm::resolveProvider() +QUrl QGeoTileProviderOsm::tileAddress(int x, int y, int z) const { - switch (m_status) { - case Resolving: - case Invalid: - case Valid: - return; - case Idle: - m_status = Resolving; - break; - } + if (m_status != Resolved || !m_provider) + return QUrl(); + return m_provider->tileAddress(x, y, z); +} - QNetworkRequest request; - request.setHeader(QNetworkRequest::UserAgentHeader, QByteArrayLiteral("QGeoTileFetcherOsm")); - request.setUrl(m_urlRedirector); - QNetworkReply *reply = m_nm->get(request); - connect(reply, SIGNAL(finished()), this, SLOT(onNetworkReplyFinished())); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), - this, SLOT(onNetworkReplyError(QNetworkReply::NetworkError))); +QString QGeoTileProviderOsm::mapCopyRight() const +{ + if (m_status != Resolved || !m_provider) + return QString(); + return m_provider->mapCopyRight(); } -void QGeoTileProviderOsm::disableRedirection() +QString QGeoTileProviderOsm::dataCopyRight() const { - m_status = Invalid; - m_provider.m_valid = false; + if (m_status != Resolved || !m_provider) + return QString(); + return m_provider->dataCopyRight(); } -void QGeoTileProviderOsm::handleError(QNetworkReply::NetworkError error) +QString QGeoTileProviderOsm::styleCopyRight() const { - switch (error) { - case QNetworkReply::ConnectionRefusedError: - case QNetworkReply::TooManyRedirectsError: - case QNetworkReply::InsecureRedirectError: - case QNetworkReply::ContentAccessDenied: - case QNetworkReply::ContentOperationNotPermittedError: - case QNetworkReply::ContentNotFoundError: - case QNetworkReply::AuthenticationRequiredError: - case QNetworkReply::ContentGoneError: - case QNetworkReply::OperationNotImplementedError: - case QNetworkReply::ServiceUnavailableError: - // Errors we don't expect to recover from in the near future, which - // prevent accessing the redirection info but not the actual providers. - m_status = Invalid; - default: - break; - } + if (m_status != Resolved || !m_provider) + return QString(); + return m_provider->styleCopyRight(); } -QUrl QGeoTileProviderOsm::tileAddress(int x, int y, int z) const +QString QGeoTileProviderOsm::format() const { - if (m_provider.isValid()) - return m_provider.tileAddress(x,y,z); - if (m_providerFallback.isValid()) - return m_providerFallback.tileAddress(x,y,z); - return QUrl(); + if (m_status != Resolved || !m_provider) + return QString(); + return m_provider->format(); } -QString QGeoTileProviderOsm::mapCopyRight() const +int QGeoTileProviderOsm::minimumZoomLevel() const { - if (m_provider.isValid()) - return m_provider.mapCopyRight(); - if (m_providerFallback.isValid()) - return m_providerFallback.mapCopyRight(); - return QString(); + if (m_status != Resolved || !m_provider) + return 0; + return m_provider->minimumZoomLevel(); } -QString QGeoTileProviderOsm::dataCopyRight() const +int QGeoTileProviderOsm::maximumZoomLevel() const { - if (m_provider.isValid()) - return m_provider.dataCopyRight(); - if (m_providerFallback.isValid()) - return m_providerFallback.dataCopyRight(); - return QString(); + if (m_status != Resolved || !m_provider) + return 20; + return m_provider->maximumZoomLevel(); } -QString QGeoTileProviderOsm::styleCopyRight() const +bool QGeoTileProviderOsm::isHighDpi() const { - if (m_provider.isValid()) - return m_provider.styleCopyRight(); - if (m_providerFallback.isValid()) - return m_providerFallback.styleCopyRight(); - return QString(); + if (!m_provider) + return false; + return m_provider->isHighDpi(); } -QString QGeoTileProviderOsm::format() const +const QDateTime QGeoTileProviderOsm::timestamp() const { - if (m_provider.isValid()) - return m_provider.format(); - if (m_providerFallback.isValid()) - return m_providerFallback.format(); - return QString(); + if (!m_provider) + return QDateTime(); + return m_provider->timestamp(); } const QGeoMapType &QGeoTileProviderOsm::mapType() const @@ -161,33 +135,218 @@ const QGeoMapType &QGeoTileProviderOsm::mapType() const bool QGeoTileProviderOsm::isValid() const { - return (m_provider.isValid() || m_providerFallback.isValid()); + if (m_status != Resolved || !m_provider) + return false; + return m_provider->isValid(); } bool QGeoTileProviderOsm::isResolved() const { - return (m_status == Valid || m_status == Invalid); + return (m_status == Resolved); +} + +void QGeoTileProviderOsm::resolveProvider() +{ + if (m_status == Resolved || m_status == Resolving) + return; + + m_status = Resolving; + // Provider can't be null while on Idle status. + connect(m_provider, &TileProvider::resolutionFinished, this, &QGeoTileProviderOsm::onResolutionFinished); + connect(m_provider, &TileProvider::resolutionError, this, &QGeoTileProviderOsm::onResolutionError); + m_provider->resolveProvider(); +} + +void QGeoTileProviderOsm::disableRedirection() +{ + if (m_provider && m_provider->isValid()) + return; + bool found = false; + for (TileProvider *p: m_providerList) { + if (p->isValid() && !found) { + m_provider = p; + found = true; + } + p->disconnect(this); + } +} + +void QGeoTileProviderOsm::onResolutionFinished(TileProvider *provider) +{ + Q_UNUSED(provider) + // provider and m_provider are the same, at this point. m_status is Resolving. + m_status = Resolved; + emit resolutionFinished(this); +} + +void QGeoTileProviderOsm::onResolutionError(TileProvider *provider) +{ + Q_UNUSED(provider) + // provider and m_provider are the same at this point. m_status is Resolving. + if (m_provider->isInvalid()) { + m_provider = nullptr; + m_status = Resolved; + if (m_providerId >= m_providerList.size() -1) { // no hope left + emit resolutionError(this); + return; + } + // Advance the pointer in the provider list, and possibly start resolution on the next in the list. + for (int i = m_providerId + 1; i < m_providerList.size(); ++i) { + m_providerId = i; + TileProvider *p = m_providerList[m_providerId]; + if (!p->isInvalid()) { + m_provider = p; + if (!p->isValid()) { + m_status = Idle; +#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; + } + } + if (!m_provider) + emit resolutionError(this); + } else if (m_provider->isValid()) { + m_status = Resolved; + emit resolutionFinished(this); + } else { // still not resolved. But network error is recoverable. + m_status = Idle; +#if 0 // leaving triggering the retry to the tile fetcher + m_provider->resolveProvider(); +#endif + } +} + +void QGeoTileProviderOsm::addProvider(TileProvider *provider) +{ + if (!provider) + return; + QScopedPointer<TileProvider> p(provider); + if (provider->status() == TileProvider::Invalid) + return; // if the provider is already resolved and invalid, no point in adding it. + + provider = p.take(); + provider->setNetworkManager(m_nm); + provider->setParent(this); + m_providerList.append(provider); + if (!m_provider) + m_provider = provider; +} + + +/* + Class TileProvder +*/ + +static void sort2(int &a, int &b) +{ + if (a > b) { + int temp=a; + a=b; + b=temp; + } +} + +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_timestamp(defaultTs), m_highDpi(highDpi) +{ + if (!m_urlRedirector.isValid()) + m_status = Invalid; +} + +TileProvider::TileProvider(const QString &urlTemplate, + const QString &format, + const QString ©RightMap, + const QString ©RightData, + bool highDpi, + int minimumZoomLevel, + 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_timestamp(defaultTs), m_highDpi(highDpi) +{ + setupProvider(); +} + +TileProvider::~TileProvider() +{ +} + +void TileProvider::resolveProvider() +{ + if (!m_nm) + return; + + switch (m_status) { + case Resolving: + case Invalid: + case Valid: + return; + case Idle: + m_status = Resolving; + break; + } + + QNetworkRequest request; + request.setHeader(QNetworkRequest::UserAgentHeader, QByteArrayLiteral("QGeoTileFetcherOsm")); + request.setUrl(m_urlRedirector); + request.setAttribute(QNetworkRequest::BackgroundRequestAttribute, true); + QNetworkReply *reply = m_nm->get(request); + connect(reply, SIGNAL(finished()), this, SLOT(onNetworkReplyFinished()) ); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onNetworkReplyError(QNetworkReply::NetworkError))); +} + +void TileProvider::handleError(QNetworkReply::NetworkError error) +{ + switch (error) { + case QNetworkReply::ConnectionRefusedError: + case QNetworkReply::TooManyRedirectsError: + case QNetworkReply::InsecureRedirectError: + case QNetworkReply::ContentAccessDenied: + case QNetworkReply::ContentOperationNotPermittedError: + case QNetworkReply::ContentNotFoundError: + case QNetworkReply::AuthenticationRequiredError: + case QNetworkReply::ContentGoneError: + case QNetworkReply::OperationNotImplementedError: + case QNetworkReply::ServiceUnavailableError: + // Errors we don't expect to recover from in the near future, which + // prevent accessing the redirection info but not the actual providers. + m_status = Invalid; + default: + //qWarning() << "QGeoTileProviderOsm network error:" << error; + break; + } } -void QGeoTileProviderOsm::onNetworkReplyFinished() +void TileProvider::onNetworkReplyFinished() { QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); reply->deleteLater(); switch (m_status) { - case Resolving: - m_status = Idle; - case Idle: // should not happen - case Invalid: // should not happen - break; - case Valid: // should not happen - return; + case Resolving: + m_status = Idle; + case Idle: // should not happen + case Invalid: // should not happen + break; + case Valid: // should not happen + emit resolutionFinished(this); + return; } + QObject errorEmitter; + QMetaObject::Connection errorEmitterConnection = connect(&errorEmitter, &QObject::destroyed, [this](){ this->resolutionError(this); }); + if (reply->error() != QNetworkReply::NoError) { handleError(reply->error()); - if (m_status == Invalid) - emit resolutionError(this, reply->error()); return; } m_status = Invalid; @@ -205,48 +364,51 @@ void QGeoTileProviderOsm::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 * unavailable, without making the osm plugin fire requests to it. Default is true. * * MinimumZoomLevel and MaximumZoomLevel are also optional, and allow to prevent invalid tile - * requests to the providers, if they do not support the specific ZL. Default is 0 and 19, + * 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; QJsonDocument d = QJsonDocument::fromJson(reply->readAll(), &error); if (error.error != QJsonParseError::NoError) { qWarning() << "QGeoTileProviderOsm: Error parsing redirection data: "<<error.errorString() << "at "<<m_urlRedirector; - emit resolutionFinished(this); return; } if (!d.isObject()) { qWarning() << "QGeoTileProviderOsm: Invalid redirection data" << "at "<<m_urlRedirector; - emit resolutionFinished(this); return; } const QJsonObject json = d.object(); @@ -263,56 +425,200 @@ void QGeoTileProviderOsm::onNetworkReplyFinished() || !copyRightMap.isString() || !copyRightData.isString()) { qWarning() << "QGeoTileProviderOsm: Incomplete redirection data" << "at "<<m_urlRedirector; - emit resolutionFinished(this); return; } + m_urlTemplate = urlTemplate.toString(); + m_format = imageFormat.toString(); + m_copyRightMap = copyRightMap.toString(); + m_copyRightData = copyRightData.toString(); + const QJsonValue enabled = json.value(QLatin1String("Enabled")); if (enabled.isBool() && ! enabled.toBool()) { qWarning() << "QGeoTileProviderOsm: Tileserver disabled" << "at "<<m_urlRedirector; - emit resolutionFinished(this); return; } - QString styleCopyRight; const QJsonValue copyRightStyle = json.value(QLatin1String("StyleCopyRight")); if (copyRightStyle != QJsonValue::Undefined && copyRightStyle.isString()) - styleCopyRight = copyRightStyle.toString(); + m_copyRightStyle = copyRightStyle.toString(); - int minZL = 0; - int maxZL = 19; + m_minimumZoomLevel = 0; + m_maximumZoomLevel = 20; const QJsonValue minZoom = json.value(QLatin1String("MinimumZoomLevel")); if (minZoom.isDouble()) - minZL = qBound(0, int(minZoom.toDouble()), maxValidZoom); + m_minimumZoomLevel = qBound(0, int(minZoom.toDouble()), maxValidZoom); const QJsonValue maxZoom = json.value(QLatin1String("MaximumZoomLevel")); if (maxZoom.isDouble()) - maxZL = qBound(0, int(maxZoom.toDouble()), maxValidZoom); + m_maximumZoomLevel = qBound(0, int(maxZoom.toDouble()), maxValidZoom); - m_provider = TileProvider(urlTemplate.toString(), - imageFormat.toString(), - copyRightMap.toString(), - copyRightData.toString(), - minZL, - maxZL); - m_provider.setStyleCopyRight(styleCopyRight); + const QJsonValue ts = json.value(QLatin1String("Timestamp")); + if (ts.isString()) + m_timestamp = QDateTime::fromString(ts.toString(), Qt::ISODate); - if (m_provider.isValid()) - m_status = Valid; - - emit resolutionFinished(this); + setupProvider(); + if (isValid()) { + QObject::disconnect(errorEmitterConnection); + emit resolutionFinished(this); + } } -void QGeoTileProviderOsm::onNetworkReplyError(QNetworkReply::NetworkError error) +void TileProvider::onNetworkReplyError(QNetworkReply::NetworkError error) { if (m_status == Resolving) m_status = Idle; - qWarning() << "QGeoTileProviderOsm::onNetworkReplyError " << error; handleError(error); - static_cast<QNetworkReply *>(sender())->deleteLater(); - if (m_status == Invalid) - emit resolutionError(this, error); + emit resolutionError(this); +} + +void TileProvider::setupProvider() +{ + if (m_urlTemplate.isEmpty()) + return; + + if (m_format.isEmpty()) + return; + + if (m_minimumZoomLevel < 0 || m_minimumZoomLevel > 30) + return; + + if (m_maximumZoomLevel < 0 || m_maximumZoomLevel > 30 || m_maximumZoomLevel < m_minimumZoomLevel) + return; + + // Currently supporting only %x, %y and &z + int offset[3]; + offset[0] = m_urlTemplate.indexOf(QLatin1String("%x")); + if (offset[0] < 0) + return; + + offset[1] = m_urlTemplate.indexOf(QLatin1String("%y")); + if (offset[1] < 0) + return; + + offset[2] = m_urlTemplate.indexOf(QLatin1String("%z")); + if (offset[2] < 0) + return; + + int sortedOffsets[3]; + std::copy(offset, offset + 3, sortedOffsets); + sort2(sortedOffsets[0] ,sortedOffsets[1]); + sort2(sortedOffsets[1] ,sortedOffsets[2]); + sort2(sortedOffsets[0] ,sortedOffsets[1]); + + int min = sortedOffsets[0]; + int max = sortedOffsets[2]; + int mid = sortedOffsets[1]; + + // Initing LUT + for (int i=0; i<3; i++) { + if (offset[0] == sortedOffsets[i]) + paramsLUT[i] = 0; + else if (offset[1] == sortedOffsets[i]) + paramsLUT[i] = 1; + else + paramsLUT[i] = 2; + } + + m_urlPrefix = m_urlTemplate.mid(0 , min); + m_urlSuffix = m_urlTemplate.mid(max + 2, m_urlTemplate.size() - max - 2); + + paramsSep[0] = m_urlTemplate.mid(min + 2, mid - min - 2); + paramsSep[1] = m_urlTemplate.mid(mid + 2, max - mid - 2); + m_status = Valid; +} + +bool TileProvider::isValid() const +{ + return m_status == Valid; +} + +bool TileProvider::isInvalid() const +{ + return m_status == Invalid; +} + +bool TileProvider::isResolved() const +{ + return (m_status == Valid || m_status == Invalid); +} + +QString TileProvider::mapCopyRight() const +{ + return m_copyRightMap; +} + +QString TileProvider::dataCopyRight() const +{ + return m_copyRightData; +} + +QString TileProvider::styleCopyRight() const +{ + return m_copyRightStyle; +} + +QString TileProvider::format() const +{ + return m_format; +} + +int TileProvider::minimumZoomLevel() const +{ + return m_minimumZoomLevel; +} + +int TileProvider::maximumZoomLevel() const +{ + return m_maximumZoomLevel; } +const QDateTime &TileProvider::timestamp() const +{ + return m_timestamp; +} + +bool TileProvider::isHighDpi() const +{ + return m_highDpi; +} + +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) + return QUrl(); + int params[3] = { x, y, z}; + QString url; + url += m_urlPrefix; + url += QString::number(params[paramsLUT[0]]); + url += paramsSep[0]; + url += QString::number(params[paramsLUT[1]]); + url += paramsSep[1]; + url += QString::number(params[paramsLUT[2]]); + url += m_urlSuffix; + return QUrl(url); +} + +void TileProvider::setNetworkManager(QNetworkAccessManager *nm) +{ + m_nm = nm; +} + +TileProvider::Status TileProvider::status() const +{ + return m_status; +} + + QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeotileproviderosm.h b/src/plugins/geoservices/osm/qgeotileproviderosm.h index f396b3b5..b8647244 100644 --- a/src/plugins/geoservices/osm/qgeotileproviderosm.h +++ b/src/plugins/geoservices/osm/qgeotileproviderosm.h @@ -40,215 +40,148 @@ #include <QtLocation/private/qgeomaptype_p.h> #include <QtCore/QUrl> +#include <QtCore/QVector> #include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> #include <QtCore/QPointer> #include <QTimer> #include <algorithm> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> +#include <QDateTime> QT_BEGIN_NAMESPACE -class QGeoTileProviderOsm: public QObject +class TileProvider: public QObject { Q_OBJECT - - friend class QGeoTileFetcherOsm; - friend class QGeoMapReplyOsm; - friend class QGeoTiledMappingManagerEngineOsm; public: - struct TileProvider { - - static inline void sort2(int &a, int &b) - { - if (a > b) { - int temp=a; - a=b; - b=temp; - } - } - - TileProvider() : m_valid(false) - { - - } - - TileProvider(const QString &urlTemplate, - const QString &format, - const QString ©RightMap, - const QString ©RightData, - int minimumZoomLevel = 0, - int maximumZoomLevel = 19) : m_valid(false) - { - if (urlTemplate.isEmpty()) - return; - m_urlTemplate = urlTemplate; - - if (format.isEmpty()) - return; - m_format = format; - - m_copyRightMap = copyRightMap; - m_copyRightData = copyRightData; - - if (minimumZoomLevel < 0 || minimumZoomLevel > 30) - return; - m_minimumZoomLevel = minimumZoomLevel; - - if (maximumZoomLevel < 0 || maximumZoomLevel > 30 || maximumZoomLevel < minimumZoomLevel) - return; - m_maximumZoomLevel = maximumZoomLevel; - - // Currently supporting only %x, %y and &z - int offset[3]; - offset[0] = m_urlTemplate.indexOf(QLatin1String("%x")); - if (offset[0] < 0) - return; - - offset[1] = m_urlTemplate.indexOf(QLatin1String("%y")); - if (offset[1] < 0) - return; - - offset[2] = m_urlTemplate.indexOf(QLatin1String("%z")); - if (offset[2] < 0) - return; - - int sortedOffsets[3]; - std::copy(offset, offset + 3, sortedOffsets); - sort2(sortedOffsets[0] ,sortedOffsets[1]); - sort2(sortedOffsets[1] ,sortedOffsets[2]); - sort2(sortedOffsets[0] ,sortedOffsets[1]); - - int min = sortedOffsets[0]; - int max = sortedOffsets[2]; - int mid = sortedOffsets[1]; - - // Initing LUT - for (int i=0; i<3; i++) { - if (offset[0] == sortedOffsets[i]) - paramsLUT[i] = 0; - else if (offset[1] == sortedOffsets[i]) - paramsLUT[i] = 1; - else - paramsLUT[i] = 2; - } - - m_urlPrefix = m_urlTemplate.mid(0 , min); - m_urlSuffix = m_urlTemplate.mid(max + 2, m_urlTemplate.size() - max - 2); - - paramsSep[0] = m_urlTemplate.mid(min + 2, mid - min - 2); - paramsSep[1] = m_urlTemplate.mid(mid + 2, max - mid - 2); - m_valid = true; - } - - ~TileProvider() - { - } - - inline bool isValid() const - { - return m_valid; - } + enum Status {Idle, + Resolving, + Valid, + Invalid }; - inline QString mapCopyRight() const - { - return m_copyRightMap; - } + TileProvider(); + // "Online" constructor. Needs resolution to fetch the parameters + TileProvider(const QUrl &urlRedirector, bool highDpi = false); + // Offline constructor. Doesn't need URLRedirector and networkmanager + TileProvider(const QString &urlTemplate, + const QString &format, + const QString ©RightMap, + const QString ©RightData, + bool highDpi = false, + int minimumZoomLevel = 0, + int maximumZoomLevel = 19); + + ~TileProvider(); + void setNetworkManager(QNetworkAccessManager *nm); - inline QString dataCopyRight() const - { - return m_copyRightData; - } + void resolveProvider(); + void handleError(QNetworkReply::NetworkError error); + void setupProvider(); + + inline bool isValid() const; + inline bool isInvalid() const; + inline bool isResolved() const; + inline Status status() const; + + inline QString mapCopyRight() const; + inline QString dataCopyRight() const; + inline QString styleCopyRight() const; + 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; - inline QString styleCopyRight() const - { - return m_copyRightStyle; - } + // Optional properties, not needed to construct a provider + void setStyleCopyRight(const QString ©right); + void setTimestamp(const QDateTime ×tamp); - inline QString format() const - { - return m_format; - } + Status m_status; + QUrl m_urlRedirector; // The URL from where to fetch the URL template in case of a provider to resolve. + QNetworkAccessManager *m_nm; + QString m_urlTemplate; + QString m_format; + QString m_copyRightMap; + QString m_copyRightData; + QString m_copyRightStyle; + QString m_urlPrefix; + 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 + QString paramsSep[2]; // what goes in between %x, %y and %z - // Optional properties, not needed to construct a provider - void setStyleCopyRight(const QString ©right) - { - m_copyRightStyle = copyright; - } +Q_SIGNALS: + void resolutionFinished(TileProvider *provider); + void resolutionError(TileProvider *provider); - QUrl tileAddress(int x, int y, int z) const - { - if (z < m_minimumZoomLevel || z > m_maximumZoomLevel) - return QUrl(); - int params[3] = { x, y, z}; - QString url; - url += m_urlPrefix; - url += QString::number(params[paramsLUT[0]]); - url += paramsSep[0]; - url += QString::number(params[paramsLUT[1]]); - url += paramsSep[1]; - url += QString::number(params[paramsLUT[2]]); - url += m_urlSuffix; - return QUrl(url); - } +public Q_SLOTS: + void onNetworkReplyFinished(); + void onNetworkReplyError(QNetworkReply::NetworkError error); - bool m_valid; - QString m_urlTemplate; - QString m_format; - QString m_copyRightMap; - QString m_copyRightData; - QString m_copyRightStyle; - QString m_urlPrefix; - QString m_urlSuffix; - int m_minimumZoomLevel; - int m_maximumZoomLevel; +friend class QGeoTileProviderOsm; +}; - int paramsLUT[3]; //Lookup table to handle possibly shuffled x,y,z - QString paramsSep[2]; // what goes in between %x, %y and %z - }; +class QGeoTileProviderOsm: public QObject +{ + Q_OBJECT + friend class QGeoTileFetcherOsm; + friend class QGeoMapReplyOsm; + friend class QGeoTiledMappingManagerEngineOsm; +public: enum Status {Idle, Resolving, - Valid, - Invalid }; - - QGeoTileProviderOsm(const QString &urlRedir, - QNetworkAccessManager *nm, - const QGeoMapType &mapType, - const TileProvider &providerFallback); + Resolved }; + QGeoTileProviderOsm(QNetworkAccessManager *nm, + const QGeoMapType &mapType, + const QVector<TileProvider *> &providers); ~QGeoTileProviderOsm(); - - QUrl tileAddress(int x, int y, int z) const; QString mapCopyRight() const; QString dataCopyRight() const; QString styleCopyRight() const; QString format() const; + int minimumZoomLevel() const; + int maximumZoomLevel() const; + bool isHighDpi() const; const QGeoMapType &mapType() const; bool isValid() const; bool isResolved() const; + const QDateTime timestamp() const; Q_SIGNALS: void resolutionFinished(const QGeoTileProviderOsm *provider); - void resolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error); + void resolutionError(const QGeoTileProviderOsm *provider); + void resolutionRequired(); public Q_SLOTS: - void onNetworkReplyFinished(); - void onNetworkReplyError(QNetworkReply::NetworkError error); void resolveProvider(); + void disableRedirection(); + +protected Q_SLOTS: + void onResolutionFinished(TileProvider *provider); + void onResolutionError(TileProvider *provider); protected: - void disableRedirection(); - void handleError(QNetworkReply::NetworkError error); + void addProvider(TileProvider *provider); + +/* Data members */ QNetworkAccessManager *m_nm; - QUrl m_urlRedirector; // The URL from where to fetch the URL template - TileProvider m_provider; - TileProvider m_providerFallback; + QVector<TileProvider *> m_providerList; + TileProvider *m_provider; + int m_providerId; QGeoMapType m_mapType; Status m_status; - QTimer m_retryTimer; }; QT_END_NAMESPACE |