QGIS API Documentation 3.41.0-Master (3440c17df1d)
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"
21#include "qgschunknode.h"
22#include "qgslogger.h"
23#include "qgspointcloudindex.h"
25#include "qgseventtracing.h"
26
27
32
33#include <QtConcurrent>
34#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
35#include <Qt3DRender/QAttribute>
36#else
37#include <Qt3DCore/QAttribute>
38#endif
39#include <Qt3DRender/QTechnique>
40#include <Qt3DRender/QShaderProgram>
41#include <Qt3DRender/QGraphicsApiFilter>
42#include <QPointSize>
43
45
46
48
49QgsPointCloudLayerChunkLoader::QgsPointCloudLayerChunkLoader( const QgsPointCloudLayerChunkLoaderFactory *factory, QgsChunkNode *node, std::unique_ptr< QgsPointCloud3DSymbol > symbol,
50 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
56 QgsPointCloudIndex *pc = mFactory->mPointCloudIndex;
57 mContext.setAttributes( pc->attributes() );
58
59 const QgsChunkNodeId nodeId = node->tileId();
60 const IndexedPointCloudNode pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z );
61
62 Q_ASSERT( pc->hasNode( pcNode ) );
63
64 QgsDebugMsgLevel( QStringLiteral( "loading entity %1" ).arg( node->tileId().text() ), 2 );
65
66 if ( mContext.symbol()->symbolType() == QLatin1String( "single-color" ) )
67 mHandler.reset( new QgsSingleColorPointCloud3DSymbolHandler() );
68 else if ( mContext.symbol()->symbolType() == QLatin1String( "color-ramp" ) )
69 mHandler.reset( new QgsColorRampPointCloud3DSymbolHandler() );
70 else if ( mContext.symbol()->symbolType() == QLatin1String( "rgb" ) )
71 mHandler.reset( new QgsRGBPointCloud3DSymbolHandler() );
72 else if ( mContext.symbol()->symbolType() == QLatin1String( "classification" ) )
73 {
74 mHandler.reset( new QgsClassificationPointCloud3DSymbolHandler() );
75 const QgsClassificationPointCloud3DSymbol *classificationSymbol = dynamic_cast<const QgsClassificationPointCloud3DSymbol *>( mContext.symbol() );
76 mContext.setFilteredOutCategories( classificationSymbol->getFilteredOutCategories() );
77 }
78
79 //
80 // this will be run in a background thread
81 //
82 mFutureWatcher = new QFutureWatcher<void>( this );
83 connect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
84
85 const QgsBox3D box3D = node->box3D();
86 const QFuture<void> future = QtConcurrent::run( [pc, pcNode, box3D, this]
87 {
88 const QgsEventTracing::ScopedEvent e( QStringLiteral( "3D" ), QStringLiteral( "PC chunk load" ) );
89
90 if ( mContext.isCanceled() )
91 {
92 QgsDebugMsgLevel( QStringLiteral( "canceled" ), 2 );
93 return;
94 }
95
96 mHandler->processNode( pc, pcNode, mContext );
97
98 if ( mContext.isCanceled() )
99 {
100 QgsDebugMsgLevel( QStringLiteral( "canceled" ), 2 );
101 return;
102 }
103
104 if ( mContext.symbol()->renderAsTriangles() )
105 mHandler->triangulate( pc, pcNode, mContext, box3D );
106 } );
107
108 // emit finished() as soon as the handler is populated with features
109 mFutureWatcher->setFuture( future );
110}
111
112QgsPointCloudLayerChunkLoader::~QgsPointCloudLayerChunkLoader()
113{
114 if ( mFutureWatcher && !mFutureWatcher->isFinished() )
115 {
116 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
117 mContext.cancelRendering();
118 mFutureWatcher->waitForFinished();
119 }
120}
121
122void QgsPointCloudLayerChunkLoader::cancel()
123{
124 mContext.cancelRendering();
125}
126
127Qt3DCore::QEntity *QgsPointCloudLayerChunkLoader::createEntity( Qt3DCore::QEntity *parent )
128{
129 QgsPointCloudIndex *pc = mFactory->mPointCloudIndex;
130 const QgsChunkNodeId nodeId = mNode->tileId();
131 const IndexedPointCloudNode pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z );
132 Q_ASSERT( pc->hasNode( pcNode ) );
133
134 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity( parent );
135 mHandler->finalize( entity, mContext );
136 return entity;
137}
138
140
141
142QgsPointCloudLayerChunkLoaderFactory::QgsPointCloudLayerChunkLoaderFactory( const Qgs3DRenderContext &context, const QgsCoordinateTransform &coordinateTransform, QgsPointCloudIndex *pc, QgsPointCloud3DSymbol *symbol,
143 double zValueScale, double zValueOffset, int pointBudget )
144 : mRenderContext( context )
145 , mCoordinateTransform( coordinateTransform )
146 , mPointCloudIndex( pc )
147 , mZValueScale( zValueScale )
148 , mZValueOffset( zValueOffset )
149 , mPointBudget( pointBudget )
150{
151 mSymbol.reset( symbol );
152
153 try
154 {
155 mExtent = mCoordinateTransform.transformBoundingBox( mRenderContext.extent(), Qgis::TransformDirection::Reverse );
156 }
157 catch ( const QgsCsException & )
158 {
159 // bad luck, can't reproject for some reason
160 QgsDebugError( QStringLiteral( "Transformation of extent failed." ) );
161 }
162}
163
164QgsChunkLoader *QgsPointCloudLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
165{
166 const QgsChunkNodeId id = node->tileId();
167
168 Q_ASSERT( mPointCloudIndex->hasNode( IndexedPointCloudNode( id.d, id.x, id.y, id.z ) ) );
169 QgsPointCloud3DSymbol *symbol = static_cast< QgsPointCloud3DSymbol * >( mSymbol->clone() );
170 return new QgsPointCloudLayerChunkLoader( this, node, std::unique_ptr< QgsPointCloud3DSymbol >( symbol ), mCoordinateTransform, mZValueScale, mZValueOffset );
171}
172
173int QgsPointCloudLayerChunkLoaderFactory::primitivesCount( QgsChunkNode *node ) const
174{
175 const QgsChunkNodeId id = node->tileId();
176 const IndexedPointCloudNode n( id.d, id.x, id.y, id.z );
177 Q_ASSERT( mPointCloudIndex->hasNode( n ) );
178 return mPointCloudIndex->nodePointCount( n );
179}
180
181
182QgsBox3D nodeBoundsToBox3D( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const QgsCoordinateTransform &coordinateTransform, double zValueOffset, double zValueScale )
183{
184 QgsVector3D extentMin3D( static_cast<double>( nodeBounds.xMin() ) * scale.x() + offset.x(),
185 static_cast<double>( nodeBounds.yMin() ) * scale.y() + offset.y(),
186 ( static_cast<double>( nodeBounds.zMin() ) * scale.z() + offset.z() ) * zValueScale + zValueOffset );
187 QgsVector3D extentMax3D( static_cast<double>( nodeBounds.xMax() ) * scale.x() + offset.x(),
188 static_cast<double>( nodeBounds.yMax() ) * scale.y() + offset.y(),
189 ( static_cast<double>( nodeBounds.zMax() ) * scale.z() + offset.z() ) * zValueScale + zValueOffset );
190 QgsCoordinateTransform extentTransform = coordinateTransform;
191 extentTransform.setBallparkTransformsAreAppropriate( true );
192 try
193 {
194 extentMin3D = extentTransform.transform( extentMin3D );
195 extentMax3D = extentTransform.transform( extentMax3D );
196 }
197 catch ( QgsCsException & )
198 {
199 QgsDebugError( QStringLiteral( "Error transforming node bounds coordinate" ) );
200 }
201 return QgsBox3D( extentMin3D.x(), extentMin3D.y(), extentMin3D.z(),
202 extentMax3D.x(), extentMax3D.y(), extentMax3D.z() );
203}
204
205
206QgsChunkNode *QgsPointCloudLayerChunkLoaderFactory::createRootNode() const
207{
208 const QgsPointCloudDataBounds rootNodeBounds = mPointCloudIndex->nodeBounds( IndexedPointCloudNode( 0, 0, 0, 0 ) );
209 QgsBox3D rootNodeBox3D = nodeBoundsToBox3D( rootNodeBounds, mPointCloudIndex->offset(), mPointCloudIndex->scale(), mCoordinateTransform, mZValueOffset, mZValueScale );
210
211 const float error = mPointCloudIndex->nodeError( IndexedPointCloudNode( 0, 0, 0, 0 ) );
212 QgsChunkNode *node = new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, error );
213 node->setRefinementProcess( mSymbol->renderAsTriangles() ? Qgis::TileRefinementProcess::Replacement : Qgis::TileRefinementProcess::Additive );
214 return node;
215}
216
217QVector<QgsChunkNode *> QgsPointCloudLayerChunkLoaderFactory::createChildren( QgsChunkNode *node ) const
218{
219 QVector<QgsChunkNode *> children;
220 const QgsChunkNodeId nodeId = node->tileId();
221 const float childError = node->error() / 2;
222
223 for ( int i = 0; i < 8; ++i )
224 {
225 int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
226 const QgsChunkNodeId childId( nodeId.d + 1, nodeId.x * 2 + dx, nodeId.y * 2 + dy, nodeId.z * 2 + dz );
227
228 if ( !mPointCloudIndex->hasNode( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ) )
229 continue;
230 if ( !mExtent.isEmpty() &&
231 !mPointCloudIndex->nodeMapExtent( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ).intersects( mExtent ) )
232 continue;
233
234 const QgsPointCloudDataBounds childBounds = mPointCloudIndex->nodeBounds( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) );
235 QgsBox3D childBox3D = nodeBoundsToBox3D( childBounds, mPointCloudIndex->offset(), mPointCloudIndex->scale(), mCoordinateTransform, mZValueOffset, mZValueScale );
236
237 QgsChunkNode *child = new QgsChunkNode( childId, childBox3D, childError, node );
238 child->setRefinementProcess( mSymbol->renderAsTriangles() ? Qgis::TileRefinementProcess::Replacement : Qgis::TileRefinementProcess::Additive );
239 children << child;
240 }
241 return children;
242}
243
245
246
247QgsPointCloudLayerChunkedEntity::QgsPointCloudLayerChunkedEntity( Qgs3DMapSettings *map, QgsPointCloudIndex *pc,
248 const QgsCoordinateTransform &coordinateTransform, QgsPointCloud3DSymbol *symbol,
249 float maximumScreenSpaceError, bool showBoundingBoxes,
250 double zValueScale, double zValueOffset,
251 int pointBudget )
252 : QgsChunkedEntity( map,
253 maximumScreenSpaceError,
254 new QgsPointCloudLayerChunkLoaderFactory( Qgs3DRenderContext::fromMapSettings( map ), coordinateTransform, pc, symbol, zValueScale, zValueOffset, pointBudget ), true, pointBudget )
255{
256 setShowBoundingBoxes( showBoundingBoxes );
257}
258
259QgsPointCloudLayerChunkedEntity::~QgsPointCloudLayerChunkedEntity()
260{
261 // cancel / wait for jobs
262 cancelActiveJobs();
263}
264
265QVector<QgsRayCastingUtils::RayHit> QgsPointCloudLayerChunkedEntity::rayIntersection( const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context ) const
266{
267 QVector<QgsRayCastingUtils::RayHit> result;
268 QgsPointCloudLayerChunkLoaderFactory *factory = static_cast<QgsPointCloudLayerChunkLoaderFactory *>( mChunkLoaderFactory );
269
270 // transform ray
271 const QgsVector3D rayOriginMapCoords = factory->mRenderContext.worldToMapCoordinates( ray.origin() );
272 const QgsVector3D pointMapCoords = factory->mRenderContext.worldToMapCoordinates( ray.origin() + ray.origin().length() * ray.direction().normalized() );
273 QgsVector3D rayDirectionMapCoords = pointMapCoords - rayOriginMapCoords;
274 rayDirectionMapCoords.normalize();
275
276 const int screenSizePx = std::max( context.screenSize.height(), context.screenSize.width() );
277
278 const QgsPointCloud3DSymbol *symbol = factory->mSymbol.get();
279 // Symbol can be null in case of no rendering enabled
280 if ( !symbol )
281 return result;
282 const double pointSize = symbol->pointSize();
283
284 // We're using the angle as a tolerance, effectively meaning we're fetching points intersecting a cone.
285 // This may be revisited to use a cylinder instead, if the balance between near/far points does not scale
286 // well with different point sizes, screen sizes and fov values.
287 const double limitAngle = 2. * pointSize / screenSizePx * factory->mRenderContext.fieldOfView();
288
289 // adjust ray to elevation properties
290 const QgsVector3D adjustedRayOrigin = QgsVector3D( rayOriginMapCoords.x(), rayOriginMapCoords.y(), ( rayOriginMapCoords.z() - factory->mZValueOffset ) / factory->mZValueScale );
291 QgsVector3D adjustedRayDirection = QgsVector3D( rayDirectionMapCoords.x(), rayDirectionMapCoords.y(), rayDirectionMapCoords.z() / factory->mZValueScale );
292 adjustedRayDirection.normalize();
293
294 QgsPointCloudIndex *index = factory->mPointCloudIndex;
295
296 const QgsPointCloudAttributeCollection attributeCollection = index->attributes();
297 QgsPointCloudRequest request;
298 request.setAttributes( attributeCollection );
299
300 double minDist = -1.;
301 const QList<QgsChunkNode *> activeNodes = this->activeNodes();
302 for ( QgsChunkNode *node : activeNodes )
303 {
304 const QgsChunkNodeId id = node->tileId();
305 const IndexedPointCloudNode n( id.d, id.x, id.y, id.z );
306
307 if ( !index->hasNode( n ) )
308 continue;
309
310 const QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() );
311 if ( !QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) )
312 continue;
313
314 std::unique_ptr<QgsPointCloudBlock> block( index->nodeData( n, request ) );
315 if ( !block )
316 continue;
317
318 const QgsVector3D blockScale = block->scale();
319 const QgsVector3D blockOffset = block->offset();
320
321 const char *ptr = block->data();
322 const QgsPointCloudAttributeCollection blockAttributes = block->attributes();
323 const std::size_t recordSize = blockAttributes.pointRecordSize();
324 int xOffset = 0, yOffset = 0, zOffset = 0;
325 const QgsPointCloudAttribute::DataType xType = blockAttributes.find( QStringLiteral( "X" ), xOffset )->type();
326 const QgsPointCloudAttribute::DataType yType = blockAttributes.find( QStringLiteral( "Y" ), yOffset )->type();
327 const QgsPointCloudAttribute::DataType zType = blockAttributes.find( QStringLiteral( "Z" ), zOffset )->type();
328 for ( int i = 0; i < block->pointCount(); ++i )
329 {
330 double x, y, z;
331 QgsPointCloudAttribute::getPointXYZ( ptr, i, recordSize, xOffset, xType, yOffset, yType, zOffset, zType, blockScale, blockOffset, x, y, z );
332 const QgsVector3D point( x, y, z );
333
334 // check whether point is in front of the ray
335 // similar to QgsRay3D::isInFront(), but using doubles
336 QgsVector3D vectorToPoint = point - adjustedRayOrigin;
337 vectorToPoint.normalize();
338 if ( QgsVector3D::dotProduct( vectorToPoint, adjustedRayDirection ) < 0.0 )
339 continue;
340
341 // calculate the angle between the point and the projected point
342 // similar to QgsRay3D::angleToPoint(), but using doubles
343 const QgsVector3D projPoint = adjustedRayOrigin + adjustedRayDirection * QgsVector3D::dotProduct( point - adjustedRayOrigin, adjustedRayDirection );
344 const QgsVector3D v1 = projPoint - adjustedRayOrigin ;
345 const QgsVector3D v2 = point - projPoint;
346 double angle = std::atan2( v2.length(), v1.length() ) * 180 / M_PI;
347 if ( angle > limitAngle )
348 continue;
349
350 const double dist = rayOriginMapCoords.distance( point );
351
352 if ( minDist < 0 || dist < minDist )
353 {
354 minDist = dist;
355 }
356 else if ( context.singleResult )
357 {
358 continue;
359 }
360
361 // Note : applying elevation properties is done in fromPointCloudIdentificationToIdentifyResults
362 QVariantMap pointAttr = QgsPointCloudAttribute::getAttributeMap( ptr, i * recordSize, blockAttributes );
363 pointAttr[ QStringLiteral( "X" ) ] = x;
364 pointAttr[ QStringLiteral( "Y" ) ] = y;
365 pointAttr[ QStringLiteral( "Z" ) ] = z;
366
367 const QgsVector3D worldPoint = factory->mRenderContext.mapToWorldCoordinates( point );
368 QgsRayCastingUtils::RayHit hit( dist, worldPoint.toVector3D(), FID_NULL, pointAttr );
369 if ( context.singleResult )
370 result.clear();
371 result.append( hit );
372 }
373 }
374 return result;
375}
Represents a indexed point cloud node in octree.
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
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 packaged data bounds.
qint64 xMin() const
Returns x min.
qint64 zMin() const
Returns z min.
qint64 yMax() const
Returns y max.
qint64 xMax() const
Returns x max.
qint64 zMax() const
Returns z max.
qint64 yMin() const
Returns y min.
Represents a indexed point clouds data in octree.
virtual bool hasNode(const IndexedPointCloudNode &n) const
Returns whether the octree contain given node.
virtual std::unique_ptr< QgsPointCloudBlock > nodeData(const IndexedPointCloudNode &n, const QgsPointCloudRequest &request)=0
Returns node data block.
void setAttributes(const QgsPointCloudAttributeCollection &attributes)
Sets native attributes of the data.
QgsPointCloudAttributeCollection attributes() const
Returns all attributes that are stored in the file.
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.