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