Skip to content
Permalink
Browse files

Initial implementation of selection tool for point cloud

  • Loading branch information
NEDJIMAbelgacem authored and nyalldawson committed Jan 4, 2021
1 parent 20177be commit 61ee2032832321f7f18a8dad1af68e33b1080cc6
@@ -42,6 +42,7 @@ after selecting a point, performs the identification:
RasterLayer,
MeshLayer,
VectorTileLayer,
PointCloudLayer,
AllLayers
};
typedef QFlags<QgsMapToolIdentify::Type> LayerType;
@@ -89,6 +89,8 @@
#include "qgsjsonutils.h"
#include <nlohmann/json.hpp>

#include "qgspointcloudlayer.h"

QgsIdentifyResultsWebView::QgsIdentifyResultsWebView( QWidget *parent ) : QgsWebView( parent )
{
setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
@@ -512,10 +514,12 @@ void QgsIdentifyResultsDialog::addFeature( const QgsMapToolIdentify::IdentifyRes
case QgsMapLayerType::VectorTileLayer:
addFeature( qobject_cast<QgsVectorTileLayer *>( result.mLayer ), result.mLabel, result.mFields, result.mFeature, result.mDerivedAttributes );
break;

case QgsMapLayerType::PointCloudLayer:
addFeature( qobject_cast<QgsPointCloudLayer *>( result.mLayer ), result.mLabel, result.mAttributes );
break;
case QgsMapLayerType::PluginLayer:
case QgsMapLayerType::AnnotationLayer:
case QgsMapLayerType::PointCloudLayer:

break;
}
}
@@ -1264,6 +1268,41 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorTileLayer *layer,
highlightFeature( featItem );
}

void QgsIdentifyResultsDialog::addFeature( QgsPointCloudLayer *layer,
const QString &label,
const QMap< QString, QString > &attributes )
{
QTreeWidgetItem *layItem = layerItem( layer );

if ( !layItem )
{
layItem = new QTreeWidgetItem( QStringList() << layer->name() << QString::number( lstResults->topLevelItemCount() ) );
layItem->setData( 0, Qt::UserRole, QVariant::fromValue( qobject_cast<QObject *>( layer ) ) );
lstResults->addTopLevelItem( layItem );
QFont boldFont;
boldFont.setBold( true );
layItem->setFont( 0, boldFont );

connect( layer, &QObject::destroyed, this, &QgsIdentifyResultsDialog::layerDestroyed );
connect( layer, &QgsMapLayer::crsChanged, this, &QgsIdentifyResultsDialog::layerDestroyed );
}

QgsIdentifyResultsFeatureItem *featItem = new QgsIdentifyResultsFeatureItem( QgsFields(),
QgsFeature(),
layer->crs(),
QStringList() << label << QString() );

layItem->addChild( featItem );
featItem->setExpanded( true );

// attributes
for ( QMap<QString, QString>::const_iterator it = attributes.begin(); it != attributes.end(); ++it )
{
featItem->addChild( new QTreeWidgetItem( QStringList() << it.key() << it.value() ) );
}
}


void QgsIdentifyResultsDialog::editingToggled()
{
QTreeWidgetItem *layItem = layerItem( sender() );
@@ -166,6 +166,14 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti
const QgsFeature &feature,
const QMap< QString, QString > &derivedAttributes );

/**
* Adds results from point cloud layer
* \since QGIS 3.6
*/
void addFeature( QgsPointCloudLayer *layer,
const QString &label,
const QMap< QString, QString > &attributes );

//! Adds feature from identify results
void addFeature( const QgsMapToolIdentify::IdentifyResult &result );

@@ -31,6 +31,19 @@
#include "qgspointcloudlayerelevationproperties.h"
#include "qgsmessagelog.h"

QgsPointCloudIdentifyResults::QgsPointCloudIdentifyResults( const QMap<QString, QVariant> &results )
: mValid( true )
, mResults( results )
{
}

QgsPointCloudIdentifyResults::QgsPointCloudIdentifyResults( const QgsError &error )
: mError( error )
{
}

//

QgsPointCloudLayerRenderer::QgsPointCloudLayerRenderer( QgsPointCloudLayer *layer, QgsRenderContext &context )
: QgsMapLayerRenderer( layer->id(), &context )
, mLayer( layer )
@@ -181,6 +194,103 @@ bool QgsPointCloudLayerRenderer::forceRasterRender() const
return mRenderer ? mRenderer->type() != QLatin1String( "extent" ) : false;
}

