QGIS API Documentation 4.3.0-Master (b46232e4f9a)
Loading...
Searching...
No Matches
qgsvectorlayerchunkloader_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectorlayerchunkloader_p.cpp
3 --------------------------------------
4 Date : July 2019
5 Copyright : (C) 2019 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 "qgs3dsymbolregistry.h"
19#include "qgs3dutils.h"
20#include "qgsabstract3dsymbol.h"
23#include "qgsapplication.h"
24#include "qgschunknode.h"
25#include "qgseventtracing.h"
26#include "qgsexception.h"
29#include "qgsgeometry.h"
30#include "qgsglobeutils_p.h"
31#include "qgsline3dsymbol.h"
32#include "qgslogger.h"
33#include "qgspoint3dsymbol.h"
34#include "qgspolygon3dsymbol.h"
35#include "qgsvectorlayer.h"
37#include "qgswkbtypes.h"
38
39#include <QString>
40#include <Qt3DCore/QTransform>
41#include <Qt3DRender/QGeometryRenderer>
42#include <QtConcurrentRun>
43
44#include "moc_qgsvectorlayerchunkloader_p.cpp"
45
46using namespace Qt::StringLiterals;
47
49
50
51QgsVectorLayerChunkLoader::QgsVectorLayerChunkLoader( const QgsVectorLayerChunkLoaderFactory *factory, QgsChunkNode *node )
52 : QgsChunkLoader( node )
53 , mFactory( factory )
54 , mRenderContext( factory->mRenderContext )
55 , mSource( new QgsVectorLayerFeatureSource( factory->mLayer ) )
56{}
57
58void QgsVectorLayerChunkLoader::start()
59{
60 QgsChunkNode *node = chunk();
61
62 QgsVectorLayer *layer = mFactory->mLayer;
63 mLayerName = mFactory->mLayer->name();
64
65 QgsFeature3DHandler *handler = QgsApplication::symbol3DRegistry()->createHandlerForSymbol( layer, mFactory->mSymbol.get() );
66 if ( !handler )
67 {
68 QgsDebugError( u"Unknown 3D symbol type for vector layer: "_s + mFactory->mSymbol->type() );
69 return;
70 }
71 mHandler.reset( handler );
72
73 QgsExpressionContext exprContext;
75 exprContext.setFields( layer->fields() );
76 mRenderContext.setExpressionContext( exprContext );
77
78 QSet<QString> attributeNames;
79 if ( !mHandler->prepare( mRenderContext, attributeNames, node->box3D() ) )
80 {
81 QgsDebugError( u"Failed to prepare 3D feature handler!"_s );
82 return;
83 }
84
85 // build the feature request
86 // only a subset of data to be queried
88 req.setSubsetOfAttributes( attributeNames, layer->fields() );
89
90 QgsCoordinateTransform layerToRenderCrs;
91 if ( mFactory->mIsGeocentric )
92 {
93 layerToRenderCrs = QgsCoordinateTransform( layer->crs3D(), mRenderContext.crs(), mRenderContext.transformContext() );
94 layerToRenderCrs.setBallparkTransformsAreAppropriate( true );
95
96 QgsRectangle filterRect;
97 if ( layer->crs().type() == Qgis::CrsType::Geocentric )
98 {
99 try
100 {
101 filterRect = layerToRenderCrs.transformBox3D( node->box3D(), Qgis::TransformDirection::Reverse ).toRectangle();
102 }
103 catch ( const QgsCsException & )
104 {
105 QgsDebugError( u"Error transforming node box3D to layer CRS"_s );
106 }
107 }
108 else
109 {
110 const QgsRectangle lonLatRect = QgsGlobeUtils::nodeIdToLonLatRect( node->tileId() );
111 filterRect = Qgs3DUtils::tryReprojectExtent2D( lonLatRect, mFactory->mCrsToLatLon.destinationCrs(), layer->crs(), mRenderContext.transformContext() );
112 }
113 req.setFilterRect( filterRect );
114 }
115 else
116 {
117 req.setCoordinateTransform( QgsCoordinateTransform( layer->crs3D(), mRenderContext.crs(), mRenderContext.transformContext() ) );
118 req.setFilterRect( node->box3D().toRectangle() );
119 }
120
121 //
122 // this will be run in a background thread
123 //
124 mFutureWatcher = new QFutureWatcher<void>( this );
125
126 connect( mFutureWatcher, &QFutureWatcher<void>::finished, this, [this] {
127 if ( !mCanceled )
128 mFactory->mNodesAreLeafs[mNode->tileId().text()] = mNodeIsLeaf;
129 } );
130
131 connect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
132
133 const bool isGeocentric = mFactory->mIsGeocentric;
134 const QFuture<void> future = QtConcurrent::run( [req = std::move( req ), layerToRenderCrs, isGeocentric, this] {
135 const QgsScopedEvent e( u"3D"_s, u"VL chunk load"_s );
136
137 QgsFeature f;
138 QgsFeatureIterator fi = mSource->getFeatures( req );
139 int featureCount = 0;
140 bool featureLimitReached = false;
141 while ( fi.nextFeature( f ) )
142 {
143 if ( mCanceled )
144 return;
145
146 if ( ++featureCount > mFactory->mMaxFeatures )
147 {
148 featureLimitReached = true;
149 break;
150 }
151
152 if ( isGeocentric )
153 {
154 QgsGeometry g = f.geometry();
155 if ( !g.constGet()->is3D() )
156 g.get()->addZValue( 0 );
157
158 try
159 {
160 g.transform( layerToRenderCrs, Qgis::TransformDirection::Forward, true );
161 }
162 catch ( QgsCsException &e )
163 {
164 QgsDebugError( u"Error transforming feature %1 geometry to globe CRS: %2"_s.arg( f.id() ).arg( e.what() ) );
165 continue;
166 }
167 f.setGeometry( g );
168 }
169
170 mRenderContext.expressionContext().setFeature( f );
171 mHandler->processFeature( f, mRenderContext );
172 }
173
174 if ( !featureLimitReached )
175 {
176 QgsDebugMsgLevel( u"All features fetched for node: %1"_s.arg( mNode->tileId().text() ), 3 );
177
178 if ( featureCount == 0 || std::max<double>( mNode->box3D().width(), mNode->box3D().height() ) < QgsVectorLayer3DTilingSettings::maximumLeafExtent() )
179 mNodeIsLeaf = true;
180 }
181 } );
182
183 // emit finished() as soon as the handler is populated with features
184 mFutureWatcher->setFuture( future );
185}
186
187QgsVectorLayerChunkLoader::~QgsVectorLayerChunkLoader()
188{
189 if ( mFutureWatcher && !mFutureWatcher->isFinished() )
190 {
191 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
192 mFutureWatcher->waitForFinished();
193 }
194}
195
196void QgsVectorLayerChunkLoader::cancel()
197{
198 mCanceled = true;
199}
200
201Qt3DCore::QEntity *QgsVectorLayerChunkLoader::createEntity( Qt3DCore::QEntity *parent )
202{
203 if ( mHandler->featureCount() == 0 )
204 {
205 // an empty node, so we return no entity. This tags the node as having no data and effectively removes it.
206 // we just make sure first that its initial estimated vertical range does not affect its parents' bboxes calculation
207 mNode->setExactBox3D( QgsBox3D() );
208 mNode->updateParentBoundingBoxesRecursively();
209 return nullptr;
210 }
211
212 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity( parent );
213 entity->setObjectName( mLayerName + "_" + mNode->tileId().text() );
214 mHandler->finalize( entity, mRenderContext );
215
216 // fix the vertical range of the node from the estimated vertical range to the true range
217 if ( mHandler->zMinimum() != std::numeric_limits<float>::max() && mHandler->zMaximum() != std::numeric_limits<float>::lowest() )
218 {
219 QgsBox3D box = mNode->box3D();
220 box.setZMinimum( mHandler->zMinimum() );
221 box.setZMaximum( mHandler->zMaximum() );
222 mNode->setExactBox3D( box );
223 mNode->updateParentBoundingBoxesRecursively();
224 }
225
226 return entity;
227}
228
229
231
232
233QgsVectorLayerChunkLoaderFactory::QgsVectorLayerChunkLoaderFactory( const Qgs3DRenderContext &context, QgsVectorLayer *vl, QgsAbstract3DSymbol *symbol, double zMin, double zMax, int maxFeatures )
234 : mRenderContext( context )
235 , mLayer( vl )
236 , mSymbol( symbol->clone() )
237 , mMaxFeatures( maxFeatures )
238{
239 if ( context.crs().type() == Qgis::CrsType::Geocentric )
240 {
241 // TODO: add support for handling of vector layers (other than points)
242 if ( QgsWkbTypes::geometryType( mLayer->wkbType() ) != Qgis::GeometryType::Point )
243 {
244 // (we're using dummy quadtree here to make sure the empty extent does not break the scene completely)
245 QgsDebugError( u"Non-point vector layers in globe scenes are not supported yet!"_s );
246 setupQuadtree( QgsBox3D( -7e6, -7e6, -7e6, 7e6, 7e6, 7e6 ), -1, 3 );
247 return;
248 }
249
250 mIsGeocentric = true;
251
252 const QgsCoordinateReferenceSystem geographicCrs = context.crs().toGeographicCrs();
253 mCrsToLatLon = QgsCoordinateTransform( context.crs(), geographicCrs, context.transformContext() );
254 mCrsToLatLon.setBallparkTransformsAreAppropriate( true );
255
256 try
257 {
258 mRadius = QgsGlobeUtils::ellipsoidRadius( mCrsToLatLon );
259 }
260 catch ( QgsCsException &e )
261 {
262 QgsDebugError( u"Error transforming globe ellipsoid extent: %1"_s.arg( e.what() ) );
263 setupQuadtree( QgsBox3D( -7e6, -7e6, -7e6, 7e6, 7e6, 7e6 ), -1, 3 );
264 return;
265 }
266
267 // choose the smaller root extent between context and mLayer ones
268 QgsRectangle layerExtentLonLat;
269 if ( mLayer->crs().type() == Qgis::CrsType::Geocentric )
270 {
271 QgsCoordinateTransform layerToLatLon( mLayer->crs(), geographicCrs, context.transformContext() );
272 layerToLatLon.setBallparkTransformsAreAppropriate( true );
273 try
274 {
275 layerExtentLonLat = layerToLatLon.transformBox3D( mLayer->extent3D() ).toRectangle();
276 }
277 catch ( const QgsCsException &e )
278 {
279 QgsDebugError( u"Error transforming layer extent to lat/lon: %1"_s.arg( e.what() ) );
280 }
281 }
282 else
283 {
284 layerExtentLonLat = Qgs3DUtils::tryReprojectExtent2D( mLayer->extent(), mLayer->crs(), geographicCrs, context.transformContext() );
285 }
286
287 if ( layerExtentLonLat.isValid() )
288 {
289 // add small padding to avoid clipping of point features located at the edge of the bounding box
290 layerExtentLonLat.grow( 0.01 );
291 layerExtentLonLat = layerExtentLonLat.intersect( QgsRectangle( -180, -90, 180, 90 ) );
292 }
293
294 mRootNodeId = QgsGlobeUtils::findSmallestIdContainingExtent( layerExtentLonLat );
295
296 QgsBox3D rootBox3D;
297 if ( mRootNodeId.d == 0 )
298 rootBox3D = QgsBox3D( -mRadius.x(), -mRadius.y(), -mRadius.z(), mRadius.x(), mRadius.y(), mRadius.z() );
299 else if ( mRootNodeId.d == 1 )
300 rootBox3D = mRootNodeId.x == 1 ? QgsBox3D( -mRadius.x(), 0, -mRadius.z(), mRadius.x(), mRadius.y(), mRadius.z() )
301 : QgsBox3D( -mRadius.x(), -mRadius.y(), -mRadius.z(), mRadius.x(), 0, mRadius.z() );
302 else
303 rootBox3D = QgsGlobeUtils::nodeIdToBox3D( mRootNodeId, mCrsToLatLon );
304
305 const float rootError = static_cast<float>( std::max<double>( rootBox3D.width(), rootBox3D.height() ) * QgsVectorLayer3DTilingSettings::tileGeometryErrorRatio() );
306 setupQuadtree( rootBox3D, rootError );
307 return;
308 }
309
310 QgsRectangle extent = context.extent();
311 const QgsRectangle layerExtentInMapCrs = Qgs3DUtils::tryReprojectExtent2D( mLayer->extent(), mLayer->crs(), context.crs(), context.transformContext() );
312 if ( layerExtentInMapCrs.isValid() )
313 {
314 extent = context.extent().intersect( layerExtentInMapCrs );
315 }
316 if ( extent.isValid() )
317 {
318 QgsBox3D rootBox3D( extent, zMin, zMax );
319 rootBox3D.grow( 1.0 );
320
321 const float rootError = static_cast<float>( std::max<double>( rootBox3D.width(), rootBox3D.height() ) * QgsVectorLayer3DTilingSettings::tileGeometryErrorRatio() );
322 setupQuadtree( rootBox3D, rootError );
323 }
324}
325
326QgsChunkLoader *QgsVectorLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
327{
328 return new QgsVectorLayerChunkLoader( this, node );
329}
330
331QgsChunkNode *QgsVectorLayerChunkLoaderFactory::createRootNode() const
332{
333 if ( mIsGeocentric )
334 return new QgsChunkNode( mRootNodeId, mRootBox3D, mRootError );
335
336 return QgsQuadtreeChunkLoaderFactory::createRootNode();
337}
338
339bool QgsVectorLayerChunkLoaderFactory::canCreateChildren( QgsChunkNode *node )
340{
341 return mNodesAreLeafs.contains( node->tileId().text() );
342}
343
344QVector<QgsChunkNode *> QgsVectorLayerChunkLoaderFactory::createChildren( QgsChunkNode *node ) const
345{
346 if ( mNodesAreLeafs.value( node->tileId().text(), false ) )
347 return {};
348
349 if ( !mIsGeocentric )
350 return QgsQuadtreeChunkLoaderFactory::createChildren( node );
351
352 QVector<QgsChunkNode *> children;
353 if ( mMaxLevel != -1 && node->level() >= mMaxLevel )
354 return children;
355
356 const QgsChunkNodeId nodeId = node->tileId();
357 const float childError = node->error() / 2;
358
359 if ( nodeId.d == 0 )
360 {
361 const QgsChunkNodeId westId( 1, 0, 0 );
362 const QgsChunkNodeId eastId( 1, 1, 0 );
363 children << new QgsChunkNode( westId, QgsBox3D( -mRadius.x(), -mRadius.y(), -mRadius.z(), mRadius.x(), 0, mRadius.z() ), childError, node );
364 children << new QgsChunkNode( eastId, QgsBox3D( -mRadius.x(), 0, -mRadius.z(), mRadius.x(), mRadius.y(), mRadius.z() ), childError, node );
365 return children;
366 }
367
368 for ( int i = 0; i < 4; ++i )
369 {
370 const int dx = i & 1, dy = !!( i & 2 );
371 const QgsChunkNodeId childId( nodeId.d + 1, nodeId.x * 2 + dx, nodeId.y * 2 + dy );
372 children << new QgsChunkNode( childId, QgsGlobeUtils::nodeIdToBox3D( childId, mCrsToLatLon ), childError, node );
373 }
374 return children;
375}
376
377
379
380
381QgsVectorLayerChunkedEntity::QgsVectorLayerChunkedEntity(
382 Qgs3DMapSettings *map, QgsVectorLayer *vl, double zMin, double zMax, const QgsVectorLayer3DTilingSettings &tilingSettings, QgsAbstract3DSymbol *symbol
383)
384 : QgsAbstractFeatureBasedChunkedEntity( map, 3, new QgsVectorLayerChunkLoaderFactory( Qgs3DRenderContext::fromMapSettings( map ), vl, symbol, zMin, zMax, tilingSettings.maximumChunkFeatures() ), true )
385{
386 onTerrainElevationOffsetChanged();
387 setShowBoundingBoxes( tilingSettings.showBoundingBoxes() );
388}
389
390QgsVectorLayerChunkedEntity::~QgsVectorLayerChunkedEntity()
391{
392 // cancel / wait for jobs
393 cancelActiveJobs();
394}
395
396// if the AltitudeClamping is `Absolute`, do not apply the offset
397bool QgsVectorLayerChunkedEntity::applyTerrainOffset() const
398{
399 QgsVectorLayerChunkLoaderFactory *loaderFactory = static_cast<QgsVectorLayerChunkLoaderFactory *>( mChunkLoaderFactory );
400 if ( loaderFactory )
401 {
402 QString symbolType = loaderFactory->mSymbol.get()->type();
403 if ( symbolType == "line" )
404 {
405 QgsLine3DSymbol *lineSymbol = static_cast<QgsLine3DSymbol *>( loaderFactory->mSymbol.get() );
406 if ( lineSymbol && lineSymbol->altitudeClamping() == Qgis::AltitudeClamping::Absolute )
407 {
408 return false;
409 }
410 }
411 else if ( symbolType == "point" )
412 {
413 QgsPoint3DSymbol *pointSymbol = static_cast<QgsPoint3DSymbol *>( loaderFactory->mSymbol.get() );
414 if ( pointSymbol && pointSymbol->altitudeClamping() == Qgis::AltitudeClamping::Absolute )
415 {
416 return false;
417 }
418 }
419 else if ( symbolType == "polygon" )
420 {
421 QgsPolygon3DSymbol *polygonSymbol = static_cast<QgsPolygon3DSymbol *>( loaderFactory->mSymbol.get() );
422 if ( polygonSymbol && polygonSymbol->altitudeClamping() == Qgis::AltitudeClamping::Absolute )
423 {
424 return false;
425 }
426 }
427 else
428 {
429 QgsDebugMsgLevel( u"QgsVectorLayerChunkedEntity::applyTerrainOffset, unhandled symbol type %1"_s.arg( symbolType ), 2 );
430 }
431 }
432
433 return true;
434}
435
@ 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
@ Forward
Forward transform (from source to destination).
Definition qgis.h:2883
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2884
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.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
QgsFeature3DHandler * createHandlerForSymbol(const QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol)
Creates a feature handler for a symbol, for the specified vector layer.
static QgsRectangle tryReprojectExtent2D(const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs1, const QgsCoordinateReferenceSystem &crs2, const QgsCoordinateTransformContext &context)
Reprojects extent from crs1 to crs2 coordinate reference system with context context.
Abstract base class for 3D symbols that are used by VectorLayer3DRenderer objects.
virtual bool addZValue(double zValue=0)=0
Adds a z-dimension to the geometry, initialized to a preset value.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
static Qgs3DSymbolRegistry * symbol3DRegistry()
Returns registry of available 3D symbols.
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
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
Definition qgsbox3d.h:388
Represents a coordinate reference system (CRS).
QgsCoordinateReferenceSystem toGeographicCrs() const
Returns the geographic CRS associated with this CRS object.
Qgis::CrsType type() const
Returns the type of the CRS.
Handles coordinate transforms between two coordinate systems.
QgsBox3D transformBox3D(const QgsBox3D &box, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms a 3D box from the source CRS to the destination CRS.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
Custom exception class for Coordinate Reference System related exceptions.
QString what() const
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.
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 & setCoordinateTransform(const QgsCoordinateTransform &transform)
Sets the coordinate transform which will be used to transform the feature's geometries.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
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
QgsFeatureId id
Definition qgsfeature.h:63
QgsGeometry geometry
Definition qgsfeature.h:66
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
A geometry is the spatial representation of a feature.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
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).
QString name
Definition qgsmaplayer.h:87
QgsCoordinateReferenceSystem crs3D
Definition qgsmaplayer.h:92
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
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.
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
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.
Partial snapshot of vector layer's state (only the members necessary for access to features).
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