24 QString QgsDetectVectorChangesAlgorithm::name()
 const 
   26   return QStringLiteral( 
"detectvectorchanges" );
 
   29 QString QgsDetectVectorChangesAlgorithm::displayName()
 const 
   31   return QObject::tr( 
"Detect dataset changes" );
 
   34 QStringList QgsDetectVectorChangesAlgorithm::tags()
 const 
   36   return QObject::tr( 
"added,dropped,new,deleted,features,geometries,difference,delta,revised,original,version" ).split( 
',' );
 
   39 QString QgsDetectVectorChangesAlgorithm::group()
 const 
   41   return QObject::tr( 
"Vector general" );
 
   44 QString QgsDetectVectorChangesAlgorithm::groupId()
 const 
   46   return QStringLiteral( 
"vectorgeneral" );
 
   49 void QgsDetectVectorChangesAlgorithm::initAlgorithm( 
const QVariantMap & )
 
   54   std::unique_ptr< QgsProcessingParameterField > compareAttributesParam = std::make_unique< QgsProcessingParameterField >( QStringLiteral( 
"COMPARE_ATTRIBUTES" ),
 
   55       QObject::tr( 
"Attributes to consider for match (or none to compare geometry only)" ), QVariant(),
 
   57   compareAttributesParam->setDefaultToAllFields( 
true );
 
   58   addParameter( compareAttributesParam.release() );
 
   60   std::unique_ptr< QgsProcessingParameterDefinition > matchTypeParam = std::make_unique< QgsProcessingParameterEnum >( QStringLiteral( 
"MATCH_TYPE" ),
 
   61       QObject::tr( 
"Geometry comparison behavior" ),
 
   62       QStringList() << QObject::tr( 
"Exact Match" )
 
   63       << QObject::tr( 
"Tolerant Match (Topological Equality)" ),
 
   66   addParameter( matchTypeParam.release() );
 
   72   addOutput( 
new QgsProcessingOutputNumber( QStringLiteral( 
"UNCHANGED_COUNT" ), QObject::tr( 
"Count of unchanged features" ) ) );
 
   73   addOutput( 
new QgsProcessingOutputNumber( QStringLiteral( 
"ADDED_COUNT" ), QObject::tr( 
"Count of features added in revised layer" ) ) );
 
   74   addOutput( 
new QgsProcessingOutputNumber( QStringLiteral( 
"DELETED_COUNT" ), QObject::tr( 
"Count of features deleted from original layer" ) ) );
 
   77 QString QgsDetectVectorChangesAlgorithm::shortHelpString()
 const 
   79   return QObject::tr( 
"This algorithm compares two vector layers, and determines which features are unchanged, added or deleted between " 
   80                       "the two. It is designed for comparing two different versions of the same dataset.\n\n" 
   81                       "When comparing features, the original and revised feature geometries will be compared against each other. Depending " 
   82                       "on the Geometry Comparison Behavior setting, the comparison will either be made using an exact comparison (where " 
   83                       "geometries must be an exact match for each other, including the order and count of vertices) or a topological " 
   84                       "comparison only (where are geometries area considered equal if all of their component edges overlap. E.g. " 
   85                       "lines with the same vertex locations but opposite direction will be considered equal by this method). If the topological " 
   86                       "comparison is selected then any z or m values present in the geometries will not be compared.\n\n" 
   87                       "By default, the algorithm compares all attributes from the original and revised features. If the Attributes to Consider for Match " 
   88                       "parameter is changed, then only the selected attributes will be compared (e.g. allowing users to ignore a timestamp or ID field " 
   89                       "which is expected to change between the revisions).\n\n" 
   90                       "If any features in the original or revised layers do not have an associated geometry, then care must be taken to ensure " 
   91                       "that these features have a unique set of attributes selected for comparison. If this condition is not met, warnings will be " 
   92                       "raised and the resultant outputs may be misleading.\n\n" 
   93                       "The algorithm outputs three layers, one containing all features which are considered to be unchanged between the revisions, " 
   94                       "one containing features deleted from the original layer which are not present in the revised layer, and one containing features " 
   95                       "add to the revised layer which are not present in the original layer." );
 
   98 QString QgsDetectVectorChangesAlgorithm::shortDescription()
 const 
  100   return QObject::tr( 
"Calculates features which are unchanged, added or deleted between two dataset versions." );
 
  103 QgsDetectVectorChangesAlgorithm *QgsDetectVectorChangesAlgorithm::createInstance()
 const 
  105   return new QgsDetectVectorChangesAlgorithm();
 
  110   mOriginal.reset( parameterAsSource( parameters, QStringLiteral( 
"ORIGINAL" ), context ) );
 
  114   mRevised.reset( parameterAsSource( parameters, QStringLiteral( 
"REVISED" ), context ) );
 
  118   mMatchType = 
static_cast< GeometryMatchType 
>( parameterAsEnum( parameters, QStringLiteral( 
"MATCH_TYPE" ), context ) );
 
  120   switch ( mMatchType )
 
  123       if ( mOriginal->wkbType() != mRevised->wkbType() )
 
  136   if ( mOriginal->sourceCrs() != mRevised->sourceCrs() )
 
  137     feedback->
reportError( QObject::tr( 
"CRS for revised layer (%1) does not match the original layer (%2) - reprojection accuracy may affect geometry matching" ).arg( mOriginal->sourceCrs().userFriendlyIdentifier(),
 
  138                            mRevised->sourceCrs().userFriendlyIdentifier() ), false );
 
  140   mFieldsToCompare = parameterAsFields( parameters, QStringLiteral( 
"COMPARE_ATTRIBUTES" ), context );
 
  141   mOriginalFieldsToCompareIndices.reserve( mFieldsToCompare.size() );
 
  142   mRevisedFieldsToCompareIndices.reserve( mFieldsToCompare.size() );
 
  143   QStringList missingOriginalFields;
 
  144   QStringList missingRevisedFields;
 
  145   for ( 
const QString &
field : mFieldsToCompare )
 
  147     const int originalIndex = mOriginal->fields().lookupField( 
field );
 
  148     mOriginalFieldsToCompareIndices.append( originalIndex );
 
  149     if ( originalIndex < 0 )
 
  150       missingOriginalFields << 
field;
 
  152     const int revisedIndex = mRevised->fields().lookupField( 
field );
 
  153     if ( revisedIndex < 0 )
 
  154       missingRevisedFields << 
field;
 
  155     mRevisedFieldsToCompareIndices.append( revisedIndex );
 
  158   if ( !missingOriginalFields.empty() )
 
  159     throw QgsProcessingException( QObject::tr( 
"Original layer missing selected comparison attributes: %1" ).arg( missingOriginalFields.join( 
',' ) ) );
 
  160   if ( !missingRevisedFields.empty() )
 
  161     throw QgsProcessingException( QObject::tr( 
"Revised layer missing selected comparison attributes: %1" ).arg( missingRevisedFields.join( 
',' ) ) );
 
  168   QString unchangedDestId;
 
  169   std::unique_ptr< QgsFeatureSink > unchangedSink( parameterAsSink( parameters, QStringLiteral( 
"UNCHANGED" ), context, unchangedDestId, mOriginal->fields(),
 
  170       mOriginal->wkbType(), mOriginal->sourceCrs() ) );
 
  171   if ( !unchangedSink && parameters.value( QStringLiteral( 
"UNCHANGED" ) ).isValid() )
 
  175   std::unique_ptr< QgsFeatureSink > addedSink( parameterAsSink( parameters, QStringLiteral( 
"ADDED" ), context, addedDestId, mRevised->fields(),
 
  176       mRevised->wkbType(), mRevised->sourceCrs() ) );
 
  177   if ( !addedSink && parameters.value( QStringLiteral( 
"ADDED" ) ).isValid() )
 
  180   QString deletedDestId;
 
  181   std::unique_ptr< QgsFeatureSink > deletedSink( parameterAsSink( parameters, QStringLiteral( 
"DELETED" ), context, deletedDestId, mOriginal->fields(),
 
  182       mOriginal->wkbType(), mOriginal->sourceCrs() ) );
 
  183   if ( !deletedSink && parameters.value( QStringLiteral( 
"DELETED" ) ).isValid() )
 
  193   double step = mOriginal->featureCount() > 0 ? 100.0 / mOriginal->featureCount() : 0;
 
  194   QHash< QgsFeatureId, QgsGeometry > originalGeometries;
 
  195   QHash< QgsFeatureId, QgsAttributes > originalAttributes;
 
  196   QHash< QgsAttributes, QgsFeatureId > originalNullGeometryAttributes;
 
  200   attrs.resize( mFieldsToCompare.size() );
 
  209       originalGeometries.insert( f.id(), f.geometry() );
 
  212     if ( !mFieldsToCompare.empty() )
 
  215       for ( int field : mOriginalFieldsToCompareIndices )
 
  217         attrs[idx++] = f.attributes().at( field );
 
  219       originalAttributes.insert( f.
id(), attrs );
 
  224       if ( originalNullGeometryAttributes.contains( attrs ) )
 
  226         feedback->reportError( QObject::tr( 
"A non-unique set of comparison attributes was found for " 
  227                                             "one or more features without geometries - results may be misleading (features %1 and %2)" ).arg( f.id() ).arg( originalNullGeometryAttributes.value( attrs ) ) );
 
  231         originalNullGeometryAttributes.insert( attrs, f.id() );
 
  241   QSet<QgsFeatureId> unchangedOriginalIds;
 
  242   QSet<QgsFeatureId> addedRevisedIds;
 
  247   step = mRevised->featureCount() > 0 ? 100.0 / mRevised->featureCount() : 0;
 
  250   it = mRevised->getFeatures( revisedRequest );
 
  258     for ( 
int field : mRevisedFieldsToCompareIndices )
 
  263     bool matched = 
false;
 
  267       if ( originalNullGeometryAttributes.contains( attrs ) )
 
  270         unchangedOriginalIds.insert( originalNullGeometryAttributes.value( attrs ) );
 
  277       const QList<QgsFeatureId> candidates = index.intersects( revisedFeature.
geometry().
boundingBox() );
 
  284         if ( unchangedOriginalIds.contains( candidateId ) )
 
  291         if ( !mFieldsToCompare.empty() )
 
  293           if ( attrs != originalAttributes[ candidateId ] )
 
  300         QgsGeometry original = originalGeometries.value( candidateId );
 
  304           revised = revisedFeature.
geometry();
 
  306           switch ( mMatchType )
 
  322         bool geometryMatch = 
false;
 
  323         switch ( mMatchType )
 
  332             geometryMatch = revised.
equals( original );
 
  339           unchangedOriginalIds.insert( candidateId );
 
  349       addedRevisedIds.insert( revisedFeature.
id() );
 
  353     feedback->
setProgress( 0.70 * current * step + 10 ); 
 
  359   step = mOriginal->featureCount() > 0 ? 100.0 / mOriginal->featureCount() : 0;
 
  362   it = mOriginal->getFeatures( request );
 
  374     if ( unchangedOriginalIds.contains( f.
id() ) )
 
  389     feedback->
setProgress( 0.10 * current * step + 80 ); 
 
  400     step = addedRevisedIds.size() > 0 ? 100.0 / addedRevisedIds.size() : 0;
 
  401     it = mRevised->getFeatures( 
QgsFeatureRequest().setFilterFids( addedRevisedIds ) );
 
  412       feedback->
setProgress( 0.10 * current * step + 90 ); 
 
  417   feedback->
pushInfo( QObject::tr( 
"%1 features unchanged" ).arg( unchangedOriginalIds.size() ) );
 
  418   feedback->
pushInfo( QObject::tr( 
"%1 features added" ).arg( addedRevisedIds.size() ) );
 
  419   feedback->
pushInfo( QObject::tr( 
"%1 features deleted" ).arg( deleted ) );
 
  422   outputs.insert( QStringLiteral( 
"UNCHANGED" ), unchangedDestId );
 
  423   outputs.insert( QStringLiteral( 
"ADDED" ), addedDestId );
 
  424   outputs.insert( QStringLiteral( 
"DELETED" ), deletedDestId );
 
  425   outputs.insert( QStringLiteral( 
"UNCHANGED_COUNT" ), 
static_cast< long long >( unchangedOriginalIds.size() ) );
 
  426   outputs.insert( QStringLiteral( 
"ADDED_COUNT" ), 
static_cast< long long >( addedRevisedIds.size() ) );
 
  427   outputs.insert( QStringLiteral( 
"DELETED_COUNT" ), 
static_cast< long long >( deleted ) );
 
virtual bool dropMValue()=0
Drops any measure values which exist in the geometry.
virtual bool dropZValue()=0
Drops any z-dimensions which exist in the geometry.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature's geometries.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
bool hasGeometry() const
Returns true if the feature has an associated geometry.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
void setProgress(double progress)
Sets the current progress for the feedback object.
A geometry is the spatial representation of a feature.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
bool equals(const QgsGeometry &geometry) const
Test if this geometry is exactly equal to another geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
bool isGeosEqual(const QgsGeometry &) const
Compares the geometry with another geometry using GEOS.
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
A numeric output for processing algorithms.
@ FlagAdvanced
Parameter is an advanced parameter which should be hidden from users by default.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
@ TypeVectorAnyGeometry
Any vector layer with geometry.
A spatial index for QgsFeature objects.
static GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static QString geometryDisplayString(GeometryType type) SIP_HOLDGIL
Returns a display string for a geometry type.
static QString displayString(Type type) SIP_HOLDGIL
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features