QGIS API Documentation 3.43.0-Master (a93bf8b6462)
qgscesiumtilesdataprovider.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscesiumtilesdataprovider.cpp
3 --------------------
4 begin : June 2023
5 copyright : (C) 2023 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19#include "moc_qgscesiumtilesdataprovider.cpp"
20#include "qgsauthmanager.h"
21#include "qgsproviderutils.h"
22#include "qgsapplication.h"
24#include "qgsthreadingutils.h"
28#include "qgscesiumutils.h"
29#include "qgssphere.h"
30#include "qgslogger.h"
31#include "qgsorientedbox3d.h"
34#include "qgstiledscenenode.h"
35#include "qgstiledsceneindex.h"
37#include "qgstiledscenetile.h"
38#include "qgsreadwritelocker.h"
40#include "qgsellipsoidutils.h"
41
42#include <QUrl>
43#include <QIcon>
44#include <QNetworkRequest>
45#include <QJsonDocument>
46#include <QJsonObject>
47#include <QFileInfo>
48#include <QRegularExpression>
49#include <QRecursiveMutex>
50#include <QUrlQuery>
51#include <QApplication>
52#include <nlohmann/json.hpp>
53#include <qstringliteral.h>
54
56
57#define PROVIDER_KEY QStringLiteral( "cesiumtiles" )
58#define PROVIDER_DESCRIPTION QStringLiteral( "Cesium 3D Tiles data provider" )
59
60
61// This is to support a case seen with Google's tiles. Root URL is something like this:
62// https://tile.googleapis.com/.../root.json?key=123
63// The returned JSON contains relative links with "session" (e.g. "/.../abc.json?session=456")
64// When fetching such abc.json, we have to include also "key" from the original URL!
65// Then the content of abc.json contains relative links (e.g. "/.../xyz.glb") and we
66// need to add both "key" and "session" (otherwise requests fail).
67//
68// This function simply copies any query items from the base URL to the content URI.
69static QString appendQueryFromBaseUrl( const QString &contentUri, const QUrl &baseUrl )
70{
71 QUrlQuery contentQuery( QUrl( contentUri ).query() );
72 const QList<QPair<QString, QString>> baseUrlQueryItems = QUrlQuery( baseUrl.query() ).queryItems();
73 for ( const QPair<QString, QString> &kv : baseUrlQueryItems )
74 {
75 contentQuery.addQueryItem( kv.first, kv.second );
76 }
77 QUrl newContentUrl( contentUri );
78 newContentUrl.setQuery( contentQuery );
79 return newContentUrl.toString();
80}
81
82
83class QgsCesiumTiledSceneIndex final : public QgsAbstractTiledSceneIndex
84{
85 public:
86
87 QgsCesiumTiledSceneIndex(
88 const json &tileset,
89 const QUrl &rootUrl,
90 const QString &authCfg,
91 const QgsHttpHeaders &headers,
92 const QgsCoordinateTransformContext &transformContext );
93
94 std::unique_ptr< QgsTiledSceneTile > tileFromJson( const json &node, const QUrl &baseUrl, const QgsTiledSceneTile *parent, Qgis::Axis gltfUpAxis );
95 QgsTiledSceneNode *nodeFromJson( const json &node, const QUrl &baseUrl, QgsTiledSceneNode *parent, Qgis::Axis gltfUpAxis );
96 void refineNodeFromJson( QgsTiledSceneNode *node, const QUrl &baseUrl, const json &json );
97
98 QgsTiledSceneTile rootTile() const final;
99 QgsTiledSceneTile getTile( long long id ) final;
100 long long parentTileId( long long id ) const final;
101 QVector< long long > childTileIds( long long id ) const final;
102 QVector< long long > getTiles( const QgsTiledSceneRequest &request ) final;
103 Qgis::TileChildrenAvailability childAvailability( long long id ) const final;
104 bool fetchHierarchy( long long id, QgsFeedback *feedback = nullptr ) final;
105
106 protected:
107
108 QByteArray fetchContent( const QString &uri, QgsFeedback *feedback = nullptr ) final;
109
110 private:
111
112 enum class TileContentFormat
113 {
114 Json,
115 NotJson, // TODO: refine this to actual content types when/if needed!
116 };
117
118 mutable QRecursiveMutex mLock;
119 QgsCoordinateTransformContext mTransformContext;
120 std::unique_ptr< QgsTiledSceneNode > mRootNode;
121 QMap< long long, QgsTiledSceneNode * > mNodeMap;
122 QMap< long long, TileContentFormat > mTileContentFormats;
123 QString mAuthCfg;
124 QgsHttpHeaders mHeaders;
125 long long mNextTileId = 0;
126
127};
128
129class QgsCesiumTilesDataProviderSharedData
130{
131 public:
132 QgsCesiumTilesDataProviderSharedData();
133 void initialize( const QString &tileset,
134 const QUrl &rootUrl,
135 const QgsCoordinateTransformContext &transformContext,
136 const QString &authCfg,
137 const QgsHttpHeaders &headers );
138
141 QgsTiledSceneBoundingVolume mBoundingVolume;
142
143 QgsRectangle mExtent;
144 nlohmann::json mTileset;
145 QgsDoubleRange mZRange;
146
147 QgsTiledSceneIndex mIndex;
148
149 QgsLayerMetadata mLayerMetadata;
150 QString mError;
151 QReadWriteLock mReadWriteLock;
152
153};
154
155
156//
157// QgsCesiumTiledSceneIndex
158//
159
160Qgis::Axis axisFromJson( const json &json )
161{
162 const std::string gltfUpAxisString = json.get<std::string>();
163 if ( gltfUpAxisString == "z" || gltfUpAxisString == "Z" )
164 {
165 return Qgis::Axis::Z;
166 }
167 else if ( gltfUpAxisString == "y" || gltfUpAxisString == "Y" )
168 {
169 return Qgis::Axis::Y;
170 }
171 else if ( gltfUpAxisString == "x" || gltfUpAxisString == "X" )
172 {
173 return Qgis::Axis::X;
174 }
175 QgsDebugError( QStringLiteral( "Unsupported gltfUpAxis value: %1" ).arg( QString::fromStdString( gltfUpAxisString ) ) );
176 return Qgis::Axis::Y;
177}
178
179QgsCesiumTiledSceneIndex::QgsCesiumTiledSceneIndex( const json &tileset, const QUrl &rootUrl, const QString &authCfg, const QgsHttpHeaders &headers, const QgsCoordinateTransformContext &transformContext )
180 : mTransformContext( transformContext )
181 , mAuthCfg( authCfg )
182 , mHeaders( headers )
183{
184 Qgis::Axis gltfUpAxis = Qgis::Axis::Y;
185 if ( tileset.contains( "asset" ) )
186 {
187 const auto &assetJson = tileset["asset"];
188 if ( assetJson.contains( "gltfUpAxis" ) )
189 {
190 gltfUpAxis = axisFromJson( assetJson["gltfUpAxis"] );
191 }
192 }
193
194 mRootNode.reset( nodeFromJson( tileset[ "root" ], rootUrl, nullptr, gltfUpAxis ) );
195}
196
197std::unique_ptr< QgsTiledSceneTile > QgsCesiumTiledSceneIndex::tileFromJson( const json &json, const QUrl &baseUrl, const QgsTiledSceneTile *parent, Qgis::Axis gltfUpAxis )
198{
199 auto tile = std::make_unique< QgsTiledSceneTile >( mNextTileId++ );
200
201 tile->setBaseUrl( baseUrl );
202 tile->setMetadata(
203 {
204 { QStringLiteral( "gltfUpAxis" ), static_cast< int >( gltfUpAxis ) },
205 { QStringLiteral( "contentFormat" ), QStringLiteral( "cesiumtiles" ) },
206 } );
207
208 QgsMatrix4x4 transform;
209 if ( json.contains( "transform" ) && !json["transform"].is_null() )
210 {
211 const auto &transformJson = json["transform"];
212 double *ptr = transform.data();
213 for ( int i = 0; i < 16; ++i )
214 ptr[i] = transformJson[i].get<double>();
215
216 if ( parent && parent->transform() )
217 {
218 transform = *parent->transform() * transform;
219 }
220 }
221 else if ( parent && parent->transform() )
222 {
223 transform = *parent->transform();
224 }
225 if ( !transform.isIdentity() )
226 tile->setTransform( transform );
227
228 const auto &boundingVolume = json[ "boundingVolume" ];
230 if ( boundingVolume.contains( "region" ) )
231 {
232 QgsBox3D rootRegion = QgsCesiumUtils::parseRegion( boundingVolume[ "region" ] );
233 if ( !rootRegion.isNull() )
234 {
235 if ( rootRegion.width() > 20 || rootRegion.height() > 20 )
236 {
237 // treat very large regions as global -- these will not transform to EPSG:4978
238 }
239 else
240 {
241 // we need to transform regions from EPSG:4979 to EPSG:4978
242 QVector< QgsVector3D > corners = rootRegion.corners();
243
244 QVector< double > x;
245 x.reserve( 8 );
246 QVector< double > y;
247 y.reserve( 8 );
248 QVector< double > z;
249 z.reserve( 8 );
250 for ( int i = 0; i < 8; ++i )
251 {
252 const QgsVector3D &corner = corners[i];
253 x.append( corner.x() );
254 y.append( corner.y() );
255 z.append( corner.z() );
256 }
257 QgsCoordinateTransform ct( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) ), mTransformContext );
258 ct.setBallparkTransformsAreAppropriate( true );
259 try
260 {
261 ct.transformInPlace( x, y, z );
262 }
263 catch ( QgsCsException & )
264 {
265 QgsDebugError( QStringLiteral( "Cannot transform region bounding volume" ) );
266 }
267
268 const auto minMaxX = std::minmax_element( x.constBegin(), x.constEnd() );
269 const auto minMaxY = std::minmax_element( y.constBegin(), y.constEnd() );
270 const auto minMaxZ = std::minmax_element( z.constBegin(), z.constEnd() );
271 volume = QgsTiledSceneBoundingVolume( QgsOrientedBox3D::fromBox3D( QgsBox3D( *minMaxX.first, *minMaxY.first, *minMaxZ.first, *minMaxX.second, *minMaxY.second, *minMaxZ.second ) ) );
272
273 // note that matrix transforms are NOT applied to region bounding volumes!
274 }
275 }
276 }
277 else if ( boundingVolume.contains( "box" ) )
278 {
279 const QgsOrientedBox3D bbox = QgsCesiumUtils::parseBox( boundingVolume["box"] );
280 if ( !bbox.isNull() )
281 {
282 volume = QgsTiledSceneBoundingVolume( bbox );
283 if ( !transform.isIdentity() )
284 volume.transform( transform );
285 }
286 }
287 else if ( boundingVolume.contains( "sphere" ) )
288 {
289 QgsSphere sphere = QgsCesiumUtils::parseSphere( boundingVolume["sphere"] );
290 if ( !sphere.isNull() )
291 {
292 sphere = QgsCesiumUtils::transformSphere( sphere, transform );
294 }
295 }
296 else
297 {
298 QgsDebugError( QStringLiteral( "unsupported boundingVolume format" ) );
299 }
300
301 tile->setBoundingVolume( volume );
302
303 if ( json.contains( "geometricError" ) )
304 tile->setGeometricError( json["geometricError"].get< double >() );
305 if ( json.contains( "refine" ) )
306 {
307 if ( json["refine"] == "ADD" )
308 tile->setRefinementProcess( Qgis::TileRefinementProcess::Additive );
309 else if ( json["refine"] == "REPLACE" )
310 tile->setRefinementProcess( Qgis::TileRefinementProcess::Replacement );
311 }
312 else if ( parent )
313 {
314 // children inherit the parent refinement if not explicitly set -- see https://github.com/CesiumGS/cesium-native/blob/172ac5ddcce602c8b268ad342639554dea2f6004/Cesium3DTilesSelection/src/TilesetJsonLoader.cpp#L440C5-L440C40
315 tile->setRefinementProcess( parent->refinementProcess() );
316 }
317
318 if ( json.contains( "content" ) && !json["content"].is_null() )
319 {
320 const auto &contentJson = json["content"];
321
322 // sometimes URI, sometimes URL...
323 QString contentUri;
324 if ( contentJson.contains( "uri" ) && !contentJson["uri"].is_null() )
325 {
326 QString relativeUri = QString::fromStdString( contentJson["uri"].get<std::string>() );
327 contentUri = baseUrl.resolved( QUrl( relativeUri ) ).toString();
328
329 if ( baseUrl.hasQuery() && QUrl( relativeUri ).isRelative() )
330 contentUri = appendQueryFromBaseUrl( contentUri, baseUrl );
331 }
332 else if ( contentJson.contains( "url" ) && !contentJson["url"].is_null() )
333 {
334 QString relativeUri = QString::fromStdString( contentJson["url"].get<std::string>() );
335 contentUri = baseUrl.resolved( QUrl( relativeUri ) ).toString();
336
337 if ( baseUrl.hasQuery() && QUrl( relativeUri ).isRelative() )
338 contentUri = appendQueryFromBaseUrl( contentUri, baseUrl );
339 }
340 if ( !contentUri.isEmpty() )
341 {
342 tile->setResources( {{ QStringLiteral( "content" ), contentUri } } );
343 }
344 }
345
346 return tile;
347}
348
349QgsTiledSceneNode *QgsCesiumTiledSceneIndex::nodeFromJson( const json &json, const QUrl &baseUrl, QgsTiledSceneNode *parent, Qgis::Axis gltfUpAxis )
350{
351 std::unique_ptr< QgsTiledSceneTile > tile = tileFromJson( json, baseUrl, parent ? parent->tile() : nullptr, gltfUpAxis );
352 auto newNode = std::make_unique< QgsTiledSceneNode >( tile.release() );
353 mNodeMap.insert( newNode->tile()->id(), newNode.get() );
354
355 if ( parent )
356 parent->addChild( newNode.get() );
357
358 if ( json.contains( "children" ) )
359 {
360 for ( const auto &childJson : json["children"] )
361 {
362 nodeFromJson( childJson, baseUrl, newNode.get(), gltfUpAxis );
363 }
364 }
365
366 return newNode.release();
367}
368
369void QgsCesiumTiledSceneIndex::refineNodeFromJson( QgsTiledSceneNode *node, const QUrl &baseUrl, const json &json )
370{
371 const auto &rootTileJson = json["root"];
372
373 Qgis::Axis gltfUpAxis = Qgis::Axis::Y;
374 if ( json.contains( "asset" ) )
375 {
376 const auto &assetJson = json["asset"];
377 if ( assetJson.contains( "gltfUpAxis" ) )
378 {
379 gltfUpAxis = axisFromJson( assetJson["gltfUpAxis"] );
380 }
381 }
382
383 std::unique_ptr< QgsTiledSceneTile > newTile = tileFromJson( rootTileJson, baseUrl, node->tile(), gltfUpAxis );
384 // copy just the resources from the retrieved tileset to the refined node. We assume all the rest of the tile content
385 // should be the same between the node being refined and the root node of the fetched sub dataset!
386 // (Ie the bounding volume, geometric error, etc).
387 node->tile()->setResources( newTile->resources() );
388
389
390 // root tile of the sub dataset may have transform as well, we need to bring it back
391 // (actually even the referencing tile may have transform - if that's the case,
392 // that transform got combined with the root tile's transform in tileFromJson)
393 if ( newTile->transform() )
394 node->tile()->setTransform( *newTile->transform() );
395
396 if ( rootTileJson.contains( "children" ) )
397 {
398 for ( const auto &childJson : rootTileJson["children"] )
399 {
400 nodeFromJson( childJson, baseUrl, node, gltfUpAxis );
401 }
402 }
403}
404
405QgsTiledSceneTile QgsCesiumTiledSceneIndex::rootTile() const
406{
407 QMutexLocker locker( &mLock );
408 return mRootNode ? *mRootNode->tile() : QgsTiledSceneTile();
409}
410
411QgsTiledSceneTile QgsCesiumTiledSceneIndex::getTile( long long id )
412{
413 QMutexLocker locker( &mLock );
414 auto it = mNodeMap.constFind( id );
415 if ( it != mNodeMap.constEnd() )
416 {
417 return *( it.value()->tile() );
418 }
419
420 return QgsTiledSceneTile();
421}
422
423long long QgsCesiumTiledSceneIndex::parentTileId( long long id ) const
424{
425 QMutexLocker locker( &mLock );
426 auto it = mNodeMap.constFind( id );
427 if ( it != mNodeMap.constEnd() )
428 {
429 if ( QgsTiledSceneNode *parent = it.value()->parentNode() )
430 {
431 return parent->tile()->id();
432 }
433 }
434
435 return -1;
436}
437
438QVector< long long > QgsCesiumTiledSceneIndex::childTileIds( long long id ) const
439{
440 QMutexLocker locker( &mLock );
441 auto it = mNodeMap.constFind( id );
442 if ( it != mNodeMap.constEnd() )
443 {
444 QVector< long long > childIds;
445 const QList< QgsTiledSceneNode * > children = it.value()->children();
446 childIds.reserve( children.size() );
447 for ( QgsTiledSceneNode *child : children )
448 {
449 childIds << child->tile()->id();
450 }
451 return childIds;
452 }
453
454 return {};
455}
456
457QVector< long long > QgsCesiumTiledSceneIndex::getTiles( const QgsTiledSceneRequest &request )
458{
459 QVector< long long > results;
460
461 std::function< void( QgsTiledSceneNode * )> traverseNode;
462 traverseNode = [&request, &traverseNode, &results, this]( QgsTiledSceneNode * node )
463 {
464 QgsTiledSceneTile *tile = node->tile();
465
466 // check filter box first -- if the node doesn't intersect, then don't include the node and don't traverse
467 // to its children
468 if ( !request.filterBox().isNull() && !tile->boundingVolume().box().isNull() && !tile->boundingVolume().intersects( request.filterBox() ) )
469 return;
470
471 // TODO -- option to filter out nodes without content
472
473 if ( request.requiredGeometricError() <= 0 || tile->geometricError() <= 0 || tile->geometricError() > request.requiredGeometricError() )
474 {
475 // haven't traversed deep enough down this node, we need to explore children
476
477 // are children available?
478 QList< QgsTiledSceneNode * > children = node->children();
479 if ( children.empty() )
480 {
481 switch ( childAvailability( tile->id() ) )
482 {
485 break;
487 {
489 {
490 // do a blocking fetch of children
491 if ( fetchHierarchy( tile->id() ), request.feedback() )
492 {
493 children = node->children();
494 }
495 }
496 break;
497 }
498 }
499 }
500
501 for ( QgsTiledSceneNode *child : std::as_const( children ) )
502 {
503 if ( request.feedback() && request.feedback()->isCanceled() )
504 break;
505
506 traverseNode( child );
507 }
508
509 switch ( tile->refinementProcess() )
510 {
512 // child add to parent content, so we must also include the parent
513 results << tile->id();
514 break;
515
517 // children replace the parent, so we skip the parent if we found children
518 if ( children.empty() )
519 results << tile->id();
520 break;
521 }
522 }
523 else
524 {
525 results << tile->id();
526 }
527
528 };
529
530 QMutexLocker locker( &mLock );
531 if ( request.parentTileId() < 0 )
532 {
533 if ( mRootNode )
534 traverseNode( mRootNode.get() );
535 }
536 else
537 {
538 auto it = mNodeMap.constFind( request.parentTileId() );
539 if ( it != mNodeMap.constEnd() )
540 {
541 traverseNode( it.value() );
542 }
543 }
544
545 return results;
546}
547
548Qgis::TileChildrenAvailability QgsCesiumTiledSceneIndex::childAvailability( long long id ) const
549{
550 QString contentUri;
551 QMutexLocker locker( &mLock );
552 {
553 auto it = mNodeMap.constFind( id );
554 if ( it == mNodeMap.constEnd() )
556
557 if ( !it.value()->children().isEmpty() )
559
560 contentUri = it.value()->tile()->resources().value( QStringLiteral( "content" ) ).toString();
561 }
562 {
563 // maybe we already retrieved content for this node and know the answer:
564 auto it = mTileContentFormats.constFind( id );
565 if ( it != mTileContentFormats.constEnd() )
566 {
567 switch ( it.value() )
568 {
569 case TileContentFormat::NotJson:
571 case TileContentFormat::Json:
573 }
574 }
575 }
576 locker.unlock();
577
578 if ( contentUri.isEmpty() )
580
581 // https://github.com/CesiumGS/3d-tiles/tree/main/specification#tile-json says:
582 // "A file extension is not required for content.uri. A content’s tile format can
583 // be identified by the magic field in its header, or else as an external tileset if the content is JSON."
584 // This is rather annoying... it means we have to do a network request in order to determine whether
585 // a tile has children or geometry content!
586
587 // let's avoid this request if we can get away with it:
588 const thread_local QRegularExpression isJsonRx( QStringLiteral( ".*\\.json(?:\\?.*)?$" ), QRegularExpression::PatternOption::CaseInsensitiveOption );
589 if ( isJsonRx.match( contentUri ).hasMatch() )
591
592 // things we know definitely CAN'T be a child tile map:
593 const thread_local QRegularExpression antiCandidateRx( QStringLiteral( ".*\\.(gltf|glb|b3dm|i3dm|pnts|cmpt|bin|glbin|glbuf|png|jpeg|jpg)(?:\\?.*)?$" ), QRegularExpression::PatternOption::CaseInsensitiveOption );
594 if ( antiCandidateRx.match( contentUri ).hasMatch() )
596
597 // here we **could** do a fetch to verify what the content actually is. But we want this method to be non-blocking,
598 // so let's just report that there IS remote children available and then sort things out when we actually go to fetch those children...
600}
601
602bool QgsCesiumTiledSceneIndex::fetchHierarchy( long long id, QgsFeedback *feedback )
603{
604 QMutexLocker locker( &mLock );
605 auto it = mNodeMap.constFind( id );
606 if ( it == mNodeMap.constEnd() )
607 return false;
608
609 {
610 // maybe we already know what content type this tile has. If so, and it's not json, then
611 // don't try to fetch it as a hierarchy
612 auto it = mTileContentFormats.constFind( id );
613 if ( it != mTileContentFormats.constEnd() )
614 {
615 switch ( it.value() )
616 {
617 case TileContentFormat::NotJson:
618 return false;
619 case TileContentFormat::Json:
620 break;
621 }
622 }
623 }
624
625 const QString contentUri = it.value()->tile()->resources().value( QStringLiteral( "content" ) ).toString();
626 locker.unlock();
627
628 if ( contentUri.isEmpty() )
629 return false;
630
631 // if node has content json, fetch it now and parse
632 const QByteArray subTile = retrieveContent( contentUri, feedback );
633 if ( !subTile.isEmpty() )
634 {
635 // we don't know for certain that the content IS json -- from https://github.com/CesiumGS/3d-tiles/tree/main/specification#tile-json says:
636 // "A file extension is not required for content.uri. A content’s tile format can
637 // be identified by the magic field in its header, or else as an external tileset if the content is JSON."
638 try
639 {
640 const auto subTileJson = json::parse( subTile.toStdString() );
641 QMutexLocker locker( &mLock );
642 refineNodeFromJson( it.value(), QUrl( contentUri ), subTileJson );
643 mTileContentFormats.insert( id, TileContentFormat::Json );
644 return true;
645 }
646 catch ( json::parse_error & )
647 {
648 QMutexLocker locker( &mLock );
649 mTileContentFormats.insert( id, TileContentFormat::NotJson );
650 return false;
651 }
652 }
653 else
654 {
655 // we got empty content, so the hierarchy content is probably missing,
656 // so let's mark it as not JSON so that we do not try to fetch it again
657 mTileContentFormats.insert( id, TileContentFormat::NotJson );
658 return false;
659 }
660}
661
662QByteArray QgsCesiumTiledSceneIndex::fetchContent( const QString &uri, QgsFeedback *feedback )
663{
664 QUrl url( uri );
665 // TODO -- error reporting?
666 if ( uri.startsWith( "http" ) )
667 {
668 QNetworkRequest networkRequest = QNetworkRequest( url );
669 QgsSetRequestInitiatorClass( networkRequest, QStringLiteral( "QgsCesiumTiledSceneIndex" ) );
670 networkRequest.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
671 networkRequest.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
672
673 mHeaders.updateNetworkRequest( networkRequest );
674
675 if ( QThread::currentThread() == QApplication::instance()->thread() )
676 {
677 // running on main thread, use a blocking get to handle authcfg and SSL errors ok.
679 networkRequest, mAuthCfg, false, feedback );
680 return reply.content();
681 }
682 else
683 {
684 // running on background thread, use tile download manager for efficient network handling
685 if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( networkRequest, mAuthCfg ) )
686 {
687 // TODO -- report error
688 return QByteArray();
689 }
690 std::unique_ptr< QgsTileDownloadManagerReply > reply( QgsApplication::tileDownloadManager()->get( networkRequest ) );
691
692 QEventLoop loop;
693 if ( feedback )
694 QObject::connect( feedback, &QgsFeedback::canceled, &loop, &QEventLoop::quit );
695
696 QObject::connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit );
697 loop.exec();
698
699 return reply->data();
700 }
701 }
702 else if ( url.isLocalFile() && QFile::exists( url.toLocalFile() ) )
703 {
704 QFile file( url.toLocalFile() );
705 if ( file.open( QIODevice::ReadOnly ) )
706 {
707 return file.readAll();
708 }
709 }
710 return QByteArray();
711}
712
713
714//
715// QgsCesiumTilesDataProviderSharedData
716//
717
718QgsCesiumTilesDataProviderSharedData::QgsCesiumTilesDataProviderSharedData()
719 : mIndex( QgsTiledSceneIndex( nullptr ) )
720{
721
722}
723
724void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, const QUrl &rootUrl, const QgsCoordinateTransformContext &transformContext, const QString &authCfg, const QgsHttpHeaders &headers )
725{
726 mTileset = json::parse( tileset.toStdString() );
727 if ( !mTileset.contains( "root" ) )
728 {
729 mError = QObject::tr( "JSON is not a valid Cesium 3D Tiles source (does not contain \"root\" value)" );
730 return;
731 }
732
733 mLayerMetadata.setType( QStringLiteral( "dataset" ) );
734
735 if ( mTileset.contains( "asset" ) )
736 {
737 const auto &asset = mTileset[ "asset" ];
738 if ( asset.contains( "tilesetVersion" ) )
739 {
740 try
741 {
742 const QString tilesetVersion = QString::fromStdString( asset["tilesetVersion"].get<std::string>() );
743 mLayerMetadata.setIdentifier( tilesetVersion );
744 }
745 catch ( json::type_error & )
746 {
747 QgsDebugError( QStringLiteral( "Error when parsing tilesetVersion value" ) );
748 }
749 }
750 }
751
752 mIndex = QgsTiledSceneIndex(
753 new QgsCesiumTiledSceneIndex(
754 mTileset,
755 rootUrl,
756 authCfg,
757 headers,
758 transformContext
759 )
760 );
761
762 // parse root
763 {
764 const auto &root = mTileset[ "root" ];
765 // parse root bounding volume
766
767 // TODO -- read crs from metadata tags. Need to find real world examples of this. And can metadata crs override
768 // the EPSG:4979 requirement from a region bounding volume??
769
770 {
771 // TODO -- on some datasets there is a "boundingVolume" present on the tileset itself, i.e. not the root node.
772 // what does this mean? Should we use it instead of the root node bounding volume if it's present?
773
775
776 const auto &rootBoundingVolume = root[ "boundingVolume" ];
777
778 QgsMatrix4x4 rootTransform;
779 if ( root.contains( "transform" ) && !root["transform"].is_null() )
780 {
781 const auto &transformJson = root["transform"];
782 double *ptr = rootTransform.data();
783 for ( int i = 0; i < 16; ++i )
784 ptr[i] = transformJson[i].get<double>();
785 }
786
787 if ( rootBoundingVolume.contains( "region" ) )
788 {
789 const QgsBox3D rootRegion = QgsCesiumUtils::parseRegion( rootBoundingVolume[ "region" ] );
790 if ( !rootRegion.isNull() )
791 {
792 mBoundingVolume = QgsTiledSceneBoundingVolume( QgsOrientedBox3D::fromBox3D( rootRegion ) );
793
794 // only set z range for datasets which aren't too large (ie global datasets)
795 if ( !mIndex.rootTile().boundingVolume().box().isNull() )
796 {
797 mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() );
798 }
799 mLayerCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) );
800 mSceneCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) );
801
802 mLayerMetadata.setCrs( mSceneCrs );
803 mExtent = rootRegion.toRectangle();
804 spatialExtent.extentCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) );
805 spatialExtent.bounds = rootRegion;
806 }
807 }
808 else if ( rootBoundingVolume.contains( "box" ) )
809 {
810 const QgsOrientedBox3D bbox = QgsCesiumUtils::parseBox( rootBoundingVolume["box"] );
811 if ( !bbox.isNull() )
812 {
813 // layer must advertise as EPSG:4979, as the various QgsMapLayer
814 // methods which utilize QgsMapLayer::crs() (such as layer extent transformation)
815 // are all purely 2D and can't handle the cesium data source z value
816 // range in EPSG:4978 !
817 mLayerCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) );
818 mSceneCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) );
819 mLayerMetadata.setCrs( mSceneCrs );
820
821 mBoundingVolume = QgsTiledSceneBoundingVolume( bbox );
822 mBoundingVolume.transform( rootTransform );
823 try
824 {
825 QgsCoordinateTransform ct( mSceneCrs, mLayerCrs, transformContext );
826 ct.setBallparkTransformsAreAppropriate( true );
827 const QgsBox3D rootRegion = mBoundingVolume.bounds( ct );
828 // only set z range for datasets which aren't too large (ie global datasets)
829 if ( !mIndex.rootTile().boundingVolume().box().isNull() )
830 {
831 mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() );
832 }
833
834 std::unique_ptr< QgsAbstractGeometry > extent2D( mBoundingVolume.as2DGeometry( ct ) );
835 mExtent = extent2D->boundingBox();
836 }
837 catch ( QgsCsException & )
838 {
839 QgsDebugError( QStringLiteral( "Caught transform exception when transforming boundingVolume" ) );
840 }
841
842 spatialExtent.extentCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) );
843 spatialExtent.bounds = mBoundingVolume.bounds();
844 }
845 }
846 else if ( rootBoundingVolume.contains( "sphere" ) )
847 {
848 QgsSphere sphere = QgsCesiumUtils::parseSphere( rootBoundingVolume["sphere"] );
849 if ( !sphere.isNull() )
850 {
851 // layer must advertise as EPSG:4979, as the various QgsMapLayer
852 // methods which utilize QgsMapLayer::crs() (such as layer extent transformation)
853 // are all purely 2D and can't handle the cesium data source z value
854 // range in EPSG:4978 !
855 mLayerCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) );
856 mSceneCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) );
857 mLayerMetadata.setCrs( mSceneCrs );
858
859 sphere = QgsCesiumUtils::transformSphere( sphere, rootTransform );
860
862 try
863 {
864 QgsCoordinateTransform ct( mSceneCrs, mLayerCrs, transformContext );
865 ct.setBallparkTransformsAreAppropriate( true );
866 const QgsBox3D rootRegion = mBoundingVolume.bounds( ct );
867 // only set z range for datasets which aren't too large (ie global datasets)
868 if ( !mIndex.rootTile().boundingVolume().box().isNull() )
869 {
870 mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() );
871 }
872
873 std::unique_ptr< QgsAbstractGeometry > extent2D( mBoundingVolume.as2DGeometry( ct ) );
874 mExtent = extent2D->boundingBox();
875 }
876 catch ( QgsCsException & )
877 {
878 QgsDebugError( QStringLiteral( "Caught transform exception when transforming boundingVolume" ) );
879 }
880
881 spatialExtent.extentCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) );
882 spatialExtent.bounds = mBoundingVolume.bounds();
883 }
884 }
885 else
886 {
887 mError = QObject::tr( "JSON is not a valid Cesium 3D Tiles source (unsupported boundingVolume format)" );
888 return;
889 }
890
891 QgsLayerMetadata::Extent layerExtent;
892 layerExtent.setSpatialExtents( {spatialExtent } );
893 mLayerMetadata.setExtent( layerExtent );
894 }
895 }
896}
897
898
899//
900// QgsCesiumTilesDataProvider
901//
902
903QgsCesiumTilesDataProvider::QgsCesiumTilesDataProvider( const QString &uri, const ProviderOptions &providerOptions, Qgis::DataProviderReadFlags flags )
904 : QgsTiledSceneDataProvider( uri, providerOptions, flags )
905 , mShared( std::make_shared< QgsCesiumTilesDataProviderSharedData >() )
906{
907 QgsDataSourceUri dsUri;
908 dsUri.setEncodedUri( uri );
909 mAuthCfg = dsUri.authConfigId();
910 mHeaders = dsUri.httpHeaders();
911
912 mIsValid = init();
913}
914
915QgsCesiumTilesDataProvider::QgsCesiumTilesDataProvider( const QgsCesiumTilesDataProvider &other )
917 , mIsValid( other.mIsValid )
918 , mAuthCfg( other.mAuthCfg )
919 , mHeaders( other.mHeaders )
920{
921 QgsReadWriteLocker locker( other.mShared->mReadWriteLock, QgsReadWriteLocker::Read );
922 mShared = other.mShared;
923}
924
925Qgis::DataProviderFlags QgsCesiumTilesDataProvider::flags() const
926{
927 return mProviderFlags;
928}
929
930Qgis::TiledSceneProviderCapabilities QgsCesiumTilesDataProvider::capabilities() const
931{
933}
934
935QgsCesiumTilesDataProvider::~QgsCesiumTilesDataProvider() = default;
936
937QgsCesiumTilesDataProvider *QgsCesiumTilesDataProvider::clone() const
938{
940 return new QgsCesiumTilesDataProvider( *this );
941}
942
943bool QgsCesiumTilesDataProvider::init()
944{
946
947 QString tileSetUri;
948 const QString uri = dataSourceUri();
949
950 if ( uri.startsWith( QLatin1String( "ion://" ) ) )
951 {
952 QUrl url( uri );
953 const QString assetId = QUrlQuery( url ).queryItemValue( QStringLiteral( "assetId" ) );
954 const QString accessToken = QUrlQuery( url ).queryItemValue( QStringLiteral( "accessToken" ) );
955
956 const QString CESIUM_ION_URL = QStringLiteral( "https://api.cesium.com/" );
957
958 // get asset info
959 {
960 const QString assetInfoEndpoint = CESIUM_ION_URL + QStringLiteral( "v1/assets/%1" ).arg( assetId );
961 QNetworkRequest request = QNetworkRequest( assetInfoEndpoint );
962 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsCesiumTilesDataProvider" ) )
963 mHeaders.updateNetworkRequest( request );
964 if ( !accessToken.isEmpty() )
965 request.setRawHeader( "Authorization", QStringLiteral( "Bearer %1" ).arg( accessToken ).toLocal8Bit() );
966
967 QgsBlockingNetworkRequest networkRequest;
968 if ( accessToken.isEmpty() )
969 networkRequest.setAuthCfg( mAuthCfg );
970
971 switch ( networkRequest.get( request ) )
972 {
974 break;
975
979 // TODO -- error reporting
980 return false;
981 }
982
983 const QgsNetworkReplyContent content = networkRequest.reply();
984 const json assetInfoJson = json::parse( content.content().toStdString() );
985 if ( assetInfoJson["type"] != "3DTILES" )
986 {
987 appendError( QgsErrorMessage( tr( "Only ion 3D Tiles content can be accessed, not %1" ).arg( QString::fromStdString( assetInfoJson["type"].get<std::string>() ) ) ) );
988 return false;
989 }
990
991 const QString name = QString::fromStdString( assetInfoJson["name"].get<std::string>() );
992 if ( name.compare( QLatin1String( "Google Photorealistic 3D Tiles" ), Qt::CaseInsensitive ) == 0 )
993 {
994 // consider Google Photorealistic 3D Tiles as a basemap source, as this completely covers
995 // the globe and contains embedded terrain
996 mProviderFlags.setFlag( Qgis::DataProviderFlag::IsBasemapSource, true );
997 mProviderFlags.setFlag( Qgis::DataProviderFlag::Is3DBasemapSource, true );
998 }
999
1000 mShared->mLayerMetadata.setTitle( name );
1001 mShared->mLayerMetadata.setAbstract( QString::fromStdString( assetInfoJson["description"].get<std::string>() ) );
1002 const QString attribution = QString::fromStdString( assetInfoJson["attribution"].get<std::string>() );
1003 if ( !attribution.isEmpty() )
1004 mShared->mLayerMetadata.setRights( { attribution } );
1005
1006 mShared->mLayerMetadata.setDateTime( Qgis::MetadataDateType::Created, QDateTime::fromString( QString::fromStdString( assetInfoJson["dateAdded"].get<std::string>() ), Qt::DateFormat::ISODate ) );
1007 }
1008
1009 // get tileset access details
1010 {
1011 const QString tileAccessEndpoint = CESIUM_ION_URL + QStringLiteral( "v1/assets/%1/endpoint" ).arg( assetId );
1012 QNetworkRequest request = QNetworkRequest( tileAccessEndpoint );
1013 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsCesiumTilesDataProvider" ) )
1014 mHeaders.updateNetworkRequest( request );
1015 if ( !accessToken.isEmpty() )
1016 request.setRawHeader( "Authorization", QStringLiteral( "Bearer %1" ).arg( accessToken ).toLocal8Bit() );
1017
1018 QgsBlockingNetworkRequest networkRequest;
1019 if ( accessToken.isEmpty() )
1020 networkRequest.setAuthCfg( mAuthCfg );
1021
1022 switch ( networkRequest.get( request ) )
1023 {
1025 break;
1026
1030 // TODO -- error reporting
1031 return false;
1032 }
1033
1034 const QgsNetworkReplyContent content = networkRequest.reply();
1035 const json tileAccessJson = json::parse( content.content().toStdString() );
1036
1037 if ( tileAccessJson.contains( "url" ) )
1038 {
1039 tileSetUri = QString::fromStdString( tileAccessJson["url"].get<std::string>() );
1040 }
1041 else if ( tileAccessJson.contains( "options" ) )
1042 {
1043 const auto &optionsJson = tileAccessJson["options"];
1044 if ( optionsJson.contains( "url" ) )
1045 {
1046 tileSetUri = QString::fromStdString( optionsJson["url"].get<std::string>() );
1047 }
1048 }
1049
1050 if ( tileAccessJson.contains( "accessToken" ) )
1051 {
1052 // The tileset accessToken is NOT the same as the token we use to access the asset details -- ie we can't
1053 // use the same authentication as we got from the providers auth cfg!
1054 mHeaders.insert( QStringLiteral( "Authorization" ),
1055 QStringLiteral( "Bearer %1" ).arg( QString::fromStdString( tileAccessJson["accessToken"].get<std::string>() ) ) );
1056 }
1057 mAuthCfg.clear();
1058 }
1059 }
1060 else
1061 {
1062 QgsDataSourceUri dsUri;
1063 dsUri.setEncodedUri( uri );
1064 tileSetUri = dsUri.param( QStringLiteral( "url" ) );
1065 }
1066
1067 if ( !tileSetUri.isEmpty() )
1068 {
1069 const QUrl url( tileSetUri );
1070
1071 QNetworkRequest request = QNetworkRequest( url );
1072 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsCesiumTilesDataProvider" ) )
1073 mHeaders.updateNetworkRequest( request );
1074
1075 QgsBlockingNetworkRequest networkRequest;
1076 networkRequest.setAuthCfg( mAuthCfg );
1077
1078 switch ( networkRequest.get( request ) )
1079 {
1081 break;
1082
1086 // TODO -- error reporting
1087 return false;
1088 }
1089
1090 const QgsNetworkReplyContent content = networkRequest.reply();
1091
1092 mShared->initialize( content.content(), tileSetUri, transformContext(), mAuthCfg, mHeaders );
1093
1094 mShared->mLayerMetadata.addLink( QgsAbstractMetadataBase::Link( tr( "Source" ), QStringLiteral( "WWW:LINK" ), tileSetUri ) );
1095 }
1096 else
1097 {
1098 // try uri as a local file
1099 const QFileInfo fi( dataSourceUri() );
1100 if ( fi.exists() )
1101 {
1102 QFile file( dataSourceUri( ) );
1103 if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
1104 {
1105 const QByteArray raw = file.readAll();
1106 mShared->initialize( raw, QUrl::fromLocalFile( dataSourceUri() ), transformContext(), mAuthCfg, mHeaders );
1107 }
1108 else
1109 {
1110 return false;
1111 }
1112 }
1113 else
1114 {
1115 return false;
1116 }
1117 }
1118
1119 if ( !mShared->mIndex.isValid() )
1120 {
1121 appendError( mShared->mError );
1122 return false;
1123 }
1124 return true;
1125}
1126
1127QgsCoordinateReferenceSystem QgsCesiumTilesDataProvider::crs() const
1128{
1130
1131 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1132 return mShared->mLayerCrs;
1133}
1134
1135QgsRectangle QgsCesiumTilesDataProvider::extent() const
1136{
1138
1139 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1140 return mShared->mExtent;
1141}
1142
1143bool QgsCesiumTilesDataProvider::isValid() const
1144{
1146
1147 return mIsValid;
1148}
1149
1150QString QgsCesiumTilesDataProvider::name() const
1151{
1153
1154 return PROVIDER_KEY;
1155}
1156
1157QString QgsCesiumTilesDataProvider::description() const
1158{
1160
1161 return QObject::tr( "Cesium 3D Tiles" );
1162}
1163
1164QString QgsCesiumTilesDataProvider::htmlMetadata() const
1165{
1167
1168 QString metadata;
1169
1170 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1171 if ( mShared->mTileset.contains( "asset" ) )
1172 {
1173 const auto &asset = mShared->mTileset[ "asset" ];
1174 if ( asset.contains( "version" ) )
1175 {
1176 const QString version = QString::fromStdString( asset["version"].get<std::string>() );
1177 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "3D Tiles Version" ) % QStringLiteral( "</td><td>%1</a>" ).arg( version ) % QStringLiteral( "</td></tr>\n" );
1178 }
1179
1180 if ( asset.contains( "tilesetVersion" ) )
1181 {
1182 try
1183 {
1184 const QString tilesetVersion = QString::fromStdString( asset["tilesetVersion"].get<std::string>() );
1185 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Tileset Version" ) % QStringLiteral( "</td><td>%1</a>" ).arg( tilesetVersion ) % QStringLiteral( "</td></tr>\n" );
1186 }
1187 catch ( json::type_error & )
1188 {
1189 QgsDebugError( QStringLiteral( "Error when parsing tilesetVersion value" ) );
1190 }
1191 }
1192
1193 if ( asset.contains( "generator" ) )
1194 {
1195 const QString generator = QString::fromStdString( asset["generator"].get<std::string>() );
1196 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Tileset Generator" ) % QStringLiteral( "</td><td>%1</a>" ).arg( generator ) % QStringLiteral( "</td></tr>\n" );
1197 }
1198 }
1199 if ( mShared->mTileset.contains( "extensionsRequired" ) )
1200 {
1201 QStringList extensions;
1202 for ( const auto &item : mShared->mTileset["extensionsRequired"] )
1203 {
1204 extensions << QString::fromStdString( item.get<std::string>() );
1205 }
1206 if ( !extensions.isEmpty() )
1207 {
1208 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Extensions Required" ) % QStringLiteral( "</td><td><ul><li>%1</li></ul></a>" ).arg( extensions.join( QLatin1String( "</li><li>" ) ) ) % QStringLiteral( "</td></tr>\n" );
1209 }
1210 }
1211 if ( mShared->mTileset.contains( "extensionsUsed" ) )
1212 {
1213 QStringList extensions;
1214 for ( const auto &item : mShared->mTileset["extensionsUsed"] )
1215 {
1216 extensions << QString::fromStdString( item.get<std::string>() );
1217 }
1218 if ( !extensions.isEmpty() )
1219 {
1220 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Extensions Used" ) % QStringLiteral( "</td><td><ul><li>%1</li></ul></a>" ).arg( extensions.join( QLatin1String( "</li><li>" ) ) ) % QStringLiteral( "</td></tr>\n" );
1221 }
1222 }
1223
1224 if ( !mShared->mZRange.isInfinite() )
1225 {
1226 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Z Range" ) % QStringLiteral( "</td><td>%1 - %2</a>" ).arg( QLocale().toString( mShared->mZRange.lower() ), QLocale().toString( mShared->mZRange.upper() ) ) % QStringLiteral( "</td></tr>\n" );
1227 }
1228
1229 return metadata;
1230}
1231
1232QgsLayerMetadata QgsCesiumTilesDataProvider::layerMetadata() const
1233{
1235 if ( !mShared )
1236 return QgsLayerMetadata();
1237
1238 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1239 return mShared->mLayerMetadata;
1240}
1241
1242const QgsCoordinateReferenceSystem QgsCesiumTilesDataProvider::sceneCrs() const
1243{
1245 if ( !mShared )
1247
1248 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1249 return mShared->mSceneCrs ;
1250}
1251
1252const QgsTiledSceneBoundingVolume &QgsCesiumTilesDataProvider::boundingVolume() const
1253{
1255 static QgsTiledSceneBoundingVolume nullVolume;
1256 if ( !mShared )
1257 return nullVolume;
1258
1259 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1260 return mShared ? mShared->mBoundingVolume : nullVolume;
1261}
1262
1263QgsTiledSceneIndex QgsCesiumTilesDataProvider::index() const
1264{
1266 if ( !mShared )
1267 return QgsTiledSceneIndex( nullptr );
1268
1269 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1270 return mShared->mIndex;
1271}
1272
1273QgsDoubleRange QgsCesiumTilesDataProvider::zRange() const
1274{
1276 if ( !mShared )
1277 return QgsDoubleRange();
1278
1279 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1280 return mShared->mZRange;
1281}
1282
1283
1284//
1285// QgsCesiumTilesProviderMetadata
1286//
1287
1288QgsCesiumTilesProviderMetadata::QgsCesiumTilesProviderMetadata():
1289 QgsProviderMetadata( PROVIDER_KEY, PROVIDER_DESCRIPTION )
1290{
1291}
1292
1293QIcon QgsCesiumTilesProviderMetadata::icon() const
1294{
1295 return QgsApplication::getThemeIcon( QStringLiteral( "mIconCesium3dTiles.svg" ) );
1296}
1297
1298QgsCesiumTilesDataProvider *QgsCesiumTilesProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags )
1299{
1300 return new QgsCesiumTilesDataProvider( uri, options, flags );
1301}
1302
1303QList<QgsProviderSublayerDetails> QgsCesiumTilesProviderMetadata::querySublayers( const QString &uri, Qgis::SublayerQueryFlags, QgsFeedback * ) const
1304{
1305 const QVariantMap parts = decodeUri( uri );
1306 if ( parts.value( QStringLiteral( "file-name" ) ).toString().compare( QLatin1String( "tileset.json" ), Qt::CaseInsensitive ) == 0 )
1307 {
1309 details.setUri( uri );
1310 details.setProviderKey( PROVIDER_KEY );
1313 return {details};
1314 }
1315 else
1316 {
1317 return {};
1318 }
1319}
1320
1321int QgsCesiumTilesProviderMetadata::priorityForUri( const QString &uri ) const
1322{
1323 const QVariantMap parts = decodeUri( uri );
1324 if ( parts.value( QStringLiteral( "file-name" ) ).toString().compare( QLatin1String( "tileset.json" ), Qt::CaseInsensitive ) == 0 )
1325 return 100;
1326
1327 return 0;
1328}
1329
1330QList<Qgis::LayerType> QgsCesiumTilesProviderMetadata::validLayerTypesForUri( const QString &uri ) const
1331{
1332 const QVariantMap parts = decodeUri( uri );
1333 if ( parts.value( QStringLiteral( "file-name" ) ).toString().compare( QLatin1String( "tileset.json" ), Qt::CaseInsensitive ) == 0 )
1334 return QList< Qgis::LayerType>() << Qgis::LayerType::TiledScene;
1335
1336 return QList< Qgis::LayerType>();
1337}
1338
1339QVariantMap QgsCesiumTilesProviderMetadata::decodeUri( const QString &uri ) const
1340{
1341 QVariantMap uriComponents;
1342 QUrl url = QUrl::fromUserInput( uri );
1343 uriComponents.insert( QStringLiteral( "file-name" ), url.fileName() );
1344 uriComponents.insert( QStringLiteral( "path" ), uri );
1345 return uriComponents;
1346}
1347
1348QString QgsCesiumTilesProviderMetadata::filters( Qgis::FileFilterType type )
1349{
1350 switch ( type )
1351 {
1358 return QString();
1359
1361 return QObject::tr( "Cesium 3D Tiles" ) + QStringLiteral( " (tileset.json TILESET.JSON)" );
1362 }
1363 return QString();
1364}
1365
1366QgsProviderMetadata::ProviderCapabilities QgsCesiumTilesProviderMetadata::providerCapabilities() const
1367{
1368 return FileBasedUris;
1369}
1370
1371QList<Qgis::LayerType> QgsCesiumTilesProviderMetadata::supportedLayerTypes() const
1372{
1373 return { Qgis::LayerType::TiledScene };
1374}
1375
1376QString QgsCesiumTilesProviderMetadata::encodeUri( const QVariantMap &parts ) const
1377{
1378 const QString path = parts.value( QStringLiteral( "path" ) ).toString();
1379 return path;
1380}
1381
1382QgsProviderMetadata::ProviderMetadataCapabilities QgsCesiumTilesProviderMetadata::capabilities() const
1383{
1384 return ProviderMetadataCapability::LayerTypesForUri
1385 | ProviderMetadataCapability::PriorityForUri
1386 | ProviderMetadataCapability::QuerySublayers;
1387}
1388
Provides global constants and enumerations for use throughout the application.
Definition qgis.h:54
QFlags< TiledSceneProviderCapability > TiledSceneProviderCapabilities
Tiled scene data provider capabilities.
Definition qgis.h:5477
QFlags< DataProviderFlag > DataProviderFlags
Data provider flags.
Definition qgis.h:2265
FileFilterType
Type of file filters.
Definition qgis.h:1318
@ TiledScene
Tiled scene layers.
@ Vector
Vector layers.
@ VectorTile
Vector tile layers.
@ Mesh
Mesh layers.
@ Raster
Raster layers.
@ MeshDataset
Mesh datasets.
@ PointCloud
Point clouds.
@ Is3DBasemapSource
Associated source should be considered a '3D basemap' layer. See Qgis::MapLayerProperty::Is3DBasemapL...
@ IsBasemapSource
Associated source should be considered a 'basemap' layer. See Qgis::MapLayerProperty::IsBasemapLayer.
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
Definition qgis.h:450
@ ReadLayerMetadata
Provider can read layer metadata from data store. See QgsDataProvider::layerMetadata()
QFlags< SublayerQueryFlag > SublayerQueryFlags
Sublayer query flags.
Definition qgis.h:1362
TileChildrenAvailability
Possible availability states for a tile's children.
Definition qgis.h:5514
@ Available
Tile children are already available.
@ NeedFetching
Tile has children, but they are not yet available and must be fetched.
@ NoChildren
Tile is known to have no children.
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
@ NoHierarchyFetch
Do not allow hierarchy fetching when hierarchy is not currently available. Avoids network requests,...
Axis
Cartesian axes.
Definition qgis.h:2401
@ X
X-axis.
@ Z
Z-axis.
@ Y
Y-axis.
@ Created
Date created.
@ Additive
When tile is refined its content should be used alongside its children simultaneously.
@ Replacement
When tile is refined then its children should be used in place of itself.
An abstract base class for tiled scene data provider indices.
virtual QgsTiledSceneTile rootTile() const =0
Returns the root tile for the index.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsTileDownloadManager * tileDownloadManager()
Returns the application's tile download manager, used for download of map tiles when rendering.
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
bool updateNetworkRequest(QNetworkRequest &request, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkRequest with an authentication config.
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
@ NetworkError
A network error occurred.
@ ServerExceptionError
An exception was raised by the server.
@ NoError
No error was encountered.
@ TimeoutError
Timeout was reached before a reply was received.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:43
double zMaximum() const
Returns the maximum z value.
Definition qgsbox3d.h:259
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
Definition qgsbox3d.h:379
QVector< QgsVector3D > corners() const
Returns an array of all box corners as 3D vectors.
Definition qgsbox3d.cpp:359
double width() const
Returns the width of the box.
Definition qgsbox3d.h:278
double zMinimum() const
Returns the minimum z value.
Definition qgsbox3d.h:252
double height() const
Returns the height of the box.
Definition qgsbox3d.h:285
bool isNull() const
Test if the box is null (holding no spatial information).
Definition qgsbox3d.cpp:310
static QgsSphere parseSphere(const json &sphere)
Parses a sphere object from a Cesium JSON document.
static QgsOrientedBox3D parseBox(const json &box)
Parses a box object from a Cesium JSON document to an oriented bounding box.
static QgsBox3D parseRegion(const json &region)
Parses a region object from a Cesium JSON object to a 3D box.
static QgsSphere transformSphere(const QgsSphere &sphere, const QgsMatrix4x4 &transform)
Applies a transform to a sphere.
Represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
Handles coordinate transforms between two coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Stores the component parts of a data source URI (e.g.
void setEncodedUri(const QByteArray &uri)
Sets the complete encoded uri.
QgsHttpHeaders httpHeaders() const
Returns http headers.
QString param(const QString &key) const
Returns a generic parameter value corresponding to the specified key.
QString authConfigId() const
Returns any associated authentication configuration ID stored in the URI.
QgsRange which stores a range of double values.
Definition qgsrange.h:233
Represents a single error message.
Definition qgserror.h:33
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
void canceled()
Internal routines can connect to this signal if they use event loop.
Implements simple HTTP header management.
A structured metadata store for a map layer.
A simple 4x4 matrix implementation useful for transformation in 3D space.
bool isIdentity() const
Returns whether this matrix is an identity matrix.
double * data()
Returns pointer to the matrix data (stored in column-major order)
static QgsNetworkReplyContent blockingGet(QNetworkRequest &request, const QString &authCfg=QString(), bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Posts a GET request to obtain the contents of the target request and returns a new QgsNetworkReplyCon...
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
Represents a oriented (rotated) box in 3 dimensions.
bool isNull() const
Returns true if the box is a null box.
static QgsOrientedBox3D fromBox3D(const QgsBox3D &box)
Constructs an oriented box from an axis-aligned bounding box.
Holds data provider key, description, and associated shared library file or function pointer informat...
QFlags< ProviderMetadataCapability > ProviderMetadataCapabilities
QFlags< ProviderCapability > ProviderCapabilities
Contains details about a sub layer available from a dataset.
void setUri(const QString &uri)
Sets the layer's uri.
void setType(Qgis::LayerType type)
Sets the layer type.
void setName(const QString &name)
Sets the layer's name.
void setProviderKey(const QString &key)
Sets the associated data provider key.
static QString suggestLayerNameFromFilePath(const QString &path)
Suggests a suitable layer name given only a file path.
A convenience class that simplifies locking and unlocking QReadWriteLocks.
A rectangle specified with double values.
A spherical geometry object.
Definition qgssphere.h:41
bool isNull() const
Returns true if the sphere is a null (default constructed) sphere.
Definition qgssphere.cpp:33
QgsBox3D boundingBox() const
Returns the 3-dimensional bounding box containing the sphere.
Definition qgssphere.cpp:78
void finished()
Emitted when the reply has finished (either with a success or with a failure)
Represents a bounding volume for a tiled scene.
QgsOrientedBox3D box() const
Returns the volume's oriented box.
bool intersects(const QgsOrientedBox3D &box) const
Returns true if this bounds intersects the specified box.
void transform(const QgsMatrix4x4 &transform)
Applies a transform to the bounding volume.
Base class for data providers for QgsTiledSceneLayer.
An index for tiled scene data providers.
Allows representing QgsTiledSceneTiles in a hierarchical tree.
void addChild(QgsTiledSceneNode *child)
Adds a child to this node.
QgsTiledSceneNode * parentNode() const
Returns the parent of this node.
QList< QgsTiledSceneNode * > children() const
Returns this node's children.
QgsTiledSceneTile * tile()
Returns the tile associated with the node.
Tiled scene data request.
QgsOrientedBox3D filterBox() const
Returns the box from which data will be taken.
long long parentTileId() const
Returns the parent tile ID, if filtering is limited to children of a specific tile.
double requiredGeometricError() const
Returns the required geometric error threshold for the returned tiles, in meters.
QgsFeedback * feedback() const
Returns the feedback object that can be queried regularly by the request to check if it should be can...
Qgis::TiledSceneRequestFlags flags() const
Returns the flags which affect how tiles are fetched.
Represents an individual tile from a tiled scene data source.
void setTransform(const QgsMatrix4x4 &transform)
Sets the tile's transform.
Qgis::TileRefinementProcess refinementProcess() const
Returns the tile's refinement process.
const QgsTiledSceneBoundingVolume & boundingVolume() const
Returns the bounding volume for the tile.
long long id() const
Returns the tile's unique ID.
const QgsMatrix4x4 * transform() const
Returns the tile's transform.
void setResources(const QVariantMap &resources)
Sets the resources attached to the tile.
double geometricError() const
Returns the tile's geometric error, which is the error, in meters, of the tile's simplified represent...
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:49
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:51
double x() const
Returns X coordinate.
Definition qgsvector3d.h:47
#define QgsDebugError(str)
Definition qgslogger.h:40
#define QgsSetRequestInitiatorClass(request, _class)
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS
Setting options for creating vector data providers.
Metadata extent structure.
void setSpatialExtents(const QList< QgsLayerMetadata::SpatialExtent > &extents)
Sets the spatial extents of the resource.
Metadata spatial extent structure.
QgsCoordinateReferenceSystem extentCrs
Coordinate reference system for spatial extent.
QgsBox3D bounds
Geospatial extent of the resource.