QGIS API Documentation 3.41.0-Master (57ec4277f5e)
Loading...
Searching...
No Matches
qgspointcloudlayerchunkloader_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspointcloudlayerchunkloader_p.cpp
3 --------------------------------------
4 Date : October 2020
5 Copyright : (C) 2020 by Peter Petrik
6 Email : zilolv 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#include "moc_qgspointcloudlayerchunkloader_p.cpp"
18
19#include "qgs3dutils.h"
20#include "qgsbox3d.h"
22#include "qgschunknode.h"
23#include "qgslogger.h"
24#include "qgspointcloudindex.h"
26#include "qgseventtracing.h"
27
28
33
34#include <QtConcurrent>
35#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
36#include <Qt3DRender/QAttribute>
37#else
38#include <Qt3DCore/QAttribute>
39#endif
40#include <Qt3DRender/QTechnique>
41#include <Qt3DRender/QShaderProgram>
42#include <Qt3DRender/QGraphicsApiFilter>
43#include <QPointSize>
44
46
47
49
50QgsPointCloudLayerChunkLoader::QgsPointCloudLayerChunkLoader( const QgsPointCloudLayerChunkLoaderFactory *factory, QgsChunkNode *node, std::unique_ptr<QgsPointCloud3DSymbol> symbol, const QgsCoordinateTransform &coordinateTransform, double zValueScale, double zValueOffset )
51 : QgsChunkLoader( node )
52 , mFactory( factory )
53 , mContext( factory->mRenderContext, coordinateTransform, std::move( symbol ), zValueScale, zValueOffset )
54{
55 QgsPointCloudIndex *pc = mFactory->mPointCloudIndex;
56 mContext.setAttributes( pc->attributes() );
57
58 const QgsChunkNodeId nodeId = node->tileId();
59 const QgsPointCloudNodeId pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z );
60
61 Q_ASSERT( pc->hasNode( pcNode ) );
62
63 QgsDebugMsgLevel( QStringLiteral( "loading entity %1" ).arg( node->tileId().text() ), 2 );
64
65 if ( mContext.symbol()->symbolType() == QLatin1String( "single-color" ) )
66 mHandler.reset( new QgsSingleColorPointCloud3DSymbolHandler() );
67 else if ( mContext.symbol()->symbolType() == QLatin1String( "color-ramp" ) )
68 mHandler.reset( new QgsColorRampPointCloud3DSymbolHandler() );
69 else if ( mContext.symbol()->symbolType() == QLatin1String( "rgb" ) )
70 mHandler.reset( new QgsRGBPointCloud3DSymbolHandler() );
71 else if ( mContext.symbol()->symbolType() == QLatin1String( "classification" ) )
72 {
73 mHandler.reset( new QgsClassificationPointCloud3DSymbolHandler() );
74 const QgsClassificationPointCloud3DSymbol *classificationSymbol = dynamic_cast<const QgsClassificationPointCloud3DSymbol *>( mContext.symbol() );
75 mContext.setFilteredOutCategories( classificationSymbol->getFilteredOutCategories() );
76 }
77
78 //
79 // this will be run in a background thread
80 //
81 mFutureWatcher = new QFutureWatcher<void>( this );
82 connect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
83
84 const QgsBox3D box3D = node->box3D();
85 const QFuture<void> future = QtConcurrent::run( [pc, pcNode, box3D, this] {
86 const QgsEventTracing::ScopedEvent e( QStringLiteral( "3D" ), QStringLiteral( "PC chunk load" ) );
87
88 if ( mContext.isCanceled() )
89 {
90 QgsDebugMsgLevel( QStringLiteral( "canceled" ), 2 );
91 return;
92 }
93
94 mHandler->processNode( pc, pcNode, mContext );
95
96 if ( mContext.isCanceled() )
97 {
98 QgsDebugMsgLevel( QStringLiteral( "canceled" ), 2 );
99 return;
100 }
101
102 if ( mContext.symbol()->renderAsTriangles() )
103 mHandler->triangulate( pc, pcNode, mContext, box3D );
104 } );
105
106 // emit finished() as soon as the handler is populated with features
107 mFutureWatcher->setFuture( future );
108}
109
110QgsPointCloudLayerChunkLoader::~QgsPointCloudLayerChunkLoader()
111{
112 if ( mFutureWatcher && !mFutureWatcher->isFinished() )
113 {
114 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
115 mContext.cancelRendering();
116 mFutureWatcher->waitForFinished();
117 }
118}
119
120void QgsPointCloudLayerChunkLoader::cancel()
121{
122 mContext.cancelRendering();
123}
124
125Qt3DCore::QEntity *QgsPointCloudLayerChunkLoader::createEntity( Qt3DCore::QEntity *parent )
126{
127 QgsPointCloudIndex *pc = mFactory->mPointCloudIndex;
128 const QgsChunkNodeId nodeId = mNode->tileId();
129 const QgsPointCloudNodeId pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z );
130 Q_ASSERT( pc->hasNode( pcNode ) );
131
132 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity( parent );
133 mHandler->finalize( entity, mContext );
134 return entity;
135}
136
138
139
140QgsPointCloudLayerChunkLoaderFactory::QgsPointCloudLayerChunkLoaderFactory( const Qgs3DRenderContext &context, const QgsCoordinateTransform &coordinateTransform, QgsPointCloudIndex *pc, QgsPointCloud3DSymbol *symbol, double zValueScale, double zValueOffset, int pointBudget )
141 : mRenderContext( context )
142 , mCoordinateTransform( coordinateTransform )
143 , mPointCloudIndex( pc )
144 , mZValueScale( zValueScale )
145 , mZValueOffset( zValueOffset )
146 , mPointBudget( pointBudget )
147{
148 mSymbol.reset( symbol );
149
150 try
151 {
152 mExtent = mCoordinateTransform.transformBoundingBox( mRenderContext.extent(), Qgis::TransformDirection::Reverse );
153 }
154 catch ( const QgsCsException & )
155 {
156 // bad luck, can't reproject for some reason
157 QgsDebugError( QStringLiteral( "Transformation of extent failed." ) );
158 }
159}
160
161QgsChunkLoader *QgsPointCloudLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
162{
163 const QgsChunkNodeId id = node->tileId();
164
165 Q_ASSERT( mPointCloudIndex->hasNode( QgsPointCloudNodeId( id.d, id.x, id.y, id.z ) ) );
166 QgsPointCloud3DSymbol *symbol = static_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() );
167 return new QgsPointCloudLayerChunkLoader( this, node, std::unique_ptr<QgsPointCloud3DSymbol>( symbol ), mCoordinateTransform, mZValueScale, mZValueOffset );
168}
169
170int QgsPointCloudLayerChunkLoaderFactory::primitivesCount( QgsChunkNode *node ) const
171{
172 const QgsChunkNodeId id = node->tileId();
173 const QgsPointCloudNodeId n( id.d, id.x, id.y, id.z );
174 Q_ASSERT( mPointCloudIndex->hasNode( n ) );
175 return mPointCloudIndex->getNode( n ).pointCount();
176}
177
178
179static QgsBox3D nodeBoundsToBox3D( QgsBox3D nodeBounds, const QgsCoordinateTransform &coordinateTransform, double zValueOffset, double zValueScale )
180{
181 QgsVector3D extentMin3D( nodeBounds.xMinimum(), nodeBounds.yMinimum(), nodeBounds.zMinimum() * zValueScale + zValueOffset );
182 QgsVector3D extentMax3D( nodeBounds.xMaximum(), nodeBounds.yMaximum(), nodeBounds.zMaximum() * zValueScale + zValueOffset );
183 QgsCoordinateTransform extentTransform = coordinateTransform;
184 extentTransform.setBallparkTransformsAreAppropriate( true );
185 try
186 {
187 extentMin3D = extentTransform.transform( extentMin3D );
188 extentMax3D = extentTransform.transform( extentMax3D );
189 }
190 catch ( QgsCsException & )
191 {
192 QgsDebugError( QStringLiteral( "Error transforming node bounds coordinate" ) );
193 }
194 return QgsBox3D( extentMin3D.x(), extentMin3D.y(), extentMin3D.z(), extentMax3D.x(), extentMax3D.y(), extentMax3D.z() );
195}
196
197
198QgsChunkNode *QgsPointCloudLayerChunkLoaderFactory::createRootNode() const
199{
200 const QgsPointCloudNode pcNode = mPointCloudIndex->getNode( mPointCloudIndex->root() );
201 const QgsBox3D rootNodeBounds = pcNode.bounds();
202 QgsBox3D rootNodeBox3D = nodeBoundsToBox3D( rootNodeBounds, mCoordinateTransform, mZValueOffset, mZValueScale );
203
204 const float error = pcNode.error();
205 QgsChunkNode *node = new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, error );
206 node->setRefinementProcess( mSymbol->renderAsTriangles() ? Qgis::TileRefinementProcess::Replacement : Qgis::TileRefinementProcess::Additive );
207 return node;
208}
209
210QVector<QgsChunkNode *> QgsPointCloudLayerChunkLoaderFactory::createChildren( QgsChunkNode *node ) const
211{
212 QVector<QgsChunkNode *> children;
213 const QgsChunkNodeId nodeId = node->tileId();
214 const float childError = node->error() / 2;
215
216 for ( int i = 0; i < 8; ++i )
217 {
218 int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
219 const QgsChunkNodeId childId( nodeId.d + 1, nodeId.x * 2 + dx, nodeId.y * 2 + dy, nodeId.z * 2 + dz );
220 const QgsPointCloudNodeId childPcId( childId.d, childId.x, childId.y, childId.z );
221 if ( !mPointCloudIndex->hasNode( childPcId ) )
222 continue;
223 const QgsPointCloudNode childNode = mPointCloudIndex->getNode( childPcId );
224 const QgsBox3D childBounds = childNode.bounds();
225 if ( !mExtent.isEmpty() && !childBounds.intersects( mExtent ) )
226 continue;
227
228 QgsBox3D childBox3D = nodeBoundsToBox3D( childBounds, mCoordinateTransform, mZValueOffset, mZValueScale );
229
230 QgsChunkNode *child = new QgsChunkNode( childId, childBox3D, childError, node );
231 child->setRefinementProcess( mSymbol->renderAsTriangles() ? Qgis::TileRefinementProcess::Replacement : Qgis::TileRefinementProcess::Additive );
232 children << child;
233 }
234 return children;
235}
236
238
239
240QgsPointCloudLayerChunkedEntity::QgsPointCloudLayerChunkedEntity( Qgs3DMapSettings *map, QgsPointCloudIndex *pc, const QgsCoordinateTransform &coordinateTransform, QgsPointCloud3DSymbol *symbol, float maximumScreenSpaceError, bool showBoundingBoxes, double zValueScale, double zValueOffset, int pointBudget )
241 : QgsChunkedEntity( map, maximumScreenSpaceError, new QgsPointCloudLayerChunkLoaderFactory( Qgs3DRenderContext::fromMapSettings( map ), coordinateTransform, pc, symbol, zValueScale, zValueOffset, pointBudget ), true, pointBudget )
242{
243 setShowBoundingBoxes( showBoundingBoxes );
244}
245
246QgsPointCloudLayerChunkedEntity::~QgsPointCloudLayerChunkedEntity()
247{
248 // cancel / wait for jobs
249 cancelActiveJobs();
250}
251
252QVector<QgsRayCastingUtils::RayHit> QgsPointCloudLayerChunkedEntity::rayIntersection( const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context ) const
253{
254 QVector<QgsRayCastingUtils::RayHit> result;
255 QgsPointCloudLayerChunkLoaderFactory *factory = static_cast<QgsPointCloudLayerChunkLoaderFactory *>( mChunkLoaderFactory );
256
257 // transform ray
258 const QgsVector3D rayOriginMapCoords = factory->mRenderContext.worldToMapCoordinates( ray.origin() );
259 const QgsVector3D pointMapCoords = factory->mRenderContext.worldToMapCoordinates( ray.origin() + ray.origin().length() * ray.direction().normalized() );
260 QgsVector3D rayDirectionMapCoords = pointMapCoords - rayOriginMapCoords;
261 rayDirectionMapCoords.normalize();
262
263 const int screenSizePx = std::max( context.screenSize.height(), context.screenSize.width() );
264
265 const QgsPointCloud3DSymbol *symbol = factory->mSymbol.get();
266 // Symbol can be null in case of no rendering enabled
267 if ( !symbol )
268 return result;
269 const double pointSize = symbol->pointSize();
270
271 // We're using the angle as a tolerance, effectively meaning we're fetching points intersecting a cone.
272 // This may be revisited to use a cylinder instead, if the balance between near/far points does not scale
273 // well with different point sizes, screen sizes and fov values.
274 const double limitAngle = 2. * pointSize / screenSizePx * factory->mRenderContext.fieldOfView();
275
276 // adjust ray to elevation properties
277 const QgsVector3D adjustedRayOrigin = QgsVector3D( rayOriginMapCoords.x(), rayOriginMapCoords.y(), ( rayOriginMapCoords.z() - factory->mZValueOffset ) / factory->mZValueScale );
278 QgsVector3D adjustedRayDirection = QgsVector3D( rayDirectionMapCoords.x(), rayDirectionMapCoords.y(), rayDirectionMapCoords.z() / factory->mZValueScale );
279 adjustedRayDirection.normalize();
280
281 QgsPointCloudIndex *index = factory->mPointCloudIndex;
282
283 const QgsPointCloudAttributeCollection attributeCollection = index->attributes();
284 QgsPointCloudRequest request;
285 request.setAttributes( attributeCollection );
286
287 double minDist = -1.;
288 const QList<QgsChunkNode *> activeNodes = this->activeNodes();
289 for ( QgsChunkNode *node : activeNodes )
290 {
291 const QgsChunkNodeId id = node->tileId();
292 const QgsPointCloudNodeId n( id.d, id.x, id.y, id.z );
293
294 if ( !index->hasNode( n ) )
295 continue;
296
297 const QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() );
298 if ( !QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) )
299 continue;
300
301 std::unique_ptr<QgsPointCloudBlock> block( index->nodeData( n, request ) );
302 if ( !block )
303 continue;
304
305 const QgsVector3D blockScale = block->scale();
306 const QgsVector3D blockOffset = block->offset();
307
308 const char *ptr = block->data();
309 const QgsPointCloudAttributeCollection blockAttributes = block->attributes();
310 const std::size_t recordSize = blockAttributes.pointRecordSize();
311 int xOffset = 0, yOffset = 0, zOffset = 0;
312 const QgsPointCloudAttribute::DataType xType = blockAttributes.find( QStringLiteral( "X" ), xOffset )->type();
313 const QgsPointCloudAttribute::DataType yType = blockAttributes.find( QStringLiteral( "Y" ), yOffset )->type();
314 const QgsPointCloudAttribute::DataType zType = blockAttributes.find( QStringLiteral( "Z" ), zOffset )->type();
315 for ( int i = 0; i < block->pointCount(); ++i )
316 {
317 double x, y, z;
318 QgsPointCloudAttribute::getPointXYZ( ptr, i, recordSize, xOffset, xType, yOffset, yType, zOffset, zType, blockScale, blockOffset, x, y, z );
319 const QgsVector3D point( x, y, z );
320
321 // check whether point is in front of the ray
322 // similar to QgsRay3D::isInFront(), but using doubles
323 QgsVector3D vectorToPoint = point - adjustedRayOrigin;
324 vectorToPoint.normalize();
325 if ( QgsVector3D::dotProduct( vectorToPoint, adjustedRayDirection ) < 0.0 )
326 continue;
327
328 // calculate the angle between the point and the projected point
329 // similar to QgsRay3D::angleToPoint(), but using doubles
330 const QgsVector3D projPoint = adjustedRayOrigin + adjustedRayDirection * QgsVector3D::dotProduct( point - adjustedRayOrigin, adjustedRayDirection );
331 const QgsVector3D v1 = projPoint - adjustedRayOrigin;
332 const QgsVector3D v2 = point - projPoint;
333 double angle = std::atan2( v2.length(), v1.length() ) * 180 / M_PI;
334 if ( angle > limitAngle )
335 continue;
336
337 const double dist = rayOriginMapCoords.distance( point );
338
339 if ( minDist < 0 || dist < minDist )
340 {
341 minDist = dist;
342 }
343 else if ( context.singleResult )
344 {
345 continue;
346 }
347
348 // Note : applying elevation properties is done in fromPointCloudIdentificationToIdentifyResults
349 QVariantMap pointAttr = QgsPointCloudAttribute::getAttributeMap( ptr, i * recordSize, blockAttributes );
350 pointAttr[QStringLiteral( "X" )] = x;
351 pointAttr[QStringLiteral( "Y" )] = y;
352 pointAttr[QStringLiteral( "Z" )] = z;
353
354 const QgsVector3D worldPoint = factory->mRenderContext.mapToWorldCoordinates( point );
355 QgsRayCastingUtils::RayHit hit( dist, worldPoint.toVector3D(), FID_NULL, pointAttr );
356 if ( context.singleResult )
357 result.clear();
358 result.append( hit );
359 }
360 }
361 return result;
362}
The Qgis class provides global constants for use throughout the application.
Definition qgis.h:54
@ Replacement
When tile is refined then its children should be used in place of itself.
@ Reverse
Reverse/inverse transform (from destination to source)
static QgsAABB mapToWorldExtent(const QgsRectangle &extent, double zMin, double zMax, const QgsVector3D &mapOrigin)
Converts map extent to axis aligned bounding box in 3D world coordinates.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:43
double yMaximum() const
Returns the maximum y value.
Definition qgsbox3d.h:246
bool intersects(const QgsBox3D &other) const
Returns true if box intersects with another box.
Definition qgsbox3d.cpp:144
double xMinimum() const
Returns the minimum x value.
Definition qgsbox3d.h:211
double zMaximum() const
Returns the maximum z value.
Definition qgsbox3d.h:274
double xMaximum() const
Returns the maximum x value.
Definition qgsbox3d.h:218
double zMinimum() const
Returns the minimum z value.
Definition qgsbox3d.h:267
double yMinimum() const
Returns the minimum y value.
Definition qgsbox3d.h:239
QgsPointCloudCategoryList getFilteredOutCategories() const
Gets the list of categories of the classification that should not be rendered.
Class for doing transforms between two map coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
float pointSize() const
Returns the point size of the point cloud.
Collection of point cloud attributes.
int pointRecordSize() const
Returns total size of record.
const QgsPointCloudAttribute * find(const QString &attributeName, int &offset) const
Finds the attribute with the name.
QVector< QgsPointCloudAttribute > attributes() const
Returns all attributes.
DataType
Systems of unit measurement.
static void getPointXYZ(const char *ptr, int i, std::size_t pointRecordSize, int xOffset, QgsPointCloudAttribute::DataType xType, int yOffset, QgsPointCloudAttribute::DataType yType, int zOffset, QgsPointCloudAttribute::DataType zType, const QgsVector3D &indexScale, const QgsVector3D &indexOffset, double &x, double &y, double &z)
Retrieves the x, y, z values for the point at index i.
static QVariantMap getAttributeMap(const char *data, std::size_t recordOffset, const QgsPointCloudAttributeCollection &attributeCollection)
Retrieves all the attributes of a point.
DataType type() const
Returns the data type.
Represents a indexed point clouds data in octree.
virtual bool hasNode(const QgsPointCloudNodeId &n) const
Returns whether the octree contain given node.
void setAttributes(const QgsPointCloudAttributeCollection &attributes)
Sets native attributes of the data.
QgsPointCloudAttributeCollection attributes() const
Returns all attributes that are stored in the file.
virtual std::unique_ptr< QgsPointCloudBlock > nodeData(const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request)=0
Returns node data block.
Represents a indexed point cloud node's position in octree.
Keeps metadata for indexed point cloud node.
float error() const
Returns node's error in map units (used to determine in whether the node has enough detail for the cu...
QgsBox3D bounds() const
Returns node's bounding cube in CRS coords.
Point cloud data request.
void setAttributes(const QgsPointCloudAttributeCollection &attributes)
Set attributes filter in the request.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition qgsvector3d.h:31
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:50
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:52
QVector3D toVector3D() const
Converts the current object to QVector3D.
static double dotProduct(const QgsVector3D &v1, const QgsVector3D &v2)
Returns the dot product of two vectors.
double x() const
Returns X coordinate.
Definition qgsvector3d.h:48
void normalize()
Normalizes the current vector in place.
double length() const
Returns the length of the vector.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
#define FID_NULL
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
Helper struct to store ray casting parameters.
QSize screenSize
QSize of the 3d engine window.
bool singleResult
If set to true, only the closest point cloud hit will be returned (other entities always return only ...
Helper struct to store ray casting results.