QGIS API Documentation 4.1.0-Master (31622b25bb0)
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 ) const
348{
349 // GeoJSON does not support curves
350 std::unique_ptr< QgsLineString > line( curveToLine() );
351 return line->asJsonObject( precision );
352}
353
355{
356 double length = 0;
357 for ( const QgsCurve *curve : mCurves )
358 {
359 length += curve->length();
360 }
361 return length;
362}
363
365{
366 if ( mCurves.empty() )
367 {
368 return QgsPoint();
369 }
370 return mCurves.at( 0 )->startPoint();
371}
372
374{
375 if ( mCurves.empty() )
376 {
377 return QgsPoint();
378 }
379 return mCurves.at( mCurves.size() - 1 )->endPoint();
380}
381
383{
384 pts.clear();
385 if ( mCurves.empty() )
386 {
387 return;
388 }
389
390 mCurves[0]->points( pts );
391 for ( int i = 1; i < mCurves.size(); ++i )
392 {
393 QgsPointSequence pList;
394 mCurves[i]->points( pList );
395 pList.removeFirst(); //first vertex already added in previous line
396 pts.append( pList );
397 }
398}
399
401{
402 int nPoints = 0;
403 int nCurves = mCurves.size();
404 if ( nCurves < 1 )
405 {
406 return 0;
407 }
408
409 for ( int i = 0; i < nCurves; ++i )
410 {
411 nPoints += mCurves.at( i )->numPoints() - 1; //last vertex is equal to first of next section
412 }
413 nPoints += 1; //last vertex was removed above
414 return nPoints;
415}
416
418{
419 if ( mCurves.isEmpty() )
420 return true;
421
422 for ( QgsCurve *curve : mCurves )
423 {
424 if ( !curve->isEmpty() )
425 return false;
426 }
427 return true;
428}
429
431{
432 if ( mCurves.isEmpty() )
433 return true;
434
435 for ( int i = 0; i < mCurves.size(); ++i )
436 {
437 if ( !mCurves[i]->isValid( error, flags ) )
438 {
439 error = QObject::tr( "Curve[%1]: %2" ).arg( i + 1 ).arg( error );
440 return false;
441 }
442 }
443 return QgsCurve::isValid( error, flags );
444}
445
446int QgsCompoundCurve::indexOf( const QgsPoint &point ) const
447{
448 int curveStart = 0;
449 for ( const QgsCurve *curve : mCurves )
450 {
451 const int curveIndex = curve->indexOf( point );
452 if ( curveIndex >= 0 )
453 return curveStart + curveIndex;
454 // subtract 1 here, because the next curve will start with the same
455 // vertex as this curve ended at
456 curveStart += curve->numPoints() - 1;
457 }
458 return -1;
459}
460
462{
463 QgsLineString *line = new QgsLineString();
464 std::unique_ptr< QgsLineString > currentLine;
465 for ( const QgsCurve *curve : mCurves )
466 {
467 currentLine.reset( curve->curveToLine( tolerance, toleranceType ) );
468 line->append( currentLine.get() );
469 }
470 return line;
471}
472
473QgsCompoundCurve *QgsCompoundCurve::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const
474{
475 std::unique_ptr<QgsCompoundCurve> result( createEmptyWithSameType() );
476
477 for ( QgsCurve *curve : mCurves )
478 {
479 std::unique_ptr<QgsCurve> gridified( static_cast< QgsCurve * >( curve->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) );
480 if ( gridified )
481 {
482 result->mCurves.append( gridified.release() );
483 }
484 }
485
486 if ( result->mCurves.empty() )
487 return nullptr;
488 else
489 return result.release();
490}
491
493{
494 std::unique_ptr< QgsLineString > line( curveToLine() );
495 return line->simplifyByDistance( tolerance );
496}
497
498bool QgsCompoundCurve::removeDuplicateNodes( double epsilon, bool useZValues )
499{
500 bool result = false;
501 const QVector< QgsCurve * > curves = mCurves;
502 int i = 0;
503 QgsPoint lastEnd;
504 for ( QgsCurve *curve : curves )
505 {
506 result = curve->removeDuplicateNodes( epsilon, useZValues ) || result;
507 if ( curve->numPoints() == 0 || qgsDoubleNear( curve->length(), 0.0, epsilon ) )
508 {
509 // empty curve, remove it
510 delete mCurves.takeAt( i );
511 result = true;
512 }
513 else
514 {
515 // ensure this line starts exactly where previous line ended
516 if ( i > 0 )
517 {
518 curve->moveVertex( QgsVertexId( -1, -1, 0 ), lastEnd );
519 }
520 lastEnd = curve->vertexAt( QgsVertexId( -1, -1, curve->numPoints() - 1 ) );
521 }
522 i++;
523 }
524 return result;
525}
526
528{
529 if ( mCurves.empty() )
530 return false;
531
532 // if we already have the bounding box calculated, then this check is trivial!
533 if ( !mBoundingBox.isNull() )
534 {
535 return mBoundingBox.intersects( box3d );
536 }
537
538 // otherwise loop through each member curve and test the bounding box intersection.
539 // This gives us a chance to use optimisations which may be present on the individual
540 // curve subclasses, and at worst it will cause a calculation of the bounding box
541 // of each individual member curve which we would have to do anyway... (and these
542 // bounding boxes are cached, so would be reused without additional expense)
543 for ( const QgsCurve *curve : mCurves )
544 {
545 if ( curve->boundingBoxIntersects( box3d ) )
546 return true;
547 }
548
549 // even if we don't intersect the bounding box of any member curves, we may still intersect the
550 // bounding box of the overall compound curve.
551 // so here we fall back to the non-optimised base class check which has to first calculate
552 // the overall bounding box of the compound curve..
554}
555
557{
558 if ( mCurves.size() == 1 )
559 return mCurves.at( 0 );
560 else
561 return this;
562}
563
565{
566 if ( i < 0 || i >= mCurves.size() )
567 {
568 return nullptr;
569 }
570 return mCurves.at( i );
571}
572
573void QgsCompoundCurve::addCurve( QgsCurve *c, const bool extendPrevious )
574{
575 if ( !c )
576 return;
577
578 if ( mCurves.empty() )
579 {
581 }
582
583 if ( QgsWkbTypes::hasZ( mWkbType ) && !QgsWkbTypes::hasZ( c->wkbType() ) )
584 {
585 c->addZValue();
586 }
587 else if ( !QgsWkbTypes::hasZ( mWkbType ) && QgsWkbTypes::hasZ( c->wkbType() ) )
588 {
589 c->dropZValue();
590 }
591 if ( QgsWkbTypes::hasM( mWkbType ) && !QgsWkbTypes::hasM( c->wkbType() ) )
592 {
593 c->addMValue();
594 }
595 else if ( !QgsWkbTypes::hasM( mWkbType ) && QgsWkbTypes::hasM( c->wkbType() ) )
596 {
597 c->dropMValue();
598 }
599
600 QgsLineString *previousLineString = !mCurves.empty() ? qgsgeometry_cast< QgsLineString * >( mCurves.constLast() ) : nullptr;
602 const bool canExtendPrevious = extendPrevious && previousLineString && newLineString;
603 if ( canExtendPrevious )
604 {
605 previousLineString->append( newLineString );
606 // we are taking ownership, so delete the input curve
607 delete c;
608 c = nullptr;
609 }
610 else
611 {
612 mCurves.append( c );
613 }
614
615 clearCache();
616}
617
619{
620 if ( i < 0 || i >= mCurves.size() )
621 {
622 return;
623 }
624
625 delete mCurves.takeAt( i );
626 clearCache();
627}
628
630{
631 if ( mCurves.isEmpty() || mWkbType == Qgis::WkbType::Unknown )
632 {
634 }
635
636 //is last curve QgsLineString
637 QgsCurve *lastCurve = nullptr;
638 if ( !mCurves.isEmpty() )
639 {
640 lastCurve = mCurves.at( mCurves.size() - 1 );
641 }
642
643 QgsLineString *line = nullptr;
644 if ( !lastCurve || QgsWkbTypes::flatType( lastCurve->wkbType() ) != Qgis::WkbType::LineString )
645 {
646 line = new QgsLineString();
647 mCurves.append( line );
648 if ( lastCurve )
649 {
650 line->addVertex( lastCurve->endPoint() );
651 }
652 lastCurve = line;
653 }
654 else //create new QgsLineString* with point in it
655 {
656 line = static_cast<QgsLineString *>( lastCurve );
657 }
658 line->addVertex( pt );
659 clearCache();
660}
661
663{
664 QgsCurve *lastCurve = nullptr;
665 QVector< QgsCurve * > newCurves;
666 newCurves.reserve( mCurves.size() );
667 for ( QgsCurve *curve : std::as_const( mCurves ) )
668 {
669 if ( lastCurve && lastCurve->wkbType() == curve->wkbType() )
670 {
671 if ( QgsLineString *ls = qgsgeometry_cast< QgsLineString * >( lastCurve ) )
672 {
673 ls->append( qgsgeometry_cast< QgsLineString * >( curve ) );
674 delete curve;
675 }
677 {
678 cs->append( qgsgeometry_cast< QgsCircularString * >( curve ) );
679 delete curve;
680 }
681 }
682 else
683 {
684 lastCurve = curve;
685 newCurves << curve;
686 }
687 }
688 mCurves = newCurves;
689}
690
691void QgsCompoundCurve::draw( QPainter &p ) const
692{
693 for ( const QgsCurve *curve : mCurves )
694 {
695 curve->draw( p );
696 }
697}
698
700{
701 for ( QgsCurve *curve : std::as_const( mCurves ) )
702 {
703 curve->transform( ct, d, transformZ );
704 }
705 clearCache();
706}
707
708void QgsCompoundCurve::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
709{
710 for ( QgsCurve *curve : std::as_const( mCurves ) )
711 {
712 curve->transform( t, zTranslate, zScale, mTranslate, mScale );
713 }
714 clearCache();
715}
716
717void QgsCompoundCurve::addToPainterPath( QPainterPath &path ) const
718{
719 QPainterPath pp;
720
721 for ( const QgsCurve *curve : mCurves )
722 {
723 if ( curve != mCurves.at( 0 ) && pp.currentPosition() != curve->startPoint().toQPointF() )
724 {
725 pp.lineTo( curve->startPoint().toQPointF() );
726 }
727 curve->addToPainterPath( pp );
728 }
729 path.addPath( pp );
730}
731
732void QgsCompoundCurve::drawAsPolygon( QPainter &p ) const
733{
734 QPainterPath pp;
735 for ( const QgsCurve *curve : mCurves )
736 {
737 if ( curve != mCurves.at( 0 ) && pp.currentPosition() != curve->startPoint().toQPointF() )
738 {
739 pp.lineTo( curve->startPoint().toQPointF() );
740 }
741 curve->addToPainterPath( pp );
742 }
743 p.drawPath( pp );
744}
745
747{
748 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
749 if ( curveIds.empty() )
750 {
751 return false;
752 }
753 int curveId = curveIds.at( 0 ).first;
754 if ( curveId >= mCurves.size() )
755 {
756 return false;
757 }
758
759 bool success = mCurves.at( curveId )->insertVertex( curveIds.at( 0 ).second, vertex );
760 if ( success )
761 {
762 clearCache(); //bbox changed
763 }
764 return success;
765}
766
767bool QgsCompoundCurve::moveVertex( QgsVertexId position, const QgsPoint &newPos )
768{
769 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
770 QVector< QPair<int, QgsVertexId> >::const_iterator idIt = curveIds.constBegin();
771 for ( ; idIt != curveIds.constEnd(); ++idIt )
772 {
773 mCurves.at( idIt->first )->moveVertex( idIt->second, newPos );
774 }
775
776 bool success = !curveIds.isEmpty();
777 if ( success )
778 {
779 clearCache(); //bbox changed
780 }
781 return success;
782}
783
785{
786 const QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
787 if ( curveIds.isEmpty() )
788 return false;
789
790 const int curveId = curveIds.at( 0 ).first;
791 QgsCurve *curve = mCurves.at( curveId );
792 const QgsVertexId subVertexId = curveIds.at( 0 ).second;
793
794 // We are on a vertex that belongs to one curve only
795 if ( curveIds.size() == 1 )
796 {
797 const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString *>( curve );
798 // If the vertex to delete is the middle vertex of a CircularString, we transform
799 // this CircularString into a LineString without the middle vertex
800 if ( circularString && subVertexId.vertex % 2 == 1 )
801 {
802 {
804 circularString->points( points );
805
806 removeCurve( curveId );
807
808 if ( subVertexId.vertex < points.length() - 2 )
809 {
810 auto curveC = std::make_unique<QgsCircularString>();
811 curveC->setPoints( points.mid( subVertexId.vertex + 1 ) );
812 mCurves.insert( curveId, curveC.release() );
813 }
814
815 const QgsPointSequence partB = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex + 1];
816 auto curveB = std::make_unique<QgsLineString>();
817 curveB->setPoints( partB );
818 mCurves.insert( curveId, curveB.release() );
819 curve = mCurves.at( curveId );
820
821 if ( subVertexId.vertex > 1 )
822 {
823 auto curveA = std::make_unique<QgsCircularString>();
824 curveA->setPoints( points.mid( 0, subVertexId.vertex ) );
825 mCurves.insert( curveId, curveA.release() );
826 }
827 }
828 }
829 else if ( !curve->deleteVertex( subVertexId ) )
830 {
831 clearCache(); //bbox may have changed
832 return false;
833 }
834 if ( curve->numPoints() == 0 )
835 {
836 removeCurve( curveId );
837 }
838 }
839 // We are on a vertex that belongs to two curves
840 else if ( curveIds.size() == 2 )
841 {
842 const int nextCurveId = curveIds.at( 1 ).first;
843 QgsCurve *nextCurve = mCurves.at( nextCurveId );
844 const QgsVertexId nextSubVertexId = curveIds.at( 1 ).second;
845
846 Q_ASSERT( nextCurveId == curveId + 1 );
847 Q_ASSERT( subVertexId.vertex == curve->numPoints() - 1 );
848 Q_ASSERT( nextSubVertexId.vertex == 0 );
849
850 // globals start and end points
851 const QgsPoint startPoint = curve->startPoint();
852 const QgsPoint endPoint = nextCurve->endPoint();
853
854 // delete the vertex on first curve
855 if ( !curve->deleteVertex( subVertexId ) )
856 {
857 clearCache(); //bbox may have changed
858 return false;
859 }
860
861 // delete the vertex on second curve
862 if ( !nextCurve->deleteVertex( nextSubVertexId ) )
863 {
864 clearCache(); //bbox may have changed
865 return false;
866 }
867
868 // if first curve is now empty and second is not then
869 // create a LineString to link from the global start point to the
870 // new start of the second curve and delete the first curve
871 if ( curve->numPoints() == 0 && nextCurve->numPoints() != 0 )
872 {
873 QgsPoint startPointOfSecond = nextCurve->startPoint();
874 removeCurve( curveId );
875 QgsLineString *line = new QgsLineString();
876 line->insertVertex( QgsVertexId( 0, 0, 0 ), startPoint );
877 line->insertVertex( QgsVertexId( 0, 0, 1 ), startPointOfSecond );
878 mCurves.insert( curveId, line );
879 }
880 // else, if the first curve is not empty and the second is
881 // then create a LineString to link from the new end of the first curve to the
882 // global end point and delete the first curve
883 else if ( curve->numPoints() != 0 && nextCurve->numPoints() == 0 )
884 {
885 QgsPoint endPointOfFirst = curve->endPoint();
886 removeCurve( nextCurveId );
887 QgsLineString *line = new QgsLineString();
888 line->insertVertex( QgsVertexId( 0, 0, 0 ), endPointOfFirst );
889 line->insertVertex( QgsVertexId( 0, 0, 1 ), endPoint );
890 mCurves.insert( nextCurveId, line );
891 }
892 // else, if both curves are empty then
893 // remove both curves and create a LineString to link
894 // the curves before and the curves after the whole geometry
895 else if ( curve->numPoints() == 0 && nextCurve->numPoints() == 0 )
896 {
897 removeCurve( nextCurveId );
898 removeCurve( curveId );
899 QgsLineString *line = new QgsLineString();
900 line->insertVertex( QgsVertexId( 0, 0, 0 ), startPoint );
901 line->insertVertex( QgsVertexId( 0, 0, 1 ), endPoint );
902 mCurves.insert( curveId, line );
903 }
904 // else, both curves still have vertices, create a LineString to link
905 // the curves if needed
906 else
907 {
908 QgsPoint endPointOfFirst = curve->endPoint();
909 QgsPoint startPointOfSecond = nextCurve->startPoint();
910 if ( endPointOfFirst != startPointOfSecond )
911 {
912 QgsLineString *line = new QgsLineString();
913 line->insertVertex( QgsVertexId( 0, 0, 0 ), endPointOfFirst );
914 line->insertVertex( QgsVertexId( 0, 0, 1 ), startPointOfSecond );
915 mCurves.insert( nextCurveId, line );
916 }
917 }
918 condenseCurves(); // We merge consecutive LineStrings and CircularStrings
919 }
920
921 bool success = !curveIds.isEmpty();
922 if ( success )
923 clearCache(); //bbox changed
924 return success;
925}
926
927bool QgsCompoundCurve::deleteVertices( const QSet<QgsVertexId> &positions )
928{
929 // we create a list of vertices to delete for each curve
930 QMap<int, QList<QgsVertexId >> curveVertices;
931 for ( QgsVertexId position : positions )
932 {
933 if ( !hasVertex( position ) )
934 {
935 return false;
936 }
937
938 const QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
939
940 if ( curveIds.isEmpty() )
941 return false;
942
943 const int firstCurveId = curveIds.at( 0 ).first;
944 const QgsVertexId firstCurveVertex = curveIds.at( 0 ).second;
945 curveVertices[firstCurveId].append( firstCurveVertex );
946 if ( curveIds.size() == 2 ) // vertex is shared between two curves
947 {
948 const int secondCurveId = curveIds.at( 1 ).first;
949 const QgsVertexId secondCurveVertex = curveIds.at( 1 ).second;
950 curveVertices[secondCurveId].append( secondCurveVertex );
951 }
952 }
953
954 // loop through the curves in reverse order and delete vertices
955 QMapIterator<int, QList<QgsVertexId >> curveVerticesIt( curveVertices );
956 curveVerticesIt.toBack();
957 while ( curveVerticesIt.hasPrevious() )
958 {
959 curveVerticesIt.previous();
960 const int curveId = curveVerticesIt.key();
961 QgsCurve *curve = mCurves.at( curveId );
962 QList<QgsVertexId> vertices = curveVerticesIt.value();
963
964 const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString *>( curve );
965 // If the vertex to delete is the middle vertex of a circularstring arc, we transform
966 // this circularstring arc into a linestring without the middle vertex
967 if ( circularString )
968 {
969 // we loop through the vertices to see if we need to handle special case
970 // of a middle vertex (see deleteVertex)
971 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex < b.vertex; } );
972 QList<QgsVertexId> circularVerticesToDelete;
973 circularVerticesToDelete.reserve( vertices.size() );
974
975 QListIterator<QgsVertexId> curveVerticesIt( vertices );
976
977 // search for odd vertices (middle vertices of an arc)
978 for ( size_t i = vertices.size(); i-- > 0; )
979 {
980 const QgsVertexId curveVertexId = vertices.at( i );
981
982 // check if a middle vertex of an arc
983 if ( curveVertexId.vertex % 2 == 1 )
984 {
985 // check if neighbouring vertices are also to be deleted
986 // if so, we just add this vertex to the list and continue iterating
987 if ( !circularVerticesToDelete.isEmpty() )
988 {
989 if ( curveVertexId.vertex == circularVerticesToDelete.last().vertex - 1 )
990 {
991 circularVerticesToDelete.append( curveVertexId );
992 continue;
993 }
994 }
995 else if ( i != 0 && curveVertexId.vertex - 1 == vertices.at( i - 1 ).vertex )
996 {
997 circularVerticesToDelete.append( curveVertexId );
998 continue;
999 }
1000
1001 // we found a middle vertex of an arc and none of its neighbours are to be deleted
1002 // we need to handle special case of middle vertex of an arc deletion
1003 // first we delete all the vertices that come before it in this circularstring
1004 if ( !circularVerticesToDelete.isEmpty() )
1005 {
1006 if ( !curve->deleteVertices( QSet<QgsVertexId>( circularVerticesToDelete.begin(), circularVerticesToDelete.end() ) ) )
1007 {
1008 Q_ASSERT( false ); // shouldn't happen after all the checks
1009 return false;
1010 }
1011 }
1012 circularVerticesToDelete.clear();
1013
1014 // next, we remove that arc and replace it with a linestring that skips the middle vertex
1016 circularString->points( points );
1017
1018 removeCurve( curveId );
1019
1020 if ( curveVertexId.vertex < points.length() - 2 )
1021 {
1022 auto curveC = std::make_unique<QgsCircularString>();
1023 curveC->setPoints( points.mid( curveVertexId.vertex + 1 ) );
1024 mCurves.insert( curveId, curveC.release() );
1025 }
1026
1027 const QgsPointSequence partB = QgsPointSequence() << points[curveVertexId.vertex - 1] << points[curveVertexId.vertex + 1];
1028 auto curveB = std::make_unique<QgsLineString>();
1029 curveB->setPoints( partB );
1030 mCurves.insert( curveId, curveB.release() );
1031 curve = mCurves.at( curveId );
1032
1033 if ( curveVertexId.vertex > 1 )
1034 {
1035 auto curveA = std::make_unique<QgsCircularString>();
1036 curveA->setPoints( points.mid( 0, curveVertexId.vertex ) );
1037 mCurves.insert( curveId, curveA.release() );
1038 }
1039 curve = mCurves.at( curveId ); // we need to get the new curve
1040 circularString = qgsgeometry_cast<const QgsCircularString *>( curve );
1041
1042 continue;
1043 }
1044
1045 // not a middle vertex of an arc
1046 circularVerticesToDelete.append( curveVertexId );
1047 }
1048
1049 // remove any remaining circular vertices to delete
1050 if ( !circularVerticesToDelete.isEmpty() )
1051 {
1052 if ( !curve->deleteVertices( QSet<QgsVertexId>( circularVerticesToDelete.begin(), circularVerticesToDelete.end() ) ) )
1053 {
1054 Q_ASSERT( false );
1055 return false;
1056 }
1057 }
1058 continue; // circularstring handled, continue to next curve
1059 }
1060
1061 if ( !curve->deleteVertices( QSet<QgsVertexId>( vertices.begin(), vertices.end() ) ) )
1062 {
1063 Q_ASSERT( false );
1064 return false;
1065 }
1066 }
1067
1068 // remove any empty curves
1069 for ( int i = mCurves.size() - 1; i >= 0; i-- )
1070 {
1071 QgsCurve *curve = mCurves.at( i );
1072 if ( curve->numPoints() == 0 )
1073 {
1074 removeCurve( i );
1075 }
1076 }
1077
1078 if ( mCurves.isEmpty() )
1079 {
1080 clearCache();
1081 return true;
1082 }
1083
1084 // ensure all curves are connected
1085 for ( size_t i = mCurves.size() - 1; i > 0; i-- )
1086 {
1087 QgsCurve *curve = mCurves.at( i );
1088 QgsCurve *previousCurve = mCurves.at( i - 1 );
1089 if ( previousCurve->endPoint() != curve->startPoint() )
1090 {
1091 QgsLineString *line = new QgsLineString();
1092 line->insertVertex( QgsVertexId( 0, 0, 0 ), previousCurve->endPoint() );
1093 line->insertVertex( QgsVertexId( 0, 0, 1 ), curve->startPoint() );
1094 mCurves.insert( i, line );
1095 }
1096 }
1097
1098 condenseCurves(); // merge consecutive LineStrings and CircularStrings
1099 clearCache();
1100 return true;
1101}
1102
1103QVector< QPair<int, QgsVertexId> > QgsCompoundCurve::curveVertexId( QgsVertexId id ) const
1104{
1105 QVector< QPair<int, QgsVertexId> > curveIds;
1106
1107 int currentVertexIndex = 0;
1108 for ( int i = 0; i < mCurves.size(); ++i )
1109 {
1110 int increment = mCurves.at( i )->numPoints() - 1;
1111 if ( id.vertex >= currentVertexIndex && id.vertex <= currentVertexIndex + increment )
1112 {
1113 int curveVertexId = id.vertex - currentVertexIndex;
1114 QgsVertexId vid;
1115 vid.part = 0;
1116 vid.ring = 0;
1117 vid.vertex = curveVertexId;
1118 curveIds.append( qMakePair( i, vid ) );
1119 if ( curveVertexId == increment && i < ( mCurves.size() - 1 ) ) //add first vertex of next curve
1120 {
1121 vid.vertex = 0;
1122 curveIds.append( qMakePair( i + 1, vid ) );
1123 }
1124 break;
1125 }
1126 else if ( id.vertex >= currentVertexIndex && id.vertex == currentVertexIndex + increment + 1 && i == ( mCurves.size() - 1 ) )
1127 {
1128 int curveVertexId = id.vertex - currentVertexIndex;
1129 QgsVertexId vid;
1130 vid.part = 0;
1131 vid.ring = 0;
1132 vid.vertex = curveVertexId;
1133 curveIds.append( qMakePair( i, vid ) );
1134 break;
1135 }
1136 currentVertexIndex += increment;
1137 }
1138
1139 return curveIds;
1140}
1141
1143{
1144 // First we find out the sub-curves that are contain that vertex.
1145
1146 // If there is more than one, it means the vertex was at the beginning or end
1147 // of an arc, which we don't support.
1148
1149 // If there is exactly one, we may either be on a LineString, or on a CircularString.
1150
1151 // If on CircularString, we need to check if the vertex is a CurveVertex (odd index).
1152 // If so, we split the subcurve at vertex -1 and +1, , drop the middle part and insert a LineString/CircularString
1153 // instead with the same points.
1154
1155 // At the end, we call condenseCurves() to merge successible line/circular strings
1156
1157 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
1158
1159 // We cannot convert points at start/end of subcurves
1160 if ( curveIds.length() != 1 )
1161 return false;
1162
1163 int curveId = curveIds[0].first;
1164 QgsVertexId subVertexId = curveIds[0].second;
1165 QgsCurve *curve = mCurves[curveId];
1166
1167 // We cannot convert first/last point of curve
1168 if ( subVertexId.vertex == 0 || subVertexId.vertex == curve->numPoints() - 1 )
1169 return false;
1170
1171 if ( const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString *>( curve ) )
1172 {
1173 // If it's a circular string, we convert to LineString
1174
1175 // We cannot convert start/end points of arcs
1176 if ( subVertexId.vertex % 2 == 0 ) // for some reason, subVertexId.type is always SegmentVertex...
1177 return false;
1178
1180 circularString->points( points );
1181
1182 const QgsPointSequence partA = points.mid( 0, subVertexId.vertex );
1183 const QgsPointSequence partB = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex] << points[subVertexId.vertex + 1];
1184 const QgsPointSequence partC = points.mid( subVertexId.vertex + 1 );
1185
1186 auto curveA = std::make_unique<QgsCircularString>();
1187 curveA->setPoints( partA );
1188 auto curveB = std::make_unique<QgsLineString>();
1189 curveB->setPoints( partB );
1190 auto curveC = std::make_unique<QgsCircularString>();
1191 curveC->setPoints( partC );
1192
1193 removeCurve( curveId );
1194 if ( subVertexId.vertex < points.length() - 2 )
1195 mCurves.insert( curveId, curveC.release() );
1196 mCurves.insert( curveId, curveB.release() );
1197 if ( subVertexId.vertex > 1 )
1198 mCurves.insert( curveId, curveA.release() );
1199 }
1200 else if ( const QgsLineString *lineString = dynamic_cast<const QgsLineString *>( curve ) )
1201 {
1202 // If it's a linestring, we split and insert a curve
1203
1205 lineString->points( points );
1206
1207 const QgsPointSequence partA = points.mid( 0, subVertexId.vertex );
1208 const QgsPointSequence partB = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex] << points[subVertexId.vertex + 1];
1209 const QgsPointSequence partC = points.mid( subVertexId.vertex + 1 );
1210
1211 auto curveA = std::make_unique<QgsLineString>();
1212 curveA->setPoints( partA );
1213 auto curveB = std::make_unique<QgsCircularString>();
1214 curveB->setPoints( partB );
1215 auto curveC = std::make_unique<QgsLineString>();
1216 curveC->setPoints( partC );
1217
1218 removeCurve( curveId );
1219 if ( subVertexId.vertex < points.length() - 2 )
1220 mCurves.insert( curveId, curveC.release() );
1221 mCurves.insert( curveId, curveB.release() );
1222 if ( subVertexId.vertex > 1 )
1223 mCurves.insert( curveId, curveA.release() );
1224 }
1225
1226 // We merge consecutive LineStrings
1228
1229 clearCache();
1230 return true;
1231}
1232
1233
1234double QgsCompoundCurve::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1235{
1236 return QgsGeometryUtils::closestSegmentFromComponents( mCurves, QgsGeometryUtils::Vertex, pt, segmentPt, vertexAfter, leftOf, epsilon );
1237}
1238
1239bool QgsCompoundCurve::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
1240{
1241 int currentVertexId = 0;
1242 for ( int j = 0; j < mCurves.size(); ++j )
1243 {
1244 int nCurvePoints = mCurves.at( j )->numPoints();
1245 if ( ( node - currentVertexId ) < nCurvePoints )
1246 {
1247 return ( mCurves.at( j )->pointAt( node - currentVertexId, point, type ) );
1248 }
1249 currentVertexId += ( nCurvePoints - 1 );
1250 }
1251 return false;
1252}
1253
1254double QgsCompoundCurve::xAt( int index ) const
1255{
1256 int currentVertexId = 0;
1257 for ( int j = 0; j < mCurves.size(); ++j )
1258 {
1259 int nCurvePoints = mCurves.at( j )->numPoints();
1260 if ( ( index - currentVertexId ) < nCurvePoints )
1261 {
1262 return mCurves.at( j )->xAt( index - currentVertexId );
1263 }
1264 currentVertexId += ( nCurvePoints - 1 );
1265 }
1266 return 0.0;
1267}
1268
1269double QgsCompoundCurve::yAt( int index ) const
1270{
1271 int currentVertexId = 0;
1272 for ( int j = 0; j < mCurves.size(); ++j )
1273 {
1274 int nCurvePoints = mCurves.at( j )->numPoints();
1275 if ( ( index - currentVertexId ) < nCurvePoints )
1276 {
1277 return mCurves.at( j )->yAt( index - currentVertexId );
1278 }
1279 currentVertexId += ( nCurvePoints - 1 );
1280 }
1281 return 0.0;
1282}
1283
1284double QgsCompoundCurve::zAt( int index ) const
1285{
1286 int currentVertexId = 0;
1287 for ( int j = 0; j < mCurves.size(); ++j )
1288 {
1289 int nCurvePoints = mCurves.at( j )->numPoints();
1290 if ( ( index - currentVertexId ) < nCurvePoints )
1291 {
1292 return mCurves.at( j )->zAt( index - currentVertexId );
1293 }
1294 currentVertexId += ( nCurvePoints - 1 );
1295 }
1296 return 0.0;
1297}
1298
1299double QgsCompoundCurve::mAt( int index ) const
1300{
1301 int currentVertexId = 0;
1302 for ( int j = 0; j < mCurves.size(); ++j )
1303 {
1304 int nCurvePoints = mCurves.at( j )->numPoints();
1305 if ( ( index - currentVertexId ) < nCurvePoints )
1306 {
1307 return mCurves.at( j )->mAt( index - currentVertexId );
1308 }
1309 currentVertexId += ( nCurvePoints - 1 );
1310 }
1311 return 0.0;
1312}
1313
1315{
1316 bool res = true;
1317 for ( QgsCurve *curve : std::as_const( mCurves ) )
1318 {
1319 if ( !curve->transform( transformer ) )
1320 {
1321 res = false;
1322 break;
1323 }
1324
1325 if ( feedback && feedback->isCanceled() )
1326 {
1327 res = false;
1328 break;
1329 }
1330 }
1331 clearCache();
1332 return res;
1333}
1334
1335void QgsCompoundCurve::filterVertices( const std::function<bool( const QgsPoint & )> &filter )
1336{
1337 for ( QgsCurve *curve : std::as_const( mCurves ) )
1338 {
1339 curve->filterVertices( filter );
1340 }
1341 clearCache();
1342}
1343
1344void QgsCompoundCurve::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
1345{
1346 for ( QgsCurve *curve : std::as_const( mCurves ) )
1347 {
1348 curve->transformVertices( transform );
1349 }
1350 clearCache();
1351}
1352
1353std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsCompoundCurve::splitCurveAtVertex( int index ) const
1354{
1355 if ( mCurves.empty() )
1356 return std::make_tuple( std::make_unique< QgsCompoundCurve >(), std::make_unique< QgsCompoundCurve >() );
1357
1358 int curveStart = 0;
1359
1360 auto curve1 = std::make_unique< QgsCompoundCurve >();
1361 std::unique_ptr< QgsCompoundCurve > curve2;
1362
1363 for ( const QgsCurve *curve : mCurves )
1364 {
1365 const int curveSize = curve->numPoints();
1366 if ( !curve2 && index < curveStart + curveSize )
1367 {
1368 // split the curve
1369 auto [p1, p2] = curve->splitCurveAtVertex( index - curveStart );
1370 if ( !p1->isEmpty() )
1371 curve1->addCurve( p1.release() );
1372
1373 curve2 = std::make_unique< QgsCompoundCurve >();
1374 if ( !p2->isEmpty() )
1375 curve2->addCurve( p2.release() );
1376 }
1377 else
1378 {
1379 if ( curve2 )
1380 curve2->addCurve( curve->clone() );
1381 else
1382 curve1->addCurve( curve->clone() );
1383 }
1384
1385 // subtract 1 here, because the next curve will start with the same
1386 // vertex as this curve ended at
1387 curveStart += curve->numPoints() - 1;
1388 }
1389
1390 return std::make_tuple( std::move( curve1 ), curve2 ? std::move( curve2 ) : std::make_unique< QgsCompoundCurve >() );
1391}
1392
1393void QgsCompoundCurve::sumUpArea( double &sum ) const
1394{
1396 {
1397 sum += mSummedUpArea;
1398 return;
1399 }
1400
1401 mSummedUpArea = 0;
1402 for ( const QgsCurve *curve : mCurves )
1403 {
1404 curve->sumUpArea( mSummedUpArea );
1405 }
1407 sum += mSummedUpArea;
1408}
1409
1410void QgsCompoundCurve::sumUpArea3D( double &sum ) const
1411{
1413 {
1414 sum += mSummedUpArea3D;
1415 return;
1416 }
1417
1418 mSummedUpArea3D = 0;
1419 for ( const QgsCurve *curve : mCurves )
1420 {
1421 curve->sumUpArea3D( mSummedUpArea3D );
1422 }
1424 sum += mSummedUpArea3D;
1425}
1426
1428{
1429 if ( numPoints() < 1 || isClosed() )
1430 {
1431 return;
1432 }
1433 addVertex( startPoint() );
1434}
1435
1437{
1438 for ( const QgsCurve *curve : mCurves )
1439 {
1440 if ( curve->hasCurvedSegments() )
1441 {
1442 return true;
1443 }
1444 }
1445 return false;
1446}
1447
1449{
1450 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( vertex );
1451 if ( curveIds.size() == 1 )
1452 {
1453 QgsCurve *curve = mCurves[curveIds.at( 0 ).first];
1454 return curve->vertexAngle( curveIds.at( 0 ).second );
1455 }
1456 else if ( curveIds.size() > 1 )
1457 {
1458 QgsCurve *curve1 = mCurves[curveIds.at( 0 ).first];
1459 QgsCurve *curve2 = mCurves[curveIds.at( 1 ).first];
1460 double angle1 = curve1->vertexAngle( curveIds.at( 0 ).second );
1461 double angle2 = curve2->vertexAngle( curveIds.at( 1 ).second );
1462 return QgsGeometryUtilsBase::averageAngle( angle1, angle2 );
1463 }
1464 else
1465 {
1466 return 0.0;
1467 }
1468}
1469
1471{
1472 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( startVertex );
1473 double length = 0.0;
1474 for ( auto it = curveIds.constBegin(); it != curveIds.constEnd(); ++it )
1475 {
1476 length += mCurves.at( it->first )->segmentLength( it->second );
1477 }
1478 return length;
1479}
1480
1482{
1484 for ( int i = mCurves.count() - 1; i >= 0; --i )
1485 {
1486 QgsCurve *reversedCurve = mCurves.at( i )->reversed();
1487 clone->addCurve( reversedCurve );
1488 }
1489 return clone;
1490}
1491
1492QgsPoint *QgsCompoundCurve::interpolatePoint( const double distance ) const
1493{
1494 if ( distance < 0 )
1495 return nullptr;
1496
1497 double distanceTraversed = 0;
1498 for ( const QgsCurve *curve : mCurves )
1499 {
1500 const double thisCurveLength = curve->length();
1501 if ( distanceTraversed + thisCurveLength > distance || qgsDoubleNear( distanceTraversed + thisCurveLength, distance ) )
1502 {
1503 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1504 const double distanceToPoint = std::min( distance - distanceTraversed, thisCurveLength );
1505
1506 // point falls on this curve
1507 return curve->interpolatePoint( distanceToPoint );
1508 }
1509
1510 distanceTraversed += thisCurveLength;
1511 }
1512
1513 return nullptr;
1514}
1515
1516QgsCompoundCurve *QgsCompoundCurve::curveSubstring( double startDistance, double endDistance ) const
1517{
1518 if ( startDistance < 0 && endDistance < 0 )
1519 return createEmptyWithSameType();
1520
1521 endDistance = std::max( startDistance, endDistance );
1522 auto substring = std::make_unique< QgsCompoundCurve >();
1523
1524 double distanceTraversed = 0;
1525 for ( const QgsCurve *curve : mCurves )
1526 {
1527 const double thisCurveLength = curve->length();
1528 if ( distanceTraversed + thisCurveLength < startDistance )
1529 {
1530 // keep going - haven't found start yet, so no need to include this curve at all
1531 }
1532 else
1533 {
1534 std::unique_ptr< QgsCurve > part( curve->curveSubstring( startDistance - distanceTraversed, endDistance - distanceTraversed ) );
1535 if ( part )
1536 substring->addCurve( part.release() );
1537 }
1538
1539 distanceTraversed += thisCurveLength;
1540 if ( distanceTraversed > endDistance )
1541 break;
1542 }
1543
1544 return substring.release();
1545}
1546
1547bool QgsCompoundCurve::addZValue( double zValue )
1548{
1549 if ( QgsWkbTypes::hasZ( mWkbType ) )
1550 return false;
1551
1553
1554 for ( QgsCurve *curve : std::as_const( mCurves ) )
1555 {
1556 curve->addZValue( zValue );
1557 }
1558 clearCache();
1559 return true;
1560}
1561
1562bool QgsCompoundCurve::addMValue( double mValue )
1563{
1564 if ( QgsWkbTypes::hasM( mWkbType ) )
1565 return false;
1566
1568
1569 for ( QgsCurve *curve : std::as_const( mCurves ) )
1570 {
1571 curve->addMValue( mValue );
1572 }
1573 clearCache();
1574 return true;
1575}
1576
1578{
1579 if ( !QgsWkbTypes::hasZ( mWkbType ) )
1580 return false;
1581
1583 for ( QgsCurve *curve : std::as_const( mCurves ) )
1584 {
1585 curve->dropZValue();
1586 }
1587 clearCache();
1588 return true;
1589}
1590
1592{
1593 if ( !QgsWkbTypes::hasM( mWkbType ) )
1594 return false;
1595
1597 for ( QgsCurve *curve : std::as_const( mCurves ) )
1598 {
1599 curve->dropMValue();
1600 }
1601 clearCache();
1602 return true;
1603}
1604
1606{
1607 for ( QgsCurve *curve : std::as_const( mCurves ) )
1608 {
1609 curve->swapXy();
1610 }
1611 clearCache();
1612}
1613
1615{
1616 // Ensure fromVertex < toVertex for simplicity
1617 if ( fromVertex.vertex > toVertex.vertex )
1618 {
1619 return distanceBetweenVertices( toVertex, fromVertex );
1620 }
1621
1622 // Convert QgsVertexId to simple vertex numbers for compound curves (single ring, single part)
1623 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
1624 return -1.0;
1625
1626 const int fromVertexNumber = fromVertex.vertex;
1627 const int toVertexNumber = toVertex.vertex;
1628
1629 const int totalVertices = numPoints();
1630 if ( fromVertexNumber < 0 || fromVertexNumber >= totalVertices || toVertexNumber < 0 || toVertexNumber >= totalVertices )
1631 return -1.0;
1632
1633 if ( fromVertexNumber == toVertexNumber )
1634 return 0.0;
1635
1636 double totalDistance = 0.0;
1637
1638 // Find which curves contain our vertices and accumulate distances
1639 int currentVertexId = 0;
1640 int fromCurve = -1, toCurve = -1;
1641 int fromCurveVertex = -1, toCurveVertex = -1;
1642
1643 // First pass: find which curves contain from and to vertices
1644 for ( int j = 0; j < mCurves.size(); ++j )
1645 {
1646 int nCurvePoints = mCurves.at( j )->numPoints();
1647
1648 // Check if fromVertex is in this curve
1649 if ( fromCurve == -1 && fromVertexNumber >= currentVertexId && fromVertexNumber < currentVertexId + nCurvePoints )
1650 {
1651 fromCurve = j;
1652 fromCurveVertex = fromVertexNumber - currentVertexId;
1653 }
1654
1655 // Check if toVertex is in this curve
1656 if ( toCurve == -1 && toVertexNumber >= currentVertexId && toVertexNumber < currentVertexId + nCurvePoints )
1657 {
1658 toCurve = j;
1659 toCurveVertex = toVertexNumber - currentVertexId;
1660 break;
1661 }
1662
1663 currentVertexId += ( nCurvePoints - 1 ); // Subtract 1 because curves share endpoints
1664 }
1665
1666 if ( fromCurve == -1 || toCurve == -1 )
1667 return -1.0; // Invalid vertex IDs
1668
1669 if ( fromCurve == toCurve )
1670 {
1671 // Both vertices are on the same curve
1672 QgsVertexId fromId( 0, 0, fromCurveVertex );
1673 QgsVertexId toId( 0, 0, toCurveVertex );
1674 return mCurves.at( fromCurve )->distanceBetweenVertices( fromId, toId );
1675 }
1676 else
1677 {
1678 // Vertices are on different curves - accumulate distances across multiple curves
1679
1680 // Distance from fromVertex to end of its curve
1681 if ( fromCurveVertex < mCurves.at( fromCurve )->numPoints() - 1 )
1682 {
1683 QgsVertexId fromId( 0, 0, fromCurveVertex );
1684 QgsVertexId endId( 0, 0, mCurves.at( fromCurve )->numPoints() - 1 );
1685 totalDistance += mCurves.at( fromCurve )->distanceBetweenVertices( fromId, endId );
1686 }
1687
1688 // Distance of complete intermediate curves
1689 for ( int j = fromCurve + 1; j < toCurve; ++j )
1690 {
1691 totalDistance += mCurves.at( j )->length();
1692 }
1693
1694 // Distance from start of toCurve to toVertex
1695 if ( toCurveVertex > 0 )
1696 {
1697 QgsVertexId startId( 0, 0, 0 );
1698 QgsVertexId toId( 0, 0, toCurveVertex );
1699 totalDistance += mCurves.at( toCurve )->distanceBetweenVertices( startId, toId );
1700 }
1701 }
1702
1703 return totalDistance;
1704}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2196
VertexType
Types of vertex.
Definition qgis.h:3246
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:2831
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.
void points(QgsPointSequence &pts) const override
Returns a list of points within the curve.
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.
json asJsonObject(int precision=17) const override
Returns a json object representation of the geometry.
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.
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 id 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.
void append(const QgsLineString *line)
Appends the contents of another line string to the end of this line string.
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:568
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
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7222
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