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