QVector<QMap<QString, QString>> QgsPointCloudLayerRenderer::identify( const QgsGeometry &geometry, const QgsIdentifyContext &identifyContext )
{
QVector<QMap<QString, QString>> acceptedPoints;

QgsPointCloudIndex *index = mLayer->dataProvider()->index();
const IndexedPointCloudNode root = index->root();
QgsPointCloudRenderContext context( *renderContext(), index->scale(), index->offset() );

const float maximumError = context.renderContext().convertToPainterUnits( mRenderer->maximumScreenError(), mRenderer->maximumScreenErrorUnit() );// in pixels

const QgsRectangle rootNodeExtentLayerCoords = index->nodeMapExtent( root );
QgsRectangle rootNodeExtentMapCoords;
try
{
rootNodeExtentMapCoords = context.renderContext().coordinateTransform().transformBoundingBox( rootNodeExtentLayerCoords );
}
catch ( QgsCsException & )
{
QgsDebugMsg( QStringLiteral( "Could not transform node extent to map CRS" ) );
rootNodeExtentMapCoords = rootNodeExtentLayerCoords;
}

const float rootErrorInMapCoordinates = rootNodeExtentMapCoords.width() / index->span(); // in map coords

double mapUnitsPerPixel = context.renderContext().mapToPixel().mapUnitsPerPixel();
if ( ( rootErrorInMapCoordinates < 0.0 ) || ( mapUnitsPerPixel < 0.0 ) || ( maximumError < 0.0 ) )
{
QgsDebugMsg( QStringLiteral( "invalid screen error" ) );
return acceptedPoints;
}
float rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel; // in pixels

QVector<IndexedPointCloudNode> nodes = traverseTree( index, context.renderContext(), root, maximumError, rootErrorPixels, geometry );

QgsPointCloudAttributeCollection attributeCollection = mLayer->attributes();
QgsPointCloudRequest request;
request.setAttributes( attributeCollection );

// TODO: figure out why the reprojection here is invalid
// even thought we get a valid one in the renderer classes
const QgsCoordinateTransform ct = context.renderContext().coordinateTransform();
const bool reproject = ct.isValid();

for ( const IndexedPointCloudNode &n : nodes )
{
std::unique_ptr<QgsPointCloudBlock> block( index->nodeData( n, request ) );

if ( !block )
continue;

context.setAttributes( block->attributes() );

const char *ptr = block->data();
QgsPointCloudAttributeCollection blockAttributes = block->attributes();
const std::size_t recordSize = blockAttributes.pointRecordSize();
for ( int i = 0; i < block->pointCount(); ++i )
{
double x, y, z;
pointXY( context, ptr, i, x, y );
z = pointZ( context, ptr, i );
if ( reproject )
{
try
{
ct.transformInPlace( x, y, z );
}
catch ( QgsCsException & )
{
continue;
}
}
if ( geometry.intersects( QgsGeometry::fromPointXY( QgsPointXY( x, y ) ) ) )
{
QMap<QString, QString> pointAttr;
for ( QgsPointCloudAttribute attr : blockAttributes.attributes() )
{
QString attributeName = attr.name();
int attributeOffset;
blockAttributes.find( attributeName, attributeOffset );
double val = 0.0f;
context.getAttribute( ptr, i * recordSize + attributeOffset, attr.type(), val );
if ( attributeName == "X" )
pointAttr[ attributeName ] = QString::number( x );
else if ( attributeName == "Y" )
pointAttr[ attributeName ] = QString::number( y );
else
pointAttr[ attributeName ] = QString::number( val );
}

acceptedPoints.push_back( pointAttr );
}
}
}

return acceptedPoints;
}

