QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
qgspointcloudlayerrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspointcloudlayerrenderer.cpp
3  --------------------
4  begin : October 2020
5  copyright : (C) 2020 by Peter Petrik
6  email : zilolv at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include <QElapsedTimer>
19 #include <QPointer>
20 
22 #include "qgspointcloudlayer.h"
23 #include "qgsrendercontext.h"
24 #include "qgspointcloudindex.h"
25 #include "qgsstyle.h"
26 #include "qgscolorramp.h"
27 #include "qgspointcloudrequest.h"
28 #include "qgspointcloudattribute.h"
29 #include "qgspointcloudrenderer.h"
31 #include "qgslogger.h"
33 #include "qgsmessagelog.h"
34 #include "qgscircle.h"
35 #include "qgsmapclippingutils.h"
36 
37 
39  : QgsMapLayerRenderer( layer->id(), &context )
40  , mLayer( layer )
41  , mLayerAttributes( layer->attributes() )
42 {
43  // TODO: we must not keep pointer to mLayer (it's dangerous) - we must copy anything we need for rendering
44  // or use some locking to prevent read/write from multiple threads
45  if ( !mLayer || !mLayer->dataProvider() || !mLayer->renderer() )
46  return;
47 
48  mRenderer.reset( mLayer->renderer()->clone() );
49 
50  if ( mLayer->dataProvider()->index() )
51  {
52  mScale = mLayer->dataProvider()->index()->scale();
53  mOffset = mLayer->dataProvider()->index()->offset();
54  }
55 
56  if ( const QgsPointCloudLayerElevationProperties *elevationProps = qobject_cast< const QgsPointCloudLayerElevationProperties * >( mLayer->elevationProperties() ) )
57  {
58  mZOffset = elevationProps->zOffset();
59  mZScale = elevationProps->zScale();
60  }
61 
62  mCloudExtent = mLayer->dataProvider()->polygonBounds();
63 
65 
66  mReadyToCompose = false;
67 }
68 
70 {
71  QgsPointCloudRenderContext context( *renderContext(), mScale, mOffset, mZScale, mZOffset );
72 
73  // Set up the render configuration options
74  QPainter *painter = context.renderContext().painter();
75 
76  QgsScopedQPainterState painterState( painter );
77  context.renderContext().setPainterFlagsUsingContext( painter );
78 
79  if ( !mClippingRegions.empty() )
80  {
81  bool needsPainterClipPath = false;
82  const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, *renderContext(), QgsMapLayerType::VectorTileLayer, needsPainterClipPath );
83  if ( needsPainterClipPath )
84  renderContext()->painter()->setClipPath( path, Qt::IntersectClip );
85  }
86 
87  if ( mRenderer->type() == QLatin1String( "extent" ) )
88  {
89  // special case for extent only renderer!
90  mRenderer->startRender( context );
91  static_cast< QgsPointCloudExtentRenderer * >( mRenderer.get() )->renderExtent( mCloudExtent, context );
92  mRenderer->stopRender( context );
93  mReadyToCompose = true;
94  return true;
95  }
96 
97  // TODO cache!?
98  QgsPointCloudIndex *pc = mLayer->dataProvider()->index();
99  if ( !pc || !pc->isValid() )
100  {
101  mReadyToCompose = true;
102  return false;
103  }
104 
105  // if the previous layer render was relatively quick (e.g. less than 3 seconds), the we show any previously
106  // cached version of the layer during rendering instead of the usual progressive updates
107  if ( mRenderTimeHint > 0 && mRenderTimeHint <= MAX_TIME_TO_USE_CACHED_PREVIEW_IMAGE )
108  {
109  mBlockRenderUpdates = true;
110  mElapsedTimer.start();
111  }
112 
113  mRenderer->startRender( context );
114 
115  mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "X" ), QgsPointCloudAttribute::Int32 ) );
116  mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Y" ), QgsPointCloudAttribute::Int32 ) );
117 
118  // collect attributes required by renderer
119  QSet< QString > rendererAttributes = mRenderer->usedAttributes( context );
120 
121  if ( !context.renderContext().zRange().isInfinite() )
122  rendererAttributes.insert( QStringLiteral( "Z" ) );
123 
124  for ( const QString &attribute : qgis::as_const( rendererAttributes ) )
125  {
126  if ( mAttributes.indexOf( attribute ) >= 0 )
127  continue; // don't re-add attributes we are already going to fetch
128 
129  const int layerIndex = mLayerAttributes.indexOf( attribute );
130  if ( layerIndex < 0 )
131  {
132  QgsMessageLog::logMessage( QObject::tr( "Required attribute %1 not found in layer" ).arg( attribute ), QObject::tr( "Point Cloud" ) );
133  continue;
134  }
135 
136  mAttributes.push_back( mLayerAttributes.at( layerIndex ) );
137  }
138 
140 
141 #ifdef QGISDEBUG
142  QElapsedTimer t;
143  t.start();
144 #endif
145 
146  const IndexedPointCloudNode root = pc->root();
147 
148  const double maximumError = context.renderContext().convertToPainterUnits( mRenderer->maximumScreenError(), mRenderer->maximumScreenErrorUnit() );// in pixels
149 
150  const QgsRectangle rootNodeExtentLayerCoords = pc->nodeMapExtent( root );
151  QgsRectangle rootNodeExtentMapCoords;
152  try
153  {
154  rootNodeExtentMapCoords = context.renderContext().coordinateTransform().transformBoundingBox( rootNodeExtentLayerCoords );
155  }
156  catch ( QgsCsException & )
157  {
158  QgsDebugMsg( QStringLiteral( "Could not transform node extent to map CRS" ) );
159  rootNodeExtentMapCoords = rootNodeExtentLayerCoords;
160  }
161 
162  const double rootErrorInMapCoordinates = rootNodeExtentMapCoords.width() / pc->span(); // in map coords
163 
164  double mapUnitsPerPixel = context.renderContext().mapToPixel().mapUnitsPerPixel();
165  if ( ( rootErrorInMapCoordinates < 0.0 ) || ( mapUnitsPerPixel < 0.0 ) || ( maximumError < 0.0 ) )
166  {
167  QgsDebugMsg( QStringLiteral( "invalid screen error" ) );
168  mReadyToCompose = true;
169  return false;
170  }
171  double rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel; // in pixels
172  const QVector<IndexedPointCloudNode> nodes = traverseTree( pc, context.renderContext(), pc->root(), maximumError, rootErrorPixels );
173 
174  QgsPointCloudRequest request;
175  request.setAttributes( mAttributes );
176 
177  // drawing
178  int nodesDrawn = 0;
179  bool canceled = false;
180  for ( const IndexedPointCloudNode &n : nodes )
181  {
182  if ( context.renderContext().renderingStopped() )
183  {
184  QgsDebugMsgLevel( "canceled", 2 );
185  canceled = true;
186  break;
187  }
188  std::unique_ptr<QgsPointCloudBlock> block( pc->nodeData( n, request ) );
189 
190  if ( !block )
191  continue;
192 
193  context.setAttributes( block->attributes() );
194 
195  mRenderer->renderBlock( block.get(), context );
196  ++nodesDrawn;
197 
198  // as soon as first block is rendered, we can start showing layer updates.
199  // but if we are blocking render updates (so that a previously cached image is being shown), we wait
200  // at most e.g. 3 seconds before we start forcing progressive updates.
201  if ( !mBlockRenderUpdates || mElapsedTimer.elapsed() > MAX_TIME_TO_USE_CACHED_PREVIEW_IMAGE )
202  {
203  mReadyToCompose = true;
204  }
205  }
206 
207  QgsDebugMsgLevel( QStringLiteral( "totals: %1 nodes | %2 points | %3ms" ).arg( nodesDrawn )
208  .arg( context.pointsRendered() )
209  .arg( t.elapsed() ), 2 );
210 
211  mRenderer->stopRender( context );
212 
213  mReadyToCompose = true;
214  return !canceled;
215 }
216 
218 {
219  // unless we are using the extent only renderer, point cloud layers should always be rasterized -- we don't want to export points as vectors
220  // to formats like PDF!
221  return mRenderer ? mRenderer->type() != QLatin1String( "extent" ) : false;
222 }
223 
225 {
226  mRenderTimeHint = time;
227 }
228 
229 QVector<IndexedPointCloudNode> QgsPointCloudLayerRenderer::traverseTree( const QgsPointCloudIndex *pc,
230  const QgsRenderContext &context,
232  double maxErrorPixels,
233  double nodeErrorPixels )
234 {
235  QVector<IndexedPointCloudNode> nodes;
236 
237  if ( context.renderingStopped() )
238  {
239  QgsDebugMsgLevel( QStringLiteral( "canceled" ), 2 );
240  return nodes;
241  }
242 
243  if ( !context.extent().intersects( pc->nodeMapExtent( n ) ) )
244  return nodes;
245 
246  const QgsDoubleRange nodeZRange = pc->nodeZRange( n );
247  const QgsDoubleRange adjustedNodeZRange = QgsDoubleRange( nodeZRange.lower() + mZOffset, nodeZRange.upper() + mZOffset );
248  if ( !context.zRange().isInfinite() && !context.zRange().overlaps( adjustedNodeZRange ) )
249  return nodes;
250 
251  nodes.append( n );
252 
253  double childrenErrorPixels = nodeErrorPixels / 2.0;
254  if ( childrenErrorPixels < maxErrorPixels )
255  return nodes;
256 
257  const QList<IndexedPointCloudNode> children = pc->nodeChildren( n );
258  for ( const IndexedPointCloudNode &nn : children )
259  {
260  nodes += traverseTree( pc, context, nn, maxErrorPixels, childrenErrorPixels );
261  }
262 
263  return nodes;
264 }
265 
267 
Represents a indexed point cloud node in octree.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, TransformDirection direction=ForwardTransform, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
QgsRange which stores a range of double values.
Definition: qgsrange.h:203
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition: qgsrange.h:247
static QList< QgsMapClippingRegion > collectClippingRegionsForLayer(const QgsRenderContext &context, const QgsMapLayer *layer)
Collects the list of map clipping regions from a context which apply to a map layer.
static QPainterPath calculatePainterClipRegion(const QList< QgsMapClippingRegion > &regions, const QgsRenderContext &context, QgsMapLayerType layerType, bool &shouldClip)
Returns a QPainterPath representing the intersection of clipping regions from context which should be...
Base class for utility classes that encapsulate information necessary for rendering of map layers.
bool mReadyToCompose
The flag must be set to false in renderer's constructor if wants to use the smarter map redraws funct...
static constexpr int MAX_TIME_TO_USE_CACHED_PREVIEW_IMAGE
Maximum time (in ms) to allow display of a previously cached preview image while rendering layers,...
QgsRenderContext * renderContext()
Returns the render context associated with the renderer.
double mapUnitsPerPixel() const
Returns current map units per pixel.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
void push_back(const QgsPointCloudAttribute &attribute)
Adds extra attribute.
const QgsPointCloudAttribute & at(int index) const
Returns the attribute at the specified index.
int indexOf(const QString &name) const
Returns the index of the attribute with the specified name.
Attribute for point cloud data pair of name and size in bytes.
Represents packaged data bounds.
virtual QgsPointCloudIndex * index() const
Returns the point cloud index associated with the provider.
virtual QgsGeometry polygonBounds() const
Returns the polygon bounds of the layer.
A renderer for 2d visualisation of point clouds which shows the dataset's extents using a fill symbol...
Represents a indexed point clouds data in octree.
int span() const
Returns the number of points in one direction in a single node.
QgsRectangle nodeMapExtent(const IndexedPointCloudNode &node) const
Returns the extent of a node in map coordinates.
QList< IndexedPointCloudNode > nodeChildren(const IndexedPointCloudNode &n) const
Returns all children of node.
QgsVector3D offset() const
Returns offset.
QgsVector3D scale() const
Returns scale.
virtual QgsPointCloudBlock * nodeData(const IndexedPointCloudNode &n, const QgsPointCloudRequest &request)=0
Returns node data block.
virtual bool isValid() const =0
Returns whether index is loaded and valid.
IndexedPointCloudNode root()
Returns root node of the index.
QgsDoubleRange nodeZRange(const IndexedPointCloudNode &node) const
Returns the z range of a node.
Point cloud layer specific subclass of QgsMapLayerElevationProperties.
bool forceRasterRender() const override
Returns true if the renderer must be rendered to a raster paint device (e.g.
QgsPointCloudLayerRenderer(QgsPointCloudLayer *layer, QgsRenderContext &context)
Ctor.
void setLayerRenderingTimeHint(int time) override
Sets approximate render time (in ms) for the layer to render.
bool render() override
Do the rendering (based on data stored in the class).
Represents a map layer supporting display of point clouds.
QgsMapLayerElevationProperties * elevationProperties() override
Returns the layer's elevation properties.
QgsPointCloudRenderer * renderer()
Returns the 2D renderer for the point cloud.
QgsPointCloudDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
Encapsulates the render context for a 2D point cloud rendering operation.
long pointsRendered() const
Returns the total number of points rendered.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
void setAttributes(const QgsPointCloudAttributeCollection &attributes)
Sets the attributes associated with the rendered block.
virtual QgsPointCloudRenderer * clone() const =0
Create a deep copy of this renderer.
Point cloud data request.
void setAttributes(const QgsPointCloudAttributeCollection &attributes)
Set attributes filter in the request.
bool overlaps(const QgsRange< T > &other) const
Returns true if this range overlaps another range.
Definition: qgsrange.h:147
T lower() const
Returns the lower bound of the range.
Definition: qgsrange.h:66
T upper() const
Returns the upper bound of the range.
Definition: qgsrange.h:73
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:328
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
QgsDoubleRange zRange() const
Returns the range of z-values which should be rendered.
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
Scoped object for saving and restoring a QPainter object's state.
@ VectorTileLayer
Added in 3.14.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38