1 /****************************************************************************
2 **
3 ** Copyright (C) 2013-2018 Esri <contracts@esri.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 "geotiledmappingmanagerengine_esri.h"
41 #include "geotiledmap_esri.h"
42 #include "geotilefetcher_esri.h"
43 
44 #include <QtLocation/private/qgeocameracapabilities_p.h>
45 #include <QtLocation/private/qgeomaptype_p.h>
46 #include <QtLocation/private/qgeotiledmap_p.h>
47 #include <QtLocation/private/qgeofiletilecache_p.h>
48 
49 #include <QFileInfo>
50 #include <QDir>
51 #include <QUrl>
52 #include <QFile>
53 #include <QJsonDocument>
54 #include <QJsonObject>
55 
56 QT_BEGIN_NAMESPACE
57 
58 static const QString kPrefixEsri(QStringLiteral("esri."));
59 static const QString kParamUserAgent(kPrefixEsri + QStringLiteral("useragent"));
60 static const QString kParamToken(kPrefixEsri + QStringLiteral("token"));
61 static const QString kPrefixMapping(kPrefixEsri + QStringLiteral("mapping."));
62 static const QString kParamMinimumZoomLevel(kPrefixMapping + QStringLiteral("minimumZoomLevel"));
63 static const QString kParamMaximumZoomLevel(kPrefixMapping + QStringLiteral("maximumZoomLevel"));
64 
65 static const QString kPropMapSources(QStringLiteral("mapSources"));
66 static const QString kPropStyle(QStringLiteral("style"));
67 static const QString kPropName(QStringLiteral("name"));
68 static const QString kPropDescription(QStringLiteral("description"));
69 static const QString kPropMobile(QStringLiteral("mobile"));
70 static const QString kPropNight(QStringLiteral("night"));
71 static const QString kPropUrl(QStringLiteral("url"));
72 static const QString kPropMapId(QStringLiteral("mapId"));
73 static const QString kPropCopyright(QStringLiteral("copyrightText"));
74 
GeoTiledMappingManagerEngineEsri(const QVariantMap & parameters,QGeoServiceProvider::Error * error,QString * errorString)75 GeoTiledMappingManagerEngineEsri::GeoTiledMappingManagerEngineEsri(const QVariantMap &parameters,
76                                                                    QGeoServiceProvider::Error *error,
77                                                                    QString *errorString) :
78     QGeoTiledMappingManagerEngine()
79 {
80     QGeoCameraCapabilities cameraCaps;
81 
82     double minimumZoomLevel = 0;
83     double maximumZoomLevel = 19;
84 
85     if (parameters.contains(kParamMinimumZoomLevel))
86         minimumZoomLevel = parameters[kParamMinimumZoomLevel].toDouble();
87 
88     if (parameters.contains(kParamMaximumZoomLevel))
89         maximumZoomLevel = parameters[kParamMaximumZoomLevel].toDouble();
90 
91     cameraCaps.setMinimumZoomLevel(minimumZoomLevel);
92     cameraCaps.setMaximumZoomLevel(maximumZoomLevel);
93     cameraCaps.setSupportsBearing(true);
94     cameraCaps.setSupportsTilting(true);
95     cameraCaps.setMinimumTilt(0);
96     cameraCaps.setMaximumTilt(80);
97     cameraCaps.setMinimumFieldOfView(20.0);
98     cameraCaps.setMaximumFieldOfView(120.0);
99     cameraCaps.setOverzoomEnabled(true);
100     setCameraCapabilities(cameraCaps);
101 
102     setTileSize(QSize(256, 256));
103 
104     if (!initializeMapSources(error, errorString, cameraCaps))
105         return;
106 
107     QList<QGeoMapType> mapTypes;
108 
109     foreach (GeoMapSource *mapSource, m_mapSources) {
110         mapTypes << QGeoMapType(
111                         mapSource->style(),
112                         mapSource->name(),
113                         mapSource->description(),
114                         mapSource->mobile(),
115                         mapSource->night(),
116                         mapSource->mapId(),
117                         "esri",
118                         cameraCaps);
119     }
120 
121     setSupportedMapTypes(mapTypes);
122 
123     GeoTileFetcherEsri *tileFetcher = new GeoTileFetcherEsri(this);
124 
125     if (parameters.contains(kParamUserAgent))
126         tileFetcher->setUserAgent(parameters.value(kParamUserAgent).toString().toLatin1());
127 
128     if (parameters.contains(kParamToken))
129         tileFetcher->setToken(parameters.value(kParamToken).toString());
130 
131     setTileFetcher(tileFetcher);
132 
133     /* TILE CACHE */
134     QString cacheDirectory;
135     if (parameters.contains(QStringLiteral("esri.mapping.cache.directory"))) {
136         cacheDirectory = parameters.value(QStringLiteral("esri.mapping.cache.directory")).toString();
137     } else {
138         // managerName() is not yet set, we have to hardcode the plugin name below
139         cacheDirectory = QAbstractGeoTileCache::baseLocationCacheDirectory() + QLatin1String("esri");
140     }
141     QGeoFileTileCache *tileCache = new QGeoFileTileCache(cacheDirectory);
142 
143     /*
144      * Disk cache setup -- defaults to ByteSize (old behavior)
145      */
146     if (parameters.contains(QStringLiteral("esri.mapping.cache.disk.cost_strategy"))) {
147         QString cacheStrategy = parameters.value(QStringLiteral("esri.mapping.cache.disk.cost_strategy")).toString().toLower();
148         if (cacheStrategy == QLatin1String("bytesize"))
149             tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize);
150         else
151             tileCache->setCostStrategyDisk(QGeoFileTileCache::Unitary);
152     } else {
153         tileCache->setCostStrategyDisk(QGeoFileTileCache::ByteSize);
154     }
155     if (parameters.contains(QStringLiteral("esri.mapping.cache.disk.size"))) {
156         bool ok = false;
157         int cacheSize = parameters.value(QStringLiteral("esri.mapping.cache.disk.size")).toString().toInt(&ok);
158         if (ok)
159             tileCache->setMaxDiskUsage(cacheSize);
160     }
161 
162     /*
163      * Memory cache setup -- defaults to ByteSize (old behavior)
164      */
165     if (parameters.contains(QStringLiteral("esri.mapping.cache.memory.cost_strategy"))) {
166         QString cacheStrategy = parameters.value(QStringLiteral("esri.mapping.cache.memory.cost_strategy")).toString().toLower();
167         if (cacheStrategy == QLatin1String("bytesize"))
168             tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize);
169         else
170             tileCache->setCostStrategyMemory(QGeoFileTileCache::Unitary);
171     } else {
172         tileCache->setCostStrategyMemory(QGeoFileTileCache::ByteSize);
173     }
174     if (parameters.contains(QStringLiteral("esri.mapping.cache.memory.size"))) {
175         bool ok = false;
176         int cacheSize = parameters.value(QStringLiteral("esri.mapping.cache.memory.size")).toString().toInt(&ok);
177         if (ok)
178             tileCache->setMaxMemoryUsage(cacheSize);
179     }
180 
181     /*
182      * Texture cache setup -- defaults to ByteSize (old behavior)
183      */
184     if (parameters.contains(QStringLiteral("esri.mapping.cache.texture.cost_strategy"))) {
185         QString cacheStrategy = parameters.value(QStringLiteral("esri.mapping.cache.texture.cost_strategy")).toString().toLower();
186         if (cacheStrategy == QLatin1String("bytesize"))
187             tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize);
188         else
189             tileCache->setCostStrategyTexture(QGeoFileTileCache::Unitary);
190     } else {
191         tileCache->setCostStrategyTexture(QGeoFileTileCache::ByteSize);
192     }
193     if (parameters.contains(QStringLiteral("esri.mapping.cache.texture.size"))) {
194         bool ok = false;
195         int cacheSize = parameters.value(QStringLiteral("esri.mapping.cache.texture.size")).toString().toInt(&ok);
196         if (ok)
197             tileCache->setExtraTextureUsage(cacheSize);
198     }
199 
200     /* PREFETCHING */
201     if (parameters.contains(QStringLiteral("esri.mapping.prefetching_style"))) {
202         const QString prefetchingMode = parameters.value(QStringLiteral("esri.mapping.prefetching_style")).toString();
203         if (prefetchingMode == QStringLiteral("TwoNeighbourLayers"))
204             m_prefetchStyle = QGeoTiledMap::PrefetchTwoNeighbourLayers;
205         else if (prefetchingMode == QStringLiteral("OneNeighbourLayer"))
206             m_prefetchStyle = QGeoTiledMap::PrefetchNeighbourLayer;
207         else if (prefetchingMode == QStringLiteral("NoPrefetching"))
208             m_prefetchStyle = QGeoTiledMap::NoPrefetching;
209     }
210 
211     setTileCache(tileCache);
212     *error = QGeoServiceProvider::NoError;
213     errorString->clear();
214 }
215 
~GeoTiledMappingManagerEngineEsri()216 GeoTiledMappingManagerEngineEsri::~GeoTiledMappingManagerEngineEsri()
217 {
218     qDeleteAll(m_mapSources);
219 }
220 
createMap()221 QGeoMap *GeoTiledMappingManagerEngineEsri::createMap()
222 {
223     QGeoTiledMap *map = new GeoTiledMapEsri(this);
224     map->setPrefetchStyle(m_prefetchStyle);
225     return map;
226 }
227 
228 // ${z} = Zoom
229 // ${x} = X
230 // ${y} = Y
231 // ${token} = Token
232 
233 // template = 'http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{{z}}/{{y}}/{{x}}.png'
234 
initializeMapSources(QGeoServiceProvider::Error * error,QString * errorString,const QGeoCameraCapabilities & cameraCaps)235 bool GeoTiledMappingManagerEngineEsri::initializeMapSources(QGeoServiceProvider::Error *error,
236                                                             QString *errorString,
237                                                             const QGeoCameraCapabilities &cameraCaps)
238 {
239     QFile mapsFile(":/esri/maps.json");
240 
241     if (!mapsFile.open(QIODevice::ReadOnly)) {
242         *error = QGeoServiceProvider::NotSupportedError;
243         *errorString = Q_FUNC_INFO + QStringLiteral("Unable to open: ") + mapsFile.fileName();
244 
245         return false;
246     }
247 
248     QByteArray mapsData = mapsFile.readAll();
249     mapsFile.close();
250 
251     QJsonParseError parseError;
252 
253     QJsonDocument mapsDocument = QJsonDocument::fromJson(mapsData, &parseError);
254 
255     if (!mapsDocument.isObject()) {
256         *error = QGeoServiceProvider::NotSupportedError;
257         *errorString = Q_FUNC_INFO + QStringLiteral("JSON error: ") + (int)parseError.error
258                 + ", offset: " + parseError.offset
259                 + ", details: " + parseError.errorString();
260         return false;
261     }
262 
263     QVariantMap maps = mapsDocument.object().toVariantMap();
264 
265     QVariantList mapSources = maps["mapSources"].toList();
266 
267     foreach (QVariant mapSourceElement, mapSources) {
268         QVariantMap mapSource = mapSourceElement.toMap();
269 
270         int mapId = m_mapSources.count() + 1;
271 
272         m_mapSources << new GeoMapSource(
273                             GeoMapSource::mapStyle(mapSource[kPropStyle].toString()),
274                             mapSource[kPropName].toString(),
275                             mapSource[kPropDescription].toString(),
276                             mapSource[kPropMobile].toBool(),
277                             mapSource[kPropMapId].toBool(),
278                             mapId,
279                             GeoMapSource::toFormat(mapSource[kPropUrl].toString()),
280                             mapSource[kPropCopyright].toString(),
281                             cameraCaps
282                             );
283     }
284 
285     return true;
286 }
287 
mapSource(int mapId) const288 GeoMapSource *GeoTiledMappingManagerEngineEsri::mapSource(int mapId) const
289 {
290     foreach (GeoMapSource *mapSource, mapSources()) {
291         if (mapSource->mapId() == mapId)
292             return mapSource;
293     }
294 
295     return nullptr;
296 }
297 
298 QT_END_NAMESPACE
299