Skip to content

Commit

Permalink
Added pointcloud ordered by Z rendering for 2d views
Browse files Browse the repository at this point in the history
  • Loading branch information
Stefanos Natsis authored and nyalldawson committed Jan 18, 2022
1 parent f64b786 commit be99a16
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 32 deletions.
21 changes: 21 additions & 0 deletions python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in
Expand Up @@ -197,6 +197,13 @@ Abstract base class for 2d point cloud renderers.
Circle,
};

enum DrawOrder
{
Unchanged,
BottomToTop,
TopToBottom,
};

QgsPointCloudRenderer();
%Docstring
Constructor for QgsPointCloudRenderer.
Expand Down Expand Up @@ -364,6 +371,20 @@ Returns the map unit scale used for the point size.
.. seealso:: :py:func:`pointSizeUnit`

.. seealso:: :py:func:`pointSize`
%End

DrawOrder drawOrder2d() const;
%Docstring
Returns the drawing order used by the renderer for drawing points.

.. seealso:: :py:func:`setDrawOrder2d`
%End

void setDrawOrder2d( DrawOrder order );
%Docstring
Sets the drawing ``order`` used by the renderer for drawing points.

.. seealso:: :py:func:`drawOrder2d`
%End

PointSymbol pointSymbol() const;
Expand Down
4 changes: 2 additions & 2 deletions src/core/pointcloud/qgspointcloudindex.h
Expand Up @@ -164,8 +164,8 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject
//! The access type of the data, local is for local files and remote for remote files (over HTTP)
enum AccessType
{
Local, //! Local means the source is a local file on the machine
Remote //! Remote means it's loaded through a protocol like HTTP
Local, //!< Local means the source is a local file on the machine
Remote //!< Remote means it's loaded through a protocol like HTTP
};

//! Constructs index
Expand Down
149 changes: 141 additions & 8 deletions src/core/pointcloud/qgspointcloudlayerrenderer.cpp
Expand Up @@ -116,11 +116,14 @@ bool QgsPointCloudLayerRenderer::render()
mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "X" ), QgsPointCloudAttribute::Int32 ) );
mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Y" ), QgsPointCloudAttribute::Int32 ) );

if ( !context.renderContext().zRange().isInfinite() ||
mRenderer->drawOrder2d() == QgsPointCloudRenderer::DrawOrder::BottomToTop ||
mRenderer->drawOrder2d() == QgsPointCloudRenderer::DrawOrder::TopToBottom )
mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Z" ), QgsPointCloudAttribute::Int32 ) );

// collect attributes required by renderer
QSet< QString > rendererAttributes = mRenderer->usedAttributes( context );

if ( !context.renderContext().zRange().isInfinite() )
rendererAttributes.insert( QStringLiteral( "Z" ) );

