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 "qgsmessagelog.h"
19 #include "qgscompoundcurve.h"
20 #include "qgsapplication.h"
21 #include "qgscircularstring.h"
22 #include "qgsgeometryutils.h"
23 #include "qgslinestring.h"
24 #include "qgswkbptr.h"
25 #include "qgsfeedback.h"
26 
27 #include <QJsonObject>
28 #include <QPainter>
29 #include <QPainterPath>
30 #include <memory>
31 #include <nlohmann/json.hpp>
32 
QgsCompoundCurve()33 QgsCompoundCurve::QgsCompoundCurve()
34 {
35   mWkbType = QgsWkbTypes::CompoundCurve;
36 }
37 
~QgsCompoundCurve()38 QgsCompoundCurve::~QgsCompoundCurve()
39 {
40   clear();
41 }
42 
equals(const QgsCurve & other) const43 bool QgsCompoundCurve::equals( const QgsCurve &other ) const
44 {
45   const QgsCompoundCurve *otherCurve = qgsgeometry_cast< const QgsCompoundCurve * >( &other );
46   if ( !otherCurve )
47     return false;
48 
49   if ( mWkbType != otherCurve->mWkbType )
50     return false;
51 
52   if ( mCurves.size() != otherCurve->mCurves.size() )
53     return false;
54 
55   for ( int i = 0; i < mCurves.size(); ++i )
56   {
57     if ( *mCurves.at( i ) != *otherCurve->mCurves.at( i ) )
58       return false;
59   }
60 
61   return true;
62 }
63 
createEmptyWithSameType() const64 QgsCompoundCurve *QgsCompoundCurve::createEmptyWithSameType() const
65 {
66   auto result = std::make_unique< QgsCompoundCurve >();
67   result->mWkbType = mWkbType;
68   return result.release();
69 }
70 
compareToSameClass(const QgsAbstractGeometry * other) const71 int QgsCompoundCurve::compareToSameClass( const QgsAbstractGeometry *other ) const
72 {
73   const QgsCompoundCurve *otherCurve = qgsgeometry_cast<const QgsCompoundCurve *>( other );
74   if ( !otherCurve )
75     return -1;
76 
77   int i = 0;
78   int j = 0;
79   while ( i < mCurves.size() && j < otherCurve->mCurves.size() )
80   {
81     const QgsAbstractGeometry *aGeom = mCurves[i];
82     const QgsAbstractGeometry *bGeom = otherCurve->mCurves[j];
83     const int comparison = aGeom->compareTo( bGeom );
84     if ( comparison != 0 )
85     {
86       return comparison;
87     }
88     i++;
89     j++;
90   }
91   if ( i < mCurves.size() )
92   {
93     return 1;
94   }
95   if ( j < otherCurve->mCurves.size() )
96   {
97     return -1;
98   }
99   return 0;
100 }
101 
geometryType() const102 QString QgsCompoundCurve::geometryType() const
103 {
104   return QStringLiteral( "CompoundCurve" );
105 }
106 
dimension() const107 int QgsCompoundCurve::dimension() const
108 {
109   return 1;
110 }
111 
QgsCompoundCurve(const QgsCompoundCurve & curve)112 QgsCompoundCurve::QgsCompoundCurve( const QgsCompoundCurve &curve ): QgsCurve( curve )
113 {
114   mWkbType = curve.wkbType();
115   mCurves.reserve( curve.mCurves.size() );
116   for ( const QgsCurve *c : curve.mCurves )
117   {
118     mCurves.append( c->clone() );
119   }
120 }
121 
operator =(const QgsCompoundCurve & curve)122 QgsCompoundCurve &QgsCompoundCurve::operator=( const QgsCompoundCurve &curve )
123 {
124   if ( &curve != this )
125   {
126     clearCache();
127     QgsCurve::operator=( curve );
128     for ( const QgsCurve *c : curve.mCurves )
129     {
130       mCurves.append( c->clone() );
131     }
132   }
133   return *this;
134 }
135 
clone() const136 QgsCompoundCurve *QgsCompoundCurve::clone() const
137 {
138   return new QgsCompoundCurve( *this );
139 }
140 
clear()141 void QgsCompoundCurve::clear()
142 {
143   mWkbType = QgsWkbTypes::CompoundCurve;
144   qDeleteAll( mCurves );
145   mCurves.clear();
146   clearCache();
147 }
148 
calculateBoundingBox() const149 QgsRectangle QgsCompoundCurve::calculateBoundingBox() const
150 {
151   if ( mCurves.empty() )
152   {
153     return QgsRectangle();
154   }
155 
156   QgsRectangle bbox = mCurves.at( 0 )->boundingBox();
157   for ( int i = 1; i < mCurves.size(); ++i )
158   {
159     QgsRectangle curveBox = mCurves.at( i )->boundingBox();
160     bbox.combineExtentWith( curveBox );
161   }
162   return bbox;
163 }
164 
scroll(int index)165 void QgsCompoundCurve::scroll( int index )
166 {
167   const int size = numPoints();
168   if ( index < 1 || index >= size - 1 )
169     return;
170 
171   auto [p1, p2 ] = splitCurveAtVertex( index );
172 
173   mCurves.clear();
174   if ( QgsCompoundCurve *curve2 = qgsgeometry_cast< QgsCompoundCurve *>( p2.get() ) )
175   {
176     // take the curves from the second part and make them our first lot of curves
177     mCurves = std::move( curve2->mCurves );
178   }
179   if ( QgsCompoundCurve *curve1 = qgsgeometry_cast< QgsCompoundCurve *>( p1.get() ) )
180   {
181     // take the curves from the first part and append them to our curves
182     mCurves.append( curve1->mCurves );
183     curve1->mCurves.clear();
184   }
185 }
186 
fromWkb(QgsConstWkbPtr & wkbPtr)187 bool QgsCompoundCurve::fromWkb( QgsConstWkbPtr &wkbPtr )
188 {
189   clear();
190   if ( !wkbPtr )
191   {
192     return false;
193   }
194 
195   QgsWkbTypes::Type type = wkbPtr.readHeader();
196   if ( QgsWkbTypes::flatType( type ) != QgsWkbTypes::CompoundCurve )
197   {
198     return false;
199   }
200   mWkbType = type;
201 
202   int nCurves;
203   wkbPtr >> nCurves;
204   QgsCurve *currentCurve = nullptr;
205   for ( int i = 0; i < nCurves; ++i )
206   {
207     QgsWkbTypes::Type curveType = wkbPtr.readHeader();
208     wkbPtr -= 1 + sizeof( int );
209     if ( QgsWkbTypes::flatType( curveType ) == QgsWkbTypes::LineString )
210     {
211       currentCurve = new QgsLineString();
212     }
213     else if ( QgsWkbTypes::flatType( curveType ) == QgsWkbTypes::CircularString )
214     {
215       currentCurve = new QgsCircularString();
216     }
217     else
218     {
219       return false;
220     }
221     currentCurve->fromWkb( wkbPtr );  // also updates wkbPtr
222     mCurves.append( currentCurve );
223   }
224   return true;
225 }
226 
fromWkt(const QString & wkt)227 bool QgsCompoundCurve::fromWkt( const QString &wkt )
228 {
229   clear();
230 
231   QPair<QgsWkbTypes::Type, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
232 
233   if ( QgsWkbTypes::flatType( parts.first ) != QgsWkbTypes::CompoundCurve )
234     return false;
235   mWkbType = parts.first;
236 
237   QString secondWithoutParentheses = parts.second;
238   secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
239   if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
240        secondWithoutParentheses.isEmpty() )
241     return true;
242 
243   QString defaultChildWkbType = QStringLiteral( "LineString%1%2" ).arg( is3D() ? QStringLiteral( "Z" ) : QString(), isMeasure() ? QStringLiteral( "M" ) : QString() );
244 
245   const QStringList blocks = QgsGeometryUtils::wktGetChildBlocks( parts.second, defaultChildWkbType );
246   for ( const QString &childWkt : blocks )
247   {
248     QPair<QgsWkbTypes::Type, QString> childParts = QgsGeometryUtils::wktReadBlock( childWkt );
249 
250     if ( QgsWkbTypes::flatType( childParts.first ) == QgsWkbTypes::LineString )
251       mCurves.append( new QgsLineString() );
252     else if ( QgsWkbTypes::flatType( childParts.first ) == QgsWkbTypes::CircularString )
253       mCurves.append( new QgsCircularString() );
254     else
255     {
256       clear();
257       return false;
258     }
259     if ( !mCurves.back()->fromWkt( childWkt ) )
260     {
261       clear();
262       return false;
263     }
264   }
265 
266   //scan through curves and check if dimensionality of curves is different to compound curve.
267   //if so, update the type dimensionality of the compound curve to match
268   bool hasZ = false;
269   bool hasM = false;
270   for ( const QgsCurve *curve : std::as_const( mCurves ) )
271   {
272     hasZ = hasZ || curve->is3D();
273     hasM = hasM || curve->isMeasure();
274     if ( hasZ && hasM )
275       break;
276   }
277   if ( hasZ )
278     addZValue( 0 );
279   if ( hasM )
280     addMValue( 0 );
281 
282   return true;
283 }
284 
wkbSize(QgsAbstractGeometry::WkbFlags flags) const285 int QgsCompoundCurve::wkbSize( QgsAbstractGeometry::WkbFlags flags ) const
286 {
287   int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
288   for ( const QgsCurve *curve : mCurves )
289   {
290     binarySize += curve->wkbSize( flags );
291   }
292   return binarySize;
293 }
294 
asWkb(WkbFlags flags) const295 QByteArray QgsCompoundCurve::asWkb( WkbFlags flags ) const
296 {
297   QByteArray wkbArray;
298   wkbArray.resize( QgsCompoundCurve::wkbSize( flags ) );
299   QgsWkbPtr wkb( wkbArray );
300   wkb << static_cast<char>( QgsApplication::endian() );
301   wkb << static_cast<quint32>( wkbType() );
302   wkb << static_cast<quint32>( mCurves.size() );
303   for ( const QgsCurve *curve : mCurves )
304   {
305     wkb << curve->asWkb( flags );
306   }
307   return wkbArray;
308 }
309 
asWkt(int precision) const310 QString QgsCompoundCurve::asWkt( int precision ) const
311 {
312   QString wkt = wktTypeStr();
313   if ( isEmpty() )
314     wkt += QLatin1String( " EMPTY" );
315   else
316   {
317     wkt += QLatin1String( " (" );
318     for ( const QgsCurve *curve : mCurves )
319     {
320       QString childWkt = curve->asWkt( precision );
321       if ( qgsgeometry_cast<const QgsLineString *>( curve ) )
322       {
323         // Type names of linear geometries are omitted
324         childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
325       }
326       wkt += childWkt + ',';
327     }
328     if ( wkt.endsWith( ',' ) )
329     {
330       wkt.chop( 1 );
331     }
332     wkt += ')';
333   }
334   return wkt;
335 }
336 
asGml2(QDomDocument & doc,int precision,const QString & ns,const AxisOrder axisOrder) const337 QDomElement QgsCompoundCurve::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
338 {
339   // GML2 does not support curves
340   std::unique_ptr< QgsLineString > line( curveToLine() );
341   QDomElement gml = line->asGml2( doc, precision, ns, axisOrder );
342   return gml;
343 }
344 
asGml3(QDomDocument & doc,int precision,const QString & ns,const QgsAbstractGeometry::AxisOrder axisOrder) const345 QDomElement QgsCompoundCurve::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
346 {
347   QDomElement compoundCurveElem = doc.createElementNS( ns, QStringLiteral( "CompositeCurve" ) );
348 
349   if ( isEmpty() )
350     return compoundCurveElem;
351 
352   for ( const QgsCurve *curve : mCurves )
353   {
354     QDomElement curveMemberElem = doc.createElementNS( ns, QStringLiteral( "curveMember" ) );
355     QDomElement curveElem = curve->asGml3( doc, precision, ns, axisOrder );
356     curveMemberElem.appendChild( curveElem );
357     compoundCurveElem.appendChild( curveMemberElem );
358   }
359 
360   return compoundCurveElem;
361 }
362 
asJsonObject(int precision) const363 json QgsCompoundCurve::asJsonObject( int precision ) const
364 {
365   // GeoJSON does not support curves
366   std::unique_ptr< QgsLineString > line( curveToLine() );
367   return line->asJsonObject( precision );
368 }
369 
length() const370 double QgsCompoundCurve::length() const
371 {
372   double length = 0;
373   for ( const QgsCurve *curve : mCurves )
374   {
375     length += curve->length();
376   }
377   return length;
378 }
379 
startPoint() const380 QgsPoint QgsCompoundCurve::startPoint() const
381 {
382   if ( mCurves.empty() )
383   {
384     return QgsPoint();
385   }
386   return mCurves.at( 0 )->startPoint();
387 }
388 
endPoint() const389 QgsPoint QgsCompoundCurve::endPoint() const
390 {
391   if ( mCurves.empty() )
392   {
393     return QgsPoint();
394   }
395   return mCurves.at( mCurves.size() - 1 )->endPoint();
396 }
397 
points(QgsPointSequence & pts) const398 void QgsCompoundCurve::points( QgsPointSequence &pts ) const
399 {
400   pts.clear();
401   if ( mCurves.empty() )
402   {
403     return;
404   }
405 
406   mCurves[0]->points( pts );
407   for ( int i = 1; i < mCurves.size(); ++i )
408   {
409     QgsPointSequence pList;
410     mCurves[i]->points( pList );
411     pList.removeFirst(); //first vertex already added in previous line
412     pts.append( pList );
413   }
414 }
415 
numPoints() const416 int QgsCompoundCurve::numPoints() const
417 {
418   int nPoints = 0;
419   int nCurves = mCurves.size();
420   if ( nCurves < 1 )
421   {
422     return 0;
423   }
424 
425   for ( int i = 0; i < nCurves; ++i )
426   {
427     nPoints += mCurves.at( i )->numPoints() - 1; //last vertex is equal to first of next section
428   }
429   nPoints += 1; //last vertex was removed above
430   return nPoints;
431 }
432 
isEmpty() const433 bool QgsCompoundCurve::isEmpty() const
434 {
435   if ( mCurves.isEmpty() )
436     return true;
437 
438   for ( QgsCurve *curve : mCurves )
439   {
440     if ( !curve->isEmpty() )
441       return false;
442   }
443   return true;
444 }
445 
isValid(QString & error,Qgis::GeometryValidityFlags flags) const446 bool QgsCompoundCurve::isValid( QString &error, Qgis::GeometryValidityFlags flags ) const
447 {
448   if ( mCurves.isEmpty() )
449     return true;
450 
451   for ( int i = 0; i < mCurves.size() ; ++i )
452   {
453     if ( !mCurves[i]->isValid( error, flags ) )
454     {
455       error = QObject::tr( "Curve[%1]: %2" ).arg( i + 1 ).arg( error );
456       return false;
457     }
458   }
459   return QgsCurve::isValid( error, flags );
460 }
461 
indexOf(const QgsPoint & point) const462 int QgsCompoundCurve::indexOf( const QgsPoint &point ) const
463 {
464   int curveStart = 0;
465   for ( const QgsCurve *curve : mCurves )
466   {
467     const int curveIndex = curve->indexOf( point );
468     if ( curveIndex >= 0 )
469       return curveStart + curveIndex;
470     // subtract 1 here, because the next curve will start with the same
471     // vertex as this curve ended at
472     curveStart += curve->numPoints() - 1;
473   }
474   return -1;
475 }
476 
curveToLine(double tolerance,SegmentationToleranceType toleranceType) const477 QgsLineString *QgsCompoundCurve::curveToLine( double tolerance, SegmentationToleranceType toleranceType ) const
478 {
479   QgsLineString *line = new QgsLineString();
480   std::unique_ptr< QgsLineString > currentLine;
481   for ( const QgsCurve *curve : mCurves )
482   {
483     currentLine.reset( curve->curveToLine( tolerance, toleranceType ) );
484     line->append( currentLine.get() );
485   }
486   return line;
487 }
488 
snappedToGrid(double hSpacing,double vSpacing,double dSpacing,double mSpacing) const489 QgsCompoundCurve *QgsCompoundCurve::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const
490 {
491   std::unique_ptr<QgsCompoundCurve> result( createEmptyWithSameType() );
492 
493   for ( QgsCurve *curve : mCurves )
494   {
495     std::unique_ptr<QgsCurve> gridified( static_cast< QgsCurve * >( curve->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) ) );
496     if ( gridified )
497     {
498       result->mCurves.append( gridified.release() );
499     }
500   }
501 
502   if ( result->mCurves.empty() )
503     return nullptr;
504   else
505     return result.release();
506 }
507 
removeDuplicateNodes(double epsilon,bool useZValues)508 bool QgsCompoundCurve::removeDuplicateNodes( double epsilon, bool useZValues )
509 {
510   bool result = false;
511   const QVector< QgsCurve * > curves = mCurves;
512   int i = 0;
513   QgsPoint lastEnd;
514   for ( QgsCurve *curve : curves )
515   {
516     result = curve->removeDuplicateNodes( epsilon, useZValues ) || result;
517     if ( curve->numPoints() == 0 || qgsDoubleNear( curve->length(), 0.0, epsilon ) )
518     {
519       // empty curve, remove it
520       delete mCurves.takeAt( i );
521       result = true;
522     }
523     else
524     {
525       // ensure this line starts exactly where previous line ended
526       if ( i > 0 )
527       {
528         curve->moveVertex( QgsVertexId( -1, -1, 0 ), lastEnd );
529       }
530       lastEnd = curve->vertexAt( QgsVertexId( -1, -1, curve->numPoints() - 1 ) );
531     }
532     i++;
533   }
534   return result;
535 }
536 
boundingBoxIntersects(const QgsRectangle & rectangle) const537 bool QgsCompoundCurve::boundingBoxIntersects( const QgsRectangle &rectangle ) const
538 {
539   if ( mCurves.empty() )
540     return false;
541 
542   // if we already have the bounding box calculated, then this check is trivial!
543   if ( !mBoundingBox.isNull() )
544   {
545     return mBoundingBox.intersects( rectangle );
546   }
547 
548   // otherwise loop through each member curve and test the bounding box intersection.
549   // This gives us a chance to use optimisations which may be present on the individual
550   // curve subclasses, and at worst it will cause a calculation of the bounding box
551   // of each individual member curve which we would have to do anyway... (and these
552   // bounding boxes are cached, so would be reused without additional expense)
553   for ( const QgsCurve *curve : mCurves )
554   {
555     if ( curve->boundingBoxIntersects( rectangle ) )
556       return true;
557   }
558 
559   // even if we don't intersect the bounding box of any member curves, we may still intersect the
560   // bounding box of the overall compound curve.
561   // so here we fall back to the non-optimised base class check which has to first calculate
562   // the overall bounding box of the compound curve..
563   return QgsAbstractGeometry::boundingBoxIntersects( rectangle );
564 }
565 
simplifiedTypeRef() const566 const QgsAbstractGeometry *QgsCompoundCurve::simplifiedTypeRef() const
567 {
568   if ( mCurves.size() == 1 )
569     return mCurves.at( 0 );
570   else
571     return this;
572 }
573 
curveAt(int i) const574 const QgsCurve *QgsCompoundCurve::curveAt( int i ) const
575 {
576   if ( i < 0 || i >= mCurves.size() )
577   {
578     return nullptr;
579   }
580   return mCurves.at( i );
581 }
582 
addCurve(QgsCurve * c,const bool extendPrevious)583 void QgsCompoundCurve::addCurve( QgsCurve *c, const bool extendPrevious )
584 {
585   if ( !c )
586     return;
587 
588   if ( mCurves.empty() )
589   {
590     setZMTypeFromSubGeometry( c, QgsWkbTypes::CompoundCurve );
591   }
592 
593   if ( QgsWkbTypes::hasZ( mWkbType ) && !QgsWkbTypes::hasZ( c->wkbType() ) )
594   {
595     c->addZValue();
596   }
597   else if ( !QgsWkbTypes::hasZ( mWkbType ) && QgsWkbTypes::hasZ( c->wkbType() ) )
598   {
599     c->dropZValue();
600   }
601   if ( QgsWkbTypes::hasM( mWkbType ) && !QgsWkbTypes::hasM( c->wkbType() ) )
602   {
603     c->addMValue();
604   }
605   else if ( !QgsWkbTypes::hasM( mWkbType ) && QgsWkbTypes::hasM( c->wkbType() ) )
606   {
607     c->dropMValue();
608   }
609 
610   QgsLineString *previousLineString = !mCurves.empty() ? qgsgeometry_cast< QgsLineString * >( mCurves.constLast() ) : nullptr;
611   const QgsLineString *newLineString = qgsgeometry_cast< const QgsLineString * >( c );
612   const bool canExtendPrevious = extendPrevious && previousLineString && newLineString;
613   if ( canExtendPrevious )
614   {
615     previousLineString->append( newLineString );
616     // we are taking ownership, so delete the input curve
617     delete c;
618     c = nullptr;
619   }
620   else
621   {
622     mCurves.append( c );
623   }
624 
625   clearCache();
626 }
627 
removeCurve(int i)628 void QgsCompoundCurve::removeCurve( int i )
629 {
630   if ( i < 0 || i >= mCurves.size() )
631   {
632     return;
633   }
634 
635   delete mCurves.takeAt( i );
636   clearCache();
637 }
638 
addVertex(const QgsPoint & pt)639 void QgsCompoundCurve::addVertex( const QgsPoint &pt )
640 {
641   if ( mCurves.isEmpty() || mWkbType == QgsWkbTypes::Unknown )
642   {
643     setZMTypeFromSubGeometry( &pt, QgsWkbTypes::CompoundCurve );
644   }
645 
646   //is last curve QgsLineString
647   QgsCurve *lastCurve = nullptr;
648   if ( !mCurves.isEmpty() )
649   {
650     lastCurve = mCurves.at( mCurves.size() - 1 );
651   }
652 
653   QgsLineString *line = nullptr;
654   if ( !lastCurve || QgsWkbTypes::flatType( lastCurve->wkbType() ) != QgsWkbTypes::LineString )
655   {
656     line = new QgsLineString();
657     mCurves.append( line );
658     if ( lastCurve )
659     {
660       line->addVertex( lastCurve->endPoint() );
661     }
662     lastCurve = line;
663   }
664   else //create new QgsLineString* with point in it
665   {
666     line = static_cast<QgsLineString *>( lastCurve );
667   }
668   line->addVertex( pt );
669   clearCache();
670 }
671 
condenseCurves()672 void QgsCompoundCurve::condenseCurves()
673 {
674   QgsCurve *lastCurve = nullptr;
675   QVector< QgsCurve * > newCurves;
676   newCurves.reserve( mCurves.size() );
677   for ( QgsCurve *curve : std::as_const( mCurves ) )
678   {
679     if ( lastCurve && lastCurve->wkbType() == curve->wkbType() )
680     {
681       if ( QgsLineString *ls = qgsgeometry_cast< QgsLineString * >( lastCurve ) )
682       {
683         ls->append( qgsgeometry_cast< QgsLineString * >( curve ) );
684         delete curve;
685       }
686       else if ( QgsCircularString *cs = qgsgeometry_cast< QgsCircularString * >( lastCurve ) )
687       {
688         cs->append( qgsgeometry_cast< QgsCircularString * >( curve ) );
689         delete curve;
690       }
691     }
692     else
693     {
694       lastCurve = curve;
695       newCurves << curve;
696     }
697   }
698   mCurves = newCurves;
699 }
700 
draw(QPainter & p) const701 void QgsCompoundCurve::draw( QPainter &p ) const
702 {
703   for ( const QgsCurve *curve : mCurves )
704   {
705     curve->draw( p );
706   }
707 }
708 
transform(const QgsCoordinateTransform & ct,Qgis::TransformDirection d,bool transformZ)709 void QgsCompoundCurve::transform( const QgsCoordinateTransform &ct, Qgis::TransformDirection d, bool transformZ )
710 {
711   for ( QgsCurve *curve : std::as_const( mCurves ) )
712   {
713     curve->transform( ct, d, transformZ );
714   }
715   clearCache();
716 }
717 
transform(const QTransform & t,double zTranslate,double zScale,double mTranslate,double mScale)718 void QgsCompoundCurve::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
719 {
720   for ( QgsCurve *curve : std::as_const( mCurves ) )
721   {
722     curve->transform( t, zTranslate, zScale, mTranslate, mScale );
723   }
724   clearCache();
725 }
726 
addToPainterPath(QPainterPath & path) const727 void QgsCompoundCurve::addToPainterPath( QPainterPath &path ) const
728 {
729   QPainterPath pp;
730   for ( const QgsCurve *curve : mCurves )
731   {
732     curve->addToPainterPath( pp );
733   }
734   path.addPath( pp );
735 }
736 
drawAsPolygon(QPainter & p) const737 void QgsCompoundCurve::drawAsPolygon( QPainter &p ) const
738 {
739   QPainterPath pp;
740   for ( const QgsCurve *curve : mCurves )
741   {
742     curve->addToPainterPath( pp );
743   }
744   p.drawPath( pp );
745 }
746 
insertVertex(QgsVertexId position,const QgsPoint & vertex)747 bool QgsCompoundCurve::insertVertex( QgsVertexId position, const QgsPoint &vertex )
748 {
749   QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
750   if ( curveIds.empty() )
751   {
752     return false;
753   }
754   int curveId = curveIds.at( 0 ).first;
755   if ( curveId >= mCurves.size() )
756   {
757     return false;
758   }
759 
760   bool success = mCurves.at( curveId )->insertVertex( curveIds.at( 0 ).second, vertex );
761   if ( success )
762   {
763     clearCache(); //bbox changed
764   }
765   return success;
766 }
767 
moveVertex(QgsVertexId position,const QgsPoint & newPos)768 bool QgsCompoundCurve::moveVertex( QgsVertexId position, const QgsPoint &newPos )
769 {
770   QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
771   QVector< QPair<int, QgsVertexId> >::const_iterator idIt = curveIds.constBegin();
772   for ( ; idIt != curveIds.constEnd(); ++idIt )
773   {
774     mCurves.at( idIt->first )->moveVertex( idIt->second, newPos );
775   }
776 
777   bool success = !curveIds.isEmpty();
778   if ( success )
779   {
780     clearCache(); //bbox changed
781   }
782   return success;
783 }
784 
deleteVertex(QgsVertexId position)785 bool QgsCompoundCurve::deleteVertex( QgsVertexId position )
786 {
787   QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
788   if ( curveIds.size() == 1 )
789   {
790     if ( !mCurves.at( curveIds.at( 0 ).first )->deleteVertex( curveIds.at( 0 ).second ) )
791     {
792       clearCache(); //bbox may have changed
793       return false;
794     }
795     if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() == 0 )
796     {
797       removeCurve( curveIds.at( 0 ).first );
798     }
799   }
800   else if ( curveIds.size() == 2 )
801   {
802     Q_ASSERT( curveIds.at( 1 ).first == curveIds.at( 0 ).first + 1 );
803     Q_ASSERT( curveIds.at( 0 ).second.vertex == mCurves.at( curveIds.at( 0 ).first )->numPoints() - 1 );
804     Q_ASSERT( curveIds.at( 1 ).second.vertex == 0 );
805     QgsPoint startPoint = mCurves.at( curveIds.at( 0 ).first ) ->startPoint();
806     QgsPoint endPoint = mCurves.at( curveIds.at( 1 ).first ) ->endPoint();
807     if ( QgsWkbTypes::flatType( mCurves.at( curveIds.at( 0 ).first )->wkbType() ) == QgsWkbTypes::LineString &&
808          QgsWkbTypes::flatType( mCurves.at( curveIds.at( 1 ).first )->wkbType() ) == QgsWkbTypes::CircularString &&
809          mCurves.at( curveIds.at( 1 ).first )->numPoints() > 3 )
810     {
811       QgsPoint intermediatePoint;
812       Qgis::VertexType type;
813       mCurves.at( curveIds.at( 1 ).first ) ->pointAt( 2, intermediatePoint, type );
814       mCurves.at( curveIds.at( 0 ).first )->moveVertex(
815         QgsVertexId( 0, 0, mCurves.at( curveIds.at( 0 ).first )->numPoints() - 1 ), intermediatePoint );
816     }
817     else if ( !mCurves.at( curveIds.at( 0 ).first )->deleteVertex( curveIds.at( 0 ).second ) )
818     {
819       clearCache(); //bbox may have changed
820       return false;
821     }
822     if ( QgsWkbTypes::flatType( mCurves.at( curveIds.at( 0 ).first )->wkbType() ) == QgsWkbTypes::CircularString &&
823          mCurves.at( curveIds.at( 0 ).first )->numPoints() > 0 &&
824          QgsWkbTypes::flatType( mCurves.at( curveIds.at( 1 ).first )->wkbType() ) == QgsWkbTypes::LineString )
825     {
826       QgsPoint intermediatePoint = mCurves.at( curveIds.at( 0 ).first ) ->endPoint();
827       mCurves.at( curveIds.at( 1 ).first )->moveVertex( QgsVertexId( 0, 0, 0 ), intermediatePoint );
828     }
829     else if ( !mCurves.at( curveIds.at( 1 ).first )->deleteVertex( curveIds.at( 1 ).second ) )
830     {
831       clearCache(); //bbox may have changed
832       return false;
833     }
834     if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() == 0 &&
835          mCurves.at( curveIds.at( 1 ).first )->numPoints() != 0 )
836     {
837       mCurves.at( curveIds.at( 1 ).first )->moveVertex( QgsVertexId( 0, 0, 0 ), startPoint );
838       removeCurve( curveIds.at( 0 ).first );
839     }
840     else if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() != 0 &&
841               mCurves.at( curveIds.at( 1 ).first )->numPoints() == 0 )
842     {
843       mCurves.at( curveIds.at( 0 ).first )->moveVertex(
844         QgsVertexId( 0, 0, mCurves.at( curveIds.at( 0 ).first )->numPoints() - 1 ), endPoint );
845       removeCurve( curveIds.at( 1 ).first );
846     }
847     else if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() == 0 &&
848               mCurves.at( curveIds.at( 1 ).first )->numPoints() == 0 )
849     {
850       removeCurve( curveIds.at( 1 ).first );
851       removeCurve( curveIds.at( 0 ).first );
852       QgsLineString *line = new QgsLineString();
853       line->insertVertex( QgsVertexId( 0, 0, 0 ), startPoint );
854       line->insertVertex( QgsVertexId( 0, 0, 1 ), endPoint );
855       mCurves.insert( curveIds.at( 0 ).first, line );
856     }
857     else
858     {
859       QgsPoint endPointOfFirst = mCurves.at( curveIds.at( 0 ).first ) ->endPoint();
860       QgsPoint startPointOfSecond = mCurves.at( curveIds.at( 1 ).first ) ->startPoint();
861       if ( endPointOfFirst != startPointOfSecond )
862       {
863         QgsLineString *line = new QgsLineString();
864         line->insertVertex( QgsVertexId( 0, 0, 0 ), endPointOfFirst );
865         line->insertVertex( QgsVertexId( 0, 0, 1 ), startPointOfSecond );
866         mCurves.insert( curveIds.at( 1 ).first, line );
867       }
868     }
869   }
870 
871   bool success = !curveIds.isEmpty();
872   if ( success )
873   {
874     clearCache(); //bbox changed
875   }
876   return success;
877 }
878 
curveVertexId(QgsVertexId id) const879 QVector< QPair<int, QgsVertexId> > QgsCompoundCurve::curveVertexId( QgsVertexId id ) const
880 {
881   QVector< QPair<int, QgsVertexId> > curveIds;
882 
883   int currentVertexIndex = 0;
884   for ( int i = 0; i < mCurves.size(); ++i )
885   {
886     int increment = mCurves.at( i )->numPoints() - 1;
887     if ( id.vertex >= currentVertexIndex && id.vertex <= currentVertexIndex + increment )
888     {
889       int curveVertexId = id.vertex - currentVertexIndex;
890       QgsVertexId vid;
891       vid.part = 0;
892       vid.ring = 0;
893       vid.vertex = curveVertexId;
894       curveIds.append( qMakePair( i, vid ) );
895       if ( curveVertexId == increment && i < ( mCurves.size() - 1 ) ) //add first vertex of next curve
896       {
897         vid.vertex = 0;
898         curveIds.append( qMakePair( i + 1, vid ) );
899       }
900       break;
901     }
902     else if ( id.vertex >= currentVertexIndex && id.vertex == currentVertexIndex + increment + 1 && i == ( mCurves.size() - 1 ) )
903     {
904       int curveVertexId = id.vertex - currentVertexIndex;
905       QgsVertexId vid;
906       vid.part = 0;
907       vid.ring = 0;
908       vid.vertex = curveVertexId;
909       curveIds.append( qMakePair( i, vid ) );
910       break;
911     }
912     currentVertexIndex += increment;
913   }
914 
915   return curveIds;
916 }
917 
toggleCircularAtVertex(QgsVertexId position)918 bool QgsCompoundCurve::toggleCircularAtVertex( QgsVertexId position )
919 {
920 
921   // First we find out the sub-curves that are contain that vertex.
922 
923   // If there is more than one, it means the vertex was at the beginning or end
924   // of an arc, which we don't support.
925 
926   // If there is exactly one, we may either be on a LineString, or on a CircularString.
927 
928   // If on CircularString, we need to check if the vertex is a CurveVertex (odd index).
929   // If so, we split the subcurve at vertex -1 and +1, , drop the middle part and insert a LineString/CircularString
930   // instead with the same points.
931 
932   // At the end, we call condenseCurves() to merge successible line/circular strings
933 
934   QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
935 
936   // We cannot convert points at start/end of subcurves
937   if ( curveIds.length() != 1 )
938     return false;
939 
940   int curveId = curveIds[0].first;
941   QgsVertexId subVertexId = curveIds[0].second;
942   QgsCurve *curve = mCurves[curveId];
943 
944   // We cannot convert first/last point of curve
945   if ( subVertexId.vertex == 0 || subVertexId.vertex == curve->numPoints() - 1 )
946     return false;
947 
948   if ( const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString *>( curve ) )
949   {
950     // If it's a circular string, we convert to LineString
951 
952     // We cannot convert start/end points of arcs
953     if ( subVertexId.vertex % 2 == 0 ) // for some reason, subVertexId.type is always SegmentVertex...
954       return false;
955 
956     QgsPointSequence points;
957     circularString->points( points );
958 
959     const QgsPointSequence partA  = points.mid( 0, subVertexId.vertex );
960     const QgsPointSequence partB  = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex] << points[subVertexId.vertex + 1];
961     const QgsPointSequence partC  = points.mid( subVertexId.vertex + 1 );
962 
963     std::unique_ptr<QgsCircularString> curveA = std::make_unique<QgsCircularString>();
964     curveA->setPoints( partA );
965     std::unique_ptr<QgsLineString> curveB = std::make_unique<QgsLineString>();
966     curveB->setPoints( partB );
967     std::unique_ptr<QgsCircularString> curveC = std::make_unique<QgsCircularString>();
968     curveC->setPoints( partC );
969 
970     removeCurve( curveId );
971     if ( subVertexId.vertex < points.length() - 2 )
972       mCurves.insert( curveId, curveC.release() );
973     mCurves.insert( curveId, curveB.release() );
974     if ( subVertexId.vertex > 1 )
975       mCurves.insert( curveId, curveA.release() );
976   }
977   else if ( const QgsLineString *lineString = dynamic_cast<const QgsLineString *>( curve ) )
978   {
979     // If it's a linestring, we split and insert a curve
980 
981     QgsPointSequence points;
982     lineString->points( points );
983 
984     const QgsPointSequence partA  = points.mid( 0, subVertexId.vertex );
985     const QgsPointSequence partB  = QgsPointSequence() << points[subVertexId.vertex - 1] << points[subVertexId.vertex] << points[subVertexId.vertex + 1];
986     const QgsPointSequence partC  = points.mid( subVertexId.vertex + 1 );
987 
988     QgsLineString *curveA = new QgsLineString();
989     curveA->setPoints( partA );
990     QgsCircularString *curveB = new QgsCircularString();
991     curveB->setPoints( partB );
992     QgsLineString *curveC = new QgsLineString();
993     curveC->setPoints( partC );
994 
995     removeCurve( curveId );
996     if ( subVertexId.vertex < points.length() - 2 )
997       mCurves.insert( curveId, curveC );
998     mCurves.insert( curveId, curveB );
999     if ( subVertexId.vertex > 1 )
1000       mCurves.insert( curveId, curveA );
1001   }
1002 
1003   // We merge consecutive LineStrings
1004   condenseCurves();
1005 
1006   clearCache();
1007   return true;
1008 }
1009 
1010 
closestSegment(const QgsPoint & pt,QgsPoint & segmentPt,QgsVertexId & vertexAfter,int * leftOf,double epsilon) const1011 double QgsCompoundCurve::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt,  QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1012 {
1013   return QgsGeometryUtils::closestSegmentFromComponents( mCurves, QgsGeometryUtils::Vertex, pt, segmentPt, vertexAfter, leftOf, epsilon );
1014 }
1015 
pointAt(int node,QgsPoint & point,Qgis::VertexType & type) const1016 bool QgsCompoundCurve::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
1017 {
1018   int currentVertexId = 0;
1019   for ( int j = 0; j < mCurves.size(); ++j )
1020   {
1021     int nCurvePoints = mCurves.at( j )->numPoints();
1022     if ( ( node - currentVertexId ) < nCurvePoints )
1023     {
1024       return ( mCurves.at( j )->pointAt( node - currentVertexId, point, type ) );
1025     }
1026     currentVertexId += ( nCurvePoints - 1 );
1027   }
1028   return false;
1029 }
1030 
xAt(int index) const1031 double QgsCompoundCurve::xAt( int index ) const
1032 {
1033   int currentVertexId = 0;
1034   for ( int j = 0; j < mCurves.size(); ++j )
1035   {
1036     int nCurvePoints = mCurves.at( j )->numPoints();
1037     if ( ( index - currentVertexId ) < nCurvePoints )
1038     {
1039       return mCurves.at( j )->xAt( index - currentVertexId );
1040     }
1041     currentVertexId += ( nCurvePoints - 1 );
1042   }
1043   return 0.0;
1044 }
1045 
yAt(int index) const1046 double QgsCompoundCurve::yAt( int index ) const
1047 {
1048   int currentVertexId = 0;
1049   for ( int j = 0; j < mCurves.size(); ++j )
1050   {
1051     int nCurvePoints = mCurves.at( j )->numPoints();
1052     if ( ( index - currentVertexId ) < nCurvePoints )
1053     {
1054       return mCurves.at( j )->yAt( index - currentVertexId );
1055     }
1056     currentVertexId += ( nCurvePoints - 1 );
1057   }
1058   return 0.0;
1059 }
1060 
transform(QgsAbstractGeometryTransformer * transformer,QgsFeedback * feedback)1061 bool QgsCompoundCurve::transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback )
1062 {
1063   bool res = true;
1064   for ( QgsCurve *curve : std::as_const( mCurves ) )
1065   {
1066     if ( !curve->transform( transformer ) )
1067     {
1068       res = false;
1069       break;
1070     }
1071 
1072     if ( feedback && feedback->isCanceled() )
1073     {
1074       res = false;
1075       break;
1076     }
1077   }
1078   clearCache();
1079   return res;
1080 }
1081 
filterVertices(const std::function<bool (const QgsPoint &)> & filter)1082 void QgsCompoundCurve::filterVertices( const std::function<bool ( const QgsPoint & )> &filter )
1083 {
1084   for ( QgsCurve *curve : std::as_const( mCurves ) )
1085   {
1086     curve->filterVertices( filter );
1087   }
1088   clearCache();
1089 }
1090 
transformVertices(const std::function<QgsPoint (const QgsPoint &)> & transform)1091 void QgsCompoundCurve::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
1092 {
1093   for ( QgsCurve *curve : std::as_const( mCurves ) )
1094   {
1095     curve->transformVertices( transform );
1096   }
1097   clearCache();
1098 }
1099 
splitCurveAtVertex(int index) const1100 std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsCompoundCurve::splitCurveAtVertex( int index ) const
1101 {
1102   if ( mCurves.empty() )
1103     return std::make_tuple( std::make_unique< QgsCompoundCurve >(), std::make_unique< QgsCompoundCurve >() );
1104 
1105   int curveStart = 0;
1106 
1107   std::unique_ptr< QgsCompoundCurve > curve1 = std::make_unique< QgsCompoundCurve >();
1108   std::unique_ptr< QgsCompoundCurve > curve2;
1109 
1110   for ( const QgsCurve *curve : mCurves )
1111   {
1112     const int curveSize = curve->numPoints();
1113     if ( !curve2 && index < curveStart + curveSize )
1114     {
1115       // split the curve
1116       auto [ p1, p2 ] = curve->splitCurveAtVertex( index - curveStart );
1117       if ( !p1->isEmpty() )
1118         curve1->addCurve( p1.release() );
1119 
1120       curve2 = std::make_unique< QgsCompoundCurve >();
1121       if ( !p2->isEmpty() )
1122         curve2->addCurve( p2.release() );
1123     }
1124     else
1125     {
1126       if ( curve2 )
1127         curve2->addCurve( curve->clone() );
1128       else
1129         curve1->addCurve( curve->clone() );
1130     }
1131 
1132     // subtract 1 here, because the next curve will start with the same
1133     // vertex as this curve ended at
1134     curveStart += curve->numPoints() - 1;
1135   }
1136 
1137   return std::make_tuple( std::move( curve1 ), curve2 ? std::move( curve2 ) : std::make_unique< QgsCompoundCurve >() );
1138 }
1139 
sumUpArea(double & sum) const1140 void QgsCompoundCurve::sumUpArea( double &sum ) const
1141 {
1142   for ( const QgsCurve *curve : mCurves )
1143   {
1144     curve->sumUpArea( sum );
1145   }
1146 }
1147 
close()1148 void QgsCompoundCurve::close()
1149 {
1150   if ( numPoints() < 1 || isClosed() )
1151   {
1152     return;
1153   }
1154   addVertex( startPoint() );
1155 }
1156 
hasCurvedSegments() const1157 bool QgsCompoundCurve::hasCurvedSegments() const
1158 {
1159   for ( const QgsCurve *curve : mCurves )
1160   {
1161     if ( curve->hasCurvedSegments() )
1162     {
1163       return true;
1164     }
1165   }
1166   return false;
1167 }
1168 
vertexAngle(QgsVertexId vertex) const1169 double QgsCompoundCurve::vertexAngle( QgsVertexId vertex ) const
1170 {
1171   QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( vertex );
1172   if ( curveIds.size() == 1 )
1173   {
1174     QgsCurve *curve = mCurves[curveIds.at( 0 ).first];
1175     return curve->vertexAngle( curveIds.at( 0 ).second );
1176   }
1177   else if ( curveIds.size() > 1 )
1178   {
1179     QgsCurve *curve1 = mCurves[curveIds.at( 0 ).first];
1180     QgsCurve *curve2 = mCurves[curveIds.at( 1 ).first];
1181     double angle1 = curve1->vertexAngle( curveIds.at( 0 ).second );
1182     double angle2 = curve2->vertexAngle( curveIds.at( 1 ).second );
1183     return QgsGeometryUtils::averageAngle( angle1, angle2 );
1184   }
1185   else
1186   {
1187     return 0.0;
1188   }
1189 }
1190 
segmentLength(QgsVertexId startVertex) const1191 double QgsCompoundCurve::segmentLength( QgsVertexId startVertex ) const
1192 {
1193   QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( startVertex );
1194   double length = 0.0;
1195   for ( auto it = curveIds.constBegin(); it != curveIds.constEnd(); ++it )
1196   {
1197     length += mCurves.at( it->first )->segmentLength( it->second );
1198   }
1199   return length;
1200 }
1201 
reversed() const1202 QgsCompoundCurve *QgsCompoundCurve::reversed() const
1203 {
1204   QgsCompoundCurve *clone = new QgsCompoundCurve();
1205   for ( int i = mCurves.count() - 1; i >= 0; --i )
1206   {
1207     QgsCurve *reversedCurve = mCurves.at( i )->reversed();
1208     clone->addCurve( reversedCurve );
1209   }
1210   return clone;
1211 }
1212 
interpolatePoint(const double distance) const1213 QgsPoint *QgsCompoundCurve::interpolatePoint( const double distance ) const
1214 {
1215   if ( distance < 0 )
1216     return nullptr;
1217 
1218   double distanceTraversed = 0;
1219   for ( const QgsCurve *curve : mCurves )
1220   {
1221     const double thisCurveLength = curve->length();
1222     if ( distanceTraversed + thisCurveLength > distance || qgsDoubleNear( distanceTraversed + thisCurveLength, distance ) )
1223     {
1224       // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1225       const double distanceToPoint = std::min( distance - distanceTraversed, thisCurveLength );
1226 
1227       // point falls on this curve
1228       return curve->interpolatePoint( distanceToPoint );
1229     }
1230 
1231     distanceTraversed += thisCurveLength;
1232   }
1233 
1234   return nullptr;
1235 }
1236 
curveSubstring(double startDistance,double endDistance) const1237 QgsCompoundCurve *QgsCompoundCurve::curveSubstring( double startDistance, double endDistance ) const
1238 {
1239   if ( startDistance < 0 && endDistance < 0 )
1240     return createEmptyWithSameType();
1241 
1242   endDistance = std::max( startDistance, endDistance );
1243   std::unique_ptr< QgsCompoundCurve > substring = std::make_unique< QgsCompoundCurve >();
1244 
1245   double distanceTraversed = 0;
1246   for ( const QgsCurve *curve : mCurves )
1247   {
1248     const double thisCurveLength = curve->length();
1249     if ( distanceTraversed + thisCurveLength < startDistance )
1250     {
1251       // keep going - haven't found start yet, so no need to include this curve at all
1252     }
1253     else
1254     {
1255       std::unique_ptr< QgsCurve > part( curve->curveSubstring( startDistance - distanceTraversed, endDistance - distanceTraversed ) );
1256       if ( part )
1257         substring->addCurve( part.release() );
1258     }
1259 
1260     distanceTraversed += thisCurveLength;
1261     if ( distanceTraversed > endDistance )
1262       break;
1263   }
1264 
1265   return substring.release();
1266 }
1267 
addZValue(double zValue)1268 bool QgsCompoundCurve::addZValue( double zValue )
1269 {
1270   if ( QgsWkbTypes::hasZ( mWkbType ) )
1271     return false;
1272 
1273   mWkbType = QgsWkbTypes::addZ( mWkbType );
1274 
1275   for ( QgsCurve *curve : std::as_const( mCurves ) )
1276   {
1277     curve->addZValue( zValue );
1278   }
1279   clearCache();
1280   return true;
1281 }
1282 
addMValue(double mValue)1283 bool QgsCompoundCurve::addMValue( double mValue )
1284 {
1285   if ( QgsWkbTypes::hasM( mWkbType ) )
1286     return false;
1287 
1288   mWkbType = QgsWkbTypes::addM( mWkbType );
1289 
1290   for ( QgsCurve *curve : std::as_const( mCurves ) )
1291   {
1292     curve->addMValue( mValue );
1293   }
1294   clearCache();
1295   return true;
1296 }
1297 
dropZValue()1298 bool QgsCompoundCurve::dropZValue()
1299 {
1300   if ( !QgsWkbTypes::hasZ( mWkbType ) )
1301     return false;
1302 
1303   mWkbType = QgsWkbTypes::dropZ( mWkbType );
1304   for ( QgsCurve *curve : std::as_const( mCurves ) )
1305   {
1306     curve->dropZValue();
1307   }
1308   clearCache();
1309   return true;
1310 }
1311 
dropMValue()1312 bool QgsCompoundCurve::dropMValue()
1313 {
1314   if ( !QgsWkbTypes::hasM( mWkbType ) )
1315     return false;
1316 
1317   mWkbType = QgsWkbTypes::dropM( mWkbType );
1318   for ( QgsCurve *curve : std::as_const( mCurves ) )
1319   {
1320     curve->dropMValue();
1321   }
1322   clearCache();
1323   return true;
1324 }
1325 
swapXy()1326 void QgsCompoundCurve::swapXy()
1327 {
1328   for ( QgsCurve *curve : std::as_const( mCurves ) )
1329   {
1330     curve->swapXy();
1331   }
1332   clearCache();
1333 }
1334