QgsFeatureList QgsSnapToGridAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { QgsFeature f = feature; if ( f.hasGeometry() ) { double intervalX = mIntervalX; if ( mDynamicIntervalX ) intervalX = mIntervalXProperty.valueAsDouble( context.expressionContext(), intervalX ); double intervalY = mIntervalY; if ( mDynamicIntervalY ) intervalY = mIntervalYProperty.valueAsDouble( context.expressionContext(), intervalY ); double intervalZ = mIntervalZ; if ( mDynamicIntervalZ ) intervalZ = mIntervalZProperty.valueAsDouble( context.expressionContext(), intervalZ ); double intervalM = mIntervalM; if ( mDynamicIntervalM ) intervalM = mIntervalMProperty.valueAsDouble( context.expressionContext(), intervalM ); QgsGeometry outputGeometry = f.geometry().snappedToGrid( intervalX, intervalY, intervalZ, intervalM ); if ( !outputGeometry ) { feedback->reportError( QObject::tr( "Error snapping geometry %1" ).arg( feature.id() ) ); } f.setGeometry( outputGeometry ); } return QgsFeatureList() << f; }
QVariantMap QgsProcessingAlgorithm::postProcess( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { Q_ASSERT_X( QThread::currentThread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::postProcess", "postProcess() must be called from the same thread the context was created in" ); Q_ASSERT_X( mHasExecuted, "QgsProcessingAlgorithm::postProcess", QStringLiteral( "algorithm instance %1 was not executed" ).arg( name() ).toLatin1() ); Q_ASSERT_X( !mHasPostProcessed, "QgsProcessingAlgorithm::postProcess", "postProcess() was already called for this algorithm instance" ); if ( mLocalContext ) { // algorithm was processed using a temporary thread safe context. So now we need // to take the results from that temporary context, and smash them into the passed // context context.takeResultsFrom( *mLocalContext ); // now get lost, we don't need you anymore mLocalContext.reset(); } mHasPostProcessed = true; try { return postProcessAlgorithm( context, feedback ); } catch ( QgsProcessingException &e ) { QgsMessageLog::logMessage( e.what(), QObject::tr( "Processing" ), Qgis::Critical ); feedback->reportError( e.what() ); return QVariantMap(); } }
QVariantMap QgsProcessingAlgorithm::runPrepared( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { Q_ASSERT_X( mHasPrepared, "QgsProcessingAlgorithm::runPrepared", QStringLiteral( "prepare() was not called for the algorithm instance %1" ).arg( name() ).toLatin1() ); Q_ASSERT_X( !mHasExecuted, "QgsProcessingAlgorithm::runPrepared", "runPrepared() was already called for this algorithm instance" ); // Hey kids, let's all be thread safe! It's the fun thing to do! // // First, let's see if we're going to run into issues. QgsProcessingContext *runContext = nullptr; if ( context.thread() == QThread::currentThread() ) { // OH. No issues. Seems you're running everything in the same thread, so go about your business. Sorry about // the intrusion, we're just making sure everything's nice and safe here. We like to keep a clean and tidy neighbourhood, // you know, for the kids and dogs and all. runContext = &context; } else { // HA! I knew things looked a bit suspicious - seems you're running this algorithm in a different thread // from that which the passed context has an affinity for. That's fine and all, but we need to make sure // we proceed safely... // So first we create a temporary local context with affinity for the current thread mLocalContext.reset( new QgsProcessingContext() ); // copy across everything we can safely do from the passed context mLocalContext->copyThreadSafeSettings( context ); // and we'll run the actual algorithm processing using the local thread safe context runContext = mLocalContext.get(); } try { QVariantMap runResults = processAlgorithm( parameters, *runContext, feedback ); mHasExecuted = true; if ( mLocalContext ) { // ok, time to clean things up. We need to push the temporary context back into // the thread that the passed context is associated with (we can only push from the // current thread, so we HAVE to do this here) mLocalContext->pushToThread( context.thread() ); } return runResults; } catch ( QgsProcessingException & ) { if ( mLocalContext ) { // see above! mLocalContext->pushToThread( context.thread() ); } //rethrow throw; } }
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; }
QgsExpressionContext QgsProcessingAlgorithm::createExpressionContext( const QVariantMap ¶meters, QgsProcessingContext &context ) const { // start with context's expression context QgsExpressionContext c = context.expressionContext(); if ( c.scopeCount() == 0 ) { //empty scope, populate with initial scopes c << QgsExpressionContextUtils::globalScope() << QgsExpressionContextUtils::projectScope( context.project() ); } c << QgsExpressionContextUtils::processingAlgorithmScope( this, parameters, context ); return c; }
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; }
QgsFeatureList QgsFilterVerticesAlgorithmBase::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback * ) { QgsFeature f = feature; if ( f.hasGeometry() ) { QgsGeometry geometry = f.geometry(); double min = mMin; if ( mDynamicMin ) min = mMinProperty.valueAsDouble( context.expressionContext(), std::numeric_limits<double>::quiet_NaN() ); double max = mMax; if ( mDynamicMax ) max = mMaxProperty.valueAsDouble( context.expressionContext(), std::numeric_limits<double>::quiet_NaN() ); filter( geometry, min, max ); f.setGeometry( geometry ); } return QgsFeatureList() << f; }
QgsFeatureList QgsLineSubstringAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback * ) { QgsFeature f = feature; if ( f.hasGeometry() ) { const QgsGeometry geometry = f.geometry(); double startDistance = mStartDistance; if ( mDynamicStartDistance ) startDistance = mStartDistanceProperty.valueAsDouble( context.expressionContext(), startDistance ); double endDistance = mEndDistance; if ( mDynamicEndDistance ) endDistance = mEndDistanceProperty.valueAsDouble( context.expressionContext(), endDistance ); const QgsCurve *curve = nullptr; if ( !geometry.isMultipart() ) curve = qgsgeometry_cast< const QgsCurve * >( geometry.constGet() ); else { if ( const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( geometry.constGet() ) ) { if ( collection->numGeometries() > 0 ) { curve = qgsgeometry_cast< const QgsCurve * >( collection->geometryN( 0 ) ); } } } if ( curve ) { std::unique_ptr< QgsCurve > substring( curve->curveSubstring( startDistance, endDistance ) ); QgsGeometry result( std::move( substring ) ); f.setGeometry( result ); } else { f.clearGeometry(); } } return QgsFeatureList() << f; }
QgsExpressionContext QgsProcessingAlgorithm::createExpressionContext( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeatureSource *source ) const { // start with context's expression context QgsExpressionContext c = context.expressionContext(); // If there's a source capable of generating a context scope, use it if ( source ) { QgsExpressionContextScope *scope = source->createExpressionContextScope(); if ( scope ) c << scope; } else if ( c.scopeCount() == 0 ) { //empty scope, populate with initial scopes c << QgsExpressionContextUtils::globalScope() << QgsExpressionContextUtils::projectScope( context.project() ); } c << QgsExpressionContextUtils::processingAlgorithmScope( this, parameters, context ); return c; }
bool QgsProcessingAlgorithm::prepare( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { Q_ASSERT_X( QThread::currentThread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::prepare", "prepare() must be called from the same thread as context was created in" ); Q_ASSERT_X( !mHasPrepared, "QgsProcessingAlgorithm::prepare", "prepare() has already been called for the algorithm instance" ); try { mHasPrepared = prepareAlgorithm( parameters, context, feedback ); return mHasPrepared; } catch ( QgsProcessingException &e ) { QgsMessageLog::logMessage( e.what(), QObject::tr( "Processing" ), Qgis::Critical ); feedback->reportError( e.what() ); return false; } }
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 ); }
bool QgsPackageAlgorithm::packageVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { QgsVectorFileWriter::SaveVectorOptions options; options.driverName = QStringLiteral( "GPKG" ); options.layerName = layer->name(); options.actionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer; options.fileEncoding = context.defaultEncoding(); options.feedback = feedback; QString error; if ( QgsVectorFileWriter::writeAsVectorFormat( layer, path, options, &error ) != QgsVectorFileWriter::NoError ) { feedback->pushDebugInfo( QObject::tr( "Packaging layer failed: %1" ).arg( error ) ); return false; } else { return true; } }
void QgsNetworkAnalysisAlgorithmBase::loadPoints( QgsFeatureSource *source, QVector< QgsPointXY > &points, QHash< int, QgsAttributes > &attributes, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { feedback->pushInfo( QObject::tr( "Loading points…" ) ); QgsFeature feat; int i = 0; int pointId = 1; double step = source->featureCount() > 0 ? 100.0 / source->featureCount() : 0; QgsFeatureIterator features = source->getFeatures( QgsFeatureRequest().setDestinationCrs( mNetwork->sourceCrs(), context.transformContext() ) ); while ( features.nextFeature( feat ) ) { i++; if ( feedback->isCanceled() ) { break; } feedback->setProgress( i * step ); if ( !feat.hasGeometry() ) continue; QgsGeometry geom = feat.geometry(); QgsAbstractGeometry::vertex_iterator it = geom.vertices_begin(); while ( it != geom.vertices_end() ) { points.push_back( QgsPointXY( *it ) ); attributes.insert( pointId, feat.attributes() ); it++; pointId++; } } }
void QgsOverlayUtils::difference( const QgsFeatureSource &sourceA, const QgsFeatureSource &sourceB, QgsFeatureSink &sink, QgsProcessingContext &context, QgsProcessingFeedback *feedback, int &count, int totalCount, QgsOverlayUtils::DifferenceOutput outputAttrs ) { QgsFeatureRequest requestB; requestB.setSubsetOfAttributes( QgsAttributeList() ); if ( outputAttrs != OutputBA ) requestB.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() ); QgsSpatialIndex indexB( sourceB.getFeatures( requestB ), feedback ); int fieldsCountA = sourceA.fields().count(); int fieldsCountB = sourceB.fields().count(); QgsAttributes attrs; attrs.resize( outputAttrs == OutputA ? fieldsCountA : ( fieldsCountA + fieldsCountB ) ); if ( totalCount == 0 ) totalCount = 1; // avoid division by zero QgsFeature featA; QgsFeatureRequest requestA; if ( outputAttrs == OutputBA ) requestA.setDestinationCrs( sourceB.sourceCrs(), context.transformContext() ); QgsFeatureIterator fitA = sourceA.getFeatures( requestA ); while ( fitA.nextFeature( featA ) ) { if ( feedback->isCanceled() ) break; if ( featA.hasGeometry() ) { QgsGeometry geom( featA.geometry() ); QgsFeatureIds intersects = indexB.intersects( geom.boundingBox() ).toSet(); QgsFeatureRequest request; request.setFilterFids( intersects ); request.setSubsetOfAttributes( QgsAttributeList() ); if ( outputAttrs != OutputBA ) request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() ); std::unique_ptr< QgsGeometryEngine > engine; if ( !intersects.isEmpty() ) { // use prepared geometries for faster intersection tests engine.reset( QgsGeometry::createGeometryEngine( geom.constGet() ) ); engine->prepareGeometry(); } QVector<QgsGeometry> geometriesB; QgsFeature featB; QgsFeatureIterator fitB = sourceB.getFeatures( request ); while ( fitB.nextFeature( featB ) ) { if ( feedback->isCanceled() ) break; if ( engine->intersects( featB.geometry().constGet() ) ) geometriesB << featB.geometry(); } if ( !geometriesB.isEmpty() ) { QgsGeometry geomB = QgsGeometry::unaryUnion( geometriesB ); geom = geom.difference( geomB ); } if ( !sanitizeDifferenceResult( geom ) ) continue; const QgsAttributes attrsA( featA.attributes() ); switch ( outputAttrs ) { case OutputA: attrs = attrsA; break; case OutputAB: for ( int i = 0; i < fieldsCountA; ++i ) attrs[i] = attrsA[i]; break; case OutputBA: for ( int i = 0; i < fieldsCountA; ++i ) attrs[i + fieldsCountB] = attrsA[i]; break; } QgsFeature outFeat; outFeat.setGeometry( geom ); outFeat.setAttributes( attrs ); sink.addFeature( outFeat, QgsFeatureSink::FastInsert ); } else { // TODO: should we write out features that do not have geometry? sink.addFeature( featA, QgsFeatureSink::FastInsert ); } ++count; feedback->setProgress( count / ( double ) totalCount * 100. ); } }
void QgsOverlayUtils::intersection( const QgsFeatureSource &sourceA, const QgsFeatureSource &sourceB, QgsFeatureSink &sink, QgsProcessingContext &context, QgsProcessingFeedback *feedback, int &count, int totalCount, const QList<int> &fieldIndicesA, const QList<int> &fieldIndicesB ) { QgsWkbTypes::GeometryType geometryType = QgsWkbTypes::geometryType( QgsWkbTypes::multiType( sourceA.wkbType() ) ); int attrCount = fieldIndicesA.count() + fieldIndicesB.count(); QgsFeatureRequest request; request.setSubsetOfAttributes( QgsAttributeList() ); request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() ); QgsFeature outFeat; QgsSpatialIndex indexB( sourceB.getFeatures( request ), feedback ); if ( totalCount == 0 ) totalCount = 1; // avoid division by zero QgsFeature featA; QgsFeatureIterator fitA = sourceA.getFeatures( QgsFeatureRequest().setSubsetOfAttributes( fieldIndicesA ) ); while ( fitA.nextFeature( featA ) ) { if ( feedback->isCanceled() ) break; if ( !featA.hasGeometry() ) continue; QgsGeometry geom( featA.geometry() ); QgsFeatureIds intersects = indexB.intersects( geom.boundingBox() ).toSet(); QgsFeatureRequest request; request.setFilterFids( intersects ); request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() ); request.setSubsetOfAttributes( fieldIndicesB ); std::unique_ptr< QgsGeometryEngine > engine; if ( !intersects.isEmpty() ) { // use prepared geometries for faster intersection tests engine.reset( QgsGeometry::createGeometryEngine( geom.constGet() ) ); engine->prepareGeometry(); } QgsAttributes outAttributes( attrCount ); const QgsAttributes attrsA( featA.attributes() ); for ( int i = 0; i < fieldIndicesA.count(); ++i ) outAttributes[i] = attrsA[fieldIndicesA[i]]; QgsFeature featB; QgsFeatureIterator fitB = sourceB.getFeatures( request ); while ( fitB.nextFeature( featB ) ) { if ( feedback->isCanceled() ) break; QgsGeometry tmpGeom( featB.geometry() ); if ( !engine->intersects( tmpGeom.constGet() ) ) continue; QgsGeometry intGeom = geom.intersection( tmpGeom ); if ( !sanitizeIntersectionResult( intGeom, geometryType ) ) continue; const QgsAttributes attrsB( featB.attributes() ); for ( int i = 0; i < fieldIndicesB.count(); ++i ) outAttributes[fieldIndicesA.count() + i] = attrsB[fieldIndicesB[i]]; outFeat.setGeometry( intGeom ); outFeat.setAttributes( outAttributes ); sink.addFeature( outFeat, QgsFeatureSink::FastInsert ); } ++count; feedback->setProgress( count / ( double ) totalCount * 100. ); } }
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< QgsFeatureSource > hubSource( parameterAsSource( parameters, QStringLiteral( "HUBS" ), context ) ); std::unique_ptr< QgsFeatureSource > spokeSource( parameterAsSource( parameters, QStringLiteral( "SPOKES" ), context ) ); if ( !hubSource || !spokeSource ) return QVariantMap(); 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" ) ); QgsFields hubOutFields; QgsAttributeList hubFieldIndices; if ( hubFieldsToCopy.empty() ) { hubOutFields = hubSource->fields(); for ( int i = 0; i < hubOutFields.count(); ++i ) { hubFieldIndices << i; } } else { 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(); 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 = 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() ) ); if ( !sink ) return QVariantMap(); 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 ) ); 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 ); QgsFeature spokeFeature; while ( spokeFeatures.nextFeature( spokeFeature ) ) { if ( feedback->isCanceled() ) { break; } if ( !spokeFeature.hasGeometry() ) continue; QgsPoint spokePoint = getPointFromFeature( spokeFeature ); QgsGeometry line( new QgsLineString( QVector< QgsPoint >() << hubPoint << spokePoint ) ); 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 QgsBufferAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const { 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_LAYER" ), context, source->fields(), QgsWkbTypes::Polygon, source->sourceCrs(), dest ) ); 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( "MITRE_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(); if ( count <= 0 ) return QVariantMap(); QgsFeature f; QgsFeatureIterator it = source->getFeatures(); double step = 100.0 / count; 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 ); feedback->setProgress( current * step ); current++; } if ( dissolve ) { QgsGeometry finalGeometry = QgsGeometry::unaryUnion( bufferedGeometriesForDissolve ); QgsFeature f; f.setGeometry( finalGeometry ); f.setAttributes( dissolveAttrs ); sink->addFeature( f ); } QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), dest ); return outputs; }
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; }
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 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; }
bool QgsTransformAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { mDestCrs = parameterAsCrs( parameters, QStringLiteral( "TARGET_CRS" ), context ); mTransformContext = context.project() ? context.project()->transformContext() : QgsCoordinateTransformContext(); return true; }
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 QgsZonalHistogramAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { std::unique_ptr< QgsFeatureSource > zones( parameterAsSource( parameters, QStringLiteral( "INPUT_VECTOR" ), context ) ); if ( !zones ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT_VECTOR" ) ) ); long count = zones->featureCount(); double step = count > 0 ? 100.0 / count : 1; long current = 0; QList< double > uniqueValues; QMap< QgsFeatureId, QHash< double, qgssize > > featuresUniqueValues; // First loop through the zones to build up a list of unique values across all zones to determine sink fields list QgsFeatureRequest request; request.setNoAttributes(); if ( zones->sourceCrs() != mCrs ) { request.setDestinationCrs( mCrs, context.transformContext() ); } QgsFeatureIterator it = zones->getFeatures( request ); QgsFeature f; while ( it.nextFeature( f ) ) { if ( feedback && feedback->isCanceled() ) { break; } feedback->setProgress( current * step ); if ( !f.hasGeometry() ) { current++; continue; } QgsGeometry featureGeometry = f.geometry(); QgsRectangle featureRect = featureGeometry.boundingBox().intersect( mRasterExtent ); if ( featureRect.isEmpty() ) { current++; continue; } int nCellsX, nCellsY; QgsRectangle rasterBlockExtent; QgsRasterAnalysisUtils::cellInfoForBBox( mRasterExtent, featureRect, mCellSizeX, mCellSizeY, nCellsX, nCellsY, mNbCellsXProvider, mNbCellsYProvider, rasterBlockExtent ); QHash< double, qgssize > fUniqueValues; QgsRasterAnalysisUtils::statisticsFromMiddlePointTest( mRasterInterface.get(), mRasterBand, featureGeometry, nCellsX, nCellsY, mCellSizeX, mCellSizeY, rasterBlockExtent, [ &fUniqueValues]( double value ) { fUniqueValues[value]++; }, false ); if ( fUniqueValues.count() < 1 ) { // The cell resolution is probably larger than the polygon area. We switch to slower precise pixel - polygon intersection in this case // TODO: eventually deal with weight if needed QgsRasterAnalysisUtils::statisticsFromPreciseIntersection( mRasterInterface.get(), mRasterBand, featureGeometry, nCellsX, nCellsY, mCellSizeX, mCellSizeY, rasterBlockExtent, [ &fUniqueValues]( double value, double ) { fUniqueValues[value]++; }, false ); } for ( auto it = fUniqueValues.constBegin(); it != fUniqueValues.constEnd(); ++it ) { if ( uniqueValues.indexOf( it.key() ) == -1 ) { uniqueValues << it.key(); } featuresUniqueValues[f.id()][it.key()] += it.value(); } current++; } std::sort( uniqueValues.begin(), uniqueValues.end() ); QString fieldPrefix = parameterAsString( parameters, QStringLiteral( "COLUMN_PREFIX" ), context ); QgsFields newFields; for ( auto it = uniqueValues.constBegin(); it != uniqueValues.constEnd(); ++it ) { newFields.append( QgsField( QStringLiteral( "%1%2" ).arg( fieldPrefix, mHasNoDataValue && *it == mNodataValue ? QStringLiteral( "NODATA" ) : QString::number( *it ) ), QVariant::LongLong, QString(), -1, 0 ) ); } QgsFields fields = QgsProcessingUtils::combineFields( zones->fields(), newFields ); QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, zones->wkbType(), zones->sourceCrs() ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); it = zones->getFeatures( QgsFeatureRequest() ); while ( it.nextFeature( f ) ) { QgsAttributes attributes = f.attributes(); QHash< double, qgssize > fUniqueValues = featuresUniqueValues.value( f.id() ); for ( auto it = uniqueValues.constBegin(); it != uniqueValues.constEnd(); ++it ) { attributes += fUniqueValues.value( *it, 0 ); } QgsFeature outputFeature; outputFeature.setGeometry( f.geometry() ); outputFeature.setAttributes( attributes ); sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); } QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; }