Skip to content
Permalink
Browse files

[pointclouds] Add an elevation tab in layer properties, and allow

users to set a manual offset to apply to point cloud z values

This allows for adjustment of the elevation of point clouds, eg so that:
1. They match nicely with point clouds from other datasources
2. Users can use point clouds from sources with arbitrary z values,
eg. a point cloud made by opendronemap without any actual reference
z values available
  • Loading branch information
nyalldawson committed Dec 16, 2020
1 parent 9eea646 commit 1963ef9784b13f62a37572a14ae976ba4e8507b3
@@ -38,6 +38,27 @@ Constructor for QgsPointCloudLayerElevationProperties, with the specified ``pare

virtual QgsDoubleRange calculateZRange( QgsMapLayer *layer ) const;


double zOffset() const;
%Docstring
Returns the z offset, which is a fixed offset amount which should be added to z values from
the layer.

This can be used to correct or manually adjust for incorrect elevation values in a point cloud layer.

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

void setZOffset( double offset );
%Docstring
Sets the z ``offset``, which is a fixed offset amount which will be added to z values from
the layer.

This can be used to correct or manually adjust for incorrect elevation values in a point cloud layer.

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

};

/************************************************************************
@@ -25,12 +25,16 @@ Encapsulates the render context for a 2D point cloud rendering operation.
%End
public:

QgsPointCloudRenderContext( QgsRenderContext &context, const QgsVector3D &scale, const QgsVector3D &offset );
QgsPointCloudRenderContext( QgsRenderContext &context, const QgsVector3D &scale, const QgsVector3D &offset,
double zValueFixedOffset );
%Docstring
Constructor for QgsPointCloudRenderContext.

The ``scale`` and ``offset`` arguments specify the scale and offset of the layer's int32 coordinates
compared to CRS coordinates respectively.

The ``zValueFixedOffset`` argument specifies any constant offset value which must be added to z values
taken from the point cloud index.
%End


@@ -110,6 +114,11 @@ Returns the offset for the y value in a point record.
.. seealso:: :py:func:`yOffset`
%End

double zValueFixedOffset() const;
%Docstring
Returns any constant offset which must be applied to z values taken from the point cloud index.
%End


private:
QgsPointCloudRenderContext( const QgsPointCloudRenderContext &rh );
@@ -25,12 +25,14 @@
#include "qgsapplication.h"
#include "qgs3dsymbolregistry.h"
#include "qgspointcloud3dsymbol.h"
#include "qgspointcloudlayerelevationproperties.h"

#include "qgis.h"

QgsPointCloud3DRenderContext::QgsPointCloud3DRenderContext( const Qgs3DMapSettings &map, std::unique_ptr<QgsPointCloud3DSymbol> symbol )
QgsPointCloud3DRenderContext::QgsPointCloud3DRenderContext( const Qgs3DMapSettings &map, std::unique_ptr<QgsPointCloud3DSymbol> symbol, double zValueFixedOffset )
: Qgs3DRenderContext( map )
, mSymbol( std::move( symbol ) )
, mZValueFixedOffset( zValueFixedOffset )
{

}
@@ -112,7 +114,8 @@ Qt3DCore::QEntity *QgsPointCloudLayer3DRenderer::createEntity( const Qgs3DMapSet
if ( !mSymbol )
return nullptr;

return new QgsPointCloudLayerChunkedEntity( pcl->dataProvider()->index(), map, dynamic_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ), maximumScreenError(), showBoundingBoxes() );
return new QgsPointCloudLayerChunkedEntity( pcl->dataProvider()->index(), map, dynamic_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ), maximumScreenError(), showBoundingBoxes(),
static_cast< const QgsPointCloudLayerElevationProperties * >( pcl->elevationProperties() )->zOffset() );
}

void QgsPointCloudLayer3DRenderer::setSymbol( QgsPointCloud3DSymbol *symbol )
@@ -43,8 +43,14 @@ class _3D_NO_EXPORT QgsPointCloud3DRenderContext : public Qgs3DRenderContext
{
public:

//! Constructor for QgsPointCloud3DRenderContext.
QgsPointCloud3DRenderContext( const Qgs3DMapSettings &map, std::unique_ptr< QgsPointCloud3DSymbol > symbol );
/**
* Constructor for QgsPointCloud3DRenderContext.
*
* The \a zValueFixedOffset argument specifies any constant offset value which must be added to z values
* taken from the point cloud index.
*/
QgsPointCloud3DRenderContext( const Qgs3DMapSettings &map, std::unique_ptr< QgsPointCloud3DSymbol > symbol,
double zValueFixedOffset );

