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
43QT_BEGIN_NAMESPACE
44
45static const int maxValidZoom = 30;
46static const QDateTime defaultTs = QDateTime::fromString(QStringLiteral("2016-06-01T00:00:00"), f: Qt::ISODate);
47
48static 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
58QGeoTileProviderOsm::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(provider: p);
69 }
70
71 if (!m_provider || m_provider->isValid())
72 m_status = Resolved;
73
74 if (m_provider && m_provider->isValid())
75 setSSL(mapType&: m_mapType, isHTTPS: m_provider->isHTTPS());
76
77 connect(sender: this, signal: &QGeoTileProviderOsm::resolutionFinished, receiver: this, slot: &QGeoTileProviderOsm::updateCameraCapabilities);
78}
79
80QGeoTileProviderOsm::~QGeoTileProviderOsm()
81{
82}
83
84QUrl 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
91QString QGeoTileProviderOsm::mapCopyRight() const
92{
93 if (m_status != Resolved || !m_provider)
94 return QString();
95 return m_provider->mapCopyRight();
96}
97
98QString QGeoTileProviderOsm::dataCopyRight() const
99{
100 if (m_status != Resolved || !m_provider)
101 return QString();
102 return m_provider->dataCopyRight();
103}
104
105QString QGeoTileProviderOsm::styleCopyRight() const
106{
107 if (m_status != Resolved || !m_provider)
108 return QString();
109 return m_provider->styleCopyRight();
110}
111
112QString QGeoTileProviderOsm::format() const
113{
114 if (m_status != Resolved || !m_provider)
115 return QString();
116 return m_provider->format();
117}
118
119int QGeoTileProviderOsm::minimumZoomLevel() const
120{
121 if (m_status != Resolved || !m_provider)
122 return 0;
123 return m_provider->minimumZoomLevel();
124}
125
126int QGeoTileProviderOsm::maximumZoomLevel() const
127{
128 if (m_status != Resolved || !m_provider)
129 return 20;
130 return m_provider->maximumZoomLevel();
131}
132
133bool QGeoTileProviderOsm::isHighDpi() const
134{
135 if (!m_provider)
136 return false;
137 return m_provider->isHighDpi();
138}
139
140const QDateTime QGeoTileProviderOsm::timestamp() const
141{
142 if (!m_provider)
143 return QDateTime();
144 return m_provider->timestamp();
145}
146
147QGeoCameraCapabilities QGeoTileProviderOsm::cameraCapabilities() const
148{
149 return m_cameraCapabilities;
150}
151
152const QGeoMapType &QGeoTileProviderOsm::mapType() const
153{
154 return m_mapType;
155}
156
157bool QGeoTileProviderOsm::isValid() const
158{
159 if (m_status != Resolved || !m_provider)
160 return false;
161 return m_provider->isValid();
162}
163
164bool QGeoTileProviderOsm::isResolved() const
165{
166 return (m_status == Resolved);
167}
168
169void 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(sender: m_provider, signal: &TileProvider::resolutionFinished, receiver: this, slot: &QGeoTileProviderOsm::onResolutionFinished);
177 connect(sender: m_provider, signal: &TileProvider::resolutionError, receiver: this, slot: &QGeoTileProviderOsm::onResolutionError);
178 m_provider->resolveProvider();
179}
180
181void 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(t: p);
190 found = true;
191 }
192 p->disconnect(receiver: this);
193 }
194 m_status = Resolved;
195}
196
197void 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(provider: this);
203}
204
205void 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(provider: 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(provider: this);
235 } else if (m_provider->isValid()) {
236 m_status = Resolved;
237 emit resolutionFinished(provider: 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
246void 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(mapType&: m_mapType, isHTTPS: m_provider->isHTTPS());
258}
259
260void 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(t: provider);
272 if (!m_provider)
273 m_provider = provider;
274}
275
276
277/*
278 Class TileProvder
279*/
280
281static void sort2(int &a, int &b)
282{
283 if (a > b) {
284 int temp=a;
285 a=b;
286 b=temp;
287 }
288}
289
290TileProvider::TileProvider() : m_status(Invalid), m_nm(nullptr), m_timestamp(defaultTs), m_highDpi(false)
291{
292
293}
294
295TileProvider::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
302TileProvider::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
316TileProvider::~TileProvider()
317{
318}
319
320void 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(header: QNetworkRequest::UserAgentHeader, QByteArrayLiteral("QGeoTileFetcherOsm"));
337 request.setUrl(m_urlRedirector);
338 request.setAttribute(code: QNetworkRequest::BackgroundRequestAttribute, value: true);
339 request.setAttribute(code: QNetworkRequest::CacheLoadControlAttribute, value: QNetworkRequest::PreferNetwork);
340 QNetworkReply *reply = m_nm->get(request);
341 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(onNetworkReplyFinished()) );
342 connect(sender: reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), receiver: this, SLOT(onNetworkReplyError(QNetworkReply::NetworkError)));
343}
344
345void 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
367void 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(provider: this);
380 return;
381 }
382
383 QObject errorEmitter;
384 QMetaObject::Connection errorEmitterConnection = connect(sender: &errorEmitter, signal: &QObject::destroyed, slot: [this](){ this->resolutionError(provider: this); });
385
386 if (reply->error() != QNetworkReply::NoError) {
387 handleError(error: 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(json: reply->readAll(), error: &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(key: QLatin1String("UrlTemplate"));
454 const QJsonValue imageFormat = json.value(key: QLatin1String("ImageFormat"));
455 const QJsonValue copyRightMap = json.value(key: QLatin1String("MapCopyRight"));
456 const QJsonValue copyRightData = json.value(key: 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(key: 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(key: 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(key: QLatin1String("MinimumZoomLevel"));
487 if (minZoom.isDouble())
488 m_minimumZoomLevel = qBound(min: 0, val: int(minZoom.toDouble()), max: maxValidZoom);
489 const QJsonValue maxZoom = json.value(key: QLatin1String("MaximumZoomLevel"));
490 if (maxZoom.isDouble())
491 m_maximumZoomLevel = qBound(min: 0, val: int(maxZoom.toDouble()), max: maxValidZoom);
492
493 const QJsonValue ts = json.value(key: QLatin1String("Timestamp"));
494 if (ts.isString())
495 m_timestamp = QDateTime::fromString(s: ts.toString(), f: Qt::ISODate);
496
497 setupProvider();
498 if (isValid()) {
499 QObject::disconnect(errorEmitterConnection);
500 emit resolutionFinished(provider: this);
501 }
502}
503
504void 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(provider: this);
512}
513
514void 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(s: QLatin1String("%x"));
531 if (offset[0] < 0)
532 return;
533
534 offset[1] = m_urlTemplate.indexOf(s: QLatin1String("%y"));
535 if (offset[1] < 0)
536 return;
537
538 offset[2] = m_urlTemplate.indexOf(s: QLatin1String("%z"));
539 if (offset[2] < 0)
540 return;
541
542 int sortedOffsets[3];
543 std::copy(first: offset, last: offset + 3, result: sortedOffsets);
544 sort2(a&: sortedOffsets[0] ,b&: sortedOffsets[1]);
545 sort2(a&: sortedOffsets[1] ,b&: sortedOffsets[2]);
546 sort2(a&: sortedOffsets[0] ,b&: 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(position: 0 , n: min);
563 m_urlSuffix = m_urlTemplate.mid(position: max + 2, n: m_urlTemplate.size() - max - 2);
564
565 paramsSep[0] = m_urlTemplate.mid(position: min + 2, n: mid - min - 2);
566 paramsSep[1] = m_urlTemplate.mid(position: mid + 2, n: max - mid - 2);
567 m_status = Valid;
568}
569
570bool TileProvider::isValid() const
571{
572 return m_status == Valid;
573}
574
575bool TileProvider::isInvalid() const
576{
577 return m_status == Invalid;
578}
579
580bool TileProvider::isResolved() const
581{
582 return (m_status == Valid || m_status == Invalid);
583}
584
585QString TileProvider::mapCopyRight() const
586{
587 return m_copyRightMap;
588}
589
590QString TileProvider::dataCopyRight() const
591{
592 return m_copyRightData;
593}
594
595QString TileProvider::styleCopyRight() const
596{
597 return m_copyRightStyle;
598}
599
600QString TileProvider::format() const
601{
602 return m_format;
603}
604
605int TileProvider::minimumZoomLevel() const
606{
607 return m_minimumZoomLevel;
608}
609
610int TileProvider::maximumZoomLevel() const
611{
612 return m_maximumZoomLevel;
613}
614
615const QDateTime &TileProvider::timestamp() const
616{
617 return m_timestamp;
618}
619
620bool TileProvider::isHighDpi() const
621{
622 return m_highDpi;
623}
624
625bool TileProvider::isHTTPS() const
626{
627 return m_urlTemplate.startsWith(QStringLiteral("https"));
628}
629
630void TileProvider::setStyleCopyRight(const QString &copyright)
631{
632 m_copyRightStyle = copyright;
633}
634
635void TileProvider::setTimestamp(const QDateTime &timestamp)
636{
637 m_timestamp = timestamp;
638}
639
640QUrl 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
656void TileProvider::setNetworkManager(QNetworkAccessManager *nm)
657{
658 m_nm = nm;
659}
660
661TileProvider::Status TileProvider::status() const
662{
663 return m_status;
664}
665
666
667QT_END_NAMESPACE
668

source code of qtlocation/src/plugins/geoservices/osm/qgeotileproviderosm.cpp