1 /***************************************************************************
2                         qgsgeometryeditutils.cpp
3   -------------------------------------------------------------------
4 Date                 : 21 Jan 2015
5 Copyright            : (C) 2015 by Marco Hugentobler
6 email                : marco.hugentobler at sourcepole 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 "qgsgeometryeditutils.h"
17 #include "qgsfeatureiterator.h"
18 #include "qgscurve.h"
19 #include "qgscurvepolygon.h"
20 #include "qgspolygon.h"
21 #include "qgsgeometryutils.h"
22 #include "qgsgeometry.h"
23 #include "qgsgeos.h"
24 #include "qgsmultisurface.h"
25 #include "qgsproject.h"
26 #include "qgsvectorlayer.h"
27 #include <limits>
28 
addRing(QgsAbstractGeometry * geom,std::unique_ptr<QgsCurve> ring)29 Qgis::GeometryOperationResult QgsGeometryEditUtils::addRing( QgsAbstractGeometry *geom, std::unique_ptr<QgsCurve> ring )
30 {
31   if ( !ring )
32   {
33     return Qgis::GeometryOperationResult::InvalidInputGeometryType;
34   }
35 
36   QVector< QgsCurvePolygon * > polygonList;
37   QgsCurvePolygon *curvePoly = qgsgeometry_cast< QgsCurvePolygon * >( geom );
38   QgsGeometryCollection *multiGeom = qgsgeometry_cast< QgsGeometryCollection * >( geom );
39   if ( curvePoly )
40   {
41     polygonList.append( curvePoly );
42   }
43   else if ( multiGeom )
44   {
45     polygonList.reserve( multiGeom->numGeometries() );
46     for ( int i = 0; i < multiGeom->numGeometries(); ++i )
47     {
48       polygonList.append( qgsgeometry_cast< QgsCurvePolygon * >( multiGeom->geometryN( i ) ) );
49     }
50   }
51   else
52   {
53     return Qgis::GeometryOperationResult::InvalidInputGeometryType; //not polygon / multipolygon;
54   }
55 
56   //ring must be closed
57   if ( !ring->isClosed() )
58   {
59     return Qgis::GeometryOperationResult::AddRingNotClosed;
60   }
61   else if ( !ring->isRing() )
62   {
63     return Qgis::GeometryOperationResult::AddRingNotValid;
64   }
65 
66   std::unique_ptr<QgsGeometryEngine> ringGeom( QgsGeometry::createGeometryEngine( ring.get() ) );
67   ringGeom->prepareGeometry();
68 
69   //for each polygon, test if inside outer ring and no intersection with other interior ring
70   QVector< QgsCurvePolygon * >::const_iterator polyIter = polygonList.constBegin();
71   for ( ; polyIter != polygonList.constEnd(); ++polyIter )
72   {
73     if ( ringGeom->within( *polyIter ) )
74     {
75       //check if disjoint with other interior rings
76       const int nInnerRings = ( *polyIter )->numInteriorRings();
77       for ( int i = 0; i < nInnerRings; ++i )
78       {
79         if ( !ringGeom->disjoint( ( *polyIter )->interiorRing( i ) ) )
80         {
81           return Qgis::GeometryOperationResult::AddRingCrossesExistingRings;
82         }
83       }
84 
85       //make sure dimensionality of ring matches geometry
86       if ( QgsWkbTypes::hasZ( geom->wkbType() ) )
87         ring->addZValue( 0 );
88       if ( QgsWkbTypes::hasM( geom->wkbType() ) )
89         ring->addMValue( 0 );
90 
91       ( *polyIter )->addInteriorRing( ring.release() );
92       return Qgis::GeometryOperationResult::Success; //success
93     }
94   }
95   return Qgis::GeometryOperationResult::AddRingNotInExistingFeature; //not contained in any outer ring
96 }
97 
addPart(QgsAbstractGeometry * geom,std::unique_ptr<QgsAbstractGeometry> part)98 Qgis::GeometryOperationResult QgsGeometryEditUtils::addPart( QgsAbstractGeometry *geom, std::unique_ptr<QgsAbstractGeometry> part )
99 {
100   if ( !geom )
101   {
102     return Qgis::GeometryOperationResult::InvalidBaseGeometry;
103   }
104 
105   if ( !part )
106   {
107     return Qgis::GeometryOperationResult::InvalidInputGeometryType;
108   }
109 
110   //multitype?
111   QgsGeometryCollection *geomCollection = qgsgeometry_cast<QgsGeometryCollection *>( geom );
112   if ( !geomCollection )
113   {
114     return Qgis::GeometryOperationResult::AddPartNotMultiGeometry;
115   }
116 
117   bool added = false;
118   if ( QgsWkbTypes::flatType( geom->wkbType() ) == QgsWkbTypes::MultiSurface
119        || QgsWkbTypes::flatType( geom->wkbType() ) == QgsWkbTypes::MultiPolygon )
120   {
121     QgsCurve *curve = qgsgeometry_cast<QgsCurve *>( part.get() );
122 
123     if ( curve && curve->isClosed() && curve->numPoints() >= 4 )
124     {
125       std::unique_ptr<QgsCurvePolygon> poly;
126       if ( QgsWkbTypes::flatType( curve->wkbType() ) == QgsWkbTypes::LineString )
127       {
128         poly = std::make_unique< QgsPolygon >();
129       }
130       else
131       {
132         poly = std::make_unique< QgsCurvePolygon >();
133       }
134       // Ownership is still with part, curve points to the same object and is transferred
135       // to poly here.
136       part.release();
137       poly->setExteriorRing( curve );
138       added = geomCollection->addGeometry( poly.release() );
139     }
140     else if ( QgsWkbTypes::flatType( part->wkbType() ) == QgsWkbTypes::Polygon
141               || QgsWkbTypes::flatType( part->wkbType() ) == QgsWkbTypes::Triangle
142               || QgsWkbTypes::flatType( part->wkbType() ) == QgsWkbTypes::CurvePolygon )
143     {
144       added = geomCollection->addGeometry( part.release() );
145     }
146     else if ( QgsWkbTypes::flatType( part->wkbType() ) == QgsWkbTypes::MultiPolygon
147               ||  QgsWkbTypes::flatType( part->wkbType() ) == QgsWkbTypes::MultiSurface )
148     {
149       std::unique_ptr<QgsGeometryCollection> parts( static_cast<QgsGeometryCollection *>( part.release() ) );
150 
151       int i;
152       const int n = geomCollection->numGeometries();
153       for ( i = 0; i < parts->numGeometries() && geomCollection->addGeometry( parts->geometryN( i )->clone() ); i++ )
154         ;
155 
156       added = i == parts->numGeometries();
157       if ( !added )
158       {
159         while ( geomCollection->numGeometries() > n )
160           geomCollection->removeGeometry( n );
161         return Qgis::GeometryOperationResult::InvalidInputGeometryType;
162       }
163     }
164     else
165     {
166       return Qgis::GeometryOperationResult::InvalidInputGeometryType;
167     }
168   }
169   else
170   {
171     added = geomCollection->addGeometry( part.release() );
172   }
173   return added ? Qgis::GeometryOperationResult::Success : Qgis::GeometryOperationResult::InvalidInputGeometryType;
174 }
175 
deleteRing(QgsAbstractGeometry * geom,int ringNum,int partNum)176 bool QgsGeometryEditUtils::deleteRing( QgsAbstractGeometry *geom, int ringNum, int partNum )
177 {
178   if ( !geom || partNum < 0 )
179   {
180     return false;
181   }
182 
183   if ( ringNum < 1 ) //cannot remove exterior ring
184   {
185     return false;
186   }
187 
188   QgsAbstractGeometry *g = geom;
189   QgsGeometryCollection *c = qgsgeometry_cast<QgsGeometryCollection *>( geom );
190   if ( c )
191   {
192     g = c->geometryN( partNum );
193   }
194   else if ( partNum > 0 )
195   {
196     //part num specified, but not a multi part geometry type
197     return false;
198   }
199 
200   QgsCurvePolygon *cpoly = qgsgeometry_cast<QgsCurvePolygon *>( g );
201   if ( !cpoly )
202   {
203     return false;
204   }
205 
206   return cpoly->removeInteriorRing( ringNum - 1 );
207 }
208 
deletePart(QgsAbstractGeometry * geom,int partNum)209 bool QgsGeometryEditUtils::deletePart( QgsAbstractGeometry *geom, int partNum )
210 {
211   if ( !geom )
212   {
213     return false;
214   }
215 
216   QgsGeometryCollection *c = qgsgeometry_cast<QgsGeometryCollection *>( geom );
217   if ( !c )
218   {
219     return false;
220   }
221 
222   return c->removeGeometry( partNum );
223 }
224 
avoidIntersections(const QgsAbstractGeometry & geom,const QList<QgsVectorLayer * > & avoidIntersectionsLayers,bool & haveInvalidGeometry,const QHash<QgsVectorLayer *,QSet<QgsFeatureId>> & ignoreFeatures)225 std::unique_ptr<QgsAbstractGeometry> QgsGeometryEditUtils::avoidIntersections( const QgsAbstractGeometry &geom,
226     const QList<QgsVectorLayer *> &avoidIntersectionsLayers,
227     bool &haveInvalidGeometry,
228     const QHash<QgsVectorLayer *, QSet<QgsFeatureId> > &ignoreFeatures
229                                                                              )
230 {
231 
232   haveInvalidGeometry = false;
233   std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometry::createGeometryEngine( &geom ) );
234   if ( !geomEngine )
235   {
236     return nullptr;
237   }
238   const QgsWkbTypes::Type geomTypeBeforeModification = geom.wkbType();
239 
240 
241   //check if g has polygon type
242   if ( QgsWkbTypes::geometryType( geomTypeBeforeModification ) != QgsWkbTypes::PolygonGeometry )
243   {
244     return nullptr;
245   }
246 
247   if ( avoidIntersectionsLayers.isEmpty() )
248     return nullptr; //no intersections stored in project does not mean error
249 
250   QVector< QgsGeometry > nearGeometries;
251 
252   //go through list, convert each layer to vector layer and call QgsVectorLayer::removePolygonIntersections for each
253   for ( QgsVectorLayer *currentLayer : avoidIntersectionsLayers )
254   {
255     QgsFeatureIds ignoreIds;
256     const QHash<QgsVectorLayer *, QSet<qint64> >::const_iterator ignoreIt = ignoreFeatures.constFind( currentLayer );
257     if ( ignoreIt != ignoreFeatures.constEnd() )
258       ignoreIds = ignoreIt.value();
259 
260     QgsFeatureIterator fi = currentLayer->getFeatures( QgsFeatureRequest( geom.boundingBox() )
261                             .setFlags( QgsFeatureRequest::ExactIntersect )
262                             .setNoAttributes() );
263     QgsFeature f;
264     while ( fi.nextFeature( f ) )
265     {
266       if ( ignoreIds.contains( f.id() ) )
267         continue;
268 
269       if ( !f.hasGeometry() )
270         continue;
271 
272       if ( !f.geometry().isGeosValid() )
273         haveInvalidGeometry = true;
274 
275       nearGeometries << f.geometry();
276     }
277   }
278 
279   if ( nearGeometries.isEmpty() )
280   {
281     return nullptr;
282   }
283 
284   const std::unique_ptr< QgsAbstractGeometry > combinedGeometries( geomEngine->combine( nearGeometries ) );
285   if ( !combinedGeometries )
286   {
287     return nullptr;
288   }
289 
290   std::unique_ptr< QgsAbstractGeometry > diffGeom( geomEngine->difference( combinedGeometries.get() ) );
291 
292   return diffGeom;
293 }
294