//! QgsPointCloudRenderContext cannot be copied.
QgsPointCloud3DRenderContext( const QgsPointCloud3DRenderContext &rh ) = delete;
@@ -127,13 +133,19 @@ class _3D_NO_EXPORT QgsPointCloud3DRenderContext : public Qgs3DRenderContext
}
}

/**
* Returns any constant offset which must be applied to z values taken from the point cloud index.
*/
double zValueFixedOffset() const { return mZValueFixedOffset; }

private:
#ifdef SIP_RUN
QgsPointCloudRenderContext( const QgsPointCloudRenderContext &rh );
#endif
QgsPointCloudAttributeCollection mAttributes;
std::unique_ptr<QgsPointCloud3DSymbol> mSymbol;
QgsPointCloudCategoryList mFilteredOutCategories;
double mZValueFixedOffset = 0;
};


@@ -46,10 +46,10 @@

///////////////

QgsPointCloudLayerChunkLoader::QgsPointCloudLayerChunkLoader( const QgsPointCloudLayerChunkLoaderFactory *factory, QgsChunkNode *node, std::unique_ptr< QgsPointCloud3DSymbol > symbol )
QgsPointCloudLayerChunkLoader::QgsPointCloudLayerChunkLoader( const QgsPointCloudLayerChunkLoaderFactory *factory, QgsChunkNode *node, std::unique_ptr< QgsPointCloud3DSymbol > symbol, double zValueOffset )
: QgsChunkLoader( node )
, mFactory( factory )
, mContext( factory->mMap, std::move( symbol ) )
, mContext( factory->mMap, std::move( symbol ), zValueOffset )
{
QgsPointCloudIndex *pc = mFactory->mPointCloudIndex;
mContext.setAttributes( pc->attributes() );
@@ -126,9 +126,10 @@ Qt3DCore::QEntity *QgsPointCloudLayerChunkLoader::createEntity( Qt3DCore::QEntit
///////////////


QgsPointCloudLayerChunkLoaderFactory::QgsPointCloudLayerChunkLoaderFactory( const Qgs3DMapSettings &map, QgsPointCloudIndex *pc, QgsPointCloud3DSymbol *symbol )
QgsPointCloudLayerChunkLoaderFactory::QgsPointCloudLayerChunkLoaderFactory( const Qgs3DMapSettings &map, QgsPointCloudIndex *pc, QgsPointCloud3DSymbol *symbol, double zValueOffset )
: mMap( map )
, mPointCloudIndex( pc )
, mZValueOffset( zValueOffset )
{
mSymbol.reset( symbol );
}
@@ -137,14 +138,14 @@ QgsChunkLoader *QgsPointCloudLayerChunkLoaderFactory::createChunkLoader( QgsChun
{
QgsChunkNodeId id = node->tileId();
Q_ASSERT( mPointCloudIndex->hasNode( IndexedPointCloudNode( id.d, id.x, id.y, id.z ) ) );
return new QgsPointCloudLayerChunkLoader( this, node, std::unique_ptr< QgsPointCloud3DSymbol >( static_cast< QgsPointCloud3DSymbol * >( mSymbol->clone() ) ) );
return new QgsPointCloudLayerChunkLoader( this, node, std::unique_ptr< QgsPointCloud3DSymbol >( static_cast< QgsPointCloud3DSymbol * >( mSymbol->clone() ) ), mZValueOffset );
}

QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const Qgs3DMapSettings &map );
QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const Qgs3DMapSettings &map, double zValueOffset );

QgsChunkNode *QgsPointCloudLayerChunkLoaderFactory::createRootNode() const
{
QgsAABB bbox = nodeBoundsToAABB( mPointCloudIndex->nodeBounds( IndexedPointCloudNode( 0, 0, 0, 0 ) ), mPointCloudIndex->offset(), mPointCloudIndex->scale(), mMap );
QgsAABB bbox = nodeBoundsToAABB( mPointCloudIndex->nodeBounds( IndexedPointCloudNode( 0, 0, 0, 0 ) ), mPointCloudIndex->offset(), mPointCloudIndex->scale(), mMap, mZValueOffset );
float error = mPointCloudIndex->nodeError( IndexedPointCloudNode( 0, 0, 0, 0 ) );
return new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), bbox, error );
}
@@ -183,11 +184,11 @@ QVector<QgsChunkNode *> QgsPointCloudLayerChunkLoaderFactory::createChildren( Qg
///////////////


QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const Qgs3DMapSettings &map )
QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const Qgs3DMapSettings &map, double zValueOffset )
{
// TODO: reprojection from layer to map coordinates if needed
QgsVector3D extentMin3D( nodeBounds.xMin() * scale.x() + offset.x(), nodeBounds.yMin() * scale.y() + offset.y(), nodeBounds.zMin() * scale.z() + offset.z() );
QgsVector3D extentMax3D( nodeBounds.xMax() * scale.x() + offset.x(), nodeBounds.yMax() * scale.y() + offset.y(), nodeBounds.zMax() * scale.z() + offset.z() );
QgsVector3D extentMin3D( nodeBounds.xMin() * scale.x() + offset.x(), nodeBounds.yMin() * scale.y() + offset.y(), nodeBounds.zMin() * scale.z() + offset.z() + zValueOffset );
QgsVector3D extentMax3D( nodeBounds.xMax() * scale.x() + offset.x(), nodeBounds.yMax() * scale.y() + offset.y(), nodeBounds.zMax() * scale.z() + offset.z() + zValueOffset );
QgsVector3D worldExtentMin3D = Qgs3DUtils::mapToWorldCoordinates( extentMin3D, map.origin() );
QgsVector3D worldExtentMax3D = Qgs3DUtils::mapToWorldCoordinates( extentMax3D, map.origin() );
QgsAABB rootBbox( worldExtentMin3D.x(), worldExtentMin3D.y(), worldExtentMin3D.z(),
@@ -196,9 +197,9 @@ QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset
}


QgsPointCloudLayerChunkedEntity::QgsPointCloudLayerChunkedEntity( QgsPointCloudIndex *pc, const Qgs3DMapSettings &map, QgsPointCloud3DSymbol *symbol, float maxScreenError, bool showBoundingBoxes )
QgsPointCloudLayerChunkedEntity::QgsPointCloudLayerChunkedEntity( QgsPointCloudIndex *pc, const Qgs3DMapSettings &map, QgsPointCloud3DSymbol *symbol, float maxScreenError, bool showBoundingBoxes, double zValueOffset )
: QgsChunkedEntity( maxScreenError,
new QgsPointCloudLayerChunkLoaderFactory( map, pc, symbol ), true )
new QgsPointCloudLayerChunkLoaderFactory( map, pc, symbol, zValueOffset ), true )
{
setUsingAdditiveStrategy( true );
setShowBoundingBoxes( showBoundingBoxes );
@@ -58,7 +58,7 @@ class QgsPointCloudLayerChunkLoaderFactory : public QgsChunkLoaderFactory
* Constructs the factory
* The factory takes ownership over the passed \a symbol
*/
QgsPointCloudLayerChunkLoaderFactory( const Qgs3DMapSettings &map, QgsPointCloudIndex *pc, QgsPointCloud3DSymbol *symbol );
QgsPointCloudLayerChunkLoaderFactory( const Qgs3DMapSettings &map, QgsPointCloudIndex *pc, QgsPointCloud3DSymbol *symbol, double zValueOffset );

//! Creates loader for the given chunk node. Ownership of the returned is passed to the caller.
virtual QgsChunkLoader *createChunkLoader( QgsChunkNode *node ) const override;
@@ -67,6 +67,7 @@ class QgsPointCloudLayerChunkLoaderFactory : public QgsChunkLoaderFactory
const Qgs3DMapSettings &mMap;
QgsPointCloudIndex *mPointCloudIndex;
std::unique_ptr< QgsPointCloud3DSymbol > mSymbol;
double mZValueOffset = 0;
};


