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