QVariantMap QgsReclassifyAlgorithmBase::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { QVector< QgsReclassifyUtils::RasterClass > classes = createClasses( mBoundsType, parameters, context, feedback ); QgsReclassifyUtils::reportClasses( classes, feedback ); QgsReclassifyUtils::checkForOverlaps( classes, feedback ); const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context ); QFileInfo fi( outputFile ); const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() ); std::unique_ptr< QgsRasterFileWriter > writer = qgis::make_unique< QgsRasterFileWriter >( outputFile ); writer->setOutputProviderKey( QStringLiteral( "gdal" ) ); writer->setOutputFormat( outputFormat ); std::unique_ptr<QgsRasterDataProvider > provider( writer->createOneBandRaster( mDataType, mNbCellsXProvider, mNbCellsYProvider, mExtent, mCrs ) ); if ( !provider ) throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) ); if ( !provider->isValid() ) throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) ); provider->setNoDataValue( 1, mNoDataValue ); QgsReclassifyUtils::reclassify( classes, mInterface.get(), mBand, mExtent, mNbCellsXProvider, mNbCellsYProvider, provider.get(), mNoDataValue, mUseNoDataForMissingValues, feedback ); QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), outputFile ); return outputs; }
bool QgsReclassifyByLayerAlgorithm::_prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { std::unique_ptr< QgsFeatureSource >tableSource( parameterAsSource( parameters, QStringLiteral( "INPUT_TABLE" ), context ) ); if ( !tableSource ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT_TABLE" ) ) ); QString fieldMin = parameterAsString( parameters, QStringLiteral( "MIN_FIELD" ), context ); mMinFieldIdx = tableSource->fields().lookupField( fieldMin ); if ( mMinFieldIdx < 0 ) throw QgsProcessingException( QObject::tr( "Invalid field specified for MIN_FIELD: %1" ).arg( fieldMin ) ); QString fieldMax = parameterAsString( parameters, QStringLiteral( "MAX_FIELD" ), context ); mMaxFieldIdx = tableSource->fields().lookupField( fieldMax ); if ( mMaxFieldIdx < 0 ) throw QgsProcessingException( QObject::tr( "Invalid field specified for MAX_FIELD: %1" ).arg( fieldMax ) ); QString fieldValue = parameterAsString( parameters, QStringLiteral( "VALUE_FIELD" ), context ); mValueFieldIdx = tableSource->fields().lookupField( fieldValue ); if ( mValueFieldIdx < 0 ) throw QgsProcessingException( QObject::tr( "Invalid field specified for VALUE_FIELD: %1" ).arg( fieldValue ) ); QgsFeatureRequest request; request.setFlags( QgsFeatureRequest::NoGeometry ); request.setSubsetOfAttributes( QgsAttributeList() << mMinFieldIdx << mMaxFieldIdx << mValueFieldIdx ); mTableIterator = tableSource->getFeatures( request ); return true; }
QVariantMap QgsFileDownloaderAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { mFeedback = feedback; QString url = parameterAsString( parameters, QStringLiteral( "URL" ), context ); QString outputFile = parameterAsFileOutput( parameters, QStringLiteral( "OUTPUT" ), context ); QEventLoop loop; QTimer timer; QgsFileDownloader *downloader = new QgsFileDownloader( QUrl( url ), outputFile, QString(), true ); connect( mFeedback, &QgsFeedback::canceled, downloader, &QgsFileDownloader::cancelDownload ); connect( downloader, &QgsFileDownloader::downloadError, this, &QgsFileDownloaderAlgorithm::reportErrors ); connect( downloader, &QgsFileDownloader::downloadProgress, this, &QgsFileDownloaderAlgorithm::receiveProgressFromDownloader ); connect( downloader, &QgsFileDownloader::downloadExited, &loop, &QEventLoop::quit ); connect( &timer, &QTimer::timeout, this, &QgsFileDownloaderAlgorithm::sendProgressFeedback ); downloader->startDownload(); timer.start( 1000 ); loop.exec(); timer.stop(); bool exists = QFileInfo( outputFile ).exists(); if ( !feedback->isCanceled() && !exists ) throw QgsProcessingException( tr( "Output file doesn't exist." ) ); QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), exists ? outputFile : QString() ); return outputs; }
QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { return processCollection( parameters, context, feedback, [ & ]( const QVector< QgsGeometry > &parts )->QgsGeometry { QgsGeometry result( QgsGeometry::unaryUnion( parts ) ); if ( QgsWkbTypes::geometryType( result.wkbType() ) == QgsWkbTypes::LineGeometry ) result = result.mergeLines(); // Geos may fail in some cases, let's try a slower but safer approach // See: https://issues.qgis.org/issues/20591 - Dissolve tool failing to produce outputs if ( ! result.lastError().isEmpty() && parts.count() > 2 ) { if ( feedback->isCanceled() ) return result; feedback->pushDebugInfo( QObject::tr( "GEOS exception: taking the slower route ..." ) ); result = QgsGeometry(); for ( const auto &p : parts ) { result = QgsGeometry::unaryUnion( QVector< QgsGeometry >() << result << p ); if ( QgsWkbTypes::geometryType( result.wkbType() ) == QgsWkbTypes::LineGeometry ) result = result.mergeLines(); if ( feedback->isCanceled() ) return result; } } if ( ! result.lastError().isEmpty() ) { feedback->reportError( result.lastError(), true ); if ( result.isEmpty() ) throw QgsProcessingException( QObject::tr( "The algorithm returned no output." ) ); } return result; }, 10000 ); }
bool QgsOverlayUtils::sanitizeIntersectionResult( QgsGeometry &geom, QgsWkbTypes::GeometryType geometryType ) { if ( geom.isNull() ) { // TODO: not sure if this ever happens - if it does, that means GEOS failed badly - would be good to have a test for such situation throw QgsProcessingException( QStringLiteral( "%1\n\n%2" ).arg( QObject::tr( "GEOS geoprocessing error: intersection failed." ), geom.lastError() ) ); } // Intersection of geometries may give use also geometries we do not want in our results. // For example, two square polygons touching at the corner have a point as the intersection, but no area. // In other cases we may get a mixture of geometries in the output - we want to keep only the expected types. if ( QgsWkbTypes::flatType( geom.wkbType() ) == QgsWkbTypes::GeometryCollection ) { // try to filter out irrelevant parts with different geometry type than what we want geom.convertGeometryCollectionToSubclass( geometryType ); if ( geom.isEmpty() ) return false; } if ( QgsWkbTypes::geometryType( geom.wkbType() ) != geometryType ) { // we can't make use of this resulting geometry return false; } // some data providers are picky about the geometries we pass to them: we can't add single-part geometries // when we promised multi-part geometries, so ensure we have the right type geom.convertToMultiType(); return true; }
QgsFeatureList QgsAddXYFieldsAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { if ( mTransformNeedsInitialization ) { mTransform = QgsCoordinateTransform( mSourceCrs, mCrs, context.transformContext() ); mTransformNeedsInitialization = false; } QVariant x; QVariant y; if ( feature.hasGeometry() ) { if ( feature.geometry().isMultipart() ) throw QgsProcessingException( QObject::tr( "Multipoint features are not supported - please convert to single point features first." ) ); const QgsPointXY point = feature.geometry().asPoint(); try { const QgsPointXY transformed = mTransform.transform( point ); x = transformed.x(); y = transformed.y(); } catch ( QgsCsException & ) { feedback->reportError( QObject::tr( "Could not transform point to destination CRS" ) ); } } QgsFeature f = feature; QgsAttributes attributes = f.attributes(); attributes << x << y; f.setAttributes( attributes ); return QgsFeatureList() << f; }
QVector<QgsReclassifyUtils::RasterClass> QgsReclassifyByTableAlgorithm::createClasses( QgsReclassifyUtils::RasterClass::BoundsType boundsType, const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { const QVariantList table = parameterAsMatrix( parameters, QStringLiteral( "TABLE" ), context ); if ( table.count() % 3 != 0 ) throw QgsProcessingException( QObject::tr( "Invalid value for TABLE: list must contain a multiple of 3 elements (found %1)" ).arg( table.count() ) ); const int rows = table.count() / 3; QVector< QgsReclassifyUtils::RasterClass > classes; for ( int row = 0; row < rows; ++row ) { bool ok = false; // null values map to nan, which corresponds to a range extended to +/- infinity.... const QVariant minVariant = table.at( row * 3 ); double minValue; if ( minVariant.isNull() || minVariant.toString().isEmpty() ) { minValue = std::numeric_limits<double>::quiet_NaN(); } else { minValue = minVariant.toDouble( &ok ); if ( !ok ) throw QgsProcessingException( QObject::tr( "Invalid value for minimum: %1" ).arg( table.at( row * 3 ).toString() ) ); } const QVariant maxVariant = table.at( row * 3 + 1 ); double maxValue; if ( maxVariant.isNull() || maxVariant.toString().isEmpty() ) { maxValue = std::numeric_limits<double>::quiet_NaN(); ok = true; } else { maxValue = maxVariant.toDouble( &ok ); if ( !ok ) throw QgsProcessingException( QObject::tr( "Invalid value for maximum: %1" ).arg( table.at( row * 3 + 1 ).toString() ) ); } const double value = table.at( row * 3 + 2 ).toDouble( &ok ); if ( !ok ) throw QgsProcessingException( QObject::tr( "Invalid output value: %1" ).arg( table.at( row * 3 + 2 ).toString() ) ); classes << QgsReclassifyUtils::RasterClass( minValue, maxValue, boundsType, value ); } return classes; }
QVariantMap QgsExtractByExtentAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { std::unique_ptr< QgsFeatureSource > featureSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); if ( !featureSource ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); 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 ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); 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; }
bool QgsReclassifyAlgorithmBase::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { mDataType = QgsRasterAnalysisUtils::rasterTypeChoiceToDataType( parameterAsEnum( parameters, QStringLiteral( "DATA_TYPE" ), context ) ); QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT_RASTER" ), context ); if ( !layer ) throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT_RASTER" ) ) ); mBand = parameterAsInt( parameters, QStringLiteral( "RASTER_BAND" ), context ); if ( mBand < 1 || mBand > layer->bandCount() ) throw QgsProcessingException( QObject::tr( "Invalid band number for RASTER_BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ) .arg( layer->bandCount() ) ); mInterface.reset( layer->dataProvider()->clone() ); mExtent = layer->extent(); mCrs = layer->crs(); mRasterUnitsPerPixelX = std::abs( layer->rasterUnitsPerPixelX() ); mRasterUnitsPerPixelY = std::abs( layer->rasterUnitsPerPixelY() ); mNbCellsXProvider = mInterface->xSize(); mNbCellsYProvider = mInterface->ySize(); mNoDataValue = parameterAsDouble( parameters, QStringLiteral( "NO_DATA" ), context ); mUseNoDataForMissingValues = parameterAsBool( parameters, QStringLiteral( "NODATA_FOR_MISSING" ), context ); int boundsType = parameterAsEnum( parameters, QStringLiteral( "RANGE_BOUNDARIES" ), context ); switch ( boundsType ) { case 0: mBoundsType = QgsReclassifyUtils::RasterClass::IncludeMax; break; case 1: mBoundsType = QgsReclassifyUtils::RasterClass::IncludeMin; break; case 2: mBoundsType = QgsReclassifyUtils::RasterClass::IncludeMinAndMax; break; case 3: mBoundsType = QgsReclassifyUtils::RasterClass::Exclusive; break; } return _prepareAlgorithm( parameters, context, feedback ); }
void QgsProcessingFeatureBasedAlgorithm::prepareSource( const QVariantMap ¶meters, QgsProcessingContext &context ) { if ( ! mSource ) { mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); if ( !mSource ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); } }
QgsProcessingAlgorithm *QgsProcessingAlgorithm::create( const QVariantMap &configuration ) const { std::unique_ptr< QgsProcessingAlgorithm > creation( createInstance() ); if ( ! creation ) throw QgsProcessingException( QObject::tr( "Error creating algorithm from createInstance()" ) ); creation->setProvider( provider() ); creation->initAlgorithm( configuration ); return creation.release(); }
QVector<QgsReclassifyUtils::RasterClass> QgsReclassifyByLayerAlgorithm::createClasses( QgsRasterRange::BoundsType boundsType, const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * ) { QVector< QgsReclassifyUtils::RasterClass > classes; QgsFeature f; while ( mTableIterator.nextFeature( f ) ) { bool ok = false; // null values map to nan, which corresponds to a range extended to +/- infinity.... const QVariant minVariant = f.attribute( mMinFieldIdx ); double minValue; if ( minVariant.isNull() || minVariant.toString().isEmpty() ) { minValue = std::numeric_limits<double>::quiet_NaN(); } else { minValue = minVariant.toDouble( &ok ); if ( !ok ) throw QgsProcessingException( QObject::tr( "Invalid value for minimum: %1" ).arg( minVariant.toString() ) ); } const QVariant maxVariant = f.attribute( mMaxFieldIdx ); double maxValue; if ( maxVariant.isNull() || maxVariant.toString().isEmpty() ) { maxValue = std::numeric_limits<double>::quiet_NaN(); ok = true; } else { maxValue = maxVariant.toDouble( &ok ); if ( !ok ) throw QgsProcessingException( QObject::tr( "Invalid value for maximum: %1" ).arg( maxVariant.toString() ) ); } const double value = f.attribute( mValueFieldIdx ).toDouble( &ok ); if ( !ok ) throw QgsProcessingException( QObject::tr( "Invalid output value: %1" ).arg( f.attribute( mValueFieldIdx ).toString() ) ); classes << QgsReclassifyUtils::RasterClass( minValue, maxValue, boundsType, value ); } return classes; }
QVariantMap QgsOrderByExpressionAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); if ( !source ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); QString expressionString = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context ); bool ascending = parameterAsBoolean( parameters, QStringLiteral( "ASCENDING" ), context ); bool nullsFirst = parameterAsBoolean( parameters, QStringLiteral( "NULLS_FIRST" ), context ); QString sinkId; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, sinkId, source->fields(), source->wkbType(), source->sourceCrs() ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); long count = source->featureCount(); double step = count > 0 ? 100.0 / count : 1; int current = 0; QgsFeatureRequest request; request.addOrderBy( expressionString, ascending, nullsFirst ); QgsFeature inFeature; QgsFeatureIterator features = source->getFeatures( request, QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks ); while ( features.nextFeature( inFeature ) ) { if ( feedback->isCanceled() ) { break; } sink->addFeature( inFeature ); feedback->setProgress( current * step ); current++; } QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), sinkId ); return outputs; }
QVariantMap QgsRenameLayerAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { QgsMapLayer *layer = parameterAsLayer( parameters, QStringLiteral( "INPUT" ), context ); QString name = parameterAsString( parameters, QStringLiteral( "NAME" ), context ); if ( !layer ) throw QgsProcessingException( QObject::tr( "Invalid input layer" ) ); if ( name.isEmpty() ) throw QgsProcessingException( QObject::tr( "Invalid (empty) layer name" ) ); bool parameterWasLayerName = parameters.value( QStringLiteral( "INPUT" ) ).toString() == layer->name(); layer->setName( name ); QVariantMap results; if ( parameterWasLayerName ) results.insert( QStringLiteral( "OUTPUT" ), name ); else results.insert( QStringLiteral( "OUTPUT" ), parameters.value( QStringLiteral( "INPUT" ) ) ); return results; }
QVariantMap QgsProcessingFeatureBasedAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { prepareSource( parameters, context ); QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields( mSource->fields() ), outputWkbType( mSource->wkbType() ), outputCrs( mSource->sourceCrs() ), sinkFlags() ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); // prepare expression context for feature iteration QgsExpressionContext prevContext = context.expressionContext(); QgsExpressionContext algContext = prevContext; algContext.appendScopes( createExpressionContext( parameters, context, mSource.get() ).takeScopes() ); context.setExpressionContext( algContext ); long count = mSource->featureCount(); QgsFeature f; QgsFeatureIterator it = mSource->getFeatures( request(), sourceFlags() ); double step = count > 0 ? 100.0 / count : 1; int current = 0; while ( it.nextFeature( f ) ) { if ( feedback->isCanceled() ) { break; } context.expressionContext().setFeature( f ); const QgsFeatureList transformed = processFeature( f, context, feedback ); for ( QgsFeature transformedFeature : transformed ) sink->addFeature( transformedFeature, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; } mSource.reset(); // probably not necessary - context's aren't usually recycled, but can't hurt context.setExpressionContext( prevContext ); QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; }
QVariantMap QgsIntersectionAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { std::unique_ptr< QgsFeatureSource > sourceA( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); if ( !sourceA ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); std::unique_ptr< QgsFeatureSource > sourceB( parameterAsSource( parameters, QStringLiteral( "OVERLAY" ), context ) ); if ( !sourceB ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "OVERLAY" ) ) ); QgsWkbTypes::Type geomType = QgsWkbTypes::multiType( sourceA->wkbType() ); const QStringList fieldsA = parameterAsFields( parameters, QStringLiteral( "INPUT_FIELDS" ), context ); const QStringList fieldsB = parameterAsFields( parameters, QStringLiteral( "OVERLAY_FIELDS" ), context ); QList<int> fieldIndicesA = QgsProcessingUtils::fieldNamesToIndices( fieldsA, sourceA->fields() ); QList<int> fieldIndicesB = QgsProcessingUtils::fieldNamesToIndices( fieldsB, sourceB->fields() ); QgsFields outputFields = QgsProcessingUtils::combineFields( QgsProcessingUtils::indicesToFields( fieldIndicesA, sourceA->fields() ), QgsProcessingUtils::indicesToFields( fieldIndicesB, sourceB->fields() ) ); QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields, geomType, sourceA->sourceCrs() ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); int count = 0; int total = sourceA->featureCount(); QgsOverlayUtils::intersection( *sourceA.get(), *sourceB.get(), *sink.get(), context, feedback, count, total, fieldIndicesA, fieldIndicesB ); return outputs; }
void QgsNetworkAnalysisAlgorithmBase::loadCommonParams( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { Q_UNUSED( feedback ); mNetwork.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); if ( !mNetwork ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); int strategy = parameterAsInt( parameters, QStringLiteral( "STRATEGY" ), context ); QString directionFieldName = parameterAsString( parameters, QStringLiteral( "DIRECTION_FIELD" ), context ); QString forwardValue = parameterAsString( parameters, QStringLiteral( "VALUE_FORWARD" ), context ); QString backwardValue = parameterAsString( parameters, QStringLiteral( "VALUE_BACKWARD" ), context ); QString bothValue = parameterAsString( parameters, QStringLiteral( "VALUE_BOTH" ), context ); QgsVectorLayerDirector::Direction defaultDirection = static_cast< QgsVectorLayerDirector::Direction>( parameterAsInt( parameters, QStringLiteral( "DEFAULT_DIRECTION" ), context ) ); QString speedFieldName = parameterAsString( parameters, QStringLiteral( "SPEED_FIELD" ), context ); double defaultSpeed = parameterAsDouble( parameters, QStringLiteral( "DEFAULT_SPEED" ), context ); double tolerance = parameterAsDouble( parameters, QStringLiteral( "TOLERANCE" ), context ); int directionField = -1; if ( !directionFieldName.isEmpty() ) { directionField = mNetwork->fields().lookupField( directionFieldName ); } int speedField = -1; if ( !speedFieldName.isEmpty() ) { speedField = mNetwork->fields().lookupField( speedFieldName ); } mDirector = new QgsVectorLayerDirector( mNetwork.get(), directionField, forwardValue, backwardValue, bothValue, defaultDirection ); QgsUnitTypes::DistanceUnit distanceUnits = context.project()->crs().mapUnits(); mMultiplier = QgsUnitTypes::fromUnitToUnitFactor( distanceUnits, QgsUnitTypes::DistanceMeters ); if ( strategy ) { mDirector->addStrategy( new QgsNetworkSpeedStrategy( speedField, defaultSpeed, mMultiplier * 1000.0 / 3600.0 ) ); mMultiplier = 3600; } else { mDirector->addStrategy( new QgsNetworkDistanceStrategy() ); } mBuilder = qgis::make_unique< QgsGraphBuilder >( mNetwork->sourceCrs(), true, tolerance ); }
//! Makes sure that what came out from difference of two geometries is good to be used in the output static bool sanitizeDifferenceResult( QgsGeometry &geom ) { if ( geom.isNull() ) { // TODO: not sure if this ever happens - if it does, that means GEOS failed badly - would be good to have a test for such situation throw QgsProcessingException( QStringLiteral( "%1\n\n%2" ).arg( QObject::tr( "GEOS geoprocessing error: difference failed." ), geom.lastError() ) ); } // if geomB covers the whole source geometry, we get an empty geometry collection if ( geom.isEmpty() ) return false; // some data providers are picky about the geometries we pass to them: we can't add single-part geometries // when we promised multi-part geometries, so ensure we have the right type geom.convertToMultiType(); return true; }
bool QgsZonalHistogramAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT_RASTER" ), context ); if ( !layer ) throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT_RASTER" ) ) ); mRasterBand = parameterAsInt( parameters, QStringLiteral( "RASTER_BAND" ), context ); mHasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( mRasterBand ); mNodataValue = layer->dataProvider()->sourceNoDataValue( mRasterBand ); mRasterInterface.reset( layer->dataProvider()->clone() ); mRasterExtent = layer->extent(); mCrs = layer->crs(); mCellSizeX = std::abs( layer->rasterUnitsPerPixelX() ); mCellSizeY = std::abs( layer->rasterUnitsPerPixelX() ); mNbCellsXProvider = mRasterInterface->xSize(); mNbCellsYProvider = mRasterInterface->ySize(); return true; }
QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); if ( !source ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); 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 ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); 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, source.get() ); 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, QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks ); 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; }
QVariantMap QgsSplitWithLinesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); if ( !source ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); std::unique_ptr< QgsFeatureSource > linesSource( parameterAsSource( parameters, QStringLiteral( "LINES" ), context ) ); if ( !linesSource ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "LINES" ) ) ); 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 ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); QgsSpatialIndex spatialIndex; QMap< QgsFeatureId, QgsGeometry > splitGeoms; QgsFeatureRequest request; request.setSubsetOfAttributes( QgsAttributeList() ); request.setDestinationCrs( source->sourceCrs(), context.transformContext() ); QgsFeatureIterator splitLines = linesSource->getFeatures( request ); QgsFeature aSplitFeature; while ( splitLines.nextFeature( aSplitFeature ) ) { if ( feedback->isCanceled() ) { break; } splitGeoms.insert( aSplitFeature.id(), aSplitFeature.geometry() ); spatialIndex.addFeature( 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() ); QVector< QgsGeometry > inGeoms = inGeom.asGeometryCollection(); const QgsFeatureIds lines = spatialIndex.intersects( inGeom.boundingBox() ).toSet(); if ( !lines.empty() ) // has intersection of bounding boxes { QVector< 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.constGet() ) ); engine->prepareGeometry(); } if ( engine->intersects( splitGeom.constGet() ) ) { QVector< QgsGeometry > splitGeomParts = splitGeom.asGeometryCollection(); splittingLines.append( splitGeomParts ); } } if ( !splittingLines.empty() ) { for ( const QgsGeometry &splitGeom : qgis::as_const( splittingLines ) ) { QVector<QgsPointXY> splitterPList; QVector< QgsGeometry > outGeoms; // use prepared geometries for faster intersection tests std::unique_ptr< QgsGeometryEngine > splitGeomEngine( QgsGeometry::createGeometryEngine( splitGeom.constGet() ) ); splitGeomEngine->prepareGeometry(); while ( !inGeoms.empty() ) { if ( feedback->isCanceled() ) { break; } QgsGeometry inGeom = inGeoms.takeFirst(); if ( !inGeom ) continue; if ( splitGeomEngine->intersects( inGeom.constGet() ) ) { QgsGeometry before = inGeom; if ( splitterPList.empty() ) { const QgsCoordinateSequence sequence = splitGeom.constGet()->coordinateSequence(); for ( const QgsRingSequence &part : sequence ) { for ( const QgsPointSequence &ring : part ) { for ( const QgsPoint &pt : ring ) { splitterPList << QgsPointXY( pt ); } } } } QVector< QgsGeometry > newGeometries; QVector<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.isGeosEqual( 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; } } } QVector< QgsGeometry > parts; for ( const QgsGeometry &aGeom : qgis::as_const( inGeoms ) ) { if ( feedback->isCanceled() ) { break; } bool passed = true; if ( QgsWkbTypes::geometryType( aGeom.wkbType() ) == QgsWkbTypes::LineGeometry ) { int numPoints = aGeom.constGet()->nCoordinates(); if ( numPoints <= 2 ) { if ( numPoints == 2 ) passed = !static_cast< const QgsCurve * >( aGeom.constGet() )->isClosed(); // tests if vertex 0 = vertex 1 else passed = false; // sometimes splitting results in lines of zero length } } if ( passed ) parts.append( aGeom ); } for ( const QgsGeometry &g : parts ) { outFeat.setGeometry( g ); sink->addFeature( outFeat, QgsFeatureSink::FastInsert ); } feedback->setProgress( i * step ); } QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; }
QVariantMap QgsJoinWithLinesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { if ( parameters.value( QStringLiteral( "SPOKES" ) ) == parameters.value( QStringLiteral( "HUBS" ) ) ) throw QgsProcessingException( QObject::tr( "Same layer given for both hubs and spokes" ) ); std::unique_ptr< QgsProcessingFeatureSource > hubSource( parameterAsSource( parameters, QStringLiteral( "HUBS" ), context ) ); if ( !hubSource ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "HUBS" ) ) ); std::unique_ptr< QgsProcessingFeatureSource > spokeSource( parameterAsSource( parameters, QStringLiteral( "SPOKES" ), context ) ); if ( !hubSource || !spokeSource ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "SPOKES" ) ) ); QString fieldHubName = parameterAsString( parameters, QStringLiteral( "HUB_FIELD" ), context ); int fieldHubIndex = hubSource->fields().lookupField( fieldHubName ); const QStringList hubFieldsToCopy = parameterAsFields( parameters, QStringLiteral( "HUB_FIELDS" ), context ); QString fieldSpokeName = parameterAsString( parameters, QStringLiteral( "SPOKE_FIELD" ), context ); int fieldSpokeIndex = spokeSource->fields().lookupField( fieldSpokeName ); const QStringList spokeFieldsToCopy = parameterAsFields( parameters, QStringLiteral( "SPOKE_FIELDS" ), context ); if ( fieldHubIndex < 0 || fieldSpokeIndex < 0 ) throw QgsProcessingException( QObject::tr( "Invalid ID field" ) ); const bool geodesic = parameterAsBool( parameters, QStringLiteral( "GEODESIC" ), context ); const double geodesicDistance = parameterAsDouble( parameters, QStringLiteral( "GEODESIC_DISTANCE" ), context ) * 1000; bool dynamicGeodesicDistance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "GEODESIC_DISTANCE" ) ); QgsExpressionContext expressionContext = createExpressionContext( parameters, context, hubSource.get() ); QgsProperty geodesicDistanceProperty; if ( dynamicGeodesicDistance ) { geodesicDistanceProperty = parameters.value( QStringLiteral( "GEODESIC_DISTANCE" ) ).value< QgsProperty >(); } const bool splitAntimeridian = parameterAsBool( parameters, QStringLiteral( "ANTIMERIDIAN_SPLIT" ), context ); QgsDistanceArea da; da.setSourceCrs( hubSource->sourceCrs(), context.transformContext() ); da.setEllipsoid( context.project()->ellipsoid() ); QgsFields hubOutFields; QgsAttributeList hubFieldIndices; if ( hubFieldsToCopy.empty() ) { hubOutFields = hubSource->fields(); hubFieldIndices.reserve( hubOutFields.count() ); for ( int i = 0; i < hubOutFields.count(); ++i ) { hubFieldIndices << i; } } else { hubFieldIndices.reserve( hubOutFields.count() ); for ( const QString &field : hubFieldsToCopy ) { int index = hubSource->fields().lookupField( field ); if ( index >= 0 ) { hubFieldIndices << index; hubOutFields.append( hubSource->fields().at( index ) ); } } } QgsAttributeList hubFields2Fetch = hubFieldIndices; hubFields2Fetch << fieldHubIndex; QgsFields spokeOutFields; QgsAttributeList spokeFieldIndices; if ( spokeFieldsToCopy.empty() ) { spokeOutFields = spokeSource->fields(); spokeFieldIndices.reserve( spokeOutFields.count() ); for ( int i = 0; i < spokeOutFields.count(); ++i ) { spokeFieldIndices << i; } } else { for ( const QString &field : spokeFieldsToCopy ) { int index = spokeSource->fields().lookupField( field ); if ( index >= 0 ) { spokeFieldIndices << index; spokeOutFields.append( spokeSource->fields().at( index ) ); } } } QgsAttributeList spokeFields2Fetch = spokeFieldIndices; spokeFields2Fetch << fieldSpokeIndex; QgsFields fields = QgsProcessingUtils::combineFields( hubOutFields, spokeOutFields ); QgsWkbTypes::Type outType = geodesic ? QgsWkbTypes::MultiLineString : QgsWkbTypes::LineString; bool hasZ = false; if ( QgsWkbTypes::hasZ( hubSource->wkbType() ) || QgsWkbTypes::hasZ( spokeSource->wkbType() ) ) { outType = QgsWkbTypes::addZ( outType ); hasZ = true; } bool hasM = false; if ( QgsWkbTypes::hasM( hubSource->wkbType() ) || QgsWkbTypes::hasM( spokeSource->wkbType() ) ) { outType = QgsWkbTypes::addM( outType ); hasM = true; } QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, outType, hubSource->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); auto getPointFromFeature = [hasZ, hasM]( const QgsFeature & feature )->QgsPoint { QgsPoint p; if ( feature.geometry().type() == QgsWkbTypes::PointGeometry && !feature.geometry().isMultipart() ) p = *static_cast< const QgsPoint *>( feature.geometry().constGet() ); else p = *static_cast< const QgsPoint *>( feature.geometry().pointOnSurface().constGet() ); if ( hasZ && !p.is3D() ) p.addZValue( 0 ); if ( hasM && !p.isMeasure() ) p.addMValue( 0 ); return p; }; QgsFeatureIterator hubFeatures = hubSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( hubFields2Fetch ), QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks ); double step = hubSource->featureCount() > 0 ? 100.0 / hubSource->featureCount() : 1; int i = 0; QgsFeature hubFeature; while ( hubFeatures.nextFeature( hubFeature ) ) { i++; if ( feedback->isCanceled() ) { break; } feedback->setProgress( i * step ); if ( !hubFeature.hasGeometry() ) continue; QgsPoint hubPoint = getPointFromFeature( hubFeature ); // only keep selected attributes QgsAttributes hubAttributes; for ( int j = 0; j < hubFeature.attributes().count(); ++j ) { if ( !hubFieldIndices.contains( j ) ) continue; hubAttributes << hubFeature.attribute( j ); } QgsFeatureRequest spokeRequest = QgsFeatureRequest().setDestinationCrs( hubSource->sourceCrs(), context.transformContext() ); spokeRequest.setSubsetOfAttributes( spokeFields2Fetch ); spokeRequest.setFilterExpression( QgsExpression::createFieldEqualityExpression( fieldSpokeName, hubFeature.attribute( fieldHubIndex ) ) ); QgsFeatureIterator spokeFeatures = spokeSource->getFeatures( spokeRequest, QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks ); QgsFeature spokeFeature; while ( spokeFeatures.nextFeature( spokeFeature ) ) { if ( feedback->isCanceled() ) { break; } if ( !spokeFeature.hasGeometry() ) continue; QgsPoint spokePoint = getPointFromFeature( spokeFeature ); QgsGeometry line; if ( !geodesic ) { line = QgsGeometry( new QgsLineString( QVector< QgsPoint >() << hubPoint << spokePoint ) ); if ( splitAntimeridian ) line = da.splitGeometryAtAntimeridian( line ); } else { double distance = geodesicDistance; if ( dynamicGeodesicDistance ) { expressionContext.setFeature( hubFeature ); distance = geodesicDistanceProperty.valueAsDouble( expressionContext, distance ); } std::unique_ptr< QgsMultiLineString > ml = qgis::make_unique< QgsMultiLineString >(); std::unique_ptr< QgsLineString > l = qgis::make_unique< QgsLineString >( QVector< QgsPoint >() << hubPoint ); QVector< QVector< QgsPointXY > > points = da.geodesicLine( QgsPointXY( hubPoint ), QgsPointXY( spokePoint ), distance, splitAntimeridian ); QVector< QgsPointXY > points1 = points.at( 0 ); points1.pop_front(); if ( points.count() == 1 ) points1.pop_back(); QgsLineString geodesicPoints( points1 ); l->append( &geodesicPoints ); if ( points.count() == 1 ) l->addVertex( spokePoint ); ml->addGeometry( l.release() ); if ( points.count() > 1 ) { QVector< QgsPointXY > points2 = points.at( 1 ); points2.pop_back(); l = qgis::make_unique< QgsLineString >( points2 ); if ( hasZ ) l->addZValue( std::numeric_limits<double>::quiet_NaN() ); if ( hasM ) l->addMValue( std::numeric_limits<double>::quiet_NaN() ); l->addVertex( spokePoint ); ml->addGeometry( l.release() ); } line = QgsGeometry( std::move( ml ) ); } QgsFeature outFeature; QgsAttributes outAttributes = hubAttributes; // only keep selected attributes QgsAttributes spokeAttributes; for ( int j = 0; j < spokeFeature.attributes().count(); ++j ) { if ( !spokeFieldIndices.contains( j ) ) continue; spokeAttributes << spokeFeature.attribute( j ); } outAttributes.append( spokeAttributes ); outFeature.setAttributes( outAttributes ); outFeature.setGeometry( line ); sink->addFeature( outFeature, QgsFeatureSink::FastInsert ); } } QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; }
QVariantMap QgsLineIntersectionAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { std::unique_ptr< QgsFeatureSource > sourceA( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); if ( !sourceA ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); std::unique_ptr< QgsFeatureSource > sourceB( parameterAsSource( parameters, QStringLiteral( "INTERSECT" ), context ) ); if ( !sourceB ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INTERSECT" ) ) ); const QStringList fieldsA = parameterAsFields( parameters, QStringLiteral( "INPUT_FIELDS" ), context ); const QStringList fieldsB = parameterAsFields( parameters, QStringLiteral( "INTERSECT_FIELDS" ), context ); QgsAttributeList fieldIndicesA = QgsProcessingUtils::fieldNamesToIndices( fieldsA, sourceA->fields() ); QgsAttributeList fieldIndicesB = QgsProcessingUtils::fieldNamesToIndices( fieldsB, sourceB->fields() ); QString intersectFieldsPrefix = parameterAsString( parameters, QStringLiteral( "INTERSECT_FIELDS_PREFIX" ), context ); QgsFields outFields = QgsProcessingUtils::combineFields( QgsProcessingUtils::indicesToFields( fieldIndicesA, sourceA->fields() ), QgsProcessingUtils::indicesToFields( fieldIndicesB, sourceB->fields() ), intersectFieldsPrefix ); QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outFields, QgsWkbTypes::Point, sourceA->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); QgsSpatialIndex spatialIndex( sourceB->getFeatures( QgsFeatureRequest().setNoAttributes().setDestinationCrs( sourceA->sourceCrs(), context.transformContext() ) ), feedback ); QgsFeature outFeature; QgsFeatureIterator features = sourceA->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( fieldIndicesA ) ); 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.constGet() ) ); engine->prepareGeometry(); QgsFeatureRequest request = QgsFeatureRequest().setFilterFids( lines ); request.setDestinationCrs( sourceA->sourceCrs(), context.transformContext() ); request.setSubsetOfAttributes( fieldIndicesB ); QgsFeature inFeatureB; QgsFeatureIterator featuresB = sourceB->getFeatures( request ); while ( featuresB.nextFeature( inFeatureB ) ) { if ( feedback->isCanceled() ) { break; } QgsGeometry tmpGeom = inFeatureB.geometry(); if ( engine->intersects( tmpGeom.constGet() ) ) { QgsMultiPointXY points; QgsGeometry intersectGeom = inGeom.intersection( tmpGeom ); QgsAttributes outAttributes; for ( int a : qgis::as_const( fieldIndicesA ) ) { outAttributes.append( inFeatureA.attribute( a ) ); } for ( int b : qgis::as_const( fieldIndicesB ) ) { outAttributes.append( inFeatureB.attribute( b ) ); } if ( QgsWkbTypes::flatType( intersectGeom.wkbType() ) == QgsWkbTypes::GeometryCollection ) { const QVector<QgsGeometry> geomCollection = intersectGeom.asGeometryCollection(); for ( const QgsGeometry &part : geomCollection ) { if ( part.type() == QgsWkbTypes::PointGeometry ) { if ( part.isMultipart() ) { points = part.asMultiPoint(); } else { points.append( part.asPoint() ); } } } } else if ( intersectGeom.type() == QgsWkbTypes::PointGeometry ) { if ( intersectGeom.isMultipart() ) { points = intersectGeom.asMultiPoint(); } else { points.append( intersectGeom.asPoint() ); } } for ( const QgsPointXY &j : qgis::as_const( points ) ) { outFeature.setGeometry( QgsGeometry::fromPointXY( j ) ); outFeature.setAttributes( outAttributes ); sink->addFeature( outFeature, QgsFeatureSink::FastInsert ); } } } } feedback->setProgress( i * step ); } QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; }
QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const { QSet< QString > toExecute; QMap< QString, ChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin(); for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt ) { if ( childIt->isActive() && childIt->algorithm() ) toExecute.insert( childIt->childId() ); } QTime totalTime; totalTime.start(); QMap< QString, QVariantMap > childResults; QVariantMap finalResults; QSet< QString > executed; bool executedAlg = true; while ( executedAlg && executed.count() < toExecute.count() ) { executedAlg = false; Q_FOREACH ( const QString &childId, toExecute ) { if ( executed.contains( childId ) ) continue; bool canExecute = true; Q_FOREACH ( const QString &dependency, dependsOnChildAlgorithms( childId ) ) { if ( !executed.contains( dependency ) ) { canExecute = false; break; } } if ( !canExecute ) continue; executedAlg = true; feedback->pushDebugInfo( QObject::tr( "Prepare algorithm: %1" ).arg( childId ) ); const ChildAlgorithm &child = mChildAlgorithms[ childId ]; QVariantMap childParams = parametersForChildAlgorithm( child, parameters, childResults ); feedback->setProgressText( QObject::tr( "Running %1 [%2/%3]" ).arg( child.description() ).arg( executed.count() + 1 ).arg( toExecute.count() ) ); //feedback->pushDebugInfo( "Parameters: " + ', '.join( [str( p ).strip() + // '=' + str( p.value ) for p in alg.algorithm.parameters] ) ) QTime childTime; childTime.start(); bool ok = false; QVariantMap results = child.algorithm()->run( childParams, context, feedback, &ok ); if ( !ok ) { QString error = QObject::tr( "Error encountered while running %1" ).arg( child.description() ); feedback->reportError( error ); throw QgsProcessingException( error ); } childResults.insert( childId, results ); // look through child alg's outputs to determine whether any of these should be copied // to the final model outputs QMap<QString, QgsProcessingModelAlgorithm::ModelOutput> outputs = child.modelOutputs(); QMap<QString, QgsProcessingModelAlgorithm::ModelOutput>::const_iterator outputIt = outputs.constBegin(); for ( ; outputIt != outputs.constEnd(); ++outputIt ) { finalResults.insert( childId + ':' + outputIt->name(), results.value( outputIt->childOutputName() ) ); } executed.insert( childId ); feedback->pushDebugInfo( QObject::tr( "OK. Execution took %1 s (%2 outputs)." ).arg( childTime.elapsed() / 1000.0 ).arg( results.count() ) ); } } feedback->pushDebugInfo( QObject::tr( "Model processed ok. Executed %1 algorithms total in %2 s." ).arg( executed.count() ).arg( totalTime.elapsed() / 1000.0 ) ); return finalResults; }
void QgsFileDownloaderAlgorithm::reportErrors( QStringList errors ) { throw QgsProcessingException( errors.join( '\n' ) ); }
QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { bool overwrite = parameterAsBool( parameters, QStringLiteral( "OVERWRITE" ), context ); QString packagePath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context ); if ( packagePath.isEmpty() ) throw QgsProcessingException( QObject::tr( "No output file specified." ) ); // delete existing geopackage if it exists if ( overwrite && QFile::exists( packagePath ) ) { feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( packagePath ) ); if ( !QFile( packagePath ).remove() ) { throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ) ); } } OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" ); if ( !hGpkgDriver ) { throw QgsProcessingException( QObject::tr( "GeoPackage driver not found." ) ); } gdal::ogr_datasource_unique_ptr hDS( OGR_Dr_CreateDataSource( hGpkgDriver, packagePath.toUtf8().constData(), nullptr ) ); if ( !hDS ) throw QgsProcessingException( QObject::tr( "Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) ); bool errored = false; const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context ); QgsProcessingMultiStepFeedback multiStepFeedback( layers.count(), feedback ); int i = 0; for ( QgsMapLayer *layer : layers ) { if ( feedback->isCanceled() ) break; multiStepFeedback.setCurrentStep( i ); i++; feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( i ).arg( layers.count() ).arg( layer ? layer->name() : QString() ) ); if ( !layer ) { // don't throw immediately - instead do what we can and error out later feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) ); errored = true; continue; } switch ( layer->type() ) { case QgsMapLayer::VectorLayer: { if ( !packageVectorLayer( qobject_cast< QgsVectorLayer * >( layer ), packagePath, context, &multiStepFeedback ) ) errored = true; break; } case QgsMapLayer::RasterLayer: { //not supported feedback->pushDebugInfo( QObject::tr( "Raster layers are not currently supported." ) ); errored = true; break; } case QgsMapLayer::PluginLayer: //not supported feedback->pushDebugInfo( QObject::tr( "Packaging plugin layers is not supported." ) ); errored = true; break; } } if ( errored ) throw QgsProcessingException( QObject::tr( "Error obtained while packaging one or more layers." ) ); QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), packagePath ); return outputs; }
QVariantMap QgsShortestPathPointToPointAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { loadCommonParams( parameters, context, feedback ); QgsFields fields; fields.append( QgsField( QStringLiteral( "start" ), QVariant::String ) ); fields.append( QgsField( QStringLiteral( "end" ), QVariant::String ) ); fields.append( QgsField( QStringLiteral( "cost" ), QVariant::Double ) ); QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, QgsWkbTypes::LineString, mNetwork->sourceCrs() ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); QgsPointXY startPoint = parameterAsPoint( parameters, QStringLiteral( "START_POINT" ), context, mNetwork->sourceCrs() ); QgsPointXY endPoint = parameterAsPoint( parameters, QStringLiteral( "END_POINT" ), context, mNetwork->sourceCrs() ); feedback->pushInfo( QObject::tr( "Building graph…" ) ); QVector< QgsPointXY > points; points << startPoint << endPoint; QVector< QgsPointXY > snappedPoints; mDirector->makeGraph( mBuilder.get(), points, snappedPoints, feedback ); feedback->pushInfo( QObject::tr( "Calculating shortest path…" ) ); QgsGraph *graph = mBuilder->graph(); int idxStart = graph->findVertex( snappedPoints[0] ); int idxEnd = graph->findVertex( snappedPoints[1] ); QVector< int > tree; QVector< double > costs; QgsGraphAnalyzer::dijkstra( graph, idxStart, 0, &tree, &costs ); if ( tree.at( idxEnd ) == -1 ) { throw QgsProcessingException( QObject::tr( "There is no route from start point to end point." ) ); } QVector<QgsPointXY> route; route.push_front( graph->vertex( idxEnd ).point() ); double cost = costs.at( idxEnd ); while ( idxEnd != idxStart ) { idxEnd = graph->edge( tree.at( idxEnd ) ).fromVertex(); route.push_front( graph->vertex( idxEnd ).point() ); } feedback->pushInfo( QObject::tr( "Writing results…" ) ); QgsGeometry geom = QgsGeometry::fromPolylineXY( route ); QgsFeature feat; feat.setFields( fields ); QgsAttributes attributes; attributes << startPoint.toString() << endPoint.toString() << cost / mMultiplier; feat.setGeometry( geom ); feat.setAttributes( attributes ); sink->addFeature( feat, QgsFeatureSink::FastInsert ); QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); outputs.insert( QStringLiteral( "TRAVEL_COST" ), cost / mMultiplier ); return outputs; }
QVariantMap QgsShortestPathPointToLayerAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { loadCommonParams( parameters, context, feedback ); QgsPointXY startPoint = parameterAsPoint( parameters, QStringLiteral( "START_POINT" ), context, mNetwork->sourceCrs() ); std::unique_ptr< QgsFeatureSource > endPoints( parameterAsSource( parameters, QStringLiteral( "END_POINTS" ), context ) ); if ( !endPoints ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "END_POINTS" ) ) ); QgsFields fields = endPoints->fields(); fields.append( QgsField( QStringLiteral( "start" ), QVariant::String ) ); fields.append( QgsField( QStringLiteral( "end" ), QVariant::String ) ); fields.append( QgsField( QStringLiteral( "cost" ), QVariant::Double ) ); QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, QgsWkbTypes::LineString, mNetwork->sourceCrs() ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); QVector< QgsPointXY > points; points.push_front( startPoint ); QHash< int, QgsAttributes > sourceAttributes; loadPoints( endPoints.get(), points, sourceAttributes, context, feedback ); feedback->pushInfo( QObject::tr( "Building graph…" ) ); QVector< QgsPointXY > snappedPoints; mDirector->makeGraph( mBuilder.get(), points, snappedPoints, feedback ); feedback->pushInfo( QObject::tr( "Calculating shortest paths…" ) ); QgsGraph *graph = mBuilder->graph(); int idxStart = graph->findVertex( snappedPoints[0] ); int idxEnd; QVector< int > tree; QVector< double > costs; QgsGraphAnalyzer::dijkstra( graph, idxStart, 0, &tree, &costs ); QVector<QgsPointXY> route; double cost; QgsFeature feat; feat.setFields( fields ); QgsAttributes attributes; int step = points.size() > 0 ? 100.0 / points.size() : 1; for ( int i = 1; i < points.size(); i++ ) { if ( feedback->isCanceled() ) { break; } idxEnd = graph->findVertex( snappedPoints[i] ); if ( tree.at( idxEnd ) == -1 ) { feedback->reportError( QObject::tr( "There is no route from start point (%1) to end point (%2)." ) .arg( startPoint.toString(), points[i].toString() ) ); feat.clearGeometry(); attributes = sourceAttributes.value( i ); attributes.append( QVariant() ); attributes.append( points[i].toString() ); feat.setAttributes( attributes ); sink->addFeature( feat, QgsFeatureSink::FastInsert ); continue; } route.clear(); route.push_front( graph->vertex( idxEnd ).point() ); cost = costs.at( idxEnd ); while ( idxEnd != idxStart ) { idxEnd = graph->edge( tree.at( idxEnd ) ).fromVertex(); route.push_front( graph->vertex( idxEnd ).point() ); } QgsGeometry geom = QgsGeometry::fromPolylineXY( route ); QgsFeature feat; feat.setFields( fields ); attributes = sourceAttributes.value( i ); attributes.append( startPoint.toString() ); attributes.append( points[i].toString() ); attributes.append( cost / mMultiplier ); feat.setAttributes( attributes ); feat.setGeometry( geom ); sink->addFeature( feat, QgsFeatureSink::FastInsert ); feedback->setProgress( i * step ); } QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; }
QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { std::unique_ptr< QgsFeatureSource > featureSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); if ( !featureSource ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); std::unique_ptr< QgsFeatureSource > maskSource( parameterAsSource( parameters, QStringLiteral( "OVERLAY" ), context ) ); if ( !maskSource ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "OVERLAY" ) ) ); QString dest; QgsWkbTypes::GeometryType sinkType = QgsWkbTypes::geometryType( featureSource->wkbType() ); std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, featureSource->fields(), QgsWkbTypes::multiType( featureSource->wkbType() ), featureSource->sourceCrs() ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); // first build up a list of clip geometries QVector< QgsGeometry > clipGeoms; QgsFeatureIterator it = maskSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QList< int >() ).setDestinationCrs( featureSource->sourceCrs(), context.transformContext() ) ); 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 ); if ( combinedClipGeom.isEmpty() ) { throw QgsProcessingException( QObject::tr( "Could not create the combined clip geometry: %1" ).arg( combinedClipGeom.lastError() ) ); } singleClipFeature = false; } else { combinedClipGeom = clipGeoms.at( 0 ); singleClipFeature = true; } // use prepared geometries for faster intersection tests std::unique_ptr< QgsGeometryEngine > engine( QgsGeometry::createGeometryEngine( combinedClipGeom.constGet() ) ); 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().constGet() ) ) continue; QgsGeometry newGeometry; if ( !engine->contains( inputFeature.geometry().constGet() ) ) { QgsGeometry currentGeometry = inputFeature.geometry(); newGeometry = combinedClipGeom.intersection( currentGeometry ); if ( newGeometry.wkbType() == QgsWkbTypes::Unknown || QgsWkbTypes::flatType( newGeometry.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(); } if ( !QgsOverlayUtils::sanitizeIntersectionResult( newGeometry, sinkType ) ) continue; 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() ); } }
QVariantMap QgsTransectAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { Side orientation = static_cast< QgsTransectAlgorithm::Side >( parameterAsInt( parameters, QStringLiteral( "SIDE" ), context ) ); double angle = fabs( parameterAsDouble( parameters, QStringLiteral( "ANGLE" ), context ) ); bool dynamicAngle = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "ANGLE" ) ); QgsProperty angleProperty; if ( dynamicAngle ) angleProperty = parameters.value( QStringLiteral( "ANGLE" ) ).value< QgsProperty >(); double length = parameterAsDouble( parameters, QStringLiteral( "LENGTH" ), context ); bool dynamicLength = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "LENGTH" ) ); QgsProperty lengthProperty; if ( dynamicLength ) lengthProperty = parameters.value( QStringLiteral( "LENGTH" ) ).value< QgsProperty >(); if ( orientation == QgsTransectAlgorithm::Both ) length /= 2.0; std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); if ( !source ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); QgsExpressionContext expressionContext = createExpressionContext( parameters, context, dynamic_cast< QgsProcessingFeatureSource * >( source.get() ) ); QgsFields fields = source->fields(); fields.append( QgsField( QStringLiteral( "TR_FID" ), QVariant::Int, QString(), 20 ) ); fields.append( QgsField( QStringLiteral( "TR_ID" ), QVariant::Int, QString(), 20 ) ); fields.append( QgsField( QStringLiteral( "TR_SEGMENT" ), QVariant::Int, QString(), 20 ) ); fields.append( QgsField( QStringLiteral( "TR_ANGLE" ), QVariant::Double, QString(), 5, 2 ) ); fields.append( QgsField( QStringLiteral( "TR_LENGTH" ), QVariant::Double, QString(), 20, 6 ) ); fields.append( QgsField( QStringLiteral( "TR_ORIENT" ), QVariant::Int, QString(), 1 ) ); QgsWkbTypes::Type outputWkb = QgsWkbTypes::LineString; if ( QgsWkbTypes::hasZ( source->wkbType() ) ) outputWkb = QgsWkbTypes::addZ( outputWkb ); if ( QgsWkbTypes::hasM( source->wkbType() ) ) outputWkb = QgsWkbTypes::addM( outputWkb ); QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, outputWkb, source->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); QgsFeatureIterator features = source->getFeatures( ); int current = -1; int number = 0; double step = source->featureCount() > 0 ? 100.0 / source->featureCount() : 1; QgsFeature feat; while ( features.nextFeature( feat ) ) { current++; if ( feedback->isCanceled() ) { break; } feedback->setProgress( current * step ); if ( !feat.hasGeometry() ) continue; QgsGeometry inputGeometry = feat.geometry(); if ( dynamicLength || dynamicAngle ) { expressionContext.setFeature( feat ); } double evaluatedLength = length; if ( dynamicLength ) evaluatedLength = lengthProperty.valueAsDouble( context.expressionContext(), length ); double evaluatedAngle = angle; if ( dynamicAngle ) evaluatedAngle = angleProperty.valueAsDouble( context.expressionContext(), angle ); inputGeometry.convertToMultiType(); const QgsMultiLineString *multiLine = static_cast< const QgsMultiLineString * >( inputGeometry.constGet() ); for ( int id = 0; id < multiLine->numGeometries(); ++id ) { const QgsLineString *line = static_cast< const QgsLineString * >( multiLine->geometryN( id ) ); QgsAbstractGeometry::vertex_iterator it = line->vertices_begin(); while ( it != line->vertices_end() ) { QgsVertexId vertexId = it.vertexId(); int i = vertexId.vertex; QgsFeature outFeat; QgsAttributes attrs = feat.attributes(); attrs << current << number << i + 1 << evaluatedAngle << ( ( orientation == QgsTransectAlgorithm::Both ) ? evaluatedLength * 2 : evaluatedLength ) << orientation; outFeat.setAttributes( attrs ); double angleAtVertex = line->vertexAngle( vertexId ); outFeat.setGeometry( calcTransect( *it, angleAtVertex, evaluatedLength, orientation, evaluatedAngle ) ); sink->addFeature( outFeat, QgsFeatureSink::FastInsert ); number++; it++; } } } QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; }