18#include <QElapsedTimer>
40#include <delaunator.hpp>
46 , mLayerName( layer->name() )
47 , mLayerAttributes( layer->attributes() )
48 , mSubIndexes( layer && layer->dataProvider() ? layer->dataProvider()->subIndexes() : QVector<QgsPointCloudSubIndex>() )
50 , mEnableProfile( context.flags() &
Qgis::RenderContextFlag::RecordProfile )
61 if ( !mSubIndexes.isEmpty() )
72 mZOffset = elevationProps->zOffset();
73 mZScale = elevationProps->zScale();
82 mPreparationTime = timer.elapsed();
87 std::unique_ptr< QgsScopedRuntimeProfile > profile;
90 profile = std::make_unique< QgsScopedRuntimeProfile >( mLayerName, QStringLiteral(
"rendering" ),
layerId() );
91 if ( mPreparationTime > 0 )
95 std::unique_ptr< QgsScopedRuntimeProfile > preparingProfile;
98 preparingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr(
"Preparing render" ), QStringLiteral(
"rendering" ) );
109 if ( !mClippingRegions.empty() )
111 bool needsPainterClipPath =
false;
113 if ( needsPainterClipPath )
117 if ( mRenderer->type() == QLatin1String(
"extent" ) )
120 mRenderer->startRender( context );
122 mRenderer->stopRender( context );
129 if ( mSubIndexes.isEmpty() &&
140 mBlockRenderUpdates =
true;
141 mElapsedTimer.start();
144 mRenderer->startRender( context );
156 QSet< QString > rendererAttributes = mRenderer->usedAttributes( context );
159 for (
const QString &attribute : std::as_const( rendererAttributes ) )
161 if ( mAttributes.
indexOf( attribute ) >= 0 )
164 const int layerIndex = mLayerAttributes.
indexOf( attribute );
165 if ( layerIndex < 0 )
167 QgsMessageLog::logMessage( QObject::tr(
"Required attribute %1 not found in layer" ).arg( attribute ), QObject::tr(
"Point Cloud" ) );
171 mAttributes.
push_back( mLayerAttributes.
at( layerIndex ) );
181 QgsDebugError( QStringLiteral(
"Transformation of extent failed!" ) );
184 preparingProfile.reset();
185 std::unique_ptr< QgsScopedRuntimeProfile > renderingProfile;
186 if ( mEnableProfile )
188 renderingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr(
"Rendering" ), QStringLiteral(
"rendering" ) );
191 bool canceled =
false;
192 if ( mSubIndexes.isEmpty() )
194 canceled = !renderIndex( pc );
198 mSubIndexExtentRenderer->startRender( context );
199 for (
const auto &si : mSubIndexes )
206 if ( !renderExtent.
intersects( si.extent() ) )
209 if ( !pc || !pc->
isValid() || renderExtent.
width() > si.extent().width() )
213 mSubIndexExtentRenderer->renderExtent( si.polygonBounds(), context );
217 canceled = !renderIndex( pc );
220 mSubIndexExtentRenderer->stopRender( context );
223 mRenderer->stopRender( context );
245 const double maximumError = context.renderContext().convertToPainterUnits( mRenderer->maximumScreenError(), mRenderer->maximumScreenErrorUnit() );
249 if ( !context.renderContext().coordinateTransform().isShortCircuited() )
259 QgsDebugError( QStringLiteral(
"Could not transform node extent to map CRS" ) );
260 rootNodeExtentMapCoords = rootNodeExtentLayerCoords;
265 rootNodeExtentMapCoords = rootNodeExtentLayerCoords;
268 const double rootErrorInMapCoordinates = rootNodeExtentMapCoords.
width() / pc->
span();
270 double mapUnitsPerPixel = context.renderContext().mapToPixel().mapUnitsPerPixel();
271 if ( ( rootErrorInMapCoordinates < 0.0 ) || ( mapUnitsPerPixel < 0.0 ) || ( maximumError < 0.0 ) )
276 double rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel;
277 const QVector<IndexedPointCloudNode> nodes = traverseTree( pc, context.renderContext(), pc->
root(), maximumError, rootErrorPixels );
284 bool canceled =
false;
287 if ( mRenderer->renderAsTriangles() )
299 nodesDrawn += renderNodesSorted( nodes, pc, context, request, canceled, mRenderer->drawOrder2d() );
308 nodesDrawn += renderNodesSync( nodes, pc, context, request, canceled );
313 nodesDrawn += renderNodesAsync( nodes, pc, context, request, canceled );
321 QgsDebugMsgLevel( QStringLiteral(
"totals: %1 nodes | %2 points | %3ms" ).arg( nodesDrawn )
322 .arg( context.pointsRendered() )
323 .arg( t.elapsed() ), 2 );
350 std::unique_ptr<QgsPointCloudBlock> block( pc->
nodeData( n, request ) );
363 mRenderer->renderBlock( block.get(), context );
379 if ( mRenderer->renderAsTriangles() )
383 renderTriangulatedSurface( context );
405 QVector<QgsPointCloudBlockRequest *> blockRequests;
410 for (
int i = 0; i < nodes.size(); ++i )
415 blockRequests.append( blockRequest );
417 [
this, &canceled, &nodesDrawn, &loop, &blockRequests, &context, nStr, blockRequest ]()
419 blockRequests.removeOne( blockRequest );
422 if ( blockRequests.isEmpty() )
425 std::unique_ptr<QgsPointCloudBlock> block( blockRequest->
takeBlock() );
427 blockRequest->deleteLater();
437 QgsDebugError( QStringLiteral(
"Unable to load node %1, error: %2" ).arg( nStr, blockRequest->
errorStr() ) );
448 mRenderer->renderBlock( block.get(), context );
473 std::unique_ptr<QgsPointCloudBlock> block = blockRequest->
takeBlock();
476 blockRequest->deleteLater();
479 if ( mRenderer->renderAsTriangles() )
483 renderTriangulatedSurface( context );
500 QByteArray allByteArrays;
502 QVector<QPair<int, double>> allPairs;
512 std::unique_ptr<QgsPointCloudBlock> block( pc->
nodeData( n, request ) );
520 if ( blockCount == 0 )
522 blockScale = block->scale();
523 blockOffset = block->offset();
528 offsetDifference = blockOffset - block->offset();
531 const char *ptr = block->data();
539 for (
int i = 0; i < block->pointCount(); ++i )
541 allByteArrays.append( ptr + i * recordSize, recordSize );
544 if ( offsetDifference.
x() != 0 )
546 qint32 ix = *
reinterpret_cast< const qint32 *
>( ptr + i * recordSize + context.
xOffset() );
547 ix -= std::lround( offsetDifference.
x() / context.
scale().
x() );
548 const char *xPtr =
reinterpret_cast< const char *
>( &ix );
549 allByteArrays.replace( pointCount * recordSize + context.
xOffset(), 4, QByteArray( xPtr, 4 ) );
551 if ( offsetDifference.
y() != 0 )
553 qint32 iy = *
reinterpret_cast< const qint32 *
>( ptr + i * recordSize + context.
yOffset() );
554 iy -= std::lround( offsetDifference.
y() / context.
scale().
y() );
555 const char *yPtr =
reinterpret_cast< const char *
>( &iy );
556 allByteArrays.replace( pointCount * recordSize + context.
yOffset(), 4, QByteArray( yPtr, 4 ) );
559 qint32 iz = *
reinterpret_cast< const qint32 *
>( ptr + i * recordSize + context.
zOffset() );
560 if ( offsetDifference.
z() != 0 )
562 iz -= std::lround( offsetDifference.
z() / context.
scale().
z() );
563 const char *zPtr =
reinterpret_cast< const char *
>( &iz );
564 allByteArrays.replace( pointCount * recordSize + context.
zOffset(), 4, QByteArray( zPtr, 4 ) );
566 allPairs.append( qMakePair( pointCount,
double( iz ) + block->offset().z() ) );
573 if ( pointCount == 0 )
579 std::sort( allPairs.begin(), allPairs.end(), []( QPair<int, double> a, QPair<int, double> b ) { return a.second < b.second; } );
582 std::sort( allPairs.begin(), allPairs.end(), []( QPair<int, double> a, QPair<int, double> b ) { return a.second > b.second; } );
589 QByteArray sortedByteArray;
590 sortedByteArray.reserve( allPairs.size() );
591 for ( QPair<int, double> pair : allPairs )
592 sortedByteArray.append( allByteArrays.mid( pair.first * recordSize, recordSize ) );
603 context.
setScale( bigBlock->scale() );
607 mRenderer->renderBlock( bigBlock.get(), context );
615inline bool isEdgeTooLong(
const QPointF &p1,
const QPointF &p2,
float length )
618 return p.x() * p.x() + p.y() * p.y() > length;
621static void renderTriangle( QImage &img, QPointF *pts, QRgb c0, QRgb c1, QRgb c2,
float horizontalFilter,
float *elev,
QgsElevationMap *elevationMap )
623 if ( horizontalFilter > 0 )
625 float filterThreshold2 = horizontalFilter * horizontalFilter;
632 QgsRectangle screenBBox = QgsMeshLayerUtils::triangleBoundingBox( pts[0], pts[1], pts[2] );
634 QSize outputSize = img.size();
636 int topLim = std::max(
int( screenBBox.
yMinimum() ), 0 );
637 int bottomLim = std::min(
int( screenBBox.
yMaximum() ), outputSize.height() - 1 );
638 int leftLim = std::max(
int( screenBBox.
xMinimum() ), 0 );
639 int rightLim = std::min(
int( screenBBox.
xMaximum() ), outputSize.width() - 1 );
641 int red0 = qRed( c0 ), green0 = qGreen( c0 ), blue0 = qBlue( c0 );
642 int red1 = qRed( c1 ), green1 = qGreen( c1 ), blue1 = qBlue( c1 );
643 int red2 = qRed( c2 ), green2 = qGreen( c2 ), blue2 = qBlue( c2 );
647 for (
int j = topLim; j <= bottomLim; j++ )
649 QRgb *scanLine = ( QRgb * ) img.scanLine( j );
650 QRgb *elevScanLine = elevData ? elevData +
static_cast<size_t>( outputSize.width() * j ) : nullptr;
651 for (
int k = leftLim; k <= rightLim; k++ )
654 double lam1, lam2, lam3;
655 if ( !QgsMeshLayerUtils::calculateBarycentricCoordinates( pts[0], pts[1], pts[2], pt, lam3, lam2, lam1 ) )
659 int r =
static_cast<int>( red0 * lam1 + red1 * lam2 + red2 * lam3 );
660 int g =
static_cast<int>( green0 * lam1 + green1 * lam2 + green2 * lam3 );
661 int b =
static_cast<int>( blue0 * lam1 + blue1 * lam2 + blue2 * lam3 );
662 scanLine[k] = qRgb( r, g, b );
667 float z =
static_cast<float>( elev[0] * lam1 + elev[1] * lam2 + elev[2] * lam3 );
677 const std::vector<double> &points = triangulation.
points;
680 if ( points.size() < 3 )
682 QgsDebugMsgLevel( QStringLiteral(
"Need at least 3 points to triangulate" ), 4 );
686 std::unique_ptr<delaunator::Delaunator> delaunator;
689 delaunator.reset(
new delaunator::Delaunator( points ) );
691 catch ( std::exception & )
698 float horizontalFilter = 0;
699 if ( mRenderer->horizontalTriangleFilter() )
702 mRenderer->horizontalTriangleFilterThreshold(), mRenderer->horizontalTriangleFilterUnit() ) );
709 const std::vector<size_t> &triangleIndexes = delaunator->triangles;
714 for (
size_t i = 0; i < triangleIndexes.size(); i += 3 )
716 size_t v0 = triangleIndexes[i], v1 = triangleIndexes[i + 1], v2 = triangleIndexes[i + 2];
717 triangle[0].rx() = points[v0 * 2];
718 triangle[0].ry() = points[v0 * 2 + 1];
719 triangle[1].rx() = points[v1 * 2];
720 triangle[1].ry() = points[v1 * 2 + 1];
721 triangle[2].rx() = points[v2 * 2];
722 triangle[2].ry() = points[v2 * 2 + 1];
731 QRgb c0 = triangulation.
colors[v0], c1 = triangulation.
colors[v1], c2 = triangulation.
colors[v2];
732 renderTriangle( img, triangle, c0, c1, c2, horizontalFilter, elev, elevationMap );
735 painter->drawImage( 0, 0, img );
743 if ( mRenderer->renderAsTriangles() )
753 return mRenderer ? mRenderer->type() != QLatin1String(
"extent" ) :
false;
758 mRenderTimeHint = time;
761QVector<IndexedPointCloudNode> QgsPointCloudLayerRenderer::traverseTree(
const QgsPointCloudIndex *pc,
764 double maxErrorPixels,
765 double nodeErrorPixels )
767 QVector<IndexedPointCloudNode> nodes;
786 double childrenErrorPixels = nodeErrorPixels / 2.0;
787 if ( childrenErrorPixels < maxErrorPixels )
790 const QList<IndexedPointCloudNode> children = pc->
nodeChildren( n );
793 nodes += traverseTree( pc, context, nn, maxErrorPixels, childrenErrorPixels );
Represents a indexed point cloud node in octree.
QString toString() const
Encode node to string.
The Qgis class provides global constants for use throughout the application.
QFlags< MapLayerRendererFlag > MapLayerRendererFlags
Flags which control how map layer renderers behave.
PointCloudDrawOrder
Pointcloud rendering order for 2d views.
@ BottomToTop
Draw points with larger Z values last.
@ Default
Draw points in the order they are stored.
@ TopToBottom
Draw points with larger Z values first.
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
@ RenderPartialOutputOverPreviousCachedImage
When rendering temporary in-progress preview renders, these preview renders can be drawn over any pre...
@ RenderPartialOutputs
The renderer benefits from rendering temporary in-progress preview renders. These are temporary resul...
@ Reverse
Reverse/inverse transform (from destination to source)
static QgsRuntimeProfiler * profiler()
Returns the application runtime profiler.
Custom exception class for Coordinate Reference System related exceptions.
QgsRange which stores a range of double values.
bool isInfinite() const
Returns true if the range consists of all possible values.
Stores digital elevation model in a raster image which may get updated as a part of map layer renderi...
static QRgb encodeElevation(float z)
Converts elevation value to an actual color.
QRgb * rawElevationImageData()
Returns pointer to the actual elevation image data.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
bool isCanceled() const
Tells whether the operation has been canceled already.
void canceled()
Internal routines can connect to this signal if they use event loop.
static QPainterPath calculatePainterClipRegion(const QList< QgsMapClippingRegion > ®ions, const QgsRenderContext &context, Qgis::LayerType layerType, bool &shouldClip)
Returns a QPainterPath representing the intersection of clipping regions from context which should be...
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.
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,...
QString layerId() const
Gets access to the ID of the layer rendered by this class.
QgsRenderContext * renderContext()
Returns the render context associated with the renderer.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Collection of point cloud attributes.
void push_back(const QgsPointCloudAttribute &attribute)
Adds extra attribute.
const QgsPointCloudAttribute & at(int index) const
Returns the attribute at the specified index.
QVector< QgsPointCloudAttribute > attributes() const
Returns all attributes.
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.
Base class for handling loading QgsPointCloudBlock asynchronously.
QString errorStr()
Returns the error message string of the request.
void finished()
Emitted when the request processing has finished.
std::unique_ptr< QgsPointCloudBlock > takeBlock()
Returns the requested block.
Base class for storing raw data from point cloud nodes.
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.
virtual qint64 nodePointCount(const IndexedPointCloudNode &n) const
Returns the number of points of a given node n.
QgsRectangle nodeMapExtent(const IndexedPointCloudNode &node) const
Returns the extent of a node in map coordinates.
virtual QgsPointCloudBlockRequest * asyncNodeData(const IndexedPointCloudNode &n, const QgsPointCloudRequest &request)=0
Returns a handle responsible for loading a node data block.
virtual QList< IndexedPointCloudNode > nodeChildren(const IndexedPointCloudNode &n) const
Returns all children of node.
@ Remote
Remote means it's loaded through a protocol like HTTP.
@ Local
Local means the source is a local file on the machine.
QgsVector3D offset() const
Returns offset.
QgsVector3D scale() const
Returns scale.
virtual AccessType accessType() const =0
Returns the access type of the data If the access type is Remote, data will be fetched from an HTTP s...
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.
virtual std::unique_ptr< QgsPointCloudBlock > nodeData(const IndexedPointCloudNode &n, const QgsPointCloudRequest &request)=0
Returns node data block.
Point cloud layer specific subclass of QgsMapLayerElevationProperties.
~QgsPointCloudLayerRenderer()
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).
Qgis::MapLayerRendererFlags flags() const override
Returns flags which control how the map layer rendering behaves.
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.
int yOffset() const
Returns the offset for the y value in a point record.
QgsVector3D offset() const
Returns the offset of the layer's int32 coordinates compared to CRS coords.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
void setOffset(const QgsVector3D &offset)
Sets the offset of the layer's int32 coordinates compared to CRS coords.
void setScale(const QgsVector3D &scale)
Sets the scale of the layer's int32 coordinates compared to CRS coords.
int pointRecordSize() const
Returns the size of a single point record.
int xOffset() const
Returns the offset for the x value in a point record.
QgsVector3D scale() const
Returns the scale of the layer's int32 coordinates compared to CRS coords.
TriangulationData & triangulationData()
Returns reference to the triangulation data structure (only used when rendering as triangles is enabl...
int zOffset() const
Returns the offset for the y value in a point record.
QgsFeedback * feedback() const
Returns the feedback object used to cancel rendering.
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.
T lower() const
Returns the lower bound of the range.
T upper() const
Returns the upper bound of the range.
A rectangle specified with double values.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double width() const
Returns the width of the rectangle.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
QgsElevationMap * elevationMap() const
Returns the destination elevation map for the render operation.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
float devicePixelRatio() const
Returns the device pixel ratio.
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QgsDoubleRange zRange() const
Returns the range of z-values which should be rendered.
QSize deviceOutputSize() const
Returns the device output size of the render.
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
QPainter * previewRenderPainter()
Returns the const destination QPainter for temporary in-progress preview renders.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
void record(const QString &name, double time, const QString &group="startup", const QString &id=QString())
Manually adds a profile event with the given name and total time (in seconds).
Scoped object for saving and restoring a QPainter object's state.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
double y() const
Returns Y coordinate.
double z() const
Returns Z coordinate.
double x() const
Returns X coordinate.
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
bool isEdgeTooLong(const QPointF &p1, const QPointF &p2, float length)
Helper data structure used when rendering points as triangulated surface.
std::vector< QRgb > colors
RGB color for each point.
std::vector< float > elevations
Z value for each point (only used when global map shading is enabled)
std::vector< double > points
X,Y for each point - kept in this structure so that we can use it without further conversions in Dela...