@@ -86,7 +87,7 @@ class QgsPointCloudLayerChunkLoader : public QgsChunkLoader
* Constructs the loader
* QgsPointCloudLayerChunkLoader takes ownership over symbol
*/
QgsPointCloudLayerChunkLoader( const QgsPointCloudLayerChunkLoaderFactory *factory, QgsChunkNode *node, std::unique_ptr< QgsPointCloud3DSymbol > symbol );
QgsPointCloudLayerChunkLoader( const QgsPointCloudLayerChunkLoaderFactory *factory, QgsChunkNode *node, std::unique_ptr< QgsPointCloud3DSymbol > symbol, double zValueOffset );
~QgsPointCloudLayerChunkLoader() override;

virtual void cancel() override;
@@ -115,7 +116,8 @@ class QgsPointCloudLayerChunkedEntity : public QgsChunkedEntity
{
Q_OBJECT
public:
explicit QgsPointCloudLayerChunkedEntity( QgsPointCloudIndex *pc, const Qgs3DMapSettings &map, QgsPointCloud3DSymbol *symbol, float maxScreenError, bool showBoundingBoxes );
explicit QgsPointCloudLayerChunkedEntity( QgsPointCloudIndex *pc, const Qgs3DMapSettings &map, QgsPointCloud3DSymbol *symbol, float maxScreenError, bool showBoundingBoxes,
double zValueOffset );

~QgsPointCloudLayerChunkedEntity();
};
@@ -260,6 +260,7 @@ void QgsSingleColorPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *p