for ( const QString &attribute : std::as_const( rendererAttributes ) )
{
Expand Down Expand Up @@ -179,13 +182,25 @@ bool QgsPointCloudLayerRenderer::render()
int nodesDrawn = 0;
bool canceled = false;

if ( pc->accessType() == QgsPointCloudIndex::AccessType::Local )
{
nodesDrawn += renderNodesSync( nodes, pc, context, request, canceled );
}
else if ( pc->accessType() == QgsPointCloudIndex::AccessType::Remote )
switch ( mRenderer->drawOrder2d() )
{
nodesDrawn += renderNodesAsync( nodes, pc, context, request, canceled );
case QgsPointCloudRenderer::DrawOrder::BottomToTop:
case QgsPointCloudRenderer::DrawOrder::TopToBottom:
{
nodesDrawn += renderNodesSorted( nodes, pc, context, request, canceled, mRenderer->drawOrder2d() );
break;
}
case QgsPointCloudRenderer::DrawOrder::Unchanged:
{
if ( pc->accessType() == QgsPointCloudIndex::AccessType::Local )
{
nodesDrawn += renderNodesSync( nodes, pc, context, request, canceled );
}
else if ( pc->accessType() == QgsPointCloudIndex::AccessType::Remote )
{
nodesDrawn += renderNodesAsync( nodes, pc, context, request, canceled );
}
}
}

#ifdef QGISDEBUG
Expand Down Expand Up @@ -344,6 +359,124 @@ int QgsPointCloudLayerRenderer::renderNodesAsync( const QVector<IndexedPointClou
return nodesDrawn;
}

int QgsPointCloudLayerRenderer::renderNodesSorted( const QVector<IndexedPointCloudNode> &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled, QgsPointCloudRenderer::DrawOrder order )
{
int blockCount = 0;
int pointCount = 0;

QgsVector3D blockScale;
QgsVector3D blockOffset;
QgsPointCloudAttributeCollection blockAttributes;
int recordSize;

// We'll collect byte array data from all blocks
QByteArray allByteArrays;
// And pairs of byte array start positions paired with their Z values for sorting
QVector<QPair<int, double>> allPairs;

for ( const IndexedPointCloudNode &n : nodes )
{
if ( context.renderContext().renderingStopped() )
{
QgsDebugMsgLevel( "canceled", 2 );
canceled = true;
break;
}
std::unique_ptr<QgsPointCloudBlock> block( pc->nodeData( n, request ) );

if ( !block )
continue;

// Individual nodes may have different offset values than the root node
// we'll calculate the differences and translate x,y,z values to use the root node's offset
QgsVector3D offsetDifference = QgsVector3D( 0, 0, 0 );
if ( blockCount == 0 )
{
blockScale = block->scale();
blockOffset = block->offset();
blockAttributes = block->attributes();
}
else
{
offsetDifference = blockOffset - block->offset();
}

const char *ptr = block->data();

context.setScale( block->scale() );
context.setOffset( block->offset() );
context.setAttributes( block->attributes() );

recordSize = context.pointRecordSize();

for ( int i = 0; i < block->pointCount(); ++i )
{
allByteArrays.append( ptr + i * recordSize, recordSize );

// Calculate the translated values only for axes that have a different offset
if ( offsetDifference.x() != 0 )
{
qint32 ix = *reinterpret_cast< const qint32 * >( ptr + i * recordSize + context.xOffset() );
ix -= std::lround( offsetDifference.x() / context.scale().x() );
const char *xPtr = reinterpret_cast< const char * >( &ix );
allByteArrays.replace( pointCount * recordSize + context.xOffset(), 4, QByteArray( xPtr, 4 ) );
}
if ( offsetDifference.y() != 0 )
{
qint32 iy = *reinterpret_cast< const qint32 * >( ptr + i * recordSize + context.yOffset() );
iy -= std::lround( offsetDifference.y() / context.scale().y() );
const char *yPtr = reinterpret_cast< const char * >( &iy );
allByteArrays.replace( pointCount * recordSize + context.yOffset(), 4, QByteArray( yPtr, 4 ) );
}
// We need the Z value regardless of the node's offset
qint32 iz = *reinterpret_cast< const qint32 * >( ptr + i * recordSize + context.zOffset() );
if ( offsetDifference.z() != 0 )
{
iz -= std::lround( offsetDifference.z() / context.scale().z() );
const char *zPtr = reinterpret_cast< const char * >( &iz );
allByteArrays.replace( pointCount * recordSize + context.zOffset(), 4, QByteArray( zPtr, 4 ) );
}
allPairs.append( qMakePair( pointCount, double( iz ) + block->offset().z() ) );

++pointCount;
}
++blockCount;
}

if ( pointCount == 0 )
return 0;

if ( order == QgsPointCloudRenderer::DrawOrder::BottomToTop )
std::sort( allPairs.begin(), allPairs.end(), []( QPair<int, double> a, QPair<int, double> b ) { return a.second < b.second; } );
else if ( order == QgsPointCloudRenderer::DrawOrder::TopToBottom )
std::sort( allPairs.begin(), allPairs.end(), []( QPair<int, double> a, QPair<int, double> b ) { return a.second > b.second; } );

// Now we can reconstruct a byte array sorted by Z value
QByteArray sortedByteArray;
for ( QPair<int, double> pair : allPairs )
sortedByteArray.append( allByteArrays.mid( pair.first * recordSize, recordSize ) );

std::unique_ptr<QgsPointCloudBlock> bigBlock { new QgsPointCloudBlock( pointCount,
blockAttributes,
sortedByteArray,
blockScale,
blockOffset ) };

QgsVector3D contextScale = context.scale();
QgsVector3D contextOffset = context.offset();

context.setScale( bigBlock->scale() );
context.setOffset( bigBlock->offset() );
context.setAttributes( bigBlock->attributes() );

mRenderer->renderBlock( bigBlock.get(), context );

context.setScale( contextScale );
context.setOffset( contextOffset );

return blockCount;
}

bool QgsPointCloudLayerRenderer::forceRasterRender() const
{
// unless we are using the extent only renderer, point cloud layers should always be rasterized -- we don't want to export points as vectors
Expand Down
2 changes: 1 addition & 1 deletion src/core/pointcloud/qgspointcloudlayerrenderer.h
Expand Up @@ -72,9 +72,9 @@ class CORE_EXPORT QgsPointCloudLayerRenderer: public QgsMapLayerRenderer

private:
QVector<IndexedPointCloudNode> traverseTree( const QgsPointCloudIndex *pc, const QgsRenderContext &context, IndexedPointCloudNode n, double maxErrorPixels, double nodeErrorPixels );

int renderNodesSync( const QVector<IndexedPointCloudNode> &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled );
int renderNodesAsync( const QVector<IndexedPointCloudNode> &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled );
int renderNodesSorted( const QVector<IndexedPointCloudNode> &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled, QgsPointCloudRenderer::DrawOrder order );

QgsPointCloudLayer *mLayer = nullptr;

Expand Down
13 changes: 13 additions & 0 deletions src/core/pointcloud/qgspointcloudrenderer.cpp
Expand Up @@ -162,6 +162,7 @@ void QgsPointCloudRenderer::copyCommonProperties( QgsPointCloudRenderer *destina
destination->setMaximumScreenError( mMaximumScreenError );
destination->setMaximumScreenErrorUnit( mMaximumScreenErrorUnit );
destination->setPointSymbol( mPointSymbol );
destination->setDrawOrder2d( mDrawOrder2d );
}

void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element, const QgsReadWriteContext & )
Expand All @@ -173,6 +174,7 @@ void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element,
mMaximumScreenError = element.attribute( QStringLiteral( "maximumScreenError" ), QStringLiteral( "0.3" ) ).toDouble();
mMaximumScreenErrorUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "maximumScreenErrorUnit" ), QStringLiteral( "MM" ) ) );
mPointSymbol = static_cast< PointSymbol >( element.attribute( QStringLiteral( "pointSymbol" ), QStringLiteral( "0" ) ).toInt() );
mDrawOrder2d = static_cast< DrawOrder >( element.attribute( QStringLiteral( "drawOrder2d" ), QStringLiteral( "0" ) ).toInt() );
}

