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