26#include <QApplication>
32#include "moc_qgspointcloudlayerexporter.cpp"
34using namespace Qt::StringLiterals;
38#include <pdal/StageFactory.hpp>
39#include <pdal/io/BufferReader.hpp>
40#include <pdal/Dimension.hpp>
52 return u
"ESRI Shapefile"_s;
63 : mLayerAttributeCollection( layer->
attributes() )
64 , mIndex( layer->index() )
69 mPointRecordFormat = layer->
dataProvider()->originalMetadata().value( u
"dataformat_id"_s ).toInt( &ok );
71 mPointRecordFormat = 3;
95 mFilterGeometryEngine = std::make_unique<QgsGeos>( geometry );
96 mFilterGeometryEngine->prepareGeometry();
105 QVector< QgsGeometry > allGeometries;
108 if ( selectedFeaturesOnly )
120 allGeometries.append( f.
geometry() );
130 QgsDebugError( u
"Error transforming union of filter layer: %1"_s.arg( cse.
what() ) );
139 mRequestedAttributes.clear();
141 const QVector<QgsPointCloudAttribute> allAttributes = mLayerAttributeCollection.attributes();
145 if ( attribute.name().compare(
'X'_L1, Qt::CaseInsensitive ) &&
146 attribute.name().compare(
'Y'_L1, Qt::CaseInsensitive ) &&
147 attribute.name().compare(
'Z'_L1, Qt::CaseInsensitive ) &&
148 attributeList.contains( attribute.name() ) &&
149 ! mRequestedAttributes.contains( attribute.name() ) )
151 mRequestedAttributes.append( attribute.name() );
158 QStringList allAttributeNames;
159 const QVector<QgsPointCloudAttribute> allAttributes = mLayerAttributeCollection.attributes();
162 allAttributeNames.append( attribute.name() );
169 const QVector<QgsPointCloudAttribute> allAttributes = mLayerAttributeCollection.
attributes();
174 if ( attribute.name().compare(
'X'_L1, Qt::CaseInsensitive ) ||
175 attribute.name().compare(
'Y'_L1, Qt::CaseInsensitive ) ||
176 attribute.name().compare(
'Z'_L1, Qt::CaseInsensitive ) ||
177 mRequestedAttributes.contains( attribute.name(), Qt::CaseInsensitive ) )
179 requestAttributes.
push_back( attribute );
182 return requestAttributes;
185QgsFields QgsPointCloudLayerExporter::outputFields()
187 const QVector<QgsPointCloudAttribute>
attributes = mLayerAttributeCollection.attributes();
190 for (
const QgsPointCloudAttribute &attribute :
attributes )
192 if ( mRequestedAttributes.contains( attribute.name(), Qt::CaseInsensitive ) )
193 fields.
append( QgsField( attribute.name(), attribute.variantType(), attribute.displayType() ) );
201 mMemoryLayer =
nullptr;
206 if ( QApplication::instance()->thread() != QThread::currentThread() )
208 QgsDebugMsgLevel( u
"prepareExport() should better be called from the main thread!"_s, 2 );
219 if ( mExtent.isFinite() )
231 QStringList layerCreationOptions;
240 ExporterMemory exp(
this );
252 ExporterPdal exp(
this );
255 catch ( std::runtime_error &e )
257 setLastError( QString::fromLatin1( e.what() ) );
258 QgsDebugError( u
"PDAL has thrown an exception: {}"_s.arg( e.what() ) );
265 layerCreationOptions << u
"GEOMETRY=AS_XYZ"_s
266 << u
"SEPARATOR=COMMA"_s;
283 ExporterVector exp(
this );
297 mMemoryLayer =
nullptr;
303 const QFileInfo fileInfo( mFilename );
309 QString uri( mFilename );
310 uri +=
"|layername=" + mName;
318 const QFileInfo fileInfo( mFilename );
319 return new QgsVectorLayer( mFilename, fileInfo.completeBaseName(), u
"ogr"_s );
329void QgsPointCloudLayerExporter::ExporterBase::run()
331 QgsRectangle geometryFilterRectangle( -std::numeric_limits<double>::infinity(),
332 -std::numeric_limits<double>::infinity(),
333 std::numeric_limits<double>::infinity(),
334 std::numeric_limits<double>::infinity(),
336 if ( mParent->mFilterGeometryEngine )
343 QVector<QgsPointCloudNodeId> nodes;
344 qint64 pointCount = 0;
345 QQueue<QgsPointCloudNodeId> queue;
346 queue.push_back( mParent->mIndex.root() );
347 while ( !queue.empty() )
349 QgsPointCloudNode node = mParent->mIndex.getNode( queue.front() );
351 const QgsBox3D nodeBounds = node.
bounds();
352 if ( mParent->mExtent.intersects( nodeBounds.
toRectangle() ) &&
353 mParent->mZRange.overlaps( { nodeBounds.zMinimum(), nodeBounds.zMaximum() } ) &&
354 geometryFilterRectangle.intersects( nodeBounds.
toRectangle() ) )
357 nodes.push_back( node.
id() );
359 for (
const QgsPointCloudNodeId &child : node.
children() )
361 queue.push_back( child );
365 const qint64 pointsToExport = mParent->mPointsLimit > 0 ? std::min( mParent->mPointsLimit, pointCount ) : pointCount;
366 QgsPointCloudRequest request;
367 request.
setAttributes( mParent->requestedAttributeCollection() );
368 std::unique_ptr<QgsPointCloudBlock> block =
nullptr;
369 qint64 pointsExported = 0;
370 for (
const QgsPointCloudNodeId &node : nodes )
372 block = mParent->mIndex.nodeData( node, request );
373 const QgsPointCloudAttributeCollection attributesCollection = block->attributes();
374 const char *ptr = block->data();
375 int count = block->pointCount();
377 const QgsVector3D scale = block->scale();
378 const QgsVector3D offset = block->offset();
379 int xOffset = 0, yOffset = 0, zOffset = 0;
383 for (
int i = 0; i < count; ++i )
386 if ( mParent->mFeedback &&
389 if ( pointsToExport > 0 )
391 mParent->mFeedback->setProgress( 100 *
static_cast< float >( pointsExported ) / pointsToExport );
393 if ( mParent->mFeedback->isCanceled() )
395 mParent->setLastError( QObject::tr(
"Canceled by user" ) );
400 if ( pointsExported >= pointsToExport )
410 if ( ! mParent->mZRange.contains( z ) ||
411 ! mParent->mExtent.contains( x, y ) ||
412 ( mParent->mFilterGeometryEngine && ! mParent->mFilterGeometryEngine->contains( x, y ) ) )
419 mParent->mTransform->transformInPlace( x, y, z );
421 handlePoint( x, y, z, attributeMap, pointsExported );
424 catch (
const QgsCsException &cse )
443QgsPointCloudLayerExporter::ExporterMemory::~ExporterMemory()
445 mParent->mMemoryLayer->moveToThread( QApplication::instance()->thread() );
448void QgsPointCloudLayerExporter::ExporterMemory::handlePoint(
double x,
double y,
double z,
const QVariantMap &map,
const qint64 pointNumber )
450 Q_UNUSED( pointNumber )
453 feature.
setGeometry( QgsGeometry(
new QgsPoint( x, y, z ) ) );
454 QgsAttributes featureAttributes;
455 for (
const QString &attribute : std::as_const( mParent->mRequestedAttributes ) )
457 const double val = map[ attribute ].toDouble();
458 featureAttributes.append( val );
461 mFeatures.append( feature );
464void QgsPointCloudLayerExporter::ExporterMemory::handleNode()
466 QgsVectorLayer *vl = qgis::down_cast<QgsVectorLayer *>( mParent->mMemoryLayer );
477void QgsPointCloudLayerExporter::ExporterMemory::handleAll()
491QgsPointCloudLayerExporter::ExporterVector::~ExporterVector()
493 delete mParent->mVectorSink;
494 mParent->mVectorSink =
nullptr;
497void QgsPointCloudLayerExporter::ExporterVector::handlePoint(
double x,
double y,
double z,
const QVariantMap &map,
const qint64 pointNumber )
499 Q_UNUSED( pointNumber )
502 feature.
setGeometry( QgsGeometry(
new QgsPoint( x, y, z ) ) );
503 QgsAttributes featureAttributes;
504 for (
const QString &attribute : std::as_const( mParent->mRequestedAttributes ) )
506 const double val = map[ attribute ].toDouble();
507 featureAttributes.append( val );
510 mFeatures.append( feature );
513void QgsPointCloudLayerExporter::ExporterVector::handleNode()
515 if ( ! mParent->mVectorSink->addFeatures( mFeatures ) )
517 mParent->setLastError( mParent->mVectorSink->lastError() );
522void QgsPointCloudLayerExporter::ExporterVector::handleAll()
534 : mPointFormat( exp->mPointRecordFormat )
538 mOptions.add(
"filename", mParent->mFilename.toStdString() );
539 mOptions.add(
"a_srs", mParent->mTargetCrs.toWkt().toStdString() );
540 mOptions.add(
"minor_version", u
"4"_s.toStdString() );
541 mOptions.add(
"format", QString::number( mPointFormat ).toStdString() );
542 if ( mParent->mTransform->isShortCircuited() )
544 mOptions.add(
"offset_x", QString::number( mParent->mIndex.offset().x() ).toStdString() );
545 mOptions.add(
"offset_y", QString::number( mParent->mIndex.offset().y() ).toStdString() );
546 mOptions.add(
"offset_z", QString::number( mParent->mIndex.offset().z() ).toStdString() );
547 mOptions.add(
"scale_x", QString::number( mParent->mIndex.scale().x() ).toStdString() );
548 mOptions.add(
"scale_y", QString::number( mParent->mIndex.scale().y() ).toStdString() );
549 mOptions.add(
"scale_z", QString::number( mParent->mIndex.scale().z() ).toStdString() );
552 mTable.layout()->registerDim( pdal::Dimension::Id::X );
553 mTable.layout()->registerDim( pdal::Dimension::Id::Y );
554 mTable.layout()->registerDim( pdal::Dimension::Id::Z );
556 mTable.layout()->registerDim( pdal::Dimension::Id::Classification );
557 mTable.layout()->registerDim( pdal::Dimension::Id::Intensity );
558 mTable.layout()->registerDim( pdal::Dimension::Id::ReturnNumber );
559 mTable.layout()->registerDim( pdal::Dimension::Id::NumberOfReturns );
560 mTable.layout()->registerDim( pdal::Dimension::Id::ScanDirectionFlag );
561 mTable.layout()->registerDim( pdal::Dimension::Id::EdgeOfFlightLine );
562 mTable.layout()->registerDim( pdal::Dimension::Id::ScanAngleRank );
563 mTable.layout()->registerDim( pdal::Dimension::Id::UserData );
564 mTable.layout()->registerDim( pdal::Dimension::Id::PointSourceId );
566 if ( mPointFormat == 6 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 9 || mPointFormat == 10 )
568 mTable.layout()->registerDim( pdal::Dimension::Id::ScanChannel );
569 mTable.layout()->registerDim( pdal::Dimension::Id::ClassFlags );
572 if ( mPointFormat != 0 && mPointFormat != 2 )
574 mTable.layout()->registerDim( pdal::Dimension::Id::GpsTime );
577 if ( mPointFormat == 2 || mPointFormat == 3 || mPointFormat == 5 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 10 )
579 mTable.layout()->registerDim( pdal::Dimension::Id::Red );
580 mTable.layout()->registerDim( pdal::Dimension::Id::Green );
581 mTable.layout()->registerDim( pdal::Dimension::Id::Blue );
584 if ( mPointFormat == 8 || mPointFormat == 10 )
586 mTable.layout()->registerDim( pdal::Dimension::Id::Infrared );
589 mView = std::make_shared<pdal::PointView>( mTable );
592void QgsPointCloudLayerExporter::ExporterPdal::handlePoint(
double x,
double y,
double z,
const QVariantMap &map,
const qint64 pointNumber )
594 mView->setField( pdal::Dimension::Id::X, pointNumber, x );
595 mView->setField( pdal::Dimension::Id::Y, pointNumber, y );
596 mView->setField( pdal::Dimension::Id::Z, pointNumber, z );
599 mView->setField( pdal::Dimension::Id::Classification, pointNumber, map[ u
"Classification"_s ].toInt() );
600 mView->setField( pdal::Dimension::Id::Intensity, pointNumber, map[ u
"Intensity"_s ].toInt() );
601 mView->setField( pdal::Dimension::Id::ReturnNumber, pointNumber, map[ u
"ReturnNumber"_s ].toInt() );
602 mView->setField( pdal::Dimension::Id::NumberOfReturns, pointNumber, map[ u
"NumberOfReturns"_s ].toInt() );
603 mView->setField( pdal::Dimension::Id::ScanDirectionFlag, pointNumber, map[ u
"ScanDirectionFlag"_s ].toInt() );
604 mView->setField( pdal::Dimension::Id::EdgeOfFlightLine, pointNumber, map[ u
"EdgeOfFlightLine"_s ].toInt() );
605 mView->setField( pdal::Dimension::Id::ScanAngleRank, pointNumber, map[ u
"ScanAngleRank"_s ].toFloat() );
606 mView->setField( pdal::Dimension::Id::UserData, pointNumber, map[ u
"UserData"_s ].toInt() );
607 mView->setField( pdal::Dimension::Id::PointSourceId, pointNumber, map[ u
"PointSourceId"_s ].toInt() );
609 if ( mPointFormat == 6 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 9 || mPointFormat == 10 )
611 mView->setField( pdal::Dimension::Id::ScanChannel, pointNumber, map[ u
"ScannerChannel"_s ].toInt() );
612 const int classificationFlags = ( map[ u
"Synthetic"_s ].toInt() & 0x01 ) << 0 |
613 ( map[ u
"KeyPoint"_s ].toInt() & 0x01 ) << 1 |
614 ( map[ u
"Withheld"_s ].toInt() & 0x01 ) << 2 |
615 ( map[ u
"Overlap"_s ].toInt() & 0x01 ) << 3;
616 mView->setField( pdal::Dimension::Id::ClassFlags, pointNumber, classificationFlags );
619 if ( mPointFormat != 0 && mPointFormat != 2 )
621 mView->setField( pdal::Dimension::Id::GpsTime, pointNumber, map[ u
"GpsTime"_s ].toDouble() );
624 if ( mPointFormat == 2 || mPointFormat == 3 || mPointFormat == 5 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 10 )
626 mView->setField( pdal::Dimension::Id::Red, pointNumber, map[ u
"Red"_s ].toInt() );
627 mView->setField( pdal::Dimension::Id::Green, pointNumber, map[ u
"Green"_s ].toInt() );
628 mView->setField( pdal::Dimension::Id::Blue, pointNumber, map[ u
"Blue"_s ].toInt() );
631 if ( mPointFormat == 8 || mPointFormat == 10 )
633 mView->setField( pdal::Dimension::Id::Infrared, pointNumber, map[ u
"Infrared"_s ].toInt() );
637void QgsPointCloudLayerExporter::ExporterPdal::handleNode()
642void QgsPointCloudLayerExporter::ExporterPdal::handleAll()
644 pdal::BufferReader reader;
645 reader.addView( mView );
647 pdal::StageFactory factory;
649 pdal::Stage *writer = factory.createStage(
"writers.las" );
651 writer->setInput( reader );
652 writer->setOptions( mOptions );
653 writer->prepare( mTable );
654 writer->execute( mTable );
671 mOwnedFeedback->cancel();
681 mExp->setFeedback( mOwnedFeedback.get() );
@ NoSymbology
Export only data.
@ Reverse
Reverse/inverse transform (from destination to source).
Abstract base class for all geometries.
virtual QgsRectangle boundingBox() const
Returns the minimal bounding box for the geometry.
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
Represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
Custom exception class for Coordinate Reference System related exceptions.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
bool hasGeometry() const
Returns true if the feature has an associated geometry.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
Container of fields for a vector layer.
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
A geometry is the spatial representation of a feature.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters ¶meters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
Base class for all map layer types.
QgsCoordinateReferenceSystem crs
static QgsVectorLayer * createMemoryLayer(const QString &name, const QgsFields &fields, Qgis::WkbType geometryType=Qgis::WkbType::NoGeometry, const QgsCoordinateReferenceSystem &crs=QgsCoordinateReferenceSystem(), bool loadDefaultStyle=true) SIP_FACTORY
Creates a new memory layer using the specified parameters.
A collection of point cloud attributes.
void push_back(const QgsPointCloudAttribute &attribute)
Adds extra attribute.
int pointRecordSize() const
Returns total size of record.
const QgsPointCloudAttribute * find(const QString &attributeName, int &offset) const
Finds the attribute with the name.
QVector< QgsPointCloudAttribute > attributes() const
Returns all attributes.
Attribute for point cloud data pair of name and size in bytes.
DataType
Systems of unit measurement.
static void getPointXYZ(const char *ptr, int i, std::size_t pointRecordSize, int xOffset, QgsPointCloudAttribute::DataType xType, int yOffset, QgsPointCloudAttribute::DataType yType, int zOffset, QgsPointCloudAttribute::DataType zType, const QgsVector3D &indexScale, const QgsVector3D &indexOffset, double &x, double &y, double &z)
Retrieves the x, y, z values for the point at index i.
static QVariantMap getAttributeMap(const char *data, std::size_t recordOffset, const QgsPointCloudAttributeCollection &attributeCollection)
Retrieves all the attributes of a point.
DataType type() const
Returns the data type.
void cancel() override
Notifies the task that it should terminate.
QgsPointCloudLayerExporterTask(QgsPointCloudLayerExporter *exporter)
Constructor for QgsPointCloudLayerExporterTask.
void exportComplete()
Emitted when exporting the layer is successfully completed.
void finished(bool result) override
If the task is managed by a QgsTaskManager, this will be called after the task has finished (whether ...
bool run() override
Performs the task's operation.
Handles exporting point cloud layers to memory layers, OGR supported files and PDAL supported files.
QgsMapLayer * takeExportedLayer()
Gets a pointer to the exported layer.
QgsCoordinateReferenceSystem crs() const
Gets the crs for the exported file.
ExportFormat format() const
Returns the format for the exported file or layer.
void setAttributes(const QStringList &attributes)
Sets the list of point cloud attributes that will be exported.
ExportFormat
Supported export formats for point clouds.
@ Csv
Comma separated values.
@ Las
LAS/LAZ point cloud.
~QgsPointCloudLayerExporter()
void setAllAttributes()
Sets that all attributes will be exported.
bool setFormat(const ExportFormat format)
Sets the format for the exported file.
static QString getOgrDriverName(ExportFormat format)
Gets the OGR driver name for the specified format.
QStringList attributes() const
Gets the list of point cloud attributes that will be exported.
QgsPointCloudLayerExporter(QgsPointCloudLayer *layer)
Constructor for QgsPointCloudLayerExporter, associated with the specified layer.
void prepareExport()
Creates the QgsVectorLayer for exporting to a memory layer, if necessary.
static QList< ExportFormat > supportedFormats()
Gets a list of the supported export formats.
void setFilterGeometry(const QgsAbstractGeometry *geometry)
Sets a spatial filter for points to be exported based on geom in the point cloud's CRS.
void doExport()
Performs the actual exporting operation.
Represents a map layer supporting display of point clouds.
QgsPointCloudDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
QList< QgsPointCloudNodeId > children() const
Returns IDs of child nodes.
qint64 pointCount() const
Returns number of points contained in node data.
QgsPointCloudNodeId id() const
Returns node's ID (unique in index).
QgsBox3D bounds() const
Returns node's bounding cube in CRS coords.
void setAttributes(const QgsPointCloudAttributeCollection &attributes)
Set attributes filter in the request.
A rectangle specified with double values.
virtual void cancel()
Notifies the task that it should terminate.
QgsTask(const QString &description=QString(), QgsTask::Flags flags=AllFlags)
Constructor for QgsTask.
@ CanCancel
Task can be canceled.
void setProgress(double progress)
Sets the task's current progress.
QString lastError() const override
Returns the most recent error encountered by the sink, e.g.
bool addFeatures(QgsFeatureList &flist, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) override
Adds a list of features to the sink.
Options to pass to QgsVectorFileWriter::writeAsVectorFormat().
QString driverName
OGR driver to use.
QString layerName
Layer name. If let empty, it will be derived from the filename.
QStringList layerOptions
List of OGR layer creation options.
Qgis::FeatureSymbologyExport symbologyExport
Symbology to export.
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile
Action on existing file.
QStringList datasourceOptions
List of OGR data source creation options.
QgsFeedback * feedback
Optional feedback object allowing cancellation of layer save.
static QStringList defaultLayerOptions(const QString &driverName)
Returns a list of the default layer options for a specified driver.
static QgsVectorFileWriter * create(const QString &fileName, const QgsFields &fields, Qgis::WkbType geometryType, const QgsCoordinateReferenceSystem &srs, const QgsCoordinateTransformContext &transformContext, const QgsVectorFileWriter::SaveVectorOptions &options, QgsFeatureSink::SinkFlags sinkFlags=QgsFeatureSink::SinkFlags(), QString *newFilename=nullptr, QString *newLayer=nullptr)
Create a new vector file writer.
static QStringList defaultDatasetOptions(const QString &driverName)
Returns a list of the default dataset options for a specified driver.
Represents a vector layer which manages a vector based dataset.
QgsFeatureIterator getSelectedFeatures(QgsFeatureRequest request=QgsFeatureRequest()) const
Returns an iterator of the selected features.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
#define BUILTIN_UNREACHABLE
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)