1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtLocation module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 
37 #include <QtPositioning/private/qwebmercator_p.h>
38 #include "qgeocameracapabilities_p.h"
39 #include "qgeotiledmappingmanagerengine_nokia.h"
40 #include "qgeotiledmap_nokia.h"
41 #include "qgeotilefetcher_nokia.h"
42 #include "qgeotilespec_p.h"
43 #include "qgeofiletilecachenokia.h"
44 
45 #include <QDebug>
46 #include <QDir>
47 #include <QVariant>
48 #include <QtCore/QJsonArray>
49 #include <QtCore/QJsonObject>
50 #include <QtCore/QJsonDocument>
51 #include <QtCore/qmath.h>
52 #include <QtCore/qstandardpaths.h>
53 
54 QT_BEGIN_NAMESPACE
55 
QGeoTiledMappingManagerEngineNokia(QGeoNetworkAccessManager * networkManager,const QVariantMap & parameters,QGeoServiceProvider::Error * error,QString * errorString)56 QGeoTiledMappingManagerEngineNokia::QGeoTiledMappingManagerEngineNokia(
57     QGeoNetworkAccessManager *networkManager,
58     const QVariantMap &parameters,
59     QGeoServiceProvider::Error *error,
60     QString *errorString)
61     : QGeoTiledMappingManagerEngine()
62 {
63     Q_UNUSED(error);
64     Q_UNUSED(errorString);
65 
66     int ppi = 72;
67     if (parameters.contains(QStringLiteral("here.mapping.highdpi_tiles"))) {
68         const QString param = parameters.value(QStringLiteral("here.mapping.highdpi_tiles")).toString().toLower();
69         if (param == "true")
70             ppi = 250;
71     }
72 
73     QGeoCameraCapabilities capabilities;
74 
75     capabilities.setMinimumZoomLevel(0.0);
76     capabilities.setMaximumZoomLevel(20.0);
77     if (ppi > 72) {
78         // Zoom levels 0 and 20 are not supported for 512x512 tiles.
79         capabilities.setMinimumZoomLevel(1.0);
80         capabilities.setMaximumZoomLevel(19.0);
81     }
82     capabilities.setSupportsBearing(true);
83     capabilities.setSupportsTilting(true);
84     capabilities.setMinimumTilt(0);
85     capabilities.setMaximumTilt(80);
86     capabilities.setMinimumFieldOfView(20.0);
87     capabilities.setMaximumFieldOfView(120.0);
88     capabilities.setOverzoomEnabled(true);
89     setCameraCapabilities(capabilities);
90 
91     setTileSize(QSize(256, 256));
92 
93     int mapId = 0;
94     const QByteArray pluginName = "here";
95     QList<QGeoMapType> types;
96     types << QGeoMapType(QGeoMapType::StreetMap, tr("Street Map"), tr("Normal map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
97     types << QGeoMapType(QGeoMapType::SatelliteMapDay, tr("Satellite Map"), tr("Satellite map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
98     types << QGeoMapType(QGeoMapType::TerrainMap, tr("Terrain Map"), tr("Terrain map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
99     types << QGeoMapType(QGeoMapType::HybridMap, tr("Hybrid Map"), tr("Satellite map view with streets in daylight mode"), false, false, ++mapId, pluginName, capabilities);
100     types << QGeoMapType(QGeoMapType::TransitMap, tr("Transit Map"), tr("Color-reduced map view with public transport scheme in daylight mode"), false, false, ++mapId, pluginName, capabilities);
101     types << QGeoMapType(QGeoMapType::GrayStreetMap, tr("Gray Street Map"), tr("Color-reduced map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
102     types << QGeoMapType(QGeoMapType::StreetMap, tr("Mobile Street Map"), tr("Mobile normal map view in daylight mode"), true, false, ++mapId, pluginName, capabilities);
103     types << QGeoMapType(QGeoMapType::TerrainMap, tr("Mobile Terrain Map"), tr("Mobile terrain map view in daylight mode"), true, false, ++mapId, pluginName, capabilities);
104     types << QGeoMapType(QGeoMapType::HybridMap, tr("Mobile Hybrid Map"), tr("Mobile satellite map view with streets in daylight mode"), true, false, ++mapId, pluginName, capabilities);
105     types << QGeoMapType(QGeoMapType::TransitMap, tr("Mobile Transit Map"), tr("Mobile color-reduced map view with public transport scheme in daylight mode"), true, false, ++mapId, pluginName, capabilities);
106     types << QGeoMapType(QGeoMapType::GrayStreetMap, tr("Mobile Gray Street Map"), tr("Mobile color-reduced map view in daylight mode"), true, false, ++mapId, pluginName, capabilities);
107     types << QGeoMapType(QGeoMapType::StreetMap, tr("Custom Street Map"), tr("Normal map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
108     types << QGeoMapType(QGeoMapType::StreetMap, tr("Night Street Map"), tr("Normal map view in night mode"), false, true, ++mapId, pluginName, capabilities);
109     types << QGeoMapType(QGeoMapType::StreetMap, tr("Mobile Night Street Map"), tr("Mobile normal map view in night mode"), true, true, ++mapId, pluginName, capabilities);
110     types << QGeoMapType(QGeoMapType::GrayStreetMap, tr("Gray Night Street Map"), tr("Color-reduced map view in night mode (especially used for background maps)"), false, true, ++mapId, pluginName, capabilities);
111     types << QGeoMapType(QGeoMapType::GrayStreetMap, tr("Mobile Gray Night Street Map"), tr("Mobile color-reduced map view in night mode (especially used for background maps)"), true, true, ++mapId, pluginName, capabilities);
112     types << QGeoMapType(QGeoMapType::PedestrianMap, tr("Pedestrian Street Map"), tr("Pedestrian map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
113     types << QGeoMapType(QGeoMapType::PedestrianMap, tr("Mobile Pedestrian Street Map"), tr("Mobile pedestrian map view in daylight mode for mobile usage"), true, false, ++mapId, pluginName, capabilities);
114     types << QGeoMapType(QGeoMapType::PedestrianMap, tr("Pedestrian Night Street Map"), tr("Pedestrian map view in night mode"), false, true, ++mapId, pluginName, capabilities);
115     types << QGeoMapType(QGeoMapType::PedestrianMap, tr("Mobile Pedestrian Night Street Map"), tr("Mobile pedestrian map view in night mode for mobile usage"), true, true, ++mapId, pluginName, capabilities);
116     types << QGeoMapType(QGeoMapType::CarNavigationMap, tr("Car Navigation Map"), tr("Normal map view in daylight mode for car navigation"), false, false, ++mapId, pluginName, capabilities);
117     setSupportedMapTypes(types);
118 
119     QGeoTileFetcherNokia *fetcher = new QGeoTileFetcherNokia(parameters, networkManager, this, tileSize(), ppi);
120     setTileFetcher(fetcher);
121 
122     /* TILE CACHE */
123     // TODO: do this in a plugin-neutral way so that other tiled map plugins
124     //       don't need this boilerplate or hardcode plugin name
125     if (parameters.contains(QStringLiteral("here.mapping.cache.directory"))) {
126         m_cacheDirectory = parameters.value(QStringLiteral("here.mapping.cache.directory")).toString();
127     } else {
128         // managerName() is not yet set, we have to hardcode the plugin name below
129         m_cacheDirectory = QAbstractGeoTileCache::baseLocationCacheDirectory() + QLatin1String(pluginName);
130     }
131 
132     QGeoFileTileCache *tileCache = new QGeoFileTileCacheNokia(ppi, m_cacheDirectory);
133 
134     /*
135      * Disk cache setup -- defaults to ByteSize (old behavior)
136      */
137     if (parameters.contains(QStringLiteral("here.mapping.cache.disk.cost_strategy"))) {
138         QString cacheStrategy = parameters.value(QStringLiteral("here.mapping.cache.disk.cost_strategy")).toString().toLower();
139         if (cacheStrategy == QLatin1String("bytesize"))
140             tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize);
141         else
142             tileCache->setCostStrategyDisk(QGeoFileTileCache::Unitary);
143     } else {
144         tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize);
145     }
146     if (parameters.contains(QStringLiteral("here.mapping.cache.disk.size"))) {
147       bool ok = false;
148       int cacheSize = parameters.value(QStringLiteral("here.mapping.cache.disk.size")).toString().toInt(&ok);
149       if (ok)
150           tileCache->setMaxDiskUsage(cacheSize);
151     }
152 
153     /*
154      * Memory cache setup -- defaults to ByteSize (old behavior)
155      */
156     if (parameters.contains(QStringLiteral("here.mapping.cache.memory.cost_strategy"))) {
157         QString cacheStrategy = parameters.value(QStringLiteral("here.mapping.cache.memory.cost_strategy")).toString().toLower();
158         if (cacheStrategy == QLatin1String("bytesize"))
159             tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize);
160         else
161             tileCache->setCostStrategyMemory(QGeoFileTileCache::Unitary);
162     } else {
163         tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize);
164     }
165     if (parameters.contains(QStringLiteral("here.mapping.cache.memory.size"))) {
166       bool ok = false;
167       int cacheSize = parameters.value(QStringLiteral("here.mapping.cache.memory.size")).toString().toInt(&ok);
168       if (ok)
169           tileCache->setMaxMemoryUsage(cacheSize);
170     }
171 
172     /*
173      * Texture cache setup -- defaults to ByteSize (old behavior)
174      */
175     if (parameters.contains(QStringLiteral("here.mapping.cache.texture.cost_strategy"))) {
176         QString cacheStrategy = parameters.value(QStringLiteral("here.mapping.cache.texture.cost_strategy")).toString().toLower();
177         if (cacheStrategy == QLatin1String("bytesize"))
178             tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize);
179         else
180             tileCache->setCostStrategyTexture(QGeoFileTileCache::Unitary);
181     } else {
182         tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize);
183     }
184     if (parameters.contains(QStringLiteral("here.mapping.cache.texture.size"))) {
185       bool ok = false;
186       int cacheSize = parameters.value(QStringLiteral("here.mapping.cache.texture.size")).toString().toInt(&ok);
187       if (ok)
188           tileCache->setExtraTextureUsage(cacheSize);
189     }
190 
191     /* PREFETCHING */
192     if (parameters.contains(QStringLiteral("here.mapping.prefetching_style"))) {
193         const QString prefetchingMode = parameters.value(QStringLiteral("here.mapping.prefetching_style")).toString();
194         if (prefetchingMode == QStringLiteral("TwoNeighbourLayers"))
195             m_prefetchStyle = QGeoTiledMap::PrefetchTwoNeighbourLayers;
196         else if (prefetchingMode == QStringLiteral("OneNeighbourLayer"))
197             m_prefetchStyle = QGeoTiledMap::PrefetchNeighbourLayer;
198         else if (prefetchingMode == QStringLiteral("NoPrefetching"))
199             m_prefetchStyle = QGeoTiledMap::NoPrefetching;
200     }
201 
202     setTileCache(tileCache);
203     populateMapSchemes();
204     loadMapVersion();
205     QMetaObject::invokeMethod(fetcher, "fetchCopyrightsData", Qt::QueuedConnection);
206     QMetaObject::invokeMethod(fetcher, "fetchVersionData", Qt::QueuedConnection);
207 }
208 
~QGeoTiledMappingManagerEngineNokia()209 QGeoTiledMappingManagerEngineNokia::~QGeoTiledMappingManagerEngineNokia()
210 {
211 }
212 
populateMapSchemes()213 void QGeoTiledMappingManagerEngineNokia::populateMapSchemes()
214 {
215     m_mapSchemes[0] = QStringLiteral("normal.day");
216     m_mapSchemes[1] = QStringLiteral("normal.day");
217     m_mapSchemes[2] = QStringLiteral("satellite.day");
218     m_mapSchemes[3] = QStringLiteral("terrain.day");
219     m_mapSchemes[4] = QStringLiteral("hybrid.day");
220     m_mapSchemes[5] = QStringLiteral("normal.day.transit");
221     m_mapSchemes[6] = QStringLiteral("normal.day.grey");
222     m_mapSchemes[7] = QStringLiteral("normal.day.mobile");
223     m_mapSchemes[8] = QStringLiteral("terrain.day.mobile");
224     m_mapSchemes[9] = QStringLiteral("hybrid.day.mobile");
225     m_mapSchemes[10] = QStringLiteral("normal.day.transit.mobile");
226     m_mapSchemes[11] = QStringLiteral("normal.day.grey.mobile");
227     m_mapSchemes[12] = QStringLiteral("normal.day.custom");
228     m_mapSchemes[13] = QStringLiteral("normal.night");
229     m_mapSchemes[14] = QStringLiteral("normal.night.mobile");
230     m_mapSchemes[15] = QStringLiteral("normal.night.grey");
231     m_mapSchemes[16] = QStringLiteral("normal.night.grey.mobile");
232     m_mapSchemes[17] = QStringLiteral("pedestrian.day");
233     m_mapSchemes[18] = QStringLiteral("pedestrian.day.mobile");
234     m_mapSchemes[19] = QStringLiteral("pedestrian.night");
235     m_mapSchemes[20] = QStringLiteral("pedestrian.night.mobile");
236     m_mapSchemes[21] = QStringLiteral("carnav.day.grey");
237 }
238 
getScheme(int mapId)239 QString QGeoTiledMappingManagerEngineNokia::getScheme(int mapId)
240 {
241     return m_mapSchemes[mapId];
242 }
243 
getBaseScheme(int mapId)244 QString QGeoTiledMappingManagerEngineNokia::getBaseScheme(int mapId)
245 {
246     QString fullScheme(m_mapSchemes[mapId]);
247 
248     return fullScheme.section(QLatin1Char('.'), 0, 0);
249 }
250 
mapVersion()251 int QGeoTiledMappingManagerEngineNokia::mapVersion()
252 {
253     return m_mapVersion.version();
254 }
255 
loadCopyrightsDescriptorsFromJson(const QByteArray & jsonData)256 void QGeoTiledMappingManagerEngineNokia::loadCopyrightsDescriptorsFromJson(const QByteArray &jsonData)
257 {
258     QJsonDocument doc = QJsonDocument::fromJson(QByteArray(jsonData));
259     if (doc.isNull()) {
260         qDebug() << "QGeoTiledMappingManagerEngineNokia::loadCopyrightsDescriptorsFromJson() Invalid JSon document";
261         return;
262     }
263 
264     QJsonObject jsonObj = doc.object();
265 
266     m_copyrights.clear();
267     for (auto it = jsonObj.constBegin(), end = jsonObj.constEnd(); it != end; ++it) {
268         QList<CopyrightDesc> copyrightDescList;
269 
270         QJsonArray descs = it.value().toArray();
271         for (int descIndex = 0; descIndex < descs.count(); descIndex++) {
272             CopyrightDesc copyrightDesc;
273             QJsonObject desc = descs.at(descIndex).toObject();
274 
275             copyrightDesc.minLevel = desc["minLevel"].toDouble();
276             copyrightDesc.maxLevel = desc["maxLevel"].toDouble();
277             copyrightDesc.label = desc["label"].toString();
278             copyrightDesc.alt  = desc["alt"].toString();
279 
280             QJsonArray coordBoxes = desc["boxes"].toArray();
281             for (int boxIndex = 0; boxIndex < coordBoxes.count(); boxIndex++) {
282                 QJsonArray box = coordBoxes[boxIndex].toArray();
283                 qreal top    = box[0].toDouble();
284                 qreal left   = box[1].toDouble();
285                 qreal bottom = box[2].toDouble();
286                 qreal right  = box[3].toDouble();
287                 QGeoRectangle boundingBox(QGeoCoordinate(top > bottom? top : bottom,
288                                                            left),
289                                             QGeoCoordinate(top > bottom? bottom : top,
290                                                            right));
291                 copyrightDesc.boxes << boundingBox;
292             }
293             copyrightDescList << copyrightDesc;
294         }
295         m_copyrights[it.key()] = copyrightDescList;
296     }
297 }
298 
parseNewVersionInfo(const QByteArray & versionData)299 void QGeoTiledMappingManagerEngineNokia::parseNewVersionInfo(const QByteArray &versionData)
300 {
301     const QString versionString = QString::fromUtf8(versionData);
302 
303     const QStringList versionLines =  versionString.split(QLatin1Char('\n'));
304     QJsonObject newVersionData;
305     foreach (const QString &line, versionLines) {
306         const QStringList versionInfo = line.split(':');
307         if (versionInfo.size() > 1) {
308             const QString versionKey = versionInfo[0].trimmed();
309             const QString versionValue = versionInfo[1].trimmed();
310             if (!versionKey.isEmpty() && !versionValue.isEmpty()) {
311                 newVersionData[versionKey] = versionValue;
312             }
313         }
314     }
315 
316     updateVersion(newVersionData);
317 }
318 
updateVersion(const QJsonObject & newVersionData)319 void QGeoTiledMappingManagerEngineNokia::updateVersion(const QJsonObject &newVersionData) {
320 
321     if (m_mapVersion.isNewVersion(newVersionData)) {
322 
323         m_mapVersion.setVersionData(newVersionData);
324         m_mapVersion.setVersion(m_mapVersion.version() + 1);
325 
326         saveMapVersion();
327         setTileVersion(m_mapVersion.version());
328     }
329 }
330 
saveMapVersion()331 void QGeoTiledMappingManagerEngineNokia::saveMapVersion()
332 {
333     QDir saveDir(m_cacheDirectory);
334     QFile saveFile(saveDir.filePath(QStringLiteral("here_version")));
335 
336     if (!saveFile.open(QIODevice::WriteOnly)) {
337         qWarning("Failed to write here/nokia map version.");
338         return;
339     }
340 
341     saveFile.write(m_mapVersion.toJson());
342     saveFile.close();
343 }
344 
loadMapVersion()345 void QGeoTiledMappingManagerEngineNokia::loadMapVersion()
346 {
347     QDir saveDir(m_cacheDirectory);
348     QFile loadFile(saveDir.filePath(QStringLiteral("here_version")));
349 
350     if (!loadFile.open(QIODevice::ReadOnly)) {
351         qWarning("Failed to read here/nokia map version.");
352         return;
353     }
354 
355     QByteArray saveData = loadFile.readAll();
356     loadFile.close();
357 
358     QJsonDocument doc(QJsonDocument::fromJson(saveData));
359 
360     QJsonObject object = doc.object();
361 
362     m_mapVersion.setVersion(object[QStringLiteral("version")].toInt());
363     m_mapVersion.setVersionData(object[QStringLiteral("data")].toObject());
364     setTileVersion(m_mapVersion.version());
365 }
366 
evaluateCopyrightsText(const QGeoMapType mapType,const qreal zoomLevel,const QSet<QGeoTileSpec> & tiles)367 QString QGeoTiledMappingManagerEngineNokia::evaluateCopyrightsText(const QGeoMapType mapType,
368                                                                    const qreal zoomLevel,
369                                                                    const QSet<QGeoTileSpec> &tiles)
370 {
371     static const QChar copyrightSymbol(0x00a9);
372     typedef QSet<QGeoTileSpec>::const_iterator tile_iter;
373     QGeoRectangle viewport;
374     double viewX0, viewY0, viewX1, viewY1;
375 
376     tile_iter tile = tiles.constBegin();
377     tile_iter lastTile = tiles.constEnd();
378 
379     if (tiles.count()) {
380         double divFactor = qPow(2.0, tile->zoom());
381         viewX0 = viewX1 = tile->x();
382         viewY0 = viewY1 = tile->y();
383 
384         // this approach establishes a geo-bounding box from passed tiles to test for intersecition
385         // with copyrights boxes.
386         int numTiles = 0;
387         for (; tile != lastTile; ++tile) {
388             if (tile->x() < viewX0)
389                 viewX0 = tile->x();
390             if (tile->x() > viewX1)
391                 viewX1 = tile->x();
392             if (tile->y() < viewY0)
393                 viewY0 = tile->y();
394             if (tile->y() > viewY1)
395                 viewY1 = tile->y();
396             numTiles++;
397         }
398 
399         viewX1++;
400         viewY1++;
401 
402         QDoubleVector2D pt;
403 
404         pt.setX(viewX0 / divFactor);
405         pt.setY(viewY0 / divFactor);
406         viewport.setTopLeft(QWebMercator::mercatorToCoord(pt));
407         pt.setX(viewX1 / divFactor);
408         pt.setY(viewY1 / divFactor);
409         viewport.setBottomRight(QWebMercator::mercatorToCoord(pt));
410     }
411 
412     // TODO: the following invalidation detection algorithm may be improved later.
413     QList<CopyrightDesc> descriptorList = m_copyrights[ getBaseScheme(mapType.mapId()) ];
414     CopyrightDesc *descriptor;
415     int descIndex, boxIndex;
416     QString copyrightsText;
417     QSet<QString> copyrightStrings;
418 
419     for (descIndex = 0; descIndex < descriptorList.count(); descIndex++) {
420         if (descriptorList[descIndex].minLevel <= zoomLevel && zoomLevel <= descriptorList[descIndex].maxLevel) {
421             descriptor = &descriptorList[descIndex];
422 
423             for (boxIndex = 0; boxIndex < descriptor->boxes.count(); boxIndex++) {
424                 QGeoRectangle box = descriptor->boxes[boxIndex];
425 
426                 if (box.intersects(viewport)) {
427                     copyrightStrings.insert(descriptor->label);
428                     break;
429                 }
430             }
431             if (!descriptor->boxes.count())
432                 copyrightStrings.insert(descriptor->label);
433         }
434     }
435 
436     foreach (const QString &copyrightString, copyrightStrings) {
437         if (copyrightsText.length())
438             copyrightsText += QLatin1Char('\n');
439         copyrightsText += copyrightSymbol;
440         copyrightsText += copyrightString;
441     }
442 
443     return copyrightsText;
444 }
445 
createMap()446 QGeoMap *QGeoTiledMappingManagerEngineNokia::createMap()
447 {
448     QGeoTiledMap *map = new QGeoTiledMapNokia(this);
449     map->setPrefetchStyle(m_prefetchStyle);
450     return map;
451 }
452 
453 QT_END_NAMESPACE
454 
455