1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Aaron McCarthy <mccarthy.aaron@gmail.com>
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtLocation module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qgeotiledmappingmanagerengineosm.h"
41 #include "qgeotilefetcherosm.h"
42 #include "qgeotiledmaposm.h"
43 #include "qgeofiletilecacheosm.h"
44 
45 #include <QtLocation/private/qgeocameracapabilities_p.h>
46 #include <QtLocation/private/qgeomaptype_p.h>
47 #include <QtLocation/private/qgeotiledmap_p.h>
48 #include <QtLocation/private/qgeofiletilecache_p.h>
49 
50 #include <QtNetwork/QNetworkAccessManager>
51 #include <QtNetwork/QNetworkDiskCache>
52 
53 QT_BEGIN_NAMESPACE
54 
QGeoTiledMappingManagerEngineOsm(const QVariantMap & parameters,QGeoServiceProvider::Error * error,QString * errorString)55 QGeoTiledMappingManagerEngineOsm::QGeoTiledMappingManagerEngineOsm(const QVariantMap &parameters, QGeoServiceProvider::Error *error, QString *errorString)
56 :   QGeoTiledMappingManagerEngine()
57 {
58     QGeoCameraCapabilities cameraCaps;
59     cameraCaps.setMinimumZoomLevel(0.0);
60     cameraCaps.setMaximumZoomLevel(19.0);
61     cameraCaps.setSupportsBearing(true);
62     cameraCaps.setSupportsTilting(true);
63     cameraCaps.setMinimumTilt(0);
64     cameraCaps.setMaximumTilt(80);
65     cameraCaps.setMinimumFieldOfView(20.0);
66     cameraCaps.setMaximumFieldOfView(120.0);
67     cameraCaps.setOverzoomEnabled(true);
68     setCameraCapabilities(cameraCaps);
69 
70     setTileSize(QSize(256, 256));
71 
72     const QByteArray pluginName = "osm";
73     if (parameters.contains(QStringLiteral("osm.mapping.cache.directory"))) {
74         m_cacheDirectory = parameters.value(QStringLiteral("osm.mapping.cache.directory")).toString();
75     } else {
76         // managerName() is not yet set, we have to hardcode the plugin name below
77         m_cacheDirectory = QAbstractGeoTileCache::baseLocationCacheDirectory() + QLatin1String(pluginName);
78     }
79     QNetworkAccessManager *nmCached = new QNetworkAccessManager(this);
80     QNetworkDiskCache *diskCache = new QNetworkDiskCache(this);
81     diskCache->setCacheDirectory(m_cacheDirectory + QLatin1String("/providers"));
82     diskCache->setMaximumCacheSize(100000000000); // enough to prevent diskCache to fiddle with tile cache. it's anyway used only for providers.
83     nmCached->setCache(diskCache);
84 
85     QNetworkAccessManager *nm = new QNetworkAccessManager(); // Gets owned by QGeoTileFetcherOsm
86     QString domain = QStringLiteral("http://maps-redirect.qt.io/osm/5.8/");
87     if (parameters.contains(QStringLiteral("osm.mapping.providersrepository.address"))) {
88         QString customAddress = parameters.value(QStringLiteral("osm.mapping.providersrepository.address")).toString();
89         // Allowing some malformed addresses
90         if (customAddress.indexOf(QStringLiteral(":")) < 0) // defaulting to http:// if no prefix is found
91             customAddress = QStringLiteral("http://") + customAddress;
92         if (customAddress[customAddress.length()-1] != QLatin1Char('/'))
93             customAddress += QLatin1Char('/');
94         if (QUrl(customAddress).isValid())
95             domain = customAddress;
96         else
97             qWarning() << "Invalid custom providers repository address: " << customAddress;
98     }
99 
100     bool highdpi = false;
101     if (parameters.contains(QStringLiteral("osm.mapping.highdpi_tiles"))) {
102         const QString param = parameters.value(QStringLiteral("osm.mapping.highdpi_tiles")).toString().toLower();
103         if (param == "true")
104             highdpi = true;
105     }
106 
107     /* TileProviders setup */
108     QVector<TileProvider *> providers_street;
109     QVector<TileProvider *> providers_satellite;
110     QVector<TileProvider *> providers_cycle;
111     QVector<TileProvider *> providers_transit;
112     QVector<TileProvider *> providers_nighttransit;
113     QVector<TileProvider *> providers_terrain;
114     QVector<TileProvider *> providers_hiking;
115     if (highdpi) {
116         providers_street.push_back(new TileProvider(domain + "street-hires", true));
117         providers_satellite.push_back(new TileProvider(domain + "satellite-hires", true));
118         providers_cycle.push_back(new TileProvider(domain + "cycle-hires", true));
119         providers_transit.push_back(new TileProvider(domain + "transit-hires", true));
120         providers_nighttransit.push_back(new TileProvider(domain + "night-transit-hires", true));
121         providers_terrain.push_back(new TileProvider(domain + "terrain-hires", true));
122         providers_hiking.push_back(new TileProvider(domain + "hiking-hires", true));
123     }
124     providers_street.push_back(new TileProvider(domain + "street"));
125     providers_satellite.push_back(new TileProvider(domain + "satellite"));
126     providers_cycle.push_back(new TileProvider(domain + "cycle"));
127     providers_transit.push_back(new TileProvider(domain + "transit"));
128     providers_nighttransit.push_back(new TileProvider(domain + "night-transit"));
129     providers_terrain.push_back(new TileProvider(domain + "terrain"));
130     providers_hiking.push_back(new TileProvider(domain + "hiking"));
131     // Backups
132     const QDateTime defaultTs = QDateTime::fromString(QStringLiteral("2016-06-01T00:00:00"), Qt::ISODate);
133     providers_street.push_back(
134         new TileProvider(QStringLiteral("http://c.tile.openstreetmap.org/%z/%x/%y.png"),
135             QStringLiteral("png"),
136             QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap.org</a>"),
137             QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors")));
138     providers_street.back()->setTimestamp(defaultTs);
139 
140     // No available open access satellite backup with satisfactory level of details at the present.
141 
142     providers_cycle.push_back(
143         new TileProvider(QStringLiteral("http://c.tile.opencyclemap.org/cycle/%z/%x/%y.png"),
144             QStringLiteral("png"),
145             QStringLiteral("<a href='http://www.thunderforest.com/'>Thunderforest</a>"),
146             QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors")));
147     providers_cycle.back()->setTimestamp(defaultTs);
148 
149     providers_transit.push_back(
150         new TileProvider(QStringLiteral("http://c.tile2.opencyclemap.org/transport/%z/%x/%y.png"),
151             QStringLiteral("png"),
152             QStringLiteral("<a href='http://www.thunderforest.com/'>Thunderforest</a>"),
153             QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors")));
154     providers_transit.back()->setTimestamp(defaultTs);
155 
156     providers_nighttransit.push_back(
157         new TileProvider(QStringLiteral("http://a.tile.thunderforest.com/transport-dark/%z/%x/%y.png"),
158             QStringLiteral("png"),
159             QStringLiteral("<a href='http://www.thunderforest.com/'>Thunderforest</a>"),
160             QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors")) );
161     providers_nighttransit.back()->setTimestamp(defaultTs);
162 
163     providers_terrain.push_back(
164         new TileProvider(QStringLiteral("http://a.tile.thunderforest.com/landscape/%z/%x/%y.png"),
165             QStringLiteral("png"),
166             QStringLiteral("<a href='http://www.thunderforest.com/'>Thunderforest</a>"),
167             QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors")));
168     providers_terrain.back()->setTimestamp(defaultTs);
169 
170     providers_hiking.push_back(
171         new TileProvider(QStringLiteral("http://a.tile.thunderforest.com/outdoors/%z/%x/%y.png"),
172             QStringLiteral("png"),
173             QStringLiteral("<a href='http://www.thunderforest.com/'>Thunderforest</a>"),
174             QStringLiteral("<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors")));
175     providers_hiking.back()->setTimestamp(defaultTs);
176 
177 
178     /* QGeoTileProviderOsms setup */
179     m_providers.push_back( new QGeoTileProviderOsm( nmCached,
180             QGeoMapType(QGeoMapType::StreetMap, tr("Street Map"), tr("Street map view in daylight mode"), false, false, 1, pluginName, cameraCaps),
181             providers_street, cameraCaps ));
182     m_providers.push_back( new QGeoTileProviderOsm( nmCached,
183             QGeoMapType(QGeoMapType::SatelliteMapDay, tr("Satellite Map"), tr("Satellite map view in daylight mode"), false, false, 2, pluginName, cameraCaps),
184             providers_satellite, cameraCaps ));
185     m_providers.push_back( new QGeoTileProviderOsm( nmCached,
186             QGeoMapType(QGeoMapType::CycleMap, tr("Cycle Map"), tr("Cycle map view in daylight mode"), false, false, 3, pluginName, cameraCaps),
187             providers_cycle, cameraCaps ));
188     m_providers.push_back( new QGeoTileProviderOsm( nmCached,
189             QGeoMapType(QGeoMapType::TransitMap, tr("Transit Map"), tr("Public transit map view in daylight mode"), false, false, 4, pluginName, cameraCaps),
190             providers_transit, cameraCaps ));
191     m_providers.push_back( new QGeoTileProviderOsm( nmCached,
192             QGeoMapType(QGeoMapType::TransitMap, tr("Night Transit Map"), tr("Public transit map view in night mode"), false, true, 5, pluginName, cameraCaps),
193             providers_nighttransit, cameraCaps ));
194     m_providers.push_back( new QGeoTileProviderOsm( nmCached,
195             QGeoMapType(QGeoMapType::TerrainMap, tr("Terrain Map"), tr("Terrain map view"), false, false, 6, pluginName, cameraCaps),
196             providers_terrain, cameraCaps ));
197     m_providers.push_back( new QGeoTileProviderOsm( nmCached,
198             QGeoMapType(QGeoMapType::PedestrianMap, tr("Hiking Map"), tr("Hiking map view"), false, false, 7, pluginName, cameraCaps),
199             providers_hiking, cameraCaps ));
200 
201     if (parameters.contains(QStringLiteral("osm.mapping.custom.host"))
202             || parameters.contains(QStringLiteral("osm.mapping.host"))) {
203         // Adding a custom provider
204         QString tmsServer;
205         if (parameters.contains(QStringLiteral("osm.mapping.host")))
206             tmsServer = parameters.value(QStringLiteral("osm.mapping.host")).toString();
207         if (parameters.contains(QStringLiteral("osm.mapping.custom.host"))) // priority to the new one
208             tmsServer = parameters.value(QStringLiteral("osm.mapping.custom.host")).toString();
209 
210         QString mapCopyright;
211         QString dataCopyright;
212         if (parameters.contains(QStringLiteral("osm.mapping.custom.mapcopyright")))
213             mapCopyright = parameters.value(QStringLiteral("osm.mapping.custom.mapcopyright")).toString();
214         if (parameters.contains(QStringLiteral("osm.mapping.custom.datacopyright")))
215             dataCopyright = parameters.value(QStringLiteral("osm.mapping.custom.datacopyright")).toString();
216 
217         if (parameters.contains(QStringLiteral("osm.mapping.copyright")))
218             m_customCopyright = parameters.value(QStringLiteral("osm.mapping.copyright")).toString();
219 
220         m_providers.push_back(
221             new QGeoTileProviderOsm( nmCached,
222                 QGeoMapType(QGeoMapType::CustomMap, tr("Custom URL Map"), tr("Custom url map view set via urlprefix parameter"), false, false, 8, pluginName, cameraCaps),
223                 { new TileProvider(tmsServer + QStringLiteral("%z/%x/%y.png"),
224                     QStringLiteral("png"),
225                     mapCopyright,
226                     dataCopyright) }, cameraCaps
227                 ));
228 
229         m_providers.last()->disableRedirection();
230    }
231 
232     bool disableRedirection = false;
233     if (parameters.contains(QStringLiteral("osm.mapping.providersrepository.disabled")))
234         disableRedirection = parameters.value(QStringLiteral("osm.mapping.providersrepository.disabled")).toBool();
235 
236     for (QGeoTileProviderOsm * provider: qAsConst(m_providers)) {
237         // Providers are parented inside QGeoFileTileCacheOsm, as they are used in its destructor.
238         if (disableRedirection) {
239             provider->disableRedirection();
240         } else {
241             connect(provider, &QGeoTileProviderOsm::resolutionFinished,
242                     this, &QGeoTiledMappingManagerEngineOsm::onProviderResolutionFinished);
243             connect(provider, &QGeoTileProviderOsm::resolutionError,
244                     this, &QGeoTiledMappingManagerEngineOsm::onProviderResolutionError);
245         }
246     }
247     updateMapTypes();
248 
249 
250     /* TILE CACHE */
251     if (parameters.contains(QStringLiteral("osm.mapping.offline.directory")))
252         m_offlineDirectory = parameters.value(QStringLiteral("osm.mapping.offline.directory")).toString();
253     QGeoFileTileCacheOsm *tileCache = new QGeoFileTileCacheOsm(m_providers, m_offlineDirectory, m_cacheDirectory);
254 
255     /*
256      * Disk cache setup -- defaults to ByteSize (old behavior)
257      */
258     if (parameters.contains(QStringLiteral("osm.mapping.cache.disk.cost_strategy"))) {
259         QString cacheStrategy = parameters.value(QStringLiteral("osm.mapping.cache.disk.cost_strategy")).toString().toLower();
260         if (cacheStrategy == QLatin1String("bytesize"))
261             tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize);
262         else
263             tileCache->setCostStrategyDisk(QGeoFileTileCache::Unitary);
264     } else {
265         tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize);
266     }
267     if (parameters.contains(QStringLiteral("osm.mapping.cache.disk.size"))) {
268         bool ok = false;
269         int cacheSize = parameters.value(QStringLiteral("osm.mapping.cache.disk.size")).toString().toInt(&ok);
270         if (ok)
271             tileCache->setMaxDiskUsage(cacheSize);
272     }
273 
274     /*
275      * Memory cache setup -- defaults to ByteSize (old behavior)
276      */
277     if (parameters.contains(QStringLiteral("osm.mapping.cache.memory.cost_strategy"))) {
278         QString cacheStrategy = parameters.value(QStringLiteral("osm.mapping.cache.memory.cost_strategy")).toString().toLower();
279         if (cacheStrategy == QLatin1String("bytesize"))
280             tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize);
281         else
282             tileCache->setCostStrategyMemory(QGeoFileTileCache::Unitary);
283     } else {
284         tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize);
285     }
286     if (parameters.contains(QStringLiteral("osm.mapping.cache.memory.size"))) {
287         bool ok = false;
288         int cacheSize = parameters.value(QStringLiteral("osm.mapping.cache.memory.size")).toString().toInt(&ok);
289         if (ok)
290             tileCache->setMaxMemoryUsage(cacheSize);
291     }
292 
293     /*
294      * Texture cache setup -- defaults to ByteSize (old behavior)
295      */
296     if (parameters.contains(QStringLiteral("osm.mapping.cache.texture.cost_strategy"))) {
297         QString cacheStrategy = parameters.value(QStringLiteral("osm.mapping.cache.texture.cost_strategy")).toString().toLower();
298         if (cacheStrategy == QLatin1String("bytesize"))
299             tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize);
300         else
301             tileCache->setCostStrategyTexture(QGeoFileTileCache::Unitary);
302     } else {
303         tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize);
304     }
305     if (parameters.contains(QStringLiteral("osm.mapping.cache.texture.size"))) {
306         bool ok = false;
307         int cacheSize = parameters.value(QStringLiteral("osm.mapping.cache.texture.size")).toString().toInt(&ok);
308         if (ok)
309             tileCache->setExtraTextureUsage(cacheSize);
310     }
311 
312 
313     setTileCache(tileCache);
314 
315 
316     /* TILE FETCHER */
317     QGeoTileFetcherOsm *tileFetcher = new QGeoTileFetcherOsm(m_providers, nm, this);
318     if (parameters.contains(QStringLiteral("osm.useragent"))) {
319         const QByteArray ua = parameters.value(QStringLiteral("osm.useragent")).toString().toLatin1();
320         tileFetcher->setUserAgent(ua);
321     }
322     setTileFetcher(tileFetcher);
323 
324     /* PREFETCHING */
325     if (parameters.contains(QStringLiteral("osm.mapping.prefetching_style"))) {
326         const QString prefetchingMode = parameters.value(QStringLiteral("osm.mapping.prefetching_style")).toString();
327         if (prefetchingMode == QStringLiteral("TwoNeighbourLayers"))
328             m_prefetchStyle = QGeoTiledMap::PrefetchTwoNeighbourLayers;
329         else if (prefetchingMode == QStringLiteral("OneNeighbourLayer"))
330             m_prefetchStyle = QGeoTiledMap::PrefetchNeighbourLayer;
331         else if (prefetchingMode == QStringLiteral("NoPrefetching"))
332             m_prefetchStyle = QGeoTiledMap::NoPrefetching;
333     }
334 
335     *error = QGeoServiceProvider::NoError;
336     errorString->clear();
337 }
338 
~QGeoTiledMappingManagerEngineOsm()339 QGeoTiledMappingManagerEngineOsm::~QGeoTiledMappingManagerEngineOsm()
340 {
341 }
342 
createMap()343 QGeoMap *QGeoTiledMappingManagerEngineOsm::createMap()
344 {
345     QGeoTiledMap *map = new QGeoTiledMapOsm(this);
346     connect(qobject_cast<QGeoFileTileCacheOsm *>(tileCache()), &QGeoFileTileCacheOsm::mapDataUpdated
347             , map, &QGeoTiledMap::clearScene);
348     map->setPrefetchStyle(m_prefetchStyle);
349     return map;
350 }
351 
providers()352 const QVector<QGeoTileProviderOsm *> &QGeoTiledMappingManagerEngineOsm::providers()
353 {
354     return m_providers;
355 }
356 
customCopyright() const357 QString QGeoTiledMappingManagerEngineOsm::customCopyright() const
358 {
359     return m_customCopyright;
360 }
361 
onProviderResolutionFinished(const QGeoTileProviderOsm * provider)362 void QGeoTiledMappingManagerEngineOsm::onProviderResolutionFinished(const QGeoTileProviderOsm *provider)
363 {
364     if (!provider->isResolved())
365         return;
366     updateMapTypes();
367 }
368 
onProviderResolutionError(const QGeoTileProviderOsm * provider)369 void QGeoTiledMappingManagerEngineOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider)
370 {
371     if (!provider->isResolved())
372         return;
373     updateMapTypes();
374 }
375 
updateMapTypes()376 void QGeoTiledMappingManagerEngineOsm::updateMapTypes()
377 {
378     QList<QGeoMapType> mapTypes;
379     foreach (QGeoTileProviderOsm * provider, m_providers) {
380         // assume provider are ok until they have been resolved invalid
381         if (!provider->isResolved() || provider->isValid())
382             mapTypes << provider->mapType();
383     }
384     const QList<QGeoMapType> currentlySupportedMapTypes = supportedMapTypes();
385     if (currentlySupportedMapTypes != mapTypes)
386         // See map type implementations in QGeoTiledMapOsm and QGeoTileFetcherOsm.
387         setSupportedMapTypes(mapTypes);
388 }
389 
390 QT_END_NAMESPACE
391