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