1 /***************************************************************************
2                         qgsgeometrycollection.cpp
3   -------------------------------------------------------------------
4 Date                 : 28 Oct 2014
5 Copyright            : (C) 2014 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 "qgsgeometrycollection.h"
17 #include "qgsapplication.h"
18 #include "qgsgeometryfactory.h"
19 #include "qgsgeometryutils.h"
20 #include "qgscircularstring.h"
21 #include "qgscompoundcurve.h"
22 #include "qgslinestring.h"
23 #include "qgsmultilinestring.h"
24 #include "qgspoint.h"
25 #include "qgsmultipoint.h"
26 #include "qgspolygon.h"
27 #include "qgsmultipolygon.h"
28 #include "qgswkbptr.h"
29 #include "qgsgeos.h"
30 #include "qgsfeedback.h"
31 
32 #include <nlohmann/json.hpp>
33 #include <memory>
34 
QgsGeometryCollection()35 QgsGeometryCollection::QgsGeometryCollection()
36 {
37   mWkbType = QgsWkbTypes::GeometryCollection;
38 }
39 
QgsGeometryCollection(const QgsGeometryCollection & c)40 QgsGeometryCollection::QgsGeometryCollection( const QgsGeometryCollection &c ):
41   QgsAbstractGeometry( c ),
42   mBoundingBox( c.mBoundingBox ),
43   mHasCachedValidity( c.mHasCachedValidity ),
44   mValidityFailureReason( c.mValidityFailureReason )
45 {
46   int nGeoms = c.mGeometries.size();
47   mGeometries.resize( nGeoms );
48   for ( int i = 0; i < nGeoms; ++i )
49   {
50     mGeometries[i] = c.mGeometries.at( i )->clone();
51   }
52 }
53 
operator =(const QgsGeometryCollection & c)54 QgsGeometryCollection &QgsGeometryCollection::operator=( const QgsGeometryCollection &c )
55 {
56   if ( &c != this )
57   {
58     clearCache();
59     QgsAbstractGeometry::operator=( c );
60     int nGeoms = c.mGeometries.size();
61     mGeometries.resize( nGeoms );
62     for ( int i = 0; i < nGeoms; ++i )
63     {
64       mGeometries[i] = c.mGeometries.at( i )->clone();
65     }
66   }
67   return *this;
68 }
69 
~QgsGeometryCollection()70 QgsGeometryCollection::~QgsGeometryCollection()
71 {
72   clear();
73 }
74 
operator ==(const QgsAbstractGeometry & other) const75 bool QgsGeometryCollection::operator==( const QgsAbstractGeometry &other ) const
76 {
77   const QgsGeometryCollection *otherCollection = qgsgeometry_cast< const QgsGeometryCollection * >( &other );
78   if ( !otherCollection )
79     return false;
80 
81   if ( mWkbType != otherCollection->mWkbType )
82     return false;
83 
84   if ( mGeometries.count() != otherCollection->mGeometries.count() )
85     return false;
86 
87   for ( int i = 0; i < mGeometries.count(); ++i )
88   {
89     QgsAbstractGeometry *g1 = mGeometries.at( i );
90     QgsAbstractGeometry *g2 = otherCollection->mGeometries.at( i );
91 
92     // Quick check if the geometries are exactly the same
93     if ( g1 != g2 )
94     {
95       if ( !g1 || !g2 )
96         return false;
97 
98       // Slower check, compare the contents of the geometries
99       if ( *g1 != *g2 )
100         return false;
101     }
102   }
103 
104   return true;
105 }
106 
operator !=(const QgsAbstractGeometry & other) const107 bool QgsGeometryCollection::operator!=( const QgsAbstractGeometry &other ) const
108 {
109   return !operator==( other );
110 }
111 
createEmptyWithSameType() const112 QgsGeometryCollection *QgsGeometryCollection::createEmptyWithSameType() const
113 {
114   auto result = std::make_unique< QgsGeometryCollection >();
115   result->mWkbType = mWkbType;
116   return result.release();
117 }
118 
clone() const119 QgsGeometryCollection *QgsGeometryCollection::clone() const
120 {
121   return new QgsGeometryCollection( *this );
122 }
123 
clear()124 void QgsGeometryCollection::clear()
125 {
126   qDeleteAll( mGeometries );
127   mGeometries.clear();
128   clearCache(); //set bounding box invalid
129 }
130 
snappedToGrid(double hSpacing,double vSpacing,double dSpacing,double mSpacing) const131 QgsGeometryCollection *QgsGeometryCollection::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const
132 {
133   std::unique_ptr<QgsGeometryCollection> result;
134 
135   for ( auto geom : mGeometries )
136   {
137     std::unique_ptr<QgsAbstractGeometry> gridified { geom->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) };
138     if ( gridified )
139     {
140       if ( !result )
141         result = std::unique_ptr<QgsGeometryCollection> { createEmptyWithSameType() };
142 
143       result->mGeometries.append( gridified.release() );
144     }
145   }
146 
147   return result.release();
148 }
149 
removeDuplicateNodes(double epsilon,bool useZValues)150 bool QgsGeometryCollection::removeDuplicateNodes( double epsilon, bool useZValues )
151 {
152   bool result = false;
153   for ( QgsAbstractGeometry *geom : std::as_const( mGeometries ) )
154   {
155     if ( geom->removeDuplicateNodes( epsilon, useZValues ) ) result = true;
156   }
157   return result;
158 }
159 
boundary() const160 QgsAbstractGeometry *QgsGeometryCollection::boundary() const
161 {
162   return nullptr;
163 }
164 
adjacentVertices(QgsVertexId vertex,QgsVertexId & previousVertex,QgsVertexId & nextVertex) const165 void QgsGeometryCollection::adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex ) const
166 {
167   if ( vertex.part < 0 || vertex.part >= mGeometries.count() )
168   {
169     previousVertex = QgsVertexId();
170     nextVertex = QgsVertexId();
171     return;
172   }
173 
174   mGeometries.at( vertex.part )->adjacentVertices( vertex, previousVertex, nextVertex );
175 }
176 
vertexNumberFromVertexId(QgsVertexId id) const177 int QgsGeometryCollection::vertexNumberFromVertexId( QgsVertexId id ) const
178 {
179   if ( id.part < 0 || id.part >= mGeometries.count() )
180     return -1;
181 
182   int number = 0;
183   int part = 0;
184   for ( QgsAbstractGeometry *geometry : mGeometries )
185   {
186     if ( part == id.part )
187     {
188       int partNumber =  geometry->vertexNumberFromVertexId( QgsVertexId( 0, id.ring, id.vertex ) );
189       if ( partNumber == -1 )
190         return -1;
191       return number + partNumber;
192     }
193     else
194     {
195       number += geometry->nCoordinates();
196     }
197 
198     part++;
199   }
200   return -1; // should not happen
201 }
202 
boundingBoxIntersects(const QgsRectangle & rectangle) const203 bool QgsGeometryCollection::boundingBoxIntersects( const QgsRectangle &rectangle ) const
204 {
205   if ( mGeometries.empty() )
206     return false;
207 
208   // if we already have the bounding box calculated, then this check is trivial!
209   if ( !mBoundingBox.isNull() )
210   {
211     return mBoundingBox.intersects( rectangle );
212   }
213 
214   // otherwise loop through each member geometry and test the bounding box intersection.
215   // This gives us a chance to use optimisations which may be present on the individual
216   // geometry subclasses, and at worst it will cause a calculation of the bounding box
217   // of each individual member geometry which we would have to do anyway... (and these
218   // bounding boxes are cached, so would be reused without additional expense)
219   for ( const QgsAbstractGeometry *geometry : mGeometries )
220   {
221     if ( geometry->boundingBoxIntersects( rectangle ) )
222       return true;
223   }
224 
225   // even if we don't intersect the bounding box of any member geometries, we may still intersect the
226   // bounding box of the overall collection.
227   // so here we fall back to the non-optimised base class check which has to first calculate
228   // the overall bounding box of the collection..
229   return QgsAbstractGeometry::boundingBoxIntersects( rectangle );
230 }
231 
reserve(int size)232 void QgsGeometryCollection::reserve( int size )
233 {
234   mGeometries.reserve( size );
235 }
236 
geometryN(int n)237 QgsAbstractGeometry *QgsGeometryCollection::geometryN( int n )
238 {
239   clearCache();
240   return mGeometries.value( n );
241 }
242 
isEmpty() const243 bool QgsGeometryCollection::isEmpty() const
244 {
245   if ( mGeometries.isEmpty() )
246     return true;
247 
248   for ( QgsAbstractGeometry *geometry : mGeometries )
249   {
250     if ( !geometry->isEmpty() )
251       return false;
252   }
253   return true;
254 }
255 
addGeometry(QgsAbstractGeometry * g)256 bool QgsGeometryCollection::addGeometry( QgsAbstractGeometry *g )
257 {
258   if ( !g )
259   {
260     return false;
261   }
262 
263   mGeometries.append( g );
264   clearCache(); //set bounding box invalid
265   return true;
266 }
267 
insertGeometry(QgsAbstractGeometry * g,int index)268 bool QgsGeometryCollection::insertGeometry( QgsAbstractGeometry *g, int index )
269 {
270   if ( !g )
271   {
272     return false;
273   }
274 
275   index = std::min( static_cast<int>( mGeometries.count() ), index );
276 
277   mGeometries.insert( index, g );
278   clearCache(); //set bounding box invalid
279   return true;
280 }
281 
removeGeometry(int nr)282 bool QgsGeometryCollection::removeGeometry( int nr )
283 {
284   if ( nr >= mGeometries.size() || nr < 0 )
285   {
286     return false;
287   }
288   delete mGeometries.at( nr );
289   mGeometries.remove( nr );
290   clearCache(); //set bounding box invalid
291   return true;
292 }
293 
normalize()294 void QgsGeometryCollection::normalize()
295 {
296   for ( QgsAbstractGeometry *geometry : std::as_const( mGeometries ) )
297   {
298     geometry->normalize();
299   }
300   std::sort( mGeometries.begin(), mGeometries.end(), []( const QgsAbstractGeometry * a, const QgsAbstractGeometry * b )
301   {
302     return a->compareTo( b ) > 0;
303   } );
304 }
305 
dimension() const306 int QgsGeometryCollection::dimension() const
307 {
308   int maxDim = 0;
309   QVector< QgsAbstractGeometry * >::const_iterator it = mGeometries.constBegin();
310   for ( ; it != mGeometries.constEnd(); ++it )
311   {
312     int dim = ( *it )->dimension();
313     if ( dim > maxDim )
314     {
315       maxDim = dim;
316     }
317   }
318   return maxDim;
319 }
320 
geometryType() const321 QString QgsGeometryCollection::geometryType() const
322 {
323   return QStringLiteral( "GeometryCollection" );
324 }
325 
transform(const QgsCoordinateTransform & ct,Qgis::TransformDirection d,bool transformZ)326 void QgsGeometryCollection::transform( const QgsCoordinateTransform &ct, Qgis::TransformDirection d, bool transformZ )
327 {
328   for ( QgsAbstractGeometry *g : std::as_const( mGeometries ) )
329   {
330     g->transform( ct, d, transformZ );
331   }
332   clearCache(); //set bounding box invalid
333 }
334 
transform(const QTransform & t,double zTranslate,double zScale,double mTranslate,double mScale)335 void QgsGeometryCollection::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
336 {
337   for ( QgsAbstractGeometry *g : std::as_const( mGeometries ) )
338   {
339     g->transform( t, zTranslate, zScale, mTranslate, mScale );
340   }
341   clearCache(); //set bounding box invalid
342 }
343 
draw(QPainter & p) const344 void QgsGeometryCollection::draw( QPainter &p ) const
345 {
346   QVector< QgsAbstractGeometry * >::const_iterator it = mGeometries.constBegin();
347   for ( ; it != mGeometries.constEnd(); ++it )
348   {
349     ( *it )->draw( p );
350   }
351 }
352 
asQPainterPath() const353 QPainterPath QgsGeometryCollection::asQPainterPath() const
354 {
355   QPainterPath p;
356   for ( const QgsAbstractGeometry *geom : mGeometries )
357   {
358     QPainterPath partPath = geom->asQPainterPath();
359     if ( !partPath.isEmpty() )
360       p.addPath( partPath );
361   }
362   return p;
363 }
364 
fromWkb(QgsConstWkbPtr & wkbPtr)365 bool QgsGeometryCollection::fromWkb( QgsConstWkbPtr &wkbPtr )
366 {
367   if ( !wkbPtr )
368   {
369     return false;
370   }
371 
372   QgsWkbTypes::Type wkbType = wkbPtr.readHeader();
373   if ( QgsWkbTypes::flatType( wkbType ) != QgsWkbTypes::flatType( mWkbType ) )
374     return false;
375 
376   mWkbType = wkbType;
377 
378   int nGeometries = 0;
379   wkbPtr >> nGeometries;
380 
381   QVector<QgsAbstractGeometry *> geometryListBackup = mGeometries;
382   mGeometries.clear();
383   mGeometries.reserve( nGeometries );
384   for ( int i = 0; i < nGeometries; ++i )
385   {
386     std::unique_ptr< QgsAbstractGeometry > geom( QgsGeometryFactory::geomFromWkb( wkbPtr ) );  // also updates wkbPtr
387     if ( geom )
388     {
389       if ( !addGeometry( geom.release() ) )
390       {
391         qDeleteAll( mGeometries );
392         mGeometries = geometryListBackup;
393         return false;
394       }
395     }
396   }
397   qDeleteAll( geometryListBackup );
398 
399   clearCache(); //set bounding box invalid
400 
401   return true;
402 }
403 
fromWkt(const QString & wkt)404 bool QgsGeometryCollection::fromWkt( const QString &wkt )
405 {
406   return fromCollectionWkt( wkt, QVector<QgsAbstractGeometry *>() << new QgsPoint << new QgsLineString << new QgsPolygon
407                             << new QgsCircularString << new QgsCompoundCurve
408                             << new QgsCurvePolygon
409                             << new QgsMultiPoint << new QgsMultiLineString
410                             << new QgsMultiPolygon << new QgsGeometryCollection
411                             << new QgsMultiCurve << new QgsMultiSurface, QStringLiteral( "GeometryCollection" ) );
412 }
413 
wkbSize(QgsAbstractGeometry::WkbFlags flags) const414 int QgsGeometryCollection::wkbSize( QgsAbstractGeometry::WkbFlags flags ) const
415 {
416   int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
417   for ( const QgsAbstractGeometry *geom : mGeometries )
418   {
419     if ( geom )
420     {
421       binarySize += geom->wkbSize( flags );
422     }
423   }
424 
425   return binarySize;
426 }
427 
asWkb(WkbFlags flags) const428 QByteArray QgsGeometryCollection::asWkb( WkbFlags flags ) const
429 {
430   int countNonNull = 0;
431   for ( const QgsAbstractGeometry *geom : mGeometries )
432   {
433     if ( geom )
434     {
435       countNonNull ++;
436     }
437   }
438 
439   QByteArray wkbArray;
440   wkbArray.resize( QgsGeometryCollection::wkbSize( flags ) );
441   QgsWkbPtr wkb( wkbArray );
442   wkb << static_cast<char>( QgsApplication::endian() );
443   wkb << static_cast<quint32>( wkbType() );
444   wkb << static_cast<quint32>( countNonNull );
445   for ( const QgsAbstractGeometry *geom : mGeometries )
446   {
447     if ( geom )
448     {
449       wkb << geom->asWkb( flags );
450     }
451   }
452   return wkbArray;
453 }
454 
asWkt(int precision) const455 QString QgsGeometryCollection::asWkt( int precision ) const
456 {
457   QString wkt = wktTypeStr();
458 
459   if ( isEmpty() )
460     wkt += QLatin1String( " EMPTY" );
461   else
462   {
463     wkt += QLatin1String( " (" );
464     for ( const QgsAbstractGeometry *geom : mGeometries )
465     {
466       QString childWkt = geom->asWkt( precision );
467       if ( wktOmitChildType() )
468       {
469         childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
470       }
471       wkt += childWkt + ',';
472     }
473     if ( wkt.endsWith( ',' ) )
474     {
475       wkt.chop( 1 ); // Remove last ','
476     }
477     wkt += ')';
478   }
479   return wkt;
480 }
481 
asGml2(QDomDocument & doc,int precision,const QString & ns,const QgsAbstractGeometry::AxisOrder axisOrder) const482 QDomElement QgsGeometryCollection::asGml2( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
483 {
484   QDomElement elemMultiGeometry = doc.createElementNS( ns, QStringLiteral( "MultiGeometry" ) );
485   for ( const QgsAbstractGeometry *geom : mGeometries )
486   {
487     QDomElement elemGeometryMember = doc.createElementNS( ns, QStringLiteral( "geometryMember" ) );
488     elemGeometryMember.appendChild( geom->asGml2( doc, precision, ns, axisOrder ) );
489     elemMultiGeometry.appendChild( elemGeometryMember );
490   }
491   return elemMultiGeometry;
492 }
493 
asGml3(QDomDocument & doc,int precision,const QString & ns,const QgsAbstractGeometry::AxisOrder axisOrder) const494 QDomElement QgsGeometryCollection::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
495 {
496   QDomElement elemMultiGeometry = doc.createElementNS( ns, QStringLiteral( "MultiGeometry" ) );
497   for ( const QgsAbstractGeometry *geom : mGeometries )
498   {
499     QDomElement elemGeometryMember = doc.createElementNS( ns, QStringLiteral( "geometryMember" ) );
500     elemGeometryMember.appendChild( geom->asGml3( doc, precision, ns, axisOrder ) );
501     elemMultiGeometry.appendChild( elemGeometryMember );
502   }
503   return elemMultiGeometry;
504 }
505 
asJsonObject(int precision) const506 json QgsGeometryCollection::asJsonObject( int precision ) const
507 {
508   json coordinates( json::array( ) );
509   for ( const QgsAbstractGeometry *geom : std::as_const( mGeometries ) )
510   {
511     coordinates.push_back( geom->asJsonObject( precision ) );
512   }
513   return
514   {
515     { "type",  "GeometryCollection" },
516     { "geometries", coordinates }
517   };
518 }
519 
asKml(int precision) const520 QString QgsGeometryCollection::asKml( int precision ) const
521 {
522   QString kml;
523   kml.append( QLatin1String( "<MultiGeometry>" ) );
524   const QVector< QgsAbstractGeometry * > &geometries = mGeometries;
525   for ( const QgsAbstractGeometry *geometry : geometries )
526   {
527     kml.append( geometry->asKml( precision ) );
528   }
529   kml.append( QLatin1String( "</MultiGeometry>" ) );
530   return kml;
531 }
532 
boundingBox() const533 QgsRectangle QgsGeometryCollection::boundingBox() const
534 {
535   if ( mBoundingBox.isNull() )
536   {
537     mBoundingBox = calculateBoundingBox();
538   }
539   return mBoundingBox;
540 }
541 
calculateBoundingBox() const542 QgsRectangle QgsGeometryCollection::calculateBoundingBox() const
543 {
544   if ( mGeometries.empty() )
545   {
546     return QgsRectangle();
547   }
548 
549   QgsRectangle bbox = mGeometries.at( 0 )->boundingBox();
550   for ( int i = 1; i < mGeometries.size(); ++i )
551   {
552     if ( mGeometries.at( i )->isEmpty() )
553       continue;
554 
555     QgsRectangle geomBox = mGeometries.at( i )->boundingBox();
556     if ( bbox.isNull() )
557     {
558       // workaround treatment of a QgsRectangle(0,0,0,0) as a "null"/invalid rectangle
559       // if bbox is null, then the first geometry must have returned a bounding box of (0,0,0,0)
560       // so just manually include that as a point... ew.
561       geomBox.combineExtentWith( QPointF( 0, 0 ) );
562       bbox = geomBox;
563     }
564     else if ( geomBox.isNull() )
565     {
566       // ...as above... this part must have a bounding box of (0,0,0,0).
567       // if we try to combine the extent with this "null" box it will just be ignored.
568       bbox.combineExtentWith( QPointF( 0, 0 ) );
569     }
570     else
571     {
572       bbox.combineExtentWith( geomBox );
573     }
574   }
575   return bbox;
576 }
577 
clearCache() const578 void QgsGeometryCollection::clearCache() const
579 {
580   mBoundingBox = QgsRectangle();
581   mHasCachedValidity = false;
582   mValidityFailureReason.clear();
583   QgsAbstractGeometry::clearCache();
584 }
585 
coordinateSequence() const586 QgsCoordinateSequence QgsGeometryCollection::coordinateSequence() const
587 {
588   QgsCoordinateSequence sequence;
589   QVector< QgsAbstractGeometry * >::const_iterator geomIt = mGeometries.constBegin();
590   for ( ; geomIt != mGeometries.constEnd(); ++geomIt )
591   {
592     QgsCoordinateSequence geomCoords = ( *geomIt )->coordinateSequence();
593 
594     QgsCoordinateSequence::const_iterator cIt = geomCoords.constBegin();
595     for ( ; cIt != geomCoords.constEnd(); ++cIt )
596     {
597       sequence.push_back( *cIt );
598     }
599   }
600 
601   return sequence;
602 }
603 
nCoordinates() const604 int QgsGeometryCollection::nCoordinates() const
605 {
606   int count = 0;
607 
608   QVector< QgsAbstractGeometry * >::const_iterator geomIt = mGeometries.constBegin();
609   for ( ; geomIt != mGeometries.constEnd(); ++geomIt )
610   {
611     count += ( *geomIt )->nCoordinates();
612   }
613 
614   return count;
615 }
616 
closestSegment(const QgsPoint & pt,QgsPoint & segmentPt,QgsVertexId & vertexAfter,int * leftOf,double epsilon) const617 double QgsGeometryCollection::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt,  QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
618 {
619   return QgsGeometryUtils::closestSegmentFromComponents( mGeometries, QgsGeometryUtils::Part, pt, segmentPt, vertexAfter, leftOf, epsilon );
620 }
621 
nextVertex(QgsVertexId & id,QgsPoint & vertex) const622 bool QgsGeometryCollection::nextVertex( QgsVertexId &id, QgsPoint &vertex ) const
623 {
624   if ( id.part < 0 )
625   {
626     id.part = 0;
627     id.ring = -1;
628     id.vertex = -1;
629   }
630   if ( mGeometries.isEmpty() )
631   {
632     return false;
633   }
634 
635   if ( id.part >= mGeometries.count() )
636     return false;
637 
638   QgsAbstractGeometry *geom = mGeometries.at( id.part );
639   if ( geom->nextVertex( id, vertex ) )
640   {
641     return true;
642   }
643   if ( ( id.part + 1 ) >= numGeometries() )
644   {
645     return false;
646   }
647   ++id.part;
648   id.ring = -1;
649   id.vertex = -1;
650   return mGeometries.at( id.part )->nextVertex( id, vertex );
651 }
652 
insertVertex(QgsVertexId position,const QgsPoint & vertex)653 bool QgsGeometryCollection::insertVertex( QgsVertexId position, const QgsPoint &vertex )
654 {
655   if ( position.part >= mGeometries.size() )
656   {
657     return false;
658   }
659 
660   bool success = mGeometries.at( position.part )->insertVertex( position, vertex );
661   if ( success )
662   {
663     clearCache(); //set bounding box invalid
664   }
665   return success;
666 }
667 
moveVertex(QgsVertexId position,const QgsPoint & newPos)668 bool QgsGeometryCollection::moveVertex( QgsVertexId position, const QgsPoint &newPos )
669 {
670   if ( position.part < 0 || position.part >= mGeometries.size() )
671   {
672     return false;
673   }
674 
675   bool success = mGeometries.at( position.part )->moveVertex( position, newPos );
676   if ( success )
677   {
678     clearCache(); //set bounding box invalid
679   }
680   return success;
681 }
682 
deleteVertex(QgsVertexId position)683 bool QgsGeometryCollection::deleteVertex( QgsVertexId position )
684 {
685   if ( position.part < 0 || position.part >= mGeometries.size() )
686   {
687     return false;
688   }
689 
690   QgsAbstractGeometry *geom = mGeometries.at( position.part );
691   if ( !geom )
692   {
693     return false;
694   }
695 
696   bool success = geom->deleteVertex( position );
697 
698   //remove geometry if no vertices left
699   if ( geom->isEmpty() )
700   {
701     removeGeometry( position.part );
702   }
703 
704   if ( success )
705   {
706     clearCache(); //set bounding box invalid
707   }
708   return success;
709 }
710 
length() const711 double QgsGeometryCollection::length() const
712 {
713   double length = 0.0;
714   QVector< QgsAbstractGeometry * >::const_iterator geomIt = mGeometries.constBegin();
715   for ( ; geomIt != mGeometries.constEnd(); ++geomIt )
716   {
717     length += ( *geomIt )->length();
718   }
719   return length;
720 }
721 
area() const722 double QgsGeometryCollection::area() const
723 {
724   double area = 0.0;
725   QVector< QgsAbstractGeometry * >::const_iterator geomIt = mGeometries.constBegin();
726   for ( ; geomIt != mGeometries.constEnd(); ++geomIt )
727   {
728     area += ( *geomIt )->area();
729   }
730   return area;
731 }
732 
perimeter() const733 double QgsGeometryCollection::perimeter() const
734 {
735   double perimeter = 0.0;
736   QVector< QgsAbstractGeometry * >::const_iterator geomIt = mGeometries.constBegin();
737   for ( ; geomIt != mGeometries.constEnd(); ++geomIt )
738   {
739     perimeter += ( *geomIt )->perimeter();
740   }
741   return perimeter;
742 }
743 
fromCollectionWkt(const QString & wkt,const QVector<QgsAbstractGeometry * > & subtypes,const QString & defaultChildWkbType)744 bool QgsGeometryCollection::fromCollectionWkt( const QString &wkt, const QVector<QgsAbstractGeometry *> &subtypes, const QString &defaultChildWkbType )
745 {
746   clear();
747 
748   QPair<QgsWkbTypes::Type, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
749 
750   if ( QgsWkbTypes::flatType( parts.first ) != QgsWkbTypes::flatType( wkbType() ) )
751   {
752     qDeleteAll( subtypes );
753     return false;
754   }
755   mWkbType = parts.first;
756 
757   QString secondWithoutParentheses = parts.second;
758   secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
759   if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
760        secondWithoutParentheses.isEmpty() )
761     return true;
762 
763   QString defChildWkbType = QStringLiteral( "%1%2%3 " ).arg( defaultChildWkbType, is3D() ? QStringLiteral( "Z" ) : QString(), isMeasure() ? QStringLiteral( "M" ) : QString() );
764 
765   const QStringList blocks = QgsGeometryUtils::wktGetChildBlocks( parts.second, defChildWkbType );
766   for ( const QString &childWkt : blocks )
767   {
768     QPair<QgsWkbTypes::Type, QString> childParts = QgsGeometryUtils::wktReadBlock( childWkt );
769 
770     bool success = false;
771     for ( const QgsAbstractGeometry *geom : subtypes )
772     {
773       if ( QgsWkbTypes::flatType( childParts.first ) == QgsWkbTypes::flatType( geom->wkbType() ) )
774       {
775         mGeometries.append( geom->clone() );
776         if ( mGeometries.back()->fromWkt( childWkt ) )
777         {
778           success = true;
779           break;
780         }
781       }
782     }
783     if ( !success )
784     {
785       clear();
786       qDeleteAll( subtypes );
787       return false;
788     }
789   }
790   qDeleteAll( subtypes );
791 
792   //scan through geometries and check if dimensionality of geometries is different to collection.
793   //if so, update the type dimensionality of the collection to match
794   bool hasZ = false;
795   bool hasM = false;
796   for ( QgsAbstractGeometry *geom : std::as_const( mGeometries ) )
797   {
798     hasZ = hasZ || geom->is3D();
799     hasM = hasM || geom->isMeasure();
800     if ( hasZ && hasM )
801       break;
802   }
803   if ( hasZ )
804     addZValue( 0 );
805   if ( hasM )
806     addMValue( 0 );
807 
808   return true;
809 }
810 
hasCurvedSegments() const811 bool QgsGeometryCollection::hasCurvedSegments() const
812 {
813   QVector< QgsAbstractGeometry * >::const_iterator it = mGeometries.constBegin();
814   for ( ; it != mGeometries.constEnd(); ++it )
815   {
816     if ( ( *it )->hasCurvedSegments() )
817     {
818       return true;
819     }
820   }
821   return false;
822 }
823 
segmentize(double tolerance,SegmentationToleranceType toleranceType) const824 QgsAbstractGeometry *QgsGeometryCollection::segmentize( double tolerance, SegmentationToleranceType toleranceType ) const
825 {
826   std::unique_ptr< QgsAbstractGeometry > geom( QgsGeometryFactory::geomFromWkbType( QgsWkbTypes::linearType( mWkbType ) ) );
827   QgsGeometryCollection *geomCollection = qgsgeometry_cast<QgsGeometryCollection *>( geom.get() );
828   if ( !geomCollection )
829   {
830     return clone();
831   }
832 
833   geomCollection->reserve( mGeometries.size() );
834   QVector< QgsAbstractGeometry * >::const_iterator geomIt = mGeometries.constBegin();
835   for ( ; geomIt != mGeometries.constEnd(); ++geomIt )
836   {
837     geomCollection->addGeometry( ( *geomIt )->segmentize( tolerance, toleranceType ) );
838   }
839   return geom.release();
840 }
841 
vertexAngle(QgsVertexId vertex) const842 double QgsGeometryCollection::vertexAngle( QgsVertexId vertex ) const
843 {
844   if ( vertex.part < 0 || vertex.part >= mGeometries.size() )
845   {
846     return 0.0;
847   }
848 
849   QgsAbstractGeometry *geom = mGeometries[vertex.part];
850   if ( !geom )
851   {
852     return 0.0;
853   }
854 
855   return geom->vertexAngle( vertex );
856 }
857 
segmentLength(QgsVertexId startVertex) const858 double QgsGeometryCollection::segmentLength( QgsVertexId startVertex ) const
859 {
860   if ( startVertex.part < 0 || startVertex.part >= mGeometries.size() )
861   {
862     return 0.0;
863   }
864 
865   const QgsAbstractGeometry *geom = mGeometries[startVertex.part];
866   if ( !geom )
867   {
868     return 0.0;
869   }
870 
871   return geom->segmentLength( startVertex );
872 }
873 
vertexCount(int part,int ring) const874 int QgsGeometryCollection::vertexCount( int part, int ring ) const
875 {
876   if ( part < 0 || part >= mGeometries.size() )
877   {
878     return 0;
879   }
880 
881   return mGeometries[part]->vertexCount( 0, ring );
882 }
883 
ringCount(int part) const884 int QgsGeometryCollection::ringCount( int part ) const
885 {
886   if ( part < 0 || part >= mGeometries.size() )
887   {
888     return 0;
889   }
890 
891   return mGeometries[part]->ringCount();
892 }
893 
partCount() const894 int QgsGeometryCollection::partCount() const
895 {
896   return mGeometries.size();
897 }
898 
vertexAt(QgsVertexId id) const899 QgsPoint QgsGeometryCollection::vertexAt( QgsVertexId id ) const
900 {
901   return mGeometries[id.part]->vertexAt( id );
902 }
903 
isValid(QString & error,Qgis::GeometryValidityFlags flags) const904 bool QgsGeometryCollection::isValid( QString &error, Qgis::GeometryValidityFlags flags ) const
905 {
906   if ( flags == 0 && mHasCachedValidity )
907   {
908     // use cached validity results
909     error = mValidityFailureReason;
910     return error.isEmpty();
911   }
912 
913   QgsGeos geos( this );
914   bool res = geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, nullptr );
915   if ( flags == 0 )
916   {
917     mValidityFailureReason = !res ? error : QString();
918     mHasCachedValidity = true;
919   }
920   return res;
921 }
922 
addZValue(double zValue)923 bool QgsGeometryCollection::addZValue( double zValue )
924 {
925   if ( QgsWkbTypes::hasZ( mWkbType ) )
926     return false;
927 
928   mWkbType = QgsWkbTypes::addZ( mWkbType );
929 
930   for ( QgsAbstractGeometry *geom : std::as_const( mGeometries ) )
931   {
932     geom->addZValue( zValue );
933   }
934   clearCache();
935   return true;
936 }
937 
addMValue(double mValue)938 bool QgsGeometryCollection::addMValue( double mValue )
939 {
940   if ( QgsWkbTypes::hasM( mWkbType ) )
941     return false;
942 
943   mWkbType = QgsWkbTypes::addM( mWkbType );
944 
945   for ( QgsAbstractGeometry *geom : std::as_const( mGeometries ) )
946   {
947     geom->addMValue( mValue );
948   }
949   clearCache();
950   return true;
951 }
952 
953 
dropZValue()954 bool QgsGeometryCollection::dropZValue()
955 {
956   if ( mWkbType != QgsWkbTypes::GeometryCollection && !is3D() )
957     return false;
958 
959   mWkbType = QgsWkbTypes::dropZ( mWkbType );
960   for ( QgsAbstractGeometry *geom : std::as_const( mGeometries ) )
961   {
962     geom->dropZValue();
963   }
964   clearCache();
965   return true;
966 }
967 
dropMValue()968 bool QgsGeometryCollection::dropMValue()
969 {
970   if ( mWkbType != QgsWkbTypes::GeometryCollection && !isMeasure() )
971     return false;
972 
973   mWkbType = QgsWkbTypes::dropM( mWkbType );
974   for ( QgsAbstractGeometry *geom : std::as_const( mGeometries ) )
975   {
976     geom->dropMValue();
977   }
978   clearCache();
979   return true;
980 }
981 
filterVertices(const std::function<bool (const QgsPoint &)> & filter)982 void QgsGeometryCollection::filterVertices( const std::function<bool ( const QgsPoint & )> &filter )
983 {
984   for ( QgsAbstractGeometry *geom : std::as_const( mGeometries ) )
985   {
986     if ( geom )
987       geom->filterVertices( filter );
988   }
989   clearCache();
990 }
991 
transformVertices(const std::function<QgsPoint (const QgsPoint &)> & transform)992 void QgsGeometryCollection::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
993 {
994   for ( QgsAbstractGeometry *geom : std::as_const( mGeometries ) )
995   {
996     if ( geom )
997       geom->transformVertices( transform );
998   }
999   clearCache();
1000 }
1001 
swapXy()1002 void QgsGeometryCollection::swapXy()
1003 {
1004   for ( QgsAbstractGeometry *geom : std::as_const( mGeometries ) )
1005   {
1006     if ( geom )
1007       geom->swapXy();
1008   }
1009   clearCache();
1010 }
1011 
toCurveType() const1012 QgsGeometryCollection *QgsGeometryCollection::toCurveType() const
1013 {
1014   std::unique_ptr< QgsGeometryCollection > newCollection( new QgsGeometryCollection() );
1015   newCollection->reserve( mGeometries.size() );
1016   for ( QgsAbstractGeometry *geom : mGeometries )
1017   {
1018     newCollection->addGeometry( geom->toCurveType() );
1019   }
1020   return newCollection.release();
1021 }
1022 
simplifiedTypeRef() const1023 const QgsAbstractGeometry *QgsGeometryCollection::simplifiedTypeRef() const
1024 {
1025   if ( mGeometries.size() == 1 )
1026     return mGeometries.at( 0 )->simplifiedTypeRef();
1027   else
1028     return this;
1029 }
1030 
transform(QgsAbstractGeometryTransformer * transformer,QgsFeedback * feedback)1031 bool QgsGeometryCollection::transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback )
1032 {
1033   if ( !transformer )
1034     return false;
1035 
1036   bool res = true;
1037   for ( QgsAbstractGeometry *geom : std::as_const( mGeometries ) )
1038   {
1039     if ( geom )
1040       res = geom->transform( transformer, feedback );
1041 
1042     if ( feedback && feedback->isCanceled() )
1043       res = false;
1044 
1045     if ( !res )
1046       break;
1047   }
1048   clearCache();
1049   return res;
1050 }
1051 
wktOmitChildType() const1052 bool QgsGeometryCollection::wktOmitChildType() const
1053 {
1054   return false;
1055 }
1056 
childCount() const1057 int QgsGeometryCollection::childCount() const
1058 {
1059   return mGeometries.count();
1060 }
1061 
childGeometry(int index) const1062 QgsAbstractGeometry *QgsGeometryCollection::childGeometry( int index ) const
1063 {
1064   if ( index < 0 || index > mGeometries.count() )
1065     return nullptr;
1066   return mGeometries.at( index );
1067 }
1068 
compareToSameClass(const QgsAbstractGeometry * other) const1069 int QgsGeometryCollection::compareToSameClass( const QgsAbstractGeometry *other ) const
1070 {
1071   const QgsGeometryCollection *otherCollection = qgsgeometry_cast<const QgsGeometryCollection *>( other );
1072   if ( !otherCollection )
1073     return -1;
1074 
1075   int i = 0;
1076   int j = 0;
1077   while ( i < mGeometries.size() && j < otherCollection->mGeometries.size() )
1078   {
1079     const QgsAbstractGeometry *aGeom = mGeometries[i];
1080     const QgsAbstractGeometry *bGeom = otherCollection->mGeometries[j];
1081     const int comparison = aGeom->compareTo( bGeom );
1082     if ( comparison != 0 )
1083     {
1084       return comparison;
1085     }
1086     i++;
1087     j++;
1088   }
1089   if ( i < mGeometries.size() )
1090   {
1091     return 1;
1092   }
1093   if ( j < otherCollection->mGeometries.size() )
1094   {
1095     return -1;
1096   }
1097   return 0;
1098 }
1099