const QgsVector3D scale = pc->scale();
const QgsVector3D offset = pc->offset();
const double zValueOffset = context.zValueFixedOffset();

for ( int i = 0; i < count; ++i )
{
@@ -269,7 +270,7 @@ void QgsSingleColorPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *p

double x = offset.x() + scale.x() * ix;
double y = offset.y() + scale.y() * iy;
double z = offset.z() + scale.z() * iz;
double z = offset.z() + scale.z() * iz + zValueOffset;
QgsVector3D point( x, y, z );
QgsVector3D p = context.map().mapToWorldCoordinates( point );
outNormal.positions.push_back( QVector3D( p.x(), p.y(), p.z() ) );
@@ -314,6 +315,7 @@ void QgsColorRampPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc,
bool attrIsZ = false;
QgsPointCloudAttribute::DataType attributeType = QgsPointCloudAttribute::Float;
int attributeOffset = 0;
const double zValueOffset = context.zValueFixedOffset();
QgsColorRampPointCloud3DSymbol *symbol = dynamic_cast<QgsColorRampPointCloud3DSymbol *>( context.symbol() );
if ( symbol )
{
@@ -369,7 +371,7 @@ void QgsColorRampPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc,

double x = offset.x() + scale.x() * ix;
double y = offset.y() + scale.y() * iy;
double z = offset.z() + scale.z() * iz;
double z = offset.z() + scale.z() * iz + zValueOffset;
QgsVector3D point( x, y, z );

QgsVector3D p = context.map().mapToWorldCoordinates( point );
@@ -451,6 +453,7 @@ void QgsRGBPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const

const QgsVector3D scale = pc->scale();
const QgsVector3D offset = pc->offset();
const double zValueOffset = context.zValueFixedOffset();

QgsContrastEnhancement *redContrastEnhancement = symbol->redContrastEnhancement();
QgsContrastEnhancement *greenContrastEnhancement = symbol->greenContrastEnhancement();
@@ -470,7 +473,7 @@ void QgsRGBPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const
qint32 iz = *( qint32 * )( ptr + i * recordSize + 8 );
double x = offset.x() + scale.x() * ix;
double y = offset.y() + scale.y() * iy;
double z = offset.z() + scale.z() * iz;
double z = offset.z() + scale.z() * iz + zValueOffset;
QgsVector3D point( x, y, z );
QgsVector3D p = context.map().mapToWorldCoordinates( point );

@@ -595,6 +598,8 @@ void QgsClassificationPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex

const QgsVector3D scale = pc->scale();
const QgsVector3D offset = pc->offset();
const double zValueOffset = context.zValueFixedOffset();

QSet<int> filteredOutValues = context.getFilteredOutValues();
for ( int i = 0; i < count; ++i )
{
@@ -604,7 +609,7 @@ void QgsClassificationPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex

double x = offset.x() + scale.x() * ix;
double y = offset.y() + scale.y() * iy;
double z = offset.z() + scale.z() * iz;
double z = offset.z() + scale.z() * iz + zValueOffset;
QgsVector3D point( x, y, z );

QgsVector3D p = context.map().mapToWorldCoordinates( point );
@@ -29,6 +29,7 @@
#include "qgsmaplayerconfigwidget.h"
#include "qgspointcloudattributemodel.h"
#include "qgsdatumtransformdialog.h"
#include "qgspointcloudlayerelevationproperties.h"
#include <QFileDialog>
#include <QMenu>
#include <QMessageBox>
@@ -66,6 +67,8 @@ QgsPointCloudLayerProperties::QgsPointCloudLayerProperties( QgsPointCloudLayer *
metadataFrame->setLayout( layout );
mOptsPage_Metadata->setContentsMargins( 0, 0, 0, 0 );

mOffsetZSpinBox->setClearValue( 0 );

// update based on lyr's current state
syncToLayer();

@@ -146,6 +149,9 @@ void QgsPointCloudLayerProperties::apply()

mLayer->setName( mLayerOrigNameLineEdit->text() );

// elevation tab
static_cast< QgsPointCloudLayerElevationProperties * >( mLayer->elevationProperties() )->setZOffset( mOffsetZSpinBox->value() );

for ( QgsMapLayerConfigWidget *w : mConfigWidgets )
w->apply();

@@ -185,6 +191,9 @@ void QgsPointCloudLayerProperties::syncToLayer()

mCrsSelector->setCrs( mLayer->crs() );

// elevation tab
mOffsetZSpinBox->setValue( static_cast< const QgsPointCloudLayerElevationProperties * >( mLayer->elevationProperties() )->zOffset() );

for ( QgsMapLayerConfigWidget *w : mConfigWidgets )
w->syncToLayer( mLayer );
}
@@ -111,7 +111,7 @@ void QgsPointCloudAttributeByRampRenderer::renderBlock( const QgsPointCloudBlock
if ( applyYOffset )
attributeValue = context.offset().y() + context.scale().y() * attributeValue;
if ( applyZOffset )
attributeValue = context.offset().z() + context.scale().z() * attributeValue;
attributeValue = context.offset().z() + context.scale().z() * attributeValue + context.zValueFixedOffset();

mColorRampShader.shade( attributeValue, &red, &green, &blue, &alpha );
drawPoint( x, y, QColor( red, green, blue, alpha ), context );
@@ -28,14 +28,18 @@ bool QgsPointCloudLayerElevationProperties::hasElevation() const
return true;
}

QDomElement QgsPointCloudLayerElevationProperties::writeXml( QDomElement &, QDomDocument &document, const QgsReadWriteContext & )
QDomElement QgsPointCloudLayerElevationProperties::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & )
{
QDomElement element = document.createElement( QStringLiteral( "elevation" ) );
element.setAttribute( QStringLiteral( "zoffset" ), qgsDoubleToString( mZOffset ) );
parentElement.appendChild( element );
return element;
}

bool QgsPointCloudLayerElevationProperties::readXml( const QDomElement &, const QgsReadWriteContext & )
bool QgsPointCloudLayerElevationProperties::readXml( const QDomElement &element, const QgsReadWriteContext & )
{
QDomElement elevationElement = element.firstChildElement( QStringLiteral( "elevation" ) ).toElement();
mZOffset = elevationElement.attribute( QStringLiteral( "zoffset" ), QStringLiteral( "0" ) ).toDouble();
return true;
}

@@ -56,7 +60,7 @@ QgsDoubleRange QgsPointCloudLayerElevationProperties::calculateZRange( QgsMapLay
const QVariant zMax = pcLayer->dataProvider()->metadataStatistic( QStringLiteral( "Z" ), QgsStatisticalSummary::Max );
if ( zMin.isValid() && zMax.isValid() )
{
return QgsDoubleRange( zMin.toDouble(), zMax.toDouble() );
return QgsDoubleRange( zMin.toDouble() + mZOffset, zMax.toDouble() + mZOffset );
}
}
}

0 comments on commit 1963ef9

Please sign in to comment.
You can’t perform that action at this time.