LCOV - code coverage report
Current view: top level - analysis/vector - qgsgeometrysnappersinglesource.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 180 0.0 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :   qgsgeometrysnappersinglesource.cpp
       3                 :            :   ---------------------
       4                 :            :   Date                 : May 2018
       5                 :            :   Copyright            : (C) 2018 by Martin Dobias
       6                 :            :   Email                : wonder dot sk at gmail dot com
       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 "qgsgeometrysnappersinglesource.h"
      17                 :            : 
      18                 :            : #include "qgsfeatureiterator.h"
      19                 :            : #include "qgsfeaturesink.h"
      20                 :            : #include "qgsfeaturesource.h"
      21                 :            : #include "qgsfeedback.h"
      22                 :            : #include "qgsgeometrycollection.h"
      23                 :            : #include "qgsgeometryutils.h"
      24                 :            : #include "qgslinestring.h"
      25                 :            : #include "qgspolygon.h"
      26                 :            : #include "qgsspatialindex.h"
      27                 :            : 
      28                 :            : //! record about vertex coordinates and index of anchor to which it is snapped
      29                 :            : struct AnchorPoint
      30                 :            : {
      31                 :            :   //! coordinates of the point
      32                 :            :   double x, y;
      33                 :            : 
      34                 :            :   /**
      35                 :            :    * Anchor information:
      36                 :            :    *  0+ - index of anchor to which this point should be snapped
      37                 :            :    * -1  - initial value (undefined)
      38                 :            :    * -2  - this point is an anchor, i.e. do not snap this point (snap others to this point)
      39                 :            :    */
      40                 :            :   int anchor;
      41                 :            : };
      42                 :            : 
      43                 :            : 
      44                 :            : //! record about anchor being along a segment
      45                 :            : struct AnchorAlongSegment
      46                 :            : {
      47                 :            :   int anchor;    //!< Index of the anchor point
      48                 :            :   double along;  //!< Distance of the anchor point along the segment
      49                 :            : };
      50                 :            : 
      51                 :            : 
      52                 :          0 : static void buildSnapIndex( QgsFeatureIterator &fi, QgsSpatialIndex &index, QVector<AnchorPoint> &pnts, QgsFeedback *feedback, int &count, int totalCount )
      53                 :            : {
      54                 :          0 :   QgsFeature f;
      55                 :          0 :   int pntId = 0;
      56                 :            : 
      57                 :          0 :   while ( fi.nextFeature( f ) )
      58                 :            :   {
      59                 :          0 :     if ( feedback->isCanceled() )
      60                 :          0 :       break;
      61                 :            : 
      62                 :          0 :     QgsGeometry g = f.geometry();
      63                 :            : 
      64                 :          0 :     for ( auto it = g.vertices_begin(); it != g.vertices_end(); ++it )
      65                 :            :     {
      66                 :          0 :       QgsPoint pt = *it;
      67                 :          0 :       QgsRectangle rect( pt.x(), pt.y(), pt.x(), pt.y() );
      68                 :            : 
      69                 :          0 :       QList<QgsFeatureId> ids = index.intersects( rect );
      70                 :          0 :       if ( ids.isEmpty() )
      71                 :            :       {
      72                 :            :         // add to tree and to structure
      73                 :          0 :         index.addFeature( pntId, pt.boundingBox() );
      74                 :            : 
      75                 :            :         AnchorPoint xp;
      76                 :          0 :         xp.x = pt.x();
      77                 :          0 :         xp.y = pt.y();
      78                 :          0 :         xp.anchor = -1;
      79                 :          0 :         pnts.append( xp );
      80                 :          0 :         pntId++;
      81                 :          0 :       }
      82                 :          0 :     }
      83                 :            : 
      84                 :          0 :     ++count;
      85                 :          0 :     feedback->setProgress( 100. * count / totalCount );
      86                 :          0 :   }
      87                 :          0 : }
      88                 :            : 
      89                 :            : 
      90                 :          0 : static void assignAnchors( QgsSpatialIndex &index, QVector<AnchorPoint> &pnts, double thresh )
      91                 :            : {
      92                 :          0 :   double thresh2 = thresh * thresh;
      93                 :          0 :   int nanchors = 0, ntosnap = 0;
      94                 :          0 :   for ( int point = 0; point < pnts.count(); ++point )
      95                 :            :   {
      96                 :          0 :     if ( pnts[point].anchor >= 0 )
      97                 :          0 :       continue;
      98                 :            : 
      99                 :          0 :     pnts[point].anchor = -2; // make it anchor
     100                 :          0 :     nanchors++;
     101                 :            : 
     102                 :            :     // Find points in threshold
     103                 :          0 :     double x = pnts[point].x, y = pnts[point].y;
     104                 :          0 :     QgsRectangle rect( x - thresh, y - thresh, x + thresh, y + thresh );
     105                 :            : 
     106                 :          0 :     const QList<QgsFeatureId> ids = index.intersects( rect );
     107                 :          0 :     for ( QgsFeatureId pointb : ids )
     108                 :            :     {
     109                 :          0 :       if ( pointb == point )
     110                 :          0 :         continue;
     111                 :            : 
     112                 :          0 :       double dx = pnts[pointb].x - pnts[point].x;
     113                 :          0 :       double dy = pnts[pointb].y - pnts[point].y;
     114                 :          0 :       double dist2 = dx * dx + dy * dy;
     115                 :          0 :       if ( dist2 > thresh2 )
     116                 :          0 :         continue;   // outside threshold
     117                 :            : 
     118                 :          0 :       if ( pnts[pointb].anchor == -1 )
     119                 :            :       {
     120                 :            :         // doesn't have an anchor yet
     121                 :          0 :         pnts[pointb].anchor = point;
     122                 :          0 :         ntosnap++;
     123                 :          0 :       }
     124                 :          0 :       else if ( pnts[pointb].anchor >= 0 )
     125                 :            :       {
     126                 :            :         // check distance to previously assigned anchor
     127                 :          0 :         double dx2 = pnts[pnts[pointb].anchor].x - pnts[pointb].x;
     128                 :          0 :         double dy2 = pnts[pnts[pointb].anchor].y - pnts[pointb].y;
     129                 :          0 :         double dist2_a = dx2 * dx2 + dy2 * dy2;
     130                 :          0 :         if ( dist2 < dist2_a )
     131                 :          0 :           pnts[pointb].anchor = point;   // replace old anchor
     132                 :          0 :       }
     133                 :            :     }
     134                 :          0 :   }
     135                 :          0 : }
     136                 :            : 
     137                 :            : 
     138                 :          0 : static bool snapPoint( QgsPoint *pt, QgsSpatialIndex &index, QVector<AnchorPoint> &pnts )
     139                 :            : {
     140                 :            :   // Find point ( should always find one point )
     141                 :          0 :   QList<QgsFeatureId> fids = index.intersects( QgsRectangle( pt->x(), pt->y(), pt->x(), pt->y() ) );
     142                 :            :   Q_ASSERT( fids.count() == 1 );
     143                 :            : 
     144                 :          0 :   int spoint = fids[0];
     145                 :          0 :   int anchor = pnts[spoint].anchor;
     146                 :            : 
     147                 :          0 :   if ( anchor >= 0 )
     148                 :            :   {
     149                 :            :     // to be snapped
     150                 :          0 :     pt->setX( pnts[anchor].x );
     151                 :          0 :     pt->setY( pnts[anchor].y );
     152                 :          0 :     return true;
     153                 :            :   }
     154                 :            : 
     155                 :          0 :   return false;
     156                 :          0 : }
     157                 :            : 
     158                 :            : 
     159                 :          0 : static bool snapLineString( QgsLineString *linestring, QgsSpatialIndex &index, QVector<AnchorPoint> &pnts, double thresh )
     160                 :            : {
     161                 :          0 :   QVector<QgsPoint> newPoints;
     162                 :          0 :   QVector<int> anchors;  // indexes of anchors for vertices
     163                 :          0 :   double thresh2 = thresh * thresh;
     164                 :            :   double minDistX, minDistY;   // coordinates of the closest point on the segment line
     165                 :          0 :   bool changed = false;
     166                 :            : 
     167                 :            :   // snap vertices
     168                 :          0 :   for ( int v = 0; v < linestring->numPoints(); v++ )
     169                 :            :   {
     170                 :          0 :     double x = linestring->xAt( v );
     171                 :          0 :     double y = linestring->yAt( v );
     172                 :          0 :     QgsRectangle rect( x, y, x, y );
     173                 :            : 
     174                 :            :     // Find point ( should always find one point )
     175                 :          0 :     QList<QgsFeatureId> fids = index.intersects( rect );
     176                 :            :     Q_ASSERT( fids.count() == 1 );
     177                 :            : 
     178                 :          0 :     int spoint = fids.first();
     179                 :          0 :     int anchor = pnts[spoint].anchor;
     180                 :          0 :     if ( anchor >= 0 )
     181                 :            :     {
     182                 :            :       // to be snapped
     183                 :          0 :       linestring->setXAt( v, pnts[anchor].x );
     184                 :          0 :       linestring->setYAt( v, pnts[anchor].y );
     185                 :          0 :       anchors.append( anchor ); // point on new location
     186                 :          0 :       changed = true;
     187                 :          0 :     }
     188                 :            :     else
     189                 :            :     {
     190                 :          0 :       anchors.append( spoint ); // old point
     191                 :            :     }
     192                 :          0 :   }
     193                 :            : 
     194                 :            :   // Snap all segments to anchors in threshold
     195                 :          0 :   for ( int v = 0; v < linestring->numPoints() - 1; v++ )
     196                 :            :   {
     197                 :          0 :     double x1 = linestring->xAt( v );
     198                 :          0 :     double x2 = linestring->xAt( v + 1 );
     199                 :          0 :     double y1 = linestring->yAt( v );
     200                 :          0 :     double y2 = linestring->yAt( v + 1 );
     201                 :            : 
     202                 :          0 :     newPoints << linestring->pointN( v );
     203                 :            : 
     204                 :            :     // Box
     205                 :          0 :     double xmin = x1, xmax = x2, ymin = y1, ymax = y2;
     206                 :          0 :     if ( xmin > xmax )
     207                 :          0 :       std::swap( xmin, xmax );
     208                 :          0 :     if ( ymin > ymax )
     209                 :          0 :       std::swap( ymin, ymax );
     210                 :            : 
     211                 :          0 :     QgsRectangle rect( xmin - thresh, ymin - thresh, xmax + thresh, ymax + thresh );
     212                 :            : 
     213                 :            :     // Find points
     214                 :          0 :     const QList<QgsFeatureId> fids = index.intersects( rect );
     215                 :            : 
     216                 :          0 :     QVector<AnchorAlongSegment> newVerticesAlongSegment;
     217                 :            : 
     218                 :            :     // Snap to anchor in threshold different from end points
     219                 :          0 :     for ( QgsFeatureId fid : fids )
     220                 :            :     {
     221                 :          0 :       int spoint = fid;
     222                 :            : 
     223                 :          0 :       if ( spoint == anchors[v] || spoint == anchors[v + 1] )
     224                 :          0 :         continue; // end point
     225                 :          0 :       if ( pnts[spoint].anchor >= 0 )
     226                 :          0 :         continue; // point is not anchor
     227                 :            : 
     228                 :            :       // Check the distance
     229                 :          0 :       double dist2 = QgsGeometryUtils::sqrDistToLine( pnts[spoint].x, pnts[spoint].y, x1, y1, x2, y2, minDistX, minDistY, 0 );
     230                 :            :       // skip points that are behind segment's endpoints or extremely close to them
     231                 :          0 :       double dx1 = minDistX - x1, dx2 = minDistX - x2;
     232                 :          0 :       double dy1 = minDistY - y1, dy2 = minDistY - y2;
     233                 :          0 :       bool isOnSegment = !qgsDoubleNear( dx1 * dx1 + dy1 * dy1, 0 ) && !qgsDoubleNear( dx2 * dx2 + dy2 * dy2, 0 );
     234                 :          0 :       if ( isOnSegment && dist2 <= thresh2 )
     235                 :            :       {
     236                 :            :         // an anchor is in the threshold
     237                 :            :         AnchorAlongSegment item;
     238                 :          0 :         item.anchor = spoint;
     239                 :          0 :         item.along = QgsPointXY( x1, y1 ).distance( minDistX, minDistY );
     240                 :          0 :         newVerticesAlongSegment << item;
     241                 :          0 :       }
     242                 :            :     }
     243                 :            : 
     244                 :          0 :     if ( !newVerticesAlongSegment.isEmpty() )
     245                 :            :     {
     246                 :            :       // sort by distance along the segment
     247                 :          0 :       std::sort( newVerticesAlongSegment.begin(), newVerticesAlongSegment.end(), []( AnchorAlongSegment p1, AnchorAlongSegment p2 )
     248                 :            :       {
     249                 :          0 :         return p1.along < p2.along;
     250                 :            :       } );
     251                 :            : 
     252                 :            :       // insert new vertices
     253                 :          0 :       for ( int i = 0; i < newVerticesAlongSegment.count(); i++ )
     254                 :            :       {
     255                 :          0 :         int anchor = newVerticesAlongSegment[i].anchor;
     256                 :          0 :         newPoints << QgsPoint( pnts[anchor].x, pnts[anchor].y, 0 );
     257                 :          0 :       }
     258                 :          0 :       changed = true;
     259                 :          0 :     }
     260                 :          0 :   }
     261                 :            : 
     262                 :            :   // append end point
     263                 :          0 :   newPoints << linestring->pointN( linestring->numPoints() - 1 );
     264                 :            : 
     265                 :            :   // replace linestring's points
     266                 :          0 :   if ( changed )
     267                 :          0 :     linestring->setPoints( newPoints );
     268                 :            : 
     269                 :          0 :   return changed;
     270                 :          0 : }
     271                 :            : 
     272                 :            : 
     273                 :          0 : static bool snapGeometry( QgsAbstractGeometry *g, QgsSpatialIndex &index, QVector<AnchorPoint> &pnts, double thresh )
     274                 :            : {
     275                 :          0 :   bool changed = false;
     276                 :          0 :   if ( QgsLineString *linestring = qgsgeometry_cast<QgsLineString *>( g ) )
     277                 :            :   {
     278                 :          0 :     changed |= snapLineString( linestring, index, pnts, thresh );
     279                 :          0 :   }
     280                 :          0 :   else if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( g ) )
     281                 :            :   {
     282                 :          0 :     if ( QgsLineString *exteriorRing = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() ) )
     283                 :          0 :       changed |= snapLineString( exteriorRing, index, pnts, thresh );
     284                 :          0 :     for ( int i = 0; i < polygon->numInteriorRings(); ++i )
     285                 :            :     {
     286                 :          0 :       if ( QgsLineString *interiorRing = qgsgeometry_cast<QgsLineString *>( polygon->interiorRing( i ) ) )
     287                 :          0 :         changed |= snapLineString( interiorRing, index, pnts, thresh );
     288                 :          0 :     }
     289                 :          0 :   }
     290                 :          0 :   else if ( QgsGeometryCollection *collection = qgsgeometry_cast<QgsGeometryCollection *>( g ) )
     291                 :            :   {
     292                 :          0 :     for ( int i = 0; i < collection->numGeometries(); ++i )
     293                 :          0 :       changed |= snapGeometry( collection->geometryN( i ), index, pnts, thresh );
     294                 :          0 :   }
     295                 :          0 :   else if ( QgsPoint *pt = qgsgeometry_cast<QgsPoint *>( g ) )
     296                 :            :   {
     297                 :          0 :     changed |= snapPoint( pt, index, pnts );
     298                 :          0 :   }
     299                 :            : 
     300                 :          0 :   return changed;
     301                 :            : }
     302                 :            : 
     303                 :            : 
     304                 :          0 : int QgsGeometrySnapperSingleSource::run( const QgsFeatureSource &source, QgsFeatureSink &sink, double thresh, QgsFeedback *feedback )
     305                 :            : {
     306                 :            :   // the logic here comes from GRASS implementation of Vect_snap_lines_list()
     307                 :            : 
     308                 :          0 :   int count = 0;
     309                 :          0 :   int totalCount = source.featureCount() * 2;
     310                 :            : 
     311                 :            :   // step 1: record all point locations in a spatial index + extra data structure to keep
     312                 :            :   // reference to which other point they have been snapped to (in the next phase).
     313                 :            : 
     314                 :          0 :   QgsSpatialIndex index;
     315                 :          0 :   QVector<AnchorPoint> pnts;
     316                 :          0 :   QgsFeatureRequest request;
     317                 :          0 :   request.setNoAttributes();
     318                 :          0 :   QgsFeatureIterator fi = source.getFeatures( request );
     319                 :          0 :   buildSnapIndex( fi, index, pnts, feedback, count, totalCount );
     320                 :            : 
     321                 :          0 :   if ( feedback->isCanceled() )
     322                 :          0 :     return 0;
     323                 :            : 
     324                 :            :   // step 2: go through all registered points and if not yet marked mark it as anchor and
     325                 :            :   // assign this anchor to all not yet marked points in threshold
     326                 :            : 
     327                 :          0 :   assignAnchors( index, pnts, thresh );
     328                 :            : 
     329                 :            :   // step 3: alignment of vertices and segments to the anchors
     330                 :            :   // Go through all lines and:
     331                 :            :   //   1) for all vertices: if not anchor snap it to its anchor
     332                 :            :   //   2) for all segments: snap it to all anchors in threshold (except anchors of vertices of course)
     333                 :            : 
     334                 :          0 :   int modified = 0;
     335                 :          0 :   QgsFeature f;
     336                 :          0 :   fi = source.getFeatures();
     337                 :          0 :   while ( fi.nextFeature( f ) )
     338                 :            :   {
     339                 :          0 :     if ( feedback->isCanceled() )
     340                 :          0 :       break;
     341                 :            : 
     342                 :          0 :     QgsGeometry geom = f.geometry();
     343                 :          0 :     if ( snapGeometry( geom.get(), index, pnts, thresh ) )
     344                 :            :     {
     345                 :          0 :       f.setGeometry( geom );
     346                 :          0 :       ++modified;
     347                 :          0 :     }
     348                 :            : 
     349                 :          0 :     sink.addFeature( f, QgsFeatureSink::FastInsert );
     350                 :            : 
     351                 :          0 :     ++count;
     352                 :          0 :     feedback->setProgress( 100. * count / totalCount );
     353                 :          0 :   }
     354                 :            : 
     355                 :          0 :   return modified;
     356                 :          0 : }

Generated by: LCOV version 1.14