QList<IndexedPointCloudNode> QgsPointCloudLayerRenderer::traverseTree( const QgsPointCloudIndex *pc,
const QgsRenderContext &context,
IndexedPointCloudNode n,
@@ -218,5 +328,36 @@ QList<IndexedPointCloudNode> QgsPointCloudLayerRenderer::traverseTree( const Qgs
return nodes;
}

QVector<IndexedPointCloudNode> QgsPointCloudLayerRenderer::traverseTree( const QgsPointCloudIndex *pc,
const QgsRenderContext &context,
IndexedPointCloudNode n,
float maxErrorPixels,
float nodeErrorPixels,
const QgsGeometry &geom )
{
QVector<IndexedPointCloudNode> nodes;

if ( !geom.intersects( pc->nodeMapExtent( n ) ) )
return nodes;

if ( !context.zRange().isInfinite() && !context.zRange().overlaps( pc->nodeZRange( n ) ) )
return nodes;

nodes.append( n );

float childrenErrorPixels = nodeErrorPixels / 2.0f;
if ( childrenErrorPixels < maxErrorPixels )
return nodes;

const QList<IndexedPointCloudNode> children = pc->nodeChildren( n );
for ( const IndexedPointCloudNode &nn : children )
{
if ( geom.intersects( pc->nodeMapExtent( nn ) ) )
nodes += traverseTree( pc, context, nn, maxErrorPixels, childrenErrorPixels, geom );
}

return nodes;
}

QgsPointCloudLayerRenderer::~QgsPointCloudLayerRenderer() = default;

@@ -25,6 +25,11 @@
#include "qgspointcloudindex.h"
#include "qgsgeometry.h"

#include "qgserror.h"
#include "qgspointcloudindex.h"
#include "qgsidentifycontext.h"
#include "qgspointcloudrenderer.h"

#include <QDomElement>
#include <QString>
#include <QPainter>
@@ -36,6 +41,59 @@ class QgsPointCloudRenderContext;

#define SIP_NO_FILE

class CORE_EXPORT QgsPointCloudIdentifyResults
{
public:

/**
* Constructor for QgsPointCloudIdentifyResults.
*/
QgsPointCloudIdentifyResults() = default;

/**
* \brief Constructor. Creates valid result.
* \param format the result format
* \param results the results
*/
QgsPointCloudIdentifyResults( const QMap<QString, QVariant> &results );

/**
* \brief Constructor. Creates invalid result with error.
* \param error the error
*/
QgsPointCloudIdentifyResults( const QgsError &error );

virtual ~QgsPointCloudIdentifyResults() = default;

//! \brief Returns TRUE if valid
bool isValid() const { return mValid; }

/**
* Returns the identify results. Results are different for each format:
*
* - QgsRaster::IdentifyFormatValue: a map of values for each band, where keys are band numbers (from 1).
* - QgsRaster::IdentifyFormatFeature: a map of WMS sublayer keys and lists of QgsFeatureStore values.
* - QgsRaster::IdentifyFormatHtml: a map of WMS sublayer keys and HTML strings.
*/
QMap<QString, QVariant> results() const { return mResults; }

//! Returns the last error
QgsError error() const { return mError; }

//! Sets the last error
void setError( const QgsError &error ) { mError = error;}

private:
//! \brief Is valid
bool mValid = false;

//! \brief Results
QMap<QString, QVariant> mResults;

//! \brief Error
QgsError mError;
};

/**
* \ingroup core
*
@@ -56,11 +114,14 @@ class CORE_EXPORT QgsPointCloudLayerRenderer: public QgsMapLayerRenderer

bool render() override;
bool forceRasterRender() const override;
QVector<QMap<QString, QString>> identify( const QgsGeometry &geometry, const QgsIdentifyContext &identifyContext );

private:

//! Traverses tree and returns all nodes in specified depth
QList<IndexedPointCloudNode> traverseTree( const QgsPointCloudIndex *pc, const QgsRenderContext &context, IndexedPointCloudNode n, float maxErrorPixels, float nodeErrorPixels );
//! Traverses tree and returns all nodes that intersects with a \a geometry in specified depth
QVector<IndexedPointCloudNode> traverseTree( const QgsPointCloudIndex *pc, const QgsRenderContext &context, IndexedPointCloudNode n, float maxErrorPixels, float nodeErrorPixels, const QgsGeometry &geometry );

QgsPointCloudLayer *mLayer = nullptr;

@@ -75,6 +136,25 @@ class CORE_EXPORT QgsPointCloudLayerRenderer: public QgsMapLayerRenderer
QgsPointCloudAttributeCollection mAttributes;
QgsGeometry mCloudExtent;

/**
* Retrieves the x and y coordinate for the point at index \a i.
*/
static void pointXY( QgsPointCloudRenderContext &context, const char *ptr, int i, double &x, double &y )
{
const qint32 ix = *reinterpret_cast< const qint32 * >( ptr + i * context.pointRecordSize() + context.xOffset() );
const qint32 iy = *reinterpret_cast< const qint32 * >( ptr + i * context.pointRecordSize() + context.yOffset() );
x = context.offset().x() + context.scale().x() * ix;
y = context.offset().y() + context.scale().y() * iy;
}

/**
* Retrieves the z value for the point at index \a i.
*/
static double pointZ( QgsPointCloudRenderContext &context, const char *ptr, int i )
{
const qint32 iz = *reinterpret_cast<const qint32 * >( ptr + i * context.pointRecordSize() + context.zOffset() );
return context.offset().z() + context.scale().z() * iz;
}
};

#endif // QGSPOINTCLOUDLAYERRENDERER_H

0 comments on commit 61ee203

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