20#include <nlohmann/json.hpp>
45#include <QApplication>
48#include <QJsonDocument>
50#include <QNetworkRequest>
51#include <QRecursiveMutex>
52#include <QRegularExpression>
56#include <qstringliteral.h>
58#include "moc_qgscesiumtilesdataprovider.cpp"
60using namespace Qt::StringLiterals;
64#define PROVIDER_KEY u"cesiumtiles"_s
65#define PROVIDER_DESCRIPTION u"Cesium 3D Tiles data provider"_s
71 QgsCesiumTiledSceneIndex(
const json &tileset,
const QUrl &rootUrl,
const QString &authCfg,
const QgsHttpHeaders &headers,
const QgsCoordinateTransformContext &transformContext );
73 std::unique_ptr< QgsTiledSceneTile > tileFromJson(
const json &node,
const QUrl &baseUrl,
const QgsTiledSceneTile *parent,
Qgis::Axis gltfUpAxis );
79 std::unique_ptr< QgsTiledSceneNode > nodeFromJson(
const json &node,
const QUrl &baseUrl, QgsTiledSceneNode *parent,
Qgis::Axis gltfUpAxis );
80 void refineNodeFromJson( QgsTiledSceneNode *node,
const QUrl &baseUrl,
const json &json );
82 QgsTiledSceneTile
rootTile() const final;
83 QgsTiledSceneTile getTile(
long long id ) final;
84 long long parentTileId(
long long id ) const final;
85 QVector<
long long > childTileIds(
long long id ) const final;
86 QVector<
long long > getTiles( const QgsTiledSceneRequest &request ) final;
87 Qgis::TileChildrenAvailability childAvailability(
long long id ) const final;
88 bool fetchHierarchy(
long long id, QgsFeedback *feedback =
nullptr ) final;
91 QByteArray fetchContent( const QString &uri, QgsFeedback *feedback =
nullptr ) final;
94 enum class TileContentFormat
100 void populateSubtreeRecursively(
101 QgsTiledSceneNode *node,
102 const QgsCesiumImplicitTiling::TileCoordinate &coord,
103 long long implicitRootTileId,
104 QgsCesiumImplicitTiling::Root &tilingData,
105 const QgsCesiumImplicitTiling::TileCoordinate &subtreeCoord
108 mutable QRecursiveMutex mLock;
109 QgsCoordinateTransformContext mTransformContext;
110 std::unique_ptr< QgsTiledSceneNode > mRootNode;
111 QMap< long long, QgsTiledSceneNode * > mNodeMap;
112 QMap< long long, TileContentFormat > mTileContentFormats;
114 QgsHttpHeaders mHeaders;
115 long long mNextTileId = 0;
120 QMap<long long, QgsCesiumImplicitTiling::Root> mImplicitTilingRoots;
123 struct ImplicitTileInfo
126 long long implicitRootTileId = 0;
128 QgsCesiumImplicitTiling::TileCoordinate coord;
132 QMap<long long, ImplicitTileInfo> mImplicitTileIndex;
135class QgsCesiumTilesDataProviderSharedData
138 QgsCesiumTilesDataProviderSharedData();
139 void initialize(
const QString &tileset,
const QUrl &rootUrl,
const QgsCoordinateTransformContext &transformContext,
const QString &authCfg,
const QgsHttpHeaders &headers );
141 QgsCoordinateReferenceSystem mLayerCrs;
142 QgsCoordinateReferenceSystem mSceneCrs;
143 QgsTiledSceneBoundingVolume mBoundingVolume;
145 QgsRectangle mExtent;
146 nlohmann::json mTileset;
147 QgsDoubleRange mZRange;
149 QgsTiledSceneIndex mIndex;
151 QgsLayerMetadata mLayerMetadata;
153 QReadWriteLock mReadWriteLock;
163 const std::string gltfUpAxisString = json.get<std::string>();
164 if ( gltfUpAxisString ==
"z" || gltfUpAxisString ==
"Z" )
168 else if ( gltfUpAxisString ==
"y" || gltfUpAxisString ==
"Y" )
172 else if ( gltfUpAxisString ==
"x" || gltfUpAxisString ==
"X" )
176 QgsDebugError( u
"Unsupported gltfUpAxis value: %1"_s.arg( QString::fromStdString( gltfUpAxisString ) ) );
181 : mTransformContext( transformContext )
182 , mAuthCfg( authCfg )
183 , mHeaders( headers )
186 if ( tileset.contains(
"asset" ) )
188 const auto &assetJson = tileset[
"asset"];
189 if ( assetJson.contains(
"gltfUpAxis" ) )
191 gltfUpAxis = axisFromJson( assetJson[
"gltfUpAxis"] );
195 mRootNode = nodeFromJson( tileset[
"root"], rootUrl,
nullptr, gltfUpAxis );
198std::unique_ptr< QgsTiledSceneTile > QgsCesiumTiledSceneIndex::tileFromJson(
const json &json,
const QUrl &baseUrl,
const QgsTiledSceneTile *parent,
Qgis::Axis gltfUpAxis )
200 auto tile = std::make_unique< QgsTiledSceneTile >( mNextTileId++ );
202 tile->setBaseUrl( baseUrl );
204 { u
"gltfUpAxis"_s,
static_cast< int >( gltfUpAxis ) },
205 { u
"contentFormat"_s, u
"cesiumtiles"_s },
209 if ( json.contains(
"transform" ) && !json[
"transform"].is_null() )
211 const auto &transformJson = json[
"transform"];
212 double *ptr = transform.
data();
213 for (
int i = 0; i < 16; ++i )
214 ptr[i] = transformJson[i].get<double>();
218 transform = *parent->
transform() * transform;
221 else if ( parent && parent->
transform() )
226 tile->setTransform( transform );
228 const auto &boundingVolume = json[
"boundingVolume"];
230 if ( boundingVolume.contains(
"region" ) )
238 else if ( boundingVolume.contains(
"box" ) )
248 else if ( boundingVolume.contains(
"sphere" ) )
262 tile->setBoundingVolume( volume );
264 if ( json.contains(
"geometricError" ) )
265 tile->setGeometricError( json[
"geometricError"].get< double >() );
266 if ( json.contains(
"refine" ) )
268 if ( json[
"refine"] ==
"ADD" )
270 else if ( json[
"refine"] ==
"REPLACE" )
279 if ( json.contains(
"content" ) && !json[
"content"].is_null() )
281 const auto &contentJson = json[
"content"];
285 if ( contentJson.contains(
"uri" ) && !contentJson[
"uri"].is_null() )
287 QString relativeUri = QString::fromStdString( contentJson[
"uri"].get<std::string>() );
288 contentUri = baseUrl.resolved( QUrl( relativeUri ) ).toString();
290 if ( baseUrl.hasQuery() && QUrl( relativeUri ).isRelative() )
293 else if ( contentJson.contains(
"url" ) && !contentJson[
"url"].is_null() )
295 QString relativeUri = QString::fromStdString( contentJson[
"url"].get<std::string>() );
296 contentUri = baseUrl.resolved( QUrl( relativeUri ) ).toString();
298 if ( baseUrl.hasQuery() && QUrl( relativeUri ).isRelative() )
301 if ( !contentUri.isEmpty() )
303 tile->setResources( { { u
"content"_s, contentUri } } );
310std::unique_ptr<QgsTiledSceneNode> QgsCesiumTiledSceneIndex::nodeFromJson(
const json &json,
const QUrl &baseUrl,
QgsTiledSceneNode *parent,
Qgis::Axis gltfUpAxis )
312 std::unique_ptr< QgsTiledSceneTile > tile = tileFromJson( json, baseUrl, parent ? parent->
tile() :
nullptr, gltfUpAxis );
313 auto newNode = std::make_unique< QgsTiledSceneNode >( tile.release() );
314 mNodeMap.insert( newNode->tile()->id(), newNode.get() );
316 if ( json.contains(
"implicitTiling" ) )
321 const long long rootTileId = newNode->tile()->id();
322 mImplicitTilingRoots.insert( rootTileId, tilingData );
323 mImplicitTileIndex.insert( rootTileId, { rootTileId, { 0, 0, 0 } } );
326 else if ( json.contains(
"children" ) )
328 for (
const auto &childJson : json[
"children"] )
330 nodeFromJson( childJson, baseUrl, newNode.get(), gltfUpAxis );
336 parent->
addChild( newNode.release() );
345void QgsCesiumTiledSceneIndex::refineNodeFromJson(
QgsTiledSceneNode *node,
const QUrl &baseUrl,
const json &json )
347 const auto &rootTileJson = json[
"root"];
350 if ( json.contains(
"asset" ) )
352 const auto &assetJson = json[
"asset"];
353 if ( assetJson.contains(
"gltfUpAxis" ) )
355 gltfUpAxis = axisFromJson( assetJson[
"gltfUpAxis"] );
359 std::unique_ptr< QgsTiledSceneTile > newTile = tileFromJson( rootTileJson, baseUrl, node->
tile(), gltfUpAxis );
369 if ( newTile->transform() )
372 if ( rootTileJson.contains(
"children" ) )
374 for (
const auto &childJson : rootTileJson[
"children"] )
376 nodeFromJson( childJson, baseUrl, node, gltfUpAxis );
383 QMutexLocker locker( &mLock );
389 QMutexLocker locker( &mLock );
390 auto it = mNodeMap.constFind(
id );
391 if ( it != mNodeMap.constEnd() )
393 return *( it.value()->tile() );
399long long QgsCesiumTiledSceneIndex::parentTileId(
long long id )
const
401 QMutexLocker locker( &mLock );
402 auto it = mNodeMap.constFind(
id );
403 if ( it != mNodeMap.constEnd() )
407 return parent->
tile()->
id();
414QVector< long long > QgsCesiumTiledSceneIndex::childTileIds(
long long id )
const
416 QMutexLocker locker( &mLock );
417 auto it = mNodeMap.constFind(
id );
418 if ( it != mNodeMap.constEnd() )
420 QVector< long long > childIds;
421 const QList< QgsTiledSceneNode * > children = it.value()->children();
422 childIds.reserve( children.size() );
425 childIds << child->tile()->id();
435 QVector< long long > results;
438 traverseNode = [&request, &traverseNode, &results,
this](
QgsTiledSceneNode *node ) {
453 QList< QgsTiledSceneNode * > children = node->
children();
454 if ( children.empty() )
456 switch ( childAvailability( tile->
id() ) )
466 if ( fetchHierarchy( tile->
id() ), request.
feedback() )
481 traverseNode( child );
488 results << tile->
id();
493 if ( children.empty() )
494 results << tile->
id();
500 results << tile->
id();
504 QMutexLocker locker( &mLock );
508 traverseNode( mRootNode.get() );
513 if ( it != mNodeMap.constEnd() )
515 traverseNode( it.value() );
525 QMutexLocker locker( &mLock );
527 auto it = mNodeMap.constFind(
id );
528 if ( it == mNodeMap.constEnd() )
532 auto implicitIt = mImplicitTileIndex.constFind(
id );
533 if ( implicitIt != mImplicitTileIndex.constEnd() )
535 const ImplicitTileInfo &info = implicitIt.value();
536 const auto tilingDataIt = mImplicitTilingRoots.constFind( info.implicitRootTileId );
537 if ( tilingDataIt == mImplicitTilingRoots.constEnd() )
541 if ( !it.value()->children().isEmpty() )
547 if ( !it.value()->children().isEmpty() )
550 contentUri = it.value()->tile()->resources().value( u
"content"_s ).toString();
554 auto it = mTileContentFormats.constFind(
id );
555 if ( it != mTileContentFormats.constEnd() )
557 switch ( it.value() )
559 case TileContentFormat::NotJson:
561 case TileContentFormat::Json:
568 if ( contentUri.isEmpty() )
578 const thread_local QRegularExpression isJsonRx( u
".*\\.json(?:\\?.*)?$"_s, QRegularExpression::PatternOption::CaseInsensitiveOption );
579 if ( isJsonRx.match( contentUri ).hasMatch() )
583 const thread_local QRegularExpression antiCandidateRx( u
".*\\.(gltf|glb|b3dm|i3dm|pnts|cmpt|bin|glbin|glbuf|png|jpeg|jpg)(?:\\?.*)?$"_s, QRegularExpression::PatternOption::CaseInsensitiveOption );
584 if ( antiCandidateRx.match( contentUri ).hasMatch() )
592void QgsCesiumTiledSceneIndex::populateSubtreeRecursively(
601 for (
auto it = children.constBegin(); it != children.constEnd(); ++it )
603 long long childId = it.value()->tile()->id();
604 mImplicitTileIndex.insert( childId, { implicitRootTileId, it.key() } );
605 mNodeMap.insert( childId, it.value() );
611 const auto childIt = mImplicitTileIndex.constFind( child->tile()->id() );
612 if ( childIt == mImplicitTileIndex.constEnd() )
616 const int childLocalLevel = childCoord.
level - subtreeCoord.
level;
620 populateSubtreeRecursively( child, childCoord, implicitRootTileId, tilingData, subtreeCoord );
625bool QgsCesiumTiledSceneIndex::fetchHierarchy(
long long id,
QgsFeedback *feedback )
627 QMutexLocker locker( &mLock );
628 auto it = mNodeMap.constFind(
id );
629 if ( it == mNodeMap.constEnd() )
633 auto implicitIt = mImplicitTileIndex.constFind(
id );
634 if ( implicitIt != mImplicitTileIndex.constEnd() )
636 const ImplicitTileInfo &info = implicitIt.value();
639 if ( !it.value()->children().isEmpty() )
643 Q_ASSERT( info.coord.level == subtreeCoord.
level );
646 if ( !tilingData.
subtreeCache.contains( subtreeCoord ) )
651 const QByteArray data = retrieveContent( subtreeUri, feedback );
655 tilingData.
subtreeCache.insert( subtreeCoord, subtree );
660 if ( it.value()->tile()->resources().isEmpty() && !tilingData.
contentUriTemplate.isEmpty() )
666 it.value()->tile()->setResources( { { u
"content"_s, rootContentUri } } );
670 populateSubtreeRecursively( it.value(), info.coord, info.implicitRootTileId, tilingData, subtreeCoord );
672 return !it.value()->children().isEmpty();
678 auto it = mTileContentFormats.constFind(
id );
679 if ( it != mTileContentFormats.constEnd() )
681 switch ( it.value() )
683 case TileContentFormat::NotJson:
685 case TileContentFormat::Json:
691 const QString contentUri = it.value()->tile()->resources().value( u
"content"_s ).toString();
694 if ( contentUri.isEmpty() )
698 const QByteArray subTile = retrieveContent( contentUri, feedback );
699 if ( !subTile.isEmpty() )
706 const auto subTileJson = json::parse( subTile.toStdString() );
707 QMutexLocker locker( &mLock );
708 refineNodeFromJson( it.value(), QUrl( contentUri ), subTileJson );
709 mTileContentFormats.insert(
id, TileContentFormat::Json );
712 catch ( json::parse_error & )
714 QMutexLocker locker( &mLock );
715 mTileContentFormats.insert(
id, TileContentFormat::NotJson );
723 mTileContentFormats.insert(
id, TileContentFormat::NotJson );
728QByteArray QgsCesiumTiledSceneIndex::fetchContent(
const QString &uri,
QgsFeedback *feedback )
732 if ( uri.startsWith(
"http" ) )
734 QNetworkRequest networkRequest = QNetworkRequest( url );
736 networkRequest.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
737 networkRequest.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
739 mHeaders.updateNetworkRequest( networkRequest );
741 if ( QThread::currentThread() == QApplication::instance()->thread() )
764 return reply->data();
767 else if ( url.isLocalFile() && QFile::exists( url.toLocalFile() ) )
769 QFile file( url.toLocalFile() );
770 if ( file.open( QIODevice::ReadOnly ) )
772 return file.readAll();
783QgsCesiumTilesDataProviderSharedData::QgsCesiumTilesDataProviderSharedData()
789 mTileset = json::parse( tileset.toStdString() );
790 if ( !mTileset.contains(
"root" ) )
792 mError = QObject::tr(
"JSON is not a valid Cesium 3D Tiles source (does not contain \"root\" value)" );
796 mLayerMetadata.setType( u
"dataset"_s );
798 if ( mTileset.contains(
"asset" ) )
800 const auto &asset = mTileset[
"asset"];
801 if ( asset.contains(
"tilesetVersion" ) )
805 const QString tilesetVersion = QString::fromStdString( asset[
"tilesetVersion"].get<std::string>() );
806 mLayerMetadata.setIdentifier( tilesetVersion );
808 catch ( json::type_error & )
810 QgsDebugError( u
"Error when parsing tilesetVersion value"_s );
815 mIndex =
QgsTiledSceneIndex(
new QgsCesiumTiledSceneIndex( mTileset, rootUrl, authCfg, headers, transformContext ) );
819 const auto &root = mTileset[
"root"];
831 const auto &rootBoundingVolume = root[
"boundingVolume"];
834 if ( root.contains(
"transform" ) && !root[
"transform"].is_null() )
836 const auto &transformJson = root[
"transform"];
837 double *ptr = rootTransform.
data();
838 for (
int i = 0; i < 16; ++i )
839 ptr[i] = transformJson[i].get<double>();
842 if ( rootBoundingVolume.contains(
"region" ) )
845 if ( !rootRegion.
isNull() )
850 if ( !mIndex.rootTile().boundingVolume().box().isNull() )
857 mLayerMetadata.setCrs( mSceneCrs );
860 spatialExtent.
bounds = rootRegion;
863 else if ( rootBoundingVolume.contains(
"box" ) )
874 mLayerMetadata.setCrs( mSceneCrs );
877 mBoundingVolume.transform( rootTransform );
881 ct.setBallparkTransformsAreAppropriate(
true );
882 const QgsBox3D rootRegion = mBoundingVolume.bounds( ct );
884 if ( !mIndex.rootTile().boundingVolume().box().isNull() )
889 std::unique_ptr< QgsAbstractGeometry > extent2D( mBoundingVolume.as2DGeometry( ct ) );
890 mExtent = extent2D->boundingBox();
894 QgsDebugError( u
"Caught transform exception when transforming boundingVolume"_s );
898 spatialExtent.
bounds = mBoundingVolume.bounds();
901 else if ( rootBoundingVolume.contains(
"sphere" ) )
912 mLayerMetadata.setCrs( mSceneCrs );
920 ct.setBallparkTransformsAreAppropriate(
true );
921 const QgsBox3D rootRegion = mBoundingVolume.bounds( ct );
923 if ( !mIndex.rootTile().boundingVolume().box().isNull() )
928 std::unique_ptr< QgsAbstractGeometry > extent2D( mBoundingVolume.as2DGeometry( ct ) );
929 mExtent = extent2D->boundingBox();
933 QgsDebugError( u
"Caught transform exception when transforming boundingVolume"_s );
937 spatialExtent.
bounds = mBoundingVolume.bounds();
942 mError = QObject::tr(
"JSON is not a valid Cesium 3D Tiles source (unsupported boundingVolume format)" );
948 mLayerMetadata.setExtent( layerExtent );
958QgsCesiumTilesDataProvider::QgsCesiumTilesDataProvider(
const QString &uri,
const ProviderOptions &providerOptions,
Qgis::DataProviderReadFlags flags )
960 , mShared( std::make_shared< QgsCesiumTilesDataProviderSharedData >() )
970QgsCesiumTilesDataProvider::QgsCesiumTilesDataProvider(
const QgsCesiumTilesDataProvider &other )
972 , mIsValid( other.mIsValid )
973 , mAuthCfg( other.mAuthCfg )
974 , mHeaders( other.mHeaders )
977 mShared = other.mShared;
982 return mProviderFlags;
990QgsCesiumTilesDataProvider::~QgsCesiumTilesDataProvider() =
default;
992QgsCesiumTilesDataProvider *QgsCesiumTilesDataProvider::clone()
const
995 return new QgsCesiumTilesDataProvider( *
this );
998bool QgsCesiumTilesDataProvider::init()
1003 const QString uri = dataSourceUri();
1005 if ( uri.startsWith(
"ion://"_L1 ) )
1008 const QString assetId = QUrlQuery( url ).queryItemValue( u
"assetId"_s );
1009 const QString accessToken = QUrlQuery( url ).queryItemValue( u
"accessToken"_s );
1011 const QString CESIUM_ION_URL = u
"https://api.cesium.com/"_s;
1015 const QString assetInfoEndpoint = CESIUM_ION_URL + u
"v1/assets/%1"_s.arg( assetId );
1016 QNetworkRequest request = QNetworkRequest( assetInfoEndpoint );
1018 if ( !accessToken.isEmpty() )
1019 request.setRawHeader(
"Authorization", u
"Bearer %1"_s.arg( accessToken ).toLocal8Bit() );
1022 if ( accessToken.isEmpty() )
1025 switch ( networkRequest.
get( request ) )
1038 const json assetInfoJson = json::parse( content.
content().toStdString() );
1039 if ( assetInfoJson[
"type"] !=
"3DTILES" )
1041 appendError(
QgsErrorMessage( tr(
"Only ion 3D Tiles content can be accessed, not %1" ).arg( QString::fromStdString( assetInfoJson[
"type"].get<std::string>() ) ) ) );
1045 const QString name = QString::fromStdString( assetInfoJson[
"name"].get<std::string>() );
1046 if ( name.compare(
"Google Photorealistic 3D Tiles"_L1, Qt::CaseInsensitive ) == 0 )
1054 mShared->mLayerMetadata.setTitle( name );
1055 mShared->mLayerMetadata.setAbstract( QString::fromStdString( assetInfoJson[
"description"].get<std::string>() ) );
1056 const QString attribution = QString::fromStdString( assetInfoJson[
"attribution"].get<std::string>() );
1057 if ( !attribution.isEmpty() )
1058 mShared->mLayerMetadata.setRights( { attribution } );
1060 mShared->mLayerMetadata.setDateTime(
Qgis::MetadataDateType::Created, QDateTime::fromString( QString::fromStdString( assetInfoJson[
"dateAdded"].get<std::string>() ), Qt::DateFormat::ISODate ) );
1065 const QString tileAccessEndpoint = CESIUM_ION_URL + u
"v1/assets/%1/endpoint"_s.arg( assetId );
1066 QNetworkRequest request = QNetworkRequest( tileAccessEndpoint );
1068 if ( !accessToken.isEmpty() )
1069 request.setRawHeader(
"Authorization", u
"Bearer %1"_s.arg( accessToken ).toLocal8Bit() );
1072 if ( accessToken.isEmpty() )
1075 switch ( networkRequest.
get( request ) )
1088 const json tileAccessJson = json::parse( content.
content().toStdString() );
1090 if ( tileAccessJson.contains(
"url" ) )
1092 tileSetUri = QString::fromStdString( tileAccessJson[
"url"].get<std::string>() );
1094 else if ( tileAccessJson.contains(
"options" ) )
1096 const auto &optionsJson = tileAccessJson[
"options"];
1097 if ( optionsJson.contains(
"url" ) )
1099 tileSetUri = QString::fromStdString( optionsJson[
"url"].get<std::string>() );
1103 if ( tileAccessJson.contains(
"accessToken" ) )
1107 mHeaders.insert( u
"Authorization"_s, u
"Bearer %1"_s.arg( QString::fromStdString( tileAccessJson[
"accessToken"].get<std::string>() ) ) );
1116 tileSetUri = dsUri.
param( u
"url"_s );
1119 if ( !tileSetUri.isEmpty() )
1121 const QUrl url( tileSetUri );
1123 QNetworkRequest request = QNetworkRequest( url );
1129 switch ( networkRequest.
get( request ) )
1143 mShared->initialize( content.
content(), tileSetUri, transformContext(), mAuthCfg, mHeaders );
1150 const QFileInfo fi( dataSourceUri() );
1153 QFile file( dataSourceUri() );
1154 if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
1156 const QByteArray raw = file.readAll();
1157 mShared->initialize( raw, QUrl::fromLocalFile( dataSourceUri() ), transformContext(), mAuthCfg, mHeaders );
1170 if ( !mShared->mIndex.isValid() )
1172 appendError( mShared->mError );
1183 return mShared->mLayerCrs;
1191 return mShared->mExtent;
1194bool QgsCesiumTilesDataProvider::isValid()
const
1201QString QgsCesiumTilesDataProvider::name()
const
1205 return PROVIDER_KEY;
1208QString QgsCesiumTilesDataProvider::description()
const
1212 return QObject::tr(
"Cesium 3D Tiles" );
1215QString QgsCesiumTilesDataProvider::htmlMetadata()
const
1222 if ( mShared->mTileset.contains(
"asset" ) )
1224 const auto &asset = mShared->mTileset[
"asset"];
1225 if ( asset.contains(
"version" ) )
1227 const QString version = QString::fromStdString( asset[
"version"].get<std::string>() );
1228 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"3D Tiles Version" ) % u
"</td><td>%1</a>"_s.arg( version ) % u
"</td></tr>\n"_s;
1231 if ( asset.contains(
"tilesetVersion" ) )
1235 const QString tilesetVersion = QString::fromStdString( asset[
"tilesetVersion"].get<std::string>() );
1236 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Tileset Version" ) % u
"</td><td>%1</a>"_s.arg( tilesetVersion ) % u
"</td></tr>\n"_s;
1238 catch ( json::type_error & )
1240 QgsDebugError( u
"Error when parsing tilesetVersion value"_s );
1244 if ( asset.contains(
"generator" ) )
1246 const QString generator = QString::fromStdString( asset[
"generator"].get<std::string>() );
1247 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Tileset Generator" ) % u
"</td><td>%1</a>"_s.arg( generator ) % u
"</td></tr>\n"_s;
1250 if ( mShared->mTileset.contains(
"extensionsRequired" ) )
1252 QStringList extensions;
1253 for (
const auto &item : mShared->mTileset[
"extensionsRequired"] )
1255 extensions << QString::fromStdString( item.get<std::string>() );
1257 if ( !extensions.isEmpty() )
1259 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Extensions Required" ) % u
"</td><td><ul><li>%1</li></ul></a>"_s.arg( extensions.join(
"</li><li>"_L1 ) ) % u
"</td></tr>\n"_s;
1262 if ( mShared->mTileset.contains(
"extensionsUsed" ) )
1264 QStringList extensions;
1265 for (
const auto &item : mShared->mTileset[
"extensionsUsed"] )
1267 extensions << QString::fromStdString( item.get<std::string>() );
1269 if ( !extensions.isEmpty() )
1271 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Extensions Used" ) % u
"</td><td><ul><li>%1</li></ul></a>"_s.arg( extensions.join(
"</li><li>"_L1 ) ) % u
"</td></tr>\n"_s;
1275 if ( !mShared->mZRange.isInfinite() )
1277 metadata += u
"<tr><td class=\"highlight\">"_s
1279 % u
"</td><td>%1 - %2</a>"_s.arg( QLocale().toString( mShared->mZRange.lower() ), QLocale().toString( mShared->mZRange.upper() ) )
1280 % u
"</td></tr>\n"_s;
1293 return mShared->mLayerMetadata;
1303 return mShared->mSceneCrs;
1314 return mShared ? mShared->mBoundingVolume : nullVolume;
1324 return mShared->mIndex;
1334 return mShared->mZRange;
1342QgsCesiumTilesProviderMetadata::QgsCesiumTilesProviderMetadata()
1346QIcon QgsCesiumTilesProviderMetadata::icon()
const
1353 return new QgsCesiumTilesDataProvider( uri, options, flags );
1358 const QVariantMap parts = decodeUri( uri );
1359 if ( parts.value( u
"file-name"_s ).toString().compare(
"tileset.json"_L1, Qt::CaseInsensitive ) == 0 )
1374int QgsCesiumTilesProviderMetadata::priorityForUri(
const QString &uri )
const
1376 const QVariantMap parts = decodeUri( uri );
1377 if ( parts.value( u
"file-name"_s ).toString().compare(
"tileset.json"_L1, Qt::CaseInsensitive ) == 0 )
1383QList<Qgis::LayerType> QgsCesiumTilesProviderMetadata::validLayerTypesForUri(
const QString &uri )
const
1385 const QVariantMap parts = decodeUri( uri );
1386 if ( parts.value( u
"file-name"_s ).toString().compare(
"tileset.json"_L1, Qt::CaseInsensitive ) == 0 )
1389 return QList< Qgis::LayerType>();
1392QVariantMap QgsCesiumTilesProviderMetadata::decodeUri(
const QString &uri )
const
1394 QVariantMap uriComponents;
1395 QUrl url = QUrl::fromUserInput( uri );
1396 uriComponents.insert( u
"file-name"_s, url.fileName() );
1397 uriComponents.insert( u
"path"_s, uri );
1398 return uriComponents;
1414 return QObject::tr(
"Cesium 3D Tiles" ) + u
" (tileset.json TILESET.JSON)"_s;
1421 return FileBasedUris;
1424QList<Qgis::LayerType> QgsCesiumTilesProviderMetadata::supportedLayerTypes()
const
1429QString QgsCesiumTilesProviderMetadata::encodeUri(
const QVariantMap &parts )
const
1431 const QString path = parts.value( u
"path"_s ).toString();
1437 return ProviderMetadataCapability::LayerTypesForUri | ProviderMetadataCapability::PriorityForUri | ProviderMetadataCapability::QuerySublayers;
QFlags< TiledSceneProviderCapability > TiledSceneProviderCapabilities
Tiled scene data provider capabilities.
QFlags< DataProviderFlag > DataProviderFlags
Data provider flags.
FileFilterType
Type of file filters.
@ TiledScene
Tiled scene layers.
@ VectorTile
Vector tile layers.
@ MeshDataset
Mesh datasets.
@ PointCloud
Point clouds.
@ Is3DBasemapSource
Associated source should be considered a '3D basemap' layer. See Qgis::MapLayerProperty::Is3DBasemapL...
@ IsBasemapSource
Associated source should be considered a 'basemap' layer. See Qgis::MapLayerProperty::IsBasemapLayer.
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
@ ReadLayerMetadata
Provider can read layer metadata from data store. See QgsDataProvider::layerMetadata().
QFlags< SublayerQueryFlag > SublayerQueryFlags
Sublayer query flags.
TileChildrenAvailability
Possible availability states for a tile's children.
@ 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,...
@ 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.
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
void setAuthCfg(const QString &authCfg)
Sets the authentication config id which should be used during the request.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ 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.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
A 3-dimensional box composed of x, y, z coordinates.
double zMaximum() const
Returns the maximum z value.
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
double zMinimum() const
Returns the minimum z value.
bool isNull() const
Test if the box is null (holding no spatial information).
static QMap< TileCoordinate, QgsTiledSceneNode * > createImplicitTilingChildren(QgsTiledSceneNode *node, const TileCoordinate &coord, Root &tilingData, const TileCoordinate &subtreeCoord, QgsCoordinateTransformContext &transformContext, long long &nextTileId)
Creates immediate children of a node according to implicit tiling.
static TileCoordinate tileCoordinateToParentSubtree(TileCoordinate coord, int subtreeLevels)
Returns parent subtree's tile coordinates of the given tile.
static QString expandTemplateUri(const QString &templateUri, const QUrl &baseUrl, const TileCoordinate &coord)
Expands template URI (using {level}, {x}, {y} markers) to the final URI.
static Qgis::TileChildrenAvailability childAvailability(const Root &tilingData, const TileCoordinate &coord)
Returns tile availability of a child based on cached subtree data.
static bool parseImplicitTiling(const json &tileJson, QgsTiledSceneNode *newNode, const QUrl &baseUrl, Qgis::Axis gltfUpAxis, Root &tilingData)
Parses JSON definition of implicit tiling into tilingData argument and returns true on success.
static Subtree parseSubtree(const Root &tilingData, const QByteArray &data)
Parses subtree definition and returns it.
static int subtreeBitIndex(int localLevel, int localX, int localY)
Returns the bit index within a subtree's availability bitstream for a given local tile position.
static QgsSphere parseSphere(const json &sphere)
Parses a sphere object from a Cesium JSON document.
static QString appendQueryFromBaseUrl(const QString &contentUri, const QUrl &baseUrl)
Copies any query items from the base URL to the content URI - to replicate undocumented Cesium JS beh...
static QgsOrientedBox3D parseBox(const json &box)
Parses a box object from a Cesium JSON document to an oriented bounding box.
static QgsTiledSceneBoundingVolume boundingVolumeFromRegion(const QgsBox3D ®ion, const QgsCoordinateTransformContext &transformContext)
Calculates oriented bounding box in EPSG:4978 from "region" defined with min/max lat/lon coordinates ...
static QgsBox3D parseRegion(const json ®ion)
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.
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.
Represents a single error message.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
bool isCanceled() const
Tells whether the operation has been canceled already.
void canceled()
Internal routines can connect to this signal if they use event loop.
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.
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.
bool isNull() const
Returns true if the sphere is a null (default constructed) sphere.
QgsBox3D boundingBox() const
Returns the 3-dimensional bounding box containing the sphere.
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...
#define QgsDebugError(str)
#define QgsSetRequestInitiatorClass(request, _class)
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS
Definition of root implicit tiling node (typically root node of the whole tileset,...
QMap< TileCoordinate, Subtree > subtreeCache
QString contentUriTemplate
int subtreeLevels
how many levels are stored in a single subtree
QString subtreeUriTemplate
Data about subtree of a node - there should be subtree at least for root implicit tiling node,...
QBitArray contentAvailability
Bit array whether a tile has some content.
Contains ZXY coordinates of a node within implicit tiling.
Setting options for creating vector data providers.