QGIS API Documentation 4.3.0-Master (52aa6503bed)
Loading...
Searching...
No Matches
qgscategorizedchunkloader_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscategorizedchunkloader_p.cpp
3 --------------------------------------
4 Date : November 2025
5 Copyright : (C) 2025 by Jean Felder
6 Email : jean dot felder at oslandia 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 <functional>
19
20#include "qgs3dsymbolregistry.h"
21#include "qgs3dutils.h"
23#include "qgsapplication.h"
26#include "qgschunkloader.h"
27#include "qgschunknode.h"
28#include "qgseventtracing.h"
31#include "qgsfields.h"
32#include "qgsline3dsymbol.h"
33#include "qgspoint3dsymbol.h"
34#include "qgspolygon3dsymbol.h"
35#include "qgsthreadingutils.h"
36#include "qgsvectorlayer.h"
38
39#include <QMutex>
40#include <QString>
41#include <Qt3DCore/QTransform>
42#include <QtConcurrentRun>
43
44#include "moc_qgscategorizedchunkloader_p.cpp"
45
46using namespace Qt::StringLiterals;
47
49
50QgsCategorizedChunkLoader::QgsCategorizedChunkLoader( const Qgs3DRenderContext &context, QgsVectorLayer *vectorLayer, const QgsCategorized3DRenderer *renderer, double zMin, double zMax, int maxFeatures )
51 : mRenderContext( context )
52 , mLayer( vectorLayer )
53 , mCategories( &renderer->categories() )
54 , mAttributeName( renderer->classAttribute() )
55 , mNodesAreLeafs( std::make_shared<NodeIsLeafMap>() )
56 , mMaxFeatures( maxFeatures )
57{
58 if ( context.crs().type() == Qgis::CrsType::Geocentric )
59 {
60 // TODO: add support for handling of vector layers
61 // (we're using dummy quadtree here to make sure the empty extent does not break the scene completely)
62 QgsDebugError( u"Vector layers in globe scenes are not supported yet!"_s );
63 setupQuadtree( QgsBox3D( -7e6, -7e6, -7e6, 7e6, 7e6, 7e6 ), -1, 3 );
64 return;
65 }
66
67 // choose the smaller root extent between context and mLayer ones:
68 QgsRectangle extent = context.extent();
69 if ( context.extent().contains( mLayer->extent() ) )
70 {
71 extent = mLayer->extent();
72 }
73 QgsBox3D rootBox3D( extent, zMin, zMax );
74
75 // add small padding to avoid clipping of point features located at the edge of the bounding box
76 rootBox3D.grow( 1.0 );
77
78 const float rootError = static_cast<float>( std::max<double>( rootBox3D.width(), rootBox3D.height() ) * QgsVectorLayer3DTilingSettings::tileGeometryErrorRatio() );
79 setupQuadtree( rootBox3D, rootError );
80}
81
82QgsCategorizedChunkLoader::~QgsCategorizedChunkLoader() = default;
83
84void QgsCategorizedChunkLoader::processFeature( ChunkLoadingContext &ctx, const QgsFeature &feature )
85{
86 ctx.renderCtx.expressionContext().setFeature( feature );
87
88 // Get Value for feature
89 QgsAttributes attributes = feature.attributes();
90 QVariant value;
91 if ( ctx.attributeIdx == -1 )
92 {
93 value = ctx.expression->evaluate( &ctx.renderCtx.expressionContext() );
94 }
95 else
96 {
97 value = attributes.value( ctx.attributeIdx );
98 }
99
100 auto handlerIt = ctx.featuresHandlerHash.constFind( QgsVariantUtils::isNull( value ) ? QString() : value.toString() );
101 if ( handlerIt == ctx.featuresHandlerHash.constEnd() )
102 {
103 if ( ctx.featuresHandlerHash.isEmpty() )
104 {
105 QgsDebugError( u"There are no hashed symbols!"_s );
106 }
107 else
108 {
109 QgsDebugMsgLevel( u"Attribute value not found: %1"_s.arg( value.toString() ), 3 );
110 }
111 return;
112 }
113
114 QgsFeature3DHandler *handler = *handlerIt;
115 handler->processFeature( feature, ctx.renderCtx );
116}
117
118
119QFuture<QgsChunkLoaderResult> QgsCategorizedChunkLoader::loadChunk( QgsChunkNode *node )
120{
121 ChunkLoadingContext ctx;
122 ctx.node = node;
123 ctx.renderCtx = mRenderContext; // Copy since we'll be mutating it
124 ctx.source = std::make_unique<QgsVectorLayerFeatureSource>( mLayer );
125 ctx.nodesAreLeafs = mNodesAreLeafs;
126 ctx.maxFeatures = mMaxFeatures;
127
128 QgsExpressionContext exprContext;
130 exprContext.setFields( mLayer->fields() );
131 ctx.renderCtx.setExpressionContext( exprContext );
132
133 // build the feature handlers
134 QSet<QString> attributesNames;
135
136 // prepare the expression
137 ctx.attributeIdx = mLayer->fields().lookupField( mAttributeName );
138 if ( ctx.attributeIdx == -1 )
139 {
140 ctx.expression.reset( new QgsExpression( mAttributeName ) );
141 ctx.expression->prepare( &ctx.renderCtx.expressionContext() );
142 }
143
144 // build features hash
145 for ( const Qgs3DRendererCategory &category : *mCategories )
146 {
147 if ( !category.renderState() )
148 {
149 continue;
150 }
151
152 const QVariant value = category.value();
153 std::unique_ptr<QgsFeature3DHandler> handler( QgsApplication::symbol3DRegistry()->createHandlerForSymbol( mLayer, category.symbol() ) );
154 ctx.handlers.push_back( std::move( handler ) );
155 QgsFeature3DHandler *handlerPtr = ctx.handlers.back().get();
156 if ( value.userType() == QMetaType::Type::QVariantList )
157 {
158 const QVariantList variantList = value.toList();
159 for ( const QVariant &listElt : variantList )
160 {
161 ctx.featuresHandlerHash.insert( listElt.toString(), handlerPtr );
162 }
163 }
164 else
165 {
166 ctx.featuresHandlerHash.insert( value.toString(), handlerPtr );
167 }
168
169 handlerPtr->prepare( ctx.renderCtx, attributesNames, node->box3D() );
170 }
171 attributesNames.insert( mAttributeName );
172
173 // build the feature request
174 // only a subset of data to be queried
175 const QgsRectangle rect = node->box3D().toRectangle();
176 QgsFeatureRequest request;
177 request.setDestinationCrs( ctx.renderCtx.crs(), ctx.renderCtx.transformContext() );
178 request.setSubsetOfAttributes( attributesNames, mLayer->fields() );
179 request.setFilterRect( rect );
180
181 const QString rendererFilter = QgsCategorizedSymbolUtils<QgsCategorized3DRenderer>::buildCategorizedFilter( mAttributeName, mLayer->fields(), *mCategories );
182 if ( !rendererFilter.isEmpty() )
183 {
184 request.setFilterExpression( rendererFilter );
185 }
186
187 return QtConcurrent::run( &QgsCategorizedChunkLoader::loadChunkInWorker, std::move( ctx ), request ).then( this, [this]( ChunkLoadingContext ctx ) {
188 return QgsChunkLoaderResult { std::bind_front( &QgsCategorizedChunkLoader::createEntity, this, ctx ) };
189 } );
190}
191
192void QgsCategorizedChunkLoader::loadChunkInWorker( QPromise<ChunkLoadingContext> &promise, ChunkLoadingContext ctx, QgsFeatureRequest request )
193{
194 const QgsScopedEvent event( u"3D"_s, u"Categorized chunk load"_s );
195 QgsFeature feature;
196 QgsFeatureIterator featureIt = ctx.source->getFeatures( request );
197 int featureCount = 0;
198 bool featureLimitReached = false;
199 while ( featureIt.nextFeature( feature ) )
200 {
201 if ( promise.isCanceled() )
202 {
203 break;
204 }
205
206 if ( ++featureCount > ctx.maxFeatures )
207 {
208 featureLimitReached = true;
209 break;
210 }
211
212 processFeature( ctx, feature );
213 }
214 bool nodeIsLeaf = false;
215 if ( !featureLimitReached )
216 {
217 QgsDebugMsgLevel( u"All features fetched for node: %1"_s.arg( ctx.node->tileId().text() ), 3 );
218
219 if ( featureCount == 0 || std::max<double>( ctx.node->box3D().width(), ctx.node->box3D().height() ) < QgsVectorLayer3DTilingSettings::maximumLeafExtent() )
220 nodeIsLeaf = true;
221 }
222
223 {
224 QMutexLocker<QMutex> locker( &ctx.nodesAreLeafs->mutex );
225 ctx.nodesAreLeafs->map[ctx.node->tileId().text()] = nodeIsLeaf;
226 }
227
228 promise.addResult( std::move( ctx ) );
229}
230
231Qt3DCore::QEntity *QgsCategorizedChunkLoader::createEntity( ChunkLoadingContext ctx, Qt3DCore::QEntity *parent )
232{
234 long long featureCount = 0;
235 for ( const auto &featureHandler : ctx.handlers )
236 {
237 featureCount += featureHandler->featureCount();
238 }
239 if ( featureCount == 0 )
240 {
241 // an empty node, so we return no entity. This tags the node as having no data and effectively removes it.
242 return nullptr;
243 }
244
245 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity( parent );
246 float zMin = std::numeric_limits<float>::max();
247 float zMax = std::numeric_limits<float>::lowest();
248 for ( const auto &featureHandler : ctx.handlers )
249 {
250 featureHandler->finalize( entity, ctx.renderCtx );
251 if ( featureHandler->zMinimum() < zMin )
252 {
253 zMin = featureHandler->zMinimum();
254 }
255 if ( featureHandler->zMaximum() > zMax )
256 {
257 zMax = featureHandler->zMaximum();
258 }
259 }
260
261 // fix the vertical range of the node from the estimated vertical range to the true range
262 if ( zMin != std::numeric_limits<float>::max() && zMax != std::numeric_limits<float>::lowest() )
263 {
264 QgsBox3D box = ctx.node->box3D();
265 box.setZMinimum( zMin );
266 box.setZMaximum( zMax );
267 ctx.node->setExactBox3D( box );
268 ctx.node->updateParentBoundingBoxesRecursively();
269 }
270
271 return entity;
272}
273
274QFuture<QVector<QgsChunkNode *>> QgsCategorizedChunkLoader::createChildren( QgsChunkNode *node )
275{
276 {
277 QMutexLocker<QMutex> locker( &mNodesAreLeafs->mutex );
278 if ( mNodesAreLeafs->map.value( node->tileId().text(), false ) )
279 return QtFuture::makeReadyValueFuture( QVector<QgsChunkNode *> {} );
280 }
281
282 return QgsQuadtreeChunkLoader::createChildren( node );
283}
284
285
287
288QgsCategorizedChunkedEntity::QgsCategorizedChunkedEntity(
289 Qgs3DMapSettings *mapSettings, QgsVectorLayer *vectorLayer, double zMin, double zMax, const QgsVectorLayer3DTilingSettings &tilingSettings, const QgsCategorized3DRenderer *renderer
290)
291 : QgsAbstractFeatureBasedChunkedEntity(
292 mapSettings,
293 -1, // max. allowed screen error (negative tau means that we need to go until leaves are reached)
294 new QgsCategorizedChunkLoader( Qgs3DRenderContext::fromMapSettings( mapSettings ), vectorLayer, renderer, zMin, zMax, tilingSettings.maximumChunkFeatures() ),
295 true
296 )
297{
298 onTerrainElevationOffsetChanged();
299 setShowBoundingBoxes( tilingSettings.showBoundingBoxes() );
300}
301
302QgsCategorizedChunkedEntity::~QgsCategorizedChunkedEntity()
303{
304 // cancel / wait for jobs
305 cancelActiveJobs();
306}
307
308// if the AltitudeClamping is `Absolute`, do not apply the offset
309bool QgsCategorizedChunkedEntity::applyTerrainOffset() const
310{
311 QgsCategorizedChunkLoader *loader = static_cast<QgsCategorizedChunkLoader *>( mChunkLoader );
312 if ( loader )
313 {
314 for ( const auto &category : *loader->mCategories )
315 {
316 const QgsAbstract3DSymbol *symbol = category.symbol();
317 if ( category.symbol() )
318 {
319 QString symbolType = symbol->type();
320 if ( symbolType == "line" )
321 {
322 const QgsLine3DSymbol *lineSymbol = static_cast<const QgsLine3DSymbol *>( symbol );
323 if ( lineSymbol && lineSymbol->altitudeClamping() == Qgis::AltitudeClamping::Absolute )
324 {
325 return false;
326 }
327 }
328 else if ( symbolType == "point" )
329 {
330 const QgsPoint3DSymbol *pointSymbol = static_cast<const QgsPoint3DSymbol *>( symbol );
331 if ( pointSymbol && pointSymbol->altitudeClamping() == Qgis::AltitudeClamping::Absolute )
332 {
333 return false;
334 }
335 }
336 else if ( symbolType == "polygon" )
337 {
338 const QgsPolygon3DSymbol *polygonSymbol = static_cast<const QgsPolygon3DSymbol *>( symbol );
339 if ( polygonSymbol && polygonSymbol->altitudeClamping() == Qgis::AltitudeClamping::Absolute )
340 {
341 return false;
342 }
343 }
344 else
345 {
346 QgsDebugMsgLevel( u"QgsRuleBasedChunkedEntityChunkedEntity::applyTerrainOffset, unhandled symbol type %1"_s.arg( symbolType ), 2 );
347 }
348 }
349 }
350 }
351
352 return true;
353}
354
@ Absolute
Elevation is taken directly from feature and is independent of terrain height (final elevation = feat...
Definition qgis.h:4246
@ Geocentric
Geocentric CRS.
Definition qgis.h:2516
Definition of the world.
Rendering context for preparation of 3D entities.
QgsCoordinateReferenceSystem crs() const
Returns the coordinate reference system used in the 3D scene.
QgsRectangle extent() const
Returns the 3D scene's 2D extent in the 3D scene's CRS.
Represents an individual category (class) from a QgsCategorized3DRenderer.
Abstract base class for 3D symbols that are used by VectorLayer3DRenderer objects.
virtual QString type() const =0
Returns identifier of symbol type. Each 3D symbol implementation should return a different type.
static Qgs3DSymbolRegistry * symbol3DRegistry()
Returns registry of available 3D symbols.
A vector of attributes.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
void setZMinimum(double z)
Sets the minimum z value.
Definition qgsbox3d.cpp:94
void setZMaximum(double z)
Sets the maximum z value.
Definition qgsbox3d.cpp:99
void grow(double delta)
Grows the box in place by the specified amount in all dimensions.
Definition qgsbox3d.cpp:302
static QString buildCategorizedFilter(const QString &attributeName, const QgsFields &fields, const Categories &categories)
Builds a filter expression from categories.
Qgis::CrsType type() const
Returns the type of the CRS.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFields(const QgsFields &fields)
Convenience function for setting a fields for the context.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
Handles parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature's geometries.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsAttributes attributes
Definition qgsfeature.h:64
3D symbol that draws linestring geometries as planar polygons (created from lines using a buffer with...
Qgis::AltitudeClamping altitudeClamping() const
Returns method that determines altitude (whether to clamp to feature to terrain).
3D symbol that draws point geometries as 3D objects using one of the predefined shapes.
Qgis::AltitudeClamping altitudeClamping() const
Returns method that determines altitude (whether to clamp to feature to terrain).
3D symbol that draws polygon geometries as planar polygons, optionally extruded (with added walls).
Qgis::AltitudeClamping altitudeClamping() const
Returns method that determines altitude (whether to clamp to feature to terrain).
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Defines configuration of how a vector layer gets tiled for 3D rendering.
static double tileGeometryErrorRatio()
This is the ratio of tile's largest size to geometry error and is used when setting the root tile's e...
bool showBoundingBoxes() const
Returns whether to display bounding boxes of entity's tiles (for debugging).
static double maximumLeafExtent()
This is the maximum width or height a tile can have and still be considered a leaf node.
Represents a vector layer which manages a vector based dataset.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
#define QGIS_CHECK_MAIN_THREAD_ACCESS