mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
2706 lines
100 KiB
C++
2706 lines
100 KiB
C++
/***************************************************************************
|
|
qgsnativealgorithms.cpp
|
|
---------------------
|
|
begin : April 2017
|
|
copyright : (C) 2017 by Nyall Dawson
|
|
email : nyall dot dawson at gmail dot com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "qgsnativealgorithms.h"
|
|
#include "qgsfeatureiterator.h"
|
|
#include "qgsprocessingcontext.h"
|
|
#include "qgsprocessingfeedback.h"
|
|
#include "qgsprocessingutils.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsrasterlayer.h"
|
|
#include "qgsgeometry.h"
|
|
#include "qgsgeometryengine.h"
|
|
#include "qgswkbtypes.h"
|
|
|
|
#include <functional>
|
|
|
|
///@cond PRIVATE
|
|
|
|
QgsNativeAlgorithms::QgsNativeAlgorithms( QObject *parent )
|
|
: QgsProcessingProvider( parent )
|
|
{}
|
|
|
|
QIcon QgsNativeAlgorithms::icon() const
|
|
{
|
|
return QgsApplication::getThemeIcon( QStringLiteral( "/providerQgis.svg" ) );
|
|
}
|
|
|
|
QString QgsNativeAlgorithms::svgIconPath() const
|
|
{
|
|
return QgsApplication::iconPath( QStringLiteral( "providerQgis.svg" ) );
|
|
}
|
|
|
|
QString QgsNativeAlgorithms::id() const
|
|
{
|
|
return QStringLiteral( "native" );
|
|
}
|
|
|
|
QString QgsNativeAlgorithms::name() const
|
|
{
|
|
return tr( "QGIS (native c++)" );
|
|
}
|
|
|
|
bool QgsNativeAlgorithms::supportsNonFileBasedOutput() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void QgsNativeAlgorithms::loadAlgorithms()
|
|
{
|
|
addAlgorithm( new QgsBufferAlgorithm() );
|
|
addAlgorithm( new QgsCentroidAlgorithm() );
|
|
addAlgorithm( new QgsClipAlgorithm() );
|
|
addAlgorithm( new QgsDissolveAlgorithm() );
|
|
addAlgorithm( new QgsCollectAlgorithm() );
|
|
addAlgorithm( new QgsExtractByAttributeAlgorithm() );
|
|
addAlgorithm( new QgsExtractByExpressionAlgorithm() );
|
|
addAlgorithm( new QgsMultipartToSinglepartAlgorithm() );
|
|
addAlgorithm( new QgsSubdivideAlgorithm() );
|
|
addAlgorithm( new QgsTransformAlgorithm() );
|
|
addAlgorithm( new QgsRemoveNullGeometryAlgorithm() );
|
|
addAlgorithm( new QgsBoundingBoxAlgorithm() );
|
|
addAlgorithm( new QgsOrientedMinimumBoundingBoxAlgorithm() );
|
|
addAlgorithm( new QgsMinimumEnclosingCircleAlgorithm() );
|
|
addAlgorithm( new QgsConvexHullAlgorithm() );
|
|
addAlgorithm( new QgsPromoteToMultipartAlgorithm() );
|
|
addAlgorithm( new QgsSelectByLocationAlgorithm() );
|
|
addAlgorithm( new QgsExtractByLocationAlgorithm() );
|
|
addAlgorithm( new QgsFixGeometriesAlgorithm() );
|
|
addAlgorithm( new QgsMergeLinesAlgorithm() );
|
|
addAlgorithm( new QgsSaveSelectedFeatures() );
|
|
addAlgorithm( new QgsSmoothAlgorithm() );
|
|
addAlgorithm( new QgsSimplifyAlgorithm() );
|
|
addAlgorithm( new QgsExtractByExtentAlgorithm() );
|
|
addAlgorithm( new QgsExtentToLayerAlgorithm() );
|
|
addAlgorithm( new QgsLineIntersectionAlgorithm() );
|
|
addAlgorithm( new QgsSplitWithLinesAlgorithm() );
|
|
addAlgorithm( new QgsMeanCoordinatesAlgorithm() );
|
|
addAlgorithm( new QgsRasterLayerUniqueValuesReportAlgorithm() );
|
|
}
|
|
|
|
void QgsSaveSelectedFeatures::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Selected features" ), QgsProcessing::TypeVectorPoint ) );
|
|
}
|
|
|
|
QString QgsSaveSelectedFeatures::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm creates a new layer with all the selected features in a given vector layer.\n\n"
|
|
"If the selected layer has no selected features, the newly created layer will be empty." );
|
|
}
|
|
|
|
QgsSaveSelectedFeatures *QgsSaveSelectedFeatures::createInstance() const
|
|
{
|
|
return new QgsSaveSelectedFeatures();
|
|
}
|
|
|
|
QVariantMap QgsSaveSelectedFeatures::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
QgsVectorLayer *selectLayer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
|
|
|
|
QString dest;
|
|
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, selectLayer->fields(), selectLayer->wkbType(), selectLayer->sourceCrs() ) );
|
|
if ( !sink )
|
|
return QVariantMap();
|
|
|
|
|
|
int count = selectLayer->selectedFeatureCount();
|
|
int current = 0;
|
|
double step = count > 0 ? 100.0 / count : 1;
|
|
|
|
QgsFeatureIterator it = selectLayer->getSelectedFeatures();;
|
|
QgsFeature feat;
|
|
while ( it.nextFeature( feat ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
sink->addFeature( feat, QgsFeatureSink::FastInsert );
|
|
|
|
feedback->setProgress( current++ * step );
|
|
}
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
|
|
return outputs;
|
|
}
|
|
|
|
void QgsCentroidAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Centroids" ), QgsProcessing::TypeVectorPoint ) );
|
|
}
|
|
|
|
QString QgsCentroidAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm creates a new point layer, with points representing the centroid of the geometries in an input layer.\n\n"
|
|
"The attributes associated to each point in the output layer are the same ones associated to the original features." );
|
|
}
|
|
|
|
QgsCentroidAlgorithm *QgsCentroidAlgorithm::createInstance() const
|
|
{
|
|
return new QgsCentroidAlgorithm();
|
|
}
|
|
|
|
QgsFeature QgsCentroidAlgorithm::processFeature( const QgsFeature &f, QgsProcessingFeedback *feedback )
|
|
{
|
|
QgsFeature feature = f;
|
|
if ( feature.hasGeometry() )
|
|
{
|
|
feature.setGeometry( feature.geometry().centroid() );
|
|
if ( !feature.geometry() )
|
|
{
|
|
feedback->pushInfo( QObject::tr( "Error calculating centroid for feature %1" ).arg( feature.id() ) );
|
|
}
|
|
}
|
|
return feature;
|
|
}
|
|
//
|
|
// QgsBufferAlgorithm
|
|
//
|
|
|
|
void QgsBufferAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "DISTANCE" ), QObject::tr( "Distance" ), QgsProcessingParameterNumber::Double, 10 ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "SEGMENTS" ), QObject::tr( "Segments" ), QgsProcessingParameterNumber::Integer, 5, false, 1 ) );
|
|
|
|
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "END_CAP_STYLE" ), QObject::tr( "End cap style" ), QStringList() << QObject::tr( "Round" ) << QObject::tr( "Flat" ) << QObject::tr( "Square" ), false ) );
|
|
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "JOIN_STYLE" ), QObject::tr( "Join style" ), QStringList() << QObject::tr( "Round" ) << QObject::tr( "Miter" ) << QObject::tr( "Bevel" ), false ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "MITER_LIMIT" ), QObject::tr( "Miter limit" ), QgsProcessingParameterNumber::Double, 2, false, 1 ) );
|
|
|
|
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "DISSOLVE" ), QObject::tr( "Dissolve result" ), false ) );
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Buffered" ), QgsProcessing::TypeVectorPolygon ) );
|
|
}
|
|
|
|
QString QgsBufferAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm computes a buffer area for all the features in an input layer, using a fixed or dynamic distance.\n\n"
|
|
"The segments parameter controls the number of line segments to use to approximate a quarter circle when creating rounded offsets.\n\n"
|
|
"The end cap style parameter controls how line endings are handled in the buffer.\n\n"
|
|
"The join style parameter specifies whether round, miter or beveled joins should be used when offsetting corners in a line.\n\n"
|
|
"The miter limit parameter is only applicable for miter join styles, and controls the maximum distance from the offset curve to use when creating a mitered join." );
|
|
}
|
|
|
|
QgsBufferAlgorithm *QgsBufferAlgorithm::createInstance() const
|
|
{
|
|
return new QgsBufferAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsBufferAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
if ( !source )
|
|
return QVariantMap();
|
|
|
|
QString dest;
|
|
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), QgsWkbTypes::Polygon, source->sourceCrs() ) );
|
|
if ( !sink )
|
|
return QVariantMap();
|
|
|
|
// fixed parameters
|
|
bool dissolve = parameterAsBool( parameters, QStringLiteral( "DISSOLVE" ), context );
|
|
int segments = parameterAsInt( parameters, QStringLiteral( "SEGMENTS" ), context );
|
|
QgsGeometry::EndCapStyle endCapStyle = static_cast< QgsGeometry::EndCapStyle >( 1 + parameterAsInt( parameters, QStringLiteral( "END_CAP_STYLE" ), context ) );
|
|
QgsGeometry::JoinStyle joinStyle = static_cast< QgsGeometry::JoinStyle>( 1 + parameterAsInt( parameters, QStringLiteral( "JOIN_STYLE" ), context ) );
|
|
double miterLimit = parameterAsDouble( parameters, QStringLiteral( "MITER_LIMIT" ), context );
|
|
double bufferDistance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
|
|
bool dynamicBuffer = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) );
|
|
const QgsProcessingParameterDefinition *distanceParamDef = parameterDefinition( QStringLiteral( "DISTANCE" ) );
|
|
|
|
long count = source->featureCount();
|
|
|
|
QgsFeature f;
|
|
QgsFeatureIterator it = source->getFeatures();
|
|
|
|
double step = count > 0 ? 100.0 / count : 1;
|
|
int current = 0;
|
|
|
|
QList< QgsGeometry > bufferedGeometriesForDissolve;
|
|
QgsAttributes dissolveAttrs;
|
|
|
|
while ( it.nextFeature( f ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
if ( dissolveAttrs.isEmpty() )
|
|
dissolveAttrs = f.attributes();
|
|
|
|
QgsFeature out = f;
|
|
if ( out.hasGeometry() )
|
|
{
|
|
if ( dynamicBuffer )
|
|
{
|
|
context.expressionContext().setFeature( f );
|
|
bufferDistance = QgsProcessingParameters::parameterAsDouble( distanceParamDef, parameters, context );
|
|
}
|
|
|
|
QgsGeometry outputGeometry = f.geometry().buffer( bufferDistance, segments, endCapStyle, joinStyle, miterLimit );
|
|
if ( !outputGeometry )
|
|
{
|
|
QgsMessageLog::logMessage( QObject::tr( "Error calculating buffer for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING );
|
|
}
|
|
if ( dissolve )
|
|
bufferedGeometriesForDissolve << outputGeometry;
|
|
else
|
|
out.setGeometry( outputGeometry );
|
|
}
|
|
|
|
if ( !dissolve )
|
|
sink->addFeature( out, QgsFeatureSink::FastInsert );
|
|
|
|
feedback->setProgress( current * step );
|
|
current++;
|
|
}
|
|
|
|
if ( dissolve )
|
|
{
|
|
QgsGeometry finalGeometry = QgsGeometry::unaryUnion( bufferedGeometriesForDissolve );
|
|
QgsFeature f;
|
|
f.setGeometry( finalGeometry );
|
|
f.setAttributes( dissolveAttrs );
|
|
sink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
}
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
|
|
return outputs;
|
|
}
|
|
|
|
|
|
void QgsDissolveAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
|
addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Unique ID fields" ), QVariant(),
|
|
QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true, true ) );
|
|
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Dissolved" ) ) );
|
|
}
|
|
|
|
QString QgsDissolveAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm takes a polygon or line vector layer and combines their geometries into new geometries. One or more attributes can "
|
|
"be specified to dissolve only geometries belonging to the same class (having the same value for the specified attributes), alternatively "
|
|
"all geometries can be dissolved.\n\n"
|
|
"All output geometries will be converted to multi geometries. "
|
|
"In case the input is a polygon layer, common boundaries of adjacent polygons being dissolved will get erased." );
|
|
}
|
|
|
|
QgsDissolveAlgorithm *QgsDissolveAlgorithm::createInstance() const
|
|
{
|
|
return new QgsDissolveAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsCollectorAlgorithm::processCollection( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback,
|
|
const std::function<QgsGeometry( const QList< QgsGeometry >& )> &collector, int maxQueueLength )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
if ( !source )
|
|
return QVariantMap();
|
|
|
|
QString dest;
|
|
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), QgsWkbTypes::multiType( source->wkbType() ), source->sourceCrs() ) );
|
|
|
|
if ( !sink )
|
|
return QVariantMap();
|
|
|
|
QStringList fields = parameterAsFields( parameters, QStringLiteral( "FIELD" ), context );
|
|
|
|
long count = source->featureCount();
|
|
|
|
QgsFeature f;
|
|
QgsFeatureIterator it = source->getFeatures();
|
|
|
|
double step = count > 0 ? 100.0 / count : 1;
|
|
int current = 0;
|
|
|
|
if ( fields.isEmpty() )
|
|
{
|
|
// dissolve all - not using fields
|
|
bool firstFeature = true;
|
|
// we dissolve geometries in blocks using unaryUnion
|
|
QList< QgsGeometry > geomQueue;
|
|
QgsFeature outputFeature;
|
|
|
|
while ( it.nextFeature( f ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( firstFeature )
|
|
{
|
|
outputFeature = f;
|
|
firstFeature = false;
|
|
}
|
|
|
|
if ( f.hasGeometry() && f.geometry() )
|
|
{
|
|
geomQueue.append( f.geometry() );
|
|
if ( maxQueueLength > 0 && geomQueue.length() > maxQueueLength )
|
|
{
|
|
// queue too long, combine it
|
|
QgsGeometry tempOutputGeometry = collector( geomQueue );
|
|
geomQueue.clear();
|
|
geomQueue << tempOutputGeometry;
|
|
}
|
|
}
|
|
|
|
feedback->setProgress( current * step );
|
|
current++;
|
|
}
|
|
|
|
outputFeature.setGeometry( collector( geomQueue ) );
|
|
sink->addFeature( outputFeature, QgsFeatureSink::FastInsert );
|
|
}
|
|
else
|
|
{
|
|
QList< int > fieldIndexes;
|
|
Q_FOREACH ( const QString &field, fields )
|
|
{
|
|
int index = source->fields().lookupField( field );
|
|
if ( index >= 0 )
|
|
fieldIndexes << index;
|
|
}
|
|
|
|
QHash< QVariant, QgsAttributes > attributeHash;
|
|
QHash< QVariant, QList< QgsGeometry > > geometryHash;
|
|
|
|
while ( it.nextFeature( f ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
QVariantList indexAttributes;
|
|
Q_FOREACH ( int index, fieldIndexes )
|
|
{
|
|
indexAttributes << f.attribute( index );
|
|
}
|
|
|
|
if ( !attributeHash.contains( indexAttributes ) )
|
|
{
|
|
// keep attributes of first feature
|
|
attributeHash.insert( indexAttributes, f.attributes() );
|
|
}
|
|
|
|
if ( f.hasGeometry() && f.geometry() )
|
|
{
|
|
geometryHash[ indexAttributes ].append( f.geometry() );
|
|
}
|
|
}
|
|
|
|
int numberFeatures = attributeHash.count();
|
|
QHash< QVariant, QgsAttributes >::const_iterator attrIt = attributeHash.constBegin();
|
|
for ( ; attrIt != attributeHash.constEnd(); ++attrIt )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
QgsFeature outputFeature;
|
|
if ( geometryHash.contains( attrIt.key() ) )
|
|
{
|
|
QgsGeometry geom = collector( geometryHash.value( attrIt.key() ) );
|
|
if ( !geom.isMultipart() )
|
|
{
|
|
geom.convertToMultiType();
|
|
}
|
|
outputFeature.setGeometry( geom );
|
|
}
|
|
outputFeature.setAttributes( attrIt.value() );
|
|
sink->addFeature( outputFeature, QgsFeatureSink::FastInsert );
|
|
|
|
feedback->setProgress( current * 100.0 / numberFeatures );
|
|
current++;
|
|
}
|
|
}
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
|
|
return outputs;
|
|
}
|
|
|
|
QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
return processCollection( parameters, context, feedback, []( const QList< QgsGeometry > &parts )->QgsGeometry
|
|
{
|
|
return QgsGeometry::unaryUnion( parts );
|
|
}, 10000 );
|
|
}
|
|
|
|
QVariantMap QgsCollectAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
return processCollection( parameters, context, feedback, []( const QList< QgsGeometry > &parts )->QgsGeometry
|
|
{
|
|
return QgsGeometry::collectGeometry( parts );
|
|
} );
|
|
}
|
|
|
|
|
|
void QgsClipAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "OVERLAY" ), QObject::tr( "Clip layer" ), QList< int >() << QgsProcessing::TypeVectorPolygon ) );
|
|
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Clipped" ) ) );
|
|
}
|
|
|
|
QString QgsClipAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm clips a vector layer using the polygons of an additional polygons layer. Only the parts of the features "
|
|
"in the input layer that falls within the polygons of the clipping layer will be added to the resulting layer.\n\n"
|
|
"The attributes of the features are not modified, although properties such as area or length of the features will "
|
|
"be modified by the clipping operation. If such properties are stored as attributes, those attributes will have to "
|
|
"be manually updated." );
|
|
}
|
|
|
|
QgsClipAlgorithm *QgsClipAlgorithm::createInstance() const
|
|
{
|
|
return new QgsClipAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > featureSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
if ( !featureSource )
|
|
return QVariantMap();
|
|
|
|
std::unique_ptr< QgsFeatureSource > maskSource( parameterAsSource( parameters, QStringLiteral( "OVERLAY" ), context ) );
|
|
if ( !maskSource )
|
|
return QVariantMap();
|
|
|
|
QString dest;
|
|
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, featureSource->fields(), QgsWkbTypes::multiType( featureSource->wkbType() ), featureSource->sourceCrs() ) );
|
|
|
|
if ( !sink )
|
|
return QVariantMap();
|
|
|
|
// first build up a list of clip geometries
|
|
QList< QgsGeometry > clipGeoms;
|
|
QgsFeatureIterator it = maskSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QList< int >() ).setDestinationCrs( featureSource->sourceCrs() ) );
|
|
QgsFeature f;
|
|
while ( it.nextFeature( f ) )
|
|
{
|
|
if ( f.hasGeometry() )
|
|
clipGeoms << f.geometry();
|
|
}
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
|
|
|
|
if ( clipGeoms.isEmpty() )
|
|
return outputs;
|
|
|
|
// are we clipping against a single feature? if so, we can show finer progress reports
|
|
bool singleClipFeature = false;
|
|
QgsGeometry combinedClipGeom;
|
|
if ( clipGeoms.length() > 1 )
|
|
{
|
|
combinedClipGeom = QgsGeometry::unaryUnion( clipGeoms );
|
|
singleClipFeature = false;
|
|
}
|
|
else
|
|
{
|
|
combinedClipGeom = clipGeoms.at( 0 );
|
|
singleClipFeature = true;
|
|
}
|
|
|
|
// use prepared geometries for faster intersection tests
|
|
std::unique_ptr< QgsGeometryEngine > engine( QgsGeometry::createGeometryEngine( combinedClipGeom.geometry() ) );
|
|
engine->prepareGeometry();
|
|
|
|
QgsFeatureIds testedFeatureIds;
|
|
|
|
int i = -1;
|
|
Q_FOREACH ( const QgsGeometry &clipGeom, clipGeoms )
|
|
{
|
|
i++;
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
QgsFeatureIterator inputIt = featureSource->getFeatures( QgsFeatureRequest().setFilterRect( clipGeom.boundingBox() ) );
|
|
QgsFeatureList inputFeatures;
|
|
QgsFeature f;
|
|
while ( inputIt.nextFeature( f ) )
|
|
inputFeatures << f;
|
|
|
|
if ( inputFeatures.isEmpty() )
|
|
continue;
|
|
|
|
double step = 0;
|
|
if ( singleClipFeature )
|
|
step = 100.0 / inputFeatures.length();
|
|
|
|
int current = 0;
|
|
Q_FOREACH ( const QgsFeature &inputFeature, inputFeatures )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( !inputFeature.hasGeometry() )
|
|
continue;
|
|
|
|
if ( testedFeatureIds.contains( inputFeature.id() ) )
|
|
{
|
|
// don't retest a feature we have already checked
|
|
continue;
|
|
}
|
|
testedFeatureIds.insert( inputFeature.id() );
|
|
|
|
if ( !engine->intersects( inputFeature.geometry().geometry() ) )
|
|
continue;
|
|
|
|
QgsGeometry newGeometry;
|
|
if ( !engine->contains( inputFeature.geometry().geometry() ) )
|
|
{
|
|
QgsGeometry currentGeometry = inputFeature.geometry();
|
|
newGeometry = combinedClipGeom.intersection( currentGeometry );
|
|
if ( newGeometry.wkbType() == QgsWkbTypes::Unknown || QgsWkbTypes::flatType( newGeometry.geometry()->wkbType() ) == QgsWkbTypes::GeometryCollection )
|
|
{
|
|
QgsGeometry intCom = inputFeature.geometry().combine( newGeometry );
|
|
QgsGeometry intSym = inputFeature.geometry().symDifference( newGeometry );
|
|
newGeometry = intCom.difference( intSym );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// clip geometry totally contains feature geometry, so no need to perform intersection
|
|
newGeometry = inputFeature.geometry();
|
|
}
|
|
|
|
QgsFeature outputFeature;
|
|
outputFeature.setGeometry( newGeometry );
|
|
outputFeature.setAttributes( inputFeature.attributes() );
|
|
sink->addFeature( outputFeature, QgsFeatureSink::FastInsert );
|
|
|
|
|
|
if ( singleClipFeature )
|
|
feedback->setProgress( current * step );
|
|
}
|
|
|
|
if ( !singleClipFeature )
|
|
{
|
|
// coarse progress report for multiple clip geometries
|
|
feedback->setProgress( 100.0 * static_cast< double >( i ) / clipGeoms.length() );
|
|
}
|
|
}
|
|
|
|
return outputs;
|
|
}
|
|
|
|
|
|
void QgsTransformAlgorithm::initParameters( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterCrs( QStringLiteral( "TARGET_CRS" ), QObject::tr( "Target CRS" ), QStringLiteral( "EPSG:4326" ) ) );
|
|
}
|
|
|
|
QString QgsTransformAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm reprojects a vector layer. It creates a new layer with the same features "
|
|
"as the input one, but with geometries reprojected to a new CRS.\n\n"
|
|
"Attributes are not modified by this algorithm." );
|
|
}
|
|
|
|
QgsTransformAlgorithm *QgsTransformAlgorithm::createInstance() const
|
|
{
|
|
return new QgsTransformAlgorithm();
|
|
}
|
|
|
|
bool QgsTransformAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
|
|
{
|
|
mDestCrs = parameterAsCrs( parameters, QStringLiteral( "TARGET_CRS" ), context );
|
|
return true;
|
|
}
|
|
|
|
QgsFeature QgsTransformAlgorithm::processFeature( const QgsFeature &f, QgsProcessingFeedback * )
|
|
{
|
|
QgsFeature feature = f;
|
|
if ( !mCreatedTransform )
|
|
{
|
|
mCreatedTransform = true;
|
|
mTransform = QgsCoordinateTransform( sourceCrs(), mDestCrs );
|
|
}
|
|
|
|
if ( feature.hasGeometry() )
|
|
{
|
|
QgsGeometry g = feature.geometry();
|
|
if ( g.transform( mTransform ) == 0 )
|
|
{
|
|
feature.setGeometry( g );
|
|
}
|
|
else
|
|
{
|
|
feature.clearGeometry();
|
|
}
|
|
}
|
|
return feature;
|
|
}
|
|
|
|
|
|
void QgsSubdivideAlgorithm::initParameters( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "MAX_NODES" ), QObject::tr( "Maximum nodes in parts" ), QgsProcessingParameterNumber::Integer,
|
|
256, false, 8, 100000 ) );
|
|
}
|
|
|
|
QString QgsSubdivideAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "Subdivides the geometry. The returned geometry will be a collection containing subdivided parts "
|
|
"from the original geometry, where no part has more then the specified maximum number of nodes.\n\n"
|
|
"This is useful for dividing a complex geometry into less complex parts, which are better able to be spatially "
|
|
"indexed and faster to perform further operations such as intersects on. The returned geometry parts may "
|
|
"not be valid and may contain self-intersections.\n\n"
|
|
"Curved geometries will be segmentized before subdivision." );
|
|
}
|
|
|
|
QgsSubdivideAlgorithm *QgsSubdivideAlgorithm::createInstance() const
|
|
{
|
|
return new QgsSubdivideAlgorithm();
|
|
}
|
|
|
|
QgsWkbTypes::Type QgsSubdivideAlgorithm::outputWkbType( QgsWkbTypes::Type inputWkbType ) const
|
|
{
|
|
return QgsWkbTypes::multiType( inputWkbType );
|
|
}
|
|
|
|
QgsFeature QgsSubdivideAlgorithm::processFeature( const QgsFeature &f, QgsProcessingFeedback *feedback )
|
|
{
|
|
QgsFeature feature = f;
|
|
if ( feature.hasGeometry() )
|
|
{
|
|
feature.setGeometry( feature.geometry().subdivide( mMaxNodes ) );
|
|
if ( !feature.geometry() )
|
|
{
|
|
feedback->reportError( QObject::tr( "Error calculating subdivision for feature %1" ).arg( feature.id() ) );
|
|
}
|
|
}
|
|
return feature;
|
|
}
|
|
|
|
bool QgsSubdivideAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
|
|
{
|
|
mMaxNodes = parameterAsInt( parameters, QStringLiteral( "MAX_NODES" ), context );
|
|
return true;
|
|
}
|
|
|
|
|
|
void QgsMultipartToSinglepartAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
|
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Single parts" ) ) );
|
|
}
|
|
|
|
QString QgsMultipartToSinglepartAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm takes a vector layer with multipart geometries and generates a new one in which all geometries contain "
|
|
"a single part. Features with multipart geometries are divided in as many different features as parts the geometry "
|
|
"contain, and the same attributes are used for each of them." );
|
|
}
|
|
|
|
QgsMultipartToSinglepartAlgorithm *QgsMultipartToSinglepartAlgorithm::createInstance() const
|
|
{
|
|
return new QgsMultipartToSinglepartAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsMultipartToSinglepartAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
if ( !source )
|
|
return QVariantMap();
|
|
|
|
QgsWkbTypes::Type sinkType = QgsWkbTypes::singleType( source->wkbType() );
|
|
|
|
QString dest;
|
|
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(),
|
|
sinkType, source->sourceCrs() ) );
|
|
if ( !sink )
|
|
return QVariantMap();
|
|
|
|
long count = source->featureCount();
|
|
|
|
QgsFeature f;
|
|
QgsFeatureIterator it = source->getFeatures();
|
|
|
|
double step = count > 0 ? 100.0 / count : 1;
|
|
int current = 0;
|
|
while ( it.nextFeature( f ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
QgsFeature out = f;
|
|
if ( out.hasGeometry() )
|
|
{
|
|
QgsGeometry inputGeometry = f.geometry();
|
|
if ( inputGeometry.isMultipart() )
|
|
{
|
|
Q_FOREACH ( const QgsGeometry &g, inputGeometry.asGeometryCollection() )
|
|
{
|
|
out.setGeometry( g );
|
|
sink->addFeature( out, QgsFeatureSink::FastInsert );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sink->addFeature( out, QgsFeatureSink::FastInsert );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// feature with null geometry
|
|
sink->addFeature( out, QgsFeatureSink::FastInsert );
|
|
}
|
|
|
|
feedback->setProgress( current * step );
|
|
current++;
|
|
}
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
|
|
return outputs;
|
|
}
|
|
|
|
|
|
void QgsExtractByExpressionAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
|
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "EXPRESSION" ), QObject::tr( "Expression" ), QVariant(), QStringLiteral( "INPUT" ) ) );
|
|
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Matching features" ) ) );
|
|
QgsProcessingParameterFeatureSink *failOutput = new QgsProcessingParameterFeatureSink( QStringLiteral( "FAIL_OUTPUT" ), QObject::tr( "Non-matching" ),
|
|
QgsProcessing::TypeVectorAnyGeometry, QVariant(), true );
|
|
failOutput->setCreateByDefault( false );
|
|
addParameter( failOutput );
|
|
}
|
|
|
|
QString QgsExtractByExpressionAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm creates a new vector layer that only contains matching features from an input layer. "
|
|
"The criteria for adding features to the resulting layer is based on a QGIS expression.\n\n"
|
|
"For more information about expressions see the <a href =\"{qgisdocs}/user_manual/working_with_vector/expression.html\">user manual</a>" );
|
|
}
|
|
|
|
QgsExtractByExpressionAlgorithm *QgsExtractByExpressionAlgorithm::createInstance() const
|
|
{
|
|
return new QgsExtractByExpressionAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
if ( !source )
|
|
return QVariantMap();
|
|
|
|
QString expressionString = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context );
|
|
|
|
QString matchingSinkId;
|
|
std::unique_ptr< QgsFeatureSink > matchingSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, matchingSinkId, source->fields(),
|
|
source->wkbType(), source->sourceCrs() ) );
|
|
if ( !matchingSink )
|
|
return QVariantMap();
|
|
|
|
QString nonMatchingSinkId;
|
|
std::unique_ptr< QgsFeatureSink > nonMatchingSink( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, nonMatchingSinkId, source->fields(),
|
|
source->wkbType(), source->sourceCrs() ) );
|
|
|
|
QgsExpression expression( expressionString );
|
|
if ( expression.hasParserError() )
|
|
{
|
|
throw QgsProcessingException( expression.parserErrorString() );
|
|
}
|
|
|
|
QgsExpressionContext expressionContext = createExpressionContext( parameters, context );
|
|
|
|
long count = source->featureCount();
|
|
|
|
double step = count > 0 ? 100.0 / count : 1;
|
|
int current = 0;
|
|
|
|
if ( !nonMatchingSink )
|
|
{
|
|
// not saving failing features - so only fetch good features
|
|
QgsFeatureRequest req;
|
|
req.setFilterExpression( expressionString );
|
|
req.setExpressionContext( expressionContext );
|
|
|
|
QgsFeatureIterator it = source->getFeatures( req );
|
|
QgsFeature f;
|
|
while ( it.nextFeature( f ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
matchingSink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
|
|
feedback->setProgress( current * step );
|
|
current++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// saving non-matching features, so we need EVERYTHING
|
|
expressionContext.setFields( source->fields() );
|
|
expression.prepare( &expressionContext );
|
|
|
|
QgsFeatureIterator it = source->getFeatures();
|
|
QgsFeature f;
|
|
while ( it.nextFeature( f ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
expressionContext.setFeature( f );
|
|
if ( expression.evaluate( &expressionContext ).toBool() )
|
|
{
|
|
matchingSink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
}
|
|
else
|
|
{
|
|
nonMatchingSink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
}
|
|
|
|
feedback->setProgress( current * step );
|
|
current++;
|
|
}
|
|
}
|
|
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), matchingSinkId );
|
|
if ( nonMatchingSink )
|
|
outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), nonMatchingSinkId );
|
|
return outputs;
|
|
}
|
|
|
|
|
|
void QgsExtractByAttributeAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
|
addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Selection attribute" ), QVariant(), QStringLiteral( "INPUT" ) ) );
|
|
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "OPERATOR" ), QObject::tr( "Operator" ), QStringList()
|
|
<< QObject::tr( "=" )
|
|
<< QObject::trUtf8( "≠" )
|
|
<< QObject::tr( ">" )
|
|
<< QObject::tr( ">=" )
|
|
<< QObject::tr( "<" )
|
|
<< QObject::tr( "<=" )
|
|
<< QObject::tr( "begins with" )
|
|
<< QObject::tr( "contains" )
|
|
<< QObject::tr( "is null" )
|
|
<< QObject::tr( "is not null" )
|
|
<< QObject::tr( "does not contain" ) ) );
|
|
addParameter( new QgsProcessingParameterString( QStringLiteral( "VALUE" ), QObject::tr( "Value" ), QVariant(), false, true ) );
|
|
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted (attribute)" ) ) );
|
|
QgsProcessingParameterFeatureSink *failOutput = new QgsProcessingParameterFeatureSink( QStringLiteral( "FAIL_OUTPUT" ), QObject::tr( "Extracted (non-matching)" ),
|
|
QgsProcessing::TypeVectorAnyGeometry, QVariant(), true );
|
|
failOutput->setCreateByDefault( false );
|
|
addParameter( failOutput );
|
|
}
|
|
|
|
QString QgsExtractByAttributeAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( " This algorithm creates a new vector layer that only contains matching features from an input layer. "
|
|
"The criteria for adding features to the resulting layer is defined based on the values "
|
|
"of an attribute from the input layer." );
|
|
}
|
|
|
|
QgsExtractByAttributeAlgorithm *QgsExtractByAttributeAlgorithm::createInstance() const
|
|
{
|
|
return new QgsExtractByAttributeAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
if ( !source )
|
|
return QVariantMap();
|
|
|
|
QString fieldName = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
|
|
Operation op = static_cast< Operation >( parameterAsEnum( parameters, QStringLiteral( "OPERATOR" ), context ) );
|
|
QString value = parameterAsString( parameters, QStringLiteral( "VALUE" ), context );
|
|
|
|
QString matchingSinkId;
|
|
std::unique_ptr< QgsFeatureSink > matchingSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, matchingSinkId, source->fields(),
|
|
source->wkbType(), source->sourceCrs() ) );
|
|
if ( !matchingSink )
|
|
return QVariantMap();
|
|
|
|
QString nonMatchingSinkId;
|
|
std::unique_ptr< QgsFeatureSink > nonMatchingSink( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, nonMatchingSinkId, source->fields(),
|
|
source->wkbType(), source->sourceCrs() ) );
|
|
|
|
|
|
int idx = source->fields().lookupField( fieldName );
|
|
QVariant::Type fieldType = source->fields().at( idx ).type();
|
|
|
|
if ( fieldType != QVariant::String && ( op == BeginsWith || op == Contains || op == DoesNotContain ) )
|
|
{
|
|
QString method;
|
|
switch ( op )
|
|
{
|
|
case BeginsWith:
|
|
method = QObject::tr( "begins with" );
|
|
break;
|
|
case Contains:
|
|
method = QObject::tr( "contains" );
|
|
break;
|
|
case DoesNotContain:
|
|
method = QObject::tr( "does not contain" );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
throw QgsProcessingException( QObject::tr( "Operator '%1' can be used only with string fields." ).arg( method ) );
|
|
}
|
|
|
|
QString fieldRef = QgsExpression::quotedColumnRef( fieldName );
|
|
QString quotedVal = QgsExpression::quotedValue( value );
|
|
QString expr;
|
|
switch ( op )
|
|
{
|
|
case Equals:
|
|
expr = QStringLiteral( "%1 = %3" ).arg( fieldRef, quotedVal );
|
|
break;
|
|
case NotEquals:
|
|
expr = QStringLiteral( "%1 != %3" ).arg( fieldRef, quotedVal );
|
|
break;
|
|
case GreaterThan:
|
|
expr = QStringLiteral( "%1 > %3" ).arg( fieldRef, quotedVal );
|
|
break;
|
|
case GreaterThanEqualTo:
|
|
expr = QStringLiteral( "%1 >= %3" ).arg( fieldRef, quotedVal );
|
|
break;
|
|
case LessThan:
|
|
expr = QStringLiteral( "%1 < %3" ).arg( fieldRef, quotedVal );
|
|
break;
|
|
case LessThanEqualTo:
|
|
expr = QStringLiteral( "%1 <= %3" ).arg( fieldRef, quotedVal );
|
|
break;
|
|
case BeginsWith:
|
|
expr = QStringLiteral( "%1 LIKE '%2%'" ).arg( fieldRef, value );
|
|
break;
|
|
case Contains:
|
|
expr = QStringLiteral( "%1 LIKE '%%2%'" ).arg( fieldRef, value );
|
|
break;
|
|
case IsNull:
|
|
expr = QStringLiteral( "%1 IS NULL" ).arg( fieldRef );
|
|
break;
|
|
case IsNotNull:
|
|
expr = QStringLiteral( "%1 IS NOT NULL" ).arg( fieldRef );
|
|
break;
|
|
case DoesNotContain:
|
|
expr = QStringLiteral( "%1 NOT LIKE '%%2%'" ).arg( fieldRef, value );
|
|
break;
|
|
}
|
|
|
|
QgsExpression expression( expr );
|
|
if ( expression.hasParserError() )
|
|
{
|
|
throw QgsProcessingException( expression.parserErrorString() );
|
|
}
|
|
|
|
QgsExpressionContext expressionContext = createExpressionContext( parameters, context );
|
|
|
|
long count = source->featureCount();
|
|
|
|
double step = count > 0 ? 100.0 / count : 1;
|
|
int current = 0;
|
|
|
|
if ( !nonMatchingSink )
|
|
{
|
|
// not saving failing features - so only fetch good features
|
|
QgsFeatureRequest req;
|
|
req.setFilterExpression( expr );
|
|
req.setExpressionContext( expressionContext );
|
|
|
|
QgsFeatureIterator it = source->getFeatures( req );
|
|
QgsFeature f;
|
|
while ( it.nextFeature( f ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
matchingSink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
|
|
feedback->setProgress( current * step );
|
|
current++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// saving non-matching features, so we need EVERYTHING
|
|
expressionContext.setFields( source->fields() );
|
|
expression.prepare( &expressionContext );
|
|
|
|
QgsFeatureIterator it = source->getFeatures();
|
|
QgsFeature f;
|
|
while ( it.nextFeature( f ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
expressionContext.setFeature( f );
|
|
if ( expression.evaluate( &expressionContext ).toBool() )
|
|
{
|
|
matchingSink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
}
|
|
else
|
|
{
|
|
nonMatchingSink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
}
|
|
|
|
feedback->setProgress( current * step );
|
|
current++;
|
|
}
|
|
}
|
|
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), matchingSinkId );
|
|
if ( nonMatchingSink )
|
|
outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), nonMatchingSinkId );
|
|
return outputs;
|
|
}
|
|
|
|
|
|
void QgsRemoveNullGeometryAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
|
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Non null geometries" ),
|
|
QgsProcessing::TypeVectorAnyGeometry, QVariant(), true ) );
|
|
QgsProcessingParameterFeatureSink *nullOutput = new QgsProcessingParameterFeatureSink( QStringLiteral( "NULL_OUTPUT" ), QObject::tr( "Null geometries" ),
|
|
QgsProcessing::TypeVector, QVariant(), true );
|
|
nullOutput->setCreateByDefault( false );
|
|
addParameter( nullOutput );
|
|
}
|
|
|
|
QString QgsRemoveNullGeometryAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm removes any features which do not have a geometry from a vector layer. "
|
|
"All other features will be copied unchanged.\n\n"
|
|
"Optionally, the features with null geometries can be saved to a separate output." );
|
|
}
|
|
|
|
QgsRemoveNullGeometryAlgorithm *QgsRemoveNullGeometryAlgorithm::createInstance() const
|
|
{
|
|
return new QgsRemoveNullGeometryAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsRemoveNullGeometryAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
if ( !source )
|
|
return QVariantMap();
|
|
|
|
QString nonNullSinkId;
|
|
std::unique_ptr< QgsFeatureSink > nonNullSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, nonNullSinkId, source->fields(),
|
|
source->wkbType(), source->sourceCrs() ) );
|
|
|
|
QString nullSinkId;
|
|
std::unique_ptr< QgsFeatureSink > nullSink( parameterAsSink( parameters, QStringLiteral( "NULL_OUTPUT" ), context, nullSinkId, source->fields() ) );
|
|
|
|
long count = source->featureCount();
|
|
|
|
double step = count > 0 ? 100.0 / count : 1;
|
|
int current = 0;
|
|
|
|
QgsFeature f;
|
|
QgsFeatureIterator it = source->getFeatures();
|
|
while ( it.nextFeature( f ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( f.hasGeometry() && nonNullSink )
|
|
{
|
|
nonNullSink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
}
|
|
else if ( !f.hasGeometry() && nullSink )
|
|
{
|
|
nullSink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
}
|
|
|
|
feedback->setProgress( current * step );
|
|
current++;
|
|
}
|
|
|
|
QVariantMap outputs;
|
|
if ( nonNullSink )
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), nonNullSinkId );
|
|
if ( nullSink )
|
|
outputs.insert( QStringLiteral( "NULL_OUTPUT" ), nullSinkId );
|
|
return outputs;
|
|
}
|
|
|
|
|
|
QString QgsBoundingBoxAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm calculates the bounding box (envelope) for each feature in an input layer." ) +
|
|
QStringLiteral( "\n\n" ) +
|
|
QObject::tr( "See the 'Minimum bounding geometry' algorithm for a bounding box calculation which covers the whole layer or grouped subsets of features." );
|
|
}
|
|
|
|
QgsBoundingBoxAlgorithm *QgsBoundingBoxAlgorithm::createInstance() const
|
|
{
|
|
return new QgsBoundingBoxAlgorithm();
|
|
}
|
|
|
|
QgsFields QgsBoundingBoxAlgorithm::outputFields( const QgsFields &inputFields ) const
|
|
{
|
|
QgsFields fields = inputFields;
|
|
fields.append( QgsField( QStringLiteral( "width" ), QVariant::Double, QString(), 20, 6 ) );
|
|
fields.append( QgsField( QStringLiteral( "height" ), QVariant::Double, QString(), 20, 6 ) );
|
|
fields.append( QgsField( QStringLiteral( "area" ), QVariant::Double, QString(), 20, 6 ) );
|
|
fields.append( QgsField( QStringLiteral( "perimeter" ), QVariant::Double, QString(), 20, 6 ) );
|
|
return fields;
|
|
}
|
|
|
|
QgsFeature QgsBoundingBoxAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback * )
|
|
{
|
|
QgsFeature f = feature;
|
|
if ( f.hasGeometry() )
|
|
{
|
|
QgsRectangle bounds = f.geometry().boundingBox();
|
|
QgsGeometry outputGeometry = QgsGeometry::fromRect( bounds );
|
|
f.setGeometry( outputGeometry );
|
|
QgsAttributes attrs = f.attributes();
|
|
attrs << bounds.width()
|
|
<< bounds.height()
|
|
<< bounds.area()
|
|
<< bounds.perimeter();
|
|
f.setAttributes( attrs );
|
|
}
|
|
return f;
|
|
}
|
|
|
|
QString QgsOrientedMinimumBoundingBoxAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm calculates the minimum area rotated rectangle which covers each feature in an input layer." ) +
|
|
QStringLiteral( "\n\n" ) +
|
|
QObject::tr( "See the 'Minimum bounding geometry' algorithm for a oriented bounding box calculation which covers the whole layer or grouped subsets of features." );
|
|
}
|
|
|
|
QgsOrientedMinimumBoundingBoxAlgorithm *QgsOrientedMinimumBoundingBoxAlgorithm::createInstance() const
|
|
{
|
|
return new QgsOrientedMinimumBoundingBoxAlgorithm();
|
|
}
|
|
|
|
QgsFields QgsOrientedMinimumBoundingBoxAlgorithm::outputFields( const QgsFields &inputFields ) const
|
|
{
|
|
QgsFields fields = inputFields;
|
|
fields.append( QgsField( QStringLiteral( "width" ), QVariant::Double, QString(), 20, 6 ) );
|
|
fields.append( QgsField( QStringLiteral( "height" ), QVariant::Double, QString(), 20, 6 ) );
|
|
fields.append( QgsField( QStringLiteral( "angle" ), QVariant::Double, QString(), 20, 6 ) );
|
|
fields.append( QgsField( QStringLiteral( "area" ), QVariant::Double, QString(), 20, 6 ) );
|
|
fields.append( QgsField( QStringLiteral( "perimeter" ), QVariant::Double, QString(), 20, 6 ) );
|
|
return fields;
|
|
}
|
|
|
|
QgsFeature QgsOrientedMinimumBoundingBoxAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback * )
|
|
{
|
|
QgsFeature f = feature;
|
|
if ( f.hasGeometry() )
|
|
{
|
|
double area = 0;
|
|
double angle = 0;
|
|
double width = 0;
|
|
double height = 0;
|
|
QgsGeometry outputGeometry = f.geometry().orientedMinimumBoundingBox( area, angle, width, height );
|
|
f.setGeometry( outputGeometry );
|
|
QgsAttributes attrs = f.attributes();
|
|
attrs << width
|
|
<< height
|
|
<< angle
|
|
<< area
|
|
<< 2 * width + 2 * height;
|
|
f.setAttributes( attrs );
|
|
}
|
|
return f;
|
|
}
|
|
|
|
|
|
void QgsMinimumEnclosingCircleAlgorithm::initParameters( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "SEGMENTS" ), QObject::tr( "Number of segments in circles" ), QgsProcessingParameterNumber::Integer,
|
|
72, false, 8, 100000 ) );
|
|
}
|
|
|
|
QString QgsMinimumEnclosingCircleAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm calculates the minimum enclosing circle which covers each feature in an input layer." ) +
|
|
QStringLiteral( "\n\n" ) +
|
|
QObject::tr( "See the 'Minimum bounding geometry' algorithm for a minimal enclosing circle calculation which covers the whole layer or grouped subsets of features." );
|
|
}
|
|
|
|
QgsMinimumEnclosingCircleAlgorithm *QgsMinimumEnclosingCircleAlgorithm::createInstance() const
|
|
{
|
|
return new QgsMinimumEnclosingCircleAlgorithm();
|
|
}
|
|
|
|
QgsFields QgsMinimumEnclosingCircleAlgorithm::outputFields( const QgsFields &inputFields ) const
|
|
{
|
|
QgsFields fields = inputFields;
|
|
fields.append( QgsField( QStringLiteral( "radius" ), QVariant::Double, QString(), 20, 6 ) );
|
|
fields.append( QgsField( QStringLiteral( "area" ), QVariant::Double, QString(), 20, 6 ) );
|
|
return fields;
|
|
}
|
|
|
|
bool QgsMinimumEnclosingCircleAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
|
|
{
|
|
mSegments = parameterAsInt( parameters, QStringLiteral( "SEGMENTS" ), context );
|
|
return true;
|
|
}
|
|
|
|
QgsFeature QgsMinimumEnclosingCircleAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback * )
|
|
{
|
|
QgsFeature f = feature;
|
|
if ( f.hasGeometry() )
|
|
{
|
|
double radius = 0;
|
|
QgsPointXY center;
|
|
QgsGeometry outputGeometry = f.geometry().minimalEnclosingCircle( center, radius, mSegments );
|
|
f.setGeometry( outputGeometry );
|
|
QgsAttributes attrs = f.attributes();
|
|
attrs << radius
|
|
<< M_PI *radius *radius;
|
|
f.setAttributes( attrs );
|
|
}
|
|
return f;
|
|
}
|
|
|
|
QString QgsConvexHullAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm calculates the convex hull for each feature in an input layer." ) +
|
|
QStringLiteral( "\n\n" ) +
|
|
QObject::tr( "See the 'Minimum bounding geometry' algorithm for a convex hull calculation which covers the whole layer or grouped subsets of features." );
|
|
}
|
|
|
|
QgsConvexHullAlgorithm *QgsConvexHullAlgorithm::createInstance() const
|
|
{
|
|
return new QgsConvexHullAlgorithm();
|
|
}
|
|
|
|
QgsFields QgsConvexHullAlgorithm::outputFields( const QgsFields &inputFields ) const
|
|
{
|
|
QgsFields fields = inputFields;
|
|
fields.append( QgsField( QStringLiteral( "area" ), QVariant::Double, QString(), 20, 6 ) );
|
|
fields.append( QgsField( QStringLiteral( "perimeter" ), QVariant::Double, QString(), 20, 6 ) );
|
|
return fields;
|
|
}
|
|
|
|
QgsFeature QgsConvexHullAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback )
|
|
{
|
|
QgsFeature f = feature;
|
|
if ( f.hasGeometry() )
|
|
{
|
|
QgsGeometry outputGeometry = f.geometry().convexHull();
|
|
if ( !outputGeometry )
|
|
feedback->reportError( outputGeometry.lastError() );
|
|
f.setGeometry( outputGeometry );
|
|
if ( outputGeometry )
|
|
{
|
|
QgsAttributes attrs = f.attributes();
|
|
attrs << outputGeometry.geometry()->area()
|
|
<< outputGeometry.geometry()->perimeter();
|
|
f.setAttributes( attrs );
|
|
}
|
|
}
|
|
return f;
|
|
}
|
|
|
|
|
|
QString QgsPromoteToMultipartAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm takes a vector layer with singlepart geometries and generates a new one in which all geometries are "
|
|
"multipart. Input features which are already multipart features will remain unchanged." ) +
|
|
QStringLiteral( "\n\n" ) +
|
|
QObject::tr( "This algorithm can be used to force geometries to multipart types in order to be compatibility with data providers "
|
|
"with strict singlepart/multipart compatibility checks." ) +
|
|
QStringLiteral( "\n\n" ) +
|
|
QObject::tr( "See the 'Collect geometries' or 'Aggregate' algorithms for alternative options." );
|
|
}
|
|
|
|
QgsPromoteToMultipartAlgorithm *QgsPromoteToMultipartAlgorithm::createInstance() const
|
|
{
|
|
return new QgsPromoteToMultipartAlgorithm();
|
|
}
|
|
|
|
QgsWkbTypes::Type QgsPromoteToMultipartAlgorithm::outputWkbType( QgsWkbTypes::Type inputWkbType ) const
|
|
{
|
|
return QgsWkbTypes::multiType( inputWkbType );
|
|
}
|
|
|
|
QgsFeature QgsPromoteToMultipartAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback * )
|
|
{
|
|
QgsFeature f = feature;
|
|
if ( f.hasGeometry() && !f.geometry().isMultipart() )
|
|
{
|
|
QgsGeometry g = f.geometry();
|
|
g.convertToMultiType();
|
|
f.setGeometry( g );
|
|
}
|
|
return f;
|
|
}
|
|
|
|
|
|
void QgsCollectAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
|
addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Unique ID fields" ), QVariant(),
|
|
QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true, true ) );
|
|
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Collected" ) ) );
|
|
}
|
|
|
|
QString QgsCollectAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm takes a vector layer and collects its geometries into new multipart geometries. One or more attributes can "
|
|
"be specified to collect only geometries belonging to the same class (having the same value for the specified attributes), alternatively "
|
|
"all geometries can be collected." ) +
|
|
QStringLiteral( "\n\n" ) +
|
|
QObject::tr( "All output geometries will be converted to multi geometries, even those with just a single part. "
|
|
"This algorithm does not dissolve overlapping geometries - they will be collected together without modifying the shape of each geometry part." ) +
|
|
QStringLiteral( "\n\n" ) +
|
|
QObject::tr( "See the 'Promote to multipart' or 'Aggregate' algorithms for alternative options." );
|
|
}
|
|
|
|
QgsCollectAlgorithm *QgsCollectAlgorithm::createInstance() const
|
|
{
|
|
return new QgsCollectAlgorithm();
|
|
}
|
|
|
|
|
|
|
|
void QgsSelectByLocationAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
QStringList methods = QStringList() << QObject::tr( "creating new selection" )
|
|
<< QObject::tr( "adding to current selection" )
|
|
<< QObject::tr( "select within current selection" )
|
|
<< QObject::tr( "removing from current selection" );
|
|
|
|
addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Select features from" ),
|
|
QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
|
|
addPredicateParameter();
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INTERSECT" ),
|
|
QObject::tr( "By comparing to the features from" ),
|
|
QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
|
|
|
|
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ),
|
|
QObject::tr( "Modify current selection by" ),
|
|
methods, false, 0 ) );
|
|
}
|
|
|
|
QString QgsSelectByLocationAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm creates a selection in a vector layer. The criteria for selecting "
|
|
"features is based on the spatial relationship between each feature and the features in an additional layer." );
|
|
}
|
|
|
|
QgsSelectByLocationAlgorithm *QgsSelectByLocationAlgorithm::createInstance() const
|
|
{
|
|
return new QgsSelectByLocationAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsSelectByLocationAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
QgsVectorLayer *selectLayer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
|
|
QgsVectorLayer::SelectBehavior method = static_cast< QgsVectorLayer::SelectBehavior >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
|
|
std::unique_ptr< QgsFeatureSource > intersectSource( parameterAsSource( parameters, QStringLiteral( "INTERSECT" ), context ) );
|
|
const QList< int > selectedPredicates = parameterAsEnums( parameters, QStringLiteral( "PREDICATE" ), context );
|
|
|
|
QgsFeatureIds selectedIds;
|
|
auto addToSelection = [&]( const QgsFeature & feature )
|
|
{
|
|
selectedIds.insert( feature.id() );
|
|
};
|
|
process( selectLayer, intersectSource.get(), selectedPredicates, addToSelection, true, feedback );
|
|
|
|
selectLayer->selectByIds( selectedIds, method );
|
|
QVariantMap results;
|
|
results.insert( QStringLiteral( "OUTPUT" ), parameters.value( QStringLiteral( "INPUT" ) ) );
|
|
return results;
|
|
}
|
|
|
|
void QgsLocationBasedAlgorithm::process( QgsFeatureSource *targetSource,
|
|
QgsFeatureSource *intersectSource,
|
|
const QList< int > &selectedPredicates,
|
|
const std::function < void( const QgsFeature & ) > &handleFeatureFunction,
|
|
bool onlyRequireTargetIds,
|
|
QgsFeedback *feedback )
|
|
{
|
|
// build a list of 'reversed' predicates, because in this function
|
|
// we actually test the reverse of what the user wants (allowing us
|
|
// to prepare geometries and optimise the algorithm)
|
|
QList< Predicate > predicates;
|
|
for ( int i : selectedPredicates )
|
|
{
|
|
predicates << reversePredicate( static_cast< Predicate >( i ) );
|
|
}
|
|
|
|
QgsFeatureIds disjointSet;
|
|
if ( predicates.contains( Disjoint ) )
|
|
disjointSet = targetSource->allFeatureIds();
|
|
|
|
QgsFeatureIds foundSet;
|
|
QgsFeatureRequest request = QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() ).setDestinationCrs( targetSource->sourceCrs() );
|
|
QgsFeatureIterator fIt = intersectSource->getFeatures( request );
|
|
double step = intersectSource->featureCount() > 0 ? 100.0 / intersectSource->featureCount() : 1;
|
|
int current = 0;
|
|
QgsFeature f;
|
|
std::unique_ptr< QgsGeometryEngine > engine;
|
|
while ( fIt.nextFeature( f ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
break;
|
|
|
|
if ( !f.hasGeometry() )
|
|
continue;
|
|
|
|
engine.reset();
|
|
|
|
QgsRectangle bbox = f.geometry().boundingBox();
|
|
request = QgsFeatureRequest().setFilterRect( bbox );
|
|
if ( onlyRequireTargetIds )
|
|
request.setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() );
|
|
|
|
QgsFeatureIterator testFeatureIt = targetSource->getFeatures( request );
|
|
QgsFeature testFeature;
|
|
while ( testFeatureIt.nextFeature( testFeature ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
break;
|
|
|
|
if ( foundSet.contains( testFeature.id() ) )
|
|
{
|
|
// already added this one, no need for further tests
|
|
continue;
|
|
}
|
|
if ( predicates.count() == 1 && predicates.at( 0 ) == Disjoint && !disjointSet.contains( testFeature.id() ) )
|
|
{
|
|
// calculating only the disjoint set, and we've already eliminated this feature so no need for further tests
|
|
continue;
|
|
}
|
|
|
|
if ( !engine )
|
|
{
|
|
engine.reset( QgsGeometry::createGeometryEngine( f.geometry().geometry() ) );
|
|
engine->prepareGeometry();
|
|
}
|
|
|
|
for ( Predicate predicate : qgsAsConst( predicates ) )
|
|
{
|
|
bool isMatch = false;
|
|
switch ( predicate )
|
|
{
|
|
case Intersects:
|
|
isMatch = engine->intersects( testFeature.geometry().geometry() );
|
|
break;
|
|
case Contains:
|
|
isMatch = engine->contains( testFeature.geometry().geometry() );
|
|
break;
|
|
case Disjoint:
|
|
if ( engine->intersects( testFeature.geometry().geometry() ) )
|
|
{
|
|
disjointSet.remove( testFeature.id() );
|
|
}
|
|
break;
|
|
case IsEqual:
|
|
isMatch = engine->isEqual( testFeature.geometry().geometry() );
|
|
break;
|
|
case Touches:
|
|
isMatch = engine->touches( testFeature.geometry().geometry() );
|
|
break;
|
|
case Overlaps:
|
|
isMatch = engine->overlaps( testFeature.geometry().geometry() );
|
|
break;
|
|
case Within:
|
|
isMatch = engine->within( testFeature.geometry().geometry() );
|
|
break;
|
|
case Crosses:
|
|
isMatch = engine->crosses( testFeature.geometry().geometry() );
|
|
break;
|
|
}
|
|
if ( isMatch )
|
|
{
|
|
foundSet.insert( testFeature.id() );
|
|
handleFeatureFunction( testFeature );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
current += 1;
|
|
feedback->setProgress( current * step );
|
|
}
|
|
|
|
if ( predicates.contains( Disjoint ) )
|
|
{
|
|
disjointSet = disjointSet.subtract( foundSet );
|
|
QgsFeatureRequest disjointReq = QgsFeatureRequest().setFilterFids( disjointSet );
|
|
if ( onlyRequireTargetIds )
|
|
disjointReq.setSubsetOfAttributes( QgsAttributeList() ).setFlags( QgsFeatureRequest::NoGeometry );
|
|
QgsFeatureIterator disjointIt = targetSource->getFeatures( disjointReq );
|
|
QgsFeature f;
|
|
while ( disjointIt.nextFeature( f ) )
|
|
{
|
|
handleFeatureFunction( f );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgsLocationBasedAlgorithm::addPredicateParameter()
|
|
{
|
|
std::unique_ptr< QgsProcessingParameterEnum > predicateParam( new QgsProcessingParameterEnum( QStringLiteral( "PREDICATE" ),
|
|
QObject::tr( "Where the features (geometric predicate)" ),
|
|
predicateOptionsList(), true, QVariant::fromValue( QList< int >() << 0 ) ) );
|
|
|
|
QVariantMap predicateMetadata;
|
|
QVariantMap widgetMetadata;
|
|
widgetMetadata.insert( QStringLiteral( "class" ), QStringLiteral( "processing.gui.wrappers.EnumWidgetWrapper" ) );
|
|
widgetMetadata.insert( QStringLiteral( "useCheckBoxes" ), true );
|
|
widgetMetadata.insert( QStringLiteral( "columns" ), 2 );
|
|
predicateMetadata.insert( QStringLiteral( "widget_wrapper" ), widgetMetadata );
|
|
predicateParam->setMetadata( predicateMetadata );
|
|
|
|
addParameter( predicateParam.release() );
|
|
}
|
|
|
|
QgsLocationBasedAlgorithm::Predicate QgsLocationBasedAlgorithm::reversePredicate( QgsLocationBasedAlgorithm::Predicate predicate ) const
|
|
{
|
|
switch ( predicate )
|
|
{
|
|
case Intersects:
|
|
return Intersects;
|
|
case Contains:
|
|
return Within;
|
|
case Disjoint:
|
|
return Disjoint;
|
|
case IsEqual:
|
|
return IsEqual;
|
|
case Touches:
|
|
return Touches;
|
|
case Overlaps:
|
|
return Overlaps;
|
|
case Within:
|
|
return Contains;
|
|
case Crosses:
|
|
return Crosses;
|
|
}
|
|
// no warnings
|
|
return Intersects;
|
|
}
|
|
|
|
QStringList QgsLocationBasedAlgorithm::predicateOptionsList() const
|
|
{
|
|
return QStringList() << QObject::tr( "intersect" )
|
|
<< QObject::tr( "contain" )
|
|
<< QObject::tr( "disjoint" )
|
|
<< QObject::tr( "equal" )
|
|
<< QObject::tr( "touch" )
|
|
<< QObject::tr( "overlap" )
|
|
<< QObject::tr( "are within" )
|
|
<< QObject::tr( "cross" );
|
|
}
|
|
|
|
|
|
|
|
void QgsExtractByLocationAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Extract features from" ),
|
|
QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
|
|
addPredicateParameter();
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INTERSECT" ),
|
|
QObject::tr( "By comparing to the features from" ),
|
|
QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
|
|
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted (location)" ) ) );
|
|
}
|
|
|
|
QString QgsExtractByLocationAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm creates a new vector layer that only contains matching features from an "
|
|
"input layer. The criteria for adding features to the resulting layer is defined "
|
|
"based on the spatial relationship between each feature and the features in an additional layer." );
|
|
}
|
|
|
|
QgsExtractByLocationAlgorithm *QgsExtractByLocationAlgorithm::createInstance() const
|
|
{
|
|
return new QgsExtractByLocationAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsExtractByLocationAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
std::unique_ptr< QgsFeatureSource > intersectSource( parameterAsSource( parameters, QStringLiteral( "INTERSECT" ), context ) );
|
|
const QList< int > selectedPredicates = parameterAsEnums( parameters, QStringLiteral( "PREDICATE" ), context );
|
|
QString dest;
|
|
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, input->fields(), input->wkbType(), input->sourceCrs() ) );
|
|
|
|
if ( !sink )
|
|
return QVariantMap();
|
|
|
|
auto addToSink = [&]( const QgsFeature & feature )
|
|
{
|
|
QgsFeature f = feature;
|
|
sink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
};
|
|
process( input.get(), intersectSource.get(), selectedPredicates, addToSink, false, feedback );
|
|
|
|
QVariantMap results;
|
|
results.insert( QStringLiteral( "OUTPUT" ), dest );
|
|
return results;
|
|
}
|
|
|
|
|
|
QString QgsFixGeometriesAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm attempts to create a valid representation of a given invalid geometry without "
|
|
"losing any of the input vertices. Already-valid geometries are returned without further intervention. "
|
|
"Always outputs multi-geometry layer.\n\n"
|
|
"NOTE: M values will be dropped from the output." );
|
|
}
|
|
|
|
QgsFixGeometriesAlgorithm *QgsFixGeometriesAlgorithm::createInstance() const
|
|
{
|
|
return new QgsFixGeometriesAlgorithm();
|
|
}
|
|
|
|
QgsFeature QgsFixGeometriesAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback )
|
|
{
|
|
if ( !feature.hasGeometry() )
|
|
return feature;
|
|
|
|
QgsFeature outputFeature = feature;
|
|
|
|
QgsGeometry outputGeometry = outputFeature.geometry().makeValid();
|
|
if ( !outputGeometry )
|
|
{
|
|
feedback->pushInfo( QObject::tr( "makeValid failed for feature %1 " ).arg( feature.id() ) );
|
|
outputFeature.clearGeometry();
|
|
return outputFeature;
|
|
}
|
|
|
|
if ( outputGeometry.wkbType() == QgsWkbTypes::Unknown ||
|
|
QgsWkbTypes::flatType( outputGeometry.geometry()->wkbType() ) == QgsWkbTypes::GeometryCollection )
|
|
{
|
|
// keep only the parts of the geometry collection with correct type
|
|
const QList< QgsGeometry > tmpGeometries = outputGeometry.asGeometryCollection();
|
|
QList< QgsGeometry > matchingParts;
|
|
for ( const QgsGeometry &g : tmpGeometries )
|
|
{
|
|
if ( g.type() == feature.geometry().type() )
|
|
matchingParts << g;
|
|
}
|
|
if ( !matchingParts.empty() )
|
|
outputGeometry = QgsGeometry::collectGeometry( matchingParts );
|
|
else
|
|
outputGeometry = QgsGeometry();
|
|
}
|
|
|
|
outputGeometry.convertToMultiType();
|
|
outputFeature.setGeometry( outputGeometry );
|
|
return outputFeature;
|
|
}
|
|
|
|
|
|
QString QgsMergeLinesAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm joins all connected parts of MultiLineString geometries into single LineString geometries.\n\n"
|
|
"If any parts of the input MultiLineString geometries are not connected, the resultant "
|
|
"geometry will be a MultiLineString containing any lines which could be merged and any non-connected line parts." );
|
|
}
|
|
|
|
QgsMergeLinesAlgorithm *QgsMergeLinesAlgorithm::createInstance() const
|
|
{
|
|
return new QgsMergeLinesAlgorithm();
|
|
}
|
|
|
|
QgsFeature QgsMergeLinesAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback )
|
|
{
|
|
if ( !feature.hasGeometry() )
|
|
return feature;
|
|
|
|
QgsFeature out = feature;
|
|
QgsGeometry outputGeometry = feature.geometry().mergeLines();
|
|
if ( !outputGeometry )
|
|
feedback->reportError( QObject::tr( "Error merging lines for feature %1" ).arg( feature.id() ) );
|
|
|
|
out.setGeometry( outputGeometry );
|
|
return out;
|
|
}
|
|
|
|
QString QgsSmoothAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm smooths the geometries in a line or polygon layer. It creates a new layer with the "
|
|
"same features as the ones in the input layer, but with geometries containing a higher number of vertices "
|
|
"and corners in the geometries smoothed out.\n\n"
|
|
"The iterations parameter dictates how many smoothing iterations will be applied to each "
|
|
"geometry. A higher number of iterations results in smoother geometries with the cost of "
|
|
"greater number of nodes in the geometries.\n\n"
|
|
"The offset parameter controls how \"tightly\" the smoothed geometries follow the original geometries. "
|
|
"Smaller values results in a tighter fit, and larger values will create a looser fit.\n\n"
|
|
"The maximum angle parameter can be used to prevent smoothing of "
|
|
"nodes with large angles. Any node where the angle of the segments to either "
|
|
"side is larger than this will not be smoothed. For example, setting the maximum "
|
|
"angle to 90 degrees or lower would preserve right angles in the geometry." );
|
|
}
|
|
|
|
QgsSmoothAlgorithm *QgsSmoothAlgorithm::createInstance() const
|
|
{
|
|
return new QgsSmoothAlgorithm();
|
|
}
|
|
|
|
void QgsSmoothAlgorithm::initParameters( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "ITERATIONS" ),
|
|
QObject::tr( "Iterations" ), QgsProcessingParameterNumber::Integer,
|
|
1, false, 1, 10 ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "OFFSET" ),
|
|
QObject::tr( "Offset" ), QgsProcessingParameterNumber::Double,
|
|
0.25, false, 0.0, 0.5 ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "MAX_ANGLE" ),
|
|
QObject::tr( "Maximum node angle to smooth" ), QgsProcessingParameterNumber::Double,
|
|
180.0, false, 0.0, 180.0 ) );
|
|
}
|
|
|
|
bool QgsSmoothAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
|
|
{
|
|
mIterations = parameterAsInt( parameters, QStringLiteral( "ITERATIONS" ), context );
|
|
mOffset = parameterAsDouble( parameters, QStringLiteral( "OFFSET" ), context );
|
|
mMaxAngle = parameterAsDouble( parameters, QStringLiteral( "MAX_ANGLE" ), context );
|
|
return true;
|
|
}
|
|
|
|
QgsFeature QgsSmoothAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback )
|
|
{
|
|
QgsFeature f = feature;
|
|
if ( f.hasGeometry() )
|
|
{
|
|
QgsGeometry outputGeometry = f.geometry().smooth( mIterations, mOffset, -1, mMaxAngle );
|
|
if ( !outputGeometry )
|
|
{
|
|
feedback->reportError( QObject::tr( "Error smoothing geometry %1" ).arg( feature.id() ) );
|
|
}
|
|
f.setGeometry( outputGeometry );
|
|
}
|
|
return f;
|
|
}
|
|
|
|
QString QgsSimplifyAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm simplifies the geometries in a line or polygon layer. It creates a new layer "
|
|
"with the same features as the ones in the input layer, but with geometries containing a lower number of vertices.\n\n"
|
|
"The algorithm gives a choice of simplification methods, including distance based "
|
|
"(the \"Douglas-Peucker\" algorithm), area based (\"Visvalingam\" algorithm) and snapping geometries to a grid." );
|
|
}
|
|
|
|
QgsSimplifyAlgorithm *QgsSimplifyAlgorithm::createInstance() const
|
|
{
|
|
return new QgsSimplifyAlgorithm();
|
|
}
|
|
|
|
void QgsSimplifyAlgorithm::initParameters( const QVariantMap & )
|
|
{
|
|
QStringList methods;
|
|
methods << QObject::tr( "Distance (Douglas-Peucker)" )
|
|
<< QObject::tr( "Snap to grid" )
|
|
<< QObject::tr( "Area (Visvalingam)" );
|
|
|
|
addParameter( new QgsProcessingParameterEnum(
|
|
QStringLiteral( "METHOD" ),
|
|
QObject::tr( "Simplification method" ),
|
|
methods, false, 0 ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TOLERANCE" ),
|
|
QObject::tr( "Tolerance" ), QgsProcessingParameterNumber::Double,
|
|
1.0, false, 0, 10000000.0 ) );
|
|
}
|
|
|
|
bool QgsSimplifyAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
|
|
{
|
|
mTolerance = parameterAsDouble( parameters, QStringLiteral( "TOLERANCE" ), context );
|
|
mMethod = static_cast< QgsMapToPixelSimplifier::SimplifyAlgorithm >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
|
|
if ( mMethod != QgsMapToPixelSimplifier::Distance )
|
|
mSimplifier.reset( new QgsMapToPixelSimplifier( QgsMapToPixelSimplifier::SimplifyGeometry, mTolerance, mMethod ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
QgsFeature QgsSimplifyAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback * )
|
|
{
|
|
QgsFeature f = feature;
|
|
if ( f.hasGeometry() )
|
|
{
|
|
QgsGeometry inputGeometry = f.geometry();
|
|
QgsGeometry outputGeometry;
|
|
if ( mMethod == QgsMapToPixelSimplifier::Distance )
|
|
{
|
|
outputGeometry = inputGeometry.simplify( mTolerance );
|
|
}
|
|
else
|
|
{
|
|
outputGeometry = mSimplifier->simplify( inputGeometry );
|
|
}
|
|
f.setGeometry( outputGeometry );
|
|
}
|
|
return f;
|
|
}
|
|
|
|
|
|
|
|
|
|
void QgsExtractByExtentAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
|
addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
|
|
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "CLIP" ), QObject::tr( "Clip features to extent" ), false ) );
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted" ) ) );
|
|
}
|
|
|
|
QString QgsExtractByExtentAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm creates a new vector layer that only contains features which fall within a specified extent. "
|
|
"Any features which intersect the extent will be included.\n\n"
|
|
"Optionally, feature geometries can also be clipped to the extent. If this option is selected, then the output "
|
|
"geometries will automatically be converted to multi geometries to ensure uniform output geometry types." );
|
|
}
|
|
|
|
QgsExtractByExtentAlgorithm *QgsExtractByExtentAlgorithm::createInstance() const
|
|
{
|
|
return new QgsExtractByExtentAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsExtractByExtentAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > featureSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
if ( !featureSource )
|
|
return QVariantMap();
|
|
|
|
QgsRectangle extent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, featureSource->sourceCrs() );
|
|
bool clip = parameterAsBool( parameters, QStringLiteral( "CLIP" ), context );
|
|
|
|
// if clipping, we force multi output
|
|
QgsWkbTypes::Type outType = clip ? QgsWkbTypes::multiType( featureSource->wkbType() ) : featureSource->wkbType();
|
|
|
|
QString dest;
|
|
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, featureSource->fields(), outType, featureSource->sourceCrs() ) );
|
|
|
|
if ( !sink )
|
|
return QVariantMap();
|
|
|
|
QgsGeometry clipGeom = parameterAsExtentGeometry( parameters, QStringLiteral( "EXTENT" ), context, featureSource->sourceCrs() );
|
|
|
|
double step = featureSource->featureCount() > 0 ? 100.0 / featureSource->featureCount() : 1;
|
|
QgsFeatureIterator inputIt = featureSource->getFeatures( QgsFeatureRequest().setFilterRect( extent ).setFlags( QgsFeatureRequest::ExactIntersect ) );
|
|
QgsFeature f;
|
|
int i = -1;
|
|
while ( inputIt.nextFeature( f ) )
|
|
{
|
|
i++;
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( clip )
|
|
{
|
|
QgsGeometry g = f.geometry().intersection( clipGeom );
|
|
g.convertToMultiType();
|
|
f.setGeometry( g );
|
|
}
|
|
|
|
sink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
feedback->setProgress( i * step );
|
|
}
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
|
|
return outputs;
|
|
}
|
|
|
|
|
|
void QgsExtentToLayerAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterExtent( QStringLiteral( "INPUT" ), QObject::tr( "Extent" ) ) );
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extent" ), QgsProcessing::TypeVectorPolygon ) );
|
|
}
|
|
|
|
QString QgsExtentToLayerAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm creates a new vector layer that contains a single feature with geometry matching an extent parameter.\n\n"
|
|
"It can be used in models to convert an extent into a layer which can be used for other algorithms which require "
|
|
"a layer based input." );
|
|
}
|
|
|
|
QgsExtentToLayerAlgorithm *QgsExtentToLayerAlgorithm::createInstance() const
|
|
{
|
|
return new QgsExtentToLayerAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsExtentToLayerAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
QgsCoordinateReferenceSystem crs = parameterAsExtentCrs( parameters, QStringLiteral( "INPUT" ), context );
|
|
QgsGeometry geom = parameterAsExtentGeometry( parameters, QStringLiteral( "INPUT" ), context );
|
|
|
|
QgsFields fields;
|
|
fields.append( QgsField( QStringLiteral( "id" ), QVariant::Int ) );
|
|
|
|
QString dest;
|
|
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, QgsWkbTypes::Polygon, crs ) );
|
|
if ( !sink )
|
|
return QVariantMap();
|
|
|
|
QgsFeature f;
|
|
f.setAttributes( QgsAttributes() << 1 );
|
|
f.setGeometry( geom );
|
|
sink->addFeature( f, QgsFeatureSink::FastInsert );
|
|
|
|
feedback->setProgress( 100 );
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
|
|
return outputs;
|
|
}
|
|
|
|
void QgsLineIntersectionAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
|
|
QObject::tr( "Input layer" ), QList< int >() << QgsProcessing::TypeVectorLine ) );
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INTERSECT" ),
|
|
QObject::tr( "Intersect layer" ), QList< int >() << QgsProcessing::TypeVectorLine ) );
|
|
|
|
addParameter( new QgsProcessingParameterField(
|
|
QStringLiteral( "INPUT_FIELDS" ),
|
|
QObject::tr( "Input fields to keep (leave empty to keep all fields)" ), QVariant(),
|
|
QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any,
|
|
true, true ) );
|
|
addParameter( new QgsProcessingParameterField(
|
|
QStringLiteral( "INTERSECT_FIELDS" ),
|
|
QObject::tr( "Intersect fields to keep (leave empty to keep all fields)" ), QVariant(),
|
|
QStringLiteral( "INTERSECT" ), QgsProcessingParameterField::Any,
|
|
true, true ) );
|
|
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Intersections" ), QgsProcessing::TypeVectorPoint ) );
|
|
}
|
|
|
|
QString QgsLineIntersectionAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm creates point features where the lines in the Intersect layer intersect the lines in the Input layer." );
|
|
}
|
|
|
|
QgsLineIntersectionAlgorithm *QgsLineIntersectionAlgorithm::createInstance() const
|
|
{
|
|
return new QgsLineIntersectionAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsLineIntersectionAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > sourceA( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
if ( !sourceA )
|
|
return QVariantMap();
|
|
|
|
std::unique_ptr< QgsFeatureSource > sourceB( parameterAsSource( parameters, QStringLiteral( "INTERSECT" ), context ) );
|
|
if ( !sourceB )
|
|
return QVariantMap();
|
|
|
|
const QStringList fieldsA = parameterAsFields( parameters, QStringLiteral( "INPUT_FIELDS" ), context );
|
|
const QStringList fieldsB = parameterAsFields( parameters, QStringLiteral( "INTERSECT_FIELDS" ), context );
|
|
|
|
QgsFields outFieldsA;
|
|
QgsAttributeList fieldsAIndices;
|
|
|
|
if ( fieldsA.empty() )
|
|
{
|
|
outFieldsA = sourceA->fields();
|
|
for ( int i = 0; i < outFieldsA.count(); ++i )
|
|
{
|
|
fieldsAIndices << i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( const QString &field : fieldsA )
|
|
{
|
|
int index = sourceA->fields().lookupField( field );
|
|
if ( index >= 0 )
|
|
{
|
|
fieldsAIndices << index;
|
|
outFieldsA.append( sourceA->fields().at( index ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsFields outFieldsB;
|
|
QgsAttributeList fieldsBIndices;
|
|
|
|
if ( fieldsB.empty() )
|
|
{
|
|
outFieldsB = sourceB->fields();
|
|
for ( int i = 0; i < outFieldsB.count(); ++i )
|
|
{
|
|
fieldsBIndices << i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( const QString &field : fieldsB )
|
|
{
|
|
int index = sourceB->fields().lookupField( field );
|
|
if ( index >= 0 )
|
|
{
|
|
fieldsBIndices << index;
|
|
outFieldsB.append( sourceB->fields().at( index ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsFields outFields = QgsProcessingUtils::combineFields( outFieldsA, outFieldsB );
|
|
|
|
QString dest;
|
|
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outFields, QgsWkbTypes::Point, sourceA->sourceCrs() ) );
|
|
if ( !sink )
|
|
return QVariantMap();
|
|
|
|
QgsSpatialIndex spatialIndex( sourceB->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() ).setDestinationCrs( sourceA->sourceCrs() ) ), feedback );
|
|
QgsFeature outFeature;
|
|
QgsFeatureIterator features = sourceA->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( fieldsAIndices ) );
|
|
double step = sourceA->featureCount() > 0 ? 100.0 / sourceA->featureCount() : 1;
|
|
int i = 0;
|
|
QgsFeature inFeatureA;
|
|
while ( features.nextFeature( inFeatureA ) )
|
|
{
|
|
i++;
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( !inFeatureA.hasGeometry() )
|
|
continue;
|
|
|
|
QgsGeometry inGeom = inFeatureA.geometry();
|
|
QgsFeatureIds lines = spatialIndex.intersects( inGeom.boundingBox() ).toSet();
|
|
if ( !lines.empty() )
|
|
{
|
|
// use prepared geometries for faster intersection tests
|
|
std::unique_ptr< QgsGeometryEngine > engine( QgsGeometry::createGeometryEngine( inGeom.geometry() ) );
|
|
engine->prepareGeometry();
|
|
|
|
QgsFeatureRequest request = QgsFeatureRequest().setFilterFids( lines );
|
|
request.setDestinationCrs( sourceA->sourceCrs() );
|
|
request.setSubsetOfAttributes( fieldsBIndices );
|
|
|
|
QgsFeature inFeatureB;
|
|
QgsFeatureIterator featuresB = sourceB->getFeatures( request );
|
|
while ( featuresB.nextFeature( inFeatureB ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
QgsGeometry tmpGeom = inFeatureB.geometry();
|
|
if ( engine->intersects( tmpGeom.geometry() ) )
|
|
{
|
|
QgsMultiPoint points;
|
|
QgsGeometry intersectGeom = inGeom.intersection( tmpGeom );
|
|
QgsAttributes outAttributes;
|
|
for ( int a : qgsAsConst( fieldsAIndices ) )
|
|
{
|
|
outAttributes.append( inFeatureA.attribute( a ) );
|
|
}
|
|
for ( int b : qgsAsConst( fieldsBIndices ) )
|
|
{
|
|
outAttributes.append( inFeatureB.attribute( b ) );
|
|
}
|
|
if ( intersectGeom.type() == QgsWkbTypes::PointGeometry )
|
|
{
|
|
if ( intersectGeom.isMultipart() )
|
|
{
|
|
points = intersectGeom.asMultiPoint();
|
|
}
|
|
else
|
|
{
|
|
points.append( intersectGeom.asPoint() );
|
|
}
|
|
|
|
for ( const QgsPointXY &j : qgsAsConst( points ) )
|
|
{
|
|
outFeature.setGeometry( QgsGeometry::fromPoint( j ) );
|
|
outFeature.setAttributes( outAttributes );
|
|
sink->addFeature( outFeature, QgsFeatureSink::FastInsert );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
feedback->setProgress( i * step );
|
|
|
|
}
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
|
|
return outputs;
|
|
}
|
|
|
|
|
|
void QgsSplitWithLinesAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
|
|
QObject::tr( "Input layer" ), QList< int >() << QgsProcessing::TypeVectorLine << QgsProcessing::TypeVectorPolygon ) );
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "LINES" ),
|
|
QObject::tr( "Split layer" ), QList< int >() << QgsProcessing::TypeVectorLine ) );
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Split" ) ) );
|
|
}
|
|
|
|
QString QgsSplitWithLinesAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm splits the lines or polygons in one layer using the lines in another layer to define the breaking points. "
|
|
"Intersection between geometries in both layers are considered as split points.\n\n"
|
|
"Output will contain multi geometries for split features." );
|
|
}
|
|
|
|
QgsSplitWithLinesAlgorithm *QgsSplitWithLinesAlgorithm::createInstance() const
|
|
{
|
|
return new QgsSplitWithLinesAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsSplitWithLinesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
if ( !source )
|
|
return QVariantMap();
|
|
|
|
std::unique_ptr< QgsFeatureSource > linesSource( parameterAsSource( parameters, QStringLiteral( "LINES" ), context ) );
|
|
if ( !linesSource )
|
|
return QVariantMap();
|
|
|
|
bool sameLayer = parameters.value( QStringLiteral( "INPUT" ) ) == parameters.value( QStringLiteral( "LINES" ) );
|
|
|
|
QString dest;
|
|
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(),
|
|
QgsWkbTypes::multiType( source->wkbType() ), source->sourceCrs() ) );
|
|
if ( !sink )
|
|
return QVariantMap();
|
|
|
|
QgsSpatialIndex spatialIndex;
|
|
QMap< QgsFeatureId, QgsGeometry > splitGeoms;
|
|
QgsFeatureRequest request;
|
|
request.setSubsetOfAttributes( QgsAttributeList() );
|
|
request.setDestinationCrs( source->sourceCrs() );
|
|
|
|
QgsFeatureIterator splitLines = linesSource->getFeatures( request );
|
|
QgsFeature aSplitFeature;
|
|
while ( splitLines.nextFeature( aSplitFeature ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
splitGeoms.insert( aSplitFeature.id(), aSplitFeature.geometry() );
|
|
spatialIndex.insertFeature( aSplitFeature );
|
|
}
|
|
|
|
QgsFeature outFeat;
|
|
QgsFeatureIterator features = source->getFeatures();
|
|
|
|
double step = source->featureCount() > 0 ? 100.0 / source->featureCount() : 1;
|
|
int i = 0;
|
|
QgsFeature inFeatureA;
|
|
while ( features.nextFeature( inFeatureA ) )
|
|
{
|
|
i++;
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( !inFeatureA.hasGeometry() )
|
|
{
|
|
sink->addFeature( inFeatureA, QgsFeatureSink::FastInsert );
|
|
continue;
|
|
}
|
|
|
|
QgsGeometry inGeom = inFeatureA.geometry();
|
|
outFeat.setAttributes( inFeatureA.attributes() );
|
|
|
|
QList< QgsGeometry > inGeoms = inGeom.asGeometryCollection();
|
|
|
|
const QgsFeatureIds lines = spatialIndex.intersects( inGeom.boundingBox() ).toSet();
|
|
if ( !lines.empty() ) // has intersection of bounding boxes
|
|
{
|
|
QList< QgsGeometry > splittingLines;
|
|
|
|
// use prepared geometries for faster intersection tests
|
|
std::unique_ptr< QgsGeometryEngine > engine;
|
|
|
|
for ( QgsFeatureId line : lines )
|
|
{
|
|
// check if trying to self-intersect
|
|
if ( sameLayer && inFeatureA.id() == line )
|
|
continue;
|
|
|
|
QgsGeometry splitGeom = splitGeoms.value( line );
|
|
if ( !engine )
|
|
{
|
|
engine.reset( QgsGeometry::createGeometryEngine( inGeom.geometry() ) );
|
|
engine->prepareGeometry();
|
|
}
|
|
|
|
if ( engine->intersects( splitGeom.geometry() ) )
|
|
{
|
|
QList< QgsGeometry > splitGeomParts = splitGeom.asGeometryCollection();
|
|
splittingLines.append( splitGeomParts );
|
|
}
|
|
}
|
|
|
|
if ( !splittingLines.empty() )
|
|
{
|
|
for ( const QgsGeometry &splitGeom : qgsAsConst( splittingLines ) )
|
|
{
|
|
QList<QgsPointXY> splitterPList;
|
|
QList< QgsGeometry > outGeoms;
|
|
|
|
// use prepared geometries for faster intersection tests
|
|
std::unique_ptr< QgsGeometryEngine > splitGeomEngine( QgsGeometry::createGeometryEngine( splitGeom.geometry() ) );
|
|
splitGeomEngine->prepareGeometry();
|
|
while ( !inGeoms.empty() )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
QgsGeometry inGeom = inGeoms.takeFirst();
|
|
if ( !inGeom )
|
|
continue;
|
|
|
|
if ( splitGeomEngine->intersects( inGeom.geometry() ) )
|
|
{
|
|
QgsGeometry before = inGeom;
|
|
if ( splitterPList.empty() )
|
|
{
|
|
const QgsCoordinateSequence sequence = splitGeom.geometry()->coordinateSequence();
|
|
for ( const QgsRingSequence &part : sequence )
|
|
{
|
|
for ( const QgsPointSequence &ring : part )
|
|
{
|
|
for ( const QgsPoint &pt : ring )
|
|
{
|
|
splitterPList << QgsPointXY( pt );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QList< QgsGeometry > newGeometries;
|
|
QList<QgsPointXY> topologyTestPoints;
|
|
QgsGeometry::OperationResult result = inGeom.splitGeometry( splitterPList, newGeometries, false, topologyTestPoints );
|
|
|
|
// splitGeometry: If there are several intersections
|
|
// between geometry and splitLine, only the first one is considered.
|
|
if ( result == QgsGeometry::Success ) // split occurred
|
|
{
|
|
if ( inGeom.equals( before ) )
|
|
{
|
|
// bug in splitGeometry: sometimes it returns 0 but
|
|
// the geometry is unchanged
|
|
outGeoms.append( inGeom );
|
|
}
|
|
else
|
|
{
|
|
inGeoms.append( inGeom );
|
|
inGeoms.append( newGeometries );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outGeoms.append( inGeom );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outGeoms.append( inGeom );
|
|
}
|
|
|
|
}
|
|
inGeoms = outGeoms;
|
|
}
|
|
}
|
|
}
|
|
|
|
QList< QgsGeometry > parts;
|
|
for ( const QgsGeometry &aGeom : qgsAsConst( inGeoms ) )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
bool passed = true;
|
|
if ( QgsWkbTypes::geometryType( aGeom.wkbType() ) == QgsWkbTypes::LineGeometry )
|
|
{
|
|
int numPoints = aGeom.geometry()->nCoordinates();
|
|
|
|
if ( numPoints <= 2 )
|
|
{
|
|
if ( numPoints == 2 )
|
|
passed = !static_cast< QgsCurve * >( aGeom.geometry() )->isClosed(); // tests if vertex 0 = vertex 1
|
|
else
|
|
passed = false; // sometimes splitting results in lines of zero length
|
|
}
|
|
}
|
|
|
|
if ( passed )
|
|
parts.append( aGeom );
|
|
}
|
|
|
|
if ( !parts.empty() )
|
|
{
|
|
outFeat.setGeometry( QgsGeometry::collectGeometry( parts ) );
|
|
sink->addFeature( outFeat, QgsFeatureSink::FastInsert );
|
|
}
|
|
|
|
feedback->setProgress( i * step );
|
|
}
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
|
|
return outputs;
|
|
}
|
|
|
|
|
|
void QgsMeanCoordinatesAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
|
|
QObject::tr( "Input layer" ), QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
|
|
addParameter( new QgsProcessingParameterField( QStringLiteral( "WEIGHT" ), QObject::tr( "Weight field" ),
|
|
QVariant(), QStringLiteral( "INPUT" ),
|
|
QgsProcessingParameterField::Numeric, false, true ) );
|
|
addParameter( new QgsProcessingParameterField( QStringLiteral( "UID" ),
|
|
QObject::tr( "Unique ID field" ), QVariant(),
|
|
QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, false, true ) );
|
|
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Mean coordinates" ), QgsProcessing::TypeVectorPoint ) );
|
|
}
|
|
|
|
QString QgsMeanCoordinatesAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm computes a point layer with the center of mass of geometries in an input layer.\n\n"
|
|
"An attribute can be specified as containing weights to be applied to each feature when computing the center of mass.\n\n"
|
|
"If an attribute is selected in the <Unique ID field> parameter, features will be grouped according "
|
|
"to values in this field. Instead of a single point with the center of mass of the whole layer, "
|
|
"the output layer will contain a center of mass for the features in each category." );
|
|
}
|
|
|
|
QgsMeanCoordinatesAlgorithm *QgsMeanCoordinatesAlgorithm::createInstance() const
|
|
{
|
|
return new QgsMeanCoordinatesAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsMeanCoordinatesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
|
if ( !source )
|
|
return QVariantMap();
|
|
|
|
QString weightFieldName = parameterAsString( parameters, QStringLiteral( "WEIGHT" ), context );
|
|
QString uniqueFieldName = parameterAsString( parameters, QStringLiteral( "UID" ), context );
|
|
|
|
QgsAttributeList attributes;
|
|
int weightIndex = -1;
|
|
if ( !weightFieldName.isEmpty() )
|
|
{
|
|
weightIndex = source->fields().lookupField( weightFieldName );
|
|
if ( weightIndex >= 0 )
|
|
attributes.append( weightIndex );
|
|
}
|
|
|
|
int uniqueFieldIndex = -1;
|
|
if ( !uniqueFieldName.isEmpty() )
|
|
{
|
|
uniqueFieldIndex = source->fields().lookupField( uniqueFieldName );
|
|
if ( uniqueFieldIndex >= 0 )
|
|
attributes.append( uniqueFieldIndex );
|
|
}
|
|
|
|
QgsFields fields;
|
|
fields.append( QgsField( QStringLiteral( "MEAN_X" ), QVariant::Double, QString(), 24, 15 ) );
|
|
fields.append( QgsField( QStringLiteral( "MEAN_Y" ), QVariant::Double, QString(), 24, 15 ) );
|
|
if ( uniqueFieldIndex >= 0 )
|
|
{
|
|
QgsField uniqueField = source->fields().at( uniqueFieldIndex );
|
|
fields.append( uniqueField );
|
|
}
|
|
|
|
QString dest;
|
|
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields,
|
|
QgsWkbTypes::Point, source->sourceCrs() ) );
|
|
if ( !sink )
|
|
return QVariantMap();
|
|
|
|
QgsFeatureIterator features = source->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( attributes ) );
|
|
|
|
double step = source->featureCount() > 0 ? 50.0 / source->featureCount() : 1;
|
|
int i = 0;
|
|
QgsFeature feat;
|
|
|
|
QHash< QVariant, QList< double > > means;
|
|
while ( features.nextFeature( feat ) )
|
|
{
|
|
i++;
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
feedback->setProgress( i * step );
|
|
if ( !feat.hasGeometry() )
|
|
continue;
|
|
|
|
|
|
QVariant featureClass;
|
|
if ( uniqueFieldIndex >= 0 )
|
|
{
|
|
featureClass = feat.attribute( uniqueFieldIndex );
|
|
}
|
|
else
|
|
{
|
|
featureClass = QStringLiteral( "#####singleclass#####" );
|
|
}
|
|
|
|
double weight = 1;
|
|
if ( weightIndex >= 0 )
|
|
{
|
|
bool ok = false;
|
|
weight = feat.attribute( weightIndex ).toDouble( &ok );
|
|
if ( !ok )
|
|
weight = 1.0;
|
|
}
|
|
|
|
if ( weight < 0 )
|
|
{
|
|
throw QgsProcessingException( QObject::tr( "Negative weight value found. Please fix your data and try again." ) );
|
|
}
|
|
|
|
QList< double > values = means.value( featureClass );
|
|
double cx = 0;
|
|
double cy = 0;
|
|
double totalWeight = 0;
|
|
if ( !values.empty() )
|
|
{
|
|
cx = values.at( 0 );
|
|
cy = values.at( 1 );
|
|
totalWeight = values.at( 2 );
|
|
}
|
|
|
|
QgsVertexId vid;
|
|
QgsPoint pt;
|
|
const QgsAbstractGeometry *g = feat.geometry().geometry();
|
|
// NOTE - should this be including the duplicate nodes for closed rings? currently it is,
|
|
// but I suspect that the expected behavior would be to NOT include these
|
|
while ( g->nextVertex( vid, pt ) )
|
|
{
|
|
cx += pt.x() * weight;
|
|
cy += pt.y() * weight;
|
|
totalWeight += weight;
|
|
}
|
|
|
|
means[featureClass] = QList< double >() << cx << cy << totalWeight;
|
|
}
|
|
|
|
i = 0;
|
|
step = !means.empty() ? 50.0 / means.count() : 1;
|
|
for ( auto it = means.constBegin(); it != means.constEnd(); ++it )
|
|
{
|
|
i++;
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
feedback->setProgress( 50 + i * step );
|
|
if ( qgsDoubleNear( it.value().at( 2 ), 0 ) )
|
|
continue;
|
|
|
|
QgsFeature outFeat;
|
|
double cx = it.value().at( 0 ) / it.value().at( 2 );
|
|
double cy = it.value().at( 1 ) / it.value().at( 2 );
|
|
|
|
QgsPointXY meanPoint( cx, cy );
|
|
outFeat.setGeometry( QgsGeometry::fromPoint( meanPoint ) );
|
|
|
|
QgsAttributes attributes;
|
|
attributes << cx << cy;
|
|
if ( uniqueFieldIndex >= 0 )
|
|
attributes.append( it.key() );
|
|
|
|
outFeat.setAttributes( attributes );
|
|
sink->addFeature( outFeat, QgsFeatureSink::FastInsert );
|
|
}
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
|
|
return outputs;
|
|
}
|
|
|
|
|
|
void QgsRasterLayerUniqueValuesReportAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ),
|
|
QObject::tr( "Input layer" ) ) );
|
|
addParameter( new QgsProcessingParameterBand( QStringLiteral( "BAND" ),
|
|
QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT" ) ) );
|
|
addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT_HTML_FILE" ),
|
|
QObject::tr( "Unique values report" ), QObject::tr( "HTML files (*.html)" ), QVariant(), true ) );
|
|
|
|
addOutput( new QgsProcessingOutputHtml( QStringLiteral( "OUTPUT_HTML_FILE" ), QObject::tr( "Unique values report" ) ) );
|
|
|
|
addOutput( new QgsProcessingOutputString( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
|
|
addOutput( new QgsProcessingOutputString( QStringLiteral( "CRS_AUTHID" ), QObject::tr( "CRS authority identifier" ) ) );
|
|
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "WIDTH_IN_PIXELS" ), QObject::tr( "Width in pixels" ) ) );
|
|
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "HEIGHT_IN_PIXELS" ), QObject::tr( "Height in pixels" ) ) );
|
|
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "TOTAL_PIXEL_COUNT" ), QObject::tr( "Total pixel count" ) ) );
|
|
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "NODATA_PIXEL_COUNT" ), QObject::tr( "NODATA pixel count" ) ) );
|
|
}
|
|
|
|
QString QgsRasterLayerUniqueValuesReportAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm returns the count and area of each unique value in a given raster layer." );
|
|
}
|
|
|
|
QgsRasterLayerUniqueValuesReportAlgorithm *QgsRasterLayerUniqueValuesReportAlgorithm::createInstance() const
|
|
{
|
|
return new QgsRasterLayerUniqueValuesReportAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsRasterLayerUniqueValuesReportAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT" ), context );
|
|
int band = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
|
|
QString outputFile = parameterAsFileOutput( parameters, QStringLiteral( "OUTPUT_HTML_FILE" ), context );
|
|
|
|
|
|
QHash< double, int > uniqueValues;
|
|
int width = layer->width();
|
|
int height = layer->height();
|
|
|
|
QgsRasterBlock *rasterBlock = layer->dataProvider()->block( band, layer->extent(), width, height );
|
|
int noDataCount = -1;
|
|
if ( rasterBlock->hasNoDataValue() )
|
|
noDataCount = 0;
|
|
|
|
for ( int row = 0; row < height; row++ )
|
|
{
|
|
feedback->setProgress( 100 * row / height );
|
|
for ( int column = 0; column < width; column++ )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
break;
|
|
if ( noDataCount > -1 && rasterBlock->isNoData( row, column ) )
|
|
{
|
|
noDataCount += 1;
|
|
}
|
|
else
|
|
{
|
|
double value = rasterBlock->value( row, column );
|
|
uniqueValues[ value ]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
QMap< double, int > sortedUniqueValues;
|
|
for ( auto it = uniqueValues.constBegin(); it != uniqueValues.constEnd(); ++it )
|
|
{
|
|
sortedUniqueValues.insert( it.key(), it.value() );
|
|
}
|
|
|
|
QVariantMap outputs;
|
|
outputs.insert( QStringLiteral( "EXTENT" ), layer->extent().toString() );
|
|
outputs.insert( QStringLiteral( "CRS_AUTHID" ), layer->crs().authid() );
|
|
outputs.insert( QStringLiteral( "WIDTH_IN_PIXELS" ), width );
|
|
outputs.insert( QStringLiteral( "HEIGHT_IN_PIXELS" ), height );
|
|
outputs.insert( QStringLiteral( "TOTAL_PIXEL_COUNT" ), width * height );
|
|
outputs.insert( QStringLiteral( "NODATA_PIXEL_COUNT" ), noDataCount );
|
|
|
|
if ( !outputFile.isEmpty() )
|
|
{
|
|
QFile file( outputFile );
|
|
if ( file.open( QIODevice::WriteOnly | QIODevice::Text ) )
|
|
{
|
|
QString areaUnit = QgsUnitTypes::toAbbreviatedString( QgsUnitTypes::distanceToAreaUnit( layer->crs().mapUnits() ) );
|
|
double cellArea = layer->rasterUnitsPerPixelX() * layer->rasterUnitsPerPixelY();
|
|
|
|
QTextStream out( &file );
|
|
out << QString( "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/></head><body>\n" );
|
|
out << QString( "<p>%1: %2 (%3 %4)</p>\n" ).arg( QObject::tr( "Analyzed file" ) ).arg( layer->source() ).arg( QObject::tr( "band" ) ).arg( band );
|
|
out << QObject::tr( "<p>%1: %2</p>\n" ).arg( QObject::tr( "Extent" ) ).arg( layer->extent().toString() );
|
|
out << QObject::tr( "<p>%1: %2 (%3)</p>\n" ).arg( QObject::tr( "Projection" ) ).arg( layer->crs().description() ).arg( layer->crs().authid() );
|
|
out << QObject::tr( "<p>%1: %2 (%3 %4)</p>\n" ).arg( QObject::tr( "Width in pixels" ) ).arg( width ).arg( QObject::tr( "units per pixel" ) ).arg( layer->rasterUnitsPerPixelX() );
|
|
out << QObject::tr( "<p>%1: %2 (%3 %4)</p>\n" ).arg( QObject::tr( "Height in pixels" ) ).arg( height ).arg( QObject::tr( "units per pixel" ) ).arg( layer->rasterUnitsPerPixelY() );
|
|
out << QObject::tr( "<p>%1: %2</p>\n" ).arg( QObject::tr( "Total pixel count" ) ).arg( width * height );
|
|
if ( noDataCount > -1 )
|
|
out << QObject::tr( "<p>%1: %2</p>\n" ).arg( QObject::tr( "NODATA pixel count" ) ).arg( noDataCount );
|
|
out << QString( "<table><tr><td>%1</td><td>%2</td><td>%3 (%4)</td></tr>\n" ).arg( QObject::tr( "Value" ) ).arg( QObject::tr( "Pixel count" ) ).arg( QObject::tr( "Area" ) ).arg( areaUnit );
|
|
|
|
for ( double key : sortedUniqueValues.keys() )
|
|
{
|
|
double area = sortedUniqueValues[key] * cellArea;
|
|
out << QString( "<tr><td>%1</td><td>%2</td><td>%3</td></tr>\n" ).arg( key ).arg( sortedUniqueValues[key] ).arg( QString::number( area, 'g', 16 ) );
|
|
}
|
|
out << QString( "</table>\n</body></html>" );
|
|
outputs.insert( QStringLiteral( "OUTPUT_HTML_FILE" ), outputFile );
|
|
}
|
|
}
|
|
|
|
return outputs;
|
|
}
|
|
|
|
///@endcond
|
|
|
|
|