QGIS API Documentation 3.99.0-Master (21b3aa880ba)
Loading...
Searching...
No Matches
qgsesrii3sdataprovider.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsesrii3sdataprovider.cpp
3 --------------------------------------
4 Date : July 2025
5 Copyright : (C) 2025 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include <nlohmann/json.hpp>
19
20#include "qgsapplication.h"
21#include "qgslogger.h"
24#include "qgsproviderutils.h"
25#include "qgsreadwritelocker.h"
27#include "qgsthreadingutils.h"
29#include "qgstiledsceneindex.h"
31#include "qgstiledscenetile.h"
32#include "qgsziputils.h"
33
34#include <QDir>
35#include <QFile>
36#include <QFileInfo>
37#include <QIcon>
38#include <QQuaternion>
39
40#include "moc_qgsesrii3sdataprovider.cpp"
41
42#define I3S_PROVIDER_KEY QStringLiteral( "esrii3s" )
43#define I3S_PROVIDER_DESCRIPTION QStringLiteral( "ESRI I3S data provider" )
44
45
47
53class QgsEsriI3STiledSceneIndex final : public QgsAbstractTiledSceneIndex
54{
55 public:
56
57 QgsEsriI3STiledSceneIndex(
58 const json &tileset,
59 const QUrl &rootUrl,
60 const QgsCoordinateTransformContext &transformContext );
61
62 QgsTiledSceneTile rootTile() const final;
63 QgsTiledSceneTile getTile( long long id ) final;
64 long long parentTileId( long long id ) const final;
65 QVector< long long > childTileIds( long long id ) const final;
66 QVector< long long > getTiles( const QgsTiledSceneRequest &request ) final;
67 Qgis::TileChildrenAvailability childAvailability( long long id ) const final;
68 bool fetchHierarchy( long long id, QgsFeedback *feedback = nullptr ) final;
69
70 protected:
71
72 QByteArray fetchContent( const QString &uri, QgsFeedback *feedback = nullptr ) final;
73
74 private:
75
76 bool fetchNodePage( int nodePage, QgsFeedback *feedback = nullptr );
77 void parseNodePage( const QByteArray &nodePageContent );
78 void parseMesh( QgsTiledSceneTile &t, const json &meshJson );
79 QVariantMap parseMaterialDefinition( const json &materialDefinitionJson );
80
81 struct NodeDetails
82 {
83 long long parentNodeIndex;
84 QVector<long long> childNodeIndexes;
86 };
87
88 QVector<QString> mTextureSetFormats;
89 QVector<QVariantMap> mMaterialDefinitions;
90
91 mutable QRecursiveMutex mLock;
92 QUrl mRootUrl;
93 QgsCoordinateTransformContext mTransformContext;
94 long long mRootNodeIndex;
95 int mNodesPerPage;
96 bool mGlobalMode = false;
97 QMap< long long, NodeDetails > mNodeMap;
98 QSet<int> mCachedNodePages;
99
100};
101
102
103
111class QgsEsriI3SDataProviderSharedData
112{
113 public:
114 QgsEsriI3SDataProviderSharedData();
115 void initialize( const QString &i3sVersion,
116 const json &layerJson,
117 const QUrl &rootUrl,
118 const QgsCoordinateTransformContext &transformContext );
119
120 QString mI3sVersion;
121 json mLayerJson;
122
123 QgsCoordinateReferenceSystem mLayerCrs;
124 QgsCoordinateReferenceSystem mSceneCrs;
125 QgsTiledSceneBoundingVolume mBoundingVolume;
126
127 QgsRectangle mExtent;
128 QgsDoubleRange mZRange;
129
130 QgsTiledSceneIndex mIndex;
131
132 QString mError;
133 QReadWriteLock mReadWriteLock;
134
135};
136
137//
138// QgsEsriI3STiledSceneIndex
139//
140
141QgsEsriI3STiledSceneIndex::QgsEsriI3STiledSceneIndex(
142 const json &layerJson,
143 const QUrl &rootUrl,
144 const QgsCoordinateTransformContext &transformContext )
145 : mRootUrl( rootUrl )
146 , mTransformContext( transformContext )
147{
148 // "spatialReference" is not required, yet the spec does not say what should
149 // be the default - assuming global mode is the best we can do...
150 mGlobalMode = true;
151 if ( layerJson.contains( "spatialReference" ) && layerJson["spatialReference"].is_object() )
152 {
153 const json spatialReferenceJson = layerJson["spatialReference"];
154 if ( spatialReferenceJson.contains( "latestWkid" ) && spatialReferenceJson["latestWkid"].is_number() )
155 {
156 int epsgCode = spatialReferenceJson["latestWkid"].get<int>();
157 mGlobalMode = epsgCode == 4326;
158 }
159 else if ( spatialReferenceJson.contains( "wkid" ) && spatialReferenceJson["wkid"].is_number() )
160 {
161 int epsgCode = spatialReferenceJson["wkid"].get<int>();
162 mGlobalMode = epsgCode == 4326;
163 }
164 }
165
166 if ( layerJson.contains( "textureSetDefinitions" ) )
167 {
168 for ( auto textureSetDefinitionJson : layerJson["textureSetDefinitions"] )
169 {
170 QString formatType;
171 for ( const json &formatJson : textureSetDefinitionJson["formats"] )
172 {
173 if ( formatJson["name"].get<std::string>() == "0" )
174 {
175 formatType = QString::fromStdString( formatJson["format"].get<std::string>() );
176 break;
177 }
178 }
179 mTextureSetFormats.append( formatType );
180 }
181 }
182
183 if ( layerJson.contains( "materialDefinitions" ) )
184 {
185 for ( const json &materialDefinitionJson : layerJson["materialDefinitions"] )
186 {
187 QVariantMap materialDef = parseMaterialDefinition( materialDefinitionJson );
188 mMaterialDefinitions.append( materialDef );
189 }
190 }
191
192 json nodePagesJson = layerJson["nodePages"];
193 mNodesPerPage = nodePagesJson["nodesPerPage"].get<int>();
194 mRootNodeIndex = nodePagesJson.contains( "rootIndex" ) ? nodePagesJson["rootIndex"].get<long long>() : 0;
195
196 int rootNodePage = static_cast<int>( mRootNodeIndex / mNodesPerPage );
197 fetchNodePage( rootNodePage );
198}
199
200QVariantMap QgsEsriI3STiledSceneIndex::parseMaterialDefinition( const json &materialDefinitionJson )
201{
202 QVariantMap materialDef;
203
204 if ( materialDefinitionJson.contains( "pbrMetallicRoughness" ) )
205 {
206 const json pbrJson = materialDefinitionJson["pbrMetallicRoughness"];
207 if ( pbrJson.contains( "baseColorFactor" ) )
208 {
209 const json pbrBaseColorFactorJson = pbrJson["baseColorFactor"];
210 materialDef[QStringLiteral( "pbrBaseColorFactor" )] = QVariantList
211 {
212 pbrBaseColorFactorJson[0].get<double>(),
213 pbrBaseColorFactorJson[1].get<double>(),
214 pbrBaseColorFactorJson[2].get<double>(),
215 pbrBaseColorFactorJson[3].get<double>()
216 };
217 }
218 else
219 {
220 materialDef[QStringLiteral( "pbrBaseColorFactor" )] = QVariantList{ 1.0, 1.0, 1.0, 1.0 };
221 }
222 if ( pbrJson.contains( "baseColorTexture" ) )
223 {
224 // but right now we only support png/jpg textures which have
225 // hardcoded name "0" by the spec, and we use texture set definitions
226 // only to figure out whether it is png or jpg
227 const int textureSetDefinitionId = pbrJson["baseColorTexture"]["textureSetDefinitionId"].get<int>();
228 if ( textureSetDefinitionId < mTextureSetFormats.count() )
229 {
230 materialDef[QStringLiteral( "pbrBaseColorTextureName" )] = QStringLiteral( "0" );
231 materialDef[QStringLiteral( "pbrBaseColorTextureFormat" )] = mTextureSetFormats[textureSetDefinitionId];
232 }
233 else
234 {
235 QgsDebugError( QString( "referencing textureSetDefinition that does not exist! %1 " ).arg( textureSetDefinitionId ) );
236 }
237 }
238 }
239 else
240 {
241 materialDef[QStringLiteral( "pbrBaseColorFactor" )] = QVariantList{ 1.0, 1.0, 1.0, 1.0 };
242 }
243
244 if ( materialDefinitionJson.contains( "doubleSided" ) )
245 {
246 materialDef[QStringLiteral( "doubleSided" )] = materialDefinitionJson["doubleSided"].get<bool>();
247 }
248
249 // there are various other properties that can be defined in a material,
250 // but we do not support them: normal texture, occlusion texture, emissive texture,
251 // emissive factor, alpha mode, alpha cutoff, cull face.
252
253 return materialDef;
254}
255
256QgsTiledSceneTile QgsEsriI3STiledSceneIndex::rootTile() const
257{
258 QMutexLocker locker( &mLock );
259 if ( !mNodeMap.contains( mRootNodeIndex ) )
260 {
261 QgsDebugError( QStringLiteral( "Unable to access the root tile!" ) );
262 return QgsTiledSceneTile();
263 }
264 return mNodeMap[mRootNodeIndex].tile;
265}
266
267QgsTiledSceneTile QgsEsriI3STiledSceneIndex::getTile( long long id )
268{
269 QMutexLocker locker( &mLock );
270 auto it = mNodeMap.constFind( id );
271 if ( it != mNodeMap.constEnd() )
272 {
273 return it.value().tile;
274 }
275
276 return QgsTiledSceneTile();
277}
278
279long long QgsEsriI3STiledSceneIndex::parentTileId( long long id ) const
280{
281 QMutexLocker locker( &mLock );
282 auto it = mNodeMap.constFind( id );
283 if ( it != mNodeMap.constEnd() )
284 {
285 return it.value().parentNodeIndex;
286 }
287
288 return -1;
289}
290
291QVector< long long > QgsEsriI3STiledSceneIndex::childTileIds( long long id ) const
292{
293 QMutexLocker locker( &mLock );
294 auto it = mNodeMap.constFind( id );
295 if ( it != mNodeMap.constEnd() )
296 {
297 return it.value().childNodeIndexes;
298 }
299
300 return {};
301}
302
303QVector< long long > QgsEsriI3STiledSceneIndex::getTiles( const QgsTiledSceneRequest &request )
304{
305 QVector< long long > results;
306
307 std::function< void( long long )> traverseNode;
308 traverseNode = [&request, &traverseNode, &results, this]( long long nodeId )
309 {
310 QgsTiledSceneTile t = getTile( nodeId );
311 if ( !request.filterBox().isNull() && !t.boundingVolume().intersects( request.filterBox() ) )
312 return;
313
314 if ( request.requiredGeometricError() <= 0 || t.geometricError() <= 0 || t.geometricError() > request.requiredGeometricError() )
315 {
316 // need to go deeper, this tile does not have enough details
317
318 if ( childAvailability( t.id() ) == Qgis::TileChildrenAvailability::NeedFetching &&
320 {
321 fetchHierarchy( t.id() );
322 }
323
324 // now we should have children available (if any)
325 auto it = mNodeMap.constFind( t.id() );
326 for ( long long childId : it.value().childNodeIndexes )
327 {
328 traverseNode( childId );
329 }
330
331 if ( it.value().childNodeIndexes.isEmpty() )
332 {
333 // there are no children available, so we use this tile even though we want more detail
334 results << t.id();
335 }
336 }
337 else
338 {
339 // this tile has error sufficiently low so that we do not need to traverse
340 // the node tree further down
341 results << t.id();
342 }
343 };
344
345 QMutexLocker locker( &mLock );
346 long long startNodeId = request.parentTileId() == -1 ? mRootNodeIndex : request.parentTileId();
347 traverseNode( startNodeId );
348
349 return results;
350}
351
352Qgis::TileChildrenAvailability QgsEsriI3STiledSceneIndex::childAvailability( long long id ) const
353{
354 QMutexLocker locker( &mLock );
355 auto it = mNodeMap.constFind( id );
356 if ( it == mNodeMap.constEnd() )
357 {
358 // we have no info about the node, so what we return is a bit arbitrary anyway
360 }
361
362 if ( it.value().childNodeIndexes.isEmpty() )
363 {
365 }
366
367 for ( long long childId : it.value().childNodeIndexes )
368 {
369 if ( !mNodeMap.contains( childId ) )
370 {
371 // at least one child is missing from the node map
373 }
374 }
376}
377
378bool QgsEsriI3STiledSceneIndex::fetchHierarchy( long long id, QgsFeedback *feedback )
379{
380 QMutexLocker locker( &mLock );
381 auto it = mNodeMap.constFind( id );
382 if ( it == mNodeMap.constEnd() )
383 return false;
384
385 // gather all the missing node pages to get information about child nodes
386 QSet<int> nodePagesToFetch;
387 for ( long long childId : it.value().childNodeIndexes )
388 {
389 int nodePageIndex = static_cast<int>( childId / mNodesPerPage );
390 if ( !mCachedNodePages.contains( nodePageIndex ) )
391 nodePagesToFetch.insert( nodePageIndex );
392 }
393
394 bool success = true;
395 for ( int nodePage : nodePagesToFetch )
396 {
397 if ( !fetchNodePage( nodePage, feedback ) )
398 {
399 success = false;
400 }
401 }
402
403 return success;
404}
405
406QByteArray QgsEsriI3STiledSceneIndex::fetchContent( const QString &uri, QgsFeedback *feedback )
407{
408 QUrl url( uri );
409 if ( url.isLocalFile() )
410 {
411 const QString slpkPath = mRootUrl.toLocalFile();
412 if ( QFileInfo( slpkPath ).suffix().compare( QLatin1String( "slpk" ), Qt::CaseInsensitive ) == 0 )
413 {
414 const QString fileInSlpk = uri.mid( mRootUrl.toString().length() + 1 );
415
416 QByteArray data;
417 if ( QgsZipUtils::extractFileFromZip( slpkPath, fileInSlpk, data ) )
418 return data;
419
420 return QByteArray();
421 }
422 else
423 {
424 QFile file( url.toLocalFile() );
425 if ( file.open( QIODevice::ReadOnly ) )
426 {
427 return file.readAll();
428 }
429 }
430 }
431 else
432 {
433 QNetworkRequest networkRequest = QNetworkRequest( url );
434 QgsSetRequestInitiatorClass( networkRequest, QStringLiteral( "QgsEsriI3STiledSceneIndex" ) );
435 networkRequest.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
436 networkRequest.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
437
439 networkRequest, QString(), false, feedback );
440 return reply.content();
441 }
442
443 return QByteArray();
444}
445
446bool QgsEsriI3STiledSceneIndex::fetchNodePage( int nodePage, QgsFeedback *feedback )
447{
448 QByteArray nodePageContent;
449 if ( !mRootUrl.isLocalFile() )
450 {
451 const QString uri = QStringLiteral( "%1/layers/0/nodepages/%2" ).arg( mRootUrl.toString() ).arg( nodePage );
452 nodePageContent = retrieveContent( uri, feedback );
453 }
454 else
455 {
456 const QString uri = QStringLiteral( "%1/nodepages/%2.json.gz" ).arg( mRootUrl.toString() ).arg( nodePage );
457 const QByteArray nodePageContentGzipped = retrieveContent( uri, feedback );
458
459 if ( !QgsZipUtils::decodeGzip( nodePageContentGzipped, nodePageContent ) )
460 {
461 QgsDebugError( QStringLiteral( "Failed to decompress node page content: " ) + uri );
462 return false;
463 }
464
465 if ( nodePageContent.isEmpty() )
466 {
467 QgsDebugError( QStringLiteral( "Failed to read node page content: " ) + uri );
468 return false;
469 }
470 }
471
472 try
473 {
474 parseNodePage( nodePageContent );
475 }
476 catch ( json::exception &error )
477 {
478 QgsDebugError( QStringLiteral( "Error reading node page %1: %2" ).arg( nodePage ).arg( error.what() ) );
479 return false;
480 }
481
482 mCachedNodePages.insert( nodePage );
483 return true;
484}
485
486static QgsOrientedBox3D parseBox( const json &box )
487{
488 try
489 {
490 json center = box["center"];
491 json halfSize = box["halfSize"];
492 json quaternion = box["quaternion"]; // order is x, y, z, w
493
494 return QgsOrientedBox3D(
495 QgsVector3D( center[0].get<double>(),
496 center[1].get<double>(),
497 center[2].get<double>() ),
498 QgsVector3D( halfSize[0].get<double>(),
499 halfSize[1].get<double>(),
500 halfSize[2].get<double>() ),
501 QQuaternion( static_cast<float>( quaternion[3].get<double>() ),
502 static_cast<float>( quaternion[0].get<double>() ),
503 static_cast<float>( quaternion[1].get<double>() ),
504 static_cast<float>( quaternion[2].get<double>() ) ) );
505 }
506 catch ( nlohmann::json::exception & )
507 {
508 return QgsOrientedBox3D();
509 }
510}
511
512void QgsEsriI3STiledSceneIndex::parseMesh( QgsTiledSceneTile &t, const json &meshJson )
513{
514 if ( !meshJson.contains( "geometry" ) || !meshJson.contains( "material" ) )
515 return;
516
517 int geometryResource = meshJson["geometry"]["resource"].get<int>();
518 QString geometryUri;
519 if ( mRootUrl.isLocalFile() )
520 geometryUri = QStringLiteral( "%1/nodes/%2/geometries/1.bin.gz" ).arg( mRootUrl.toString() ).arg( geometryResource );
521 else
522 geometryUri = QStringLiteral( "%1/layers/0/nodes/%2/geometries/1" ).arg( mRootUrl.toString() ).arg( geometryResource );
523
524 // parse material and related textures
525 const json materialJson = meshJson["material"];
526 int materialIndex = materialJson["definition"].get<int>();
527 QVariantMap materialInfo;
528 if ( materialIndex >= 0 && materialIndex < mMaterialDefinitions.count() )
529 {
530 materialInfo = mMaterialDefinitions[materialIndex];
531 if ( materialInfo.contains( QStringLiteral( "pbrBaseColorTextureName" ) ) )
532 {
533 const QString textureName = materialInfo[QStringLiteral( "pbrBaseColorTextureName" )].toString();
534 const QString textureFormat = materialInfo[QStringLiteral( "pbrBaseColorTextureFormat" )].toString();
535 materialInfo.remove( QStringLiteral( "pbrBaseColorTextureName" ) );
536 materialInfo.remove( QStringLiteral( "pbrBaseColorTextureFormat" ) );
537
538 const int textureResource = materialJson["resource"].get<int>();
539 QString textureUri;
540 if ( mRootUrl.isLocalFile() )
541 textureUri = QStringLiteral( "%1/nodes/%2/textures/%3.%4" ).arg( mRootUrl.toString() ).arg( textureResource ).arg( textureName, textureFormat );
542 else
543 textureUri = QStringLiteral( "%1/layers/0/nodes/%2/textures/%3" ).arg( mRootUrl.toString() ).arg( textureResource ).arg( textureName );
544 materialInfo[QStringLiteral( "pbrBaseColorTexture" )] = textureUri;
545 }
546 }
547
548 t.setResources( { { QStringLiteral( "content" ), geometryUri } } );
549
550 QVariantMap metadata =
551 {
552 { QStringLiteral( "gltfUpAxis" ), static_cast< int >( Qgis::Axis::Z ) },
553 { QStringLiteral( "contentFormat" ), QStringLiteral( "draco" ) },
554 { QStringLiteral( "material" ), materialInfo }
555 };
556 t.setMetadata( metadata );
557}
558
559void QgsEsriI3STiledSceneIndex::parseNodePage( const QByteArray &nodePageContent )
560{
561 const json nodePageJson = json::parse( nodePageContent.toStdString() );
562 for ( const json &nodeJson : nodePageJson["nodes"] )
563 {
564 long long nodeIndex = nodeJson["index"].get<long long>();
565 long long parentNodeIndex = nodeJson.contains( "parentIndex" ) ? nodeJson["parentIndex"].get<long long>() : -1;
566
567 QgsOrientedBox3D obb = parseBox( nodeJson["obb"] );
568
569 // OBB in global scene layers should be constructed in ECEF and its values are defined like this:
570 // - center - X,Y - lon/lat in degrees, Z - elevation in meters
571 // - half-size - in meters
572 // - quaternion - in reference to ECEF coordinate system
573
574 // OBB in local scene layers should be constructed in the CRS of the layer
575 // - center, half-size - in units of the CRS
576 // - quaternion - in reference to CRS of the layer
577
578 if ( mGlobalMode )
579 {
580 QgsCoordinateTransform ct( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) ),
581 QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) ),
582 mTransformContext );
583 QgsVector3D obbCenterEcef = ct.transform( obb.center() );
584 obb = QgsOrientedBox3D( { obbCenterEcef.x(), obbCenterEcef.y(), obbCenterEcef.z() }, obb.halfAxesList() );
585 }
586
587 double threshold = -1;
588 if ( nodeJson.contains( "lodThreshold" ) )
589 {
590 double maxScreenThresholdSquared = nodeJson["lodThreshold"].get<double>();
591
592 // This conversion from "maxScreenThresholdSQ" to geometry error is copied from CesiumJS
593 // implementation of I3S (the only difference is Cesium uses longest OBB axis length
594 threshold = obb.longestSide() / sqrt( maxScreenThresholdSquared / ( M_PI / 4 ) ) * 16;
595 }
596 QVector<long long> childNodeIds;
597 if ( nodeJson.contains( "children" ) )
598 {
599 for ( const json &childJson : nodeJson["children"] )
600 {
601 childNodeIds << childJson.get<long long>();
602 }
603 }
604
605 QgsTiledSceneTile t( nodeIndex );
606 t.setBoundingVolume( obb );
607 t.setGeometricError( threshold );
608
609 QgsMatrix4x4 transform;
610 transform.translate( obb.center() );
611 t.setTransform( transform );
612
613 if ( nodeJson.contains( "mesh" ) )
614 {
615 // parse geometry and material
616 parseMesh( t, nodeJson["mesh"] );
617 }
618
619 mNodeMap.insert( nodeIndex, { parentNodeIndex, childNodeIds, t } );
620 }
621}
622
623
624//
625// QgsEsriI3SDataProviderSharedData
626//
627
628QgsEsriI3SDataProviderSharedData::QgsEsriI3SDataProviderSharedData()
629 : mIndex( QgsTiledSceneIndex( nullptr ) )
630{
631}
632
633void QgsEsriI3SDataProviderSharedData::initialize(
634 const QString &i3sVersion,
635 const json &layerJson,
636 const QUrl &rootUrl,
637 const QgsCoordinateTransformContext &transformContext )
638{
639 mI3sVersion = i3sVersion;
640 mLayerJson = layerJson;
641
642 int epsgCode = 0;
643
644 // ESRI spatialReference https://developers.arcgis.com/web-map-specification/objects/spatialReference/
645 // defines latestWkid as optional, wkid as mandatory
646 // so we first check for latestWkid and use wkid as fallback
647 if ( layerJson.contains( "spatialReference" ) && layerJson["spatialReference"].is_object() )
648 {
649 const json spatialReferenceJson = layerJson["spatialReference"];
650 if ( spatialReferenceJson.contains( "latestWkid" ) && spatialReferenceJson["latestWkid"].is_number() )
651 {
652 epsgCode = spatialReferenceJson["latestWkid"].get<int>();
653 }
654 else if ( spatialReferenceJson.contains( "wkid" ) && spatialReferenceJson["wkid"].is_number() )
655 {
656 epsgCode = spatialReferenceJson["wkid"].get<int>();
657 }
658 }
659
660 if ( epsgCode == 4326 )
661 {
662 // "global" mode
663
664 // TODO: elevation can be ellipsoidal or gravity-based!
665 mLayerCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) );
666 mSceneCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) );
667 }
668 else
669 {
670 // "local" mode - using a projected CRS
671 mLayerCrs = QgsCoordinateReferenceSystem( QString( "EPSG:%1" ).arg( epsgCode ) );
672 mSceneCrs = mLayerCrs;
673 }
674
675 mIndex = QgsTiledSceneIndex(
676 new QgsEsriI3STiledSceneIndex(
677 layerJson,
678 rootUrl,
679 transformContext
680 )
681 );
682
683 if ( layerJson.contains( "fullExtent" ) )
684 {
685 const json fullExtentJson = layerJson["fullExtent"];
686 mExtent = QgsRectangle(
687 fullExtentJson["xmin"].get<double>(),
688 fullExtentJson["ymin"].get<double>(),
689 fullExtentJson["xmax"].get<double>(),
690 fullExtentJson["ymax"].get<double>() );
691 mZRange = QgsDoubleRange(
692 fullExtentJson["zmin"].get<double>(),
693 fullExtentJson["zmax"].get<double>() );
694 }
695 else
696 {
697 QgsBox3D box = mIndex.rootTile().boundingVolume().bounds( QgsCoordinateTransform( mSceneCrs, mLayerCrs, transformContext ) );
698 mExtent = box.toRectangle();
699 mZRange = QgsDoubleRange( box.zMinimum(), box.zMaximum() );
700 }
701
702 mBoundingVolume = mIndex.rootTile().boundingVolume();
703}
704
705//
706// QgsEsriI3SDataProvider
707//
708
709
710QgsEsriI3SDataProvider::QgsEsriI3SDataProvider( const QString &uri,
711 const QgsDataProvider::ProviderOptions &providerOptions,
713 : QgsTiledSceneDataProvider( uri, providerOptions, flags )
714 , mShared( std::make_shared< QgsEsriI3SDataProviderSharedData >() )
715{
716 QgsDataSourceUri dataSource( dataSourceUri() );
717 QString sourcePath = dataSource.param( QStringLiteral( "url" ) );
718
719 if ( sourcePath.isEmpty() )
720 {
721 sourcePath = uri;
722 }
723
724 QUrl rootUrl;
725 if ( sourcePath.startsWith( QLatin1String( "http" ) ) || sourcePath.startsWith( QLatin1String( "file" ) ) )
726 {
727 rootUrl = sourcePath;
728 }
729 else
730 {
731 // when saved in project as relative path, we then get just the path... (TODO?)
732 rootUrl = QUrl::fromLocalFile( sourcePath );
733 }
734
735 QString i3sVersion;
736 json layerJson;
737 if ( sourcePath.startsWith( QLatin1String( "http" ) ) )
738 {
739 if ( !loadFromRestService( rootUrl.toString(), layerJson, i3sVersion ) )
740 return;
741 }
742 else
743 {
744 if ( !loadFromSlpk( rootUrl.toLocalFile(), layerJson, i3sVersion ) )
745 return;
746 }
747
748 if ( !layerJson.contains( "layerType" ) )
749 {
750 appendError( QgsErrorMessage( tr( "Invalid I3S source: missing layer type." ), QStringLiteral( "I3S" ) ) );
751 return;
752 }
753
754 if ( !layerJson.contains( "nodePages" ) )
755 {
756 appendError( QgsErrorMessage( tr( "Missing 'nodePages' attribute (should be available in I3S >= 1.7)" ), QStringLiteral( "I3S" ) ) );
757 return;
758 }
759
760 QString layerType = QString::fromStdString( layerJson["layerType"].get<std::string>() );
761 if ( layerType != QLatin1String( "3DObject" ) && layerType != QLatin1String( "IntegratedMesh" ) )
762 {
763 appendError( QgsErrorMessage( tr( "Unsupported layer type: " ) + layerType, QStringLiteral( "I3S" ) ) );
764 return;
765 }
766
767 mShared->initialize( i3sVersion, layerJson, rootUrl, transformContext() );
768
769 if ( !mShared->mIndex.isValid() )
770 {
771 appendError( mShared->mError );
772 return;
773 }
774
775 mIsValid = true;
776}
777
778
779bool QgsEsriI3SDataProvider::loadFromRestService( const QString &uri, json &layerJson, QString &i3sVersion )
780{
781 QNetworkRequest networkRequest = QNetworkRequest( QUrl( uri ) );
782 QgsSetRequestInitiatorClass( networkRequest, QStringLiteral( "QgsEsriI3SDataProvider" ) );
783 networkRequest.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
784 networkRequest.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
785
787 if ( reply.error() != QNetworkReply::NoError )
788 {
789 appendError( QgsErrorMessage( tr( "Failed to fetch layer metadata: " ) + networkRequest.url().toString(), QStringLiteral( "I3S" ) ) );
790 return false;
791 }
792 QByteArray sceneLayerContent = reply.content();
793
794 json serviceJson;
795 try
796 {
797 serviceJson = json::parse( sceneLayerContent.toStdString() );
798 }
799 catch ( const json::parse_error & )
800 {
801 appendError( QgsErrorMessage( tr( "Unable to parse JSON: " ) + uri, QStringLiteral( "I3S" ) ) );
802 return false;
803 }
804
805 if ( !serviceJson.contains( "serviceVersion" ) )
806 {
807 appendError( QgsErrorMessage( tr( "Missing I3S version: " ) + uri, QStringLiteral( "I3S" ) ) );
808 return false;
809 }
810 i3sVersion = QString::fromStdString( serviceJson["serviceVersion"].get<std::string>() );
811 if ( !checkI3SVersion( i3sVersion ) )
812 return false;
813
814 if ( !serviceJson.contains( "layers" ) || !serviceJson["layers"].is_array() || serviceJson["layers"].size() < 1 )
815 {
816 appendError( QgsErrorMessage( tr( "Unable to get layer info: " ) + uri, QStringLiteral( "I3S" ) ) );
817 return false;
818 }
819
820 layerJson = serviceJson["layers"][0];
821 return true;
822}
823
824bool QgsEsriI3SDataProvider::loadFromSlpk( const QString &uri, json &layerJson, QString &i3sVersion )
825{
826 bool isExtracted;
827 if ( QFileInfo( uri ).suffix().compare( QLatin1String( "slpk" ), Qt::CaseInsensitive ) == 0 )
828 {
829 isExtracted = false;
830 }
831 else
832 {
833 isExtracted = true;
834 }
835
836 QByteArray metadataContent;
837 QString metadataFileName = QStringLiteral( "metadata.json" );
838 if ( isExtracted ) // if a directory, read directly as Extracted SLPK
839 {
840 const QString metadataDirPath = QDir( uri ).filePath( metadataFileName );
841 QFile fMetadata( metadataDirPath );
842 if ( !fMetadata.open( QIODevice::ReadOnly ) )
843 {
844 appendError( QgsErrorMessage( tr( "Failed to read layer metadata: %1" ).arg( metadataDirPath ), QStringLiteral( "I3S" ) ) );
845 return false;
846 }
847 metadataContent = fMetadata.readAll();
848 }
849 else // SLPK
850 {
851 if ( !QgsZipUtils::extractFileFromZip( uri, metadataFileName, metadataContent ) )
852 {
853 appendError( QgsErrorMessage( tr( "Failed to read %1 in file: %2" ).arg( metadataFileName ).arg( uri ), QStringLiteral( "I3S" ) ) );
854 return false;
855 }
856 }
857
858 json metadataJson;
859 try
860 {
861 metadataJson = json::parse( metadataContent.toStdString() );
862 }
863 catch ( const json::parse_error & )
864 {
865 appendError( QgsErrorMessage( tr( "Unable to parse %1 in: %2" ).arg( metadataFileName ).arg( uri ), QStringLiteral( "I3S" ) ) );
866 return false;
867 }
868
869 if ( !metadataJson.contains( "I3SVersion" ) )
870 {
871 appendError( QgsErrorMessage( tr( "Missing I3S version in %1 in: %2" ).arg( metadataFileName ).arg( uri ), QStringLiteral( "I3S" ) ) );
872 return false;
873 }
874 i3sVersion = QString::fromStdString( metadataJson["I3SVersion"].get<std::string>() );
875 if ( !checkI3SVersion( i3sVersion ) )
876 return false;
877
878 QByteArray sceneLayerContentGzipped;
879 const QString sceneLayerContentFileName = QStringLiteral( "3dSceneLayer.json.gz" );
880 if ( isExtracted ) // if a directory, read directly as Extracted SLPK
881 {
882 const QString sceneLayerContentDirPath = QDir( uri ).filePath( sceneLayerContentFileName );
883 QFile fSceneLayerContent( sceneLayerContentDirPath );
884 if ( !fSceneLayerContent.open( QIODevice::ReadOnly ) )
885 {
886 appendError( QgsErrorMessage( tr( "Failed to read layer metadata: %1" ).arg( sceneLayerContentDirPath ), QStringLiteral( "I3S" ) ) );
887 return false;
888 }
889 sceneLayerContentGzipped = fSceneLayerContent.readAll();
890 }
891 else // SLPK
892 {
893 if ( !QgsZipUtils::extractFileFromZip( uri, sceneLayerContentFileName, sceneLayerContentGzipped ) )
894 {
895 appendError( QgsErrorMessage( tr( "Failed to read %1 in file: %2" ).arg( sceneLayerContentFileName ).arg( uri ), QStringLiteral( "I3S" ) ) );
896 return false;
897 }
898 }
899
900 QByteArray sceneLayerContent;
901 if ( !QgsZipUtils::decodeGzip( sceneLayerContentGzipped, sceneLayerContent ) )
902 {
903 appendError( QgsErrorMessage( tr( "Failed to decompress %1 in: %2" ).arg( sceneLayerContentFileName ).arg( uri ), QStringLiteral( "I3S" ) ) );
904 return false;
905 }
906
907 try
908 {
909 layerJson = json::parse( sceneLayerContent.toStdString() );
910 }
911 catch ( const json::parse_error & )
912 {
913 appendError( QgsErrorMessage( tr( "Unable to parse %1 in: %2" ).arg( sceneLayerContentFileName ).arg( uri ), QStringLiteral( "I3S" ) ) );
914 return false;
915 }
916
917 return true;
918}
919
920bool QgsEsriI3SDataProvider::checkI3SVersion( const QString &i3sVersion )
921{
922 // We support I3S version >= 1.7 released in 2019. Earlier versions
923 // of the spec are much less efficient to work with (e.g. they do not
924 // support node pages, no Draco compression of geometries)
925
926 // Note: for more confusion, OGC has different versioning of I3S.
927 // ESRI I3S version 1.7 should be equivalent to OGC I3S version 1.3.
928 // Fortunately OGC versioning is not really used anywhere (apart from OGC docs)
929 // so we can ignore OGC I3S versions and use ESRI I3S version.
930
931 QStringList i3sVersionComponents = i3sVersion.split( '.' );
932 if ( i3sVersionComponents.size() != 2 )
933 {
934 appendError( QgsErrorMessage( tr( "Unexpected I3S version format: " ) + i3sVersion, QStringLiteral( "I3S" ) ) );
935 return false;
936 }
937 int i3sVersionMajor = i3sVersionComponents[0].toInt();
938 int i3sVersionMinor = i3sVersionComponents[1].toInt();
939 if ( i3sVersionMajor != 1 || ( i3sVersionMajor == 1 && i3sVersionMinor < 7 ) )
940 {
941 appendError( QgsErrorMessage( tr( "Unsupported I3S version: " ) + i3sVersion, QStringLiteral( "I3S" ) ) );
942 return false;
943 }
944 return true;
945}
946
947
948QgsEsriI3SDataProvider::QgsEsriI3SDataProvider( const QgsEsriI3SDataProvider &other )
950 , mIsValid( other.mIsValid )
951{
952 QgsReadWriteLocker locker( other.mShared->mReadWriteLock, QgsReadWriteLocker::Read );
953 mShared = other.mShared;
954}
955
956QgsEsriI3SDataProvider::~QgsEsriI3SDataProvider() = default;
957
958Qgis::DataProviderFlags QgsEsriI3SDataProvider::flags() const
959{
961}
962
963Qgis::TiledSceneProviderCapabilities QgsEsriI3SDataProvider::capabilities() const
964{
966}
967
968QgsEsriI3SDataProvider *QgsEsriI3SDataProvider::clone() const
969{
971 return new QgsEsriI3SDataProvider( *this );
972}
973
974QgsCoordinateReferenceSystem QgsEsriI3SDataProvider::crs() const
975{
977
978 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
979 return mShared->mLayerCrs;
980}
981
982QgsRectangle QgsEsriI3SDataProvider::extent() const
983{
985
986 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
987 return mShared->mExtent;
988}
989
990bool QgsEsriI3SDataProvider::isValid() const
991{
993
994 return mIsValid;
995}
996
997QString QgsEsriI3SDataProvider::name() const
998{
1000
1001 return I3S_PROVIDER_KEY;
1002}
1003
1004QString QgsEsriI3SDataProvider::description() const
1005{
1007
1008 return QObject::tr( "ESRI I3S" );
1009}
1010
1011QString QgsEsriI3SDataProvider::htmlMetadata() const
1012{
1014
1015 QString metadata;
1016
1017 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1018
1019 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "I3S Version" ) % QStringLiteral( "</td><td>%1</a>" ).arg( mShared->mI3sVersion ) % QStringLiteral( "</td></tr>\n" );
1020
1021 QString layerType = QString::fromStdString( mShared->mLayerJson["layerType"].get<std::string>() );
1022 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Layer Type" ) % QStringLiteral( "</td><td>%1</a>" ).arg( layerType ) % QStringLiteral( "</td></tr>\n" );
1023
1024 if ( mShared->mLayerJson.contains( "version" ) )
1025 {
1026 // [required] "The ID of the last update session in which any resource belonging to this layer has been updated."
1027 // (even though marked as required, not all datasets provide it)
1028 QString version = QString::fromStdString( mShared->mLayerJson["version"].get<std::string>() );
1029 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Version" ) % QStringLiteral( "</td><td>%1</a>" ).arg( version ) % QStringLiteral( "</td></tr>\n" );
1030 }
1031
1032 // [optional] "The name of this layer."
1033 if ( mShared->mLayerJson.contains( "name" ) )
1034 {
1035 QString name = QString::fromStdString( mShared->mLayerJson["name"].get<std::string>() );
1036 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Name" ) % QStringLiteral( "</td><td>%1</a>" ).arg( name ) % QStringLiteral( "</td></tr>\n" );
1037 }
1038 // [optional] "The display alias to be used for this layer."
1039 if ( mShared->mLayerJson.contains( "alias" ) )
1040 {
1041 QString alias = QString::fromStdString( mShared->mLayerJson["alias"].get<std::string>() );
1042 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Alias" ) % QStringLiteral( "</td><td>%1</a>" ).arg( alias ) % QStringLiteral( "</td></tr>\n" );
1043 }
1044 // [optional] "Description string for this layer."
1045 if ( mShared->mLayerJson.contains( "description" ) )
1046 {
1047 QString description = QString::fromStdString( mShared->mLayerJson["description"].get<std::string>() );
1048 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Description" ) % QStringLiteral( "</td><td>%1</a>" ).arg( description ) % QStringLiteral( "</td></tr>\n" );
1049 }
1050
1051 if ( !mShared->mZRange.isInfinite() )
1052 {
1053 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Z Range" ) % QStringLiteral( "</td><td>%1 - %2</a>" ).arg( QLocale().toString( mShared->mZRange.lower() ), QLocale().toString( mShared->mZRange.upper() ) ) % QStringLiteral( "</td></tr>\n" );
1054 }
1055
1056 return metadata;
1057}
1058
1059const QgsCoordinateReferenceSystem QgsEsriI3SDataProvider::sceneCrs() const
1060{
1062 if ( !mShared )
1064
1065 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1066 return mShared->mSceneCrs;
1067}
1068
1069const QgsTiledSceneBoundingVolume &QgsEsriI3SDataProvider::boundingVolume() const
1070{
1072 static QgsTiledSceneBoundingVolume nullVolume;
1073 if ( !mShared )
1074 return nullVolume;
1075
1076 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1077 return mShared ? mShared->mBoundingVolume : nullVolume;
1078}
1079
1080QgsTiledSceneIndex QgsEsriI3SDataProvider::index() const
1081{
1083 if ( !mShared )
1084 return QgsTiledSceneIndex( nullptr );
1085
1086 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1087 return mShared->mIndex;
1088}
1089
1090QgsDoubleRange QgsEsriI3SDataProvider::zRange() const
1091{
1093 if ( !mShared )
1094 return QgsDoubleRange();
1095
1096 QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
1097 return mShared->mZRange;
1098}
1099
1100
1101
1102//
1103// QgsEsriI3SProviderMetadata
1104//
1105
1106
1107QgsEsriI3SProviderMetadata::QgsEsriI3SProviderMetadata():
1109{
1110}
1111
1112QIcon QgsEsriI3SProviderMetadata::icon() const
1113{
1114 return QgsApplication::getThemeIcon( QStringLiteral( "mIconEsriI3s.svg" ) );
1115}
1116
1117QgsEsriI3SDataProvider *QgsEsriI3SProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags )
1118{
1119 return new QgsEsriI3SDataProvider( uri, options, flags );
1120}
1121
1122QList<QgsProviderSublayerDetails> QgsEsriI3SProviderMetadata::querySublayers( const QString &uri, Qgis::SublayerQueryFlags, QgsFeedback * ) const
1123{
1124 QString fileName;
1125 const QFileInfo fi( uri );
1126 if ( fi.isFile() )
1127 {
1128 fileName = uri;
1129 }
1130 else
1131 {
1132 const QVariantMap parts = decodeUri( uri );
1133 fileName = parts.value( QStringLiteral( "path" ) ).toString();
1134 }
1135
1136 if ( fileName.isEmpty() )
1137 return {};
1138
1139 if ( QFileInfo( fileName ).suffix().compare( QLatin1String( "slpk" ), Qt::CaseInsensitive ) == 0 )
1140 {
1141 QVariantMap parts;
1142 parts.insert( QStringLiteral( "path" ), fileName );
1143
1145 details.setUri( encodeUri( parts ) );
1146 details.setProviderKey( key() );
1149 return {details};
1150 }
1151 return {};
1152}
1153
1154QVariantMap QgsEsriI3SProviderMetadata::decodeUri( const QString &uri ) const
1155{
1156 QgsDataSourceUri dsUri( uri );
1157
1158 QVariantMap uriComponents;
1159 QString path = dsUri.param( QStringLiteral( "url" ) );
1160 if ( path.isEmpty() && !uri.isEmpty() )
1161 {
1162 path = uri;
1163 }
1164 uriComponents.insert( QStringLiteral( "path" ), path );
1165
1166 return uriComponents;
1167}
1168
1169QList< Qgis::LayerType > QgsEsriI3SProviderMetadata::validLayerTypesForUri( const QString &uri ) const
1170{
1171 const QVariantMap parts = decodeUri( uri );
1172 QString filePath = parts.value( QStringLiteral( "path" ) ).toString();
1173
1174 if ( filePath.endsWith( QStringLiteral( ".slpk" ), Qt::CaseSensitivity::CaseInsensitive ) )
1175 return { Qgis::LayerType::TiledScene };
1176
1177 return {};
1178}
1179
1180QString QgsEsriI3SProviderMetadata::encodeUri( const QVariantMap &parts ) const
1181{
1182 QgsDataSourceUri dsUri;
1183 const QString partsKey = parts.contains( QStringLiteral( "path" ) ) ? QStringLiteral( "path" ) : QStringLiteral( "url" );
1184 dsUri.setParam( QStringLiteral( "url" ), parts.value( partsKey ).toString() );
1185 return dsUri.encodedUri();
1186}
1187
1188QString QgsEsriI3SProviderMetadata::filters( Qgis::FileFilterType type )
1189{
1190 switch ( type )
1191 {
1198 return QString();
1199
1201 return QObject::tr( "ESRI Scene layer package" ) + QStringLiteral( " (*.slpk *.SLPK)" );
1202 }
1203 return QString();
1204}
1205
1206QgsProviderMetadata::ProviderCapabilities QgsEsriI3SProviderMetadata::providerCapabilities() const
1207{
1208 return FileBasedUris;
1209}
1210
1211QList<Qgis::LayerType> QgsEsriI3SProviderMetadata::supportedLayerTypes() const
1212{
1213 return { Qgis::LayerType::TiledScene };
1214}
1215
1216QgsProviderMetadata::ProviderMetadataCapabilities QgsEsriI3SProviderMetadata::capabilities() const
1217{
1218 return ProviderMetadataCapability::PriorityForUri;
1219}
1220
1221int QgsEsriI3SProviderMetadata::priorityForUri( const QString &uri ) const
1222{
1223 const QVariantMap parts = decodeUri( uri );
1224 QString filePath = parts.value( QStringLiteral( "path" ) ).toString();
1225
1226 if ( filePath.endsWith( QStringLiteral( ".slpk" ), Qt::CaseSensitivity::CaseInsensitive ) )
1227 return 100;
1228
1229 return 0;
1230}
1231
QFlags< TiledSceneProviderCapability > TiledSceneProviderCapabilities
Tiled scene data provider capabilities.
Definition qgis.h:5631
QFlags< DataProviderFlag > DataProviderFlags
Data provider flags.
Definition qgis.h:2315
FileFilterType
Type of file filters.
Definition qgis.h:1354
@ TiledScene
Tiled scene layers.
Definition qgis.h:1361
@ Vector
Vector layers.
Definition qgis.h:1355
@ VectorTile
Vector tile layers.
Definition qgis.h:1360
@ Mesh
Mesh layers.
Definition qgis.h:1357
@ Raster
Raster layers.
Definition qgis.h:1356
@ MeshDataset
Mesh datasets.
Definition qgis.h:1358
@ PointCloud
Point clouds.
Definition qgis.h:1359
@ FastExtent2D
Provider's 2D extent retrieval via QgsDataProvider::extent() is always guaranteed to be trivial/fast ...
Definition qgis.h:2310
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
Definition qgis.h:486
QFlags< SublayerQueryFlag > SublayerQueryFlags
Sublayer query flags.
Definition qgis.h:1399
TileChildrenAvailability
Possible availability states for a tile's children.
Definition qgis.h:5668
@ Available
Tile children are already available.
Definition qgis.h:5670
@ NeedFetching
Tile has children, but they are not yet available and must be fetched.
Definition qgis.h:5671
@ NoChildren
Tile is known to have no children.
Definition qgis.h:5669
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:199
@ NoHierarchyFetch
Do not allow hierarchy fetching when hierarchy is not currently available. Avoids network requests,...
Definition qgis.h:5682
@ Z
Z-axis.
Definition qgis.h:2454
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.
Definition qgsbox3d.h:42
double zMaximum() const
Returns the maximum z value.
Definition qgsbox3d.h:258
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
Definition qgsbox3d.h:378
double zMinimum() const
Returns the minimum z value.
Definition qgsbox3d.h:251
Represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
Handles coordinate transforms between two coordinate systems.
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.
Definition qgsrange.h:233
Represents a single error message.
Definition qgserror.h:33
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
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.
Holds data provider key, description, and associated shared library file or function pointer informat...
QFlags< ProviderMetadataCapability > ProviderMetadataCapabilities
QFlags< ProviderCapability > ProviderCapabilities
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...
Definition qgsvector3d.h:30
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:49
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:51
double x() const
Returns X coordinate.
Definition qgsvector3d.h:47
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 I3S_PROVIDER_KEY
#define QgsDebugError(str)
Definition qgslogger.h:57
#define QgsSetRequestInitiatorClass(request, _class)
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS
Setting options for creating vector data providers.