1 /***************************************************************************
2                          qgscurvepolygon.cpp
3                          ---------------------
4     begin                : September 2014
5     copyright            : (C) 2014 by Marco Hugentobler
6     email                : marco at sourcepole dot ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgscurvepolygon.h"
19 #include "qgsapplication.h"
20 #include "qgscircularstring.h"
21 #include "qgscompoundcurve.h"
22 #include "qgsgeometryutils.h"
23 #include "qgslinestring.h"
24 #include "qgspolygon.h"
25 #include "qgswkbptr.h"
26 #include "qgsmulticurve.h"
27 #include "qgsfeedback.h"
28 
29 #include <QJsonArray>
30 #include <QJsonObject>
31 #include <QPainter>
32 #include <QPainterPath>
33 #include <memory>
34 #include <nlohmann/json.hpp>
35 
QgsCurvePolygon()36 QgsCurvePolygon::QgsCurvePolygon()
37 {
38   mWkbType = QgsWkbTypes::CurvePolygon;
39 }
40 
~QgsCurvePolygon()41 QgsCurvePolygon::~QgsCurvePolygon()
42 {
43   clear();
44 }
45 
createEmptyWithSameType() const46 QgsCurvePolygon *QgsCurvePolygon::createEmptyWithSameType() const
47 {
48   auto result = std::make_unique< QgsCurvePolygon >();
49   result->mWkbType = mWkbType;
50   return result.release();
51 }
52 
geometryType() const53 QString QgsCurvePolygon::geometryType() const
54 {
55   return QStringLiteral( "CurvePolygon" );
56 }
57 
dimension() const58 int QgsCurvePolygon::dimension() const
59 {
60   return 2;
61 }
62 
QgsCurvePolygon(const QgsCurvePolygon & p)63 QgsCurvePolygon::QgsCurvePolygon( const QgsCurvePolygon &p )
64   : QgsSurface( p )
65 
66 {
67   mWkbType = p.mWkbType;
68   if ( p.mExteriorRing )
69   {
70     mExteriorRing.reset( p.mExteriorRing->clone() );
71   }
72 
73   for ( const QgsCurve *ring : p.mInteriorRings )
74   {
75     mInteriorRings.push_back( ring->clone() );
76   }
77 }
78 
operator =(const QgsCurvePolygon & p)79 QgsCurvePolygon &QgsCurvePolygon::operator=( const QgsCurvePolygon &p )
80 {
81   if ( &p != this )
82   {
83     clearCache();
84     QgsSurface::operator=( p );
85     if ( p.mExteriorRing )
86     {
87       mExteriorRing.reset( p.mExteriorRing->clone() );
88     }
89 
90     for ( const QgsCurve *ring : p.mInteriorRings )
91     {
92       mInteriorRings.push_back( ring->clone() );
93     }
94   }
95   return *this;
96 }
97 
operator ==(const QgsAbstractGeometry & other) const98 bool QgsCurvePolygon::operator==( const QgsAbstractGeometry &other ) const
99 {
100   const QgsCurvePolygon *otherPolygon = qgsgeometry_cast< const QgsCurvePolygon * >( &other );
101   if ( !otherPolygon )
102     return false;
103 
104   //run cheap checks first
105   if ( mWkbType != otherPolygon->mWkbType )
106     return false;
107 
108   if ( ( !mExteriorRing && otherPolygon->mExteriorRing ) || ( mExteriorRing && !otherPolygon->mExteriorRing ) )
109     return false;
110 
111   if ( mInteriorRings.count() != otherPolygon->mInteriorRings.count() )
112     return false;
113 
114   // compare rings
115   if ( mExteriorRing && otherPolygon->mExteriorRing )
116   {
117     if ( *mExteriorRing != *otherPolygon->mExteriorRing )
118       return false;
119   }
120 
121   for ( int i = 0; i < mInteriorRings.count(); ++i )
122   {
123     if ( ( !mInteriorRings.at( i ) && otherPolygon->mInteriorRings.at( i ) ) ||
124          ( mInteriorRings.at( i ) && !otherPolygon->mInteriorRings.at( i ) ) )
125       return false;
126 
127     if ( mInteriorRings.at( i ) && otherPolygon->mInteriorRings.at( i ) &&
128          *mInteriorRings.at( i ) != *otherPolygon->mInteriorRings.at( i ) )
129       return false;
130   }
131 
132   return true;
133 }
134 
operator !=(const QgsAbstractGeometry & other) const135 bool QgsCurvePolygon::operator!=( const QgsAbstractGeometry &other ) const
136 {
137   return !operator==( other );
138 }
139 
clone() const140 QgsCurvePolygon *QgsCurvePolygon::clone() const
141 {
142   return new QgsCurvePolygon( *this );
143 }
144 
clear()145 void QgsCurvePolygon::clear()
146 {
147   mWkbType = QgsWkbTypes::CurvePolygon;
148   mExteriorRing.reset();
149   qDeleteAll( mInteriorRings );
150   mInteriorRings.clear();
151   clearCache();
152 }
153 
154 
fromWkb(QgsConstWkbPtr & wkbPtr)155 bool QgsCurvePolygon::fromWkb( QgsConstWkbPtr &wkbPtr )
156 {
157   clear();
158   if ( !wkbPtr )
159   {
160     return false;
161   }
162 
163   QgsWkbTypes::Type type = wkbPtr.readHeader();
164   if ( QgsWkbTypes::flatType( type ) != QgsWkbTypes::CurvePolygon )
165   {
166     return false;
167   }
168   mWkbType = type;
169 
170   int nRings;
171   wkbPtr >> nRings;
172   std::unique_ptr< QgsCurve > currentCurve;
173   for ( int i = 0; i < nRings; ++i )
174   {
175     QgsWkbTypes::Type curveType = wkbPtr.readHeader();
176     wkbPtr -= 1 + sizeof( int );
177     QgsWkbTypes::Type flatCurveType = QgsWkbTypes::flatType( curveType );
178     if ( flatCurveType == QgsWkbTypes::LineString )
179     {
180       currentCurve.reset( new QgsLineString() );
181     }
182     else if ( flatCurveType == QgsWkbTypes::CircularString )
183     {
184       currentCurve.reset( new QgsCircularString() );
185     }
186     else if ( flatCurveType == QgsWkbTypes::CompoundCurve )
187     {
188       currentCurve.reset( new QgsCompoundCurve() );
189     }
190     else
191     {
192       return false;
193     }
194     currentCurve->fromWkb( wkbPtr );  // also updates wkbPtr
195     if ( i == 0 )
196     {
197       mExteriorRing = std::move( currentCurve );
198     }
199     else
200     {
201       mInteriorRings.append( currentCurve.release() );
202     }
203   }
204 
205   return true;
206 }
207 
fromWkt(const QString & wkt)208 bool QgsCurvePolygon::fromWkt( const QString &wkt )
209 {
210   clear();
211 
212   QPair<QgsWkbTypes::Type, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
213 
214   if ( QgsWkbTypes::geometryType( parts.first ) != QgsWkbTypes::PolygonGeometry )
215     return false;
216 
217   mWkbType = parts.first;
218 
219   QString secondWithoutParentheses = parts.second;
220   secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
221   if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
222        secondWithoutParentheses.isEmpty() )
223     return true;
224 
225   QString defaultChildWkbType = QStringLiteral( "LineString%1%2" ).arg( is3D() ? QStringLiteral( "Z" ) : QString(), isMeasure() ? QStringLiteral( "M" ) : QString() );
226 
227   const QStringList blocks = QgsGeometryUtils::wktGetChildBlocks( parts.second, defaultChildWkbType );
228   for ( const QString &childWkt : blocks )
229   {
230     QPair<QgsWkbTypes::Type, QString> childParts = QgsGeometryUtils::wktReadBlock( childWkt );
231 
232     QgsWkbTypes::Type flatCurveType = QgsWkbTypes::flatType( childParts.first );
233     if ( flatCurveType == QgsWkbTypes::LineString )
234       mInteriorRings.append( new QgsLineString() );
235     else if ( flatCurveType == QgsWkbTypes::CircularString )
236       mInteriorRings.append( new QgsCircularString() );
237     else if ( flatCurveType == QgsWkbTypes::CompoundCurve )
238       mInteriorRings.append( new QgsCompoundCurve() );
239     else
240     {
241       clear();
242       return false;
243     }
244     if ( !mInteriorRings.back()->fromWkt( childWkt ) )
245     {
246       clear();
247       return false;
248     }
249   }
250 
251   if ( mInteriorRings.isEmpty() )
252   {
253     clear();
254     return false;
255   }
256 
257   mExteriorRing.reset( mInteriorRings.takeFirst() );
258 
259   //scan through rings and check if dimensionality of rings is different to CurvePolygon.
260   //if so, update the type dimensionality of the CurvePolygon to match
261   bool hasZ = false;
262   bool hasM = false;
263   if ( mExteriorRing )
264   {
265     hasZ = hasZ || mExteriorRing->is3D();
266     hasM = hasM || mExteriorRing->isMeasure();
267   }
268   for ( const QgsCurve *curve : std::as_const( mInteriorRings ) )
269   {
270     hasZ = hasZ || curve->is3D();
271     hasM = hasM || curve->isMeasure();
272     if ( hasZ && hasM )
273       break;
274   }
275   if ( hasZ )
276     addZValue( 0 );
277   if ( hasM )
278     addMValue( 0 );
279 
280   return true;
281 }
282 
calculateBoundingBox() const283 QgsRectangle QgsCurvePolygon::calculateBoundingBox() const
284 {
285   if ( mExteriorRing )
286   {
287     return mExteriorRing->boundingBox();
288   }
289   return QgsRectangle();
290 }
291 
wkbSize(QgsAbstractGeometry::WkbFlags flags) const292 int QgsCurvePolygon::wkbSize( QgsAbstractGeometry::WkbFlags flags ) const
293 {
294   int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
295   if ( mExteriorRing )
296   {
297     binarySize += mExteriorRing->wkbSize( flags );
298   }
299   for ( const QgsCurve *curve : mInteriorRings )
300   {
301     binarySize += curve->wkbSize( flags );
302   }
303   return binarySize;
304 }
305 
asWkb(WkbFlags flags) const306 QByteArray QgsCurvePolygon::asWkb( WkbFlags flags ) const
307 {
308   QByteArray wkbArray;
309   wkbArray.resize( QgsCurvePolygon::wkbSize( flags ) );
310   QgsWkbPtr wkbPtr( wkbArray );
311   wkbPtr << static_cast<char>( QgsApplication::endian() );
312   wkbPtr << static_cast<quint32>( wkbType() );
313   wkbPtr << static_cast<quint32>( ( mExteriorRing ? 1 : 0 ) + mInteriorRings.size() );
314   if ( mExteriorRing )
315   {
316     wkbPtr << mExteriorRing->asWkb( flags );
317   }
318   for ( const QgsCurve *curve : mInteriorRings )
319   {
320     wkbPtr << curve->asWkb( flags );
321   }
322   return wkbArray;
323 }
324 
asWkt(int precision) const325 QString QgsCurvePolygon::asWkt( int precision ) const
326 {
327   QString wkt = wktTypeStr();
328 
329   if ( isEmpty() )
330     wkt += QLatin1String( " EMPTY" );
331   else
332   {
333     wkt += QLatin1String( " (" );
334     if ( mExteriorRing )
335     {
336       QString childWkt = mExteriorRing->asWkt( precision );
337       if ( qgsgeometry_cast<QgsLineString *>( mExteriorRing.get() ) )
338       {
339         // Type names of linear geometries are omitted
340         childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
341       }
342       wkt += childWkt + ',';
343     }
344     for ( const QgsCurve *curve : mInteriorRings )
345     {
346       QString childWkt = curve->asWkt( precision );
347       if ( qgsgeometry_cast<const QgsLineString *>( curve ) )
348       {
349         // Type names of linear geometries are omitted
350         childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
351       }
352       wkt += childWkt + ',';
353     }
354     if ( wkt.endsWith( ',' ) )
355     {
356       wkt.chop( 1 ); // Remove last ','
357     }
358     wkt += ')';
359   }
360   return wkt;
361 }
362 
asGml2(QDomDocument & doc,int precision,const QString & ns,const AxisOrder axisOrder) const363 QDomElement QgsCurvePolygon::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
364 {
365   // GML2 does not support curves
366   QDomElement elemPolygon = doc.createElementNS( ns, QStringLiteral( "Polygon" ) );
367 
368   if ( isEmpty() )
369     return elemPolygon;
370 
371   QDomElement elemOuterBoundaryIs = doc.createElementNS( ns, QStringLiteral( "outerBoundaryIs" ) );
372   std::unique_ptr< QgsLineString > exteriorLineString( exteriorRing()->curveToLine() );
373   QDomElement outerRing = exteriorLineString->asGml2( doc, precision, ns, axisOrder );
374   outerRing.toElement().setTagName( QStringLiteral( "LinearRing" ) );
375   elemOuterBoundaryIs.appendChild( outerRing );
376   elemPolygon.appendChild( elemOuterBoundaryIs );
377   std::unique_ptr< QgsLineString > interiorLineString;
378   for ( int i = 0, n = numInteriorRings(); i < n; ++i )
379   {
380     QDomElement elemInnerBoundaryIs = doc.createElementNS( ns, QStringLiteral( "innerBoundaryIs" ) );
381     interiorLineString.reset( interiorRing( i )->curveToLine() );
382     QDomElement innerRing = interiorLineString->asGml2( doc, precision, ns, axisOrder );
383     innerRing.toElement().setTagName( QStringLiteral( "LinearRing" ) );
384     elemInnerBoundaryIs.appendChild( innerRing );
385     elemPolygon.appendChild( elemInnerBoundaryIs );
386   }
387   return elemPolygon;
388 }
389 
asGml3(QDomDocument & doc,int precision,const QString & ns,const QgsAbstractGeometry::AxisOrder axisOrder) const390 QDomElement QgsCurvePolygon::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
391 {
392   QDomElement elemCurvePolygon = doc.createElementNS( ns, QStringLiteral( "Polygon" ) );
393 
394   if ( isEmpty() )
395     return elemCurvePolygon;
396 
397   QDomElement elemExterior = doc.createElementNS( ns, QStringLiteral( "exterior" ) );
398   QDomElement curveElem = exteriorRing()->asGml3( doc, precision, ns, axisOrder );
399   if ( curveElem.tagName() == QLatin1String( "LineString" ) )
400   {
401     curveElem.setTagName( QStringLiteral( "LinearRing" ) );
402   }
403   elemExterior.appendChild( curveElem );
404   elemCurvePolygon.appendChild( elemExterior );
405 
406   for ( int i = 0, n = numInteriorRings(); i < n; ++i )
407   {
408     QDomElement elemInterior = doc.createElementNS( ns, QStringLiteral( "interior" ) );
409     QDomElement innerRing = interiorRing( i )->asGml3( doc, precision, ns, axisOrder );
410     if ( innerRing.tagName() == QLatin1String( "LineString" ) )
411     {
412       innerRing.setTagName( QStringLiteral( "LinearRing" ) );
413     }
414     elemInterior.appendChild( innerRing );
415     elemCurvePolygon.appendChild( elemInterior );
416   }
417   return elemCurvePolygon;
418 }
419 
asJsonObject(int precision) const420 json QgsCurvePolygon::asJsonObject( int precision ) const
421 {
422   json coordinates( json::array( ) );
423   if ( auto *lExteriorRing = exteriorRing() )
424   {
425     std::unique_ptr< QgsLineString > exteriorLineString( lExteriorRing->curveToLine() );
426     QgsPointSequence exteriorPts;
427     exteriorLineString->points( exteriorPts );
428     coordinates.push_back( QgsGeometryUtils::pointsToJson( exteriorPts, precision ) );
429 
430     std::unique_ptr< QgsLineString > interiorLineString;
431     for ( int i = 0, n = numInteriorRings(); i < n; ++i )
432     {
433       interiorLineString.reset( interiorRing( i )->curveToLine() );
434       QgsPointSequence interiorPts;
435       interiorLineString->points( interiorPts );
436       coordinates.push_back( QgsGeometryUtils::pointsToJson( interiorPts, precision ) );
437     }
438   }
439   return
440   {
441     {  "type", "Polygon"  },
442     { "coordinates", coordinates }
443   };
444 }
445 
asKml(int precision) const446 QString QgsCurvePolygon::asKml( int precision ) const
447 {
448   QString kml;
449   kml.append( QLatin1String( "<Polygon>" ) );
450   if ( mExteriorRing )
451   {
452     kml.append( QLatin1String( "<outerBoundaryIs>" ) );
453     kml.append( mExteriorRing->asKml( precision ) );
454     kml.append( QLatin1String( "</outerBoundaryIs>" ) );
455   }
456   const QVector<QgsCurve *> &interiorRings = mInteriorRings;
457   for ( const QgsCurve *ring : interiorRings )
458   {
459     kml.append( QLatin1String( "<innerBoundaryIs>" ) );
460     kml.append( ring->asKml( precision ) );
461     kml.append( QLatin1String( "</innerBoundaryIs>" ) );
462   }
463   kml.append( QLatin1String( "</Polygon>" ) );
464   return kml;
465 }
466 
normalize()467 void QgsCurvePolygon::normalize()
468 {
469   // normalize rings
470   if ( mExteriorRing )
471     mExteriorRing->normalize();
472 
473   for ( QgsCurve *ring : std::as_const( mInteriorRings ) )
474   {
475     ring->normalize();
476   }
477 
478   // sort rings
479   std::sort( mInteriorRings.begin(), mInteriorRings.end(), []( const QgsCurve * a, const QgsCurve * b )
480   {
481     return a->compareTo( b ) > 0;
482   } );
483 
484   // normalize ring orientation
485   forceRHR();
486 }
487 
area() const488 double QgsCurvePolygon::area() const
489 {
490   if ( !mExteriorRing )
491   {
492     return 0.0;
493   }
494 
495   double totalArea = 0.0;
496 
497   if ( mExteriorRing->isRing() )
498   {
499     double area = 0.0;
500     mExteriorRing->sumUpArea( area );
501     totalArea += std::fabs( area );
502   }
503 
504   for ( const QgsCurve *ring : mInteriorRings )
505   {
506     double area = 0.0;
507     if ( ring->isRing() )
508     {
509       ring->sumUpArea( area );
510       totalArea -= std::fabs( area );
511     }
512   }
513   return totalArea;
514 }
515 
perimeter() const516 double QgsCurvePolygon::perimeter() const
517 {
518   if ( !mExteriorRing )
519     return 0.0;
520 
521   //sum perimeter of rings
522   double perimeter = mExteriorRing->length();
523   for ( const QgsCurve *ring : mInteriorRings )
524   {
525     perimeter += ring->length();
526   }
527   return perimeter;
528 }
529 
surfaceToPolygon() const530 QgsPolygon *QgsCurvePolygon::surfaceToPolygon() const
531 {
532   std::unique_ptr< QgsPolygon > polygon( new QgsPolygon() );
533   if ( !mExteriorRing )
534     return polygon.release();
535 
536   polygon->setExteriorRing( exteriorRing()->curveToLine() );
537   QVector<QgsCurve *> interiors;
538   int n = numInteriorRings();
539   interiors.reserve( n );
540   for ( int i = 0; i < n; ++i )
541   {
542     interiors.append( interiorRing( i )->curveToLine() );
543   }
544   polygon->setInteriorRings( interiors );
545   return polygon.release();
546 }
547 
boundary() const548 QgsAbstractGeometry *QgsCurvePolygon::boundary() const
549 {
550   if ( !mExteriorRing )
551     return nullptr;
552 
553   if ( mInteriorRings.isEmpty() )
554   {
555     return mExteriorRing->clone();
556   }
557   else
558   {
559     QgsMultiCurve *multiCurve = new QgsMultiCurve();
560     int nInteriorRings = mInteriorRings.size();
561     multiCurve->reserve( nInteriorRings + 1 );
562     multiCurve->addGeometry( mExteriorRing->clone() );
563     for ( int i = 0; i < nInteriorRings; ++i )
564     {
565       multiCurve->addGeometry( mInteriorRings.at( i )->clone() );
566     }
567     return multiCurve;
568   }
569 }
570 
snappedToGrid(double hSpacing,double vSpacing,double dSpacing,double mSpacing) const571 QgsCurvePolygon *QgsCurvePolygon::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const
572 {
573   if ( !mExteriorRing )
574     return nullptr;
575 
576 
577   std::unique_ptr< QgsCurvePolygon > polygon( createEmptyWithSameType() );
578 
579   // exterior ring
580   auto exterior = std::unique_ptr<QgsCurve> { static_cast< QgsCurve *>( mExteriorRing->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) ) };
581 
582   if ( !exterior )
583     return nullptr;
584 
585   polygon->mExteriorRing = std::move( exterior );
586 
587   //interior rings
588   for ( auto interior : mInteriorRings )
589   {
590     if ( !interior )
591       continue;
592 
593     QgsCurve *gridifiedInterior = static_cast< QgsCurve * >( interior->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) );
594 
595     if ( !gridifiedInterior )
596       continue;
597 
598     polygon->mInteriorRings.append( gridifiedInterior );
599   }
600 
601   return polygon.release();
602 
603 }
604 
removeDuplicateNodes(double epsilon,bool useZValues)605 bool QgsCurvePolygon::removeDuplicateNodes( double epsilon, bool useZValues )
606 {
607   bool result = false;
608   auto cleanRing = [epsilon, useZValues ]( QgsCurve * ring )->bool
609   {
610     if ( ring->numPoints() <= 4 )
611       return false;
612 
613     if ( ring->removeDuplicateNodes( epsilon, useZValues ) )
614     {
615       QgsPoint startPoint;
616       Qgis::VertexType type;
617       ring->pointAt( 0, startPoint, type );
618       // ensure ring is properly closed - if we removed the final node, it may no longer be properly closed
619       ring->moveVertex( QgsVertexId( -1, -1, ring->numPoints() - 1 ), startPoint );
620       return true;
621     }
622 
623     return false;
624   };
625   if ( mExteriorRing )
626   {
627     result = cleanRing( mExteriorRing.get() );
628   }
629   for ( QgsCurve *ring : std::as_const( mInteriorRings ) )
630   {
631     if ( cleanRing( ring ) ) result = true;
632   }
633   return result;
634 }
635 
boundingBoxIntersects(const QgsRectangle & rectangle) const636 bool QgsCurvePolygon::boundingBoxIntersects( const QgsRectangle &rectangle ) const
637 {
638   if ( !mExteriorRing && mInteriorRings.empty() )
639     return false;
640 
641   // if we already have the bounding box calculated, then this check is trivial!
642   if ( !mBoundingBox.isNull() )
643   {
644     return mBoundingBox.intersects( rectangle );
645   }
646 
647   // loop through each ring and test the bounding box intersection.
648   // This gives us a chance to use optimisations which may be present on the individual
649   // ring geometry subclasses, and at worst it will cause a calculation of the bounding box
650   // of each individual ring geometry which we would have to do anyway... (and these
651   // bounding boxes are cached, so would be reused without additional expense)
652   if ( mExteriorRing && mExteriorRing->boundingBoxIntersects( rectangle ) )
653     return true;
654 
655   for ( const QgsCurve *ring : mInteriorRings )
656   {
657     if ( ring->boundingBoxIntersects( rectangle ) )
658       return true;
659   }
660 
661   // even if we don't intersect the bounding box of any rings, we may still intersect the
662   // bounding box of the overall polygon (we are considering worst case scenario here and
663   // the polygon is invalid, with rings outside the exterior ring!)
664   // so here we fall back to the non-optimised base class check which has to first calculate
665   // the overall bounding box of the polygon..
666   return QgsSurface::boundingBoxIntersects( rectangle );
667 }
668 
toPolygon(double tolerance,SegmentationToleranceType toleranceType) const669 QgsPolygon *QgsCurvePolygon::toPolygon( double tolerance, SegmentationToleranceType toleranceType ) const
670 {
671   std::unique_ptr< QgsPolygon > poly( new QgsPolygon() );
672   if ( !mExteriorRing )
673   {
674     return poly.release();
675   }
676 
677   poly->setExteriorRing( mExteriorRing->curveToLine( tolerance, toleranceType ) );
678 
679   QVector<QgsCurve *> rings;
680   rings.reserve( mInteriorRings.size() );
681   for ( const QgsCurve *ring : mInteriorRings )
682   {
683     rings.push_back( ring->curveToLine( tolerance, toleranceType ) );
684   }
685   poly->setInteriorRings( rings );
686   return poly.release();
687 }
688 
setExteriorRing(QgsCurve * ring)689 void QgsCurvePolygon::setExteriorRing( QgsCurve *ring )
690 {
691   if ( !ring )
692   {
693     return;
694   }
695   mExteriorRing.reset( ring );
696 
697   //set proper wkb type
698   if ( QgsWkbTypes::flatType( wkbType() ) == QgsWkbTypes::Polygon )
699   {
700     setZMTypeFromSubGeometry( ring, QgsWkbTypes::Polygon );
701   }
702   else if ( QgsWkbTypes::flatType( wkbType() ) == QgsWkbTypes::CurvePolygon )
703   {
704     setZMTypeFromSubGeometry( ring, QgsWkbTypes::CurvePolygon );
705   }
706 
707   //match dimensionality for rings
708   for ( QgsCurve *ring : std::as_const( mInteriorRings ) )
709   {
710     if ( is3D() )
711       ring->addZValue();
712     else
713       ring->dropZValue();
714 
715     if ( isMeasure() )
716       ring->addMValue();
717     else
718       ring->dropMValue();
719   }
720   clearCache();
721 }
722 
setInteriorRings(const QVector<QgsCurve * > & rings)723 void QgsCurvePolygon::setInteriorRings( const QVector<QgsCurve *> &rings )
724 {
725   qDeleteAll( mInteriorRings );
726   mInteriorRings.clear();
727 
728   //add rings one-by-one, so that they can each be converted to the correct type for the CurvePolygon
729   for ( QgsCurve *ring : rings )
730   {
731     addInteriorRing( ring );
732   }
733   clearCache();
734 }
735 
addInteriorRing(QgsCurve * ring)736 void QgsCurvePolygon::addInteriorRing( QgsCurve *ring )
737 {
738   if ( !ring )
739     return;
740 
741   //ensure dimensionality of ring matches curve polygon
742   if ( !is3D() )
743     ring->dropZValue();
744   else if ( !ring->is3D() )
745     ring->addZValue();
746 
747   if ( !isMeasure() )
748     ring->dropMValue();
749   else if ( !ring->isMeasure() )
750     ring->addMValue();
751 
752   mInteriorRings.append( ring );
753   clearCache();
754 }
755 
removeInteriorRing(int nr)756 bool QgsCurvePolygon::removeInteriorRing( int nr )
757 {
758   if ( nr < 0 || nr >= mInteriorRings.size() )
759   {
760     return false;
761   }
762   delete mInteriorRings.takeAt( nr );
763   clearCache();
764   return true;
765 }
766 
removeInteriorRings(double minimumAllowedArea)767 void QgsCurvePolygon::removeInteriorRings( double minimumAllowedArea )
768 {
769   for ( int ringIndex = mInteriorRings.size() - 1; ringIndex >= 0; --ringIndex )
770   {
771     if ( minimumAllowedArea < 0 )
772       delete mInteriorRings.takeAt( ringIndex );
773     else
774     {
775       double area = 0.0;
776       mInteriorRings.at( ringIndex )->sumUpArea( area );
777       if ( area < minimumAllowedArea )
778         delete mInteriorRings.takeAt( ringIndex );
779     }
780   }
781 
782   clearCache();
783 }
784 
removeInvalidRings()785 void QgsCurvePolygon::removeInvalidRings()
786 {
787   QVector<QgsCurve *> validRings;
788   validRings.reserve( mInteriorRings.size() );
789   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
790   {
791     if ( !curve->isRing() )
792     {
793       // remove invalid rings
794       delete curve;
795     }
796     else
797     {
798       validRings << curve;
799     }
800   }
801   mInteriorRings = validRings;
802 }
803 
forceRHR()804 void QgsCurvePolygon::forceRHR()
805 {
806   if ( mExteriorRing && mExteriorRing->orientation() != QgsCurve::Clockwise )
807   {
808     // flip exterior ring orientation
809     std::unique_ptr< QgsCurve > flipped( mExteriorRing->reversed() );
810     mExteriorRing = std::move( flipped );
811   }
812 
813   QVector<QgsCurve *> validRings;
814   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
815   {
816     if ( curve && curve->orientation() != QgsCurve::CounterClockwise )
817     {
818       // flip interior ring orientation
819       QgsCurve *flipped = curve->reversed();
820       validRings << flipped;
821       delete curve;
822     }
823     else
824     {
825       validRings << curve;
826     }
827   }
828   mInteriorRings = validRings;
829 }
830 
asQPainterPath() const831 QPainterPath QgsCurvePolygon::asQPainterPath() const
832 {
833   QPainterPath p;
834   if ( mExteriorRing )
835   {
836     QPainterPath ring = mExteriorRing->asQPainterPath();
837     ring.closeSubpath();
838     p.addPath( ring );
839   }
840 
841   for ( const QgsCurve *ring : mInteriorRings )
842   {
843     QPainterPath ringPath = ring->asQPainterPath();
844     ringPath.closeSubpath();
845     p.addPath( ringPath );
846   }
847 
848   return p;
849 }
850 
draw(QPainter & p) const851 void QgsCurvePolygon::draw( QPainter &p ) const
852 {
853   if ( !mExteriorRing )
854     return;
855 
856   if ( mInteriorRings.empty() )
857   {
858     mExteriorRing->drawAsPolygon( p );
859   }
860   else
861   {
862     QPainterPath path;
863     mExteriorRing->addToPainterPath( path );
864 
865     for ( const QgsCurve *ring : mInteriorRings )
866     {
867       ring->addToPainterPath( path );
868     }
869     p.drawPath( path );
870   }
871 }
872 
transform(const QgsCoordinateTransform & ct,Qgis::TransformDirection d,bool transformZ)873 void QgsCurvePolygon::transform( const QgsCoordinateTransform &ct, Qgis::TransformDirection d, bool transformZ )
874 {
875   if ( mExteriorRing )
876   {
877     mExteriorRing->transform( ct, d, transformZ );
878   }
879 
880   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
881   {
882     curve->transform( ct, d, transformZ );
883   }
884   clearCache();
885 }
886 
transform(const QTransform & t,double zTranslate,double zScale,double mTranslate,double mScale)887 void QgsCurvePolygon::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
888 {
889   if ( mExteriorRing )
890   {
891     mExteriorRing->transform( t, zTranslate, zScale, mTranslate, mScale );
892   }
893 
894   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
895   {
896     curve->transform( t, zTranslate, zScale, mTranslate, mScale );
897   }
898   clearCache();
899 }
900 
coordinateSequence() const901 QgsCoordinateSequence QgsCurvePolygon::coordinateSequence() const
902 {
903   QgsCoordinateSequence sequence;
904   sequence.append( QgsRingSequence() );
905 
906   if ( mExteriorRing )
907   {
908     sequence.back().append( QgsPointSequence() );
909     mExteriorRing->points( sequence.back().back() );
910   }
911 
912   for ( const QgsCurve *ring : mInteriorRings )
913   {
914     sequence.back().append( QgsPointSequence() );
915     ring->points( sequence.back().back() );
916   }
917 
918   return sequence;
919 }
920 
nCoordinates() const921 int QgsCurvePolygon::nCoordinates() const
922 {
923   int count = 0;
924 
925   if ( mExteriorRing )
926   {
927     count += mExteriorRing->nCoordinates();
928   }
929 
930   for ( const QgsCurve *ring : mInteriorRings )
931   {
932     count += ring->nCoordinates();
933   }
934 
935   return count;
936 }
937 
vertexNumberFromVertexId(QgsVertexId id) const938 int QgsCurvePolygon::vertexNumberFromVertexId( QgsVertexId id ) const
939 {
940   if ( id.part != 0 )
941     return -1;
942 
943   if ( id.ring < 0 || id.ring >= ringCount() || !mExteriorRing )
944     return -1;
945 
946   int number = 0;
947   if ( id.ring == 0 )
948   {
949     return mExteriorRing->vertexNumberFromVertexId( QgsVertexId( 0, 0, id.vertex ) );
950   }
951   else
952   {
953     number += mExteriorRing->numPoints();
954   }
955 
956   for ( int i = 0; i < mInteriorRings.count(); ++i )
957   {
958     if ( id.ring == i + 1 )
959     {
960       int partNumber = mInteriorRings.at( i )->vertexNumberFromVertexId( QgsVertexId( 0, 0, id.vertex ) );
961       if ( partNumber == -1 )
962         return -1;
963       return number + partNumber;
964     }
965     else
966     {
967       number += mInteriorRings.at( i )->numPoints();
968     }
969   }
970   return -1; // should not happen
971 }
972 
isEmpty() const973 bool QgsCurvePolygon::isEmpty() const
974 {
975   if ( !mExteriorRing )
976     return true;
977 
978   return mExteriorRing->isEmpty();
979 }
980 
closestSegment(const QgsPoint & pt,QgsPoint & segmentPt,QgsVertexId & vertexAfter,int * leftOf,double epsilon) const981 double QgsCurvePolygon::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
982 {
983   if ( !mExteriorRing )
984   {
985     return -1;
986   }
987   QVector<QgsCurve *> segmentList;
988   segmentList.append( mExteriorRing.get() );
989   segmentList.append( mInteriorRings );
990   return QgsGeometryUtils::closestSegmentFromComponents( segmentList, QgsGeometryUtils::Ring, pt, segmentPt,  vertexAfter, leftOf, epsilon );
991 }
992 
nextVertex(QgsVertexId & vId,QgsPoint & vertex) const993 bool QgsCurvePolygon::nextVertex( QgsVertexId &vId, QgsPoint &vertex ) const
994 {
995   if ( !mExteriorRing || vId.ring >= 1 + mInteriorRings.size() )
996   {
997     return false;
998   }
999 
1000   if ( vId.ring < 0 )
1001   {
1002     vId.ring = 0;
1003     vId.vertex = -1;
1004     if ( vId.part < 0 )
1005     {
1006       vId.part = 0;
1007     }
1008     return mExteriorRing->nextVertex( vId, vertex );
1009   }
1010   else
1011   {
1012     QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings[vId.ring - 1];
1013 
1014     if ( ring->nextVertex( vId, vertex ) )
1015     {
1016       return true;
1017     }
1018     ++vId.ring;
1019     vId.vertex = -1;
1020     if ( vId.ring >= 1 + mInteriorRings.size() )
1021     {
1022       return false;
1023     }
1024     ring = mInteriorRings[ vId.ring - 1 ];
1025     return ring->nextVertex( vId, vertex );
1026   }
1027 }
1028 
ringAdjacentVertices(const QgsCurve * curve,QgsVertexId vertex,QgsVertexId & previousVertex,QgsVertexId & nextVertex)1029 void ringAdjacentVertices( const QgsCurve *curve, QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex )
1030 {
1031   int n = curve->numPoints();
1032   if ( vertex.vertex < 0 || vertex.vertex >= n )
1033   {
1034     previousVertex = QgsVertexId();
1035     nextVertex = QgsVertexId();
1036     return;
1037   }
1038 
1039   if ( vertex.vertex == 0 && n < 3 )
1040   {
1041     previousVertex = QgsVertexId();
1042   }
1043   else if ( vertex.vertex == 0 )
1044   {
1045     previousVertex = QgsVertexId( vertex.part, vertex.ring, n - 2 );
1046   }
1047   else
1048   {
1049     previousVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex - 1 );
1050   }
1051   if ( vertex.vertex == n - 1 && n < 3 )
1052   {
1053     nextVertex = QgsVertexId();
1054   }
1055   else if ( vertex.vertex == n - 1 )
1056   {
1057     nextVertex = QgsVertexId( vertex.part, vertex.ring, 1 );
1058   }
1059   else
1060   {
1061     nextVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex + 1 );
1062   }
1063 }
1064 
adjacentVertices(QgsVertexId vertex,QgsVertexId & previousVertex,QgsVertexId & nextVertex) const1065 void QgsCurvePolygon::adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex ) const
1066 {
1067   if ( !mExteriorRing || vertex.ring < 0 || vertex.ring >= 1 + mInteriorRings.size() )
1068   {
1069     previousVertex = QgsVertexId();
1070     nextVertex = QgsVertexId();
1071     return;
1072   }
1073 
1074   if ( vertex.ring == 0 )
1075   {
1076     ringAdjacentVertices( mExteriorRing.get(), vertex, previousVertex, nextVertex );
1077   }
1078   else
1079   {
1080     ringAdjacentVertices( mInteriorRings.at( vertex.ring - 1 ), vertex, previousVertex, nextVertex );
1081   }
1082 }
1083 
insertVertex(QgsVertexId vId,const QgsPoint & vertex)1084 bool QgsCurvePolygon::insertVertex( QgsVertexId vId, const QgsPoint &vertex )
1085 {
1086   if ( !mExteriorRing || vId.ring < 0 || vId.ring >= 1 + mInteriorRings.size() )
1087   {
1088     return false;
1089   }
1090 
1091   QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( vId.ring - 1 );
1092   int n = ring->numPoints();
1093   bool success = ring->insertVertex( QgsVertexId( 0, 0, vId.vertex ), vertex );
1094   if ( !success )
1095   {
1096     return false;
1097   }
1098 
1099   // If first or last vertex is inserted, re-sync the last/first vertex
1100   if ( vId.vertex == 0 )
1101     ring->moveVertex( QgsVertexId( 0, 0, n ), vertex );
1102   else if ( vId.vertex == n )
1103     ring->moveVertex( QgsVertexId( 0, 0, 0 ), vertex );
1104 
1105   clearCache();
1106 
1107   return true;
1108 }
1109 
moveVertex(QgsVertexId vId,const QgsPoint & newPos)1110 bool QgsCurvePolygon::moveVertex( QgsVertexId vId, const QgsPoint &newPos )
1111 {
1112   if ( !mExteriorRing || vId.ring < 0 || vId.ring >= 1 + mInteriorRings.size() )
1113   {
1114     return false;
1115   }
1116 
1117   QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( vId.ring - 1 );
1118   int n = ring->numPoints();
1119   bool success = ring->moveVertex( vId, newPos );
1120   if ( success )
1121   {
1122     // If first or last vertex is moved, also move the last/first vertex
1123     if ( vId.vertex == 0 )
1124       ring->moveVertex( QgsVertexId( vId.part, vId.ring, n - 1 ), newPos );
1125     else if ( vId.vertex == n - 1 )
1126       ring->moveVertex( QgsVertexId( vId.part, vId.ring, 0 ), newPos );
1127     clearCache();
1128   }
1129   return success;
1130 }
1131 
deleteVertex(QgsVertexId vId)1132 bool QgsCurvePolygon::deleteVertex( QgsVertexId vId )
1133 {
1134   if ( !mExteriorRing || vId.ring < 0 || vId.ring >= 1 + mInteriorRings.size() )
1135   {
1136     return false;
1137   }
1138 
1139   QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( vId.ring - 1 );
1140   int n = ring->numPoints();
1141   if ( n <= 4 )
1142   {
1143     //no points will be left in ring, so remove whole ring
1144     if ( vId.ring == 0 )
1145     {
1146       mExteriorRing.reset();
1147       if ( !mInteriorRings.isEmpty() )
1148       {
1149         mExteriorRing.reset( mInteriorRings.takeFirst() );
1150       }
1151     }
1152     else
1153     {
1154       removeInteriorRing( vId.ring - 1 );
1155     }
1156     clearCache();
1157     return true;
1158   }
1159 
1160   bool success = ring->deleteVertex( vId );
1161   if ( success )
1162   {
1163     // If first or last vertex is removed, re-sync the last/first vertex
1164     // Do not use "n - 2", but "ring->numPoints() - 1" as more than one vertex
1165     // may have been deleted (e.g. with CircularString)
1166     if ( vId.vertex == 0 )
1167       ring->moveVertex( QgsVertexId( 0, 0, ring->numPoints() - 1 ), ring->vertexAt( QgsVertexId( 0, 0, 0 ) ) );
1168     else if ( vId.vertex == n - 1 )
1169       ring->moveVertex( QgsVertexId( 0, 0, 0 ), ring->vertexAt( QgsVertexId( 0, 0, ring->numPoints() - 1 ) ) );
1170     clearCache();
1171   }
1172   return success;
1173 }
1174 
hasCurvedSegments() const1175 bool QgsCurvePolygon::hasCurvedSegments() const
1176 {
1177   if ( mExteriorRing && mExteriorRing->hasCurvedSegments() )
1178   {
1179     return true;
1180   }
1181 
1182   for ( const QgsCurve *ring : mInteriorRings )
1183   {
1184     if ( ring->hasCurvedSegments() )
1185     {
1186       return true;
1187     }
1188   }
1189   return false;
1190 }
1191 
segmentize(double tolerance,SegmentationToleranceType toleranceType) const1192 QgsAbstractGeometry *QgsCurvePolygon::segmentize( double tolerance, SegmentationToleranceType toleranceType ) const
1193 {
1194   return toPolygon( tolerance, toleranceType );
1195 }
1196 
vertexAngle(QgsVertexId vertex) const1197 double QgsCurvePolygon::vertexAngle( QgsVertexId vertex ) const
1198 {
1199   if ( !mExteriorRing || vertex.ring < 0 || vertex.ring >= 1 + mInteriorRings.size() )
1200   {
1201     //makes no sense - conversion of false to double!
1202     return false;
1203   }
1204 
1205   QgsCurve *ring = vertex.ring == 0 ? mExteriorRing.get() : mInteriorRings[vertex.ring - 1];
1206   return ring->vertexAngle( vertex );
1207 }
1208 
vertexCount(int,int ring) const1209 int QgsCurvePolygon::vertexCount( int /*part*/, int ring ) const
1210 {
1211   return ring == 0 ? mExteriorRing->vertexCount() : mInteriorRings[ring - 1]->vertexCount();
1212 }
1213 
ringCount(int) const1214 int QgsCurvePolygon::ringCount( int ) const
1215 {
1216   return ( nullptr != mExteriorRing ) + mInteriorRings.size();
1217 }
1218 
partCount() const1219 int QgsCurvePolygon::partCount() const
1220 {
1221   return ringCount() > 0 ? 1 : 0;
1222 }
1223 
vertexAt(QgsVertexId id) const1224 QgsPoint QgsCurvePolygon::vertexAt( QgsVertexId id ) const
1225 {
1226   return id.ring == 0 ? mExteriorRing->vertexAt( id ) : mInteriorRings[id.ring - 1]->vertexAt( id );
1227 }
1228 
segmentLength(QgsVertexId startVertex) const1229 double QgsCurvePolygon::segmentLength( QgsVertexId startVertex ) const
1230 {
1231   if ( !mExteriorRing || startVertex.ring < 0 || startVertex.ring >= 1 + mInteriorRings.size() )
1232   {
1233     return 0.0;
1234   }
1235 
1236   const QgsCurve *ring = startVertex.ring == 0 ? mExteriorRing.get() : mInteriorRings[startVertex.ring - 1];
1237   return ring->segmentLength( startVertex );
1238 }
1239 
addZValue(double zValue)1240 bool QgsCurvePolygon::addZValue( double zValue )
1241 {
1242   if ( QgsWkbTypes::hasZ( mWkbType ) )
1243     return false;
1244 
1245   mWkbType = QgsWkbTypes::addZ( mWkbType );
1246 
1247   if ( mExteriorRing )
1248     mExteriorRing->addZValue( zValue );
1249   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1250   {
1251     curve->addZValue( zValue );
1252   }
1253   clearCache();
1254   return true;
1255 }
1256 
addMValue(double mValue)1257 bool QgsCurvePolygon::addMValue( double mValue )
1258 {
1259   if ( QgsWkbTypes::hasM( mWkbType ) )
1260     return false;
1261 
1262   mWkbType = QgsWkbTypes::addM( mWkbType );
1263 
1264   if ( mExteriorRing )
1265     mExteriorRing->addMValue( mValue );
1266   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1267   {
1268     curve->addMValue( mValue );
1269   }
1270   clearCache();
1271   return true;
1272 }
1273 
dropZValue()1274 bool QgsCurvePolygon::dropZValue()
1275 {
1276   if ( !is3D() )
1277     return false;
1278 
1279   mWkbType = QgsWkbTypes::dropZ( mWkbType );
1280   if ( mExteriorRing )
1281     mExteriorRing->dropZValue();
1282   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1283   {
1284     curve->dropZValue();
1285   }
1286   clearCache();
1287   return true;
1288 }
1289 
dropMValue()1290 bool QgsCurvePolygon::dropMValue()
1291 {
1292   if ( !isMeasure() )
1293     return false;
1294 
1295   mWkbType = QgsWkbTypes::dropM( mWkbType );
1296   if ( mExteriorRing )
1297     mExteriorRing->dropMValue();
1298   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1299   {
1300     curve->dropMValue();
1301   }
1302   clearCache();
1303   return true;
1304 }
1305 
swapXy()1306 void QgsCurvePolygon::swapXy()
1307 {
1308   if ( mExteriorRing )
1309     mExteriorRing->swapXy();
1310   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1311   {
1312     curve->swapXy();
1313   }
1314   clearCache();
1315 }
1316 
toCurveType() const1317 QgsCurvePolygon *QgsCurvePolygon::toCurveType() const
1318 {
1319   return clone();
1320 }
1321 
transform(QgsAbstractGeometryTransformer * transformer,QgsFeedback * feedback)1322 bool QgsCurvePolygon::transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback )
1323 {
1324   if ( !transformer )
1325     return false;
1326 
1327   bool res = true;
1328   if ( mExteriorRing )
1329     res = mExteriorRing->transform( transformer, feedback );
1330 
1331   if ( !res || ( feedback && feedback->isCanceled() ) )
1332   {
1333     clearCache();
1334     return false;
1335   }
1336 
1337   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1338   {
1339     res = curve->transform( transformer );
1340 
1341     if ( feedback && feedback->isCanceled() )
1342       res = false;
1343 
1344     if ( !res )
1345       break;
1346   }
1347   clearCache();
1348   return res;
1349 }
1350 
filterVertices(const std::function<bool (const QgsPoint &)> & filter)1351 void QgsCurvePolygon::filterVertices( const std::function<bool ( const QgsPoint & )> &filter )
1352 {
1353   if ( mExteriorRing )
1354     mExteriorRing->filterVertices( filter );
1355 
1356   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1357   {
1358     curve->filterVertices( filter );
1359   }
1360   clearCache();
1361 }
1362 
transformVertices(const std::function<QgsPoint (const QgsPoint &)> & transform)1363 void QgsCurvePolygon::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
1364 {
1365   if ( mExteriorRing )
1366     mExteriorRing->transformVertices( transform );
1367 
1368   for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1369   {
1370     curve->transformVertices( transform );
1371   }
1372   clearCache();
1373 }
1374 
childCount() const1375 int QgsCurvePolygon::childCount() const
1376 {
1377   return 1 + mInteriorRings.count();
1378 }
1379 
childGeometry(int index) const1380 QgsAbstractGeometry *QgsCurvePolygon::childGeometry( int index ) const
1381 {
1382   if ( index == 0 )
1383     return mExteriorRing.get();
1384   else
1385     return mInteriorRings.at( index - 1 );
1386 }
1387 
compareToSameClass(const QgsAbstractGeometry * other) const1388 int QgsCurvePolygon::compareToSameClass( const QgsAbstractGeometry *other ) const
1389 {
1390   const QgsCurvePolygon *otherPolygon = qgsgeometry_cast<const QgsCurvePolygon *>( other );
1391   if ( !otherPolygon )
1392     return -1;
1393 
1394   if ( mExteriorRing && !otherPolygon->mExteriorRing )
1395     return 1;
1396   else if ( !mExteriorRing && otherPolygon->mExteriorRing )
1397     return -1;
1398   else if ( mExteriorRing && otherPolygon->mExteriorRing )
1399   {
1400     int shellComp = mExteriorRing->compareTo( otherPolygon->mExteriorRing.get() );
1401     if ( shellComp != 0 )
1402     {
1403       return shellComp;
1404     }
1405   }
1406 
1407   const int nHole1 = mInteriorRings.size();
1408   const int nHole2 = otherPolygon->mInteriorRings.size();
1409   if ( nHole1 < nHole2 )
1410   {
1411     return -1;
1412   }
1413   if ( nHole1 > nHole2 )
1414   {
1415     return 1;
1416   }
1417 
1418   for ( int i = 0; i < nHole1; i++ )
1419   {
1420     const int holeComp = mInteriorRings.at( i )->compareTo( otherPolygon->mInteriorRings.at( i ) );
1421     if ( holeComp != 0 )
1422     {
1423       return holeComp;
1424     }
1425   }
1426 
1427   return 0;
1428 }
1429