18#include <nlohmann/json.hpp>
43#include "moc_qgsesrii3sdataprovider.cpp"
45using namespace Qt::StringLiterals;
47#define I3S_PROVIDER_KEY u"esrii3s"_s
48#define I3S_PROVIDER_DESCRIPTION u"ESRI I3S data provider"_s
66 QVector< long long >
childTileIds(
long long id )
const final;
75 bool fetchNodePage(
int nodePage,
QgsFeedback *feedback =
nullptr );
76 void parseNodePage(
const QByteArray &nodePageContent );
78 QVariantMap parseMaterialDefinition(
const json &materialDefinitionJson );
82 long long parentNodeIndex;
83 QVector<long long> childNodeIndexes;
87 QVector<QString> mTextureSetFormats;
88 QVector<QVariantMap> mMaterialDefinitions;
90 mutable QRecursiveMutex mLock;
93 long long mRootNodeIndex;
95 bool mGlobalMode =
false;
96 QMap< long long, NodeDetails > mNodeMap;
97 QSet<int> mCachedNodePages;
108class QgsEsriI3SDataProviderSharedData
111 QgsEsriI3SDataProviderSharedData();
112 void initialize(
const QString &i3sVersion,
const json &layerJson,
const QUrl &rootUrl,
const QgsCoordinateTransformContext &transformContext );
117 QgsCoordinateReferenceSystem mLayerCrs;
118 QgsCoordinateReferenceSystem mSceneCrs;
119 QgsTiledSceneBoundingVolume mBoundingVolume;
121 QgsRectangle mExtent;
122 QgsDoubleRange mZRange;
124 QgsTiledSceneIndex mIndex;
127 QReadWriteLock mReadWriteLock;
134QgsEsriI3STiledSceneIndex::QgsEsriI3STiledSceneIndex(
const json &layerJson,
const QUrl &rootUrl,
const QgsCoordinateTransformContext &transformContext )
135 : mRootUrl( rootUrl )
136 , mTransformContext( transformContext )
141 if ( layerJson.contains(
"spatialReference" ) && layerJson[
"spatialReference"].is_object() )
143 const json spatialReferenceJson = layerJson[
"spatialReference"];
144 if ( spatialReferenceJson.contains(
"latestWkid" ) && spatialReferenceJson[
"latestWkid"].is_number() )
146 int epsgCode = spatialReferenceJson[
"latestWkid"].get<int>();
147 mGlobalMode = epsgCode == 4326;
149 else if ( spatialReferenceJson.contains(
"wkid" ) && spatialReferenceJson[
"wkid"].is_number() )
151 int epsgCode = spatialReferenceJson[
"wkid"].get<int>();
152 mGlobalMode = epsgCode == 4326;
156 if ( layerJson.contains(
"textureSetDefinitions" ) )
158 for ( auto textureSetDefinitionJson : layerJson[
"textureSetDefinitions"] )
161 for ( const json &formatJson : textureSetDefinitionJson[
"formats"] )
163 if ( formatJson[
"name"].get<std::string>() ==
"0" )
165 formatType = QString::fromStdString( formatJson[
"format"].get<std::string>() );
169 mTextureSetFormats.append( formatType );
173 if ( layerJson.contains(
"materialDefinitions" ) )
175 for ( const json &materialDefinitionJson : layerJson[
"materialDefinitions"] )
177 QVariantMap materialDef = parseMaterialDefinition( materialDefinitionJson );
178 mMaterialDefinitions.append( materialDef );
182 json nodePagesJson = layerJson[
"nodePages"];
183 mNodesPerPage = nodePagesJson[
"nodesPerPage"].get<
int>();
184 mRootNodeIndex = nodePagesJson.contains(
"rootIndex" ) ? nodePagesJson[
"rootIndex"].get<long long>() : 0;
186 int rootNodePage =
static_cast<int>( mRootNodeIndex / mNodesPerPage );
187 fetchNodePage( rootNodePage );
190QVariantMap QgsEsriI3STiledSceneIndex::parseMaterialDefinition(
const json &materialDefinitionJson )
192 QVariantMap materialDef;
194 if ( materialDefinitionJson.contains(
"pbrMetallicRoughness" ) )
196 const json pbrJson = materialDefinitionJson[
"pbrMetallicRoughness"];
197 if ( pbrJson.contains(
"baseColorFactor" ) )
199 const json pbrBaseColorFactorJson = pbrJson[
"baseColorFactor"];
200 materialDef[u
"pbrBaseColorFactor"_s]
201 = QVariantList { pbrBaseColorFactorJson[0].get<
double>(), pbrBaseColorFactorJson[1].get<double>(), pbrBaseColorFactorJson[2].get<
double>(), pbrBaseColorFactorJson[3].get<double>() };
205 materialDef[u
"pbrBaseColorFactor"_s] = QVariantList { 1.0, 1.0, 1.0, 1.0 };
207 if ( pbrJson.contains(
"baseColorTexture" ) )
212 const int textureSetDefinitionId = pbrJson[
"baseColorTexture"][
"textureSetDefinitionId"].get<
int>();
213 if ( textureSetDefinitionId < mTextureSetFormats.count() )
215 materialDef[u
"pbrBaseColorTextureName"_s] = u
"0"_s;
216 materialDef[u
"pbrBaseColorTextureFormat"_s] = mTextureSetFormats[textureSetDefinitionId];
220 QgsDebugError( QString(
"referencing textureSetDefinition that does not exist! %1 " ).arg( textureSetDefinitionId ) );
226 materialDef[u
"pbrBaseColorFactor"_s] = QVariantList { 1.0, 1.0, 1.0, 1.0 };
229 if ( materialDefinitionJson.contains(
"doubleSided" ) )
231 materialDef[u
"doubleSided"_s] = materialDefinitionJson[
"doubleSided"].get<
bool>();
243 QMutexLocker locker( &mLock );
244 if ( !mNodeMap.contains( mRootNodeIndex ) )
249 return mNodeMap[mRootNodeIndex].tile;
254 QMutexLocker locker( &mLock );
255 auto it = mNodeMap.constFind(
id );
256 if ( it != mNodeMap.constEnd() )
258 return it.value().tile;
264long long QgsEsriI3STiledSceneIndex::parentTileId(
long long id )
const
266 QMutexLocker locker( &mLock );
267 auto it = mNodeMap.constFind(
id );
268 if ( it != mNodeMap.constEnd() )
270 return it.value().parentNodeIndex;
276QVector< long long > QgsEsriI3STiledSceneIndex::childTileIds(
long long id )
const
278 QMutexLocker locker( &mLock );
279 auto it = mNodeMap.constFind(
id );
280 if ( it != mNodeMap.constEnd() )
282 return it.value().childNodeIndexes;
290 QVector< long long > results;
292 std::function< void(
long long )> traverseNode;
293 traverseNode = [&request, &traverseNode, &results,
this](
long long nodeId ) {
304 fetchHierarchy( t.
id() );
308 auto it = mNodeMap.constFind( t.
id() );
309 for (
long long childId : it.value().childNodeIndexes )
311 traverseNode( childId );
314 if ( it.value().childNodeIndexes.isEmpty() )
328 QMutexLocker locker( &mLock );
330 traverseNode( startNodeId );
337 QMutexLocker locker( &mLock );
338 auto it = mNodeMap.constFind(
id );
339 if ( it == mNodeMap.constEnd() )
345 if ( it.value().childNodeIndexes.isEmpty() )
350 for (
long long childId : it.value().childNodeIndexes )
352 if ( !mNodeMap.contains( childId ) )
361bool QgsEsriI3STiledSceneIndex::fetchHierarchy(
long long id,
QgsFeedback *feedback )
363 QMutexLocker locker( &mLock );
364 auto it = mNodeMap.constFind(
id );
365 if ( it == mNodeMap.constEnd() )
369 QSet<int> nodePagesToFetch;
370 for (
long long childId : it.value().childNodeIndexes )
372 int nodePageIndex =
static_cast<int>( childId / mNodesPerPage );
373 if ( !mCachedNodePages.contains( nodePageIndex ) )
374 nodePagesToFetch.insert( nodePageIndex );
378 for (
int nodePage : nodePagesToFetch )
380 if ( !fetchNodePage( nodePage, feedback ) )
389QByteArray QgsEsriI3STiledSceneIndex::fetchContent(
const QString &uri,
QgsFeedback *feedback )
392 if ( url.isLocalFile() )
394 const QString slpkPath = mRootUrl.toLocalFile();
395 if ( QFileInfo( slpkPath ).suffix().compare(
"slpk"_L1, Qt::CaseInsensitive ) == 0 )
397 const QString fileInSlpk = uri.mid( mRootUrl.toString().length() + 1 );
407 QFile file( url.toLocalFile() );
408 if ( file.open( QIODevice::ReadOnly ) )
410 return file.readAll();
416 QNetworkRequest networkRequest = QNetworkRequest( url );
418 networkRequest.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
419 networkRequest.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
428bool QgsEsriI3STiledSceneIndex::fetchNodePage(
int nodePage,
QgsFeedback *feedback )
430 QByteArray nodePageContent;
431 if ( !mRootUrl.isLocalFile() )
433 const QString uri = u
"%1/layers/0/nodepages/%2"_s.arg( mRootUrl.toString() ).arg( nodePage );
434 nodePageContent = retrieveContent( uri, feedback );
438 const QString uri = u
"%1/nodepages/%2.json.gz"_s.arg( mRootUrl.toString() ).arg( nodePage );
439 const QByteArray nodePageContentGzipped = retrieveContent( uri, feedback );
443 QgsDebugError( u
"Failed to decompress node page content: "_s + uri );
447 if ( nodePageContent.isEmpty() )
449 QgsDebugError( u
"Failed to read node page content: "_s + uri );
456 parseNodePage( nodePageContent );
458 catch ( json::exception &error )
460 QgsDebugError( u
"Error reading node page %1: %2"_s.arg( nodePage ).arg( error.what() ) );
464 mCachedNodePages.insert( nodePage );
472 json center = box[
"center"];
473 json halfSize = box[
"halfSize"];
474 json quaternion = box[
"quaternion"];
477 QgsVector3D( center[0].get<double>(), center[1].get<double>(), center[2].get<double>() ),
478 QgsVector3D( halfSize[0].get<double>(), halfSize[1].get<double>(), halfSize[2].get<double>() ),
480 static_cast<float>( quaternion[3].get<double>() ),
481 static_cast<float>( quaternion[0].get<double>() ),
482 static_cast<float>( quaternion[1].get<double>() ),
483 static_cast<float>( quaternion[2].get<double>() )
487 catch ( nlohmann::json::exception & )
493void QgsEsriI3STiledSceneIndex::parseMesh(
QgsTiledSceneTile &t,
const json &meshJson )
495 if ( !meshJson.contains(
"geometry" ) || !meshJson.contains(
"material" ) )
498 int geometryResource = meshJson[
"geometry"][
"resource"].get<
int>();
500 if ( mRootUrl.isLocalFile() )
501 geometryUri = u
"%1/nodes/%2/geometries/1.bin.gz"_s.arg( mRootUrl.toString() ).arg( geometryResource );
503 geometryUri = u
"%1/layers/0/nodes/%2/geometries/1"_s.arg( mRootUrl.toString() ).arg( geometryResource );
506 const json materialJson = meshJson[
"material"];
507 int materialIndex = materialJson[
"definition"].get<
int>();
508 QVariantMap materialInfo;
509 if ( materialIndex >= 0 && materialIndex < mMaterialDefinitions.count() )
511 materialInfo = mMaterialDefinitions[materialIndex];
512 if ( materialInfo.contains( u
"pbrBaseColorTextureName"_s ) )
514 const QString textureName = materialInfo[u
"pbrBaseColorTextureName"_s].toString();
515 const QString textureFormat = materialInfo[u
"pbrBaseColorTextureFormat"_s].toString();
516 materialInfo.remove( u
"pbrBaseColorTextureName"_s );
517 materialInfo.remove( u
"pbrBaseColorTextureFormat"_s );
519 const int textureResource = materialJson[
"resource"].get<
int>();
521 if ( mRootUrl.isLocalFile() )
522 textureUri = u
"%1/nodes/%2/textures/%3.%4"_s.arg( mRootUrl.toString() ).arg( textureResource ).arg( textureName, textureFormat );
524 textureUri = u
"%1/layers/0/nodes/%2/textures/%3"_s.arg( mRootUrl.toString() ).arg( textureResource ).arg( textureName );
525 materialInfo[u
"pbrBaseColorTexture"_s] = textureUri;
531 QVariantMap metadata = { { u
"gltfUpAxis"_s,
static_cast< int >(
Qgis::Axis::Z ) }, { u
"contentFormat"_s, u
"draco"_s }, { u
"material"_s, materialInfo } };
535void QgsEsriI3STiledSceneIndex::parseNodePage(
const QByteArray &nodePageContent )
537 const json nodePageJson = json::parse( nodePageContent.toStdString() );
538 for (
const json &nodeJson : nodePageJson[
"nodes"] )
540 long long nodeIndex = nodeJson[
"index"].get<
long long>();
541 long long parentNodeIndex = nodeJson.contains(
"parentIndex" ) ? nodeJson[
"parentIndex"].get<
long long>() : -1;
561 double threshold = -1;
562 if ( nodeJson.contains(
"lodThreshold" ) )
564 double maxScreenThresholdSquared = nodeJson[
"lodThreshold"].get<
double>();
568 threshold = obb.
longestSide() / sqrt( maxScreenThresholdSquared / ( M_PI / 4 ) ) * 16;
570 QVector<long long> childNodeIds;
571 if ( nodeJson.contains(
"children" ) )
573 for (
const json &childJson : nodeJson[
"children"] )
575 childNodeIds << childJson.get<
long long>();
587 if ( nodeJson.contains(
"mesh" ) )
590 parseMesh( t, nodeJson[
"mesh"] );
593 mNodeMap.insert( nodeIndex, { parentNodeIndex, childNodeIds, t } );
602QgsEsriI3SDataProviderSharedData::QgsEsriI3SDataProviderSharedData()
606void QgsEsriI3SDataProviderSharedData::initialize(
const QString &i3sVersion,
const json &layerJson,
const QUrl &rootUrl,
const QgsCoordinateTransformContext &transformContext )
608 mI3sVersion = i3sVersion;
609 mLayerJson = layerJson;
616 if ( layerJson.contains(
"spatialReference" ) && layerJson[
"spatialReference"].is_object() )
618 const json spatialReferenceJson = layerJson[
"spatialReference"];
619 if ( spatialReferenceJson.contains(
"latestWkid" ) && spatialReferenceJson[
"latestWkid"].is_number() )
621 epsgCode = spatialReferenceJson[
"latestWkid"].get<
int>();
623 else if ( spatialReferenceJson.contains(
"wkid" ) && spatialReferenceJson[
"wkid"].is_number() )
625 epsgCode = spatialReferenceJson[
"wkid"].get<
int>();
629 if ( epsgCode == 4326 )
641 mSceneCrs = mLayerCrs;
644 mIndex =
QgsTiledSceneIndex(
new QgsEsriI3STiledSceneIndex( layerJson, rootUrl, transformContext ) );
646 if ( layerJson.contains(
"fullExtent" ) )
648 const json fullExtentJson = layerJson[
"fullExtent"];
649 mExtent =
QgsRectangle( fullExtentJson[
"xmin"].get<double>(), fullExtentJson[
"ymin"].get<double>(), fullExtentJson[
"xmax"].get<double>(), fullExtentJson[
"ymax"].get<double>() );
650 mZRange =
QgsDoubleRange( fullExtentJson[
"zmin"].get<double>(), fullExtentJson[
"zmax"].get<double>() );
659 mBoundingVolume = mIndex.rootTile().boundingVolume();
669 , mShared( std::make_shared< QgsEsriI3SDataProviderSharedData >() )
672 const QString url = dataSource.param( u
"url"_s );
673 QString sourcePath = QUrl::fromPercentEncoding( url.toUtf8() );
675 if ( sourcePath.isEmpty() )
681 if ( sourcePath.startsWith(
"http"_L1 ) || sourcePath.startsWith(
"file"_L1 ) )
683 rootUrl = sourcePath;
688 rootUrl = QUrl::fromLocalFile( sourcePath );
693 if ( sourcePath.startsWith(
"http"_L1 ) )
695 if ( !loadFromRestService( rootUrl.toString(), layerJson, i3sVersion ) )
700 if ( !loadFromSlpk( rootUrl.toLocalFile(), layerJson, i3sVersion ) )
704 if ( !layerJson.contains(
"layerType" ) )
706 appendError(
QgsErrorMessage( tr(
"Invalid I3S source: missing layer type." ), u
"I3S"_s ) );
710 if ( !layerJson.contains(
"nodePages" ) )
712 appendError(
QgsErrorMessage( tr(
"Missing 'nodePages' attribute (should be available in I3S >= 1.7)" ), u
"I3S"_s ) );
716 QString layerType = QString::fromStdString( layerJson[
"layerType"].get<std::string>() );
717 if ( layerType !=
"3DObject"_L1 && layerType !=
"IntegratedMesh"_L1 )
719 appendError(
QgsErrorMessage( tr(
"Unsupported layer type: " ) + layerType, u
"I3S"_s ) );
723 mShared->initialize( i3sVersion, layerJson, rootUrl, transformContext() );
725 if ( !mShared->mIndex.isValid() )
727 appendError( mShared->mError );
735bool QgsEsriI3SDataProvider::loadFromRestService(
const QString &uri, json &layerJson, QString &i3sVersion )
737 QUrl queryUrl( uri );
738 QUrlQuery query( queryUrl );
739 query.addQueryItem( u
"f"_s, u
"json"_s );
740 queryUrl.setQuery( query );
742 QNetworkRequest networkRequest = QNetworkRequest( queryUrl );
744 networkRequest.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
745 networkRequest.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
748 if ( reply.
error() != QNetworkReply::NoError )
750 appendError(
QgsErrorMessage( tr(
"Failed to fetch layer metadata: " ) + networkRequest.url().toString(), u
"I3S"_s ) );
753 QByteArray sceneLayerContent = reply.
content();
758 serviceJson = json::parse( sceneLayerContent.toStdString() );
760 catch (
const json::parse_error & )
762 appendError(
QgsErrorMessage( tr(
"Unable to parse JSON: " ) + uri, u
"I3S"_s ) );
766 if ( !serviceJson.contains(
"serviceVersion" ) )
768 appendError(
QgsErrorMessage( tr(
"Missing I3S version: " ) + uri, u
"I3S"_s ) );
771 i3sVersion = QString::fromStdString( serviceJson[
"serviceVersion"].get<std::string>() );
772 if ( !checkI3SVersion( i3sVersion ) )
775 if ( !serviceJson.contains(
"layers" ) || !serviceJson[
"layers"].is_array() || serviceJson[
"layers"].size() < 1 )
777 appendError(
QgsErrorMessage( tr(
"Unable to get layer info: " ) + uri, u
"I3S"_s ) );
781 layerJson = serviceJson[
"layers"][0];
785bool QgsEsriI3SDataProvider::loadFromSlpk(
const QString &uri, json &layerJson, QString &i3sVersion )
788 if ( QFileInfo( uri ).suffix().compare(
"slpk"_L1, Qt::CaseInsensitive ) == 0 )
797 QByteArray metadataContent;
798 QString metadataFileName = u
"metadata.json"_s;
801 const QString metadataDirPath = QDir( uri ).filePath( metadataFileName );
802 QFile fMetadata( metadataDirPath );
803 if ( !fMetadata.open( QIODevice::ReadOnly ) )
805 appendError(
QgsErrorMessage( tr(
"Failed to read layer metadata: %1" ).arg( metadataDirPath ), u
"I3S"_s ) );
808 metadataContent = fMetadata.readAll();
814 appendError(
QgsErrorMessage( tr(
"Failed to read %1 in file: %2" ).arg( metadataFileName ).arg( uri ), u
"I3S"_s ) );
822 metadataJson = json::parse( metadataContent.toStdString() );
824 catch (
const json::parse_error & )
826 appendError(
QgsErrorMessage( tr(
"Unable to parse %1 in: %2" ).arg( metadataFileName ).arg( uri ), u
"I3S"_s ) );
830 if ( !metadataJson.contains(
"I3SVersion" ) )
832 appendError(
QgsErrorMessage( tr(
"Missing I3S version in %1 in: %2" ).arg( metadataFileName ).arg( uri ), u
"I3S"_s ) );
835 i3sVersion = QString::fromStdString( metadataJson[
"I3SVersion"].get<std::string>() );
836 if ( !checkI3SVersion( i3sVersion ) )
839 QByteArray sceneLayerContentGzipped;
840 const QString sceneLayerContentFileName = u
"3dSceneLayer.json.gz"_s;
843 const QString sceneLayerContentDirPath = QDir( uri ).filePath( sceneLayerContentFileName );
844 QFile fSceneLayerContent( sceneLayerContentDirPath );
845 if ( !fSceneLayerContent.open( QIODevice::ReadOnly ) )
847 appendError(
QgsErrorMessage( tr(
"Failed to read layer metadata: %1" ).arg( sceneLayerContentDirPath ), u
"I3S"_s ) );
850 sceneLayerContentGzipped = fSceneLayerContent.readAll();
856 appendError(
QgsErrorMessage( tr(
"Failed to read %1 in file: %2" ).arg( sceneLayerContentFileName ).arg( uri ), u
"I3S"_s ) );
861 QByteArray sceneLayerContent;
864 appendError(
QgsErrorMessage( tr(
"Failed to decompress %1 in: %2" ).arg( sceneLayerContentFileName ).arg( uri ), u
"I3S"_s ) );
870 layerJson = json::parse( sceneLayerContent.toStdString() );
872 catch (
const json::parse_error & )
874 appendError(
QgsErrorMessage( tr(
"Unable to parse %1 in: %2" ).arg( sceneLayerContentFileName ).arg( uri ), u
"I3S"_s ) );
881bool QgsEsriI3SDataProvider::checkI3SVersion(
const QString &i3sVersion )
892 QStringList i3sVersionComponents = i3sVersion.split(
'.' );
893 if ( i3sVersionComponents.size() != 2 )
895 appendError(
QgsErrorMessage( tr(
"Unexpected I3S version format: " ) + i3sVersion, u
"I3S"_s ) );
898 int i3sVersionMajor = i3sVersionComponents[0].toInt();
899 int i3sVersionMinor = i3sVersionComponents[1].toInt();
900 if ( i3sVersionMajor != 1 || ( i3sVersionMajor == 1 && i3sVersionMinor < 7 ) )
902 appendError(
QgsErrorMessage( tr(
"Unsupported I3S version: " ) + i3sVersion, u
"I3S"_s ) );
909QgsEsriI3SDataProvider::QgsEsriI3SDataProvider(
const QgsEsriI3SDataProvider &other )
911 , mIsValid( other.mIsValid )
914 mShared = other.mShared;
917QgsEsriI3SDataProvider::~QgsEsriI3SDataProvider() =
default;
929QgsEsriI3SDataProvider *QgsEsriI3SDataProvider::clone()
const
932 return new QgsEsriI3SDataProvider( *
this );
940 return mShared->mLayerCrs;
948 return mShared->mExtent;
951bool QgsEsriI3SDataProvider::isValid()
const
958QString QgsEsriI3SDataProvider::name()
const
965QString QgsEsriI3SDataProvider::description()
const
969 return QObject::tr(
"ESRI I3S" );
972QString QgsEsriI3SDataProvider::htmlMetadata()
const
980 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"I3S Version" ) % u
"</td><td>%1</a>"_s.arg( mShared->mI3sVersion ) % u
"</td></tr>\n"_s;
982 QString layerType = QString::fromStdString( mShared->mLayerJson[
"layerType"].get<std::string>() );
983 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Layer Type" ) % u
"</td><td>%1</a>"_s.arg( layerType ) % u
"</td></tr>\n"_s;
985 if ( mShared->mLayerJson.contains(
"version" ) )
989 QString version = QString::fromStdString( mShared->mLayerJson[
"version"].get<std::string>() );
990 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Version" ) % u
"</td><td>%1</a>"_s.arg( version ) % u
"</td></tr>\n"_s;
994 if ( mShared->mLayerJson.contains(
"name" ) )
996 QString name = QString::fromStdString( mShared->mLayerJson[
"name"].get<std::string>() );
997 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Name" ) % u
"</td><td>%1</a>"_s.arg( name ) % u
"</td></tr>\n"_s;
1000 if ( mShared->mLayerJson.contains(
"alias" ) )
1002 QString alias = QString::fromStdString( mShared->mLayerJson[
"alias"].get<std::string>() );
1003 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Alias" ) % u
"</td><td>%1</a>"_s.arg( alias ) % u
"</td></tr>\n"_s;
1006 if ( mShared->mLayerJson.contains(
"description" ) )
1008 QString description = QString::fromStdString( mShared->mLayerJson[
"description"].get<std::string>() );
1009 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Description" ) % u
"</td><td>%1</a>"_s.arg( description ) % u
"</td></tr>\n"_s;
1012 if ( !mShared->mZRange.isInfinite() )
1014 metadata += u
"<tr><td class=\"highlight\">"_s
1016 % u
"</td><td>%1 - %2</a>"_s.arg( QLocale().toString( mShared->mZRange.lower() ), QLocale().toString( mShared->mZRange.upper() ) )
1017 % u
"</td></tr>\n"_s;
1030 return mShared->mSceneCrs;
1041 return mShared ? mShared->mBoundingVolume : nullVolume;
1051 return mShared->mIndex;
1061 return mShared->mZRange;
1070QgsEsriI3SProviderMetadata::QgsEsriI3SProviderMetadata()
1074QIcon QgsEsriI3SProviderMetadata::icon()
const
1081 return new QgsEsriI3SDataProvider( uri, options, flags );
1087 const QFileInfo fi( uri );
1094 const QVariantMap parts = decodeUri( uri );
1095 fileName = parts.value( u
"path"_s ).toString();
1098 if ( fileName.isEmpty() )
1101 if ( QFileInfo( fileName ).suffix().compare(
"slpk"_L1, Qt::CaseInsensitive ) == 0 )
1104 parts.insert( u
"path"_s, fileName );
1107 details.
setUri( encodeUri( parts ) );
1116QVariantMap QgsEsriI3SProviderMetadata::decodeUri(
const QString &uri )
const
1120 QVariantMap uriComponents;
1121 QString path = dsUri.param( u
"url"_s );
1122 if ( path.isEmpty() && !uri.isEmpty() )
1126 uriComponents.insert( u
"path"_s, path );
1128 return uriComponents;
1131QList< Qgis::LayerType > QgsEsriI3SProviderMetadata::validLayerTypesForUri(
const QString &uri )
const
1133 const QVariantMap parts = decodeUri( uri );
1134 QString filePath = parts.value( u
"path"_s ).toString();
1136 if ( filePath.endsWith( u
".slpk"_s, Qt::CaseSensitivity::CaseInsensitive ) )
1142QString QgsEsriI3SProviderMetadata::encodeUri(
const QVariantMap &parts )
const
1145 const QString partsKey = parts.contains( u
"path"_s ) ? u
"path"_s : u
"url"_s;
1146 dsUri.
setParam( u
"url"_s, parts.value( partsKey ).toString() );
1163 return QObject::tr(
"ESRI Scene layer package" ) + u
" (*.slpk *.SLPK)"_s;
1170 return FileBasedUris;
1173QList<Qgis::LayerType> QgsEsriI3SProviderMetadata::supportedLayerTypes()
const
1180 return ProviderMetadataCapability::PriorityForUri;
1183int QgsEsriI3SProviderMetadata::priorityForUri(
const QString &uri )
const
1185 const QVariantMap parts = decodeUri( uri );
1186 QString filePath = parts.value( u
"path"_s ).toString();
1188 if ( filePath.endsWith( u
".slpk"_s, Qt::CaseSensitivity::CaseInsensitive ) )
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.
@ FastExtent2D
Provider's 2D extent retrieval via QgsDataProvider::extent() is always guaranteed to be trivial/fast ...
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
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,...
An abstract base class for tiled scene data provider indices.
virtual QVector< long long > childTileIds(long long id) const =0
Returns a list of the tile IDs of any children for the tile with matching id.
virtual long long parentTileId(long long id) const =0
Returns the tile ID of the parent tile of the tile with matching id, or -1 if the tile has no parent.
virtual QgsTiledSceneTile rootTile() const =0
Returns the root tile for the index.
virtual QByteArray fetchContent(const QString &uri, QgsFeedback *feedback=nullptr)=0
Fetches index content for the specified uri.
virtual bool fetchHierarchy(long long id, QgsFeedback *feedback=nullptr)=0
Populates the tile with the given id by fetching any sub datasets attached to the tile.
virtual QgsTiledSceneTile getTile(long long id)=0
Returns the tile with matching id, or an invalid tile if the matching tile is not available.
virtual Qgis::TileChildrenAvailability childAvailability(long long id) const =0
Returns the availability for a tile's children.
virtual QVector< long long > getTiles(const QgsTiledSceneRequest &request)=0
Returns the tile IDs which match the given request.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
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.
Represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
Stores the component parts of a data source URI (e.g.
QByteArray encodedUri() const
Returns the complete encoded URI as a byte array.
void setParam(const QString &key, const QString &value)
Sets a generic parameter value on 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.
A simple 4x4 matrix implementation useful for transformation in 3D space.
void translate(const QgsVector3D &vector)
Multiplies this matrix by another that translates coordinates by the components of a vector.
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.
QNetworkReply::NetworkError error() const
Returns the reply's error message, or QNetworkReply::NoError if no error was encountered.
Represents a oriented (rotated) box in 3 dimensions.
double longestSide() const
Returns size of the longest side of the box.
bool isNull() const
Returns true if the box is a null box.
QList< double > halfAxesList() const
Returns the half axes matrix;.
QgsVector3D center() const
Returns the vector to the center of the 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.
Represents a bounding volume for a tiled scene.
bool intersects(const QgsOrientedBox3D &box) const
Returns true if this bounds intersects the specified box.
Base class for data providers for QgsTiledSceneLayer.
An index for tiled scene data providers.
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.
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.
void setMetadata(const QVariantMap &metadata)
Sets additional metadata attached to the tile.
void setGeometricError(double error)
Sets the tile's geometric error, which is the error, in meters, of the tile's simplified representati...
const QgsTiledSceneBoundingVolume & boundingVolume() const
Returns the bounding volume for the tile.
void setBoundingVolume(const QgsTiledSceneBoundingVolume &volume)
Sets the bounding volume for the tile.
long long id() const
Returns the tile's unique ID.
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...
double y() const
Returns Y coordinate.
double z() const
Returns Z coordinate.
double x() const
Returns X coordinate.
static bool extractFileFromZip(const QString &zipFilename, const QString &filenameInZip, QByteArray &bytesOut)
Extracts a file from a zip archive, returns true on success.
static bool decodeGzip(const QByteArray &bytesIn, QByteArray &bytesOut)
Decodes gzip byte stream, returns true on success.
#define I3S_PROVIDER_DESCRIPTION
#define QgsDebugError(str)
#define QgsSetRequestInitiatorClass(request, _class)
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS
Setting options for creating vector data providers.