QGIS API Documentation 4.3.0-Master (0ad0cdb9438)
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 return false;
481}
482
484{
485 QgsLineString *line = new QgsLineString();
486 std::unique_ptr< QgsLineString > currentLine;
487 for ( const QgsCurve *curve : mCurves )
488 {
489 currentLine.reset( curve->curveToLine( tolerance, toleranceType ) );
490 line->append( currentLine.get() );
491 }
492 return line;
493}
494
495QgsCompoundCurve *QgsCompoundCurve::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const
496{
497 std::unique_ptr<QgsCompoundCurve> result( createEmptyWithSameType() );
498
499 for ( QgsCurve *curve : mCurves )
500 {
501 std::unique_ptr<QgsCurve> gridified( static_cast< QgsCurve * >( curve->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) );
502 if ( gridified )
503 {
504 result->mCurves.append( gridified.release() );
505 }
506 }
507
508 if ( result->mCurves.empty() )
509 return nullptr;
510 else
511 return result.release();
512}
513
515{
516 std::unique_ptr< QgsLineString > line( curveToLine() );
517 return line->simplifyByDistance( tolerance );
518}
519
520bool QgsCompoundCurve::removeDuplicateNodes( double epsilon, bool useZValues )
521{
522 bool result = false;
523 const QVector< QgsCurve * > curves = mCurves;
524 int i = 0;
525 QgsPoint lastEnd;
526 for ( QgsCurve *curve : curves )
527 {
528 result = curve->removeDuplicateNodes( epsilon, useZValues ) || result;
529 if ( curve->numPoints() == 0 || qgsDoubleNear( curve->length(), 0.0, epsilon ) )
530 {
531 // empty curve, remove it
532 delete mCurves.takeAt( i );
533 result = true;
534 }
535 else
536 {
537 // ensure this line starts exactly where previous line ended
538 if ( i > 0 )
539 {
540 curve->moveVertex( QgsVertexId( -1, -1, 0 ), lastEnd );
541 }
542 lastEnd = curve->vertexAt( QgsVertexId( -1, -1, curve->numPoints() - 1 ) );
543 }
544 i++;
545 }
546 return result;
547}
548
550{
551 if ( mCurves.empty() )
552 return false;
553
554 // if we already have the bounding box calculated, then this check is trivial!
555 if ( !mBoundingBox.isNull() )
556 {
557 return mBoundingBox.intersects( box3d );
558 }
559
560 // otherwise loop through each member curve and test the bounding box intersection.
561 // This gives us a chance to use optimisations which may be present on the individual
562 // curve subclasses, and at worst it will cause a calculation of the bounding box
563 // of each individual member curve which we would have to do anyway... (and these
564 // bounding boxes are cached, so would be reused without additional expense)
565 for ( const QgsCurve *curve : mCurves )
566 {
567 if ( curve->boundingBoxIntersects( box3d ) )
568 return true;
569 }
570
571 // even if we don't intersect the bounding box of any member curves, we may still intersect the
572 // bounding box of the overall compound curve.
573 // so here we fall back to the non-optimised base class check which has to first calculate
574 // the overall bounding box of the compound curve..
576}
577
579{
580 if ( mCurves.size() == 1 )
581 return mCurves.at( 0 );
582 else
583 return this;
584}
585
587{
588 if ( i < 0 || i >= mCurves.size() )
589 {
590 return nullptr;
591 }
592 return mCurves.at( i );
593}
594
595void QgsCompoundCurve::addCurve( QgsCurve *c, const bool extendPrevious )
596{
597 if ( !c )
598 return;
599
600 if ( mCurves.empty() )
601 {
603 }
604
605 if ( QgsWkbTypes::hasZ( mWkbType ) && !QgsWkbTypes::hasZ( c->wkbType() ) )
606 {
607 c->addZValue();
608 }
609 else if ( !QgsWkbTypes::hasZ( mWkbType ) && QgsWkbTypes::hasZ( c->wkbType() ) )
610 {
611 c->dropZValue();
612 }
613 if ( QgsWkbTypes::hasM( mWkbType ) && !QgsWkbTypes::hasM( c->wkbType() ) )
614 {
615 c->addMValue();
616 }
617 else if ( !QgsWkbTypes::hasM( mWkbType ) && QgsWkbTypes::hasM( c->wkbType() ) )
618 {
619 c->dropMValue();
620 }
621
622 QgsLineString *previousLineString = !mCurves.empty() ? qgsgeometry_cast< QgsLineString * >( mCurves.constLast() ) : nullptr;
624 const bool canExtendPrevious = extendPrevious && previousLineString && newLineString;
625 if ( canExtendPrevious )
626 {
627 previousLineString->append( newLineString );
628 // we are taking ownership, so delete the input curve
629 delete c;
630 c = nullptr;
631 }
632 else
633 {
634 mCurves.append( c );
635 }
636
637 clearCache();
638}
639
641{
642 if ( i < 0 || i >= mCurves.size() )
643 {
644 return;
645 }
646
647 delete mCurves.takeAt( i );
648 clearCache();
649}
650
652{
653 if ( mCurves.isEmpty() || mWkbType == Qgis::WkbType::Unknown )
654 {
656 }
657
658 //is last curve QgsLineString
659 QgsCurve *lastCurve = nullptr;
660 if ( !mCurves.isEmpty() )
661 {
662 lastCurve = mCurves.at( mCurves.size() - 1 );
663 }
664
665 QgsLineString *line = nullptr;
666 if ( !lastCurve || QgsWkbTypes::flatType( lastCurve->wkbType() ) != Qgis::WkbType::LineString )
667 {
668 line = new QgsLineString();
669 mCurves.append( line );
670 if ( lastCurve )
671 {
672 line->addVertex( lastCurve->endPoint() );
673 }
674 lastCurve = line;
675 }
676 else //create new QgsLineString* with point in it
677 {
678 line = static_cast<QgsLineString *>( lastCurve );
679 }
680 line->addVertex( pt );
681 clearCache();
682}
683
685{
686 QgsCurve *lastCurve = nullptr;
687 QVector< QgsCurve * > newCurves;
688 newCurves.reserve( mCurves.size() );
689 for ( QgsCurve *curve : std::as_const( mCurves ) )
690 {
691 if ( lastCurve && lastCurve->wkbType() == curve->wkbType() )
692 {
693 if ( QgsLineString *ls = qgsgeometry_cast< QgsLineString * >( lastCurve ) )
694 {
695 ls->append( qgsgeometry_cast< QgsLineString * >( curve ) );
696 delete curve;
697 }
699 {
700 cs->append( qgsgeometry_cast< QgsCircularString * >( curve ) );
701 delete curve;
702 }
703 }
704 else
705 {
706 lastCurve = curve;
707 newCurves << curve;
708 }
709 }
710 mCurves = newCurves;
711}
712
713void QgsCompoundCurve::draw( QPainter &p ) const
714{
715 for ( const QgsCurve *curve : mCurves )
716 {
717 curve->draw( p );
718 }
719}
720
722{
723 for ( QgsCurve *curve : std::as_const( mCurves ) )
724 {
725 curve->transform( ct, d, transformZ );
726 }
727 clearCache();
728}
729
730void QgsCompoundCurve::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
731{
732 for ( QgsCurve *curve : std::as_const( mCurves ) )
733 {
734 curve->transform( t, zTranslate, zScale, mTranslate, mScale );
735 }
736 clearCache();
737}
738
739void QgsCompoundCurve::addToPainterPath( QPainterPath &path ) const
740{
741 QPainterPath pp;
742
743 for ( const QgsCurve *curve : mCurves )
744 {
745 if ( curve != mCurves.at( 0 ) && pp.currentPosition() != curve->startPoint().toQPointF() )
746 {
747 pp.lineTo( curve->startPoint().toQPointF() );
748 }
749 curve->addToPainterPath( pp );
750 }
751 path.addPath( pp );
752}
753
754void QgsCompoundCurve::drawAsPolygon( QPainter &p ) const
755{
756 QPainterPath pp;
757 for ( const QgsCurve *curve : mCurves )
758 {
759 if ( curve != mCurves.at( 0 ) && pp.currentPosition() != curve->startPoint().toQPointF() )
760 {
761 pp.lineTo( curve->startPoint().toQPointF() );
762 }
763 curve->addToPainterPath( pp );
764 }
765 p.drawPath( pp );
766}
767
769{
770 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
771 if ( curveIds.empty() )
772 {
773 return false;
774 }
775 int curveId = curveIds.at( 0 ).first;
776 if ( curveId >= mCurves.size() )
777 {
778 return false;
779 }
780
781 bool success = mCurves.at( curveId )->insertVertex( curveIds.at( 0 ).second, vertex );
782 if ( success )
783 {
784 clearCache(); //bbox changed
785 }
786 return success;
787}
788
789bool QgsCompoundCurve::moveVertex( QgsVertexId position, const QgsPoint &newPos )
790{
791 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
792 QVector< QPair<int, QgsVertexId> >::const_iterator idIt = curveIds.constBegin();
793 for ( ; idIt != curveIds.constEnd(); ++idIt )
794 {
795 mCurves.at( idIt->first )->moveVertex( idIt->second, newPos );
796 }
797
798 bool success = !curveIds.isEmpty();
799 if ( success )
800 {
801 clearCache(); //bbox changed
802 }
803 return success;
804}
805
806bool QgsCompoundCurve::deleteVertices( const QSet<QgsVertexId> &positions )
807{
808 // we create a list of vertices to delete for each curve
809 QMap<int, QList<QgsVertexId >> curveVertices;
810 for ( QgsVertexId position : positions )
811 {
812 if ( !hasVertex( position ) )
813 {
814 return false;
815 }
816
817 const QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
818
819 if ( curveIds.isEmpty() )
820 return false;
821
822 const int firstCurveId = curveIds.at( 0 ).first;
823 const QgsVertexId firstCurveVertex = curveIds.at( 0 ).second;
824 curveVertices[firstCurveId].append( firstCurveVertex );
825 if ( curveIds.size() == 2 ) // vertex is shared between two curves
826 {
827 const int secondCurveId = curveIds.at( 1 ).first;
828 const QgsVertexId secondCurveVertex = curveIds.at( 1 ).second;
829 curveVertices[secondCurveId].append( secondCurveVertex );
830 }
831 }
832
833 QVector< QgsPoint > survivingPoints;
834
835 auto appendSurvivingPoints = [&survivingPoints, this]( const QgsPoint &point, const int curveId ) {
837 pts.reserve( 1 + survivingPoints.size() );
838 pts << point;
839
840 for ( size_t i = survivingPoints.size(); i-- > 0; )
841 pts << survivingPoints[i];
842
843 auto newLineString = std::make_unique<QgsLineString>();
844 newLineString->setPoints( pts );
845 mCurves.insert( curveId, newLineString.release() );
846
847 survivingPoints.clear();
848 };
849
850 // loop through the curves in reverse order and delete vertices
851 QMapIterator<int, QList<QgsVertexId >> curveVerticesIt( curveVertices );
852 curveVerticesIt.toBack();
853 int previousCurveId = -1;
854 while ( curveVerticesIt.hasPrevious() )
855 {
856 curveVerticesIt.previous();
857 const int curveId = curveVerticesIt.key();
858
859 // append surviving points at the end of a curve that has no vertices scheduled for deletion
860 if ( previousCurveId - 1 > curveId && !survivingPoints.isEmpty() )
861 {
862 QgsCurve *curve = mCurves.at( previousCurveId - 1 );
863 appendSurvivingPoints( curve->endPoint(), previousCurveId );
864 }
865
866 QgsCurve *curve = mCurves.at( curveId );
867 QList<QgsVertexId> vertices = curveVerticesIt.value();
868 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex < b.vertex; } );
869
870 const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString *>( curve );
871 // If the vertex to delete is the middle vertex of a circularstring arc, we transform
872 // this circularstring arc into a linestring without the middle vertex
873 if ( circularString )
874 {
875 // we loop through the vertices to see if we need to handle special case
876 // of a middle vertex (see deleteVertex)
877 QList<QgsVertexId> circularVerticesToDelete;
878 circularVerticesToDelete.reserve( vertices.size() );
879
880 // search for odd vertices (middle vertices of an arc)
881 for ( size_t i = vertices.size(); i-- > 0; )
882 {
883 const QgsVertexId curveVertexId = vertices.at( i );
884
885 // check if a middle vertex of an arc
886 if ( curveVertexId.vertex % 2 == 1 )
887 {
888 // check if neighbouring vertices are also to be deleted
889 // if so, we just add this vertex to the list and continue iterating
890 if ( !circularVerticesToDelete.isEmpty() )
891 {
892 if ( curveVertexId.vertex == circularVerticesToDelete.last().vertex - 1 )
893 {
894 circularVerticesToDelete.append( curveVertexId );
895 continue;
896 }
897 }
898 else if ( i != 0 && curveVertexId.vertex - 1 == vertices.at( i - 1 ).vertex )
899 {
900 circularVerticesToDelete.append( curveVertexId );
901 continue;
902 }
903
904 // we found a middle vertex of an arc and none of its neighbours are to be deleted
905 // we need to handle special case of middle vertex of an arc deletion
906 // first we delete all the vertices that come before it in this circularstring
907 if ( !circularVerticesToDelete.isEmpty() )
908 {
909 if ( !curve->deleteVertices( QSet<QgsVertexId>( circularVerticesToDelete.begin(), circularVerticesToDelete.end() ) ) )
910 {
911 Q_ASSERT( false ); // shouldn't happen after all the checks
912 return false;
913 }
914 }
915 circularVerticesToDelete.clear();
916
917 // next, we remove that arc and replace it with a linestring that skips the middle vertex
919 circularString->points( points );
920
921 removeCurve( curveId );
922
923 if ( curveVertexId.vertex < points.length() - 2 )
924 {
925 auto curveC = std::make_unique<QgsCircularString>();
926 curveC->setPoints( points.mid( curveVertexId.vertex + 1 ) );
927 mCurves.insert( curveId, curveC.release() );
928 }
929
930 const QgsPointSequence partB = QgsPointSequence() << points[curveVertexId.vertex - 1] << points[curveVertexId.vertex + 1];
931 auto curveB = std::make_unique<QgsLineString>();
932 curveB->setPoints( partB );
933 mCurves.insert( curveId, curveB.release() );
934 curve = mCurves.at( curveId );
935
936 if ( curveVertexId.vertex > 1 )
937 {
938 auto curveA = std::make_unique<QgsCircularString>();
939 curveA->setPoints( points.mid( 0, curveVertexId.vertex ) );
940 mCurves.insert( curveId, curveA.release() );
941 }
942 curve = mCurves.at( curveId ); // we need to get the new curve
943 circularString = qgsgeometry_cast<const QgsCircularString *>( curve );
944
945 continue;
946 }
947
948 // not a middle vertex of an arc
949 circularVerticesToDelete.append( curveVertexId );
950 }
951
952 // remove any remaining circular vertices to delete
953 if ( !circularVerticesToDelete.isEmpty() )
954 {
955 // check if we are deleting a shared vertex and save the end point IF it is not being deleted
956 // we don't want to make assumtions how circularstring deletes its vertices
957 // so we save it, delete the vertices, and if the curve gets deleted as a result
958 // only then we actually append the survivingPoint to the list
959 QgsPoint survivingPoint;
960 if ( curveId > 0 && circularVerticesToDelete.last().vertex == 0 && circularVerticesToDelete.first().vertex != curve->numPoints() - 1 )
961 {
962 survivingPoint = curve->endPoint();
963 }
964 else if ( curveId == 0 && circularVerticesToDelete.first().vertex == curve->numPoints() - 1 && circularVerticesToDelete.last().vertex != 0 )
965 {
966 survivingPoint = curve->startPoint();
967 }
968 if ( !curve->deleteVertices( QSet<QgsVertexId>( circularVerticesToDelete.begin(), circularVerticesToDelete.end() ) ) )
969 {
970 Q_ASSERT( false );
971 return false;
972 }
973
974 if ( curve->numPoints() == 0 )
975 {
976 removeCurve( curveId );
977 // end point wasn't marked for deletion and the curve was deleted
978 // append it to survivingPoints
979 if ( !survivingPoint.isEmpty() )
980 {
981 survivingPoints.emplace_back( survivingPoint );
982 }
983 }
984 else if ( survivingPoints.size() != 0 )
985 {
986 appendSurvivingPoints( curve->endPoint(), curveId + 1 );
987 }
988 }
989 previousCurveId = curveId;
990 continue; // circularstring handled, continue to next curve
991 }
992
993 // linestring
994 // check if we are deleting a shared vertex and all but 1 point would be deleted
995 // if that's the case, we add that point to survivingPoints, delete the curve and continue
996 if ( ( curveId > 0 && curve->numPoints() - vertices.size() == 1 && vertices.first().vertex == 0 )
997 || ( curveId == 0 && curve->numPoints() - vertices.size() == 1 && vertices.last().vertex == curve->numPoints() - 1 ) )
998 {
999 int remainingIdx = 0;
1000 for ( const QgsVertexId &v : vertices )
1001 {
1002 if ( v.vertex != remainingIdx )
1003 break;
1004 remainingIdx++;
1005 }
1006 survivingPoints.emplace_back( curve->vertexAt( QgsVertexId( 0, 0, remainingIdx ) ) );
1007 removeCurve( curveId );
1008 previousCurveId = curveId;
1009 continue; // curve removed, continue to next one
1010 }
1011
1012 if ( !curve->deleteVertices( QSet<QgsVertexId>( vertices.begin(), vertices.end() ) ) )
1013 {
1014 Q_ASSERT( false );
1015 return false;
1016 }
1017
1018 if ( curve->numPoints() == 0 )
1019 {
1020 removeCurve( curveId );
1021 }
1022 else if ( survivingPoints.size() != 0 )
1023 {
1024 appendSurvivingPoints( curve->endPoint(), curveId + 1 );
1025 }
1026
1027 previousCurveId = curveId;
1028 // linestring handled, this is the end of the loop
1029 }
1030
1031 if ( survivingPoints.size() != 0 )
1032 {
1033 if ( previousCurveId > 0 )
1034 {
1035 // (note: we went through the list in reverse order)
1036 // we are not at the start of curve list, there are curves further down
1037 // so we add those points at the start of the previous curve
1038 const QgsCurve *curve = mCurves.at( previousCurveId - 1 );
1039 appendSurvivingPoints( curve->endPoint(), previousCurveId );
1040 }
1041 else
1042 {
1043 // we reached the end of list, which means we are at the start of the geometry
1044 // so we append those points at the start of the first curve in the list (if it is not a circularstring, otherwise we make a linestring connecting to it)
1045 // if there are no curves left, but the number of surviving points is 2 or greater
1046 // we make a linestring out of them and add it as the only curve
1047 if ( mCurves.size() != 0 )
1048 {
1049 QgsCurve *curve = mCurves.at( 0 );
1051 {
1052 // only the first one could be equal to the start point, in which case, just remove it
1053 // so we do not duplicate points when adding them
1054 if ( survivingPoints[0] == curve->startPoint() )
1055 {
1056 survivingPoints.removeAt( 0 );
1057 }
1058 for ( int i = 0; i < survivingPoints.size(); ++i )
1059 curve->insertVertex( QgsVertexId( 0, 0, 0 ), survivingPoints[i] );
1060 }
1061 else // circularstring, should not add to it, just make a linestring connecting to it
1062 {
1063 QgsPointSequence pts;
1064 for ( int i = survivingPoints.size() - 1; i >= 0; --i )
1065 pts << survivingPoints[i];
1066 pts << curve->startPoint();
1067
1068 auto newLineString = std::make_unique<QgsLineString>();
1069 newLineString->setPoints( pts );
1070 mCurves.insert( 0, newLineString.release() );
1071 }
1072 }
1073 else if ( survivingPoints.size() >= 2 ) // there are no curves left, add points into a linestring and add it as only geometry
1074 {
1075 QgsPointSequence pts;
1076 for ( int i = survivingPoints.size() - 1; i >= 0; --i )
1077 pts << survivingPoints[i];
1078
1079
1080 auto newLineString = std::make_unique<QgsLineString>();
1081 newLineString->setPoints( pts );
1082
1083 mCurves.insert( 0, newLineString.release() );
1084 }
1085 }
1086 }
1087
1088 if ( mCurves.isEmpty() )
1089 {
1090 clearCache();
1091 return true;
1092 }
1093
1094 // ensure all curves are connected
1095 // if the curves are circularstring, make a linestring connecting them
1096 // otherwise, replace the first/last point of a linestring with the one prior
1097 for ( size_t i = mCurves.size() - 1; i > 0; i-- )
1098 {
1099 QgsCurve *curve = mCurves.at( i );
1100 QgsCurve *previousCurve = mCurves.at( i - 1 );
1101 if ( previousCurve->endPoint() != curve->startPoint() )
1102 {
1104 {
1105 QgsLineString *line = new QgsLineString();
1106 line->insertVertex( QgsVertexId( 0, 0, 0 ), previousCurve->endPoint() );
1107 line->insertVertex( QgsVertexId( 0, 0, 1 ), curve->startPoint() );
1108 mCurves.insert( i, line );
1109 }
1110 else // we shouldn't move the vertex of a circularstring because that changes geometry, linestring doesn't have that issue
1111 {
1113 {
1114 const QgsPoint startPoint = curve->startPoint();
1115 previousCurve->moveVertex( QgsVertexId( 0, 0, previousCurve->numPoints() - 1 ), startPoint );
1116 }
1117 else
1118 {
1119 const QgsPoint endPoint = previousCurve->endPoint();
1120 curve->moveVertex( QgsVertexId( 0, 0, 0 ), endPoint );
1121 }
1122 }
1123 }
1124 }
1125
1126 clearCache();
1127 return true;
1128}
1129
1130QVector< QPair<int, QgsVertexId> > QgsCompoundCurve::curveVertexId( QgsVertexId id ) const
1131{
1132 QVector< QPair<int, QgsVertexId> > curveIds;
1133
1134 int currentVertexIndex = 0;
1135 for ( int i = 0; i < mCurves.size(); ++i )
1136 {
1137 int increment = mCurves.at( i )->numPoints() - 1;
1138 if ( id.vertex >= currentVertexIndex && id.vertex <= currentVertexIndex + increment )
1139 {
1140 int curveVertexId = id.vertex - currentVertexIndex;
1141 QgsVertexId vid;
1142 vid.part = 0;
1143 vid.ring = 0;
1144 vid.vertex = curveVertexId;
1145 curveIds.append( qMakePair( i, vid ) );
1146 if ( curveVertexId == increment && i < ( mCurves.size() - 1 ) ) //add first vertex of next curve
1147 {
1148 vid.vertex = 0;
1149 curveIds.append( qMakePair( i + 1, vid ) );
1150 }
1151 break;
1152 }
1153 else if ( id.vertex >= currentVertexIndex && id.vertex == currentVertexIndex + increment + 1 && i == ( mCurves.size() - 1 ) )
1154 {
1155 int curveVertexId = id.vertex - currentVertexIndex;
1156 QgsVertexId vid;
1157 vid.part = 0;
1158 vid.ring = 0;
1159 vid.vertex = curveVertexId;
1160 curveIds.append( qMakePair( i, vid ) );
1161 break;
1162 }
1163 currentVertexIndex += increment;
1164 }
1165
1166 return curveIds;
1167}
1168
1170{
1171 // First we find out the sub-curves that are contain that vertex.
1172
1173 // If there is more than one, it means the vertex was at the beginning or end
1174 // of an arc, which we don't support.
1175
1176 // If there is exactly one, we may either be on a LineString, or on a CircularString.
1177
1178 // If on CircularString, we need to check if the vertex is a CurveVertex (odd index).
1179 // If so, we split the subcurve at vertex -1 and +1, , drop the middle part and insert a LineString/CircularString
1180 // instead with the same points.
1181
1182 // At the end, we call condenseCurves() to merge successible line/circular strings
1183
1184 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
1185
1186 // We cannot convert points at start/end of subcurves
1187 if ( curveIds.length() != 1 )
1188 return false;
1189
1190 int curveId = curveIds[0].first;
1191 QgsVertexId subVertexId = curveIds[0].second;
1192 QgsCurve *curve = mCurves[curveId];
1193
1194 // We cannot convert first/last point of curve
1195 if ( subVertexId.vertex == 0 || subVertexId.vertex == curve->numPoints() - 1 )
1196 return false;
1197
1198 if ( const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString *>( curve ) )
1199 {
1200 // If it's a circular string, we convert to LineString
1201
1202 // We cannot convert start/end points of arcs
1203 if ( subVertexId.vertex % 2 == 0 ) // for some reason, subVertexId.type is always SegmentVertex...
1204 return false;
1205
1207 circularString->points( points );
1208
1209 const QgsPointSequence partA = points.mid( 0, subVertexId.vertex );
1210 const QgsPointSequence partB = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex] << points[subVertexId.vertex + 1];
1211 const QgsPointSequence partC = points.mid( subVertexId.vertex + 1 );
1212
1213 auto curveA = std::make_unique<QgsCircularString>();
1214 curveA->setPoints( partA );
1215 auto curveB = std::make_unique<QgsLineString>();
1216 curveB->setPoints( partB );
1217 auto curveC = std::make_unique<QgsCircularString>();
1218 curveC->setPoints( partC );
1219
1220 removeCurve( curveId );
1221 if ( subVertexId.vertex < points.length() - 2 )
1222 mCurves.insert( curveId, curveC.release() );
1223 mCurves.insert( curveId, curveB.release() );
1224 if ( subVertexId.vertex > 1 )
1225 mCurves.insert( curveId, curveA.release() );
1226 }
1227 else if ( const QgsLineString *lineString = dynamic_cast<const QgsLineString *>( curve ) )
1228 {
1229 // If it's a linestring, we split and insert a curve
1230
1232 lineString->points( points );
1233
1234 const QgsPointSequence partA = points.mid( 0, subVertexId.vertex );
1235 const QgsPointSequence partB = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex] << points[subVertexId.vertex + 1];
1236 const QgsPointSequence partC = points.mid( subVertexId.vertex + 1 );
1237
1238 auto curveA = std::make_unique<QgsLineString>();
1239 curveA->setPoints( partA );
1240 auto curveB = std::make_unique<QgsCircularString>();
1241 curveB->setPoints( partB );
1242 auto curveC = std::make_unique<QgsLineString>();
1243 curveC->setPoints( partC );
1244
1245 removeCurve( curveId );
1246 if ( subVertexId.vertex < points.length() - 2 )
1247 mCurves.insert( curveId, curveC.release() );
1248 mCurves.insert( curveId, curveB.release() );
1249 if ( subVertexId.vertex > 1 )
1250 mCurves.insert( curveId, curveA.release() );
1251 }
1252
1253 // We merge consecutive LineStrings
1255
1256 clearCache();
1257 return true;
1258}
1259
1260
1261double QgsCompoundCurve::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1262{
1263 return QgsGeometryUtils::closestSegmentFromComponents( mCurves, QgsGeometryUtils::Vertex, pt, segmentPt, vertexAfter, leftOf, epsilon );
1264}
1265
1266bool QgsCompoundCurve::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
1267{
1268 int currentVertexId = 0;
1269 for ( int j = 0; j < mCurves.size(); ++j )
1270 {
1271 int nCurvePoints = mCurves.at( j )->numPoints();
1272 if ( ( node - currentVertexId ) < nCurvePoints )
1273 {
1274 return ( mCurves.at( j )->pointAt( node - currentVertexId, point, type ) );
1275 }
1276 currentVertexId += ( nCurvePoints - 1 );
1277 }
1278 return false;
1279}
1280
1281double QgsCompoundCurve::xAt( int index ) const
1282{
1283 int currentVertexId = 0;
1284 for ( int j = 0; j < mCurves.size(); ++j )
1285 {
1286 int nCurvePoints = mCurves.at( j )->numPoints();
1287 if ( ( index - currentVertexId ) < nCurvePoints )
1288 {
1289 return mCurves.at( j )->xAt( index - currentVertexId );
1290 }
1291 currentVertexId += ( nCurvePoints - 1 );
1292 }
1293 return 0.0;
1294}
1295
1296double QgsCompoundCurve::yAt( int index ) const
1297{
1298 int currentVertexId = 0;
1299 for ( int j = 0; j < mCurves.size(); ++j )
1300 {
1301 int nCurvePoints = mCurves.at( j )->numPoints();
1302 if ( ( index - currentVertexId ) < nCurvePoints )
1303 {
1304 return mCurves.at( j )->yAt( index - currentVertexId );
1305 }
1306 currentVertexId += ( nCurvePoints - 1 );
1307 }
1308 return 0.0;
1309}
1310
1311double QgsCompoundCurve::zAt( int index ) const
1312{
1313 int currentVertexId = 0;
1314 for ( int j = 0; j < mCurves.size(); ++j )
1315 {
1316 int nCurvePoints = mCurves.at( j )->numPoints();
1317 if ( ( index - currentVertexId ) < nCurvePoints )
1318 {
1319 return mCurves.at( j )->zAt( index - currentVertexId );
1320 }
1321 currentVertexId += ( nCurvePoints - 1 );
1322 }
1323 return 0.0;
1324}
1325
1326double QgsCompoundCurve::mAt( int index ) const
1327{
1328 int currentVertexId = 0;
1329 for ( int j = 0; j < mCurves.size(); ++j )
1330 {
1331 int nCurvePoints = mCurves.at( j )->numPoints();
1332 if ( ( index - currentVertexId ) < nCurvePoints )
1333 {
1334 return mCurves.at( j )->mAt( index - currentVertexId );
1335 }
1336 currentVertexId += ( nCurvePoints - 1 );
1337 }
1338 return 0.0;
1339}
1340
1342{
1343 bool res = true;
1344 for ( QgsCurve *curve : std::as_const( mCurves ) )
1345 {
1346 if ( !curve->transform( transformer ) )
1347 {
1348 res = false;
1349 break;
1350 }
1351
1352 if ( feedback && feedback->isCanceled() )
1353 {
1354 res = false;
1355 break;
1356 }
1357 }
1358 clearCache();
1359 return res;
1360}
1361
1362void QgsCompoundCurve::filterVertices( const std::function<bool( const QgsPoint & )> &filter )
1363{
1364 for ( QgsCurve *curve : std::as_const( mCurves ) )
1365 {
1366 curve->filterVertices( filter );
1367 }
1368 clearCache();
1369}
1370
1371void QgsCompoundCurve::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
1372{
1373 for ( QgsCurve *curve : std::as_const( mCurves ) )
1374 {
1375 curve->transformVertices( transform );
1376 }
1377 clearCache();
1378}
1379
1380std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsCompoundCurve::splitCurveAtVertex( int index ) const
1381{
1382 if ( mCurves.empty() )
1383 return std::make_tuple( std::make_unique< QgsCompoundCurve >(), std::make_unique< QgsCompoundCurve >() );
1384
1385 int curveStart = 0;
1386
1387 auto curve1 = std::make_unique< QgsCompoundCurve >();
1388 std::unique_ptr< QgsCompoundCurve > curve2;
1389
1390 for ( const QgsCurve *curve : mCurves )
1391 {
1392 const int curveSize = curve->numPoints();
1393 if ( !curve2 && index < curveStart + curveSize )
1394 {
1395 // split the curve
1396 auto [p1, p2] = curve->splitCurveAtVertex( index - curveStart );
1397 if ( !p1->isEmpty() )
1398 curve1->addCurve( p1.release() );
1399
1400 curve2 = std::make_unique< QgsCompoundCurve >();
1401 if ( !p2->isEmpty() )
1402 curve2->addCurve( p2.release() );
1403 }
1404 else
1405 {
1406 if ( curve2 )
1407 curve2->addCurve( curve->clone() );
1408 else
1409 curve1->addCurve( curve->clone() );
1410 }
1411
1412 // subtract 1 here, because the next curve will start with the same
1413 // vertex as this curve ended at
1414 curveStart += curve->numPoints() - 1;
1415 }
1416
1417 return std::make_tuple( std::move( curve1 ), curve2 ? std::move( curve2 ) : std::make_unique< QgsCompoundCurve >() );
1418}
1419
1420void QgsCompoundCurve::sumUpArea( double &sum ) const
1421{
1423 {
1424 sum += mSummedUpArea;
1425 return;
1426 }
1427
1428 mSummedUpArea = 0;
1429 for ( const QgsCurve *curve : mCurves )
1430 {
1431 curve->sumUpArea( mSummedUpArea );
1432 }
1434 sum += mSummedUpArea;
1435}
1436
1437void QgsCompoundCurve::sumUpArea3D( double &sum ) const
1438{
1440 {
1441 sum += mSummedUpArea3D;
1442 return;
1443 }
1444
1445 mSummedUpArea3D = 0;
1446 for ( const QgsCurve *curve : mCurves )
1447 {
1448 curve->sumUpArea3D( mSummedUpArea3D );
1449 }
1451 sum += mSummedUpArea3D;
1452}
1453
1455{
1456 if ( numPoints() < 1 || isClosed() )
1457 {
1458 return;
1459 }
1460 addVertex( startPoint() );
1461}
1462
1464{
1465 for ( const QgsCurve *curve : mCurves )
1466 {
1467 if ( curve->hasCurvedSegments() )
1468 {
1469 return true;
1470 }
1471 }
1472 return false;
1473}
1474
1476{
1477 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( vertex );
1478 if ( curveIds.size() == 1 )
1479 {
1480 QgsCurve *curve = mCurves[curveIds.at( 0 ).first];
1481 return curve->vertexAngle( curveIds.at( 0 ).second );
1482 }
1483 else if ( curveIds.size() > 1 )
1484 {
1485 QgsCurve *curve1 = mCurves[curveIds.at( 0 ).first];
1486 QgsCurve *curve2 = mCurves[curveIds.at( 1 ).first];
1487 double angle1 = curve1->vertexAngle( curveIds.at( 0 ).second );
1488 double angle2 = curve2->vertexAngle( curveIds.at( 1 ).second );
1489 return QgsGeometryUtilsBase::averageAngle( angle1, angle2 );
1490 }
1491 else
1492 {
1493 return 0.0;
1494 }
1495}
1496
1498{
1499 QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( startVertex );
1500 double length = 0.0;
1501 for ( auto it = curveIds.constBegin(); it != curveIds.constEnd(); ++it )
1502 {
1503 length += mCurves.at( it->first )->segmentLength( it->second );
1504 }
1505 return length;
1506}
1507
1509{
1511 for ( int i = mCurves.count() - 1; i >= 0; --i )
1512 {
1513 QgsCurve *reversedCurve = mCurves.at( i )->reversed();
1514 clone->addCurve( reversedCurve );
1515 }
1516 return clone;
1517}
1518
1519QgsPoint *QgsCompoundCurve::interpolatePoint( const double distance ) const
1520{
1521 if ( distance < 0 )
1522 return nullptr;
1523
1524 double distanceTraversed = 0;
1525 for ( const QgsCurve *curve : mCurves )
1526 {
1527 const double thisCurveLength = curve->length();
1528 if ( distanceTraversed + thisCurveLength > distance || qgsDoubleNear( distanceTraversed + thisCurveLength, distance ) )
1529 {
1530 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1531 const double distanceToPoint = std::min( distance - distanceTraversed, thisCurveLength );
1532
1533 // point falls on this curve
1534 return curve->interpolatePoint( distanceToPoint );
1535 }
1536
1537 distanceTraversed += thisCurveLength;
1538 }
1539
1540 return nullptr;
1541}
1542
1543QgsCompoundCurve *QgsCompoundCurve::curveSubstring( double startDistance, double endDistance ) const
1544{
1545 if ( startDistance < 0 && endDistance < 0 )
1546 return createEmptyWithSameType();
1547
1548 endDistance = std::max( startDistance, endDistance );
1549 auto substring = std::make_unique< QgsCompoundCurve >();
1550
1551 double distanceTraversed = 0;
1552 for ( const QgsCurve *curve : mCurves )
1553 {
1554 const double thisCurveLength = curve->length();
1555 if ( distanceTraversed + thisCurveLength < startDistance )
1556 {
1557 // keep going - haven't found start yet, so no need to include this curve at all
1558 }
1559 else
1560 {
1561 std::unique_ptr< QgsCurve > part( curve->curveSubstring( startDistance - distanceTraversed, endDistance - distanceTraversed ) );
1562 if ( part )
1563 substring->addCurve( part.release() );
1564 }
1565
1566 distanceTraversed += thisCurveLength;
1567 if ( distanceTraversed > endDistance )
1568 break;
1569 }
1570
1571 return substring.release();
1572}
1573
1574bool QgsCompoundCurve::addZValue( double zValue )
1575{
1576 if ( QgsWkbTypes::hasZ( mWkbType ) )
1577 return false;
1578
1580
1581 for ( QgsCurve *curve : std::as_const( mCurves ) )
1582 {
1583 curve->addZValue( zValue );
1584 }
1585 clearCache();
1586 return true;
1587}
1588
1589bool QgsCompoundCurve::addMValue( double mValue )
1590{
1591 if ( QgsWkbTypes::hasM( mWkbType ) )
1592 return false;
1593
1595
1596 for ( QgsCurve *curve : std::as_const( mCurves ) )
1597 {
1598 curve->addMValue( mValue );
1599 }
1600 clearCache();
1601 return true;
1602}
1603
1605{
1606 if ( !QgsWkbTypes::hasZ( mWkbType ) )
1607 return false;
1608
1610 for ( QgsCurve *curve : std::as_const( mCurves ) )
1611 {
1612 curve->dropZValue();
1613 }
1614 clearCache();
1615 return true;
1616}
1617
1619{
1620 if ( !QgsWkbTypes::hasM( mWkbType ) )
1621 return false;
1622
1624 for ( QgsCurve *curve : std::as_const( mCurves ) )
1625 {
1626 curve->dropMValue();
1627 }
1628 clearCache();
1629 return true;
1630}
1631
1633{
1634 for ( QgsCurve *curve : std::as_const( mCurves ) )
1635 {
1636 curve->swapXy();
1637 }
1638 clearCache();
1639}
1640
1642{
1643 // Ensure fromVertex < toVertex for simplicity
1644 if ( fromVertex.vertex > toVertex.vertex )
1645 {
1646 return distanceBetweenVertices( toVertex, fromVertex );
1647 }
1648
1649 // Convert QgsVertexId to simple vertex numbers for compound curves (single ring, single part)
1650 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
1651 return -1.0;
1652
1653 const int fromVertexNumber = fromVertex.vertex;
1654 const int toVertexNumber = toVertex.vertex;
1655
1656 const int totalVertices = numPoints();
1657 if ( fromVertexNumber < 0 || fromVertexNumber >= totalVertices || toVertexNumber < 0 || toVertexNumber >= totalVertices )
1658 return -1.0;
1659
1660 if ( fromVertexNumber == toVertexNumber )
1661 return 0.0;
1662
1663 double totalDistance = 0.0;
1664
1665 // Find which curves contain our vertices and accumulate distances
1666 int currentVertexId = 0;
1667 int fromCurve = -1, toCurve = -1;
1668 int fromCurveVertex = -1, toCurveVertex = -1;
1669
1670 // First pass: find which curves contain from and to vertices
1671 for ( int j = 0; j < mCurves.size(); ++j )
1672 {
1673 int nCurvePoints = mCurves.at( j )->numPoints();
1674
1675 // Check if fromVertex is in this curve
1676 if ( fromCurve == -1 && fromVertexNumber >= currentVertexId && fromVertexNumber < currentVertexId + nCurvePoints )
1677 {
1678 fromCurve = j;
1679 fromCurveVertex = fromVertexNumber - currentVertexId;
1680 }
1681
1682 // Check if toVertex is in this curve
1683 if ( toCurve == -1 && toVertexNumber >= currentVertexId && toVertexNumber < currentVertexId + nCurvePoints )
1684 {
1685 toCurve = j;
1686 toCurveVertex = toVertexNumber - currentVertexId;
1687 break;
1688 }
1689
1690 currentVertexId += ( nCurvePoints - 1 ); // Subtract 1 because curves share endpoints
1691 }
1692
1693 if ( fromCurve == -1 || toCurve == -1 )
1694 return -1.0; // Invalid vertex IDs
1695
1696 if ( fromCurve == toCurve )
1697 {
1698 // Both vertices are on the same curve
1699 QgsVertexId fromId( 0, 0, fromCurveVertex );
1700 QgsVertexId toId( 0, 0, toCurveVertex );
1701 return mCurves.at( fromCurve )->distanceBetweenVertices( fromId, toId );
1702 }
1703 else
1704 {
1705 // Vertices are on different curves - accumulate distances across multiple curves
1706
1707 // Distance from fromVertex to end of its curve
1708 if ( fromCurveVertex < mCurves.at( fromCurve )->numPoints() - 1 )
1709 {
1710 QgsVertexId fromId( 0, 0, fromCurveVertex );
1711 QgsVertexId endId( 0, 0, mCurves.at( fromCurve )->numPoints() - 1 );
1712 totalDistance += mCurves.at( fromCurve )->distanceBetweenVertices( fromId, endId );
1713 }
1714
1715 // Distance of complete intermediate curves
1716 for ( int j = fromCurve + 1; j < toCurve; ++j )
1717 {
1718 totalDistance += mCurves.at( j )->length();
1719 }
1720
1721 // Distance from start of toCurve to toVertex
1722 if ( toCurveVertex > 0 )
1723 {
1724 QgsVertexId startId( 0, 0, 0 );
1725 QgsVertexId toId( 0, 0, toCurveVertex );
1726 totalDistance += mCurves.at( toCurve )->distanceBetweenVertices( startId, toId );
1727 }
1728 }
1729
1730 return totalDistance;
1731}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2246
VertexType
Types of vertex.
Definition qgis.h:3303
GeoJsonProfile
GeoJson export Profile according to OGC Features and Geometries JSON - Part 1: Core https://docs....
Definition qgis.h:5134
@ Legacy
Legacy GeoJson profile used in QGIS prior to 4.2, which included some non-standard extensions and dev...
Definition qgis.h:5135
@ Rfc7946
GeoJson profile compliant with RFC7946 standard "http://www.opengis.net/def/profile/OGC/0/rfc7946".
Definition qgis.h:5136
@ JsonFg
GeoJson profile from OGC Features and Geometries JSON Part 1: core "http://www.opengis....
Definition qgis.h:5137
@ JsonFgPlus
GeoJson profile from OGC Features and Geometries JSON Part 1: core "http://www.opengis....
Definition qgis.h:5138
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:314
@ CompoundCurve
CompoundCurve.
Definition qgis.h:325
@ LineString
LineString.
Definition qgis.h:317
@ Unknown
Unknown.
Definition qgis.h:315
@ CircularString
CircularString.
Definition qgis.h:324
TransformDirection
Indicates the direction (forward or inverse) of a transform.
Definition qgis.h:2882
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.
virtual bool moveVertex(QgsVertexId position, const QgsPoint &newPos)=0
Moves a vertex within the geometry.
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle.
virtual double vertexAngle(QgsVertexId vertex) const =0
Returns approximate angle at a vertex.
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.
virtual bool insertVertex(QgsVertexId position, const QgsPoint &vertex)=0
Inserts a vertex into the geometry.
void setZMTypeFromSubGeometry(const QgsAbstractGeometry *subggeom, Qgis::WkbType baseGeomType)
Updates the geometry type based on whether sub geometries contain z or m values.
virtual bool boundingBoxIntersects(const QgsRectangle &rectangle) const
Returns true if the bounding box of this geometry intersects with a rectangle.
virtual 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.
bool isSimpleCurve() const override
This method returns true for classes that can be cast to the QgsSimpleCurve class.
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.
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:415
bool mHasCachedSummedUpArea
Definition qgscurve.h:412
bool mHasCachedSummedUpArea3D
Definition qgscurve.h:414
virtual bool isClosed() const
Returns true if the curve is closed.
Definition qgscurve.cpp:53
bool isValid(QString &error, Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const override
Checks validity of the geometry, and returns true if the geometry is valid.
Definition qgscurve.cpp:247
QgsPoint vertexAt(QgsVertexId id) const override
Returns the point corresponding to a specified vertex id.
Definition qgscurve.cpp:198
QgsBox3D mBoundingBox
Cached bounding box.
Definition qgscurve.h:410
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:413
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:566
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:775
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:8415
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7743
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPoint > QgsPointSequence
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:35
int vertex
Vertex number.
int part
Part number.
Definition qgsvertexid.h:96
int ring
Ring number.
Definition qgsvertexid.h:99