void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const QgsReadWriteContext & ) const
Expand All @@ -184,6 +186,7 @@ void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const Qg
element.setAttribute( QStringLiteral( "maximumScreenError" ), qgsDoubleToString( mMaximumScreenError ) );
element.setAttribute( QStringLiteral( "maximumScreenErrorUnit" ), QgsUnitTypes::encodeUnit( mMaximumScreenErrorUnit ) );
element.setAttribute( QStringLiteral( "pointSymbol" ), QString::number( mPointSymbol ) );
element.setAttribute( QStringLiteral( "drawOrder2d" ), QString::number( mDrawOrder2d ) );
}

QgsPointCloudRenderer::PointSymbol QgsPointCloudRenderer::pointSymbol() const
Expand All @@ -196,6 +199,16 @@ void QgsPointCloudRenderer::setPointSymbol( PointSymbol symbol )
mPointSymbol = symbol;
}

QgsPointCloudRenderer::DrawOrder QgsPointCloudRenderer::drawOrder2d() const
{
return mDrawOrder2d;
}

void QgsPointCloudRenderer::setDrawOrder2d( DrawOrder order )
{
mDrawOrder2d = order;
}

QVector<QVariantMap> QgsPointCloudRenderer::identify( QgsPointCloudLayer *layer, const QgsRenderContext &renderContext, const QgsGeometry &geometry, double toleranceForPointIdentification )
{
QVector<QVariantMap> selectedPoints;
Expand Down
25 changes: 25 additions & 0 deletions src/core/pointcloud/qgspointcloudrenderer.h
Expand Up @@ -272,6 +272,16 @@ class CORE_EXPORT QgsPointCloudRenderer
Circle, //!< Renders points as circles
};

