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
64 QVector< long long >
childTileIds(
long long id )
const final;
73 bool fetchNodePage(
int nodePage,
QgsFeedback *feedback =
nullptr );
74 void parseNodePage(
const QByteArray &nodePageContent );
76 QVariantMap parseMaterialDefinition(
const json &materialDefinitionJson );
80 long long parentNodeIndex;
81 QVector<long long> childNodeIndexes;
85 QVector<QString> mTextureSetFormats;
86 QVector<QVariantMap> mMaterialDefinitions;
88 mutable QRecursiveMutex mLock;
91 long long mRootNodeIndex;
93 bool mGlobalMode =
false;
94 QMap< long long, NodeDetails > mNodeMap;
95 QSet<int> mCachedNodePages;
106class QgsEsriI3SDataProviderSharedData
109 QgsEsriI3SDataProviderSharedData();
110 void initialize(
const QString &i3sVersion,
const json &layerJson,
const QUrl &rootUrl,
const QgsCoordinateTransformContext &transformContext );
115 QgsCoordinateReferenceSystem mLayerCrs;
116 QgsCoordinateReferenceSystem mSceneCrs;
117 QgsTiledSceneBoundingVolume mBoundingVolume;
119 QgsRectangle mExtent;
120 QgsDoubleRange mZRange;
122 QgsTiledSceneIndex mIndex;
125 QReadWriteLock mReadWriteLock;
132QgsEsriI3STiledSceneIndex::QgsEsriI3STiledSceneIndex(
const json &layerJson,
const QUrl &rootUrl,
const QgsCoordinateTransformContext &transformContext )
133 : mRootUrl( rootUrl )
134 , mTransformContext( transformContext )
139 if ( layerJson.contains(
"spatialReference" ) && layerJson[
"spatialReference"].is_object() )
141 const json spatialReferenceJson = layerJson[
"spatialReference"];
142 if ( spatialReferenceJson.contains(
"latestWkid" ) && spatialReferenceJson[
"latestWkid"].is_number() )
144 int epsgCode = spatialReferenceJson[
"latestWkid"].get<int>();
145 mGlobalMode = epsgCode == 4326;
147 else if ( spatialReferenceJson.contains(
"wkid" ) && spatialReferenceJson[
"wkid"].is_number() )
149 int epsgCode = spatialReferenceJson[
"wkid"].get<int>();
150 mGlobalMode = epsgCode == 4326;
154 if ( layerJson.contains(
"textureSetDefinitions" ) )
156 for ( auto textureSetDefinitionJson : layerJson[
"textureSetDefinitions"] )
159 for ( const json &formatJson : textureSetDefinitionJson[
"formats"] )
161 if ( formatJson[
"name"].get<std::string>() ==
"0" )
163 formatType = QString::fromStdString( formatJson[
"format"].get<std::string>() );
167 mTextureSetFormats.append( formatType );
171 if ( layerJson.contains(
"materialDefinitions" ) )
173 for ( const json &materialDefinitionJson : layerJson[
"materialDefinitions"] )
175 QVariantMap materialDef = parseMaterialDefinition( materialDefinitionJson );
176 mMaterialDefinitions.append( materialDef );
180 json nodePagesJson = layerJson[
"nodePages"];
181 mNodesPerPage = nodePagesJson[
"nodesPerPage"].get<
int>();
182 mRootNodeIndex = nodePagesJson.contains(
"rootIndex" ) ? nodePagesJson[
"rootIndex"].get<long long>() : 0;
184 int rootNodePage =
static_cast<int>( mRootNodeIndex / mNodesPerPage );
185 fetchNodePage( rootNodePage );
188QVariantMap QgsEsriI3STiledSceneIndex::parseMaterialDefinition(
const json &materialDefinitionJson )
190 QVariantMap materialDef;
192 if ( materialDefinitionJson.contains(
"pbrMetallicRoughness" ) )
194 const json pbrJson = materialDefinitionJson[
"pbrMetallicRoughness"];
195 if ( pbrJson.contains(
"baseColorFactor" ) )
197 const json pbrBaseColorFactorJson = pbrJson[
"baseColorFactor"];
198 materialDef[u
"pbrBaseColorFactor"_s]
199 = QVariantList { pbrBaseColorFactorJson[0].get<
double>(), pbrBaseColorFactorJson[1].get<double>(), pbrBaseColorFactorJson[2].get<
double>(), pbrBaseColorFactorJson[3].get<double>() };
203 materialDef[u
"pbrBaseColorFactor"_s] = QVariantList { 1.0, 1.0, 1.0, 1.0 };
205 if ( pbrJson.contains(
"baseColorTexture" ) )
210 const int textureSetDefinitionId = pbrJson[
"baseColorTexture"][
"textureSetDefinitionId"].get<
int>();
211 if ( textureSetDefinitionId < mTextureSetFormats.count() )
213 materialDef[u
"pbrBaseColorTextureName"_s] = u
"0"_s;
214 materialDef[u
"pbrBaseColorTextureFormat"_s] = mTextureSetFormats[textureSetDefinitionId];
218 QgsDebugError( QString(
"referencing textureSetDefinition that does not exist! %1 " ).arg( textureSetDefinitionId ) );
224 materialDef[u
"pbrBaseColorFactor"_s] = QVariantList { 1.0, 1.0, 1.0, 1.0 };
227 if ( materialDefinitionJson.contains(
"doubleSided" ) )
229 materialDef[u
"doubleSided"_s] = materialDefinitionJson[
"doubleSided"].get<
bool>();
241 QMutexLocker locker( &mLock );
242 if ( !mNodeMap.contains( mRootNodeIndex ) )
247 return mNodeMap[mRootNodeIndex].tile;
252 QMutexLocker locker( &mLock );
253 auto it = mNodeMap.constFind(
id );
254 if ( it != mNodeMap.constEnd() )
256 return it.value().tile;
262long long QgsEsriI3STiledSceneIndex::parentTileId(
long long id )
const
264 QMutexLocker locker( &mLock );
265 auto it = mNodeMap.constFind(
id );
266 if ( it != mNodeMap.constEnd() )
268 return it.value().parentNodeIndex;
274QVector< long long > QgsEsriI3STiledSceneIndex::childTileIds(
long long id )
const
276 QMutexLocker locker( &mLock );
277 auto it = mNodeMap.constFind(
id );
278 if ( it != mNodeMap.constEnd() )
280 return it.value().childNodeIndexes;
288 QVector< long long > results;
290 std::function< void(
long long )> traverseNode;
291 traverseNode = [&request, &traverseNode, &results,
this](
long long nodeId ) {
302 fetchHierarchy( t.
id() );
306 auto it = mNodeMap.constFind( t.
id() );
307 for (
long long childId : it.value().childNodeIndexes )
309 traverseNode( childId );
312 if ( it.value().childNodeIndexes.isEmpty() )
326 QMutexLocker locker( &mLock );
328 traverseNode( startNodeId );
335 QMutexLocker locker( &mLock );
336 auto it = mNodeMap.constFind(
id );
337 if ( it == mNodeMap.constEnd() )
343 if ( it.value().childNodeIndexes.isEmpty() )
348 for (
long long childId : it.value().childNodeIndexes )
350 if ( !mNodeMap.contains( childId ) )
359bool QgsEsriI3STiledSceneIndex::fetchHierarchy(
long long id,
QgsFeedback *feedback )
361 QMutexLocker locker( &mLock );
362 auto it = mNodeMap.constFind(
id );
363 if ( it == mNodeMap.constEnd() )
367 QSet<int> nodePagesToFetch;
368 for (
long long childId : it.value().childNodeIndexes )
370 int nodePageIndex =
static_cast<int>( childId / mNodesPerPage );
371 if ( !mCachedNodePages.contains( nodePageIndex ) )
372 nodePagesToFetch.insert( nodePageIndex );
376 for (
int nodePage : nodePagesToFetch )
378 if ( !fetchNodePage( nodePage, feedback ) )
387QByteArray QgsEsriI3STiledSceneIndex::fetchContent(
const QString &uri,
QgsFeedback *feedback )
390 if ( url.isLocalFile() )
392 const QString slpkPath = mRootUrl.toLocalFile();
393 if ( QFileInfo( slpkPath ).suffix().compare(
"slpk"_L1, Qt::CaseInsensitive ) == 0 )
395 const QString fileInSlpk = uri.mid( mRootUrl.toString().length() + 1 );
405 QFile file( url.toLocalFile() );
406 if ( file.open( QIODevice::ReadOnly ) )
408 return file.readAll();
414 QNetworkRequest networkRequest = QNetworkRequest( url );
416 networkRequest.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
417 networkRequest.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
426bool QgsEsriI3STiledSceneIndex::fetchNodePage(
int nodePage,
QgsFeedback *feedback )
428 QByteArray nodePageContent;
429 if ( !mRootUrl.isLocalFile() )
431 const QString uri = u
"%1/layers/0/nodepages/%2"_s.arg( mRootUrl.toString() ).arg( nodePage );
432 nodePageContent = retrieveContent( uri, feedback );
436 const QString uri = u
"%1/nodepages/%2.json.gz"_s.arg( mRootUrl.toString() ).arg( nodePage );
437 const QByteArray nodePageContentGzipped = retrieveContent( uri, feedback );
441 QgsDebugError( u
"Failed to decompress node page content: "_s + uri );
445 if ( nodePageContent.isEmpty() )
447 QgsDebugError( u
"Failed to read node page content: "_s + uri );
454 parseNodePage( nodePageContent );
456 catch ( json::exception &error )
458 QgsDebugError( u
"Error reading node page %1: %2"_s.arg( nodePage ).arg( error.what() ) );
462 mCachedNodePages.insert( nodePage );
470 json center = box[
"center"];
471 json halfSize = box[
"halfSize"];
472 json quaternion = box[
"quaternion"];
475 QgsVector3D( center[0].get<double>(), center[1].get<double>(), center[2].get<double>() ),
476 QgsVector3D( halfSize[0].get<double>(), halfSize[1].get<double>(), halfSize[2].get<double>() ),
478 static_cast<float>( quaternion[3].get<double>() ),
479 static_cast<float>( quaternion[0].get<double>() ),
480 static_cast<float>( quaternion[1].get<double>() ),
481 static_cast<float>( quaternion[2].get<double>() )
485 catch ( nlohmann::json::exception & )
491void QgsEsriI3STiledSceneIndex::parseMesh(
QgsTiledSceneTile &t,
const json &meshJson )
493 if ( !meshJson.contains(
"geometry" ) || !meshJson.contains(
"material" ) )
496 int geometryResource = meshJson[
"geometry"][
"resource"].get<
int>();
498 if ( mRootUrl.isLocalFile() )
499 geometryUri = u
"%1/nodes/%2/geometries/1.bin.gz"_s.arg( mRootUrl.toString() ).arg( geometryResource );
501 geometryUri = u
"%1/layers/0/nodes/%2/geometries/1"_s.arg( mRootUrl.toString() ).arg( geometryResource );
504 const json materialJson = meshJson[
"material"];
505 int materialIndex = materialJson[
"definition"].get<
int>();
506 QVariantMap materialInfo;
507 if ( materialIndex >= 0 && materialIndex < mMaterialDefinitions.count() )
509 materialInfo = mMaterialDefinitions[materialIndex];
510 if ( materialInfo.contains( u
"pbrBaseColorTextureName"_s ) )
512 const QString textureName = materialInfo[u
"pbrBaseColorTextureName"_s].toString();
513 const QString textureFormat = materialInfo[u
"pbrBaseColorTextureFormat"_s].toString();
514 materialInfo.remove( u
"pbrBaseColorTextureName"_s );
515 materialInfo.remove( u
"pbrBaseColorTextureFormat"_s );
517 const int textureResource = materialJson[
"resource"].get<
int>();
519 if ( mRootUrl.isLocalFile() )
520 textureUri = u
"%1/nodes/%2/textures/%3.%4"_s.arg( mRootUrl.toString() ).arg( textureResource ).arg( textureName, textureFormat );
522 textureUri = u
"%1/layers/0/nodes/%2/textures/%3"_s.arg( mRootUrl.toString() ).arg( textureResource ).arg( textureName );
523 materialInfo[u
"pbrBaseColorTexture"_s] = textureUri;
529 QVariantMap metadata = { { u
"gltfUpAxis"_s,
static_cast< int >(
Qgis::Axis::Z ) }, { u
"contentFormat"_s, u
"draco"_s }, { u
"material"_s, materialInfo } };
533void QgsEsriI3STiledSceneIndex::parseNodePage(
const QByteArray &nodePageContent )
535 const json nodePageJson = json::parse( nodePageContent.toStdString() );
536 for (
const json &nodeJson : nodePageJson[
"nodes"] )
538 long long nodeIndex = nodeJson[
"index"].get<
long long>();
539 long long parentNodeIndex = nodeJson.contains(
"parentIndex" ) ? nodeJson[
"parentIndex"].get<
long long>() : -1;
559 double threshold = -1;
560 if ( nodeJson.contains(
"lodThreshold" ) )
562 double maxScreenThresholdSquared = nodeJson[
"lodThreshold"].get<
double>();
566 threshold = obb.
longestSide() / sqrt( maxScreenThresholdSquared / ( M_PI / 4 ) ) * 16;
568 QVector<long long> childNodeIds;
569 if ( nodeJson.contains(
"children" ) )
571 for (
const json &childJson : nodeJson[
"children"] )
573 childNodeIds << childJson.get<
long long>();
585 if ( nodeJson.contains(
"mesh" ) )
588 parseMesh( t, nodeJson[
"mesh"] );
591 mNodeMap.insert( nodeIndex, { parentNodeIndex, childNodeIds, t } );
600QgsEsriI3SDataProviderSharedData::QgsEsriI3SDataProviderSharedData()
604void QgsEsriI3SDataProviderSharedData::initialize(
const QString &i3sVersion,
const json &layerJson,
const QUrl &rootUrl,
const QgsCoordinateTransformContext &transformContext )
606 mI3sVersion = i3sVersion;
607 mLayerJson = layerJson;
614 if ( layerJson.contains(
"spatialReference" ) && layerJson[
"spatialReference"].is_object() )
616 const json spatialReferenceJson = layerJson[
"spatialReference"];
617 if ( spatialReferenceJson.contains(
"latestWkid" ) && spatialReferenceJson[
"latestWkid"].is_number() )
619 epsgCode = spatialReferenceJson[
"latestWkid"].get<
int>();
621 else if ( spatialReferenceJson.contains(
"wkid" ) && spatialReferenceJson[
"wkid"].is_number() )
623 epsgCode = spatialReferenceJson[
"wkid"].get<
int>();
627 if ( epsgCode == 4326 )
639 mSceneCrs = mLayerCrs;
642 mIndex =
QgsTiledSceneIndex(
new QgsEsriI3STiledSceneIndex( layerJson, rootUrl, transformContext ) );
644 if ( layerJson.contains(
"fullExtent" ) )
646 const json fullExtentJson = layerJson[
"fullExtent"];
647 mExtent =
QgsRectangle( fullExtentJson[
"xmin"].get<double>(), fullExtentJson[
"ymin"].get<double>(), fullExtentJson[
"xmax"].get<double>(), fullExtentJson[
"ymax"].get<double>() );
648 mZRange =
QgsDoubleRange( fullExtentJson[
"zmin"].get<double>(), fullExtentJson[
"zmax"].get<double>() );
657 mBoundingVolume = mIndex.rootTile().boundingVolume();
667 , mShared( std::make_shared< QgsEsriI3SDataProviderSharedData >() )
670 const QString url = dataSource.param( u
"url"_s );
671 QString sourcePath = QUrl::fromPercentEncoding( url.toUtf8() );
673 if ( sourcePath.isEmpty() )
679 if ( sourcePath.startsWith(
"http"_L1 ) || sourcePath.startsWith(
"file"_L1 ) )
681 rootUrl = sourcePath;
686 rootUrl = QUrl::fromLocalFile( sourcePath );
691 if ( sourcePath.startsWith(
"http"_L1 ) )
693 if ( !loadFromRestService( rootUrl.toString(), layerJson, i3sVersion ) )
698 if ( !loadFromSlpk( rootUrl.toLocalFile(), layerJson, i3sVersion ) )
702 if ( !layerJson.contains(
"layerType" ) )
704 appendError(
QgsErrorMessage( tr(
"Invalid I3S source: missing layer type." ), u
"I3S"_s ) );
708 if ( !layerJson.contains(
"nodePages" ) )
710 appendError(
QgsErrorMessage( tr(
"Missing 'nodePages' attribute (should be available in I3S >= 1.7)" ), u
"I3S"_s ) );
714 QString layerType = QString::fromStdString( layerJson[
"layerType"].get<std::string>() );
715 if ( layerType !=
"3DObject"_L1 && layerType !=
"IntegratedMesh"_L1 )
717 appendError(
QgsErrorMessage( tr(
"Unsupported layer type: " ) + layerType, u
"I3S"_s ) );
721 mShared->initialize( i3sVersion, layerJson, rootUrl, transformContext() );
723 if ( !mShared->mIndex.isValid() )
725 appendError( mShared->mError );
733bool QgsEsriI3SDataProvider::loadFromRestService(
const QString &uri, json &layerJson, QString &i3sVersion )
735 QNetworkRequest networkRequest = QNetworkRequest( QUrl( uri ) );
737 networkRequest.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
738 networkRequest.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
741 if ( reply.
error() != QNetworkReply::NoError )
743 appendError(
QgsErrorMessage( tr(
"Failed to fetch layer metadata: " ) + networkRequest.url().toString(), u
"I3S"_s ) );
746 QByteArray sceneLayerContent = reply.
content();
751 serviceJson = json::parse( sceneLayerContent.toStdString() );
753 catch (
const json::parse_error & )
755 appendError(
QgsErrorMessage( tr(
"Unable to parse JSON: " ) + uri, u
"I3S"_s ) );
759 if ( !serviceJson.contains(
"serviceVersion" ) )
761 appendError(
QgsErrorMessage( tr(
"Missing I3S version: " ) + uri, u
"I3S"_s ) );
764 i3sVersion = QString::fromStdString( serviceJson[
"serviceVersion"].get<std::string>() );
765 if ( !checkI3SVersion( i3sVersion ) )
768 if ( !serviceJson.contains(
"layers" ) || !serviceJson[
"layers"].is_array() || serviceJson[
"layers"].size() < 1 )
770 appendError(
QgsErrorMessage( tr(
"Unable to get layer info: " ) + uri, u
"I3S"_s ) );
774 layerJson = serviceJson[
"layers"][0];
778bool QgsEsriI3SDataProvider::loadFromSlpk(
const QString &uri, json &layerJson, QString &i3sVersion )
781 if ( QFileInfo( uri ).suffix().compare(
"slpk"_L1, Qt::CaseInsensitive ) == 0 )
790 QByteArray metadataContent;
791 QString metadataFileName = u
"metadata.json"_s;
794 const QString metadataDirPath = QDir( uri ).filePath( metadataFileName );
795 QFile fMetadata( metadataDirPath );
796 if ( !fMetadata.open( QIODevice::ReadOnly ) )
798 appendError(
QgsErrorMessage( tr(
"Failed to read layer metadata: %1" ).arg( metadataDirPath ), u
"I3S"_s ) );
801 metadataContent = fMetadata.readAll();
807 appendError(
QgsErrorMessage( tr(
"Failed to read %1 in file: %2" ).arg( metadataFileName ).arg( uri ), u
"I3S"_s ) );
815 metadataJson = json::parse( metadataContent.toStdString() );
817 catch (
const json::parse_error & )
819 appendError(
QgsErrorMessage( tr(
"Unable to parse %1 in: %2" ).arg( metadataFileName ).arg( uri ), u
"I3S"_s ) );
823 if ( !metadataJson.contains(
"I3SVersion" ) )
825 appendError(
QgsErrorMessage( tr(
"Missing I3S version in %1 in: %2" ).arg( metadataFileName ).arg( uri ), u
"I3S"_s ) );
828 i3sVersion = QString::fromStdString( metadataJson[
"I3SVersion"].get<std::string>() );
829 if ( !checkI3SVersion( i3sVersion ) )
832 QByteArray sceneLayerContentGzipped;
833 const QString sceneLayerContentFileName = u
"3dSceneLayer.json.gz"_s;
836 const QString sceneLayerContentDirPath = QDir( uri ).filePath( sceneLayerContentFileName );
837 QFile fSceneLayerContent( sceneLayerContentDirPath );
838 if ( !fSceneLayerContent.open( QIODevice::ReadOnly ) )
840 appendError(
QgsErrorMessage( tr(
"Failed to read layer metadata: %1" ).arg( sceneLayerContentDirPath ), u
"I3S"_s ) );
843 sceneLayerContentGzipped = fSceneLayerContent.readAll();
849 appendError(
QgsErrorMessage( tr(
"Failed to read %1 in file: %2" ).arg( sceneLayerContentFileName ).arg( uri ), u
"I3S"_s ) );
854 QByteArray sceneLayerContent;
857 appendError(
QgsErrorMessage( tr(
"Failed to decompress %1 in: %2" ).arg( sceneLayerContentFileName ).arg( uri ), u
"I3S"_s ) );
863 layerJson = json::parse( sceneLayerContent.toStdString() );
865 catch (
const json::parse_error & )
867 appendError(
QgsErrorMessage( tr(
"Unable to parse %1 in: %2" ).arg( sceneLayerContentFileName ).arg( uri ), u
"I3S"_s ) );
874bool QgsEsriI3SDataProvider::checkI3SVersion(
const QString &i3sVersion )
885 QStringList i3sVersionComponents = i3sVersion.split(
'.' );
886 if ( i3sVersionComponents.size() != 2 )
888 appendError(
QgsErrorMessage( tr(
"Unexpected I3S version format: " ) + i3sVersion, u
"I3S"_s ) );
891 int i3sVersionMajor = i3sVersionComponents[0].toInt();
892 int i3sVersionMinor = i3sVersionComponents[1].toInt();
893 if ( i3sVersionMajor != 1 || ( i3sVersionMajor == 1 && i3sVersionMinor < 7 ) )
895 appendError(
QgsErrorMessage( tr(
"Unsupported I3S version: " ) + i3sVersion, u
"I3S"_s ) );
902QgsEsriI3SDataProvider::QgsEsriI3SDataProvider(
const QgsEsriI3SDataProvider &other )
904 , mIsValid( other.mIsValid )
907 mShared = other.mShared;
910QgsEsriI3SDataProvider::~QgsEsriI3SDataProvider() =
default;
922QgsEsriI3SDataProvider *QgsEsriI3SDataProvider::clone()
const
925 return new QgsEsriI3SDataProvider( *
this );
933 return mShared->mLayerCrs;
941 return mShared->mExtent;
944bool QgsEsriI3SDataProvider::isValid()
const
951QString QgsEsriI3SDataProvider::name()
const
958QString QgsEsriI3SDataProvider::description()
const
962 return QObject::tr(
"ESRI I3S" );
965QString QgsEsriI3SDataProvider::htmlMetadata()
const
973 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"I3S Version" ) % u
"</td><td>%1</a>"_s.arg( mShared->mI3sVersion ) % u
"</td></tr>\n"_s;
975 QString layerType = QString::fromStdString( mShared->mLayerJson[
"layerType"].get<std::string>() );
976 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Layer Type" ) % u
"</td><td>%1</a>"_s.arg( layerType ) % u
"</td></tr>\n"_s;
978 if ( mShared->mLayerJson.contains(
"version" ) )
982 QString version = QString::fromStdString( mShared->mLayerJson[
"version"].get<std::string>() );
983 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Version" ) % u
"</td><td>%1</a>"_s.arg( version ) % u
"</td></tr>\n"_s;
987 if ( mShared->mLayerJson.contains(
"name" ) )
989 QString name = QString::fromStdString( mShared->mLayerJson[
"name"].get<std::string>() );
990 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Name" ) % u
"</td><td>%1</a>"_s.arg( name ) % u
"</td></tr>\n"_s;
993 if ( mShared->mLayerJson.contains(
"alias" ) )
995 QString alias = QString::fromStdString( mShared->mLayerJson[
"alias"].get<std::string>() );
996 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Alias" ) % u
"</td><td>%1</a>"_s.arg( alias ) % u
"</td></tr>\n"_s;
999 if ( mShared->mLayerJson.contains(
"description" ) )
1001 QString description = QString::fromStdString( mShared->mLayerJson[
"description"].get<std::string>() );
1002 metadata += u
"<tr><td class=\"highlight\">"_s % tr(
"Description" ) % u
"</td><td>%1</a>"_s.arg( description ) % u
"</td></tr>\n"_s;
1005 if ( !mShared->mZRange.isInfinite() )
1007 metadata += u
"<tr><td class=\"highlight\">"_s
1009 % u
"</td><td>%1 - %2</a>"_s.arg( QLocale().toString( mShared->mZRange.lower() ), QLocale().toString( mShared->mZRange.upper() ) )
1010 % u
"</td></tr>\n"_s;
1023 return mShared->mSceneCrs;
1034 return mShared ? mShared->mBoundingVolume : nullVolume;
1044 return mShared->mIndex;
1054 return mShared->mZRange;
1063QgsEsriI3SProviderMetadata::QgsEsriI3SProviderMetadata()
1067QIcon QgsEsriI3SProviderMetadata::icon()
const
1074 return new QgsEsriI3SDataProvider( uri, options, flags );
1080 const QFileInfo fi( uri );
1087 const QVariantMap parts = decodeUri( uri );
1088 fileName = parts.value( u
"path"_s ).toString();
1091 if ( fileName.isEmpty() )
1094 if ( QFileInfo( fileName ).suffix().compare(
"slpk"_L1, Qt::CaseInsensitive ) == 0 )
1097 parts.insert( u
"path"_s, fileName );
1100 details.
setUri( encodeUri( parts ) );
1109QVariantMap QgsEsriI3SProviderMetadata::decodeUri(
const QString &uri )
const
1113 QVariantMap uriComponents;
1114 QString path = dsUri.param( u
"url"_s );
1115 if ( path.isEmpty() && !uri.isEmpty() )
1119 uriComponents.insert( u
"path"_s, path );
1121 return uriComponents;
1124QList< Qgis::LayerType > QgsEsriI3SProviderMetadata::validLayerTypesForUri(
const QString &uri )
const
1126 const QVariantMap parts = decodeUri( uri );
1127 QString filePath = parts.value( u
"path"_s ).toString();
1129 if ( filePath.endsWith( u
".slpk"_s, Qt::CaseSensitivity::CaseInsensitive ) )
1135QString QgsEsriI3SProviderMetadata::encodeUri(
const QVariantMap &parts )
const
1138 const QString partsKey = parts.contains( u
"path"_s ) ? u
"path"_s : u
"url"_s;
1139 dsUri.
setParam( u
"url"_s, parts.value( partsKey ).toString() );
1156 return QObject::tr(
"ESRI Scene layer package" ) + u
" (*.slpk *.SLPK)"_s;
1163 return FileBasedUris;
1166QList<Qgis::LayerType> QgsEsriI3SProviderMetadata::supportedLayerTypes()
const
1173 return ProviderMetadataCapability::PriorityForUri;
1176int QgsEsriI3SProviderMetadata::priorityForUri(
const QString &uri )
const
1178 const QVariantMap parts = decodeUri( uri );
1179 QString filePath = parts.value( u
"path"_s ).toString();
1181 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.