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