/**
* Pointcloud rendering order for 2d views
*/
enum DrawOrder
{
Unchanged, //!< Draw points in the order they are stored
BottomToTop, //!< Draw points with larger Z values last
TopToBottom, //!< Draw points with larger Z values first
};

/**
* Constructor for QgsPointCloudRenderer.
*/
Expand Down Expand Up @@ -436,6 +446,20 @@ class CORE_EXPORT QgsPointCloudRenderer
*/
const QgsMapUnitScale &pointSizeMapUnitScale() const { return mPointSizeMapUnitScale; }

/**
* Returns the drawing order used by the renderer for drawing points.
*
* \see setDrawOrder2d()
*/
DrawOrder drawOrder2d() const;

/**
* Sets the drawing \a order used by the renderer for drawing points.
*
* \see drawOrder2d()
*/
void setDrawOrder2d( DrawOrder order );

/**
* Returns the symbol used by the renderer for drawing points.
*
Expand Down Expand Up @@ -592,6 +616,7 @@ class CORE_EXPORT QgsPointCloudRenderer

PointSymbol mPointSymbol = Square;
int mPainterPenWidth = 1;
DrawOrder mDrawOrder2d = Unchanged;
};

#endif // QGSPOINTCLOUDRENDERER_H
8 changes: 7 additions & 1 deletion src/gui/pointcloud/qgspointcloudrendererpropertieswidget.cpp
Expand Up @@ -101,6 +101,10 @@ QgsPointCloudRendererPropertiesWidget::QgsPointCloudRendererPropertiesWidget( Qg
connect( mPointSizeSpinBox, qOverload<double>( &QgsDoubleSpinBox::valueChanged ), this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged );
connect( mPointSizeUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged );

mDrawOrderComboBox->addItem( tr( "Default" ), QgsPointCloudRenderer::DrawOrder::Unchanged );
mDrawOrderComboBox->addItem( tr( "Bottom to top" ), QgsPointCloudRenderer::DrawOrder::BottomToTop );
mDrawOrderComboBox->addItem( tr( "Top to bottom" ), QgsPointCloudRenderer::DrawOrder::TopToBottom );

mMaxErrorUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
mMaxErrorSpinBox->setClearValue( 0.3 );
Expand All @@ -109,7 +113,7 @@ QgsPointCloudRendererPropertiesWidget::QgsPointCloudRendererPropertiesWidget( Qg
connect( mMaxErrorUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged );

connect( mPointStyleComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged );

connect( mDrawOrderComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged );
syncToLayer( layer );
}

Expand Down Expand Up @@ -148,6 +152,7 @@ void QgsPointCloudRendererPropertiesWidget::syncToLayer( QgsMapLayer *layer )
mPointSizeUnitWidget->setMapUnitScale( mLayer->renderer()->pointSizeMapUnitScale() );

mPointStyleComboBox->setCurrentIndex( mPointStyleComboBox->findData( mLayer->renderer()->pointSymbol() ) );
mDrawOrderComboBox->setCurrentIndex( mDrawOrderComboBox->findData( mLayer->renderer()->drawOrder2d() ) );

mMaxErrorSpinBox->setValue( mLayer->renderer()->maximumScreenError() );
mMaxErrorUnitWidget->setUnit( mLayer->renderer()->maximumScreenErrorUnit() );
Expand Down Expand Up @@ -184,6 +189,7 @@ void QgsPointCloudRendererPropertiesWidget::apply()

mLayer->renderer()->setMaximumScreenError( mMaxErrorSpinBox->value() );
mLayer->renderer()->setMaximumScreenErrorUnit( mMaxErrorUnitWidget->unit() );
mLayer->renderer()->setDrawOrder2d( static_cast< QgsPointCloudRenderer::DrawOrder >( mDrawOrderComboBox->currentData().toInt() ) );
}

void QgsPointCloudRendererPropertiesWidget::rendererChanged()
Expand Down

0 comments on commit be99a16

Please sign in to comment.