QGIS API Documentation 4.3.0-Master (f865f0c58a6)
Loading...
Searching...
No Matches
qgscompoundcurve.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscompoundcurve.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 "qgscompoundcurve.h"
19
20#include <memory>
21#include <nlohmann/json.hpp>
22
23#include "qgsapplication.h"
24#include "qgscircularstring.h"
25#include "qgsfeedback.h"
26#include "qgsgeometryutils.h"
27#include "qgslinestring.h"
28#include "qgsmessagelog.h"
29#include "qgswkbptr.h"
30
31#include <QJsonObject>
32#include <QPainter>
33#include <QPainterPath>
34#include <QString>
35
36using namespace Qt::StringLiterals;
37
42
47
49{
50 auto result = std::make_unique< QgsCompoundCurve >();
51 result->mWkbType = mWkbType;
52 return result.release();
53}
54
56{
58 if ( !otherCurve )
59 return -1;
60
61 int i = 0;
62 int j = 0;
63 while ( i < mCurves.size() && j < otherCurve->mCurves.size() )
64 {
65 const QgsAbstractGeometry *aGeom = mCurves[i];
66 const QgsAbstractGeometry *bGeom = otherCurve->mCurves[j];
67 const int comparison = aGeom->compareTo( bGeom );
68 if ( comparison != 0 )
69 {
70 return comparison;
71 }
72 i++;
73 j++;
74 }
75 if ( i < mCurves.size() )
76 {
77 return 1;
78 }
79 if ( j < otherCurve->mCurves.size() )
80 {
81 return -1;
82 }
83 return 0;
84}
85
87{
88 return u"CompoundCurve"_s;
89}
90
92{
93 return 1;
94}
95
97 : QgsCurve( curve )
98{
99 mWkbType = curve.wkbType();
100 mCurves.reserve( curve.mCurves.size() );
101 for ( const QgsCurve *c : curve.mCurves )
102 {
103 mCurves.append( c->clone() );
104 }
105}
106
107// cppcheck-suppress operatorEqVarError
109{
110 if ( &curve != this )
111 {
112 QgsCurve::operator=( curve );
113 for ( const QgsCurve *c : curve.mCurves )
114 {
115 mCurves.append( c->clone() );
116 }
117 }
118 return *this;
119}
120
122{
123 return new QgsCompoundCurve( *this );
124}
125
127{
129 qDeleteAll( mCurves );
130 mCurves.clear();
131 clearCache();
132}
133
135{
136 if ( mCurves.empty() )
137 {
138 return QgsBox3D();
139 }
140
141 QgsBox3D bbox = mCurves.at( 0 )->boundingBox3D();
142 for ( int i = 1; i < mCurves.size(); ++i )
143 {
144 QgsBox3D curveBox = mCurves.at( i )->boundingBox3D();
145 bbox.combineWith( curveBox );
146 }
147 return bbox;
148}
149
151{
152 const int size = numPoints();
153 if ( index < 1 || index >= size - 1 )
154 return;
155
156 auto [p1, p2] = splitCurveAtVertex( index );
157
158 mCurves.clear();
160 {
161 // take the curves from the second part and make them our first lot of curves
162 mCurves = std::move( curve2->mCurves );
163 }
165 {
166 // take the curves from the first part and append them to our curves
167 mCurves.append( curve1->mCurves );
168 curve1->mCurves.clear();
169 }
170}
171
173{
174 clear();
175 if ( !wkbPtr )
176 {
177 return false;
178 }
179
180 Qgis::WkbType type = wkbPtr.readHeader();
182 {
183 return false;
184 }
185 mWkbType = type;
186
187 int nCurves;
188 wkbPtr >> nCurves;
189 QgsCurve *currentCurve = nullptr;
190 for ( int i = 0; i < nCurves; ++i )
191 {
192 Qgis::WkbType curveType = wkbPtr.readHeader();
193 wkbPtr -= 1 + sizeof( int );
195 {
196 currentCurve = new QgsLineString();
197 }
198 else if ( QgsWkbTypes::flatType( curveType ) == Qgis::WkbType::CircularString )
199 {
200 currentCurve = new QgsCircularString();
201 }
202 else
203 {
204 return false;
205 }
206 currentCurve->fromWkb( wkbPtr ); // also updates wkbPtr
207 mCurves.append( currentCurve );
208 }
209 return true;
210}
211
212bool QgsCompoundCurve::fromWkt( const QString &wkt )
213{
214 clear();
215
216 QPair<Qgis::WkbType, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
217
219 return false;
220 mWkbType = parts.first;
221
222 QString secondWithoutParentheses = parts.second;
223 secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
224 if ( ( parts.second.compare( "EMPTY"_L1, Qt::CaseInsensitive ) == 0 ) || secondWithoutParentheses.isEmpty() )
225 return true;
226
227 QString defaultChildWkbType = u"LineString%1%2"_s.arg( is3D() ? u"Z"_s : QString(), isMeasure() ? u"M"_s : QString() );
228
229 const QStringList blocks = QgsGeometryUtils::wktGetChildBlocks( parts.second, defaultChildWkbType );
230 for ( const QString &childWkt : blocks )
231 {
232 QPair<Qgis::WkbType, QString> childParts = QgsGeometryUtils::wktReadBlock( childWkt );
233
234 if ( QgsWkbTypes::flatType( childParts.first ) == Qgis::WkbType::LineString )
235 mCurves.append( new QgsLineString() );
236 else if ( QgsWkbTypes::flatType( childParts.first ) == Qgis::WkbType::CircularString )
237 mCurves.append( new QgsCircularString() );
238 else
239 {
240 clear();
241 return false;
242 }
243 if ( !mCurves.back()->fromWkt( childWkt ) )
244 {
245 clear();
246 return false;
247 }
248 }
249
250 //scan through curves and check if dimensionality of curves is different to compound curve.
251 //if so, update the type dimensionality of the compound curve to match
252 bool hasZ = false;
253 bool hasM = false;
254 for ( const QgsCurve *curve : std::as_const( mCurves ) )
255 {
256 hasZ = hasZ || curve->is3D();
257 hasM = hasM || curve->isMeasure();
258 if ( hasZ && hasM )
259 break;
260 }
261 if ( hasZ )
262 addZValue( 0 );
263 if ( hasM )
264 addMValue( 0 );
265
266 return true;
267}
268
270{
271 int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
272 for ( const QgsCurve *curve : mCurves )
273 {
274 binarySize += curve->wkbSize( flags );
275 }
276 return binarySize;
277}
278
279QByteArray QgsCompoundCurve::asWkb( WkbFlags flags ) const
280{
281 QByteArray wkbArray;
282 wkbArray.resize( QgsCompoundCurve::wkbSize( flags ) );
283 QgsWkbPtr wkb( wkbArray );
284 wkb << static_cast<char>( QgsApplication::endian() );
285 wkb << static_cast<quint32>( wkbType() );
286 wkb << static_cast<quint32>( mCurves.size() );
287 for ( const QgsCurve *curve : mCurves )
288 {
289 wkb << curve->asWkb( flags );
290 }
291 return wkbArray;
292}
293
294QString QgsCompoundCurve::asWkt( int precision ) const
295{
296 QString wkt = wktTypeStr();
297 if ( isEmpty() )
298 wkt += " EMPTY"_L1;
299 else
300 {
301 wkt += " ("_L1;
302 for ( const QgsCurve *curve : mCurves )
303 {
304 QString childWkt = curve->asWkt( precision );
306 {
307 // Type names of linear geometries are omitted
308 childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
309 }
310 wkt += childWkt + ',';
311 }
312 if ( wkt.endsWith( ',' ) )
313 {
314 wkt.chop( 1 );
315 }
316 wkt += ')';
317 }
318 return wkt;
319}
320
321QDomElement QgsCompoundCurve::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
322{
323 // GML2 does not support curves
324 std::unique_ptr< QgsLineString > line( curveToLine() );
325 QDomElement gml = line->asGml2( doc, precision, ns, axisOrder );
326 return gml;
327}
328
329QDomElement QgsCompoundCurve::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
330{
331 QDomElement compoundCurveElem = doc.createElementNS( ns, u"CompositeCurve"_s );
332
333 if ( isEmpty() )
334 return compoundCurveElem;
335
336 for ( const QgsCurve *curve : mCurves )
337 {
338 QDomElement curveMemberElem = doc.createElementNS( ns, u"curveMember"_s );
339 QDomElement curveElem = curve->asGml3( doc, precision, ns, axisOrder );
340 curveMemberElem.appendChild( curveElem );
341 compoundCurveElem.appendChild( curveMemberElem );
342 }
343
344 return compoundCurveElem;
345}
346
347json QgsCompoundCurve::asJsonObject( int precision, Qgis::GeoJsonProfile profile ) const
348{
349 switch ( profile )
350 {
353 {
354 std::unique_ptr< QgsLineString > line( curveToLine() );
355 return line->asJsonObject( precision );
356 }
359 {
360 json geometries = json::array();
361 for ( const QgsCurve *curve : mCurves )
362 {
363 geometries.push_back( curve->asJsonObject( precision, profile ) );
364 }
365 return { { "type", "CompoundCurve" }, { "geometries", geometries } };
366 }
367 }
369}
370
372{
373 double length = 0;
374 for ( const QgsCurve *curve : mCurves )
375 {
376 length += curve->length();
377 }
378 return length;
379}
380
382{
383 if ( mCurves.empty() )
384 {
385 return QgsPoint();
386 }
387 return mCurves.at( 0 )->startPoint();
388}
389
391{
392 if ( mCurves.empty() )
393 {
394 return QgsPoint();
395 }
396 return mCurves.at( mCurves.size() - 1 )->endPoint();
397}
398
400{
401 pts.clear();
402 if ( mCurves.empty() )
403 {
404 return;
405 }
406
407 mCurves[0]->points( pts );
408 for ( int i = 1; i < mCurves.size(); ++i )
409 {
410 QgsPointSequence pList;
411 mCurves[i]->points( pList );
412 pList.removeFirst(); //first vertex already added in previous line
413 pts.append( pList );
414 }
415}
416
418{
419 int nPoints = 0;
420 int nCurves = mCurves.size();
421 if ( nCurves < 1 )
422 {
423 return 0;
424 }
425
426 for ( int i = 0; i < nCurves; ++i )
427 {
428 nPoints += mCurves.at( i )->numPoints() - 1; //last vertex is equal to first of next section
429 }
430 nPoints += 1; //last vertex was removed above
431 return nPoints;
432}
433
435{
436 if ( mCurves.isEmpty() )
437 return true;
438
439 for ( QgsCurve *curve : mCurves )
440 {
441 if ( !curve->isEmpty() )
442 return false;
443 }
444 return true;
445}
446
448{
449 if ( mCurves.isEmpty() )
450 return true;
451
452 for ( int i = 0; i < mCurves.size(); ++i )
453 {
454 if ( !mCurves[i]->isValid( error, flags ) )
455 {
456 error = QObject::tr( "Curve[%1]: %2" ).arg( i + 1 ).arg( error );
457 return false;
458 }
459 }
460 return QgsCurve::isValid( error, flags );
461}
462
463int QgsCompoundCurve::indexOf( const QgsPoint &point ) const
464{
465 int curveStart = 0;
466 for ( const QgsCurve *curve : mCurves )
467 {
468 const int curveIndex = curve->indexOf( point );
469 if ( curveIndex >= 0 )
470 return curveStart + curveIndex;
471 // subtract 1 here, because the next curve will start with the same
472 // vertex as this curve ended at
473 curveStart += curve->numPoints() - 1;
474 }
475 return -1;
476}
477
479{
480 QgsLineString *line = new QgsLineString();
481 std::unique_ptr< QgsLineString > currentLine;
482 for ( const QgsCurve *curve : mCurves )
483 {
484 currentLine.reset( curve->curveToLine( tolerance, toleranceType ) );
485 line->append( currentLine.get() );
486 }
487 return line;
488}
489
490QgsCompoundCurve *QgsCompoundCurve::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const
491{
492 std::unique_ptr<QgsCompoundCurve> result( createEmptyWithSameType() );
493
494 for ( QgsCurve *curve : mCurves )
495 {
496 std::unique_ptr<QgsCurve> gridified( static_cast< QgsCurve * >( curve->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) );
497 if ( gridified )
498 {
499 result->mCurves.append( gridified.release() );
500 }
501 }
502
503 if ( result->mCurves.empty() )
504 return nullptr;
505 else
506 return result.release();
507}
508
510{
511 std::unique_ptr< QgsLineString > line( curveToLine() );
512 return line->simplifyByDistance( tolerance );
513}
514
515bool QgsCompoundCurve::removeDuplicateNodes( double epsilon, bool useZValues )
516{
517 bool result = false;
518 const QVector< QgsCurve * > curves = mCurves;
519 int i = 0;
520 QgsPoint lastEnd;
521 for ( QgsCurve *curve : curves )
522 {
523 result = curve->removeDuplicateNodes( epsilon, useZValues ) || result;
524 if ( curve->numPoints() == 0 || qgsDoubleNear( curve->length(), 0.0, epsilon ) )
525 {
526 // empty curve, remove it
527 delete mCurves.takeAt( i );
528 result = true;
529 }
530 else
531 {
532 // ensure this line starts exactly where previous line ended
533 if ( i > 0 )
534 {
535 curve->moveVertex( QgsVertexId( -1, -1, 0 ), lastEnd );
536 }
537 lastEnd = curve->vertexAt( QgsVertexId( -1, -1, curve->numPoints() - 1 ) );
538 }
539 i++;
540 }
541 return result;
542}
543
545{
546 if ( mCurves.empty() )
547 return false;
548
549 // if we already have the bounding box calculated, then this check is trivial!
550 if ( !mBoundingBox.isNull() )
551 {
552 return mBoundingBox.intersects( box3d );
553 }
554
555 // otherwise loop through each member curve and test the bounding box intersection.
556 // This gives us a chance to use optimisations which may be present on the individual
557 // curve subclasses, and at worst it will cause a calculation of the bounding box
558 // of each individual member curve which we would have to do anyway... (and these
559 // bounding boxes are cached, so would be reused without additional expense)
560 for ( const QgsCurve *curve : mCurves )
561 {
562 if ( curve->boundingBoxIntersects( box3d ) )
563 return true;
564 }
565
566 // even if we don't intersect the bounding box of any member curves, we may still intersect the
567 // bounding box of the overall compound curve.
568 // so here we fall back to the non-optimised base class check which has to first calculate
569 // the overall bounding box of the compound curve..
571}
572
574{
575 if ( mCurves.size() == 1 )
576 return mCurves.at( 0 );
577 else
578 return this;
579}
580
582{
583 if ( i < 0 || i >= mCurves.size() )
584 {
585 return nullptr;
586 }
587 return mCurves.at( i );
588}
589
590void QgsCompoundCurve::addCurve( QgsCurve *c, const bool extendPrevious )
591{
592 if ( !c )
593 return;
594
595 if ( mCurves.empty() )
596 {
598 }
599
600 if ( QgsWkbTypes::hasZ( mWkbType ) && !QgsWkbTypes::hasZ( c->wkbType() ) )
601 {
602 c->addZValue();
603 }
604 else if ( !QgsWkbTypes::hasZ( mWkbType ) && QgsWkbTypes::hasZ( c->wkbType() ) )
605 {
606 c->dropZValue();
607 }
608 if ( QgsWkbTypes::hasM( mWkbType ) && !QgsWkbTypes::hasM( c->wkbType() ) )
609 {
610 c->addMValue();
611 }
612 else if ( !QgsWkbTypes::hasM( mWkbType ) && QgsWkbTypes::hasM( c->wkbType() ) )
613 {
614 c->dropMValue();
615 }
616
617 QgsLineString *previousLineString = !mCurves.empty() ? qgsgeometry_cast< QgsLineString * >( mCurves.constLast() ) : nullptr;
619 const bool canExtendPrevious = extendPrevious && previousLineString && newLineString;
620 if ( canExtendPrevious )
621 {
622 previousLineString->append( newLineString );
623 // we are taking ownership, so delete the input curve
624 delete c;
625 c = nullptr;
626 }
627 else
628 {
629 mCurves.append( c );
630 }
631
632 clearCache();
633}
634
636{
637 if ( i < 0 || i >= mCurves.size() )
638 {
639 return;
640 }
641
642 delete mCurves.takeAt( i );
643 clearCache();
644}
645
647{
648 if ( mCurves.isEmpty() || mWkbType == Qgis::WkbType::Unknown )
649 {
651 }
652
653 //is last curve QgsLineString
654 QgsCurve *lastCurve = nullptr;
655 if ( !mCurves.isEmpty() )
656 {
657 lastCurve = mCurves.at( mCurves.size() - 1 );
658 }
659
660 QgsLineString *line = nullptr;
661 if ( !lastCurve || QgsWkbTypes::flatType( lastCurve->wkbType() ) != Qgis::WkbType::LineString )
662 {
663 line = new QgsLineString();
664 mCurves.append( line );
665 if ( lastCurve )
666 {
667 line->addVertex( lastCurve->endPoint() );
668 }
669 lastCurve = line;
670 }
671 else //create new QgsLineString* with point in it
672 {
673 line = static_cast<QgsLineString *>( lastCurve );
674 }
675 line->addVertex( pt );
676 clearCache();
677}
678
680{
681 QgsCurve *lastCurve = nullptr;
682 QVector< QgsCurve * > newCurves;
683 newCurves.reserve( mCurves.size() );
684 for ( QgsCurve *curve : std::as_const( mCurves ) )
685 {
686 if ( lastCurve && lastCurve->wkbType() == curve->wkbType() )
687 {
688 if ( QgsLineString *ls = qgsgeometry_cast< QgsLineString * >( lastCurve ) )
689 {
690 ls->append( qgsgeometry_cast< QgsLineString * >( curve ) );
691 delete curve;
692 }
694 {
695 cs->append( qgsgeometry_cast< QgsCircularString * >( curve ) );
696 delete curve;
697 }
698 }
699 else
700 {
701 lastCurve = curve;
702 newCurves << curve;
703 }
704 }
705 mCurves = newCurves;
706}
707
708void QgsCompoundCurve::draw( QPainter &p ) const
709{
710 for ( const QgsCurve *curve : mCurves )
711 {
712 curve->draw( p );
713 }
714}
715
717{
718 for ( QgsCurve *curve : std::as_const( mCurves ) )
719 {
720 curve->transform( ct, d, transformZ );
721 }
722 clearCache();
723}
724
725void QgsCompoundCurve::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
726{
727 for ( QgsCurve *curve : std::as_const( mCurves ) )
728 {
729 curve->transform( t, zTranslate, zScale, mTranslate, mScale );
730 }
731 clearCache();
732}
733
734void QgsCompoundCurve::addToPainterPath( QPainterPath &path ) const
735{
736 QPainterPath pp;
737
738 for ( const QgsCurve *curve : mCurves )
739 {
740 if ( curve != mCurves.at( 0 ) && pp.currentPosition() != curve->startPoint().toQPointF() )
741 {
742 pp.lineTo( curve->startPoint().toQPointF() );
743 }
744 curve->addToPainterPath( pp );
745 }
746 path.addPath( pp );
747}
748
749void QgsCompoundCurve::drawAsPolygon( QPainter &p ) const
750{
751 QPainterPath pp;
752 for ( const QgsCurve *curve : mCurves )
753 {
754 if ( curve != mCurves.at( 0 ) && pp.currentPosition() != curve->startPoint().toQPointF() )
755 {
756 pp.lineTo( curve->startPoint().toQPointF() );
757 }
758 curve->addToPainterPath( pp );
759 }
760 p.drawPath( pp );
761}
762
764{
765 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
766 if ( curveIds.empty() )
767 {
768 return false;
769 }
770 int curveId = curveIds.at( 0 ).first;
771 if ( curveId >= mCurves.size() )
772 {
773 return false;
774 }
775
776 bool success = mCurves.at( curveId )->insertVertex( curveIds.at( 0 ).second, vertex );
777 if ( success )
778 {
779 clearCache(); //bbox changed
780 }
781 return success;
782}
783
784bool QgsCompoundCurve::moveVertex( QgsVertexId position, const QgsPoint &newPos )
785{
786 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
787 QVector< QPair<int, QgsVertexId> >::const_iterator idIt = curveIds.constBegin();
788 for ( ; idIt != curveIds.constEnd(); ++idIt )
789 {
790 mCurves.at( idIt->first )->moveVertex( idIt->second, newPos );
791 }
792
793 bool success = !curveIds.isEmpty();
794 if ( success )
795 {
796 clearCache(); //bbox changed
797 }
798 return success;
799}
800
802{
803 const QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
804 if ( curveIds.isEmpty() )
805 return false;
806
807 const int curveId = curveIds.at( 0 ).first;
808 QgsCurve *curve = mCurves.at( curveId );
809 const QgsVertexId subVertexId = curveIds.at( 0 ).second;
810
811 // We are on a vertex that belongs to one curve only
812 if ( curveIds.size() == 1 )
813 {
814 const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString *>( curve );
815 // If the vertex to delete is the middle vertex of a CircularString, we transform
816 // this CircularString into a LineString without the middle vertex
817 if ( circularString && subVertexId.vertex % 2 == 1 )
818 {
819 {
821 circularString->points( points );
822
823 removeCurve( curveId );
824
825 if ( subVertexId.vertex < points.length() - 2 )
826 {
827 auto curveC = std::make_unique<QgsCircularString>();
828 curveC->setPoints( points.mid( subVertexId.vertex + 1 ) );
829 mCurves.insert( curveId, curveC.release() );
830 }
831
832 const QgsPointSequence partB = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex + 1];
833 auto curveB = std::make_unique<QgsLineString>();
834 curveB->setPoints( partB );
835 mCurves.insert( curveId, curveB.release() );
836 curve = mCurves.at( curveId );
837
838 if ( subVertexId.vertex > 1 )
839 {
840 auto curveA = std::make_unique<QgsCircularString>();
841 curveA->setPoints( points.mid( 0, subVertexId.vertex ) );
842 mCurves.insert( curveId, curveA.release() );
843 }
844 }
845 }
846 else if ( !curve->deleteVertex( subVertexId ) )
847 {
848 clearCache(); //bbox may have changed
849 return false;
850 }
851 if ( curve->numPoints() == 0 )
852 {
853 removeCurve( curveId );
854 }
855 }
856 // We are on a vertex that belongs to two curves
857 else if ( curveIds.size() == 2 )
858 {
859 const int nextCurveId = curveIds.at( 1 ).first;
860 QgsCurve *nextCurve = mCurves.at( nextCurveId );
861 const QgsVertexId nextSubVertexId = curveIds.at( 1 ).second;
862
863 Q_ASSERT( nextCurveId == curveId + 1 );
864 Q_ASSERT( subVertexId.vertex == curve->numPoints() - 1 );
865 Q_ASSERT( nextSubVertexId.vertex == 0 );
866
867 // globals start and end points
868 const QgsPoint startPoint = curve->startPoint();
869 const QgsPoint endPoint = nextCurve->endPoint();
870
871 // delete the vertex on first curve
872 if ( !curve->deleteVertex( subVertexId ) )
873 {
874 clearCache(); //bbox may have changed
875 return false;
876 }
877
878 // delete the vertex on second curve
879 if ( !nextCurve->deleteVertex( nextSubVertexId ) )
880 {
881 clearCache(); //bbox may have changed
882 return false;
883 }
884
885 // if first curve is now empty and second is not then
886 // create a LineString to link from the global start point to the
887 // new start of the second curve and delete the first curve
888 if ( curve->numPoints() == 0 && nextCurve->numPoints() != 0 )
889 {
890 QgsPoint startPointOfSecond = nextCurve->startPoint();
891 removeCurve( curveId );
892 QgsLineString *line = new QgsLineString();
893 line->insertVertex( QgsVertexId( 0, 0, 0 ), startPoint );
894 line->insertVertex( QgsVertexId( 0, 0, 1 ), startPointOfSecond );
895 mCurves.insert( curveId, line );
896 }
897 // else, if the first curve is not empty and the second is
898 // then create a LineString to link from the new end of the first curve to the
899 // global end point and delete the first curve
900 else if ( curve->numPoints() != 0 && nextCurve->numPoints() == 0 )
901 {
902 QgsPoint endPointOfFirst = curve->endPoint();
903 removeCurve( nextCurveId );
904 QgsLineString *line = new QgsLineString();
905 line->insertVertex( QgsVertexId( 0, 0, 0 ), endPointOfFirst );
906 line->insertVertex( QgsVertexId( 0, 0, 1 ), endPoint );
907 mCurves.insert( nextCurveId, line );
908 }
909 // else, if both curves are empty then
910 // remove both curves and create a LineString to link
911 // the curves before and the curves after the whole geometry
912 else if ( curve->numPoints() == 0 && nextCurve->numPoints() == 0 )
913 {
914 removeCurve( nextCurveId );
915 removeCurve( curveId );
916 QgsLineString *line = new QgsLineString();
917 line->insertVertex( QgsVertexId( 0, 0, 0 ), startPoint );
918 line->insertVertex( QgsVertexId( 0, 0, 1 ), endPoint );
919 mCurves.insert( curveId, line );
920 }
921 // else, both curves still have vertices, create a LineString to link
922 // the curves if needed
923 else
924 {
925 QgsPoint endPointOfFirst = curve->endPoint();
926 QgsPoint startPointOfSecond = nextCurve->startPoint();
927 if ( endPointOfFirst != startPointOfSecond )
928 {
929 QgsLineString *line = new QgsLineString();
930 line->insertVertex( QgsVertexId( 0, 0, 0 ), endPointOfFirst );
931 line->insertVertex( QgsVertexId( 0, 0, 1 ), startPointOfSecond );
932 mCurves.insert( nextCurveId, line );
933 }
934 }
935 condenseCurves(); // We merge consecutive LineStrings and CircularStrings
936 }
937
938 bool success = !curveIds.isEmpty();
939 if ( success )
940 clearCache(); //bbox changed
941 return success;
942}
943
944bool QgsCompoundCurve::deleteVertices( const QSet<QgsVertexId> &positions )
945{
946 // we create a list of vertices to delete for each curve
947 QMap<int, QList<QgsVertexId >> curveVertices;
948 for ( QgsVertexId position : positions )
949 {
950 if ( !hasVertex( position ) )
951 {
952 return false;
953 }
954
955 const QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
956
957 if ( curveIds.isEmpty() )
958 return false;
959
960 const int firstCurveId = curveIds.at( 0 ).first;
961 const QgsVertexId firstCurveVertex = curveIds.at( 0 ).second;
962 curveVertices[firstCurveId].append( firstCurveVertex );
963 if ( curveIds.size() == 2 ) // vertex is shared between two curves
964 {
965 const int secondCurveId = curveIds.at( 1 ).first;
966 const QgsVertexId secondCurveVertex = curveIds.at( 1 ).second;
967 curveVertices[secondCurveId].append( secondCurveVertex );
968 }
969 }
970
971 QVector< QgsPoint > survivingPoints;
972
973 auto appendSurvivingPoints = [&survivingPoints, this]( const QgsPoint &point, const int curveId ) {
975 pts.reserve( 1 + survivingPoints.size() );
976 pts << point;
977
978 for ( size_t i = survivingPoints.size(); i-- > 0; )
979 pts << survivingPoints[i];
980
981 auto newLineString = std::make_unique<QgsLineString>();
982 newLineString->setPoints( pts );
983 mCurves.insert( curveId, newLineString.release() );
984
985 survivingPoints.clear();
986 };
987
988 // loop through the curves in reverse order and delete vertices
989 QMapIterator<int, QList<QgsVertexId >> curveVerticesIt( curveVertices );
990 curveVerticesIt.toBack();
991 int previousCurveId = -1;
992 while ( curveVerticesIt.hasPrevious() )
993 {
994 curveVerticesIt.previous();
995 const int curveId = curveVerticesIt.key();
996
997 // append surviving points at the end of a curve that has no vertices scheduled for deletion
998 if ( previousCurveId - 1 > curveId && !survivingPoints.isEmpty() )
999 {
1000 QgsCurve *curve = mCurves.at( previousCurveId - 1 );
1001 appendSurvivingPoints( curve->endPoint(), previousCurveId );
1002 }
1003
1004 QgsCurve *curve = mCurves.at( curveId );
1005 QList<QgsVertexId> vertices = curveVerticesIt.value();
1006 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex < b.vertex; } );
1007
1008 const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString *>( curve );
1009 // If the vertex to delete is the middle vertex of a circularstring arc, we transform
1010 // this circularstring arc into a linestring without the middle vertex
1011 if ( circularString )
1012 {
1013 // we loop through the vertices to see if we need to handle special case
1014 // of a middle vertex (see deleteVertex)
1015 QList<QgsVertexId> circularVerticesToDelete;
1016 circularVerticesToDelete.reserve( vertices.size() );
1017
1018 // search for odd vertices (middle vertices of an arc)
1019 for ( size_t i = vertices.size(); i-- > 0; )
1020 {
1021 const QgsVertexId curveVertexId = vertices.at( i );
1022
1023 // check if a middle vertex of an arc
1024 if ( curveVertexId.vertex % 2 == 1 )
1025 {
1026 // check if neighbouring vertices are also to be deleted
1027 // if so, we just add this vertex to the list and continue iterating
1028 if ( !circularVerticesToDelete.isEmpty() )
1029 {
1030 if ( curveVertexId.vertex == circularVerticesToDelete.last().vertex - 1 )
1031 {
1032 circularVerticesToDelete.append( curveVertexId );
1033 continue;
1034 }
1035 }
1036 else if ( i != 0 && curveVertexId.vertex - 1 == vertices.at( i - 1 ).vertex )
1037 {
1038 circularVerticesToDelete.append( curveVertexId );
1039 continue;
1040 }
1041
1042 // we found a middle vertex of an arc and none of its neighbours are to be deleted
1043 // we need to handle special case of middle vertex of an arc deletion
1044 // first we delete all the vertices that come before it in this circularstring
1045 if ( !circularVerticesToDelete.isEmpty() )
1046 {
1047 if ( !curve->deleteVertices( QSet<QgsVertexId>( circularVerticesToDelete.begin(), circularVerticesToDelete.end() ) ) )
1048 {
1049 Q_ASSERT( false ); // shouldn't happen after all the checks
1050 return false;
1051 }
1052 }
1053 circularVerticesToDelete.clear();
1054
1055 // next, we remove that arc and replace it with a linestring that skips the middle vertex
1057 circularString->points( points );
1058
1059 removeCurve( curveId );
1060
1061 if ( curveVertexId.vertex < points.length() - 2 )
1062 {
1063 auto curveC = std::make_unique<QgsCircularString>();
1064 curveC->setPoints( points.mid( curveVertexId.vertex + 1 ) );
1065 mCurves.insert( curveId, curveC.release() );
1066 }
1067
1068 const QgsPointSequence partB = QgsPointSequence() << points[curveVertexId.vertex - 1] << points[curveVertexId.vertex + 1];
1069 auto curveB = std::make_unique<QgsLineString>();
1070 curveB->setPoints( partB );
1071 mCurves.insert( curveId, curveB.release() );
1072 curve = mCurves.at( curveId );
1073
1074 if ( curveVertexId.vertex > 1 )
1075 {
1076 auto curveA = std::make_unique<QgsCircularString>();
1077 curveA->setPoints( points.mid( 0, curveVertexId.vertex ) );
1078 mCurves.insert( curveId, curveA.release() );
1079 }
1080 curve = mCurves.at( curveId ); // we need to get the new curve
1081 circularString = qgsgeometry_cast<const QgsCircularString *>( curve );
1082
1083 continue;
1084 }
1085
1086 // not a middle vertex of an arc
1087 circularVerticesToDelete.append( curveVertexId );
1088 }
1089
1090 // remove any remaining circular vertices to delete
1091 if ( !circularVerticesToDelete.isEmpty() )
1092 {
1093 // check if we are deleting a shared vertex and save the end point IF it is not being deleted
1094 // we don't want to make assumtions how circularstring deletes its vertices
1095 // so we save it, delete the vertices, and if the curve gets deleted as a result
1096 // only then we actually append the survivingPoint to the list
1097 QgsPoint survivingPoint;
1098 if ( curveId > 0 && circularVerticesToDelete.last().vertex == 0 && circularVerticesToDelete.first().vertex != curve->numPoints() - 1 )
1099 {
1100 survivingPoint = curve->endPoint();
1101 }
1102 else if ( curveId == 0 && circularVerticesToDelete.first().vertex == curve->numPoints() - 1 && circularVerticesToDelete.last().vertex != 0 )
1103 {
1104 survivingPoint = curve->startPoint();
1105 }
1106 if ( !curve->deleteVertices( QSet<QgsVertexId>( circularVerticesToDelete.begin(), circularVerticesToDelete.end() ) ) )
1107 {
1108 Q_ASSERT( false );
1109 return false;
1110 }
1111
1112 if ( curve->numPoints() == 0 )
1113 {
1114 removeCurve( curveId );
1115 // end point wasn't marked for deletion and the curve was deleted
1116 // append it to survivingPoints
1117 if ( !survivingPoint.isEmpty() )
1118 {
1119 survivingPoints.emplace_back( survivingPoint );
1120 }
1121 }
1122 else if ( survivingPoints.size() != 0 )
1123 {
1124 appendSurvivingPoints( curve->endPoint(), curveId + 1 );
1125 }
1126 }
1127 previousCurveId = curveId;
1128 continue; // circularstring handled, continue to next curve
1129 }
1130
1131 // linestring
1132 // check if we are deleting a shared vertex and all but 1 point would be deleted
1133 // if that's the case, we add that point to survivingPoints, delete the curve and continue
1134 if ( ( curveId > 0 && curve->numPoints() - vertices.size() == 1 && vertices.first().vertex == 0 )
1135 || ( curveId == 0 && curve->numPoints() - vertices.size() == 1 && vertices.last().vertex == curve->numPoints() - 1 ) )
1136 {
1137 int remainingIdx = 0;
1138 for ( const QgsVertexId &v : vertices )
1139 {
1140 if ( v.vertex != remainingIdx )
1141 break;
1142 remainingIdx++;
1143 }
1144 survivingPoints.emplace_back( curve->vertexAt( QgsVertexId( 0, 0, remainingIdx ) ) );
1145 removeCurve( curveId );
1146 previousCurveId = curveId;
1147 continue; // curve removed, continue to next one
1148 }
1149
1150 if ( !curve->deleteVertices( QSet<QgsVertexId>( vertices.begin(), vertices.end() ) ) )
1151 {
1152 Q_ASSERT( false );
1153 return false;
1154 }
1155
1156 if ( curve->numPoints() == 0 )
1157 {
1158 removeCurve( curveId );
1159 }
1160 else if ( survivingPoints.size() != 0 )
1161 {
1162 appendSurvivingPoints( curve->endPoint(), curveId + 1 );
1163 }
1164
1165 previousCurveId = curveId;
1166 // linestring handled, this is the end of the loop
1167 }
1168
1169 if ( survivingPoints.size() != 0 )
1170 {
1171 if ( previousCurveId > 0 )
1172 {
1173 // (note: we went through the list in reverse order)
1174 // we are not at the start of curve list, there are curves further down
1175 // so we add those points at the start of the previous curve
1176 const QgsCurve *curve = mCurves.at( previousCurveId - 1 );
1177 appendSurvivingPoints( curve->endPoint(), previousCurveId );
1178 }
1179 else
1180 {
1181 // we reached the end of list, which means we are at the start of the geometry
1182 // so we append those points at the start of the first curve in the list
1183 // if there are no curves left, but the number of surviving points is 2 or greater
1184 // we make a linestring out of them and add it as the only curve
1186 if ( mCurves.size() != 0 )
1187 {
1188 const QgsCurve *curve = mCurves.at( 0 );
1189 startPoint = curve->startPoint();
1190 }
1191
1192 QgsPointSequence pts;
1193 for ( int i = survivingPoints.size() - 1; i >= 0; --i )
1194 pts << survivingPoints[i];
1195
1196 if ( !startPoint.isEmpty() )
1197 pts << startPoint;
1198
1199 if ( pts.size() > 1 )
1200 {
1201 auto newLineString = std::make_unique<QgsLineString>();
1202 newLineString->setPoints( pts );
1203
1204 mCurves.insert( 0, newLineString.release() );
1205 }
1206 }
1207 }
1208
1209 if ( mCurves.isEmpty() )
1210 {
1211 clearCache();
1212 return true;
1213 }
1214
1215 // ensure all curves are connected
1216 for ( size_t i = mCurves.size() - 1; i > 0; i-- )
1217 {
1218 QgsCurve *curve = mCurves.at( i );
1219 QgsCurve *previousCurve = mCurves.at( i - 1 );
1220 if ( previousCurve->endPoint() != curve->startPoint() )
1221 {
1222 QgsLineString *line = new QgsLineString();
1223 line->insertVertex( QgsVertexId( 0, 0, 0 ), previousCurve->endPoint() );
1224 line->insertVertex( QgsVertexId( 0, 0, 1 ), curve->startPoint() );
1225 mCurves.insert( i, line );
1226 }
1227 }
1228
1229 condenseCurves(); // merge consecutive LineStrings and CircularStrings
1230 clearCache();
1231 return true;
1232}
1233
1234QVector< QPair<int, QgsVertexId> > QgsCompoundCurve::curveVertexId( QgsVertexId id ) const
1235{
1236 QVector< QPair<int, QgsVertexId> > curveIds;
1237
1238 int currentVertexIndex = 0;
1239 for ( int i = 0; i < mCurves.size(); ++i )
1240 {
1241 int increment = mCurves.at( i )->numPoints() - 1;
1242 if ( id.vertex >= currentVertexIndex && id.vertex <= currentVertexIndex + increment )
1243 {
1244 int curveVertexId = id.vertex - currentVertexIndex;
1245 QgsVertexId vid;
1246 vid.part = 0;
1247 vid.ring = 0;
1248 vid.vertex = curveVertexId;
1249 curveIds.append( qMakePair( i, vid ) );
1250 if ( curveVertexId == increment && i < ( mCurves.size() - 1 ) ) //add first vertex of next curve
1251 {
1252 vid.vertex = 0;
1253 curveIds.append( qMakePair( i + 1, vid ) );
1254 }
1255 break;
1256 }
1257 else if ( id.vertex >= currentVertexIndex && id.vertex == currentVertexIndex + increment + 1 && i == ( mCurves.size() - 1 ) )
1258 {
1259 int curveVertexId = id.vertex - currentVertexIndex;
1260 QgsVertexId vid;
1261 vid.part = 0;
1262 vid.ring = 0;
1263 vid.vertex = curveVertexId;
1264 curveIds.append( qMakePair( i, vid ) );
1265 break;
1266 }
1267 currentVertexIndex += increment;
1268 }
1269
1270 return curveIds;
1271}
1272
1274{
1275 // First we find out the sub-curves that are contain that vertex.
1276
1277 // If there is more than one, it means the vertex was at the beginning or end
1278 // of an arc, which we don't support.
1279
1280 // If there is exactly one, we may either be on a LineString, or on a CircularString.
1281
1282 // If on CircularString, we need to check if the vertex is a CurveVertex (odd index).
1283 // If so, we split the subcurve at vertex -1 and +1, , drop the middle part and insert a LineString/CircularString
1284 // instead with the same points.
1285
1286 // At the end, we call condenseCurves() to merge successible line/circular strings
1287
1288 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
1289
1290 // We cannot convert points at start/end of subcurves
1291 if ( curveIds.length() != 1 )
1292 return false;
1293
1294 int curveId = curveIds[0].first;
1295 QgsVertexId subVertexId = curveIds[0].second;
1296 QgsCurve *curve = mCurves[curveId];
1297
1298 // We cannot convert first/last point of curve
1299 if ( subVertexId.vertex == 0 || subVertexId.vertex == curve->numPoints() - 1 )
1300 return false;
1301
1302 if ( const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString *>( curve ) )
1303 {
1304 // If it's a circular string, we convert to LineString
1305
1306 // We cannot convert start/end points of arcs
1307 if ( subVertexId.vertex % 2 == 0 ) // for some reason, subVertexId.type is always SegmentVertex...
1308 return false;
1309
1311 circularString->points( points );
1312
1313 const QgsPointSequence partA = points.mid( 0, subVertexId.vertex );
1314 const QgsPointSequence partB = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex] << points[subVertexId.vertex + 1];
1315 const QgsPointSequence partC = points.mid( subVertexId.vertex + 1 );
1316
1317 auto curveA = std::make_unique<QgsCircularString>();
1318 curveA->setPoints( partA );
1319 auto curveB = std::make_unique<QgsLineString>();
1320 curveB->setPoints( partB );
1321 auto curveC = std::make_unique<QgsCircularString>();
1322 curveC->setPoints( partC );
1323
1324 removeCurve( curveId );
1325 if ( subVertexId.vertex < points.length() - 2 )
1326 mCurves.insert( curveId, curveC.release() );
1327 mCurves.insert( curveId, curveB.release() );
1328 if ( subVertexId.vertex > 1 )
1329 mCurves.insert( curveId, curveA.release() );
1330 }
1331 else if ( const QgsLineString *lineString = dynamic_cast<const QgsLineString *>( curve ) )
1332 {
1333 // If it's a linestring, we split and insert a curve
1334
1336 lineString->points( points );
1337
1338 const QgsPointSequence partA = points.mid( 0, subVertexId.vertex );
1339 const QgsPointSequence partB = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex] << points[subVertexId.vertex + 1];
1340 const QgsPointSequence partC = points.mid( subVertexId.vertex + 1 );
1341
1342 auto curveA = std::make_unique<QgsLineString>();
1343 curveA->setPoints( partA );
1344 auto curveB = std::make_unique<QgsCircularString>();
1345 curveB->setPoints( partB );
1346 auto curveC = std::make_unique<QgsLineString>();
1347 curveC->setPoints( partC );
1348
1349 removeCurve( curveId );
1350 if ( subVertexId.vertex < points.length() - 2 )
1351 mCurves.insert( curveId, curveC.release() );
1352 mCurves.insert( curveId, curveB.release() );
1353 if ( subVertexId.vertex > 1 )
1354 mCurves.insert( curveId, curveA.release() );
1355 }
1356
1357 // We merge consecutive LineStrings
1359
1360 clearCache();
1361 return true;
1362}
1363
1364
1365double QgsCompoundCurve::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1366{
1367 return QgsGeometryUtils::closestSegmentFromComponents( mCurves, QgsGeometryUtils::Vertex, pt, segmentPt, vertexAfter, leftOf, epsilon );
1368}
1369
1370bool QgsCompoundCurve::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
1371{
1372 int currentVertexId = 0;
1373 for ( int j = 0; j < mCurves.size(); ++j )
1374 {
1375 int nCurvePoints = mCurves.at( j )->numPoints();
1376 if ( ( node - currentVertexId ) < nCurvePoints )
1377 {
1378 return ( mCurves.at( j )->pointAt( node - currentVertexId, point, type ) );
1379 }
1380 currentVertexId += ( nCurvePoints - 1 );
1381 }
1382 return false;
1383}
1384
1385double QgsCompoundCurve::xAt( int index ) const
1386{
1387 int currentVertexId = 0;
1388 for ( int j = 0; j < mCurves.size(); ++j )
1389 {
1390 int nCurvePoints = mCurves.at( j )->numPoints();
1391 if ( ( index - currentVertexId ) < nCurvePoints )
1392 {
1393 return mCurves.at( j )->xAt( index - currentVertexId );
1394 }
1395 currentVertexId += ( nCurvePoints - 1 );
1396 }
1397 return 0.0;
1398}
1399
1400double QgsCompoundCurve::yAt( int index ) const
1401{
1402 int currentVertexId = 0;
1403 for ( int j = 0; j < mCurves.size(); ++j )
1404 {
1405 int nCurvePoints = mCurves.at( j )->numPoints();
1406 if ( ( index - currentVertexId ) < nCurvePoints )
1407 {
1408 return mCurves.at( j )->yAt( index - currentVertexId );
1409 }
1410 currentVertexId += ( nCurvePoints - 1 );
1411 }
1412 return 0.0;
1413}
1414
1415double QgsCompoundCurve::zAt( int index ) const
1416{
1417 int currentVertexId = 0;
1418 for ( int j = 0; j < mCurves.size(); ++j )
1419 {
1420 int nCurvePoints = mCurves.at( j )->numPoints();
1421 if ( ( index - currentVertexId ) < nCurvePoints )
1422 {
1423 return mCurves.at( j )->zAt( index - currentVertexId );
1424 }
1425 currentVertexId += ( nCurvePoints - 1 );
1426 }
1427 return 0.0;
1428}
1429
1430double QgsCompoundCurve::mAt( int index ) const
1431{
1432 int currentVertexId = 0;
1433 for ( int j = 0; j < mCurves.size(); ++j )
1434 {
1435 int nCurvePoints = mCurves.at( j )->numPoints();
1436 if ( ( index - currentVertexId ) < nCurvePoints )
1437 {
1438 return mCurves.at( j )->mAt( index - currentVertexId );
1439 }
1440 currentVertexId += ( nCurvePoints - 1 );
1441 }
1442 return 0.0;
1443}
1444
1446{
1447 bool res = true;
1448 for ( QgsCurve *curve : std::as_const( mCurves ) )
1449 {
1450 if ( !curve->transform( transformer ) )
1451 {
1452 res = false;
1453 break;
1454 }
1455
1456 if ( feedback && feedback->isCanceled() )
1457 {
1458 res = false;
1459 break;
1460 }
1461 }
1462 clearCache();
1463 return res;
1464}
1465
1466void QgsCompoundCurve::filterVertices( const std::function<bool( const QgsPoint & )> &filter )
1467{
1468 for ( QgsCurve *curve : std::as_const( mCurves ) )
1469 {
1470 curve->filterVertices( filter );
1471 }
1472 clearCache();
1473}
1474
1475void QgsCompoundCurve::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
1476{
1477 for ( QgsCurve *curve : std::as_const( mCurves ) )
1478 {
1479 curve->transformVertices( transform );
1480 }
1481 clearCache();
1482}
1483
1484std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsCompoundCurve::splitCurveAtVertex( int index ) const
1485{
1486 if ( mCurves.empty() )
1487 return std::make_tuple( std::make_unique< QgsCompoundCurve >(), std::make_unique< QgsCompoundCurve >() );
1488
1489 int curveStart = 0;
1490
1491 auto curve1 = std::make_unique< QgsCompoundCurve >();
1492 std::unique_ptr< QgsCompoundCurve > curve2;
1493
1494 for ( const QgsCurve *curve : mCurves )
1495 {
1496 const int curveSize = curve->numPoints();
1497 if ( !curve2 && index < curveStart + curveSize )
1498 {
1499 // split the curve
1500 auto [p1, p2] = curve->splitCurveAtVertex( index - curveStart );
1501 if ( !p1->isEmpty() )
1502 curve1->addCurve( p1.release() );
1503
1504 curve2 = std::make_unique< QgsCompoundCurve >();
1505 if ( !p2->isEmpty() )
1506 curve2->addCurve( p2.release() );
1507 }
1508 else
1509 {
1510 if ( curve2 )
1511 curve2->addCurve( curve->clone() );
1512 else
1513 curve1->addCurve( curve->clone() );
1514 }
1515
1516 // subtract 1 here, because the next curve will start with the same
1517 // vertex as this curve ended at
1518 curveStart += curve->numPoints() - 1;
1519 }
1520
1521 return std::make_tuple( std::move( curve1 ), curve2 ? std::move( curve2 ) : std::make_unique< QgsCompoundCurve >() );
1522}
1523
1524void QgsCompoundCurve::sumUpArea( double &sum ) const
1525{
1527 {
1528 sum += mSummedUpArea;
1529 return;
1530 }
1531
1532 mSummedUpArea = 0;
1533 for ( const QgsCurve *curve : mCurves )
1534 {
1535 curve->sumUpArea( mSummedUpArea );
1536 }
1538 sum += mSummedUpArea;
1539}
1540
1541void QgsCompoundCurve::sumUpArea3D( double &sum ) const
1542{
1544 {
1545 sum += mSummedUpArea3D;
1546 return;
1547 }
1548
1549 mSummedUpArea3D = 0;
1550 for ( const QgsCurve *curve : mCurves )
1551 {
1552 curve->sumUpArea3D( mSummedUpArea3D );
1553 }
1555 sum += mSummedUpArea3D;
1556}
1557
1559{
1560 if ( numPoints() < 1 || isClosed() )
1561 {
1562 return;
1563 }
1564 addVertex( startPoint() );
1565}
1566
1568{
1569 for ( const QgsCurve *curve : mCurves )
1570 {
1571 if ( curve->hasCurvedSegments() )
1572 {
1573 return true;
1574 }
1575 }
1576 return false;
1577}
1578
1580{
1581 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( vertex );
1582 if ( curveIds.size() == 1 )
1583 {
1584 QgsCurve *curve = mCurves[curveIds.at( 0 ).first];
1585 return curve->vertexAngle( curveIds.at( 0 ).second );
1586 }
1587 else if ( curveIds.size() > 1 )
1588 {
1589 QgsCurve *curve1 = mCurves[curveIds.at( 0 ).first];
1590 QgsCurve *curve2 = mCurves[curveIds.at( 1 ).first];
1591 double angle1 = curve1->vertexAngle( curveIds.at( 0 ).second );
1592 double angle2 = curve2->vertexAngle( curveIds.at( 1 ).second );
1593 return QgsGeometryUtilsBase::averageAngle( angle1, angle2 );
1594 }
1595 else
1596 {
1597 return 0.0;
1598 }
1599}
1600
1602{
1603 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( startVertex );
1604 double length = 0.0;
1605 for ( auto it = curveIds.constBegin(); it != curveIds.constEnd(); ++it )
1606 {
1607 length += mCurves.at( it->first )->segmentLength( it->second );
1608 }
1609 return length;
1610}
1611
1613{
1615 for ( int i = mCurves.count() - 1; i >= 0; --i )
1616 {
1617 QgsCurve *reversedCurve = mCurves.at( i )->reversed();
1618 clone->addCurve( reversedCurve );
1619 }
1620 return clone;
1621}
1622
1623QgsPoint *QgsCompoundCurve::interpolatePoint( const double distance ) const
1624{
1625 if ( distance < 0 )
1626 return nullptr;
1627
1628 double distanceTraversed = 0;
1629 for ( const QgsCurve *curve : mCurves )
1630 {
1631 const double thisCurveLength = curve->length();
1632 if ( distanceTraversed + thisCurveLength > distance || qgsDoubleNear( distanceTraversed + thisCurveLength, distance ) )
1633 {
1634 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1635 const double distanceToPoint = std::min( distance - distanceTraversed, thisCurveLength );
1636
1637 // point falls on this curve
1638 return curve->interpolatePoint( distanceToPoint );
1639 }
1640
1641 distanceTraversed += thisCurveLength;
1642 }
1643
1644 return nullptr;
1645}
1646
1647QgsCompoundCurve *QgsCompoundCurve::curveSubstring( double startDistance, double endDistance ) const
1648{
1649 if ( startDistance < 0 && endDistance < 0 )
1650 return createEmptyWithSameType();
1651
1652 endDistance = std::max( startDistance, endDistance );
1653 auto substring = std::make_unique< QgsCompoundCurve >();
1654
1655 double distanceTraversed = 0;
1656 for ( const QgsCurve *curve : mCurves )
1657 {
1658 const double thisCurveLength = curve->length();
1659 if ( distanceTraversed + thisCurveLength < startDistance )
1660 {
1661 // keep going - haven't found start yet, so no need to include this curve at all
1662 }
1663 else
1664 {
1665 std::unique_ptr< QgsCurve > part( curve->curveSubstring( startDistance - distanceTraversed, endDistance - distanceTraversed ) );
1666 if ( part )
1667 substring->addCurve( part.release() );
1668 }
1669
1670 distanceTraversed += thisCurveLength;
1671 if ( distanceTraversed > endDistance )
1672 break;
1673 }
1674
1675 return substring.release();
1676}
1677
1678bool QgsCompoundCurve::addZValue( double zValue )
1679{
1680 if ( QgsWkbTypes::hasZ( mWkbType ) )
1681 return false;
1682
1684
1685 for ( QgsCurve *curve : std::as_const( mCurves ) )
1686 {
1687 curve->addZValue( zValue );
1688 }
1689 clearCache();
1690 return true;
1691}
1692
1693bool QgsCompoundCurve::addMValue( double mValue )
1694{
1695 if ( QgsWkbTypes::hasM( mWkbType ) )
1696 return false;
1697
1699
1700 for ( QgsCurve *curve : std::as_const( mCurves ) )
1701 {
1702 curve->addMValue( mValue );
1703 }
1704 clearCache();
1705 return true;
1706}
1707
1709{
1710 if ( !QgsWkbTypes::hasZ( mWkbType ) )
1711 return false;
1712
1714 for ( QgsCurve *curve : std::as_const( mCurves ) )
1715 {
1716 curve->dropZValue();
1717 }
1718 clearCache();
1719 return true;
1720}
1721
1723{
1724 if ( !QgsWkbTypes::hasM( mWkbType ) )
1725 return false;
1726
1728 for ( QgsCurve *curve : std::as_const( mCurves ) )
1729 {
1730 curve->dropMValue();
1731 }
1732 clearCache();
1733 return true;
1734}
1735
1737{
1738 for ( QgsCurve *curve : std::as_const( mCurves ) )
1739 {
1740 curve->swapXy();
1741 }
1742 clearCache();
1743}
1744
1746{
1747 // Ensure fromVertex < toVertex for simplicity
1748 if ( fromVertex.vertex > toVertex.vertex )
1749 {
1750 return distanceBetweenVertices( toVertex, fromVertex );
1751 }
1752
1753 // Convert QgsVertexId to simple vertex numbers for compound curves (single ring, single part)
1754 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
1755 return -1.0;
1756
1757 const int fromVertexNumber = fromVertex.vertex;
1758 const int toVertexNumber = toVertex.vertex;
1759
1760 const int totalVertices = numPoints();
1761 if ( fromVertexNumber < 0 || fromVertexNumber >= totalVertices || toVertexNumber < 0 || toVertexNumber >= totalVertices )
1762 return -1.0;
1763
1764 if ( fromVertexNumber == toVertexNumber )
1765 return 0.0;
1766
1767 double totalDistance = 0.0;
1768
1769 // Find which curves contain our vertices and accumulate distances
1770 int currentVertexId = 0;
1771 int fromCurve = -1, toCurve = -1;
1772 int fromCurveVertex = -1, toCurveVertex = -1;
1773
1774 // First pass: find which curves contain from and to vertices
1775 for ( int j = 0; j < mCurves.size(); ++j )
1776 {
1777 int nCurvePoints = mCurves.at( j )->numPoints();
1778
1779 // Check if fromVertex is in this curve
1780 if ( fromCurve == -1 && fromVertexNumber >= currentVertexId && fromVertexNumber < currentVertexId + nCurvePoints )
1781 {
1782 fromCurve = j;
1783 fromCurveVertex = fromVertexNumber - currentVertexId;
1784 }
1785
1786 // Check if toVertex is in this curve
1787 if ( toCurve == -1 && toVertexNumber >= currentVertexId && toVertexNumber < currentVertexId + nCurvePoints )
1788 {
1789 toCurve = j;
1790 toCurveVertex = toVertexNumber - currentVertexId;
1791 break;
1792 }
1793
1794 currentVertexId += ( nCurvePoints - 1 ); // Subtract 1 because curves share endpoints
1795 }
1796
1797 if ( fromCurve == -1 || toCurve == -1 )
1798 return -1.0; // Invalid vertex IDs
1799
1800 if ( fromCurve == toCurve )
1801 {
1802 // Both vertices are on the same curve
1803 QgsVertexId fromId( 0, 0, fromCurveVertex );
1804 QgsVertexId toId( 0, 0, toCurveVertex );
1805 return mCurves.at( fromCurve )->distanceBetweenVertices( fromId, toId );
1806 }
1807 else
1808 {
1809 // Vertices are on different curves - accumulate distances across multiple curves
1810
1811 // Distance from fromVertex to end of its curve
1812 if ( fromCurveVertex < mCurves.at( fromCurve )->numPoints() - 1 )
1813 {
1814 QgsVertexId fromId( 0, 0, fromCurveVertex );
1815 QgsVertexId endId( 0, 0, mCurves.at( fromCurve )->numPoints() - 1 );
1816 totalDistance += mCurves.at( fromCurve )->distanceBetweenVertices( fromId, endId );
1817 }
1818
1819 // Distance of complete intermediate curves
1820 for ( int j = fromCurve + 1; j < toCurve; ++j )
1821 {
1822 totalDistance += mCurves.at( j )->length();
1823 }
1824
1825 // Distance from start of toCurve to toVertex
1826 if ( toCurveVertex > 0 )
1827 {
1828 QgsVertexId startId( 0, 0, 0 );
1829 QgsVertexId toId( 0, 0, toCurveVertex );
1830 totalDistance += mCurves.at( toCurve )->distanceBetweenVertices( startId, toId );
1831 }
1832 }
1833
1834 return totalDistance;
1835}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2226
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
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
@ Unknown
Unknown.
Definition qgis.h:295
@ CircularString
CircularString.
Definition qgis.h:304
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 fromWkb(QgsConstWkbPtr &wkb)=0
Sets the geometry from a WKB string.
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.
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)
Qgis::WkbType wkbType() const
Returns the WKB type of 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 int compareTo(const QgsAbstractGeometry *other) const
Comparator for sorting of geometry.
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
void combineWith(const QgsBox3D &box)
Expands the bbox so that it covers both the original rectangle and the given rectangle.
Definition qgsbox3d.cpp:211
Circular string geometry type.
bool fromWkt(const QString &wkt) override
Sets the geometry from a WKT string.
void draw(QPainter &p) const override
Draws the geometry using the specified QPainter.
void sumUpArea(double &sum) const override
Sums up the area of the curve by iterating over the vertices (shoelace formula).
QgsLineString * curveToLine(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a new line string geometry corresponding to a segmentized approximation of the curve.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into 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...
QgsAbstractGeometry * simplifyByDistance(double tolerance) const override
Simplifies the geometry by applying the Douglas Peucker simplification by distance algorithm.
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...
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
QgsCompoundCurve * reversed() const override
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
void condenseCurves()
Condenses the curves in this geometry by combining adjacent linestrings a to a single continuous line...
void close()
Appends first point if not already closed.
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
void drawAsPolygon(QPainter &p) const override
Draws the curve as a polygon on the specified QPainter.
int dimension() const override
Returns the inherent dimension of 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...
std::tuple< std::unique_ptr< QgsCurve >, std::unique_ptr< QgsCurve > > splitCurveAtVertex(int index) const final
Splits the curve at the specified vertex index, returning two curves which represent the portion of t...
bool boundingBoxIntersects(const QgsBox3D &box3d) const override
Returns true if the bounding box of this geometry intersects with a box3d.
QString geometryType() const override
Returns a unique string representing the geometry type.
double mAt(int index) const override
Returns the m-coordinate of the specified node in the line string.
double distanceBetweenVertices(QgsVertexId fromVertex, QgsVertexId toVertex) const override
Returns the distance along the curve between two vertices.
bool isEmpty() const override
Returns true if the geometry is empty.
double vertexAngle(QgsVertexId vertex) const override
Returns approximate angle at a vertex.
double segmentLength(QgsVertexId startVertex) const override
Returns the length of the segment of the geometry which begins at startVertex.
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 nCurves() const
Returns the number of curves in the geometry.
bool toggleCircularAtVertex(QgsVertexId position)
Converts the vertex at the given position from/to circular.
QgsBox3D calculateBoundingBox3D() const override
Calculates the minimal 3D bounding box for the geometry.
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.
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
const QgsAbstractGeometry * simplifiedTypeRef() const override
Returns a reference to the simplest lossless representation of this geometry, e.g.
int wkbSize(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns the length of the QByteArray returned by asWkb().
bool deleteVertices(const QSet< QgsVertexId > &positions) override
Deletes vertices within the geometry.
void sumUpArea3D(double &sum) const override
Sums up the 3d area of the curve by iterating over the vertices (shoelace formula).
void swapXy() override
Swaps the x and y coordinates from the geometry.
void removeCurve(int i)
Removes a curve from the geometry.
void addCurve(QgsCurve *c, bool extendPrevious=false)
Adds a curve to the geometry (takes ownership).
bool moveVertex(QgsVertexId position, const QgsPoint &newPos) override
Moves a vertex within the geometry.
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
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.
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.
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
bool dropMValue() override
Drops any measure values which exist in the geometry.
QgsCompoundCurve & operator=(const QgsCompoundCurve &curve)
QgsCompoundCurve * clone() const override
Clones the geometry by performing a deep copy.
const QgsCurve * curveAt(int i) const
Returns the curve at the specified index.
void points(QgsPointSequence &pts) const override
Returns a list of points within the curve.
bool isValid(QString &error, Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const override
Checks validity of the geometry, and returns true if the geometry is valid.
~QgsCompoundCurve() override
QByteArray asWkb(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns a WKB representation of the geometry.
void clear() override
Clears the geometry, ie reset it to a null geometry.
int indexOf(const QgsPoint &point) const final
Returns the index of the first vertex matching the given point, or -1 if a matching vertex is not fou...
void transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection d=Qgis::TransformDirection::Forward, bool transformZ=false) override
Transforms the geometry using a coordinate transform.
bool hasCurvedSegments() const override
Returns true if the geometry contains curved segments.
QgsCompoundCurve * curveSubstring(double startDistance, double endDistance) const override
Returns a new curve representing a substring of this curve.
void scroll(int firstVertexIndex) final
Scrolls the curve vertices so that they start with the vertex at the given index.
double length() const override
Returns the planar, 2-dimensional length of the geometry.
void addToPainterPath(QPainterPath &path) const override
Adds a curve to a painter path.
QgsPoint * interpolatePoint(double distance) const override
Returns an interpolated point on the curve at the specified distance.
QgsCompoundCurve * createEmptyWithSameType() const override
Creates a new geometry with the same class and same WKB type as the original and transfers ownership.
bool pointAt(int node, QgsPoint &point, Qgis::VertexType &type) const override
Returns the point and vertex type of a point within the curve.
QgsPoint startPoint() const override
Returns the starting point of the curve.
QgsCompoundCurve * 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.
QgsPoint endPoint() const override
Returns the end point of the curve.
int numPoints() const override
Returns the number of points in the curve.
double zAt(int index) const override
Returns the z-coordinate of the specified node in the line string.
void addVertex(const QgsPoint &pt)
Adds a vertex to the end of the geometry.
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
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.
A const WKB pointer.
Definition qgswkbptr.h:211
Qgis::WkbType readHeader() const
readHeader
Definition qgswkbptr.cpp:60
Handles coordinate transforms between two coordinate systems.
virtual int numPoints() const =0
Returns the number of points in the curve.
void clearCache() const override
Clears any cached parameters associated with the geometry, e.g., bounding boxes.
Definition qgscurve.cpp:298
double mSummedUpArea3D
Definition qgscurve.h:408
bool mHasCachedSummedUpArea
Definition qgscurve.h:405
bool mHasCachedSummedUpArea3D
Definition qgscurve.h:407
virtual bool isClosed() const
Returns true if the curve is closed.
Definition qgscurve.cpp:53
bool isValid(QString &error, Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const override
Checks validity of the geometry, and returns true if the geometry is valid.
Definition qgscurve.cpp:247
QgsPoint vertexAt(QgsVertexId id) const override
Returns the point corresponding to a specified vertex id.
Definition qgscurve.cpp:198
QgsBox3D mBoundingBox
Cached bounding box.
Definition qgscurve.h:403
virtual QgsPoint startPoint() const =0
Returns the starting point of the curve.
bool hasVertex(QgsVertexId position) const override
Returns true if the geometry contains a vertex matching the given position.
Definition qgscurve.cpp:266
virtual QgsPoint endPoint() const =0
Returns the end point of the curve.
double mSummedUpArea
Definition qgscurve.h:406
QgsCurve()=default
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
static double averageAngle(double x1, double y1, double x2, double y2, double x3, double y3)
Calculates the average angle (in radians) between the two linear segments from (x1,...
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)
Line string geometry type, with support for z-dimension and m-values.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into the geometry.
void addVertex(const QgsPoint &pt)
Adds a new vertex to the end of the line string.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
QgsPoint vertexAt(QgsVertexId) const override
Returns the point corresponding to a specified vertex id.
Definition qgspoint.cpp:572
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:781
void points(QgsPointSequence &pts) const override
Returns a list of points within the curve.
void append(const QgsSimpleCurve *curve)
Appends the contents of another simple curve to the end of this simple curve.
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::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.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define BUILTIN_UNREACHABLE
Definition qgis.h:8229
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< QgsPoint > QgsPointSequence
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