1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 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 "qgeotileproviderosm.h"
38 
39 #include <QtCore/QJsonDocument>
40 #include <QtCore/QJsonObject>
41 #include <QDebug>
42 
43 QT_BEGIN_NAMESPACE
44 
45 static const int maxValidZoom = 30;
46 static const QDateTime defaultTs = QDateTime::fromString(QStringLiteral("2016-06-01T00:00:00"), Qt::ISODate);
47 
setSSL(QGeoMapType & mapType,bool isHTTPS)48 static void setSSL(QGeoMapType &mapType, bool isHTTPS)
49 {
50     QVariantMap metadata = mapType.metadata();
51     metadata["isHTTPS"] = isHTTPS;
52 
53     mapType = QGeoMapType(mapType.style(), mapType.name(), mapType.description(), mapType.mobile(),
54                           mapType.night(), mapType.mapId(), mapType.pluginName(), mapType.cameraCapabilities(),
55                           metadata);
56 }
57 
QGeoTileProviderOsm(QNetworkAccessManager * nm,const QGeoMapType & mapType,const QVector<TileProvider * > & providers,const QGeoCameraCapabilities & cameraCapabilities)58 QGeoTileProviderOsm::QGeoTileProviderOsm(QNetworkAccessManager *nm,
59                                          const QGeoMapType &mapType,
60                                          const QVector<TileProvider *> &providers,
61                                          const QGeoCameraCapabilities &cameraCapabilities)
62 :   m_nm(nm), m_provider(nullptr), m_mapType(mapType), m_status(Idle), m_cameraCapabilities(cameraCapabilities)
63 {
64     for (int i = 0; i < providers.size(); ++i) {
65         TileProvider *p = providers[i];
66         if (!m_provider)
67             m_providerId = i;
68         addProvider(p);
69     }
70 
71     if (!m_provider || m_provider->isValid())
72         m_status = Resolved;
73 
74     if (m_provider && m_provider->isValid())
75         setSSL(m_mapType, m_provider->isHTTPS());
76 
77     connect(this, &QGeoTileProviderOsm::resolutionFinished, this, &QGeoTileProviderOsm::updateCameraCapabilities);
78 }
79 
~QGeoTileProviderOsm()80 QGeoTileProviderOsm::~QGeoTileProviderOsm()
81 {
82 }
83 
tileAddress(int x,int y,int z) const84 QUrl QGeoTileProviderOsm::tileAddress(int x, int y, int z) const
85 {
86     if (m_status != Resolved || !m_provider)
87         return QUrl();
88     return m_provider->tileAddress(x, y, z);
89 }
90 
mapCopyRight() const91 QString QGeoTileProviderOsm::mapCopyRight() const
92 {
93     if (m_status != Resolved || !m_provider)
94         return QString();
95     return m_provider->mapCopyRight();
96 }
97 
dataCopyRight() const98 QString QGeoTileProviderOsm::dataCopyRight() const
99 {
100     if (m_status != Resolved || !m_provider)
101         return QString();
102     return m_provider->dataCopyRight();
103 }
104 
styleCopyRight() const105 QString QGeoTileProviderOsm::styleCopyRight() const
106 {
107     if (m_status != Resolved || !m_provider)
108         return QString();
109     return m_provider->styleCopyRight();
110 }
111 
format() const112 QString QGeoTileProviderOsm::format() const
113 {
114     if (m_status != Resolved || !m_provider)
115         return QString();
116     return m_provider->format();
117 }
118 
minimumZoomLevel() const119 int QGeoTileProviderOsm::minimumZoomLevel() const
120 {
121     if (m_status != Resolved || !m_provider)
122         return 0;
123     return m_provider->minimumZoomLevel();
124 }
125 
maximumZoomLevel() const126 int QGeoTileProviderOsm::maximumZoomLevel() const
127 {
128     if (m_status != Resolved || !m_provider)
129         return 20;
130     return m_provider->maximumZoomLevel();
131 }
132 
isHighDpi() const133 bool QGeoTileProviderOsm::isHighDpi() const
134 {
135     if (!m_provider)
136         return false;
137     return m_provider->isHighDpi();
138 }
139 
timestamp() const140 const QDateTime QGeoTileProviderOsm::timestamp() const
141 {
142     if (!m_provider)
143         return QDateTime();
144     return m_provider->timestamp();
145 }
146 
cameraCapabilities() const147 QGeoCameraCapabilities QGeoTileProviderOsm::cameraCapabilities() const
148 {
149     return m_cameraCapabilities;
150 }
151 
mapType() const152 const QGeoMapType &QGeoTileProviderOsm::mapType() const
153 {
154     return m_mapType;
155 }
156 
isValid() const157 bool QGeoTileProviderOsm::isValid() const
158 {
159     if (m_status != Resolved || !m_provider)
160         return false;
161     return m_provider->isValid();
162 }
163 
isResolved() const164 bool QGeoTileProviderOsm::isResolved() const
165 {
166     return (m_status == Resolved);
167 }
168 
resolveProvider()169 void QGeoTileProviderOsm::resolveProvider()
170 {
171     if (m_status == Resolved || m_status == Resolving)
172         return;
173 
174     m_status = Resolving;
175     // Provider can't be null while on Idle status.
176     connect(m_provider, &TileProvider::resolutionFinished, this, &QGeoTileProviderOsm::onResolutionFinished);
177     connect(m_provider, &TileProvider::resolutionError, this, &QGeoTileProviderOsm::onResolutionError);
178     m_provider->resolveProvider();
179 }
180 
disableRedirection()181 void QGeoTileProviderOsm::disableRedirection()
182 {
183     if (m_provider && m_provider->isValid())
184         return;
185     bool found = false;
186     for (TileProvider *p: m_providerList) {
187         if (p->isValid() && !found) {
188             m_provider = p;
189             m_providerId = m_providerList.indexOf(p);
190             found = true;
191         }
192         p->disconnect(this);
193     }
194     m_status = Resolved;
195 }
196 
onResolutionFinished(TileProvider * provider)197 void QGeoTileProviderOsm::onResolutionFinished(TileProvider *provider)
198 {
199     Q_UNUSED(provider);
200     // provider and m_provider are the same, at this point. m_status is Resolving.
201     m_status = Resolved;
202     emit resolutionFinished(this);
203 }
204 
onResolutionError(TileProvider * provider)205 void QGeoTileProviderOsm::onResolutionError(TileProvider *provider)
206 {
207     Q_UNUSED(provider);
208     // provider and m_provider are the same at this point. m_status is Resolving.
209     if (!m_provider || m_provider->isInvalid()) {
210         m_provider = nullptr;
211         m_status = Resolved;
212         if (m_providerId >= m_providerList.size() -1) { // no hope left
213             emit resolutionError(this);
214             return;
215         }
216         // Advance the pointer in the provider list, and possibly start resolution on the next in the list.
217         for (int i = m_providerId + 1; i < m_providerList.size(); ++i) {
218             m_providerId = i;
219             TileProvider *p = m_providerList[m_providerId];
220             if (!p->isInvalid()) {
221                 m_provider = p;
222                 if (!p->isValid()) {
223                     m_status = Idle;
224 #if 0  // leaving triggering the retry to the tile fetcher, instead of constantly spinning it in here.
225                     m_status = Resolving;
226                     p->resolveProvider();
227 #endif
228                     emit resolutionRequired();
229                 }
230                 break;
231             }
232         }
233         if (!m_provider)
234             emit resolutionError(this);
235     } else if (m_provider->isValid()) {
236         m_status = Resolved;
237         emit resolutionFinished(this);
238     } else { // still not resolved. But network error is recoverable.
239         m_status = Idle;
240 #if 0   // leaving triggering the retry to the tile fetcher
241         m_provider->resolveProvider();
242 #endif
243     }
244 }
245 
updateCameraCapabilities()246 void QGeoTileProviderOsm::updateCameraCapabilities()
247 {
248     // Set proper min/max ZoomLevel coming from the json, if available.
249     m_cameraCapabilities.setMinimumZoomLevel(minimumZoomLevel());
250     m_cameraCapabilities.setMaximumZoomLevel(maximumZoomLevel());
251 
252     m_mapType = QGeoMapType(m_mapType.style(), m_mapType.name(), m_mapType.description(), m_mapType.mobile(),
253                             m_mapType.night(), m_mapType.mapId(), m_mapType.pluginName(), m_cameraCapabilities,
254                             m_mapType.metadata());
255 
256     if (m_provider && m_provider->isValid())
257         setSSL(m_mapType, m_provider->isHTTPS());
258 }
259 
addProvider(TileProvider * provider)260 void QGeoTileProviderOsm::addProvider(TileProvider *provider)
261 {
262     if (!provider)
263         return;
264     QScopedPointer<TileProvider> p(provider);
265     if (provider->status() == TileProvider::Invalid)
266         return; // if the provider is already resolved and invalid, no point in adding it.
267 
268     provider = p.take();
269     provider->setNetworkManager(m_nm);
270     provider->setParent(this);
271     m_providerList.append(provider);
272     if (!m_provider)
273         m_provider = provider;
274 }
275 
276 
277 /*
278     Class TileProvder
279 */
280 
sort2(int & a,int & b)281 static void sort2(int &a, int &b)
282 {
283     if (a > b) {
284         int temp=a;
285         a=b;
286         b=temp;
287     }
288 }
289 
TileProvider()290 TileProvider::TileProvider() : m_status(Invalid), m_nm(nullptr), m_timestamp(defaultTs), m_highDpi(false)
291 {
292 
293 }
294 
TileProvider(const QUrl & urlRedirector,bool highDpi)295 TileProvider::TileProvider(const QUrl &urlRedirector, bool highDpi)
296 :   m_status(Idle), m_urlRedirector(urlRedirector), m_nm(nullptr), m_timestamp(defaultTs), m_highDpi(highDpi)
297 {
298     if (!m_urlRedirector.isValid())
299         m_status = Invalid;
300 }
301 
TileProvider(const QString & urlTemplate,const QString & format,const QString & copyRightMap,const QString & copyRightData,bool highDpi,int minimumZoomLevel,int maximumZoomLevel)302 TileProvider::TileProvider(const QString &urlTemplate,
303                            const QString &format,
304                            const QString &copyRightMap,
305                            const QString &copyRightData,
306                            bool highDpi,
307                            int minimumZoomLevel,
308                            int maximumZoomLevel)
309 :   m_status(Invalid), m_nm(nullptr), m_urlTemplate(urlTemplate),
310     m_format(format), m_copyRightMap(copyRightMap), m_copyRightData(copyRightData),
311     m_minimumZoomLevel(minimumZoomLevel), m_maximumZoomLevel(maximumZoomLevel), m_timestamp(defaultTs), m_highDpi(highDpi)
312 {
313     setupProvider();
314 }
315 
~TileProvider()316 TileProvider::~TileProvider()
317 {
318 }
319 
resolveProvider()320 void TileProvider::resolveProvider()
321 {
322     if (!m_nm)
323         return;
324 
325     switch (m_status) {
326     case Resolving:
327     case Invalid:
328     case Valid:
329         return;
330     case Idle:
331         m_status = Resolving;
332         break;
333     }
334 
335     QNetworkRequest request;
336     request.setHeader(QNetworkRequest::UserAgentHeader, QByteArrayLiteral("QGeoTileFetcherOsm"));
337     request.setUrl(m_urlRedirector);
338     request.setAttribute(QNetworkRequest::BackgroundRequestAttribute, true);
339     request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork);
340     QNetworkReply *reply = m_nm->get(request);
341     connect(reply, SIGNAL(finished()), this, SLOT(onNetworkReplyFinished()) );
342     connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onNetworkReplyError(QNetworkReply::NetworkError)));
343 }
344 
handleError(QNetworkReply::NetworkError error)345 void TileProvider::handleError(QNetworkReply::NetworkError error)
346 {
347     switch (error) {
348     case QNetworkReply::ConnectionRefusedError:
349     case QNetworkReply::TooManyRedirectsError:
350     case QNetworkReply::InsecureRedirectError:
351     case QNetworkReply::ContentAccessDenied:
352     case QNetworkReply::ContentOperationNotPermittedError:
353     case QNetworkReply::ContentNotFoundError:
354     case QNetworkReply::AuthenticationRequiredError:
355     case QNetworkReply::ContentGoneError:
356     case QNetworkReply::OperationNotImplementedError:
357     case QNetworkReply::ServiceUnavailableError:
358         // Errors we don't expect to recover from in the near future, which
359         // prevent accessing the redirection info but not the actual providers.
360         m_status = Invalid;
361     default:
362         //qWarning() << "QGeoTileProviderOsm network error:" << error;
363         break;
364     }
365 }
366 
onNetworkReplyFinished()367 void TileProvider::onNetworkReplyFinished()
368 {
369     QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
370     reply->deleteLater();
371 
372     switch (m_status) {
373     case Resolving:
374         m_status = Idle;
375     case Idle:    // should not happen
376     case Invalid: // should not happen
377         break;
378     case Valid: // should not happen
379         emit resolutionFinished(this);
380         return;
381     }
382 
383     QObject errorEmitter;
384     QMetaObject::Connection errorEmitterConnection = connect(&errorEmitter, &QObject::destroyed, [this](){ this->resolutionError(this); });
385 
386     if (reply->error() != QNetworkReply::NoError) {
387         handleError(reply->error());
388         return;
389     }
390     m_status = Invalid;
391 
392     /*
393      * The content of a provider information file must be in JSON format, containing
394      * (as of Qt 5.6.2) the following fields:
395      *
396      * {
397      *     "Enabled" : bool,  (optional)
398      *     "UrlTemplate" : "<url template>", (mandatory)
399      *     "ImageFormat" : "<image format>", (mandatory)
400      *     "MapCopyRight" : "<copyright>", (mandatory)
401      *     "DataCopyRight" : "<copyright>", (mandatory)
402      *     "StyleCopyRight" : "<copyright>", (optional)
403      *     "MinimumZoomLevel" : <minimumZoomLevel>, (optional)
404      *     "MaximumZoomLevel" : <maximumZoomLevel>,  (optional)
405      *     "Timestamp" : <timestamp>, (optional)
406      * }
407      *
408      * Enabled is optional, and allows to temporarily disable a tile provider if it becomes
409      * unavailable, without making the osm plugin fire requests to it. Default is true.
410      *
411      * MinimumZoomLevel and MaximumZoomLevel are also optional, and allow to prevent invalid tile
412      * requests to the providers, if they do not support the specific ZL. Default is 0 and 20,
413      * respectively.
414      *
415      * UrlTemplate is required, and is the tile url template, with %x, %y and %z as
416      * placeholders for the actual parameters.
417      * Example:
418      * http://localhost:8080/maps/%z/%x/%y.png
419      *
420      * ImageFormat is required, and is the format of the tile.
421      * Examples:
422      * "png", "jpg"
423      *
424      * MapCopyRight is required and is the string that will be displayed in the "Map (c)" part
425      * of the on-screen copyright notice. Can be an empty string.
426      * Example:
427      * "<a href='http://www.mapquest.com/'>MapQuest</a>"
428      *
429      * DataCopyRight is required and is the string that will be displayed in the "Data (c)" part
430      * of the on-screen copyright notice. Can be an empty string.
431      * Example:
432      * "<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"
433      *
434      * StyleCopyRight is optional and is the string that will be displayed in the optional "Style (c)" part
435      * of the on-screen copyright notice.
436      *
437      * Timestamp is optional, and if set will cause QtLocation to clear the content of the cache older
438      * than this timestamp. The purpose is to prevent mixing tiles from different providers in the cache
439      * upon provider change. The value must be a string in ISO 8601 format (see Qt::ISODate)
440      */
441 
442     QJsonParseError error;
443     QJsonDocument d = QJsonDocument::fromJson(reply->readAll(), &error);
444     if (error.error != QJsonParseError::NoError) {
445         qWarning() << "QGeoTileProviderOsm: Error parsing redirection data: "<<error.errorString() << "at "<<m_urlRedirector;
446         return;
447     }
448     if (!d.isObject()) {
449         qWarning() << "QGeoTileProviderOsm: Invalid redirection data" << "at "<<m_urlRedirector;
450         return;
451     }
452     const QJsonObject json = d.object();
453     const QJsonValue urlTemplate = json.value(QLatin1String("UrlTemplate"));
454     const QJsonValue imageFormat = json.value(QLatin1String("ImageFormat"));
455     const QJsonValue copyRightMap = json.value(QLatin1String("MapCopyRight"));
456     const QJsonValue copyRightData = json.value(QLatin1String("DataCopyRight"));
457     if ( urlTemplate == QJsonValue::Undefined
458          || imageFormat == QJsonValue::Undefined
459          || copyRightMap  == QJsonValue::Undefined
460          || copyRightData  == QJsonValue::Undefined
461          || !urlTemplate.isString()
462          || !imageFormat.isString()
463          || !copyRightMap.isString()
464          || !copyRightData.isString()) {
465         qWarning() << "QGeoTileProviderOsm: Incomplete redirection data" << "at "<<m_urlRedirector;
466         return;
467     }
468 
469     m_urlTemplate = urlTemplate.toString();
470     m_format = imageFormat.toString();
471     m_copyRightMap = copyRightMap.toString();
472     m_copyRightData = copyRightData.toString();
473 
474     const QJsonValue enabled = json.value(QLatin1String("Enabled"));
475     if (enabled.isBool() && ! enabled.toBool()) {
476         qWarning() << "QGeoTileProviderOsm: Tileserver disabled" << "at "<<m_urlRedirector;
477         return;
478     }
479 
480     const QJsonValue copyRightStyle = json.value(QLatin1String("StyleCopyRight"));
481     if (copyRightStyle != QJsonValue::Undefined && copyRightStyle.isString())
482         m_copyRightStyle = copyRightStyle.toString();
483 
484     m_minimumZoomLevel = 0;
485     m_maximumZoomLevel = 20;
486     const QJsonValue minZoom = json.value(QLatin1String("MinimumZoomLevel"));
487     if (minZoom.isDouble())
488         m_minimumZoomLevel = qBound(0, int(minZoom.toDouble()), maxValidZoom);
489     const QJsonValue maxZoom = json.value(QLatin1String("MaximumZoomLevel"));
490     if (maxZoom.isDouble())
491         m_maximumZoomLevel = qBound(0, int(maxZoom.toDouble()), maxValidZoom);
492 
493     const QJsonValue ts = json.value(QLatin1String("Timestamp"));
494     if (ts.isString())
495         m_timestamp = QDateTime::fromString(ts.toString(), Qt::ISODate);
496 
497     setupProvider();
498     if (isValid()) {
499         QObject::disconnect(errorEmitterConnection);
500         emit resolutionFinished(this);
501     }
502 }
503 
onNetworkReplyError(QNetworkReply::NetworkError error)504 void TileProvider::onNetworkReplyError(QNetworkReply::NetworkError error)
505 {
506     if (m_status == Resolving)
507         m_status = Idle;
508 
509     handleError(error);
510     static_cast<QNetworkReply *>(sender())->deleteLater();
511     emit resolutionError(this);
512 }
513 
setupProvider()514 void TileProvider::setupProvider()
515 {
516     if (m_urlTemplate.isEmpty())
517         return;
518 
519     if (m_format.isEmpty())
520         return;
521 
522     if (m_minimumZoomLevel < 0 || m_minimumZoomLevel > 30)
523         return;
524 
525     if (m_maximumZoomLevel < 0 || m_maximumZoomLevel > 30 ||  m_maximumZoomLevel < m_minimumZoomLevel)
526         return;
527 
528     // Currently supporting only %x, %y and &z
529     int offset[3];
530     offset[0] = m_urlTemplate.indexOf(QLatin1String("%x"));
531     if (offset[0] < 0)
532         return;
533 
534     offset[1] = m_urlTemplate.indexOf(QLatin1String("%y"));
535     if (offset[1] < 0)
536         return;
537 
538     offset[2] = m_urlTemplate.indexOf(QLatin1String("%z"));
539     if (offset[2] < 0)
540         return;
541 
542     int sortedOffsets[3];
543     std::copy(offset, offset + 3, sortedOffsets);
544     sort2(sortedOffsets[0] ,sortedOffsets[1]);
545     sort2(sortedOffsets[1] ,sortedOffsets[2]);
546     sort2(sortedOffsets[0] ,sortedOffsets[1]);
547 
548     int min = sortedOffsets[0];
549     int max = sortedOffsets[2];
550     int mid = sortedOffsets[1];
551 
552     // Initing LUT
553     for (int i=0; i<3; i++) {
554         if (offset[0] == sortedOffsets[i])
555             paramsLUT[i] = 0;
556         else if (offset[1] == sortedOffsets[i])
557             paramsLUT[i] = 1;
558         else
559             paramsLUT[i] = 2;
560     }
561 
562     m_urlPrefix = m_urlTemplate.mid(0 , min);
563     m_urlSuffix = m_urlTemplate.mid(max + 2, m_urlTemplate.size() - max - 2);
564 
565     paramsSep[0] = m_urlTemplate.mid(min + 2, mid - min - 2);
566     paramsSep[1] = m_urlTemplate.mid(mid + 2, max - mid - 2);
567     m_status = Valid;
568 }
569 
isValid() const570 bool TileProvider::isValid() const
571 {
572     return m_status == Valid;
573 }
574 
isInvalid() const575 bool TileProvider::isInvalid() const
576 {
577     return m_status == Invalid;
578 }
579 
isResolved() const580 bool TileProvider::isResolved() const
581 {
582     return (m_status == Valid || m_status == Invalid);
583 }
584 
mapCopyRight() const585 QString TileProvider::mapCopyRight() const
586 {
587     return m_copyRightMap;
588 }
589 
dataCopyRight() const590 QString TileProvider::dataCopyRight() const
591 {
592     return m_copyRightData;
593 }
594 
styleCopyRight() const595 QString TileProvider::styleCopyRight() const
596 {
597     return m_copyRightStyle;
598 }
599 
format() const600 QString TileProvider::format() const
601 {
602     return m_format;
603 }
604 
minimumZoomLevel() const605 int TileProvider::minimumZoomLevel() const
606 {
607     return m_minimumZoomLevel;
608 }
609 
maximumZoomLevel() const610 int TileProvider::maximumZoomLevel() const
611 {
612     return m_maximumZoomLevel;
613 }
614 
timestamp() const615 const QDateTime &TileProvider::timestamp() const
616 {
617     return m_timestamp;
618 }
619 
isHighDpi() const620 bool TileProvider::isHighDpi() const
621 {
622     return m_highDpi;
623 }
624 
isHTTPS() const625 bool TileProvider::isHTTPS() const
626 {
627     return m_urlTemplate.startsWith(QStringLiteral("https"));
628 }
629 
setStyleCopyRight(const QString & copyright)630 void TileProvider::setStyleCopyRight(const QString &copyright)
631 {
632     m_copyRightStyle = copyright;
633 }
634 
setTimestamp(const QDateTime & timestamp)635 void TileProvider::setTimestamp(const QDateTime &timestamp)
636 {
637     m_timestamp = timestamp;
638 }
639 
tileAddress(int x,int y,int z) const640 QUrl TileProvider::tileAddress(int x, int y, int z) const
641 {
642     if (z < m_minimumZoomLevel || z > m_maximumZoomLevel)
643         return QUrl();
644     int params[3] = { x, y, z};
645     QString url;
646     url += m_urlPrefix;
647     url += QString::number(params[paramsLUT[0]]);
648     url += paramsSep[0];
649     url += QString::number(params[paramsLUT[1]]);
650     url += paramsSep[1];
651     url += QString::number(params[paramsLUT[2]]);
652     url += m_urlSuffix;
653     return QUrl(url);
654 }
655 
setNetworkManager(QNetworkAccessManager * nm)656 void TileProvider::setNetworkManager(QNetworkAccessManager *nm)
657 {
658     m_nm = nm;
659 }
660 
status() const661 TileProvider::Status TileProvider::status() const
662 {
663     return m_status;
664 }
665 
666 
667 QT_END_NAMESPACE
668