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