QGIS API Documentation 4.3.0-Master (dc0ce6e99ce)
Loading...
Searching...
No Matches
qgscurvepolygon.cpp
Go to the documentation of this file.
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
20#include <memory>
21#include <nlohmann/json.hpp>
22
23#include "qgsapplication.h"
24#include "qgscircularstring.h"
25#include "qgscompoundcurve.h"
26#include "qgsfeedback.h"
27#include "qgsgeometryutils.h"
28#include "qgslinestring.h"
29#include "qgsmulticurve.h"
30#include "qgspolygon.h"
31#include "qgswkbptr.h"
32
33#include <QJsonArray>
34#include <QJsonObject>
35#include <QPainter>
36#include <QPainterPath>
37#include <QString>
38
39using namespace Qt::StringLiterals;
40
45
50
52{
53 auto result = std::make_unique< QgsCurvePolygon >();
54 result->mWkbType = mWkbType;
55 return result.release();
56}
57
59{
60 return u"CurvePolygon"_s;
61}
62
64{
65 return 2;
66}
67
69 : QgsSurface( p )
70
71{
73 if ( p.mExteriorRing )
74 {
75 mExteriorRing.reset( p.mExteriorRing->clone() );
76 }
77
78 for ( const QgsCurve *ring : p.mInteriorRings )
79 {
80 mInteriorRings.push_back( ring->clone() );
81 }
82
86}
87
88// cppcheck-suppress operatorEqVarError
90{
91 if ( &p != this )
92 {
94 if ( p.mExteriorRing )
95 {
96 mExteriorRing.reset( p.mExteriorRing->clone() );
97 }
98
99 for ( const QgsCurve *ring : p.mInteriorRings )
100 {
101 mInteriorRings.push_back( ring->clone() );
102 }
103 }
104 return *this;
105}
106
108{
109 return new QgsCurvePolygon( *this );
110}
111
113{
115 mExteriorRing.reset();
116 qDeleteAll( mInteriorRings );
117 mInteriorRings.clear();
118 clearCache();
119}
120
121
123{
124 clear();
125 if ( !wkbPtr )
126 {
127 return false;
128 }
129
130 Qgis::WkbType type = wkbPtr.readHeader();
132 {
133 return false;
134 }
135 mWkbType = type;
136
137 int nRings;
138 wkbPtr >> nRings;
139 std::unique_ptr< QgsCurve > currentCurve;
140 for ( int i = 0; i < nRings; ++i )
141 {
142 Qgis::WkbType curveType = wkbPtr.readHeader();
143 wkbPtr -= 1 + sizeof( int );
144 Qgis::WkbType flatCurveType = QgsWkbTypes::flatType( curveType );
145 if ( flatCurveType == Qgis::WkbType::LineString )
146 {
147 currentCurve = std::make_unique<QgsLineString>();
148 }
149 else if ( flatCurveType == Qgis::WkbType::CircularString )
150 {
151 currentCurve = std::make_unique<QgsCircularString>();
152 }
153 else if ( flatCurveType == Qgis::WkbType::CompoundCurve )
154 {
155 currentCurve = std::make_unique<QgsCompoundCurve>();
156 }
157 else
158 {
159 return false;
160 }
161 currentCurve->fromWkb( wkbPtr ); // also updates wkbPtr
162 if ( i == 0 )
163 {
164 mExteriorRing = std::move( currentCurve );
165 }
166 else
167 {
168 mInteriorRings.append( currentCurve.release() );
169 }
170 }
171
172 return true;
173}
174
175bool QgsCurvePolygon::fromWkt( const QString &wkt )
176{
177 clear();
178
179 QPair<Qgis::WkbType, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
180
182 return false;
183
184 mWkbType = parts.first;
185
186 QString secondWithoutParentheses = parts.second;
187 secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
188 if ( ( parts.second.compare( "EMPTY"_L1, Qt::CaseInsensitive ) == 0 ) || secondWithoutParentheses.isEmpty() )
189 return true;
190
191 QString defaultChildWkbType = u"LineString%1%2"_s.arg( is3D() ? u"Z"_s : QString(), isMeasure() ? u"M"_s : QString() );
192
193 const QStringList blocks = QgsGeometryUtils::wktGetChildBlocks( parts.second, defaultChildWkbType );
194 for ( const QString &childWkt : blocks )
195 {
196 QPair<Qgis::WkbType, QString> childParts = QgsGeometryUtils::wktReadBlock( childWkt );
197
198 Qgis::WkbType flatCurveType = QgsWkbTypes::flatType( childParts.first );
199 if ( flatCurveType == Qgis::WkbType::LineString )
200 mInteriorRings.append( new QgsLineString() );
201 else if ( flatCurveType == Qgis::WkbType::CircularString )
202 mInteriorRings.append( new QgsCircularString() );
203 else if ( flatCurveType == Qgis::WkbType::CompoundCurve )
204 mInteriorRings.append( new QgsCompoundCurve() );
205 else
206 {
207 clear();
208 return false;
209 }
210 if ( !mInteriorRings.back()->fromWkt( childWkt ) )
211 {
212 clear();
213 return false;
214 }
215 }
216
217 if ( mInteriorRings.isEmpty() )
218 {
219 clear();
220 return false;
221 }
222
223 mExteriorRing.reset( mInteriorRings.takeFirst() );
224
225 //scan through rings and check if dimensionality of rings is different to CurvePolygon.
226 //if so, update the type dimensionality of the CurvePolygon to match
227 bool hasZ = false;
228 bool hasM = false;
229 if ( mExteriorRing )
230 {
231 hasZ = hasZ || mExteriorRing->is3D();
232 hasM = hasM || mExteriorRing->isMeasure();
233 }
234 for ( const QgsCurve *curve : std::as_const( mInteriorRings ) )
235 {
236 hasZ = hasZ || curve->is3D();
237 hasM = hasM || curve->isMeasure();
238 if ( hasZ && hasM )
239 break;
240 }
241 if ( hasZ )
242 addZValue( 0 );
243 if ( hasM )
244 addMValue( 0 );
245
246 return true;
247}
248
250{
251 if ( mExteriorRing )
252 {
253 return mExteriorRing->boundingBox3D();
254 }
255 return QgsBox3D();
256}
257
259{
260 int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
261 if ( mExteriorRing )
262 {
263 binarySize += mExteriorRing->wkbSize( flags );
264 }
265 for ( const QgsCurve *curve : mInteriorRings )
266 {
267 binarySize += curve->wkbSize( flags );
268 }
269 return binarySize;
270}
271
272QByteArray QgsCurvePolygon::asWkb( WkbFlags flags ) const
273{
274 QByteArray wkbArray;
275 wkbArray.resize( QgsCurvePolygon::wkbSize( flags ) );
276 QgsWkbPtr wkbPtr( wkbArray );
277 wkbPtr << static_cast<char>( QgsApplication::endian() );
278 wkbPtr << static_cast<quint32>( wkbType() );
279 wkbPtr << static_cast<quint32>( ( mExteriorRing ? 1 : 0 ) + mInteriorRings.size() );
280 if ( mExteriorRing )
281 {
282 wkbPtr << mExteriorRing->asWkb( flags );
283 }
284 for ( const QgsCurve *curve : mInteriorRings )
285 {
286 wkbPtr << curve->asWkb( flags );
287 }
288 return wkbArray;
289}
290
291QString QgsCurvePolygon::asWkt( int precision ) const
292{
293 QString wkt = wktTypeStr();
294
295 if ( isEmpty() )
296 wkt += " EMPTY"_L1;
297 else
298 {
299 wkt += " ("_L1;
300 if ( mExteriorRing )
301 {
302 QString childWkt = mExteriorRing->asWkt( precision );
304 {
305 // Type names of linear geometries are omitted
306 childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
307 }
308 wkt += childWkt + ',';
309 }
310 for ( const QgsCurve *curve : mInteriorRings )
311 {
312 if ( !curve->isEmpty() )
313 {
314 QString childWkt = curve->asWkt( precision );
316 {
317 // Type names of linear geometries are omitted
318 childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
319 }
320 wkt += childWkt + ',';
321 }
322 }
323 if ( wkt.endsWith( ',' ) )
324 {
325 wkt.chop( 1 ); // Remove last ','
326 }
327 wkt += ')';
328 }
329 return wkt;
330}
331
332QDomElement QgsCurvePolygon::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
333{
334 // GML2 does not support curves
335 QDomElement elemPolygon = doc.createElementNS( ns, u"Polygon"_s );
336
337 if ( isEmpty() )
338 return elemPolygon;
339
340 QDomElement elemOuterBoundaryIs = doc.createElementNS( ns, u"outerBoundaryIs"_s );
341 std::unique_ptr< QgsLineString > exteriorLineString( exteriorRing()->curveToLine() );
342 QDomElement outerRing = exteriorLineString->asGml2( doc, precision, ns, axisOrder );
343 outerRing.toElement().setTagName( u"LinearRing"_s );
344 elemOuterBoundaryIs.appendChild( outerRing );
345 elemPolygon.appendChild( elemOuterBoundaryIs );
346 std::unique_ptr< QgsLineString > interiorLineString;
347 for ( int i = 0, n = numInteriorRings(); i < n; ++i )
348 {
349 QDomElement elemInnerBoundaryIs = doc.createElementNS( ns, u"innerBoundaryIs"_s );
350 interiorLineString.reset( interiorRing( i )->curveToLine() );
351 QDomElement innerRing = interiorLineString->asGml2( doc, precision, ns, axisOrder );
352 innerRing.toElement().setTagName( u"LinearRing"_s );
353 elemInnerBoundaryIs.appendChild( innerRing );
354 elemPolygon.appendChild( elemInnerBoundaryIs );
355 }
356 return elemPolygon;
357}
358
359QDomElement QgsCurvePolygon::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
360{
361 QDomElement elemCurvePolygon = doc.createElementNS( ns, u"Polygon"_s );
362
363 if ( isEmpty() )
364 return elemCurvePolygon;
365
366 const auto exportRing = [&doc, precision, &ns, axisOrder]( const QgsCurve *ring ) {
367 QDomElement ringElem = ring->asGml3( doc, precision, ns, axisOrder );
368 if ( ringElem.tagName() == "LineString"_L1 )
369 {
370 ringElem.setTagName( u"LinearRing"_s );
371 }
372 else if ( ringElem.tagName() == "CompositeCurve"_L1 )
373 {
374 ringElem.setTagName( u"Ring"_s );
375 }
376 else if ( ringElem.tagName() == "Curve"_L1 )
377 {
378 QDomElement ringElemNew = doc.createElementNS( ns, u"Ring"_s );
379 QDomElement curveMemberElem = doc.createElementNS( ns, u"curveMember"_s );
380 ringElemNew.appendChild( curveMemberElem );
381 curveMemberElem.appendChild( ringElem );
382 ringElem = std::move( ringElemNew );
383 }
384 return ringElem;
385 };
386
387 QDomElement elemExterior = doc.createElementNS( ns, u"exterior"_s );
388 elemExterior.appendChild( exportRing( exteriorRing() ) );
389 elemCurvePolygon.appendChild( elemExterior );
390
391 for ( int i = 0, n = numInteriorRings(); i < n; ++i )
392 {
393 QDomElement elemInterior = doc.createElementNS( ns, u"interior"_s );
394 elemInterior.appendChild( exportRing( interiorRing( i ) ) );
395 elemCurvePolygon.appendChild( elemInterior );
396 }
397 return elemCurvePolygon;
398}
399
400json QgsCurvePolygon::asJsonObject( int precision, Qgis::GeoJsonProfile profile ) const
401{
402 switch ( profile )
403 {
406 {
407 json coordinates( json::array() );
408 if ( const QgsCurve *lExteriorRing = exteriorRing() )
409 {
410 std::unique_ptr< QgsLineString > exteriorLineString( lExteriorRing->curveToLine() );
411 QgsPointSequence exteriorPts;
412 exteriorLineString->points( exteriorPts );
413 coordinates.push_back( QgsGeometryUtils::pointsToJson( exteriorPts, precision, profile ) );
414
415 std::unique_ptr< QgsLineString > interiorLineString;
416 for ( int i = 0, n = numInteriorRings(); i < n; ++i )
417 {
418 interiorLineString.reset( interiorRing( i )->curveToLine() );
419 QgsPointSequence interiorPts;
420 interiorLineString->points( interiorPts );
421 coordinates.push_back( QgsGeometryUtils::pointsToJson( interiorPts, precision, profile ) );
422 }
423 }
424 return { { "type", "Polygon" }, { "coordinates", coordinates } };
425 }
428 {
429 json geometries = json::array();
430 if ( const QgsCurve *lExteriorRing = exteriorRing() )
431 {
432 geometries.push_back( lExteriorRing->asJsonObject( precision, profile ) );
433 for ( int i = 0, n = numInteriorRings(); i < n; ++i )
434 {
435 geometries.push_back( interiorRing( i )->asJsonObject( precision, profile ) );
436 }
437 }
438 return { { "type", "CurvePolygon" }, { "geometries", geometries } };
439 }
440 }
442}
443
444QString QgsCurvePolygon::asKml( int precision ) const
445{
446 QString kml;
447 kml.append( "<Polygon>"_L1 );
448 if ( mExteriorRing )
449 {
450 kml.append( "<outerBoundaryIs>"_L1 );
451 kml.append( mExteriorRing->asKml( precision ) );
452 kml.append( "</outerBoundaryIs>"_L1 );
453 }
454 const QVector<QgsCurve *> &interiorRings = mInteriorRings;
455 for ( const QgsCurve *ring : interiorRings )
456 {
457 kml.append( "<innerBoundaryIs>"_L1 );
458 kml.append( ring->asKml( precision ) );
459 kml.append( "</innerBoundaryIs>"_L1 );
460 }
461 kml.append( "</Polygon>"_L1 );
462 return kml;
463}
464
466{
467 // normalize rings
468 if ( mExteriorRing )
469 mExteriorRing->normalize();
470
471 for ( QgsCurve *ring : std::as_const( mInteriorRings ) )
472 {
473 ring->normalize();
474 }
475
476 // sort rings
477 std::sort( mInteriorRings.begin(), mInteriorRings.end(), []( const QgsCurve *a, const QgsCurve *b ) { return a->compareTo( b ) > 0; } );
478
479 // normalize ring orientation
480 forceRHR();
481}
482
484{
485 if ( !mExteriorRing )
486 {
487 return 0.0;
488 }
489
490 double totalArea = 0.0;
491
492 if ( mExteriorRing->isRing() )
493 {
494 double area = 0.0;
495 mExteriorRing->sumUpArea( area );
496 totalArea += std::fabs( area );
497 }
498
499 for ( const QgsCurve *ring : mInteriorRings )
500 {
501 double area = 0.0;
502 if ( ring->isRing() )
503 {
504 ring->sumUpArea( area );
505 totalArea -= std::fabs( area );
506 }
507 }
508 return totalArea;
509}
510
512{
513 if ( !mExteriorRing )
514 {
515 return 0.0;
516 }
517
518 double totalArea3D = 0.0;
519
520 if ( mExteriorRing->isRing() )
521 {
522 double area3D = 0.0;
523 mExteriorRing->sumUpArea3D( area3D );
524 totalArea3D += std::abs( area3D );
525 }
526
527 for ( const QgsCurve *ring : mInteriorRings )
528 {
529 double area3D = 0.0;
530 if ( ring->isRing() )
531 {
532 ring->sumUpArea3D( area3D );
533 totalArea3D -= std::abs( area3D );
534 }
535 }
536
537 return totalArea3D;
538}
539
541{
542 if ( !mExteriorRing )
543 return 0.0;
544
545 //sum perimeter of rings
546 double perimeter = mExteriorRing->length();
547 for ( const QgsCurve *ring : mInteriorRings )
548 {
549 perimeter += ring->length();
550 }
551 return perimeter;
552}
553
555{
556 const double p = perimeter();
557 if ( qgsDoubleNear( p, 0.0 ) )
558 return 0.0;
559
560 return 4.0 * M_PI * area() / pow( p, 2.0 );
561}
562
564{
565 auto polygon = std::make_unique<QgsPolygon>();
566 if ( !mExteriorRing )
567 return polygon.release();
568
569 polygon->setExteriorRing( exteriorRing()->curveToLine() );
570 QVector<QgsCurve *> interiors;
571 int n = numInteriorRings();
572 interiors.reserve( n );
573 for ( int i = 0; i < n; ++i )
574 {
575 interiors.append( interiorRing( i )->curveToLine() );
576 }
577 polygon->setInteriorRings( interiors );
578 return polygon.release();
579}
580
582{
583 if ( !mExteriorRing )
584 return nullptr;
585
586 if ( mInteriorRings.isEmpty() )
587 {
588 return mExteriorRing->clone();
589 }
590 else
591 {
592 QgsMultiCurve *multiCurve = new QgsMultiCurve();
593 int nInteriorRings = mInteriorRings.size();
594 multiCurve->reserve( nInteriorRings + 1 );
595 multiCurve->addGeometry( mExteriorRing->clone() );
596 for ( int i = 0; i < nInteriorRings; ++i )
597 {
598 multiCurve->addGeometry( mInteriorRings.at( i )->clone() );
599 }
600 return multiCurve;
601 }
602}
603
604QgsCurvePolygon *QgsCurvePolygon::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const
605{
606 if ( !mExteriorRing )
607 return nullptr;
608
609
610 std::unique_ptr< QgsCurvePolygon > polygon( createEmptyWithSameType() );
611
612 // exterior ring
613 auto exterior = std::unique_ptr<QgsCurve> { static_cast< QgsCurve *>( mExteriorRing->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) };
614
615 if ( !exterior )
616 return nullptr;
617
618 polygon->mExteriorRing = std::move( exterior );
619
620 //interior rings
621 for ( auto interior : mInteriorRings )
622 {
623 if ( !interior )
624 continue;
625
626 QgsCurve *gridifiedInterior = static_cast< QgsCurve * >( interior->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) );
627
628 if ( !gridifiedInterior )
629 continue;
630
631 polygon->mInteriorRings.append( gridifiedInterior );
632 }
633
634 return polygon.release();
635}
636
638{
639 if ( !mExteriorRing )
640 return nullptr;
641
642 // exterior ring
643 std::unique_ptr< QgsAbstractGeometry > exterior( mExteriorRing->simplifyByDistance( tolerance ) );
644 if ( !qgsgeometry_cast< QgsLineString * >( exterior.get() ) )
645 return nullptr;
646
647 auto polygon = std::make_unique< QgsPolygon >( qgis::down_cast< QgsLineString * >( exterior.release() ) );
648
649 //interior rings
650 for ( const QgsCurve *interior : mInteriorRings )
651 {
652 if ( !interior )
653 continue;
654
655 std::unique_ptr< QgsAbstractGeometry > simplifiedRing( interior->simplifyByDistance( tolerance ) );
656 if ( !simplifiedRing )
657 return nullptr;
658
659 if ( !qgsgeometry_cast< QgsLineString * >( simplifiedRing.get() ) )
660 return nullptr;
661
662 polygon->mInteriorRings.append( qgis::down_cast< QgsLineString * >( simplifiedRing.release() ) );
663 }
664
665 return polygon.release();
666}
667
668bool QgsCurvePolygon::removeDuplicateNodes( double epsilon, bool useZValues )
669{
670 bool result = false;
671 auto cleanRing = [epsilon, useZValues]( QgsCurve *ring ) -> bool {
672 if ( ring->numPoints() <= 4 )
673 return false;
674
675 if ( ring->removeDuplicateNodes( epsilon, useZValues ) )
676 {
677 QgsPoint startPoint;
678 Qgis::VertexType type;
679 ring->pointAt( 0, startPoint, type );
680 // ensure ring is properly closed - if we removed the final node, it may no longer be properly closed
681 ring->moveVertex( QgsVertexId( -1, -1, ring->numPoints() - 1 ), startPoint );
682 return true;
683 }
684
685 return false;
686 };
687 if ( mExteriorRing )
688 {
689 result = cleanRing( mExteriorRing.get() );
690 }
691 for ( QgsCurve *ring : std::as_const( mInteriorRings ) )
692 {
693 if ( cleanRing( ring ) )
694 result = true;
695 }
696 return result;
697}
698
700{
701 // SIMILAR LOGIC IN boundingBoxIntersects(const QgsRectangle& box), update that if you change this!
702 if ( !mExteriorRing && mInteriorRings.empty() )
703 return false;
704
705 // if we already have the bounding box calculated, then this check is trivial!
706 if ( !mBoundingBox.isNull() )
707 {
708 return mBoundingBox.intersects( box3d );
709 }
710
711 // loop through each ring and test the bounding box intersection.
712 // This gives us a chance to use optimisations which may be present on the individual
713 // ring geometry subclasses, and at worst it will cause a calculation of the bounding box
714 // of each individual ring geometry which we would have to do anyway... (and these
715 // bounding boxes are cached, so would be reused without additional expense)
716 if ( mExteriorRing && mExteriorRing->boundingBoxIntersects( box3d ) )
717 return true;
718
719 for ( const QgsCurve *ring : mInteriorRings )
720 {
721 if ( ring->boundingBoxIntersects( box3d ) )
722 return true;
723 }
724
725 // even if we don't intersect the bounding box of any rings, we may still intersect the
726 // bounding box of the overall polygon (we are considering worst case scenario here and
727 // the polygon is invalid, with rings outside the exterior ring!)
728 // so here we fall back to the non-optimised base class check which has to first calculate
729 // the overall bounding box of the polygon..
730 return QgsSurface::boundingBoxIntersects( box3d );
731
732 // SIMILAR LOGIC IN boundingBoxIntersects(const QgsRectangle& box), update that if you change this!
733}
734
736{
737 // SIMILAR LOGIC IN boundingBoxIntersects(const QgsBox3D &box3d), update that if you change this!
738
739 if ( !mExteriorRing && mInteriorRings.empty() )
740 return false;
741
742 // if we already have the bounding box calculated, then this check is trivial!
743 if ( !mBoundingBox.isNull() )
744 {
745 return mBoundingBox.toRectangle().intersects( box );
746 }
747
748 // loop through each ring and test the bounding box intersection.
749 // This gives us a chance to use optimisations which may be present on the individual
750 // ring geometry subclasses, and at worst it will cause a calculation of the bounding box
751 // of each individual ring geometry which we would have to do anyway... (and these
752 // bounding boxes are cached, so would be reused without additional expense)
753 if ( mExteriorRing && mExteriorRing->boundingBoxIntersects( box ) )
754 return true;
755
756 for ( const QgsCurve *ring : mInteriorRings )
757 {
758 if ( ring->boundingBoxIntersects( box ) )
759 return true;
760 }
761
762 // even if we don't intersect the bounding box of any rings, we may still intersect the
763 // bounding box of the overall polygon (we are considering worst case scenario here and
764 // the polygon is invalid, with rings outside the exterior ring!)
765 // so here we fall back to the non-optimised base class check which has to first calculate
766 // the overall bounding box of the polygon..
768
769 // SIMILAR LOGIC IN boundingBoxIntersects(const QgsBox3D &box3d), update that if you change this!
770}
771
772QgsPolygon *QgsCurvePolygon::toPolygon( double tolerance, SegmentationToleranceType toleranceType ) const
773{
774 auto poly = std::make_unique<QgsPolygon>();
775 if ( !mExteriorRing )
776 {
777 return poly.release();
778 }
779
780 poly->setExteriorRing( mExteriorRing->curveToLine( tolerance, toleranceType ) );
781
782 QVector<QgsCurve *> rings;
783 rings.reserve( mInteriorRings.size() );
784 for ( const QgsCurve *ring : mInteriorRings )
785 {
786 rings.push_back( ring->curveToLine( tolerance, toleranceType ) );
787 }
788 poly->setInteriorRings( rings );
789 return poly.release();
790}
791
793{
794 if ( !ring )
795 {
796 return;
797 }
798 mExteriorRing.reset( ring );
799
800 //set proper wkb type
802 {
804 }
806 {
808 }
809
810 //match dimensionality for rings
811 for ( QgsCurve *ring : std::as_const( mInteriorRings ) )
812 {
813 if ( is3D() )
814 ring->addZValue();
815 else
816 ring->dropZValue();
817
818 if ( isMeasure() )
819 ring->addMValue();
820 else
821 ring->dropMValue();
822 }
823 clearCache();
824}
825
826void QgsCurvePolygon::setInteriorRings( const QVector<QgsCurve *> &rings )
827{
828 qDeleteAll( mInteriorRings );
829 mInteriorRings.clear();
830
831 //add rings one-by-one, so that they can each be converted to the correct type for the CurvePolygon
832 for ( QgsCurve *ring : rings )
833 {
834 addInteriorRing( ring );
835 }
836 clearCache();
837}
838
840{
841 if ( !ring )
842 return;
843
844 //ensure dimensionality of ring matches curve polygon
845 if ( !is3D() )
846 ring->dropZValue();
847 else if ( !ring->is3D() )
848 ring->addZValue();
849
850 if ( !isMeasure() )
851 ring->dropMValue();
852 else if ( !ring->isMeasure() )
853 ring->addMValue();
854
855 mInteriorRings.append( ring );
856 clearCache();
857}
858
860{
861 if ( nr < 0 || nr >= mInteriorRings.size() )
862 {
863 return false;
864 }
865 delete mInteriorRings.takeAt( nr );
866 clearCache();
867 return true;
868}
869
870void QgsCurvePolygon::removeInteriorRings( double minimumAllowedArea )
871{
872 for ( int ringIndex = mInteriorRings.size() - 1; ringIndex >= 0; --ringIndex )
873 {
874 if ( minimumAllowedArea < 0 )
875 delete mInteriorRings.takeAt( ringIndex );
876 else
877 {
878 double area = 0.0;
879 mInteriorRings.at( ringIndex )->sumUpArea( area );
880 if ( std::fabs( area ) < minimumAllowedArea )
881 delete mInteriorRings.takeAt( ringIndex );
882 }
883 }
884
885 clearCache();
886}
887
888void QgsCurvePolygon::removeRing( int ringId )
889{
890 if ( ringId == 0 )
891 {
892 mExteriorRing.reset();
893 if ( !mInteriorRings.isEmpty() )
894 {
895 mExteriorRing.reset( mInteriorRings.takeFirst() );
896 }
897 }
898 else
899 {
900 removeInteriorRing( ringId - 1 );
901 }
902}
903
905{
906 QVector<QgsCurve *> validRings;
907 validRings.reserve( mInteriorRings.size() );
908 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
909 {
910 if ( !curve->isRing() )
911 {
912 // remove invalid rings
913 delete curve;
914 }
915 else
916 {
917 validRings << curve;
918 }
919 }
920 mInteriorRings = validRings;
921}
922
927
929{
931 {
932 // flip exterior ring orientation
933 std::unique_ptr< QgsCurve > flipped( mExteriorRing->reversed() );
934 mExteriorRing = std::move( flipped );
935 }
936
937 QVector<QgsCurve *> validRings;
938 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
939 {
940 if ( curve && curve->orientation() != Qgis::AngularDirection::CounterClockwise )
941 {
942 // flip interior ring orientation
943 QgsCurve *flipped = curve->reversed();
944 validRings << flipped;
945 delete curve;
946 }
947 else
948 {
949 validRings << curve;
950 }
951 }
952 mInteriorRings = validRings;
953}
954
956{
958 {
959 // flip exterior ring orientation
960 mExteriorRing.reset( mExteriorRing->reversed() );
961 }
962
963 QVector<QgsCurve *> validRings;
964 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
965 {
966 if ( curve && curve->orientation() != Qgis::AngularDirection::Clockwise )
967 {
968 // flip interior ring orientation
969 QgsCurve *flipped = curve->reversed();
970 validRings << flipped;
971 delete curve;
972 }
973 else
974 {
975 validRings << curve;
976 }
977 }
978 mInteriorRings = validRings;
979}
980
982{
983 QPainterPath p;
984 if ( mExteriorRing )
985 {
986 QPainterPath ring = mExteriorRing->asQPainterPath();
987 ring.closeSubpath();
988 p.addPath( ring );
989 }
990
991 for ( const QgsCurve *ring : mInteriorRings )
992 {
993 QPainterPath ringPath = ring->asQPainterPath();
994 ringPath.closeSubpath();
995 p.addPath( ringPath );
996 }
997
998 return p;
999}
1000
1001void QgsCurvePolygon::draw( QPainter &p ) const
1002{
1003 if ( !mExteriorRing )
1004 return;
1005
1006 if ( mInteriorRings.empty() )
1007 {
1008 mExteriorRing->drawAsPolygon( p );
1009 }
1010 else
1011 {
1012 QPainterPath path;
1013 mExteriorRing->addToPainterPath( path );
1014
1015 for ( const QgsCurve *ring : mInteriorRings )
1016 {
1017 ring->addToPainterPath( path );
1018 }
1019 p.drawPath( path );
1020 }
1021}
1022
1024{
1025 if ( mExteriorRing )
1026 {
1027 mExteriorRing->transform( ct, d, transformZ );
1028 }
1029
1030 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1031 {
1032 curve->transform( ct, d, transformZ );
1033 }
1034 clearCache();
1035}
1036
1037void QgsCurvePolygon::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
1038{
1039 if ( mExteriorRing )
1040 {
1041 mExteriorRing->transform( t, zTranslate, zScale, mTranslate, mScale );
1042 }
1043
1044 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1045 {
1046 curve->transform( t, zTranslate, zScale, mTranslate, mScale );
1047 }
1048 clearCache();
1049}
1050
1052{
1053 QgsCoordinateSequence sequence;
1054 sequence.append( QgsRingSequence() );
1055
1056 if ( mExteriorRing )
1057 {
1058 sequence.back().append( QgsPointSequence() );
1059 mExteriorRing->points( sequence.back().back() );
1060 }
1061
1062 for ( const QgsCurve *ring : mInteriorRings )
1063 {
1064 sequence.back().append( QgsPointSequence() );
1065 ring->points( sequence.back().back() );
1066 }
1067
1068 return sequence;
1069}
1070
1072{
1073 int count = 0;
1074
1075 if ( mExteriorRing )
1076 {
1077 count += mExteriorRing->nCoordinates();
1078 }
1079
1080 for ( const QgsCurve *ring : mInteriorRings )
1081 {
1082 count += ring->nCoordinates();
1083 }
1084
1085 return count;
1086}
1087
1089{
1090 if ( id.part != 0 )
1091 return -1;
1092
1093 if ( id.ring < 0 || id.ring >= ringCount() || !mExteriorRing )
1094 return -1;
1095
1096 int number = 0;
1097 if ( id.ring == 0 )
1098 {
1099 return mExteriorRing->vertexNumberFromVertexId( QgsVertexId( 0, 0, id.vertex ) );
1100 }
1101 else
1102 {
1103 number += mExteriorRing->numPoints();
1104 }
1105
1106 for ( int i = 0; i < mInteriorRings.count(); ++i )
1107 {
1108 if ( id.ring == i + 1 )
1109 {
1110 int partNumber = mInteriorRings.at( i )->vertexNumberFromVertexId( QgsVertexId( 0, 0, id.vertex ) );
1111 if ( partNumber == -1 )
1112 return -1;
1113 return number + partNumber;
1114 }
1115 else
1116 {
1117 number += mInteriorRings.at( i )->numPoints();
1118 }
1119 }
1120 return -1; // should not happen
1121}
1122
1124{
1125 if ( !mExteriorRing )
1126 return true;
1127
1128 return mExteriorRing->isEmpty();
1129}
1130
1131double QgsCurvePolygon::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1132{
1133 if ( !mExteriorRing )
1134 {
1135 return -1;
1136 }
1137 QVector<QgsCurve *> segmentList;
1138 segmentList.append( mExteriorRing.get() );
1139 segmentList.append( mInteriorRings );
1140 return QgsGeometryUtils::closestSegmentFromComponents( segmentList, QgsGeometryUtils::Ring, pt, segmentPt, vertexAfter, leftOf, epsilon );
1141}
1142
1144{
1145 if ( !mExteriorRing || vId.ring >= 1 + mInteriorRings.size() )
1146 {
1147 return false;
1148 }
1149
1150 if ( vId.ring < 0 )
1151 {
1152 vId.ring = 0;
1153 vId.vertex = -1;
1154 if ( vId.part < 0 )
1155 {
1156 vId.part = 0;
1157 }
1158 return mExteriorRing->nextVertex( vId, vertex );
1159 }
1160 else
1161 {
1162 QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings[vId.ring - 1];
1163
1164 if ( ring->nextVertex( vId, vertex ) )
1165 {
1166 return true;
1167 }
1168 ++vId.ring;
1169 vId.vertex = -1;
1170 if ( vId.ring >= 1 + mInteriorRings.size() )
1171 {
1172 return false;
1173 }
1174 ring = mInteriorRings[vId.ring - 1];
1175 return ring->nextVertex( vId, vertex );
1176 }
1177}
1178
1179void ringAdjacentVertices( const QgsCurve *curve, QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex )
1180{
1181 int n = curve->numPoints();
1182 if ( vertex.vertex < 0 || vertex.vertex >= n )
1183 {
1184 previousVertex = QgsVertexId();
1185 nextVertex = QgsVertexId();
1186 return;
1187 }
1188
1189 if ( vertex.vertex == 0 && n < 3 )
1190 {
1191 previousVertex = QgsVertexId();
1192 }
1193 else if ( vertex.vertex == 0 )
1194 {
1195 previousVertex = QgsVertexId( vertex.part, vertex.ring, n - 2 );
1196 }
1197 else
1198 {
1199 previousVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex - 1 );
1200 }
1201 if ( vertex.vertex == n - 1 && n < 3 )
1202 {
1203 nextVertex = QgsVertexId();
1204 }
1205 else if ( vertex.vertex == n - 1 )
1206 {
1207 nextVertex = QgsVertexId( vertex.part, vertex.ring, 1 );
1208 }
1209 else
1210 {
1211 nextVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex + 1 );
1212 }
1213}
1214
1216{
1217 if ( !mExteriorRing || vertex.ring < 0 || vertex.ring >= 1 + mInteriorRings.size() )
1218 {
1219 previousVertex = QgsVertexId();
1221 return;
1222 }
1223
1224 if ( vertex.ring == 0 )
1225 {
1226 ringAdjacentVertices( mExteriorRing.get(), vertex, previousVertex, nextVertex );
1227 }
1228 else
1229 {
1230 ringAdjacentVertices( mInteriorRings.at( vertex.ring - 1 ), vertex, previousVertex, nextVertex );
1231 }
1232}
1233
1235{
1236 if ( !mExteriorRing || vId.ring < 0 || vId.ring >= 1 + mInteriorRings.size() )
1237 {
1238 return false;
1239 }
1240
1241 QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( vId.ring - 1 );
1242 int n = ring->numPoints();
1243 bool success = ring->insertVertex( QgsVertexId( 0, 0, vId.vertex ), vertex );
1244 if ( !success )
1245 {
1246 return false;
1247 }
1248
1249 // If first or last vertex is inserted, re-sync the last/first vertex
1250 if ( vId.vertex == 0 )
1251 ring->moveVertex( QgsVertexId( 0, 0, n ), vertex );
1252 else if ( vId.vertex == n )
1253 ring->moveVertex( QgsVertexId( 0, 0, 0 ), vertex );
1254
1255 clearCache();
1256
1257 return true;
1258}
1259
1261{
1262 if ( !mExteriorRing || vId.ring < 0 || vId.ring >= 1 + mInteriorRings.size() )
1263 {
1264 return false;
1265 }
1266
1267 QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( vId.ring - 1 );
1268 int n = ring->numPoints();
1269 bool success = ring->moveVertex( vId, newPos );
1270 if ( success )
1271 {
1272 // If first or last vertex is moved, also move the last/first vertex
1273 if ( vId.vertex == 0 )
1274 ring->moveVertex( QgsVertexId( vId.part, vId.ring, n - 1 ), newPos );
1275 else if ( vId.vertex == n - 1 )
1276 ring->moveVertex( QgsVertexId( vId.part, vId.ring, 0 ), newPos );
1277 clearCache();
1278 }
1279 return success;
1280}
1281
1283{
1284 const int interiorRingId = vId.ring - 1;
1285 if ( !mExteriorRing || vId.ring < 0 || interiorRingId >= mInteriorRings.size() )
1286 {
1287 return false;
1288 }
1289
1290 // cppcheck-suppress containerOutOfBounds
1291 QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( interiorRingId );
1292 int n = ring->numPoints();
1293 if ( n <= 4 )
1294 {
1295 //no points will be left in ring, so remove whole ring
1296 if ( vId.ring == 0 )
1297 {
1298 mExteriorRing.reset();
1299 if ( !mInteriorRings.isEmpty() )
1300 {
1301 mExteriorRing.reset( mInteriorRings.takeFirst() );
1302 }
1303 }
1304 else
1305 {
1306 removeInteriorRing( vId.ring - 1 );
1307 }
1308 clearCache();
1309 return true;
1310 }
1311
1312 bool success = ring->deleteVertex( vId );
1313 if ( success )
1314 {
1315 // If first or last vertex is removed, re-sync the last/first vertex
1316 // Do not use "n - 2", but "ring->numPoints() - 1" as more than one vertex
1317 // may have been deleted (e.g. with CircularString)
1318 if ( vId.vertex == 0 )
1319 ring->moveVertex( QgsVertexId( 0, 0, ring->numPoints() - 1 ), ring->vertexAt( QgsVertexId( 0, 0, 0 ) ) );
1320 else if ( vId.vertex == n - 1 )
1321 ring->moveVertex( QgsVertexId( 0, 0, 0 ), ring->vertexAt( QgsVertexId( 0, 0, ring->numPoints() - 1 ) ) );
1322 clearCache();
1323 }
1324 return success;
1325}
1326
1327bool QgsCurvePolygon::deleteVertices( const QSet<QgsVertexId> &positions )
1328{
1329 if ( positions.empty() )
1330 {
1331 return false;
1332 }
1333
1334 QMap<int, QList<QgsVertexId >> ringVertices;
1335 for ( QgsVertexId pos : positions )
1336 {
1337 if ( !hasVertex( pos ) )
1338 {
1339 return false;
1340 }
1341
1342 ringVertices[pos.ring].append( QgsVertexId( 0, 0, pos.vertex ) );
1343 }
1344
1345 QMapIterator<int, QList<QgsVertexId >> ringVerticesIt( ringVertices );
1346
1347 ringVerticesIt.toBack();
1348 while ( ringVerticesIt.hasPrevious() )
1349 {
1350 ringVerticesIt.previous();
1351 QList<QgsVertexId> vertices = ringVerticesIt.value();
1352 int ringId = ringVerticesIt.key();
1353
1354 const int interiorRingId = ringId - 1;
1355
1356 // cppcheck-suppress containerOutOfBounds
1357 QgsCurve *ring = ringId == 0 ? mExteriorRing.get() : mInteriorRings.at( interiorRingId );
1358
1359 int n = ring->numPoints();
1360
1361 // sort so we can check for first/last vertex deletion
1362 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex < b.vertex; } );
1363
1364 QgsVertexId firstVertexId = vertices.first();
1365 QgsVertexId lastVertexId = vertices.last();
1366
1367 // check if we are deleting the same point twice and remove the first, but not in a compound curve
1368 if ( ( firstVertexId.vertex == 0 ) && ( lastVertexId.vertex == n - 1 ) && !( QgsWkbTypes::flatType( ring->wkbType() ) == Qgis::WkbType::CompoundCurve ) )
1369 {
1370 vertices.removeFirst();
1371 }
1372
1373 if ( vertices.size() > n - 4 )
1374 {
1375 // no points will be left in ring, so remove whole ring
1376 removeRing( ringId );
1377 continue;
1378 }
1379
1380 // we cannot make assumptions what happens in deleteVertices() of curves
1381 // circularstring can be at the end or start of the compoundcurve, and when its point is deleted, whole arc is deleted, not just that point
1382 // let deleteVertices() handle that and then we check if the points are not the same and just sync them
1384 {
1385 if ( firstVertexId.vertex == 0 && !vertices.contains( QgsVertexId( 0, 0, n - 1 ) ) )
1386 {
1387 vertices.emplace_back( 0, 0, n - 1 );
1388 }
1389 else if ( lastVertexId.vertex == n - 1 && !vertices.contains( QgsVertexId( 0, 0, 0 ) ) )
1390 {
1391 vertices.emplace_back( 0, 0, 0 );
1392 }
1393 }
1394
1395 if ( !ring->deleteVertices( QSet<QgsVertexId>( vertices.begin(), vertices.end() ) ) )
1396 {
1397 Q_ASSERT( false );
1398 return false;
1399 }
1400
1401 // safety check
1402 // consider a compoundcurve with 2 circularstrings and we are deleting first/last point
1403 // in such case, we are removing the whole geometry in that operation
1404 if ( ring->numPoints() == 0 )
1405 {
1406 removeRing( ringId );
1407 continue;
1408 }
1409
1410 // in case of a compound curve, first/last vertex may have been deleted even if not specified
1411 // in such case, we copy the first vertex and add it at the end
1413 {
1414 // add start point at the end if not the same
1415 if ( !( ring->vertexAt( QgsVertexId( 0, 0, 0 ) ) == ring->vertexAt( QgsVertexId( 0, 0, ring->numPoints() - 1 ) ) ) )
1416 {
1418 compoundRing->addVertex( ring->vertexAt( QgsVertexId( 0, 0, 0 ) ) );
1419 }
1420 continue;
1421 }
1422
1423 // If first or last vertex is removed, re-sync the last/first vertex
1424 if ( vertices.last().vertex == n - 1 )
1425 {
1426 ring->moveVertex( QgsVertexId( 0, 0, 0 ), ring->vertexAt( QgsVertexId( 0, 0, ring->numPoints() - 1 ) ) );
1427 }
1428 else if ( vertices.first().vertex == 0 )
1429 {
1430 ring->moveVertex( QgsVertexId( 0, 0, ring->numPoints() - 1 ), ring->vertexAt( QgsVertexId( 0, 0, 0 ) ) );
1431 }
1432 }
1433
1434 clearCache();
1435 return true;
1436}
1437
1439{
1440 if ( !mExteriorRing )
1441 return false;
1442
1443 if ( id.part == 0 && id.ring >= 0 && id.ring < mInteriorRings.size() + 1 )
1444 {
1445 // cppcheck-suppress containerOutOfBounds
1446 QgsCurve *ring = id.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( id.ring - 1 );
1447 return ring->hasVertex( QgsVertexId( 0, 0, id.vertex ) );
1448 }
1449
1450 return false;
1451}
1452
1454{
1455 if ( mExteriorRing && mExteriorRing->hasCurvedSegments() )
1456 {
1457 return true;
1458 }
1459
1460 for ( const QgsCurve *ring : mInteriorRings )
1461 {
1462 if ( ring->hasCurvedSegments() )
1463 {
1464 return true;
1465 }
1466 }
1467 return false;
1468}
1469
1471{
1472 return toPolygon( tolerance, toleranceType );
1473}
1474
1476{
1477 if ( !mExteriorRing || vertex.ring < 0 || vertex.ring >= 1 + mInteriorRings.size() )
1478 {
1479 //makes no sense - conversion of false to double!
1480 return false;
1481 }
1482
1483 QgsCurve *ring = vertex.ring == 0 ? mExteriorRing.get() : mInteriorRings[vertex.ring - 1];
1484 return ring->vertexAngle( vertex );
1485}
1486
1487int QgsCurvePolygon::vertexCount( int /*part*/, int ring ) const
1488{
1489 return ring == 0 ? mExteriorRing->vertexCount() : mInteriorRings[ring - 1]->vertexCount();
1490}
1491
1493{
1494 return ( nullptr != mExteriorRing ) + mInteriorRings.size();
1495}
1496
1498{
1499 return ringCount() > 0 ? 1 : 0;
1500}
1501
1503{
1504 return id.ring == 0 ? mExteriorRing->vertexAt( id ) : mInteriorRings[id.ring - 1]->vertexAt( id );
1505}
1506
1508{
1509 if ( !mExteriorRing || startVertex.ring < 0 || startVertex.ring >= 1 + mInteriorRings.size() )
1510 {
1511 return 0.0;
1512 }
1513
1514 const QgsCurve *ring = startVertex.ring == 0 ? mExteriorRing.get() : mInteriorRings[startVertex.ring - 1];
1515 return ring->segmentLength( startVertex );
1516}
1517
1518bool QgsCurvePolygon::addZValue( double zValue )
1519{
1520 if ( QgsWkbTypes::hasZ( mWkbType ) )
1521 return false;
1522
1524
1525 if ( mExteriorRing )
1526 mExteriorRing->addZValue( zValue );
1527 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1528 {
1529 curve->addZValue( zValue );
1530 }
1531 clearCache();
1532 return true;
1533}
1534
1535bool QgsCurvePolygon::addMValue( double mValue )
1536{
1537 if ( QgsWkbTypes::hasM( mWkbType ) )
1538 return false;
1539
1541
1542 if ( mExteriorRing )
1543 mExteriorRing->addMValue( mValue );
1544 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1545 {
1546 curve->addMValue( mValue );
1547 }
1548 clearCache();
1549 return true;
1550}
1551
1553{
1554 if ( !is3D() )
1555 return false;
1556
1558 if ( mExteriorRing )
1559 mExteriorRing->dropZValue();
1560 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1561 {
1562 curve->dropZValue();
1563 }
1564 clearCache();
1565 return true;
1566}
1567
1569{
1570 if ( !isMeasure() )
1571 return false;
1572
1574 if ( mExteriorRing )
1575 mExteriorRing->dropMValue();
1576 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1577 {
1578 curve->dropMValue();
1579 }
1580 clearCache();
1581 return true;
1582}
1583
1585{
1586 if ( mExteriorRing )
1587 mExteriorRing->swapXy();
1588 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1589 {
1590 curve->swapXy();
1591 }
1592 clearCache();
1593}
1594
1596{
1597 return clone();
1598}
1599
1601{
1602 if ( !transformer )
1603 return false;
1604
1605 bool res = true;
1606 if ( mExteriorRing )
1607 res = mExteriorRing->transform( transformer, feedback );
1608
1609 if ( !res || ( feedback && feedback->isCanceled() ) )
1610 {
1611 clearCache();
1612 return false;
1613 }
1614
1615 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1616 {
1617 res = curve->transform( transformer );
1618
1619 if ( feedback && feedback->isCanceled() )
1620 res = false;
1621
1622 if ( !res )
1623 break;
1624 }
1625 clearCache();
1626 return res;
1627}
1628
1629void QgsCurvePolygon::filterVertices( const std::function<bool( const QgsPoint & )> &filter )
1630{
1631 if ( mExteriorRing )
1632 mExteriorRing->filterVertices( filter );
1633
1634 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1635 {
1636 curve->filterVertices( filter );
1637 }
1638 clearCache();
1639}
1640
1641void QgsCurvePolygon::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
1642{
1643 if ( mExteriorRing )
1644 mExteriorRing->transformVertices( transform );
1645
1646 for ( QgsCurve *curve : std::as_const( mInteriorRings ) )
1647 {
1648 curve->transformVertices( transform );
1649 }
1650 clearCache();
1651}
1652
1654{
1655 return 1 + mInteriorRings.count();
1656}
1657
1659{
1660 if ( index == 0 )
1661 return mExteriorRing.get();
1662 else
1663 return mInteriorRings.at( index - 1 );
1664}
1665
1667{
1668 const QgsCurvePolygon *otherPolygon = qgsgeometry_cast<const QgsCurvePolygon *>( other );
1669 if ( !otherPolygon )
1670 return -1;
1671
1672 if ( mExteriorRing && !otherPolygon->mExteriorRing )
1673 return 1;
1674 else if ( !mExteriorRing && otherPolygon->mExteriorRing )
1675 return -1;
1676 else if ( mExteriorRing && otherPolygon->mExteriorRing )
1677 {
1678 int shellComp = mExteriorRing->compareTo( otherPolygon->mExteriorRing.get() );
1679 if ( shellComp != 0 )
1680 {
1681 return shellComp;
1682 }
1683 }
1684
1685 const int nHole1 = mInteriorRings.size();
1686 const int nHole2 = otherPolygon->mInteriorRings.size();
1687 if ( nHole1 < nHole2 )
1688 {
1689 return -1;
1690 }
1691 if ( nHole1 > nHole2 )
1692 {
1693 return 1;
1694 }
1695
1696 for ( int i = 0; i < nHole1; i++ )
1697 {
1698 const int holeComp = mInteriorRings.at( i )->compareTo( otherPolygon->mInteriorRings.at( i ) );
1699 if ( holeComp != 0 )
1700 {
1701 return holeComp;
1702 }
1703 }
1704
1705 return 0;
1706}
@ CounterClockwise
Counter-clockwise direction.
Definition qgis.h:3650
@ Clockwise
Clockwise direction.
Definition qgis.h:3649
VertexType
Types of vertex.
Definition qgis.h:3281
GeoJsonProfile
GeoJson export Profile according to OGC Features and Geometries JSON - Part 1: Core https://docs....
Definition qgis.h:5078
@ Legacy
Legacy GeoJson profile used in QGIS prior to 4.2, which included some non-standard extensions and dev...
Definition qgis.h:5079
@ Rfc7946
GeoJson profile compliant with RFC7946 standard "http://www.opengis.net/def/profile/OGC/0/rfc7946".
Definition qgis.h:5080
@ JsonFg
GeoJson profile from OGC Features and Geometries JSON Part 1: core "http://www.opengis....
Definition qgis.h:5081
@ JsonFgPlus
GeoJson profile from OGC Features and Geometries JSON Part 1: core "http://www.opengis....
Definition qgis.h:5082
@ Polygon
Polygons.
Definition qgis.h:382
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ CompoundCurve
CompoundCurve.
Definition qgis.h:305
@ LineString
LineString.
Definition qgis.h:297
@ Polygon
Polygon.
Definition qgis.h:298
@ CircularString
CircularString.
Definition qgis.h:304
@ CurvePolygon
CurvePolygon.
Definition qgis.h:306
TransformDirection
Indicates the direction (forward or inverse) of a transform.
Definition qgis.h:2862
An abstract base class for classes which transform geometries by transforming input points to output ...
virtual bool addZValue(double zValue=0)=0
Adds a z-dimension to the geometry, initialized to a preset value.
virtual bool moveVertex(QgsVertexId position, const QgsPoint &newPos)=0
Moves a vertex within the geometry.
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle.
virtual double vertexAngle(QgsVertexId vertex) const =0
Returns approximate angle at a vertex.
virtual bool dropMValue()=0
Drops any measure values which exist in the geometry.
QgsVertexIterator vertices() const
Returns a read-only, Java-style iterator for traversal of vertices of all the geometry,...
bool isMeasure() const
Returns true if the geometry contains m values.
QFlags< WkbFlag > WkbFlags
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
AxisOrder
Axis order for GML generation.
QString wktTypeStr() const
Returns the WKT type string of the geometry.
virtual bool deleteVertices(const QSet< QgsVertexId > &positions)=0
Deletes vertices within the geometry.
QgsAbstractGeometry & operator=(const QgsAbstractGeometry &geom)
virtual bool addMValue(double mValue=0)=0
Adds a measure to the geometry, initialized to a preset value.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
virtual bool insertVertex(QgsVertexId position, const QgsPoint &vertex)=0
Inserts a vertex into the geometry.
void setZMTypeFromSubGeometry(const QgsAbstractGeometry *subggeom, Qgis::WkbType baseGeomType)
Updates the geometry type based on whether sub geometries contain z or m values.
virtual bool boundingBoxIntersects(const QgsRectangle &rectangle) const
Returns true if the bounding box of this geometry intersects with a rectangle.
virtual bool deleteVertex(QgsVertexId position)=0
Deletes a vertex within the geometry.
virtual bool dropZValue()=0
Drops any z-dimensions which exist in the geometry.
virtual double segmentLength(QgsVertexId startVertex) const =0
Returns the length of the segment of the geometry which begins at startVertex.
QgsAbstractGeometry()=default
QgsGeometryConstPartIterator parts() const
Returns Java-style iterator for traversal of parts of the geometry.
static endian_t endian()
Returns whether this machine uses big or little endian.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
Circular string geometry type.
Compound curve geometry type.
void addVertex(const QgsPoint &pt)
Adds a vertex to the end of the geometry.
A const WKB pointer.
Definition qgswkbptr.h:211
Qgis::WkbType readHeader() const
readHeader
Definition qgswkbptr.cpp:60
Handles coordinate transforms between two coordinate systems.
QByteArray asWkb(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns a WKB representation of the geometry.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
QgsCoordinateSequence coordinateSequence() const override
Retrieves the sequence of geometries, rings and nodes.
int wkbSize(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns the length of the QByteArray returned by asWkb().
bool moveVertex(QgsVertexId position, const QgsPoint &newPos) override
Moves a vertex within the geometry.
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
QDomElement asGml2(QDomDocument &doc, int precision=17, const QString &ns="gml", QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const override
Returns a GML2 representation of the geometry.
double vertexAngle(QgsVertexId vertex) const override
Returns approximate rotation angle for a vertex.
bool hasCurvedSegments() const override
Returns true if the geometry contains curved segments.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
QgsAbstractGeometry * boundary() const override
Returns the closure of the combinatorial boundary of the geometry (ie the topological boundary of the...
QgsCurvePolygon * createEmptyWithSameType() const override
Creates a new geometry with the same class and same WKB type as the original and transfers ownership.
bool hasVertex(QgsVertexId position) const override
Returns true if the geometry contains a vertex matching the given position.
QPainterPath asQPainterPath() const override
Returns the geometry represented as a QPainterPath.
void swapXy() override
Swaps the x and y coordinates from the geometry.
bool isEmpty() const override
Returns true if the geometry is empty.
int vertexCount(int part=0, int ring=0) const override
Returns the number of vertices of which this geometry is built.
virtual QgsPolygon * toPolygon(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const
Returns a new polygon geometry corresponding to a segmentized approximation of the curve.
QVector< QgsCurve * > mInteriorRings
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
void normalize() final
Reorganizes the geometry into a normalized form (or "canonical" form).
void removeInteriorRings(double minimumAllowedArea=-1)
Removes the interior rings from the polygon.
QgsCurvePolygon * clone() const override
Clones the geometry by performing a deep copy.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
double area() const override
Returns the planar, 2-dimensional area of the geometry.
QgsCurvePolygon * toCurveType() const override
Returns the geometry converted to the more generic curve type.
void forceRHR()
Forces the geometry to respect the Right-Hand-Rule, in which the area that is bounded by the polygon ...
QgsCurvePolygon * simplifyByDistance(double tolerance) const override
Simplifies the geometry by applying the Douglas Peucker simplification by distance algorithm.
QString asKml(int precision=17) const override
Returns a KML representation of the geometry.
void clear() override
Clears the geometry, ie reset it to a null geometry.
QgsBox3D calculateBoundingBox3D() const override
Calculates the minimal 3D bounding box for the geometry.
virtual void setExteriorRing(QgsCurve *ring)
Sets the exterior ring of the polygon.
double closestSegment(const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf=nullptr, double epsilon=4 *std::numeric_limits< double >::epsilon()) const override
Searches for the closest segment of the geometry to a given point.
int partCount() const override
Returns count of parts contained in the geometry.
int nCoordinates() const override
Returns the number of nodes contained in the geometry.
void filterVertices(const std::function< bool(const QgsPoint &) > &filter) override
Filters the vertices from the geometry in place, removing any which do not return true for the filter...
void adjacentVertices(QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex) const override
Returns the vertices adjacent to a specified vertex within a geometry.
void draw(QPainter &p) const override
Draws the geometry using the specified QPainter.
double perimeter() const override
Returns the planar, 2-dimensional perimeter of the geometry.
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
QgsCurvePolygon & operator=(const QgsCurvePolygon &p)
void forceCounterClockwise()
Forces the polygon to respect the exterior ring is counter-clockwise, interior rings are clockwise co...
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
double area3D() const override
Returns the 3-dimensional surface area of the geometry.
void forceClockwise()
Forces the polygon to respect the exterior ring is clockwise, interior rings are counter-clockwise co...
double roundness() const
Returns the roundness of the curve polygon.
virtual void addInteriorRing(QgsCurve *ring)
Adds an interior ring to the geometry (takes ownership).
bool boundingBoxIntersects(const QgsBox3D &box3d) const override
Returns true if the bounding box of this geometry intersects with a box3d.
~QgsCurvePolygon() override
int dimension() const override
Returns the inherent dimension of the geometry.
bool nextVertex(QgsVertexId &id, QgsPoint &vertex) const override
Returns next vertex id and coordinates.
QgsPoint vertexAt(QgsVertexId id) const override
Returns the point corresponding to a specified vertex id.
virtual QgsPolygon * surfaceToPolygon() const
Gets a polygon representation of this surface.
int ringCount(int part=0) const override
Returns the number of rings of which this geometry is built.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into the geometry.
bool fromWkt(const QString &wkt) override
Sets the geometry from a WKT string.
void removeInvalidRings()
Removes any interior rings which are not valid from the polygon.
QgsAbstractGeometry * segmentize(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a geometry without curves.
int vertexNumberFromVertexId(QgsVertexId id) const override
Returns the vertex number corresponding to a vertex id.
QString geometryType() const override
Returns a unique string representing the geometry type.
void transformVertices(const std::function< QgsPoint(const QgsPoint &) > &transform) override
Transforms the vertices from the geometry in place, applying the transform function to every vertex.
int childCount() const override
Returns number of child geometries (for geometries with child geometries) or child points (for geomet...
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
QDomElement asGml3(QDomDocument &doc, int precision=17, const QString &ns="gml", QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const override
Returns a GML3 representation of the geometry.
bool removeDuplicateNodes(double epsilon=4 *std::numeric_limits< double >::epsilon(), bool useZValues=false) override
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a degenerat...
QgsCurvePolygon * snappedToGrid(double hSpacing, double vSpacing, double dSpacing=0, double mSpacing=0, bool removeRedundantPoints=false) const override
Makes a new geometry with all the points or vertices snapped to the closest point of the grid.
bool dropMValue() override
Drops any measure values which exist in the geometry.
bool deleteVertices(const QSet< QgsVertexId > &positions) override
Deletes vertices within the geometry.
QgsAbstractGeometry * childGeometry(int index) const override
Returns pointer to child geometry (for geometries with child geometries - i.e.
void setInteriorRings(const QVector< QgsCurve * > &rings)
Sets all interior rings (takes ownership).
bool removeInteriorRing(int ringIndex)
Removes an interior ring from the polygon.
std::unique_ptr< QgsCurve > mExteriorRing
json asJsonObject(int precision=17, Qgis::GeoJsonProfile profile=Qgis::GeoJsonProfile::Legacy) const override
Returns a json object representation of the geometry with the given precision and profile.
int compareToSameClass(const QgsAbstractGeometry *other) const final
Compares to an other geometry of the same class, and returns a integer for sorting of the two geometr...
double segmentLength(QgsVertexId startVertex) const override
Returns the length of the segment of the geometry which begins at startVertex.
void transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection d=Qgis::TransformDirection::Forward, bool transformZ=false) override
Transforms the geometry using a coordinate transform.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
virtual int numPoints() const =0
Returns the number of points in the curve.
QgsPoint vertexAt(QgsVertexId id) const override
Returns the point corresponding to a specified vertex id.
Definition qgscurve.cpp:198
QgsCurve * clone() const override=0
Clones the geometry by performing a deep copy.
bool hasVertex(QgsVertexId position) const override
Returns true if the geometry contains a vertex matching the given position.
Definition qgscurve.cpp:266
bool nextVertex(QgsVertexId &id, QgsPoint &vertex) const override
Returns next vertex id and coordinates.
Definition qgscurve.cpp:87
virtual QgsCurve * reversed() const =0
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void reserve(int size)
Attempts to allocate memory for at least size geometries.
static QStringList wktGetChildBlocks(const QString &wkt, const QString &defaultType=QString())
Parses a WKT string and returns of list of blocks contained in the WKT.
static QPair< Qgis::WkbType, QString > wktReadBlock(const QString &wkt)
Parses a WKT block of the format "TYPE( contents )" and returns a pair of geometry type to contents (...
static double closestSegmentFromComponents(T &container, ComponentType ctype, const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon)
static json pointsToJson(const QgsPointSequence &points, int precision, Qgis::GeoJsonProfile profile)
Returns coordinates as json object.
Line string geometry type, with support for z-dimension and m-values.
Multi curve geometry collection.
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
Polygon geometry type.
Definition qgspolygon.h:37
A rectangle specified with double values.
Surface geometry type.
Definition qgssurface.h:34
QgsBox3D mBoundingBox
Definition qgssurface.h:99
void clearCache() const override
Clears any cached parameters associated with the geometry, e.g., bounding boxes.
QString mValidityFailureReason
Definition qgssurface.h:101
bool mHasCachedValidity
Definition qgssurface.h:100
WKB pointer handler.
Definition qgswkbptr.h:47
static Qgis::WkbType dropM(Qgis::WkbType type)
Drops the m dimension (if present) for a WKB type and returns the new type.
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static Qgis::WkbType dropZ(Qgis::WkbType type)
Drops the z dimension (if present) for a WKB type and returns the new type.
static Qgis::WkbType addM(Qgis::WkbType type)
Adds the m dimension to a WKB type and returns the new type.
static Qgis::WkbType addZ(Qgis::WkbType type)
Adds the z dimension to a WKB type and returns the new type.
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
#define BUILTIN_UNREACHABLE
Definition qgis.h:8191
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7557
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsRingSequence > QgsCoordinateSequence
QVector< QgsPointSequence > QgsRingSequence
QVector< QgsPoint > QgsPointSequence
void ringAdjacentVertices(const QgsCurve *curve, QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex)
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:35
int vertex
Vertex number.
int part
Part number.
Definition qgsvertexid.h:96
int ring
Ring number.
Definition qgsvertexid.h:99