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