QGIS API Documentation 4.3.0-Master (58212645307)
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 // loop through the curves in reverse order and delete vertices
972 QMapIterator<int, QList<QgsVertexId >> curveVerticesIt( curveVertices );
973 curveVerticesIt.toBack();
974 while ( curveVerticesIt.hasPrevious() )
975 {
976 curveVerticesIt.previous();
977 const int curveId = curveVerticesIt.key();
978 QgsCurve *curve = mCurves.at( curveId );
979 QList<QgsVertexId> vertices = curveVerticesIt.value();
980
981 const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString *>( curve );
982 // If the vertex to delete is the middle vertex of a circularstring arc, we transform
983 // this circularstring arc into a linestring without the middle vertex
984 if ( circularString )
985 {
986 // we loop through the vertices to see if we need to handle special case
987 // of a middle vertex (see deleteVertex)
988 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex < b.vertex; } );
989 QList<QgsVertexId> circularVerticesToDelete;
990 circularVerticesToDelete.reserve( vertices.size() );
991
992 QListIterator<QgsVertexId> curveVerticesIt( vertices );
993
994 // search for odd vertices (middle vertices of an arc)
995 for ( size_t i = vertices.size(); i-- > 0; )
996 {
997 const QgsVertexId curveVertexId = vertices.at( i );
998
999 // check if a middle vertex of an arc
1000 if ( curveVertexId.vertex % 2 == 1 )
1001 {
1002 // check if neighbouring vertices are also to be deleted
1003 // if so, we just add this vertex to the list and continue iterating
1004 if ( !circularVerticesToDelete.isEmpty() )
1005 {
1006 if ( curveVertexId.vertex == circularVerticesToDelete.last().vertex - 1 )
1007 {
1008 circularVerticesToDelete.append( curveVertexId );
1009 continue;
1010 }
1011 }
1012 else if ( i != 0 && curveVertexId.vertex - 1 == vertices.at( i - 1 ).vertex )
1013 {
1014 circularVerticesToDelete.append( curveVertexId );
1015 continue;
1016 }
1017
1018 // we found a middle vertex of an arc and none of its neighbours are to be deleted
1019 // we need to handle special case of middle vertex of an arc deletion
1020 // first we delete all the vertices that come before it in this circularstring
1021 if ( !circularVerticesToDelete.isEmpty() )
1022 {
1023 if ( !curve->deleteVertices( QSet<QgsVertexId>( circularVerticesToDelete.begin(), circularVerticesToDelete.end() ) ) )
1024 {
1025 Q_ASSERT( false ); // shouldn't happen after all the checks
1026 return false;
1027 }
1028 }
1029 circularVerticesToDelete.clear();
1030
1031 // next, we remove that arc and replace it with a linestring that skips the middle vertex
1033 circularString->points( points );
1034
1035 removeCurve( curveId );
1036
1037 if ( curveVertexId.vertex < points.length() - 2 )
1038 {
1039 auto curveC = std::make_unique<QgsCircularString>();
1040 curveC->setPoints( points.mid( curveVertexId.vertex + 1 ) );
1041 mCurves.insert( curveId, curveC.release() );
1042 }
1043
1044 const QgsPointSequence partB = QgsPointSequence() << points[curveVertexId.vertex - 1] << points[curveVertexId.vertex + 1];
1045 auto curveB = std::make_unique<QgsLineString>();
1046 curveB->setPoints( partB );
1047 mCurves.insert( curveId, curveB.release() );
1048 curve = mCurves.at( curveId );
1049
1050 if ( curveVertexId.vertex > 1 )
1051 {
1052 auto curveA = std::make_unique<QgsCircularString>();
1053 curveA->setPoints( points.mid( 0, curveVertexId.vertex ) );
1054 mCurves.insert( curveId, curveA.release() );
1055 }
1056 curve = mCurves.at( curveId ); // we need to get the new curve
1057 circularString = qgsgeometry_cast<const QgsCircularString *>( curve );
1058
1059 continue;
1060 }
1061
1062 // not a middle vertex of an arc
1063 circularVerticesToDelete.append( curveVertexId );
1064 }
1065
1066 // remove any remaining circular vertices to delete
1067 if ( !circularVerticesToDelete.isEmpty() )
1068 {
1069 if ( !curve->deleteVertices( QSet<QgsVertexId>( circularVerticesToDelete.begin(), circularVerticesToDelete.end() ) ) )
1070 {
1071 Q_ASSERT( false );
1072 return false;
1073 }
1074 }
1075 continue; // circularstring handled, continue to next curve
1076 }
1077
1078 if ( !curve->deleteVertices( QSet<QgsVertexId>( vertices.begin(), vertices.end() ) ) )
1079 {
1080 Q_ASSERT( false );
1081 return false;
1082 }
1083 }
1084
1085 // remove any empty curves
1086 for ( int i = mCurves.size() - 1; i >= 0; i-- )
1087 {
1088 QgsCurve *curve = mCurves.at( i );
1089 if ( curve->numPoints() == 0 )
1090 {
1091 removeCurve( i );
1092 }
1093 }
1094
1095 if ( mCurves.isEmpty() )
1096 {
1097 clearCache();
1098 return true;
1099 }
1100
1101 // ensure all curves are connected
1102 for ( size_t i = mCurves.size() - 1; i > 0; i-- )
1103 {
1104 QgsCurve *curve = mCurves.at( i );
1105 QgsCurve *previousCurve = mCurves.at( i - 1 );
1106 if ( previousCurve->endPoint() != curve->startPoint() )
1107 {
1108 QgsLineString *line = new QgsLineString();
1109 line->insertVertex( QgsVertexId( 0, 0, 0 ), previousCurve->endPoint() );
1110 line->insertVertex( QgsVertexId( 0, 0, 1 ), curve->startPoint() );
1111 mCurves.insert( i, line );
1112 }
1113 }
1114
1115 condenseCurves(); // merge consecutive LineStrings and CircularStrings
1116 clearCache();
1117 return true;
1118}
1119
1120QVector< QPair<int, QgsVertexId> > QgsCompoundCurve::curveVertexId( QgsVertexId id ) const
1121{
1122 QVector< QPair<int, QgsVertexId> > curveIds;
1123
1124 int currentVertexIndex = 0;
1125 for ( int i = 0; i < mCurves.size(); ++i )
1126 {
1127 int increment = mCurves.at( i )->numPoints() - 1;
1128 if ( id.vertex >= currentVertexIndex && id.vertex <= currentVertexIndex + increment )
1129 {
1130 int curveVertexId = id.vertex - currentVertexIndex;
1131 QgsVertexId vid;
1132 vid.part = 0;
1133 vid.ring = 0;
1134 vid.vertex = curveVertexId;
1135 curveIds.append( qMakePair( i, vid ) );
1136 if ( curveVertexId == increment && i < ( mCurves.size() - 1 ) ) //add first vertex of next curve
1137 {
1138 vid.vertex = 0;
1139 curveIds.append( qMakePair( i + 1, vid ) );
1140 }
1141 break;
1142 }
1143 else if ( id.vertex >= currentVertexIndex && id.vertex == currentVertexIndex + increment + 1 && i == ( mCurves.size() - 1 ) )
1144 {
1145 int curveVertexId = id.vertex - currentVertexIndex;
1146 QgsVertexId vid;
1147 vid.part = 0;
1148 vid.ring = 0;
1149 vid.vertex = curveVertexId;
1150 curveIds.append( qMakePair( i, vid ) );
1151 break;
1152 }
1153 currentVertexIndex += increment;
1154 }
1155
1156 return curveIds;
1157}
1158
1160{
1161 // First we find out the sub-curves that are contain that vertex.
1162
1163 // If there is more than one, it means the vertex was at the beginning or end
1164 // of an arc, which we don't support.
1165
1166 // If there is exactly one, we may either be on a LineString, or on a CircularString.
1167
1168 // If on CircularString, we need to check if the vertex is a CurveVertex (odd index).
1169 // If so, we split the subcurve at vertex -1 and +1, , drop the middle part and insert a LineString/CircularString
1170 // instead with the same points.
1171
1172 // At the end, we call condenseCurves() to merge successible line/circular strings
1173
1174 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
1175
1176 // We cannot convert points at start/end of subcurves
1177 if ( curveIds.length() != 1 )
1178 return false;
1179
1180 int curveId = curveIds[0].first;
1181 QgsVertexId subVertexId = curveIds[0].second;
1182 QgsCurve *curve = mCurves[curveId];
1183
1184 // We cannot convert first/last point of curve
1185 if ( subVertexId.vertex == 0 || subVertexId.vertex == curve->numPoints() - 1 )
1186 return false;
1187
1188 if ( const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString *>( curve ) )
1189 {
1190 // If it's a circular string, we convert to LineString
1191
1192 // We cannot convert start/end points of arcs
1193 if ( subVertexId.vertex % 2 == 0 ) // for some reason, subVertexId.type is always SegmentVertex...
1194 return false;
1195
1197 circularString->points( points );
1198
1199 const QgsPointSequence partA = points.mid( 0, subVertexId.vertex );
1200 const QgsPointSequence partB = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex] << points[subVertexId.vertex + 1];
1201 const QgsPointSequence partC = points.mid( subVertexId.vertex + 1 );
1202
1203 auto curveA = std::make_unique<QgsCircularString>();
1204 curveA->setPoints( partA );
1205 auto curveB = std::make_unique<QgsLineString>();
1206 curveB->setPoints( partB );
1207 auto curveC = std::make_unique<QgsCircularString>();
1208 curveC->setPoints( partC );
1209
1210 removeCurve( curveId );
1211 if ( subVertexId.vertex < points.length() - 2 )
1212 mCurves.insert( curveId, curveC.release() );
1213 mCurves.insert( curveId, curveB.release() );
1214 if ( subVertexId.vertex > 1 )
1215 mCurves.insert( curveId, curveA.release() );
1216 }
1217 else if ( const QgsLineString *lineString = dynamic_cast<const QgsLineString *>( curve ) )
1218 {
1219 // If it's a linestring, we split and insert a curve
1220
1222 lineString->points( points );
1223
1224 const QgsPointSequence partA = points.mid( 0, subVertexId.vertex );
1225 const QgsPointSequence partB = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex] << points[subVertexId.vertex + 1];
1226 const QgsPointSequence partC = points.mid( subVertexId.vertex + 1 );
1227
1228 auto curveA = std::make_unique<QgsLineString>();
1229 curveA->setPoints( partA );
1230 auto curveB = std::make_unique<QgsCircularString>();
1231 curveB->setPoints( partB );
1232 auto curveC = std::make_unique<QgsLineString>();
1233 curveC->setPoints( partC );
1234
1235 removeCurve( curveId );
1236 if ( subVertexId.vertex < points.length() - 2 )
1237 mCurves.insert( curveId, curveC.release() );
1238 mCurves.insert( curveId, curveB.release() );
1239 if ( subVertexId.vertex > 1 )
1240 mCurves.insert( curveId, curveA.release() );
1241 }
1242
1243 // We merge consecutive LineStrings
1245
1246 clearCache();
1247 return true;
1248}
1249
1250
1251double QgsCompoundCurve::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1252{
1253 return QgsGeometryUtils::closestSegmentFromComponents( mCurves, QgsGeometryUtils::Vertex, pt, segmentPt, vertexAfter, leftOf, epsilon );
1254}
1255
1256bool QgsCompoundCurve::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
1257{
1258 int currentVertexId = 0;
1259 for ( int j = 0; j < mCurves.size(); ++j )
1260 {
1261 int nCurvePoints = mCurves.at( j )->numPoints();
1262 if ( ( node - currentVertexId ) < nCurvePoints )
1263 {
1264 return ( mCurves.at( j )->pointAt( node - currentVertexId, point, type ) );
1265 }
1266 currentVertexId += ( nCurvePoints - 1 );
1267 }
1268 return false;
1269}
1270
1271double QgsCompoundCurve::xAt( int index ) const
1272{
1273 int currentVertexId = 0;
1274 for ( int j = 0; j < mCurves.size(); ++j )
1275 {
1276 int nCurvePoints = mCurves.at( j )->numPoints();
1277 if ( ( index - currentVertexId ) < nCurvePoints )
1278 {
1279 return mCurves.at( j )->xAt( index - currentVertexId );
1280 }
1281 currentVertexId += ( nCurvePoints - 1 );
1282 }
1283 return 0.0;
1284}
1285
1286double QgsCompoundCurve::yAt( int index ) const
1287{
1288 int currentVertexId = 0;
1289 for ( int j = 0; j < mCurves.size(); ++j )
1290 {
1291 int nCurvePoints = mCurves.at( j )->numPoints();
1292 if ( ( index - currentVertexId ) < nCurvePoints )
1293 {
1294 return mCurves.at( j )->yAt( index - currentVertexId );
1295 }
1296 currentVertexId += ( nCurvePoints - 1 );
1297 }
1298 return 0.0;
1299}
1300
1301double QgsCompoundCurve::zAt( int index ) const
1302{
1303 int currentVertexId = 0;
1304 for ( int j = 0; j < mCurves.size(); ++j )
1305 {
1306 int nCurvePoints = mCurves.at( j )->numPoints();
1307 if ( ( index - currentVertexId ) < nCurvePoints )
1308 {
1309 return mCurves.at( j )->zAt( index - currentVertexId );
1310 }
1311 currentVertexId += ( nCurvePoints - 1 );
1312 }
1313 return 0.0;
1314}
1315
1316double QgsCompoundCurve::mAt( int index ) const
1317{
1318 int currentVertexId = 0;
1319 for ( int j = 0; j < mCurves.size(); ++j )
1320 {
1321 int nCurvePoints = mCurves.at( j )->numPoints();
1322 if ( ( index - currentVertexId ) < nCurvePoints )
1323 {
1324 return mCurves.at( j )->mAt( index - currentVertexId );
1325 }
1326 currentVertexId += ( nCurvePoints - 1 );
1327 }
1328 return 0.0;
1329}
1330
1332{
1333 bool res = true;
1334 for ( QgsCurve *curve : std::as_const( mCurves ) )
1335 {
1336 if ( !curve->transform( transformer ) )
1337 {
1338 res = false;
1339 break;
1340 }
1341
1342 if ( feedback && feedback->isCanceled() )
1343 {
1344 res = false;
1345 break;
1346 }
1347 }
1348 clearCache();
1349 return res;
1350}
1351
1352void QgsCompoundCurve::filterVertices( const std::function<bool( const QgsPoint & )> &filter )
1353{
1354 for ( QgsCurve *curve : std::as_const( mCurves ) )
1355 {
1356 curve->filterVertices( filter );
1357 }
1358 clearCache();
1359}
1360
1361void QgsCompoundCurve::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
1362{
1363 for ( QgsCurve *curve : std::as_const( mCurves ) )
1364 {
1365 curve->transformVertices( transform );
1366 }
1367 clearCache();
1368}
1369
1370std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsCompoundCurve::splitCurveAtVertex( int index ) const
1371{
1372 if ( mCurves.empty() )
1373 return std::make_tuple( std::make_unique< QgsCompoundCurve >(), std::make_unique< QgsCompoundCurve >() );
1374
1375 int curveStart = 0;
1376
1377 auto curve1 = std::make_unique< QgsCompoundCurve >();
1378 std::unique_ptr< QgsCompoundCurve > curve2;
1379
1380 for ( const QgsCurve *curve : mCurves )
1381 {
1382 const int curveSize = curve->numPoints();
1383 if ( !curve2 && index < curveStart + curveSize )
1384 {
1385 // split the curve
1386 auto [p1, p2] = curve->splitCurveAtVertex( index - curveStart );
1387 if ( !p1->isEmpty() )
1388 curve1->addCurve( p1.release() );
1389
1390 curve2 = std::make_unique< QgsCompoundCurve >();
1391 if ( !p2->isEmpty() )
1392 curve2->addCurve( p2.release() );
1393 }
1394 else
1395 {
1396 if ( curve2 )
1397 curve2->addCurve( curve->clone() );
1398 else
1399 curve1->addCurve( curve->clone() );
1400 }
1401
1402 // subtract 1 here, because the next curve will start with the same
1403 // vertex as this curve ended at
1404 curveStart += curve->numPoints() - 1;
1405 }
1406
1407 return std::make_tuple( std::move( curve1 ), curve2 ? std::move( curve2 ) : std::make_unique< QgsCompoundCurve >() );
1408}
1409
1410void QgsCompoundCurve::sumUpArea( double &sum ) const
1411{
1413 {
1414 sum += mSummedUpArea;
1415 return;
1416 }
1417
1418 mSummedUpArea = 0;
1419 for ( const QgsCurve *curve : mCurves )
1420 {
1421 curve->sumUpArea( mSummedUpArea );
1422 }
1424 sum += mSummedUpArea;
1425}
1426
1427void QgsCompoundCurve::sumUpArea3D( double &sum ) const
1428{
1430 {
1431 sum += mSummedUpArea3D;
1432 return;
1433 }
1434
1435 mSummedUpArea3D = 0;
1436 for ( const QgsCurve *curve : mCurves )
1437 {
1438 curve->sumUpArea3D( mSummedUpArea3D );
1439 }
1441 sum += mSummedUpArea3D;
1442}
1443
1445{
1446 if ( numPoints() < 1 || isClosed() )
1447 {
1448 return;
1449 }
1450 addVertex( startPoint() );
1451}
1452
1454{
1455 for ( const QgsCurve *curve : mCurves )
1456 {
1457 if ( curve->hasCurvedSegments() )
1458 {
1459 return true;
1460 }
1461 }
1462 return false;
1463}
1464
1466{
1467 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( vertex );
1468 if ( curveIds.size() == 1 )
1469 {
1470 QgsCurve *curve = mCurves[curveIds.at( 0 ).first];
1471 return curve->vertexAngle( curveIds.at( 0 ).second );
1472 }
1473 else if ( curveIds.size() > 1 )
1474 {
1475 QgsCurve *curve1 = mCurves[curveIds.at( 0 ).first];
1476 QgsCurve *curve2 = mCurves[curveIds.at( 1 ).first];
1477 double angle1 = curve1->vertexAngle( curveIds.at( 0 ).second );
1478 double angle2 = curve2->vertexAngle( curveIds.at( 1 ).second );
1479 return QgsGeometryUtilsBase::averageAngle( angle1, angle2 );
1480 }
1481 else
1482 {
1483 return 0.0;
1484 }
1485}
1486
1488{
1489 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( startVertex );
1490 double length = 0.0;
1491 for ( auto it = curveIds.constBegin(); it != curveIds.constEnd(); ++it )
1492 {
1493 length += mCurves.at( it->first )->segmentLength( it->second );
1494 }
1495 return length;
1496}
1497
1499{
1501 for ( int i = mCurves.count() - 1; i >= 0; --i )
1502 {
1503 QgsCurve *reversedCurve = mCurves.at( i )->reversed();
1504 clone->addCurve( reversedCurve );
1505 }
1506 return clone;
1507}
1508
1509QgsPoint *QgsCompoundCurve::interpolatePoint( const double distance ) const
1510{
1511 if ( distance < 0 )
1512 return nullptr;
1513
1514 double distanceTraversed = 0;
1515 for ( const QgsCurve *curve : mCurves )
1516 {
1517 const double thisCurveLength = curve->length();
1518 if ( distanceTraversed + thisCurveLength > distance || qgsDoubleNear( distanceTraversed + thisCurveLength, distance ) )
1519 {
1520 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1521 const double distanceToPoint = std::min( distance - distanceTraversed, thisCurveLength );
1522
1523 // point falls on this curve
1524 return curve->interpolatePoint( distanceToPoint );
1525 }
1526
1527 distanceTraversed += thisCurveLength;
1528 }
1529
1530 return nullptr;
1531}
1532
1533QgsCompoundCurve *QgsCompoundCurve::curveSubstring( double startDistance, double endDistance ) const
1534{
1535 if ( startDistance < 0 && endDistance < 0 )
1536 return createEmptyWithSameType();
1537
1538 endDistance = std::max( startDistance, endDistance );
1539 auto substring = std::make_unique< QgsCompoundCurve >();
1540
1541 double distanceTraversed = 0;
1542 for ( const QgsCurve *curve : mCurves )
1543 {
1544 const double thisCurveLength = curve->length();
1545 if ( distanceTraversed + thisCurveLength < startDistance )
1546 {
1547 // keep going - haven't found start yet, so no need to include this curve at all
1548 }
1549 else
1550 {
1551 std::unique_ptr< QgsCurve > part( curve->curveSubstring( startDistance - distanceTraversed, endDistance - distanceTraversed ) );
1552 if ( part )
1553 substring->addCurve( part.release() );
1554 }
1555
1556 distanceTraversed += thisCurveLength;
1557 if ( distanceTraversed > endDistance )
1558 break;
1559 }
1560
1561 return substring.release();
1562}
1563
1564bool QgsCompoundCurve::addZValue( double zValue )
1565{
1566 if ( QgsWkbTypes::hasZ( mWkbType ) )
1567 return false;
1568
1570
1571 for ( QgsCurve *curve : std::as_const( mCurves ) )
1572 {
1573 curve->addZValue( zValue );
1574 }
1575 clearCache();
1576 return true;
1577}
1578
1579bool QgsCompoundCurve::addMValue( double mValue )
1580{
1581 if ( QgsWkbTypes::hasM( mWkbType ) )
1582 return false;
1583
1585
1586 for ( QgsCurve *curve : std::as_const( mCurves ) )
1587 {
1588 curve->addMValue( mValue );
1589 }
1590 clearCache();
1591 return true;
1592}
1593
1595{
1596 if ( !QgsWkbTypes::hasZ( mWkbType ) )
1597 return false;
1598
1600 for ( QgsCurve *curve : std::as_const( mCurves ) )
1601 {
1602 curve->dropZValue();
1603 }
1604 clearCache();
1605 return true;
1606}
1607
1609{
1610 if ( !QgsWkbTypes::hasM( mWkbType ) )
1611 return false;
1612
1614 for ( QgsCurve *curve : std::as_const( mCurves ) )
1615 {
1616 curve->dropMValue();
1617 }
1618 clearCache();
1619 return true;
1620}
1621
1623{
1624 for ( QgsCurve *curve : std::as_const( mCurves ) )
1625 {
1626 curve->swapXy();
1627 }
1628 clearCache();
1629}
1630
1632{
1633 // Ensure fromVertex < toVertex for simplicity
1634 if ( fromVertex.vertex > toVertex.vertex )
1635 {
1636 return distanceBetweenVertices( toVertex, fromVertex );
1637 }
1638
1639 // Convert QgsVertexId to simple vertex numbers for compound curves (single ring, single part)
1640 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
1641 return -1.0;
1642
1643 const int fromVertexNumber = fromVertex.vertex;
1644 const int toVertexNumber = toVertex.vertex;
1645
1646 const int totalVertices = numPoints();
1647 if ( fromVertexNumber < 0 || fromVertexNumber >= totalVertices || toVertexNumber < 0 || toVertexNumber >= totalVertices )
1648 return -1.0;
1649
1650 if ( fromVertexNumber == toVertexNumber )
1651 return 0.0;
1652
1653 double totalDistance = 0.0;
1654
1655 // Find which curves contain our vertices and accumulate distances
1656 int currentVertexId = 0;
1657 int fromCurve = -1, toCurve = -1;
1658 int fromCurveVertex = -1, toCurveVertex = -1;
1659
1660 // First pass: find which curves contain from and to vertices
1661 for ( int j = 0; j < mCurves.size(); ++j )
1662 {
1663 int nCurvePoints = mCurves.at( j )->numPoints();
1664
1665 // Check if fromVertex is in this curve
1666 if ( fromCurve == -1 && fromVertexNumber >= currentVertexId && fromVertexNumber < currentVertexId + nCurvePoints )
1667 {
1668 fromCurve = j;
1669 fromCurveVertex = fromVertexNumber - currentVertexId;
1670 }
1671
1672 // Check if toVertex is in this curve
1673 if ( toCurve == -1 && toVertexNumber >= currentVertexId && toVertexNumber < currentVertexId + nCurvePoints )
1674 {
1675 toCurve = j;
1676 toCurveVertex = toVertexNumber - currentVertexId;
1677 break;
1678 }
1679
1680 currentVertexId += ( nCurvePoints - 1 ); // Subtract 1 because curves share endpoints
1681 }
1682
1683 if ( fromCurve == -1 || toCurve == -1 )
1684 return -1.0; // Invalid vertex IDs
1685
1686 if ( fromCurve == toCurve )
1687 {
1688 // Both vertices are on the same curve
1689 QgsVertexId fromId( 0, 0, fromCurveVertex );
1690 QgsVertexId toId( 0, 0, toCurveVertex );
1691 return mCurves.at( fromCurve )->distanceBetweenVertices( fromId, toId );
1692 }
1693 else
1694 {
1695 // Vertices are on different curves - accumulate distances across multiple curves
1696
1697 // Distance from fromVertex to end of its curve
1698 if ( fromCurveVertex < mCurves.at( fromCurve )->numPoints() - 1 )
1699 {
1700 QgsVertexId fromId( 0, 0, fromCurveVertex );
1701 QgsVertexId endId( 0, 0, mCurves.at( fromCurve )->numPoints() - 1 );
1702 totalDistance += mCurves.at( fromCurve )->distanceBetweenVertices( fromId, endId );
1703 }
1704
1705 // Distance of complete intermediate curves
1706 for ( int j = fromCurve + 1; j < toCurve; ++j )
1707 {
1708 totalDistance += mCurves.at( j )->length();
1709 }
1710
1711 // Distance from start of toCurve to toVertex
1712 if ( toCurveVertex > 0 )
1713 {
1714 QgsVertexId startId( 0, 0, 0 );
1715 QgsVertexId toId( 0, 0, toCurveVertex );
1716 totalDistance += mCurves.at( toCurve )->distanceBetweenVertices( startId, toId );
1717 }
1718 }
1719
1720 return totalDistance;
1721}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2197
VertexType
Types of vertex.
Definition qgis.h:3247
GeoJsonProfile
GeoJson export Profile according to OGC Features and Geometries JSON - Part 1: Core https://docs....
Definition qgis.h:5032
@ Legacy
Legacy GeoJson profile used in QGIS prior to 4.2, which included some non-standard extensions and dev...
Definition qgis.h:5033
@ Rfc7946
GeoJson profile compliant with RFC7946 standard "http://www.opengis.net/def/profile/OGC/0/rfc7946".
Definition qgis.h:5034
@ JsonFg
GeoJson profile from OGC Features and Geometries JSON Part 1: core "http://www.opengis....
Definition qgis.h:5035
@ JsonFgPlus
GeoJson profile from OGC Features and Geometries JSON Part 1: core "http://www.opengis....
Definition qgis.h:5036
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:2832
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
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
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:8002
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7368
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:94
int ring
Ring number.
Definition qgsvertexid.h:97