LCOV - code coverage report
Current view: top level - analysis/vector/geometry_checker - qgsgeometryoverlapcheck.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 115 184 62.5 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :     qgsgeometryoverlapcheck.cpp
       3                 :            :     ---------------------
       4                 :            :     begin                : September 2015
       5                 :            :     copyright            : (C) 2014 by Sandro Mani / Sourcepole AG
       6                 :            :     email                : smani at sourcepole dot ch
       7                 :            :  ***************************************************************************
       8                 :            :  *                                                                         *
       9                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      10                 :            :  *   it under the terms of the GNU General Public License as published by  *
      11                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      12                 :            :  *   (at your option) any later version.                                   *
      13                 :            :  *                                                                         *
      14                 :            :  ***************************************************************************/
      15                 :            : 
      16                 :            : #include "qgsgeometrycheckcontext.h"
      17                 :            : #include "qgsgeometryengine.h"
      18                 :            : #include "qgsgeometryoverlapcheck.h"
      19                 :            : #include "qgsfeaturepool.h"
      20                 :            : #include "qgsvectorlayer.h"
      21                 :            : #include "qgsfeedback.h"
      22                 :            : #include "qgsapplication.h"
      23                 :            : 
      24                 :          3 : QgsGeometryOverlapCheck::QgsGeometryOverlapCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration )
      25                 :          3 :   : QgsGeometryCheck( context, configuration )
      26                 :          6 :   , mOverlapThresholdMapUnits( configurationValue<double>( QStringLiteral( "maxOverlapArea" ) ) )
      27                 :            : 
      28                 :          6 : {
      29                 :            : 
      30                 :          3 : }
      31                 :            : 
      32                 :          3 : void QgsGeometryOverlapCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const
      33                 :            : {
      34                 :          3 :   QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap();
      35                 :          3 :   const QgsGeometryCheckerUtils::LayerFeatures layerFeaturesA( featurePools, featureIds, compatibleGeometryTypes(), feedback, mContext, true );
      36                 :          3 :   QList<QString> layerIds = featureIds.keys();
      37                 :         55 :   for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureA : layerFeaturesA )
      38                 :            :   {
      39                 :         52 :     if ( feedback && feedback->isCanceled() )
      40                 :          0 :       break;
      41                 :            : 
      42                 :            :     // Ensure each pair of layers only gets compared once: remove the current layer from the layerIds, but add it to the layerList for layerFeaturesB
      43                 :         52 :     layerIds.removeOne( layerFeatureA.layer()->id() );
      44                 :            : 
      45                 :         52 :     const QgsGeometry geomA = layerFeatureA.geometry();
      46                 :         52 :     QgsRectangle bboxA = geomA.boundingBox();
      47                 :         52 :     std::unique_ptr< QgsGeometryEngine > geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( geomA.constGet(), mContext->tolerance );
      48                 :         52 :     geomEngineA->prepareGeometry();
      49                 :         52 :     if ( !geomEngineA->isValid() )
      50                 :            :     {
      51                 :          4 :       messages.append( tr( "Overlap check failed for (%1): the geometry is invalid" ).arg( layerFeatureA.id() ) );
      52                 :          4 :       continue;
      53                 :            :     }
      54                 :            : 
      55                 :         48 :     const QgsGeometryCheckerUtils::LayerFeatures layerFeaturesB( featurePools, QList<QString>() << layerFeatureA.layer()->id() << layerIds, bboxA, compatibleGeometryTypes(), mContext );
      56                 :        222 :     for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureB : layerFeaturesB )
      57                 :            :     {
      58                 :        174 :       if ( feedback && feedback->isCanceled() )
      59                 :          0 :         break;
      60                 :            : 
      61                 :            :       // > : only report overlaps within same layer once
      62                 :        174 :       if ( layerFeatureA.layerId() == layerFeatureB.layerId() && layerFeatureB.feature().id() >= layerFeatureA.feature().id() )
      63                 :            :       {
      64                 :        109 :         continue;
      65                 :            :       }
      66                 :            : 
      67                 :         65 :       QString errMsg;
      68                 :         65 :       const QgsGeometry geometryB = layerFeatureB.geometry();
      69                 :         65 :       const QgsAbstractGeometry *geomB = geometryB.constGet();
      70                 :         65 :       if ( geomEngineA->overlaps( geomB, &errMsg ) )
      71                 :            :       {
      72                 :          9 :         std::unique_ptr<QgsAbstractGeometry> interGeom( geomEngineA->intersection( geomB ) );
      73                 :          9 :         if ( interGeom && !interGeom->isEmpty() )
      74                 :            :         {
      75                 :          7 :           QgsGeometryCheckerUtils::filter1DTypes( interGeom.get() );
      76                 :         16 :           for ( int iPart = 0, nParts = interGeom->partCount(); iPart < nParts; ++iPart )
      77                 :            :           {
      78                 :          9 :             QgsAbstractGeometry *interPart = QgsGeometryCheckerUtils::getGeomPart( interGeom.get(), iPart );
      79                 :          9 :             double area = interPart->area();
      80                 :          9 :             if ( area > mContext->reducedTolerance && ( area < mOverlapThresholdMapUnits || mOverlapThresholdMapUnits == 0.0 ) )
      81                 :            :             {
      82                 :          7 :               errors.append( new QgsGeometryOverlapCheckError( this, layerFeatureA, QgsGeometry( interPart->clone() ), interPart->centroid(), area, layerFeatureB ) );
      83                 :          7 :             }
      84                 :          9 :           }
      85                 :          7 :         }
      86                 :          2 :         else if ( !errMsg.isEmpty() )
      87                 :            :         {
      88                 :          0 :           messages.append( tr( "Overlap check between features %1 and %2 %3" ).arg( layerFeatureA.id(), layerFeatureB.id(), errMsg ) );
      89                 :          0 :         }
      90                 :          9 :       }
      91                 :         65 :     }
      92                 :         52 :   }
      93                 :          3 : }
      94                 :            : 
      95                 :          2 : void QgsGeometryOverlapCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
      96                 :            : {
      97                 :          2 :   QString errMsg;
      98                 :          2 :   QgsGeometryOverlapCheckError *overlapError = static_cast<QgsGeometryOverlapCheckError *>( error );
      99                 :            : 
     100                 :          2 :   QgsFeaturePool *featurePoolA = featurePools[ overlapError->layerId() ];
     101                 :          2 :   QgsFeaturePool *featurePoolB = featurePools[ overlapError->overlappedFeature().layerId() ];
     102                 :          2 :   QgsFeature featureA;
     103                 :          2 :   QgsFeature featureB;
     104                 :          4 :   if ( !featurePoolA->getFeature( overlapError->featureId(), featureA ) ||
     105                 :          2 :        !featurePoolB->getFeature( overlapError->overlappedFeature().featureId(), featureB ) )
     106                 :            :   {
     107                 :          0 :     error->setObsolete();
     108                 :          0 :     return;
     109                 :            :   }
     110                 :            : 
     111                 :            :   // Check if error still applies
     112                 :          2 :   QgsGeometryCheckerUtils::LayerFeature layerFeatureA( featurePoolA, featureA, mContext, true );
     113                 :          2 :   QgsGeometryCheckerUtils::LayerFeature layerFeatureB( featurePoolB, featureB, mContext, true );
     114                 :          2 :   const QgsGeometry geometryA = layerFeatureA.geometry();
     115                 :          2 :   std::unique_ptr< QgsGeometryEngine > geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( geometryA.constGet(), mContext->tolerance );
     116                 :          2 :   geomEngineA->prepareGeometry();
     117                 :            : 
     118                 :          2 :   const QgsGeometry geometryB = layerFeatureB.geometry();
     119                 :          2 :   if ( !geomEngineA->overlaps( geometryB.constGet() ) )
     120                 :            :   {
     121                 :          0 :     error->setObsolete();
     122                 :          0 :     return;
     123                 :            :   }
     124                 :          2 :   std::unique_ptr< QgsAbstractGeometry > interGeom( geomEngineA->intersection( geometryB.constGet(), &errMsg ) );
     125                 :          2 :   if ( !interGeom )
     126                 :            :   {
     127                 :          0 :     error->setFixFailed( tr( "Failed to compute intersection between overlapping features: %1" ).arg( errMsg ) );
     128                 :          0 :     return;
     129                 :            :   }
     130                 :            : 
     131                 :            :   // Search which overlap part this error parametrizes (using fuzzy-matching of the area and centroid...)
     132                 :          2 :   QgsAbstractGeometry *interPart = nullptr;
     133                 :          2 :   for ( int iPart = 0, nParts = interGeom->partCount(); iPart < nParts; ++iPart )
     134                 :            :   {
     135                 :          2 :     QgsAbstractGeometry *part = QgsGeometryCheckerUtils::getGeomPart( interGeom.get(), iPart );
     136                 :          4 :     if ( std::fabs( part->area() - overlapError->value().toDouble() ) < mContext->reducedTolerance &&
     137                 :          2 :          QgsGeometryCheckerUtils::pointsFuzzyEqual( part->centroid(), overlapError->location(), mContext->reducedTolerance ) )
     138                 :            :     {
     139                 :          2 :       interPart = part;
     140                 :          2 :       break;
     141                 :            :     }
     142                 :          0 :   }
     143                 :          2 :   if ( !interPart || interPart->isEmpty() )
     144                 :            :   {
     145                 :          0 :     error->setObsolete();
     146                 :          0 :     return;
     147                 :            :   }
     148                 :            : 
     149                 :            :   // Fix error
     150                 :          2 :   if ( method == NoChange )
     151                 :            :   {
     152                 :          0 :     error->setFixed( method );
     153                 :          0 :   }
     154                 :          2 :   else if ( method == Subtract )
     155                 :            :   {
     156                 :          2 :     std::unique_ptr< QgsGeometryEngine > geomEngineDiffA = QgsGeometryCheckerUtils::createGeomEngine( geometryA.constGet(), 0 );
     157                 :          2 :     std::unique_ptr< QgsAbstractGeometry > diff1( geomEngineDiffA->difference( interPart, &errMsg ) );
     158                 :          2 :     if ( !diff1 || diff1->isEmpty() )
     159                 :            :     {
     160                 :          0 :       diff1.reset();
     161                 :          0 :     }
     162                 :            :     else
     163                 :            :     {
     164                 :          2 :       QgsGeometryCheckerUtils::filter1DTypes( diff1.get() );
     165                 :            :     }
     166                 :          2 :     std::unique_ptr< QgsGeometryEngine > geomEngineDiffB = QgsGeometryCheckerUtils::createGeomEngine( geometryB.constGet(), 0 );
     167                 :          2 :     std::unique_ptr< QgsAbstractGeometry > diff2( geomEngineDiffB->difference( interPart, &errMsg ) );
     168                 :          2 :     if ( !diff2 || diff2->isEmpty() )
     169                 :            :     {
     170                 :          0 :       diff2.reset();
     171                 :          0 :     }
     172                 :            :     else
     173                 :            :     {
     174                 :          2 :       QgsGeometryCheckerUtils::filter1DTypes( diff2.get() );
     175                 :            :     }
     176                 :          2 :     double shared1 = diff1 ? QgsGeometryCheckerUtils::sharedEdgeLength( diff1.get(), interPart, mContext->reducedTolerance ) : 0;
     177                 :          2 :     double shared2 = diff2 ? QgsGeometryCheckerUtils::sharedEdgeLength( diff2.get(), interPart, mContext->reducedTolerance ) : 0;
     178                 :          2 :     if ( !diff1 || !diff2 || shared1 == 0. || shared2 == 0. )
     179                 :            :     {
     180                 :          0 :       error->setFixFailed( tr( "Could not find shared edges between intersection and overlapping features" ) );
     181                 :          0 :     }
     182                 :            :     else
     183                 :            :     {
     184                 :          2 :       if ( shared1 < shared2 )
     185                 :            :       {
     186                 :          1 :         QgsCoordinateTransform ct( featurePoolA->crs(), mContext->mapCrs, mContext->transformContext );
     187                 :          1 :         diff1->transform( ct, QgsCoordinateTransform::ReverseTransform );
     188                 :          1 :         featureA.setGeometry( QgsGeometry( std::move( diff1 ) ) );
     189                 :            : 
     190                 :          1 :         changes[error->layerId()][featureA.id()].append( Change( ChangeFeature, ChangeChanged ) );
     191                 :          1 :         featurePoolA->updateFeature( featureA );
     192                 :          1 :       }
     193                 :            :       else
     194                 :            :       {
     195                 :          1 :         QgsCoordinateTransform ct( featurePoolB->crs(), mContext->mapCrs, mContext->transformContext );
     196                 :          1 :         diff2->transform( ct, QgsCoordinateTransform::ReverseTransform );
     197                 :          1 :         featureB.setGeometry( QgsGeometry( std::move( diff2 ) ) );
     198                 :            : 
     199                 :          1 :         changes[overlapError->overlappedFeature().layerId()][featureB.id()].append( Change( ChangeFeature, ChangeChanged ) );
     200                 :          1 :         featurePoolB->updateFeature( featureB );
     201                 :          1 :       }
     202                 :            : 
     203                 :          2 :       error->setFixed( method );
     204                 :            :     }
     205                 :          2 :   }
     206                 :            :   else
     207                 :            :   {
     208                 :          0 :     error->setFixFailed( tr( "Unknown method" ) );
     209                 :            :   }
     210                 :          2 : }
     211                 :            : 
     212                 :          2 : QStringList QgsGeometryOverlapCheck::resolutionMethods() const
     213                 :            : {
     214                 :          3 :   static QStringList methods = QStringList()
     215                 :          1 :                                << tr( "Remove overlapping area from neighboring polygon with shortest shared edge" )
     216                 :          1 :                                << tr( "No action" );
     217                 :          2 :   return methods;
     218                 :          0 : }
     219                 :            : 
     220                 :          0 : QString QgsGeometryOverlapCheck::description() const
     221                 :            : {
     222                 :          0 :   return factoryDescription();
     223                 :            : }
     224                 :            : 
     225                 :          3 : QString QgsGeometryOverlapCheck::id() const
     226                 :            : {
     227                 :          3 :   return factoryId();
     228                 :            : }
     229                 :            : 
     230                 :          0 : QgsGeometryCheck::Flags QgsGeometryOverlapCheck::flags() const
     231                 :            : {
     232                 :          0 :   return factoryFlags();
     233                 :            : }
     234                 :            : 
     235                 :            : ///@cond private
     236                 :          0 : QString QgsGeometryOverlapCheck::factoryDescription()
     237                 :            : {
     238                 :          0 :   return tr( "Overlap" );
     239                 :            : }
     240                 :            : 
     241                 :          0 : QgsGeometryCheck::CheckType QgsGeometryOverlapCheck::factoryCheckType()
     242                 :            : {
     243                 :          0 :   return QgsGeometryCheck::LayerCheck;
     244                 :            : }
     245                 :            : 
     246                 :          3 : QString QgsGeometryOverlapCheck::factoryId()
     247                 :            : {
     248                 :          6 :   return QStringLiteral( "QgsGeometryOverlapCheck" );
     249                 :            : }
     250                 :            : 
     251                 :          0 : QgsGeometryCheck::Flags QgsGeometryOverlapCheck::factoryFlags()
     252                 :            : {
     253                 :          0 :   return QgsGeometryCheck::AvailableInValidation;
     254                 :            : }
     255                 :            : 
     256                 :         51 : QList<QgsWkbTypes::GeometryType> QgsGeometryOverlapCheck::factoryCompatibleGeometryTypes()
     257                 :            : {
     258                 :         51 :   return {QgsWkbTypes::PolygonGeometry};
     259                 :            : }
     260                 :            : 
     261                 :          0 : bool QgsGeometryOverlapCheck::factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP
     262                 :            : {
     263                 :          0 :   return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
     264                 :          0 : }
     265                 :            : 
     266                 :            : ///@endcond private
     267                 :          7 : QgsGeometryOverlapCheckError::QgsGeometryOverlapCheckError( const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsGeometry &geometry, const QgsPointXY &errorLocation, const QVariant &value, const QgsGeometryCheckerUtils::LayerFeature &overlappedFeature )
     268                 :          7 :   : QgsGeometryCheckError( check, layerFeature.layer()->id(), layerFeature.feature().id(), geometry, errorLocation, QgsVertexId(), value, ValueArea )
     269                 :          7 :   , mOverlappedFeature( OverlappedFeature( overlappedFeature.layer(), overlappedFeature.feature().id() ) )
     270                 :         14 : {
     271                 :            : 
     272                 :          7 : }
     273                 :            : 
     274                 :          0 : bool QgsGeometryOverlapCheckError::isEqual( QgsGeometryCheckError *other ) const
     275                 :            : {
     276                 :          0 :   QgsGeometryOverlapCheckError *err = dynamic_cast<QgsGeometryOverlapCheckError *>( other );
     277                 :          0 :   return err &&
     278                 :          0 :          other->layerId() == layerId() &&
     279                 :          0 :          other->featureId() == featureId() &&
     280                 :          0 :          err->overlappedFeature() == overlappedFeature() &&
     281                 :          0 :          QgsGeometryCheckerUtils::pointsFuzzyEqual( location(), other->location(), mCheck->context()->reducedTolerance ) &&
     282                 :          0 :          std::fabs( value().toDouble() - other->value().toDouble() ) < mCheck->context()->reducedTolerance;
     283                 :          0 : }
     284                 :            : 
     285                 :          0 : bool QgsGeometryOverlapCheckError::closeMatch( QgsGeometryCheckError *other ) const
     286                 :            : {
     287                 :          0 :   QgsGeometryOverlapCheckError *err = dynamic_cast<QgsGeometryOverlapCheckError *>( other );
     288                 :          0 :   return err && other->layerId() == layerId() && other->featureId() == featureId() && err->overlappedFeature() == overlappedFeature();
     289                 :            : }
     290                 :            : 
     291                 :          0 : bool QgsGeometryOverlapCheckError::handleChanges( const QgsGeometryCheck::Changes &changes )
     292                 :            : {
     293                 :          0 :   if ( !QgsGeometryCheckError::handleChanges( changes ) )
     294                 :            :   {
     295                 :          0 :     return false;
     296                 :            :   }
     297                 :          0 :   if ( changes.value( mOverlappedFeature.layerId() ).contains( mOverlappedFeature.featureId() ) )
     298                 :            :   {
     299                 :          0 :     return false;
     300                 :            :   }
     301                 :          0 :   return true;
     302                 :          0 : }
     303                 :            : 
     304                 :          0 : QString QgsGeometryOverlapCheckError::description() const
     305                 :            : {
     306                 :          0 :   return QCoreApplication::translate( "QgsGeometryTypeCheckError", "Overlap with %1 at feature %2" ).arg( mOverlappedFeature.layerName(), QString::number( mOverlappedFeature.featureId() ) );
     307                 :          0 : }
     308                 :            : 
     309                 :          0 : QMap<QString, QgsFeatureIds> QgsGeometryOverlapCheckError::involvedFeatures() const
     310                 :            : {
     311                 :          0 :   QMap<QString, QgsFeatureIds> features;
     312                 :          0 :   features[layerId()].insert( featureId() );
     313                 :          0 :   features[mOverlappedFeature.layerId()].insert( mOverlappedFeature.featureId() );
     314                 :          0 :   return features;
     315                 :          0 : }
     316                 :            : 
     317                 :          0 : QIcon QgsGeometryOverlapCheckError::icon() const
     318                 :            : {
     319                 :            : 
     320                 :          0 :   if ( status() == QgsGeometryCheckError::StatusFixed )
     321                 :          0 :     return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmCheckGeometry.svg" ) );
     322                 :            :   else
     323                 :          0 :     return QgsApplication::getThemeIcon( QStringLiteral( "/checks/Overlap.svg" ) );
     324                 :          0 : }

Generated by: LCOV version 1.14