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