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
54QT_BEGIN_NAMESPACE
55
56QGeoTiledMappingManagerEngineNokia::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(s: "Street Map"), tr(s: "Normal map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
97 types << QGeoMapType(QGeoMapType::SatelliteMapDay, tr(s: "Satellite Map"), tr(s: "Satellite map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
98 types << QGeoMapType(QGeoMapType::TerrainMap, tr(s: "Terrain Map"), tr(s: "Terrain map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
99 types << QGeoMapType(QGeoMapType::HybridMap, tr(s: "Hybrid Map"), tr(s: "Satellite map view with streets in daylight mode"), false, false, ++mapId, pluginName, capabilities);
100 types << QGeoMapType(QGeoMapType::TransitMap, tr(s: "Transit Map"), tr(s: "Color-reduced map view with public transport scheme in daylight mode"), false, false, ++mapId, pluginName, capabilities);
101 types << QGeoMapType(QGeoMapType::GrayStreetMap, tr(s: "Gray Street Map"), tr(s: "Color-reduced map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
102 types << QGeoMapType(QGeoMapType::StreetMap, tr(s: "Mobile Street Map"), tr(s: "Mobile normal map view in daylight mode"), true, false, ++mapId, pluginName, capabilities);
103 types << QGeoMapType(QGeoMapType::TerrainMap, tr(s: "Mobile Terrain Map"), tr(s: "Mobile terrain map view in daylight mode"), true, false, ++mapId, pluginName, capabilities);
104 types << QGeoMapType(QGeoMapType::HybridMap, tr(s: "Mobile Hybrid Map"), tr(s: "Mobile satellite map view with streets in daylight mode"), true, false, ++mapId, pluginName, capabilities);
105 types << QGeoMapType(QGeoMapType::TransitMap, tr(s: "Mobile Transit Map"), tr(s: "Mobile color-reduced map view with public transport scheme in daylight mode"), true, false, ++mapId, pluginName, capabilities);
106 types << QGeoMapType(QGeoMapType::GrayStreetMap, tr(s: "Mobile Gray Street Map"), tr(s: "Mobile color-reduced map view in daylight mode"), true, false, ++mapId, pluginName, capabilities);
107 types << QGeoMapType(QGeoMapType::StreetMap, tr(s: "Custom Street Map"), tr(s: "Normal map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
108 types << QGeoMapType(QGeoMapType::StreetMap, tr(s: "Night Street Map"), tr(s: "Normal map view in night mode"), false, true, ++mapId, pluginName, capabilities);
109 types << QGeoMapType(QGeoMapType::StreetMap, tr(s: "Mobile Night Street Map"), tr(s: "Mobile normal map view in night mode"), true, true, ++mapId, pluginName, capabilities);
110 types << QGeoMapType(QGeoMapType::GrayStreetMap, tr(s: "Gray Night Street Map"), tr(s: "Color-reduced map view in night mode (especially used for background maps)"), false, true, ++mapId, pluginName, capabilities);
111 types << QGeoMapType(QGeoMapType::GrayStreetMap, tr(s: "Mobile Gray Night Street Map"), tr(s: "Mobile color-reduced map view in night mode (especially used for background maps)"), true, true, ++mapId, pluginName, capabilities);
112 types << QGeoMapType(QGeoMapType::PedestrianMap, tr(s: "Pedestrian Street Map"), tr(s: "Pedestrian map view in daylight mode"), false, false, ++mapId, pluginName, capabilities);
113 types << QGeoMapType(QGeoMapType::PedestrianMap, tr(s: "Mobile Pedestrian Street Map"), tr(s: "Mobile pedestrian map view in daylight mode for mobile usage"), true, false, ++mapId, pluginName, capabilities);
114 types << QGeoMapType(QGeoMapType::PedestrianMap, tr(s: "Pedestrian Night Street Map"), tr(s: "Pedestrian map view in night mode"), false, true, ++mapId, pluginName, capabilities);
115 types << QGeoMapType(QGeoMapType::PedestrianMap, tr(s: "Mobile Pedestrian Night Street Map"), tr(s: "Mobile pedestrian map view in night mode for mobile usage"), true, true, ++mapId, pluginName, capabilities);
116 types << QGeoMapType(QGeoMapType::CarNavigationMap, tr(s: "Car Navigation Map"), tr(s: "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: &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: &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: &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(obj: fetcher, member: "fetchCopyrightsData", type: Qt::QueuedConnection);
206 QMetaObject::invokeMethod(obj: fetcher, member: "fetchVersionData", type: Qt::QueuedConnection);
207}
208
209QGeoTiledMappingManagerEngineNokia::~QGeoTiledMappingManagerEngineNokia()
210{
211}
212
213void 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
239QString QGeoTiledMappingManagerEngineNokia::getScheme(int mapId)
240{
241 return m_mapSchemes[mapId];
242}
243
244QString QGeoTiledMappingManagerEngineNokia::getBaseScheme(int mapId)
245{
246 QString fullScheme(m_mapSchemes[mapId]);
247
248 return fullScheme.section(asep: QLatin1Char('.'), astart: 0, aend: 0);
249}
250
251int QGeoTiledMappingManagerEngineNokia::mapVersion()
252{
253 return m_mapVersion.version();
254}
255
256void QGeoTiledMappingManagerEngineNokia::loadCopyrightsDescriptorsFromJson(const QByteArray &jsonData)
257{
258 QJsonDocument doc = QJsonDocument::fromJson(json: 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(i: 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
299void QGeoTiledMappingManagerEngineNokia::parseNewVersionInfo(const QByteArray &versionData)
300{
301 const QString versionString = QString::fromUtf8(str: versionData);
302
303 const QStringList versionLines = versionString.split(sep: QLatin1Char('\n'));
304 QJsonObject newVersionData;
305 foreach (const QString &line, versionLines) {
306 const QStringList versionInfo = line.split(sep: ':');
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
319void 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
331void QGeoTiledMappingManagerEngineNokia::saveMapVersion()
332{
333 QDir saveDir(m_cacheDirectory);
334 QFile saveFile(saveDir.filePath(QStringLiteral("here_version")));
335
336 if (!saveFile.open(flags: QIODevice::WriteOnly)) {
337 qWarning(msg: "Failed to write here/nokia map version.");
338 return;
339 }
340
341 saveFile.write(data: m_mapVersion.toJson());
342 saveFile.close();
343}
344
345void QGeoTiledMappingManagerEngineNokia::loadMapVersion()
346{
347 QDir saveDir(m_cacheDirectory);
348 QFile loadFile(saveDir.filePath(QStringLiteral("here_version")));
349
350 if (!loadFile.open(flags: QIODevice::ReadOnly)) {
351 qWarning(msg: "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(json: 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
367QString 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(x: 2.0, y: 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(mercator: pt));
407 pt.setX(viewX1 / divFactor);
408 pt.setY(viewY1 / divFactor);
409 viewport.setBottomRight(QWebMercator::mercatorToCoord(mercator: pt));
410 }
411
412 // TODO: the following invalidation detection algorithm may be improved later.
413 QList<CopyrightDesc> descriptorList = m_copyrights[ getBaseScheme(mapId: 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(rectangle: viewport)) {
427 copyrightStrings.insert(value: descriptor->label);
428 break;
429 }
430 }
431 if (!descriptor->boxes.count())
432 copyrightStrings.insert(value: 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
446QGeoMap *QGeoTiledMappingManagerEngineNokia::createMap()
447{
448 QGeoTiledMap *map = new QGeoTiledMapNokia(this);
449 map->setPrefetchStyle(m_prefetchStyle);
450 return map;
451}
452
453QT_END_NAMESPACE
454
455

source code of qtlocation/src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.cpp