18#include <nlohmann/json.hpp>
41#include "moc_qgsesrii3sdataprovider.cpp"
43using namespace Qt::StringLiterals;
45#define I3S_PROVIDER_KEY u"esrii3s"_s
46#define I3S_PROVIDER_DESCRIPTION u"ESRI I3S data provider"_s
60 QgsEsriI3STiledSceneIndex(
68 QVector< long long >
childTileIds(
long long id )
const final;
79 bool fetchNodePage(
int nodePage,
QgsFeedback *feedback =
nullptr );
80 void parseNodePage(
const QByteArray &nodePageContent );
82 QVariantMap parseMaterialDefinition(
const json &materialDefinitionJson );
86 long long parentNodeIndex;
87 QVector<long long> childNodeIndexes;
91 QVector<QString> mTextureSetFormats;
92 QVector<QVariantMap> mMaterialDefinitions;
94 mutable QRecursiveMutex mLock;
97 long long mRootNodeIndex;
99 bool mGlobalMode =
false;
100 QMap< long long, NodeDetails > mNodeMap;
101 QSet<int> mCachedNodePages;
114class QgsEsriI3SDataProviderSharedData
117 QgsEsriI3SDataProviderSharedData();
118 void initialize(
const QString &i3sVersion,
119 const json &layerJson,
121 const QgsCoordinateTransformContext &transformContext );
126 QgsCoordinateReferenceSystem mLayerCrs;
127 QgsCoordinateReferenceSystem mSceneCrs;
128 QgsTiledSceneBoundingVolume mBoundingVolume;
130 QgsRectangle mExtent;
131 QgsDoubleRange mZRange;
133 QgsTiledSceneIndex mIndex;
136 QReadWriteLock mReadWriteLock;
144QgsEsriI3STiledSceneIndex::QgsEsriI3STiledSceneIndex(
145 const json &layerJson,
148 : mRootUrl( rootUrl )
149 , mTransformContext( transformContext )
154 if ( layerJson.contains(
"spatialReference" ) && layerJson[
"spatialReference"].is_object() )
156 const json spatialReferenceJson = layerJson[
"spatialReference"];
157 if ( spatialReferenceJson.contains(
"latestWkid" ) && spatialReferenceJson[
"latestWkid"].is_number() )
159 int epsgCode = spatialReferenceJson[
"latestWkid"].get<int>();
160 mGlobalMode = epsgCode == 4326;
162 else if ( spatialReferenceJson.contains(
"wkid" ) && spatialReferenceJson[
"wkid"].is_number() )
164 int epsgCode = spatialReferenceJson[
"wkid"].get<int>();
165 mGlobalMode = epsgCode == 4326;
169 if ( layerJson.contains(
"textureSetDefinitions" ) )
171 for ( auto textureSetDefinitionJson : layerJson[
"textureSetDefinitions"] )
174 for ( const json &formatJson : textureSetDefinitionJson[
"formats"] )
176 if ( formatJson[
"name"].get<std::string>() ==
"0" )
178 formatType = QString::fromStdString( formatJson[
"format"].get<std::string>() );
182 mTextureSetFormats.append( formatType );
186 if ( layerJson.contains(
"materialDefinitions" ) )
188 for ( const json &materialDefinitionJson : layerJson[
"materialDefinitions"] )
190 QVariantMap materialDef = parseMaterialDefinition( materialDefinitionJson );
191 mMaterialDefinitions.append( materialDef );
195 json nodePagesJson = layerJson[
"nodePages"];
196 mNodesPerPage = nodePagesJson[
"nodesPerPage"].get<
int>();
197 mRootNodeIndex = nodePagesJson.contains(
"rootIndex" ) ? nodePagesJson[
"rootIndex"].get<long long>() : 0;
199 int rootNodePage =
static_cast<int>( mRootNodeIndex / mNodesPerPage );
200 fetchNodePage( rootNodePage );
203QVariantMap QgsEsriI3STiledSceneIndex::parseMaterialDefinition(
const json &materialDefinitionJson )
205 QVariantMap materialDef;
207 if ( materialDefinitionJson.contains(
"pbrMetallicRoughness" ) )
209 const json pbrJson = materialDefinitionJson[
"pbrMetallicRoughness"];
210 if ( pbrJson.contains(
"baseColorFactor" ) )
212 const json pbrBaseColorFactorJson = pbrJson[
"baseColorFactor"];
213 materialDef[u
"pbrBaseColorFactor"_s] = QVariantList
215 pbrBaseColorFactorJson[0].get<
double>(),
216 pbrBaseColorFactorJson[1].get<double>(),
217 pbrBaseColorFactorJson[2].get<
double>(),
218 pbrBaseColorFactorJson[3].get<double>()
223 materialDef[u
"pbrBaseColorFactor"_s] = QVariantList{ 1.0, 1.0, 1.0, 1.0 };
225 if ( pbrJson.contains(
"baseColorTexture" ) )
230 const int textureSetDefinitionId = pbrJson[
"baseColorTexture"][
"textureSetDefinitionId"].get<
int>();
231 if ( textureSetDefinitionId < mTextureSetFormats.count() )
233 materialDef[u
"pbrBaseColorTextureName"_s] = u
"0"_s;
234 materialDef[u
"pbrBaseColorTextureFormat"_s] = mTextureSetFormats[textureSetDefinitionId];
238 QgsDebugError( QString(
"referencing textureSetDefinition that does not exist! %1 " ).arg( textureSetDefinitionId ) );
244 materialDef[u
"pbrBaseColorFactor"_s] = QVariantList{ 1.0, 1.0, 1.0, 1.0 };
247 if ( materialDefinitionJson.contains(
"doubleSided" ) )
249 materialDef[u
"doubleSided"_s] = materialDefinitionJson[
"doubleSided"].get<
bool>();
261 QMutexLocker locker( &mLock );
262 if ( !mNodeMap.contains( mRootNodeIndex ) )
267 return mNodeMap[mRootNodeIndex].tile;
272 QMutexLocker locker( &mLock );
273 auto it = mNodeMap.constFind(
id );
274 if ( it != mNodeMap.constEnd() )
276 return it.value().tile;
282long long QgsEsriI3STiledSceneIndex::parentTileId(
long long id )
const
284 QMutexLocker locker( &mLock );
285 auto it = mNodeMap.constFind(
id );
286 if ( it != mNodeMap.constEnd() )
288 return it.value().parentNodeIndex;
294QVector< long long > QgsEsriI3STiledSceneIndex::childTileIds(
long long id )
const
296 QMutexLocker locker( &mLock );
297 auto it = mNodeMap.constFind(
id );
298 if ( it != mNodeMap.constEnd() )
300 return it.value().childNodeIndexes;
308 QVector< long long > results;
310 std::function< void(
long long )> traverseNode;
311 traverseNode = [&request, &traverseNode, &results,
this](
long long nodeId )
324 fetchHierarchy( t.
id() );
328 auto it = mNodeMap.constFind( t.
id() );
329 for (
long long childId : it.value().childNodeIndexes )
331 traverseNode( childId );
334 if ( it.value().childNodeIndexes.isEmpty() )
348 QMutexLocker locker( &mLock );
350 traverseNode( startNodeId );
357 QMutexLocker locker( &mLock );
358 auto it = mNodeMap.constFind(
id );
359 if ( it == mNodeMap.constEnd() )
365 if ( it.value().childNodeIndexes.isEmpty() )
370 for (
long long childId : it.value().childNodeIndexes )
372 if ( !mNodeMap.contains( childId ) )
381bool QgsEsriI3STiledSceneIndex::fetchHierarchy(
long long id,
QgsFeedback *feedback )
383 QMutexLocker locker( &mLock );
384 auto it = mNodeMap.constFind(
id );
385 if ( it == mNodeMap.constEnd() )
389 QSet<int> nodePagesToFetch;
390 for (
long long childId : it.value().childNodeIndexes )
392 int nodePageIndex =
static_cast<int>( childId / mNodesPerPage );
393 if ( !mCachedNodePages.contains( nodePageIndex ) )
394 nodePagesToFetch.insert( nodePageIndex );
398 for (
int nodePage : nodePagesToFetch )
400 if ( !fetchNodePage( nodePage, feedback ) )
409QByteArray QgsEsriI3STiledSceneIndex::fetchContent(
const QString &uri,
QgsFeedback *feedback )
412 if ( url.isLocalFile() )
414 const QString slpkPath = mRootUrl.toLocalFile();
415 if ( QFileInfo( slpkPath ).suffix().compare(
"slpk"_L1, Qt::CaseInsensitive ) == 0 )
417 const QString fileInSlpk = uri.mid( mRootUrl.toString().length() + 1 );
427 QFile file( url.toLocalFile() );
428 if ( file.open( QIODevice::ReadOnly ) )
430 return file.readAll();
436 QNetworkRequest networkRequest = QNetworkRequest( url );
438 networkRequest.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
439 networkRequest.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
442 networkRequest, QString(),
false, feedback );
449bool QgsEsriI3STiledSceneIndex::fetchNodePage(
int nodePage,
QgsFeedback *feedback )
451 QByteArray nodePageContent;
452 if ( !mRootUrl.isLocalFile() )
454 const QString uri = u
"%1/layers/0/nodepages/%2"_s.arg( mRootUrl.toString() ).arg( nodePage );
455 nodePageContent = retrieveContent( uri, feedback );
459 const QString uri = u
"%1/nodepages/%2.json.gz"_s.arg( mRootUrl.toString() ).arg( nodePage );
460 const QByteArray nodePageContentGzipped = retrieveContent( uri, feedback );
464 QgsDebugError( u
"Failed to decompress node page content: "_s + uri );
468 if ( nodePageContent.isEmpty() )
470 QgsDebugError( u
"Failed to read node page content: "_s + uri );
477 parseNodePage( nodePageContent );
479 catch ( json::exception &error )
481 QgsDebugError( u
"Error reading node page %1: %2"_s.arg( nodePage ).arg( error.what() ) );
485 mCachedNodePages.insert( nodePage );
493 json center = box[
"center"];
494 json halfSize = box[
"halfSize"];
495 json quaternion = box[
"quaternion"];
499 center[1].get<double>(),
500 center[2].get<double>() ),
502 halfSize[1].get<double>(),
503 halfSize[2].get<double>() ),
504 QQuaternion(
static_cast<float>( quaternion[3].get<double>() ),
505 static_cast<float>( quaternion[0].get<double>() ),
506 static_cast<float>( quaternion[1].get<double>() ),
507 static_cast<float>( quaternion[2].get<double>() ) ) );
509 catch ( nlohmann::json::exception & )
515void QgsEsriI3STiledSceneIndex::parseMesh(
QgsTiledSceneTile &t,
const json &meshJson )
517 if ( !meshJson.contains(
"geometry" ) || !meshJson.contains(
"material" ) )
520 int geometryResource = meshJson[
"geometry"][
"resource"].get<
int>();
522 if ( mRootUrl.isLocalFile() )
523 geometryUri = u
"%1/nodes/%2/geometries/1.bin.gz"_s.arg( mRootUrl.toString() ).arg( geometryResource );
525 geometryUri = u
"%1/layers/0/nodes/%2/geometries/1"_s.arg( mRootUrl.toString() ).arg( geometryResource );
528 const json materialJson = meshJson[
"material"];
529 int materialIndex = materialJson[
"definition"].get<
int>();
530 QVariantMap materialInfo;
531 if ( materialIndex >= 0 && materialIndex < mMaterialDefinitions.count() )
533 materialInfo = mMaterialDefinitions[materialIndex];
534 if ( materialInfo.contains( u
"pbrBaseColorTextureName"_s ) )
536 const QString textureName = materialInfo[u
"pbrBaseColorTextureName"_s].toString();
537 const QString textureFormat = materialInfo[u
"pbrBaseColorTextureFormat"_s].toString();
538 materialInfo.remove( u
"pbrBaseColorTextureName"_s );
539 materialInfo.remove( u
"pbrBaseColorTextureFormat"_s );
541 const int textureResource = materialJson[
"resource"].get<
int>();
543 if ( mRootUrl.isLocalFile() )
544 textureUri = u
"%1/nodes/%2/textures/%3.%4"_s.arg( mRootUrl.toString() ).arg( textureResource ).arg( textureName, textureFormat );
546 textureUri = u
"%1/layers/0/nodes/%2/textures/%3"_s.arg( mRootUrl.toString() ).arg( textureResource ).arg( textureName );
547 materialInfo[u
"pbrBaseColorTexture"_s] = textureUri;
553 QVariantMap metadata =
556 { u
"contentFormat"_s, u
"draco"_s },
557 { u
"material"_s, materialInfo }
562void QgsEsriI3STiledSceneIndex::parseNodePage(
const QByteArray &nodePageContent )
564 const json nodePageJson = json::parse( nodePageContent.toStdString() );
565 for (
const json &nodeJson : nodePageJson[
"nodes"] )
567 long long nodeIndex = nodeJson[
"index"].get<
long long>();
568 long long parentNodeIndex = nodeJson.contains(
"parentIndex" ) ? nodeJson[
"parentIndex"].get<
long long>() : -1;
590 double threshold = -1;
591 if ( nodeJson.contains(
"lodThreshold" ) )
593 double maxScreenThresholdSquared = nodeJson[
"lodThreshold"].get<
double>();
597 threshold = obb.
longestSide() / sqrt( maxScreenThresholdSquared / ( M_PI / 4 ) ) * 16;
599 QVector<long long> childNodeIds;
600 if ( nodeJson.contains(
"children" ) )
602 for (
const json &childJson : nodeJson[
"children"] )
604 childNodeIds << childJson.get<
long long>();
616 if ( nodeJson.contains(
"mesh" ) )
619 parseMesh( t, nodeJson[
"mesh"] );
622 mNodeMap.insert( nodeIndex, { parentNodeIndex, childNodeIds, t } );
631QgsEsriI3SDataProviderSharedData::QgsEsriI3SDataProviderSharedData()
636void QgsEsriI3SDataProviderSharedData::initialize(
637 const QString &i3sVersion,
638 const json &layerJson,
642 mI3sVersion = i3sVersion;
643 mLayerJson = layerJson;
650 if ( layerJson.contains(
"spatialReference" ) && layerJson[
"spatialReference"].is_object() )
652 const json spatialReferenceJson = layerJson[
"spatialReference"];
653 if ( spatialReferenceJson.contains(
"latestWkid" ) && spatialReferenceJson[
"latestWkid"].is_number() )
655 epsgCode = spatialReferenceJson[
"latestWkid"].get<
int>();
657 else if ( spatialReferenceJson.contains(
"wkid" ) && spatialReferenceJson[
"wkid"].is_number() )
659 epsgCode = spatialReferenceJson[
"wkid"].get<
int>();
663 if ( epsgCode == 4326 )
675 mSceneCrs = mLayerCrs;
679 new QgsEsriI3STiledSceneIndex(
686 if ( layerJson.contains(
"fullExtent" ) )
688 const json fullExtentJson = layerJson[
"fullExtent"];
690 fullExtentJson[
"xmin"].get<double>(),
691 fullExtentJson[
"ymin"].get<double>(),
692 fullExtentJson[
"xmax"].get<double>(),
693 fullExtentJson[
"ymax"].get<double>() );
695 fullExtentJson[
"zmin"].get<double>(),
696 fullExtentJson[
"zmax"].get<double>() );
705 mBoundingVolume = mIndex.rootTile().boundingVolume();
713QgsEsriI3SDataProvider::QgsEsriI3SDataProvider(
const QString &uri,
717 , mShared( std::make_shared< QgsEsriI3SDataProviderSharedData >() )
720 const QString url = dataSource.param( u
"url"_s );
721 QString sourcePath = QUrl::fromPercentEncoding( url.toUtf8() );
723 if ( sourcePath.isEmpty() )
729 if ( sourcePath.startsWith(
"http"_L1 ) || sourcePath.startsWith(
"file"_L1 ) )
731 rootUrl = sourcePath;
736 rootUrl = QUrl::fromLocalFile( sourcePath );
741 if ( sourcePath.startsWith(
"http"_L1 ) )
743 if ( !loadFromRestService( rootUrl.toString(), layerJson, i3sVersion ) )
748 if ( !loadFromSlpk( rootUrl.toLocalFile(), layerJson, i3sVersion ) )
752 if ( !layerJson.contains(
"layerType" ) )
754 appendError(
QgsErrorMessage( tr(
"Invalid I3S source: missing layer type." ), u
"I3S"_s ) );
758 if ( !layerJson.contains(
"nodePages" ) )
760 appendError(
QgsErrorMessage( tr(
"Missing 'nodePages' attribute (should be available in I3S >= 1.7)" ), u
"I3S"_s ) );
764 QString layerType = QString::fromStdString( layerJson[
"layerType"].get<std::string>() );
765 if ( layerType !=
"3DObject"_L1 && layerType !=
"IntegratedMesh"_L1 )
767 appendError(
QgsErrorMessage( tr(
"Unsupported layer type: " ) + layerType, u
"I3S"_s ) );
771 mShared->initialize( i3sVersion, layerJson, rootUrl, transformContext() );
773 if ( !mShared->mIndex.isValid() )
775 appendError( mShared->mError );
783bool QgsEsriI3SDataProvider::loadFromRestService(
const QString &uri, json &layerJson, QString &i3sVersion )
785 QNetworkRequest networkRequest = QNetworkRequest( QUrl( uri ) );
787 networkRequest.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
788 networkRequest.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
791 if ( reply.
error() != QNetworkReply::NoError )
793 appendError(
QgsErrorMessage( tr(
"Failed to fetch layer metadata: " ) + networkRequest.url().toString(), u
"I3S"_s ) );
796 QByteArray sceneLayerContent = reply.
content();
801 serviceJson = json::parse( sceneLayerContent.toStdString() );
803 catch (
const json::parse_error & )
805 appendError(
QgsErrorMessage( tr(
"Unable to parse JSON: " ) + uri, u
"I3S"_s ) );
809 if ( !serviceJson.contains(
"serviceVersion" ) )
811 appendError(
QgsErrorMessage( tr(
"Missing I3S version: " ) + uri, u
"I3S"_s ) );
814 i3sVersion = QString::fromStdString( serviceJson[
"serviceVersion"].get<std::string>() );
815 if ( !checkI3SVersion( i3sVersion ) )
818 if ( !serviceJson.contains(
"layers" ) || !serviceJson[
"layers"].is_array() || serviceJson[
"layers"].size() < 1 )
820 appendError(
QgsErrorMessage( tr(
"Unable to get layer info: " ) + uri, u
"I3S"_s ) );
824 layerJson = serviceJson[
"layers"][0];
828bool QgsEsriI3SDataProvider::loadFromSlpk(
const QString &uri, json &layerJson, QString &i3sVersion )
831 if ( QFileInfo( uri ).suffix().compare(
"slpk"_L1, Qt::CaseInsensitive ) == 0 )
840 QByteArray metadataContent;
841 QString metadataFileName = u
"metadata.json"_s;
844 const QString metadataDirPath = QDir( uri ).filePath( metadataFileName );
845 QFile fMetadata( metadataDirPath );
846 if ( !fMetadata.open( QIODevice::ReadOnly ) )
848 appendError(
QgsErrorMessage( tr(
"Failed to read layer metadata: %1" ).arg( metadataDirPath ), u
"I3S"_s ) );
851 metadataContent = fMetadata.readAll();
857 appendError(
QgsErrorMessage( tr(
"Failed to read %1 in file: %2" ).arg( metadataFileName ).arg( uri ), u
"I3S"_s ) );
865 metadataJson = json::parse( metadataContent.toStdString() );
867 catch (
const json::parse_error & )
869 appendError(
QgsErrorMessage( tr(
"Unable to parse %1 in: %2" ).arg( metadataFileName ).arg( uri ), u
"I3S"_s ) );
873 if ( !metadataJson.contains(
"I3SVersion" ) )
875 appendError(
QgsErrorMessage( tr(
"Missing I3S version in %1 in: %2" ).arg( metadataFileName ).arg( uri ), u
"I3S"_s ) );
878 i3sVersion = QString::fromStdString( metadataJson[
"I3SVersion"].get<std::string>() );
879 if ( !checkI3SVersion( i3sVersion ) )
882 QByteArray sceneLayerContentGzipped;
883 const QString sceneLayerContentFileName = u
"3dSceneLayer.json.gz"_s;
886 const QString sceneLayerContentDirPath = QDir( uri ).filePath( sceneLayerContentFileName );
887 QFile fSceneLayerContent( sceneLayerContentDirPath );
888 if ( !fSceneLayerContent.open( QIODevice::ReadOnly ) )
890 appendError(
QgsErrorMessage( tr(
"Failed to read layer metadata: %1" ).arg( sceneLayerContentDirPath ), u
"I3S"_s ) );
893 sceneLayerContentGzipped = fSceneLayerContent.readAll();
899 appendError(
QgsErrorMessage( tr(
"Failed to read %1 in file: %2" ).arg( sceneLayerContentFileName ).arg( uri ), u
"I3S"_s ) );
904 QByteArray sceneLayerContent;
907 appendError(
QgsErrorMessage( tr(
"Failed to decompress %1 in: %2" ).arg( sceneLayerContentFileName ).arg( uri ), u
"I3S"_s ) );
913 layerJson = json::parse( sceneLayerContent.toStdString() );
915 catch (
const json::parse_error & )
917 appendError(
QgsErrorMessage( tr(
"Unable to parse %1 in: %2" ).arg( sceneLayerContentFileName ).arg( uri ), u
"I3S"_s ) );
924bool QgsEsriI3SDataProvider::checkI3SVersion(
const QString &i3sVersion )
935 QStringList i3sVersionComponents = i3sVersion.split(
'.' );
936 if ( i3sVersionComponents.size() != 2 )
938 appendError(
QgsErrorMessage( tr(
"Unexpected I3S version format: " ) + i3sVersion, u
"I3S"_s ) );
941 int i3sVersionMajor = i3sVersionComponents[0].toInt();
942 int i3sVersionMinor = i3sVersionComponents[1].toInt();
943 if ( i3sVersionMajor != 1 || ( i3sVersionMajor == 1 && i3sVersionMinor < 7 ) )
945 appendError(
QgsErrorMessage( tr(
"Unsupported I3S version: " ) + i3sVersion, u
"I3S"_s ) );
952QgsEsriI3SDataProvider::QgsEsriI3SDataProvider(
const QgsEsriI3SDataProvider &other )
954 , mIsValid( other.mIsValid )
957 mShared = other.mShared;
960QgsEsriI3SDataProvider::~QgsEsriI3SDataProvider() =
default;
972QgsEsriI3SDataProvider *QgsEsriI3SDataProvider::clone()
const
975 return new QgsEsriI3SDataProvider( *
this );
983 return mShared->mLayerCrs;
991 return mShared->mExtent;
994bool QgsEsriI3SDataProvider::isValid()
const
1001QString QgsEsriI3SDataProvider::name()
const
1008QString QgsEsriI3SDataProvider::description()
const
1012 return QObject::tr(
"ESRI I3S" );
1015QString QgsEsriI3SDataProvider::htmlMetadata()
const
1023 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"I3S Version" ) % u
"</td><td>%1</a>"_s.arg( mShared->mI3sVersion ) % u
"</td></tr>\n"_s;
1025 QString layerType = QString::fromStdString( mShared->mLayerJson[
"layerType"].get<std::string>() );
1026 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Layer Type" ) % u
"</td><td>%1</a>"_s.arg( layerType ) % u
"</td></tr>\n"_s;
1028 if ( mShared->mLayerJson.contains(
"version" ) )
1032 QString version = QString::fromStdString( mShared->mLayerJson[
"version"].get<std::string>() );
1033 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Version" ) % u
"</td><td>%1</a>"_s.arg( version ) % u
"</td></tr>\n"_s;
1037 if ( mShared->mLayerJson.contains(
"name" ) )
1039 QString name = QString::fromStdString( mShared->mLayerJson[
"name"].get<std::string>() );
1040 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Name" ) % u
"</td><td>%1</a>"_s.arg( name ) % u
"</td></tr>\n"_s;
1043 if ( mShared->mLayerJson.contains(
"alias" ) )
1045 QString alias = QString::fromStdString( mShared->mLayerJson[
"alias"].get<std::string>() );
1046 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Alias" ) % u
"</td><td>%1</a>"_s.arg( alias ) % u
"</td></tr>\n"_s;
1049 if ( mShared->mLayerJson.contains(
"description" ) )
1051 QString description = QString::fromStdString( mShared->mLayerJson[
"description"].get<std::string>() );
1052 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Description" ) % u
"</td><td>%1</a>"_s.arg( description ) % u
"</td></tr>\n"_s;
1055 if ( !mShared->mZRange.isInfinite() )
1057 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Z Range" ) % u
"</td><td>%1 - %2</a>"_s.arg( QLocale().toString( mShared->mZRange.lower() ), QLocale().toString( mShared->mZRange.upper() ) ) % u
"</td></tr>\n"_s;
1070 return mShared->mSceneCrs;
1081 return mShared ? mShared->mBoundingVolume : nullVolume;
1091 return mShared->mIndex;
1101 return mShared->mZRange;
1111QgsEsriI3SProviderMetadata::QgsEsriI3SProviderMetadata():
1116QIcon QgsEsriI3SProviderMetadata::icon()
const
1123 return new QgsEsriI3SDataProvider( uri, options, flags );
1129 const QFileInfo fi( uri );
1136 const QVariantMap parts = decodeUri( uri );
1137 fileName = parts.value( u
"path"_s ).toString();
1140 if ( fileName.isEmpty() )
1143 if ( QFileInfo( fileName ).suffix().compare(
"slpk"_L1, Qt::CaseInsensitive ) == 0 )
1146 parts.insert( u
"path"_s, fileName );
1149 details.
setUri( encodeUri( parts ) );
1158QVariantMap QgsEsriI3SProviderMetadata::decodeUri(
const QString &uri )
const
1162 QVariantMap uriComponents;
1163 QString path = dsUri.param( u
"url"_s );
1164 if ( path.isEmpty() && !uri.isEmpty() )
1168 uriComponents.insert( u
"path"_s, path );
1170 return uriComponents;
1173QList< Qgis::LayerType > QgsEsriI3SProviderMetadata::validLayerTypesForUri(
const QString &uri )
const
1175 const QVariantMap parts = decodeUri( uri );
1176 QString filePath = parts.value( u
"path"_s ).toString();
1178 if ( filePath.endsWith( u
".slpk"_s, Qt::CaseSensitivity::CaseInsensitive ) )
1184QString QgsEsriI3SProviderMetadata::encodeUri(
const QVariantMap &parts )
const
1187 const QString partsKey = parts.contains( u
"path"_s ) ? u
"path"_s : u
"url"_s;
1188 dsUri.
setParam( u
"url"_s, parts.value( partsKey ).toString() );
1205 return QObject::tr(
"ESRI Scene layer package" ) + u
" (*.slpk *.SLPK)"_s;
1212 return FileBasedUris;
1215QList<Qgis::LayerType> QgsEsriI3SProviderMetadata::supportedLayerTypes()
const
1222 return ProviderMetadataCapability::PriorityForUri;
1225int QgsEsriI3SProviderMetadata::priorityForUri(
const QString &uri )
const
1227 const QVariantMap parts = decodeUri( uri );
1228 QString filePath = parts.value( u
"path"_s ).toString();
1230 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.