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

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :     qgsgeometryselfintersectioncheck.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 "qgsgeometryselfintersectioncheck.h"
      18                 :            : #include "qgspolygon.h"
      19                 :            : #include "qgslinestring.h"
      20                 :            : #include "qgsgeometryengine.h"
      21                 :            : #include "qgsmultipolygon.h"
      22                 :            : #include "qgsmultilinestring.h"
      23                 :            : #include "qgsgeometryutils.h"
      24                 :            : #include "qgsfeaturepool.h"
      25                 :            : 
      26                 :          0 : bool QgsGeometrySelfIntersectionCheckError::isEqual( const QgsSingleGeometryCheckError *other ) const
      27                 :            : {
      28                 :          0 :   return QgsSingleGeometryCheckError::isEqual( other ) &&
      29                 :          0 :          static_cast<const QgsGeometrySelfIntersectionCheckError *>( other )->intersection().segment1 == intersection().segment1 &&
      30                 :          0 :          static_cast<const QgsGeometrySelfIntersectionCheckError *>( other )->intersection().segment2 == intersection().segment2;
      31                 :            : }
      32                 :            : 
      33                 :          2 : bool QgsGeometrySelfIntersectionCheckError::handleChanges( const QList<QgsGeometryCheck::Change> &changes )
      34                 :            : {
      35                 :          2 :   if ( !QgsSingleGeometryCheckError::handleChanges( changes ) )
      36                 :          0 :     return false;
      37                 :            : 
      38                 :          4 :   for ( const QgsGeometryCheck::Change &change : changes )
      39                 :            :   {
      40                 :          4 :     if ( change.vidx.vertex == mIntersection.segment1 ||
      41                 :          2 :          change.vidx.vertex == mIntersection.segment1 + 1 ||
      42                 :          2 :          change.vidx.vertex == mIntersection.segment2 ||
      43                 :          2 :          change.vidx.vertex == mIntersection.segment2 + 1 )
      44                 :            :     {
      45                 :          0 :       return false;
      46                 :            :     }
      47                 :          2 :     else if ( change.vidx.vertex >= 0 )
      48                 :            :     {
      49                 :          2 :       if ( change.vidx.vertex < mIntersection.segment1 )
      50                 :            :       {
      51                 :          2 :         mIntersection.segment1 += change.type == QgsGeometryCheck::ChangeAdded ? 1 : -1;
      52                 :          2 :       }
      53                 :          2 :       if ( change.vidx.vertex < mIntersection.segment2 )
      54                 :            :       {
      55                 :          2 :         mIntersection.segment2 += change.type == QgsGeometryCheck::ChangeAdded ? 1 : -1;
      56                 :          2 :       }
      57                 :          2 :     }
      58                 :            :   }
      59                 :          2 :   return true;
      60                 :          2 : }
      61                 :            : 
      62                 :          0 : void QgsGeometrySelfIntersectionCheckError::update( const QgsSingleGeometryCheckError *other )
      63                 :            : {
      64                 :          0 :   QgsSingleGeometryCheckError::update( other );
      65                 :            :   // Static cast since this should only get called if isEqual == true
      66                 :          0 :   const QgsGeometrySelfIntersectionCheckError *err = static_cast<const QgsGeometrySelfIntersectionCheckError *>( other );
      67                 :          0 :   mIntersection.point = err->mIntersection.point;
      68                 :          0 : }
      69                 :            : 
      70                 :          4 : void QgsGeometrySelfIntersectionCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
      71                 :            : {
      72                 :          4 :   QgsFeaturePool *featurePool = featurePools[ error->layerId() ];
      73                 :          4 :   QgsFeature feature;
      74                 :          4 :   if ( !featurePool->getFeature( error->featureId(), feature ) )
      75                 :            :   {
      76                 :          0 :     error->setObsolete();
      77                 :          0 :     return;
      78                 :            :   }
      79                 :            : 
      80                 :          4 :   QgsGeometry featureGeom = feature.geometry();
      81                 :          4 :   QgsAbstractGeometry *geom = featureGeom.get();
      82                 :            : 
      83                 :          4 :   const QgsVertexId vidx = error->vidx();
      84                 :            : 
      85                 :            :   // Check if ring still exists
      86                 :          4 :   if ( !vidx.isValid( geom ) )
      87                 :            :   {
      88                 :          0 :     error->setObsolete();
      89                 :          0 :     return;
      90                 :            :   }
      91                 :            : 
      92                 :          4 :   const QgsGeometryCheckErrorSingle *singleError = static_cast<const QgsGeometryCheckErrorSingle *>( error );
      93                 :          4 :   const QgsGeometryUtils::SelfIntersection &inter = static_cast<const QgsGeometrySelfIntersectionCheckError *>( singleError->singleError() )->intersection();
      94                 :            :   // Check if error still applies
      95                 :          4 :   bool ringIsClosed = false;
      96                 :          4 :   int nVerts = QgsGeometryCheckerUtils::polyLineSize( geom, vidx.part, vidx.ring, &ringIsClosed );
      97                 :          4 :   if ( nVerts == 0 || inter.segment1 >= nVerts || inter.segment2 >= nVerts )
      98                 :            :   {
      99                 :          0 :     error->setObsolete();
     100                 :          0 :     return;
     101                 :            :   }
     102                 :          4 :   QgsPoint p1 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, inter.segment1 ) );
     103                 :          4 :   QgsPoint q1 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, inter.segment2 ) );
     104                 :          4 :   QgsPoint p2 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( inter.segment1 + 1 ) % nVerts ) );
     105                 :          4 :   QgsPoint q2 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( inter.segment2 + 1 ) % nVerts ) );
     106                 :          4 :   QgsPoint s;
     107                 :          4 :   bool intersection = false;
     108                 :          4 :   if ( !QgsGeometryUtils::segmentIntersection( p1, p2, q1, q2, s, intersection, mContext->tolerance ) )
     109                 :            :   {
     110                 :          0 :     error->setObsolete();
     111                 :          0 :     return;
     112                 :            :   }
     113                 :            : 
     114                 :            :   // Fix with selected method
     115                 :          4 :   if ( method == NoChange )
     116                 :            :   {
     117                 :          0 :     error->setFixed( method );
     118                 :          0 :   }
     119                 :          4 :   else if ( method == ToMultiObject || method == ToSingleObjects )
     120                 :            :   {
     121                 :            :     // Extract rings
     122                 :          4 :     QgsPointSequence ring1, ring2;
     123                 :          4 :     bool ring1EndsWithS = false;
     124                 :          4 :     bool ring2EndsWithS = false;
     125                 :         29 :     for ( int i = 0; i < nVerts; ++i )
     126                 :            :     {
     127                 :         25 :       if ( i <= inter.segment1 || i >= inter.segment2 + 1 )
     128                 :            :       {
     129                 :         13 :         ring1.append( geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, i ) ) );
     130                 :         13 :         ring1EndsWithS = false;
     131                 :         13 :         if ( i == inter.segment2 + 1 )
     132                 :            :         {
     133                 :          3 :           ring2.append( s );
     134                 :          3 :           ring2EndsWithS = true;
     135                 :          3 :         }
     136                 :         13 :       }
     137                 :            :       else
     138                 :            :       {
     139                 :         12 :         ring2.append( geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, i ) ) );
     140                 :         12 :         ring2EndsWithS = true;
     141                 :         12 :         if ( i == inter.segment1 + 1 )
     142                 :            :         {
     143                 :          4 :           ring1.append( s );
     144                 :          4 :           ring1EndsWithS = false;
     145                 :          4 :         }
     146                 :            :       }
     147                 :         25 :     }
     148                 :          4 :     if ( nVerts == inter.segment2 + 1 )
     149                 :            :     {
     150                 :          1 :       ring2.append( s );
     151                 :          1 :       ring2EndsWithS = true;
     152                 :          1 :     }
     153                 :          4 :     if ( ringIsClosed || ring1EndsWithS )
     154                 :          2 :       ring1.append( ring1.front() ); // Ensure ring is closed
     155                 :          4 :     if ( ringIsClosed || ring2EndsWithS )
     156                 :          4 :       ring2.append( ring2.front() ); // Ensure ring is closed
     157                 :            : 
     158                 :          4 :     if ( ring1.size() < 3 + ( ringIsClosed || ring1EndsWithS ) || ring2.size() < 3 + ( ringIsClosed || ring2EndsWithS ) )
     159                 :            :     {
     160                 :          0 :       error->setFixFailed( tr( "Resulting geometry is degenerate" ) );
     161                 :          0 :       return;
     162                 :            :     }
     163                 :          4 :     QgsLineString *ringGeom1 = new QgsLineString();
     164                 :          4 :     ringGeom1->setPoints( ring1 );
     165                 :          4 :     QgsLineString *ringGeom2 = new QgsLineString();
     166                 :          4 :     ringGeom2->setPoints( ring2 );
     167                 :            : 
     168                 :          4 :     QgsAbstractGeometry *part = QgsGeometryCheckerUtils::getGeomPart( geom, vidx.part );
     169                 :            :     // If is a polygon...
     170                 :          4 :     if ( dynamic_cast<QgsCurvePolygon *>( part ) )
     171                 :            :     {
     172                 :          2 :       QgsCurvePolygon *poly = static_cast<QgsCurvePolygon *>( part );
     173                 :            :       // If self-intersecting ring is an interior ring, create separate holes
     174                 :          2 :       if ( vidx.ring > 0 )
     175                 :            :       {
     176                 :          0 :         poly->removeInteriorRing( vidx.ring );
     177                 :          0 :         poly->addInteriorRing( ringGeom1 );
     178                 :          0 :         poly->addInteriorRing( ringGeom2 );
     179                 :          0 :         changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeRemoved, vidx ) );
     180                 :          0 :         changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeAdded, QgsVertexId( vidx.part, poly->ringCount() - 2 ) ) );
     181                 :          0 :         changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeAdded, QgsVertexId( vidx.part, poly->ringCount() - 1 ) ) );
     182                 :          0 :         feature.setGeometry( featureGeom );
     183                 :          0 :         featurePool->updateFeature( feature );
     184                 :          0 :       }
     185                 :            :       else
     186                 :            :       {
     187                 :            :         // If ring is exterior, build two polygons, and reassign interiors as necessary
     188                 :          2 :         poly->setExteriorRing( ringGeom1 );
     189                 :            : 
     190                 :            :         // If original feature was a linear polygon, also create the new part as a linear polygon
     191                 :          2 :         QgsCurvePolygon *poly2 = dynamic_cast<QgsPolygon *>( part ) ? new QgsPolygon() : new QgsCurvePolygon();
     192                 :          2 :         poly2->setExteriorRing( ringGeom2 );
     193                 :            : 
     194                 :            :         // Reassing interiors as necessary
     195                 :          2 :         std::unique_ptr< QgsGeometryEngine > geomEnginePoly1 = QgsGeometryCheckerUtils::createGeomEngine( poly, mContext->tolerance );
     196                 :          2 :         std::unique_ptr< QgsGeometryEngine > geomEnginePoly2 = QgsGeometryCheckerUtils::createGeomEngine( poly2, mContext->tolerance );
     197                 :          3 :         for ( int n = poly->numInteriorRings(), i = n - 1; i >= 0; --i )
     198                 :            :         {
     199                 :          1 :           if ( !geomEnginePoly1->contains( poly->interiorRing( i ) ) )
     200                 :            :           {
     201                 :          1 :             if ( geomEnginePoly2->contains( poly->interiorRing( i ) ) )
     202                 :            :             {
     203                 :          1 :               poly2->addInteriorRing( static_cast<QgsCurve *>( poly->interiorRing( i )->clone() ) );
     204                 :            :               // No point in adding ChangeAdded changes, since the entire poly2 is added anyways later on
     205                 :          1 :             }
     206                 :          1 :             poly->removeInteriorRing( i );
     207                 :          1 :             changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeRemoved, QgsVertexId( vidx.part, 1 + i ) ) );
     208                 :          1 :           }
     209                 :          1 :         }
     210                 :            : 
     211                 :          2 :         if ( method == ToMultiObject )
     212                 :            :         {
     213                 :            :           // If is already a geometry collection, just add the new polygon.
     214                 :          1 :           if ( dynamic_cast<QgsGeometryCollection *>( geom ) )
     215                 :            :           {
     216                 :          1 :             static_cast<QgsGeometryCollection *>( geom )->addGeometry( poly2 );
     217                 :          1 :             changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeChanged, QgsVertexId( vidx.part, vidx.ring ) ) );
     218                 :          1 :             changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geom->partCount() - 1 ) ) );
     219                 :          1 :             feature.setGeometry( featureGeom );
     220                 :          1 :             featurePool->updateFeature( feature );
     221                 :          1 :           }
     222                 :            :           // Otherwise, create multipolygon
     223                 :            :           else
     224                 :            :           {
     225                 :          0 :             QgsMultiPolygon *multiPoly = new QgsMultiPolygon();
     226                 :          0 :             multiPoly->addGeometry( poly->clone() );
     227                 :          0 :             multiPoly->addGeometry( poly2 );
     228                 :          0 :             feature.setGeometry( QgsGeometry( multiPoly ) );
     229                 :          0 :             featurePool->updateFeature( feature );
     230                 :          0 :             changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) );
     231                 :            :           }
     232                 :          1 :         }
     233                 :            :         else // if ( method == ToSingleObjects )
     234                 :            :         {
     235                 :          1 :           QgsFeature newFeature;
     236                 :          1 :           newFeature.setAttributes( feature.attributes() );
     237                 :          1 :           newFeature.setGeometry( QgsGeometry( poly2 ) );
     238                 :          1 :           feature.setGeometry( featureGeom );
     239                 :          1 :           featurePool->updateFeature( feature );
     240                 :          1 :           featurePool->addFeature( newFeature );
     241                 :          1 :           changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeChanged, QgsVertexId( vidx.part, vidx.ring ) ) );
     242                 :          1 :           changes[error->layerId()][newFeature.id()].append( Change( ChangeFeature, ChangeAdded ) );
     243                 :          1 :         }
     244                 :          2 :       }
     245                 :          2 :     }
     246                 :          2 :     else if ( dynamic_cast<QgsCurve *>( part ) )
     247                 :            :     {
     248                 :          2 :       if ( method == ToMultiObject )
     249                 :            :       {
     250                 :          1 :         if ( dynamic_cast<QgsGeometryCollection *>( geom ) )
     251                 :            :         {
     252                 :          1 :           QgsGeometryCollection *geomCollection = static_cast<QgsGeometryCollection *>( geom );
     253                 :          1 :           geomCollection->removeGeometry( vidx.part );
     254                 :          1 :           geomCollection->addGeometry( ringGeom1 );
     255                 :          1 :           geomCollection->addGeometry( ringGeom2 );
     256                 :          1 :           feature.setGeometry( featureGeom );
     257                 :          1 :           featurePool->updateFeature( feature );
     258                 :          1 :           changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( vidx.part ) ) );
     259                 :          1 :           changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 2 ) ) );
     260                 :          1 :           changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 1 ) ) );
     261                 :          1 :         }
     262                 :            :         else
     263                 :            :         {
     264                 :          0 :           QgsMultiCurve *geomCollection = new QgsMultiLineString();
     265                 :          0 :           geomCollection->addGeometry( ringGeom1 );
     266                 :          0 :           geomCollection->addGeometry( ringGeom2 );
     267                 :          0 :           feature.setGeometry( QgsGeometry( geomCollection ) );
     268                 :          0 :           featurePool->updateFeature( feature );
     269                 :          0 :           changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) );
     270                 :            :         }
     271                 :          1 :       }
     272                 :            :       else // if(method == ToSingleObjects)
     273                 :            :       {
     274                 :          1 :         if ( dynamic_cast<QgsGeometryCollection *>( geom ) )
     275                 :            :         {
     276                 :          1 :           QgsGeometryCollection *geomCollection = static_cast<QgsGeometryCollection *>( geom );
     277                 :          1 :           geomCollection->removeGeometry( vidx.part );
     278                 :          1 :           geomCollection->addGeometry( ringGeom1 );
     279                 :          1 :           feature.setGeometry( featureGeom );
     280                 :          1 :           featurePool->updateFeature( feature );
     281                 :          1 :           changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( vidx.part ) ) );
     282                 :          1 :           changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 1 ) ) );
     283                 :          1 :         }
     284                 :            :         else
     285                 :            :         {
     286                 :          0 :           feature.setGeometry( QgsGeometry( ringGeom1 ) );
     287                 :          0 :           featurePool->updateFeature( feature );
     288                 :          0 :           changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged, QgsVertexId( vidx.part ) ) );
     289                 :            :         }
     290                 :          1 :         QgsFeature newFeature;
     291                 :          1 :         newFeature.setAttributes( feature.attributes() );
     292                 :          1 :         newFeature.setGeometry( QgsGeometry( ringGeom2 ) );
     293                 :          1 :         featurePool->addFeature( newFeature );
     294                 :          1 :         changes[error->layerId()][newFeature.id()].append( Change( ChangeFeature, ChangeAdded ) );
     295                 :          1 :       }
     296                 :          2 :     }
     297                 :            :     else
     298                 :            :     {
     299                 :          0 :       delete ringGeom1;
     300                 :          0 :       delete ringGeom2;
     301                 :            :     }
     302                 :          4 :     error->setFixed( method );
     303                 :          4 :   }
     304                 :            :   else
     305                 :            :   {
     306                 :          0 :     error->setFixFailed( tr( "Unknown method" ) );
     307                 :            :   }
     308                 :          4 : }
     309                 :            : 
     310                 :          4 : QStringList QgsGeometrySelfIntersectionCheck::resolutionMethods() const
     311                 :            : {
     312                 :          5 :   static QStringList methods = QStringList()
     313                 :          1 :                                << tr( "Split feature into a multi-object feature" )
     314                 :          1 :                                << tr( "Split feature into multiple single-object features" )
     315                 :          1 :                                << tr( "No action" );
     316                 :          4 :   return methods;
     317                 :          0 : }
     318                 :            : 
     319                 :         34 : QList<QgsSingleGeometryCheckError *> QgsGeometrySelfIntersectionCheck::processGeometry( const QgsGeometry &geometry ) const
     320                 :            : {
     321                 :         34 :   QList<QgsSingleGeometryCheckError *> errors;
     322                 :         34 :   const QgsAbstractGeometry *geom = geometry.constGet();
     323                 :         70 :   for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart )
     324                 :            :   {
     325                 :         73 :     for ( int iRing = 0, nRings = geom->ringCount( iPart ); iRing < nRings; ++iRing )
     326                 :            :     {
     327                 :         42 :       for ( const QgsGeometryUtils::SelfIntersection &inter : QgsGeometryUtils::selfIntersections( geom, iPart, iRing, mContext->tolerance ) )
     328                 :            :       {
     329                 :          5 :         errors.append( new QgsGeometrySelfIntersectionCheckError( this, geometry, QgsGeometry( inter.point.clone() ), QgsVertexId( iPart, iRing ), inter ) );
     330                 :            :       }
     331                 :         37 :     }
     332                 :         36 :   }
     333                 :         34 :   return errors;
     334                 :         34 : }
     335                 :            : 
     336                 :            : ///@cond private
     337                 :          1 : QList<QgsWkbTypes::GeometryType> QgsGeometrySelfIntersectionCheck::factoryCompatibleGeometryTypes()
     338                 :            : {
     339                 :          1 :   return {QgsWkbTypes::LineGeometry, QgsWkbTypes::PolygonGeometry};
     340                 :            : }
     341                 :            : 
     342                 :          0 : bool QgsGeometrySelfIntersectionCheck::factoryIsCompatible( QgsVectorLayer *layer )
     343                 :            : {
     344                 :          0 :   return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
     345                 :          0 : }
     346                 :            : 
     347                 :          0 : QString QgsGeometrySelfIntersectionCheck::factoryDescription()
     348                 :            : {
     349                 :          0 :   return tr( "Self intersection" );
     350                 :            : }
     351                 :            : 
     352                 :          0 : QgsGeometryCheck::Flags QgsGeometrySelfIntersectionCheck::factoryFlags()
     353                 :            : {
     354                 :          0 :   return QgsGeometryCheck::Flags();
     355                 :            : }
     356                 :            : 
     357                 :          0 : QString QgsGeometrySelfIntersectionCheck::factoryId()
     358                 :            : {
     359                 :          0 :   return QStringLiteral( "QgsGeometrySelfIntersectionCheck" );
     360                 :            : }
     361                 :            : 
     362                 :          0 : QgsGeometryCheck::CheckType QgsGeometrySelfIntersectionCheck::factoryCheckType()
     363                 :            : {
     364                 :          0 :   return QgsGeometryCheck::FeatureNodeCheck;
     365                 :            : }
     366                 :            : ///@endcond private

Generated by: LCOV version 1.14