QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
qgscircularstring.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscircularstring.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 "qgscircularstring.h"
19
20#include <memory>
21#include <nlohmann/json.hpp>
22
23#include "qgsapplication.h"
24#include "qgsbox3d.h"
26#include "qgsfeedback.h"
28#include "qgsgeometryutils.h"
30#include "qgslinestring.h"
31#include "qgspoint.h"
32#include "qgsrectangle.h"
33#include "qgswkbptr.h"
34
35#include <QJsonObject>
36#include <QPainter>
37#include <QPainterPath>
38
43
45{
46 //get wkb type from first point
47 bool hasZ = p1.is3D();
48 bool hasM = p1.isMeasure();
50
51 mX.resize( 3 );
52 mX[ 0 ] = p1.x();
53 mX[ 1 ] = p2.x();
54 mX[ 2 ] = p3.x();
55 mY.resize( 3 );
56 mY[ 0 ] = p1.y();
57 mY[ 1 ] = p2.y();
58 mY[ 2 ] = p3.y();
59 if ( hasZ )
60 {
62 mZ.resize( 3 );
63 mZ[ 0 ] = p1.z();
64 mZ[ 1 ] = p2.z();
65 mZ[ 2 ] = p3.z();
66 }
67 if ( hasM )
68 {
70 mM.resize( 3 );
71 mM[ 0 ] = p1.m();
72 mM[ 1 ] = p2.m();
73 mM[ 2 ] = p3.m();
74 }
75}
76
77QgsCircularString::QgsCircularString( const QVector<double> &x, const QVector<double> &y, const QVector<double> &z, const QVector<double> &m )
78{
80 int pointCount = std::min( x.size(), y.size() );
81 if ( x.size() == pointCount )
82 {
83 mX = x;
84 }
85 else
86 {
87 mX = x.mid( 0, pointCount );
88 }
89 if ( y.size() == pointCount )
90 {
91 mY = y;
92 }
93 else
94 {
95 mY = y.mid( 0, pointCount );
96 }
97 if ( !z.isEmpty() && z.count() >= pointCount )
98 {
100 if ( z.size() == pointCount )
101 {
102 mZ = z;
103 }
104 else
105 {
106 mZ = z.mid( 0, pointCount );
107 }
108 }
109 if ( !m.isEmpty() && m.count() >= pointCount )
110 {
112 if ( m.size() == pointCount )
113 {
114 mM = m;
115 }
116 else
117 {
118 mM = m.mid( 0, pointCount );
119 }
120 }
121}
122
123QgsCircularString QgsCircularString::fromTwoPointsAndCenter( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &center, const bool useShortestArc )
124{
125 const QgsPoint midPoint = QgsGeometryUtils::segmentMidPointFromCenter( p1, p2, center, useShortestArc );
126 return QgsCircularString( p1, midPoint, p2 );
127}
128
130{
131 auto result = std::make_unique< QgsCircularString >();
132 result->mWkbType = mWkbType;
133 return result.release();
134}
135
137{
139 if ( !otherLine )
140 return -1;
141
142 const int size = mX.size();
143 const int otherSize = otherLine->mX.size();
144 if ( size > otherSize )
145 {
146 return 1;
147 }
148 else if ( size < otherSize )
149 {
150 return -1;
151 }
152
153 if ( is3D() && !otherLine->is3D() )
154 return 1;
155 else if ( !is3D() && otherLine->is3D() )
156 return -1;
157 const bool considerZ = is3D();
158
159 if ( isMeasure() && !otherLine->isMeasure() )
160 return 1;
161 else if ( !isMeasure() && otherLine->isMeasure() )
162 return -1;
163 const bool considerM = isMeasure();
164
165 for ( int i = 0; i < size; i++ )
166 {
167 const double x = mX[i];
168 const double otherX = otherLine->mX[i];
169 if ( x < otherX )
170 {
171 return -1;
172 }
173 else if ( x > otherX )
174 {
175 return 1;
176 }
177
178 const double y = mY[i];
179 const double otherY = otherLine->mY[i];
180 if ( y < otherY )
181 {
182 return -1;
183 }
184 else if ( y > otherY )
185 {
186 return 1;
187 }
188
189 if ( considerZ )
190 {
191 const double z = mZ[i];
192 const double otherZ = otherLine->mZ[i];
193
194 if ( z < otherZ )
195 {
196 return -1;
197 }
198 else if ( z > otherZ )
199 {
200 return 1;
201 }
202 }
203
204 if ( considerM )
205 {
206 const double m = mM[i];
207 const double otherM = otherLine->mM[i];
208
209 if ( m < otherM )
210 {
211 return -1;
212 }
213 else if ( m > otherM )
214 {
215 return 1;
216 }
217 }
218 }
219 return 0;
220}
221
223{
224 return QStringLiteral( "CircularString" );
225}
226
228{
229 return 1;
230}
231
233{
234 return new QgsCircularString( *this );
235}
236
238{
240 mX.clear();
241 mY.clear();
242 mZ.clear();
243 mM.clear();
244 clearCache();
245}
246
248{
249 QgsBox3D bbox;
250 int nPoints = numPoints();
251 for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
252 {
253 QgsRectangle box2d = segmentBoundingBox( QgsPoint( mX[i], mY[i] ), QgsPoint( mX[i + 1], mY[i + 1] ), QgsPoint( mX[i + 2], mY[i + 2] ) );
254 double zMin = std::numeric_limits<double>::quiet_NaN();
255 double zMax = std::numeric_limits<double>::quiet_NaN();
256 if ( is3D() )
257 {
258 zMin = *std::min_element( mZ.begin() + i, mZ.begin() + i + 3 );
259 zMax = *std::max_element( mZ.begin() + i, mZ.begin() + i + 3 );
260 }
261 if ( i == 0 )
262 {
263 bbox = QgsBox3D( box2d, zMin, zMax );
264 }
265 else
266 {
267 bbox.combineWith( QgsBox3D( box2d, zMin, zMax ) );
268 }
269 }
270
271 if ( nPoints > 0 && nPoints % 2 == 0 )
272 {
273 double z = std::numeric_limits<double>::quiet_NaN();
274 if ( nPoints == 2 )
275 {
276 if ( is3D() )
277 {
278 z = mZ[ 0 ];
279 }
280 bbox.combineWith( mX[ 0 ], mY[ 0 ], z );
281 }
282 if ( is3D() )
283 {
284 z = mZ[ nPoints - 1 ];
285 }
286 bbox.combineWith( mX[ nPoints - 1 ], mY[ nPoints - 1 ], z );
287 }
288 return bbox;
289}
290
292{
293 const int size = mX.size();
294 if ( index < 1 || index >= size - 1 )
295 return;
296
297 const bool useZ = is3D();
298 const bool useM = isMeasure();
299
300 QVector<double> newX( size );
301 QVector<double> newY( size );
302 QVector<double> newZ( useZ ? size : 0 );
303 QVector<double> newM( useM ? size : 0 );
304 auto it = std::copy( mX.constBegin() + index, mX.constEnd() - 1, newX.begin() );
305 it = std::copy( mX.constBegin(), mX.constBegin() + index, it );
306 *it = *newX.constBegin();
307 mX = std::move( newX );
308
309 it = std::copy( mY.constBegin() + index, mY.constEnd() - 1, newY.begin() );
310 it = std::copy( mY.constBegin(), mY.constBegin() + index, it );
311 *it = *newY.constBegin();
312 mY = std::move( newY );
313 if ( useZ )
314 {
315 it = std::copy( mZ.constBegin() + index, mZ.constEnd() - 1, newZ.begin() );
316 it = std::copy( mZ.constBegin(), mZ.constBegin() + index, it );
317 *it = *newZ.constBegin();
318 mZ = std::move( newZ );
319 }
320 if ( useM )
321 {
322 it = std::copy( mM.constBegin() + index, mM.constEnd() - 1, newM.begin() );
323 it = std::copy( mM.constBegin(), mM.constBegin() + index, it );
324 *it = *newM.constBegin();
325 mM = std::move( newM );
326 }
327}
328
329QgsRectangle QgsCircularString::segmentBoundingBox( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3 )
330{
331 double centerX, centerY, radius;
332 QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
333
334 double p1Angle = QgsGeometryUtilsBase::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
335 double p2Angle = QgsGeometryUtilsBase::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
336 double p3Angle = QgsGeometryUtilsBase::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
337
338 //start point, end point and compass points in between can be on bounding box
339 QgsRectangle bbox( pt1.x(), pt1.y(), pt1.x(), pt1.y() );
340 bbox.combineExtentWith( pt3.x(), pt3.y() );
341
342 QgsPointSequence compassPoints = compassPointsOnSegment( p1Angle, p2Angle, p3Angle, centerX, centerY, radius );
343 QgsPointSequence::const_iterator cpIt = compassPoints.constBegin();
344 for ( ; cpIt != compassPoints.constEnd(); ++cpIt )
345 {
346 bbox.combineExtentWith( cpIt->x(), cpIt->y() );
347 }
348 return bbox;
349}
350
351QgsPointSequence QgsCircularString::compassPointsOnSegment( double p1Angle, double p2Angle, double p3Angle, double centerX, double centerY, double radius )
352{
353 QgsPointSequence pointList;
354
355 QgsPoint nPoint( centerX, centerY + radius );
356 QgsPoint ePoint( centerX + radius, centerY );
357 QgsPoint sPoint( centerX, centerY - radius );
358 QgsPoint wPoint( centerX - radius, centerY );
359
360 if ( p3Angle >= p1Angle )
361 {
362 if ( p2Angle > p1Angle && p2Angle < p3Angle )
363 {
364 if ( p1Angle <= 90 && p3Angle >= 90 )
365 {
366 pointList.append( nPoint );
367 }
368 if ( p1Angle <= 180 && p3Angle >= 180 )
369 {
370 pointList.append( wPoint );
371 }
372 if ( p1Angle <= 270 && p3Angle >= 270 )
373 {
374 pointList.append( sPoint );
375 }
376 }
377 else
378 {
379 pointList.append( ePoint );
380 if ( p1Angle >= 90 || p3Angle <= 90 )
381 {
382 pointList.append( nPoint );
383 }
384 if ( p1Angle >= 180 || p3Angle <= 180 )
385 {
386 pointList.append( wPoint );
387 }
388 if ( p1Angle >= 270 || p3Angle <= 270 )
389 {
390 pointList.append( sPoint );
391 }
392 }
393 }
394 else
395 {
396 if ( p2Angle < p1Angle && p2Angle > p3Angle )
397 {
398 if ( p1Angle >= 270 && p3Angle <= 270 )
399 {
400 pointList.append( sPoint );
401 }
402 if ( p1Angle >= 180 && p3Angle <= 180 )
403 {
404 pointList.append( wPoint );
405 }
406 if ( p1Angle >= 90 && p3Angle <= 90 )
407 {
408 pointList.append( nPoint );
409 }
410 }
411 else
412 {
413 pointList.append( ePoint );
414 if ( p1Angle <= 270 || p3Angle >= 270 )
415 {
416 pointList.append( sPoint );
417 }
418 if ( p1Angle <= 180 || p3Angle >= 180 )
419 {
420 pointList.append( wPoint );
421 }
422 if ( p1Angle <= 90 || p3Angle >= 90 )
423 {
424 pointList.append( nPoint );
425 }
426 }
427 }
428 return pointList;
429}
430
432{
433 if ( !wkbPtr )
434 return false;
435
436 Qgis::WkbType type = wkbPtr.readHeader();
438 {
439 return false;
440 }
441 clearCache();
442 mWkbType = type;
443
444 //type
445 const bool hasZ = is3D();
446 const bool hasM = isMeasure();
447 int nVertices = 0;
448 wkbPtr >> nVertices;
449 mX.resize( nVertices );
450 mY.resize( nVertices );
451 if ( hasZ )
452 mZ.resize( nVertices );
453 else
454 mZ.clear();
455 if ( hasM )
456 mM.resize( nVertices );
457 else
458 mM.clear();
459 for ( int i = 0; i < nVertices; ++i )
460 {
461 wkbPtr >> mX[i];
462 wkbPtr >> mY[i];
463 if ( hasZ )
464 {
465 wkbPtr >> mZ[i];
466 }
467 if ( hasM )
468 {
469 wkbPtr >> mM[i];
470 }
471 }
472
473 return true;
474}
475
476bool QgsCircularString::fromWkt( const QString &wkt )
477{
478 clear();
479
480 QPair<Qgis::WkbType, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
481
483 return false;
484 mWkbType = parts.first;
485
486 parts.second = parts.second.remove( '(' ).remove( ')' );
487 QString secondWithoutParentheses = parts.second;
488 secondWithoutParentheses = secondWithoutParentheses.simplified().remove( ' ' );
489 if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
490 secondWithoutParentheses.isEmpty() )
491 return true;
492
494 if ( points.isEmpty() )
495 return false;
496
497 setPoints( points );
498 return true;
499}
500
502{
503 int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
504 binarySize += numPoints() * ( 2 + is3D() + isMeasure() ) * sizeof( double );
505 return binarySize;
506}
507
508QByteArray QgsCircularString::asWkb( WkbFlags flags ) const
509{
510 QByteArray wkbArray;
511 wkbArray.resize( QgsCircularString::wkbSize( flags ) );
512 QgsWkbPtr wkb( wkbArray );
513 wkb << static_cast<char>( QgsApplication::endian() );
514 wkb << static_cast<quint32>( wkbType() );
516 points( pts );
517 QgsGeometryUtils::pointsToWKB( wkb, pts, is3D(), isMeasure(), flags );
518 return wkbArray;
519}
520
521QString QgsCircularString::asWkt( int precision ) const
522{
523 QString wkt = wktTypeStr() + ' ';
524
525 if ( isEmpty() )
526 wkt += QLatin1String( "EMPTY" );
527 else
528 {
530 points( pts );
531 wkt += QgsGeometryUtils::pointsToWKT( pts, precision, is3D(), isMeasure() );
532 }
533 return wkt;
534}
535
536QDomElement QgsCircularString::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
537{
538 // GML2 does not support curves
539 std::unique_ptr< QgsLineString > line( curveToLine() );
540 QDomElement gml = line->asGml2( doc, precision, ns, axisOrder );
541 return gml;
542}
543
544QDomElement QgsCircularString::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
545{
547 points( pts );
548
549 QDomElement elemCurve = doc.createElementNS( ns, QStringLiteral( "Curve" ) );
550
551 if ( isEmpty() )
552 return elemCurve;
553
554 QDomElement elemSegments = doc.createElementNS( ns, QStringLiteral( "segments" ) );
555 QDomElement elemArcString = doc.createElementNS( ns, QStringLiteral( "ArcString" ) );
556 elemArcString.appendChild( QgsGeometryUtils::pointsToGML3( pts, doc, precision, ns, is3D(), axisOrder ) );
557 elemSegments.appendChild( elemArcString );
558 elemCurve.appendChild( elemSegments );
559 return elemCurve;
560}
561
562
563json QgsCircularString::asJsonObject( int precision ) const
564{
565 // GeoJSON does not support curves
566 std::unique_ptr< QgsLineString > line( curveToLine() );
567 return line->asJsonObject( precision );
568}
569
571{
572 return mX.isEmpty();
573}
574
576{
577 if ( !isEmpty() && ( numPoints() < 3 ) )
578 {
579 error = QObject::tr( "CircularString has less than 3 points and is not empty." );
580 return false;
581 }
582 return QgsCurve::isValid( error, flags );
583}
584
585//curve interface
587{
588 int nPoints = numPoints();
589 double length = 0;
590 for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
591 {
592 length += QgsGeometryUtilsBase::circleLength( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2] );
593 }
594 return length;
595}
596
598{
599 if ( numPoints() < 1 )
600 {
601 return QgsPoint();
602 }
603 return pointN( 0 );
604}
605
607{
608 if ( numPoints() < 1 )
609 {
610 return QgsPoint();
611 }
612 return pointN( numPoints() - 1 );
613}
614
616{
617 QgsLineString *line = new QgsLineString();
619 int nPoints = numPoints();
620
621 for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
622 {
623 QgsGeometryUtils::segmentizeArc( pointN( i ), pointN( i + 1 ), pointN( i + 2 ), points, tolerance, toleranceType, is3D(), isMeasure() );
624 }
625
626 line->setPoints( points );
627 return line;
628}
629
630QgsCircularString *QgsCircularString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool ) const
631{
632 // prepare result
633 std::unique_ptr<QgsCircularString> result { createEmptyWithSameType() };
634
635 // remove redundant not supported for circular strings
636 bool res = snapToGridPrivate( hSpacing, vSpacing, dSpacing, mSpacing, mX, mY, mZ, mM,
637 result->mX, result->mY, result->mZ, result->mM, false );
638 if ( res )
639 return result.release();
640 else
641 return nullptr;
642}
643
645{
646 std::unique_ptr< QgsLineString > line( curveToLine() );
647 return line->simplifyByDistance( tolerance );
648}
649
650bool QgsCircularString::removeDuplicateNodes( double epsilon, bool useZValues )
651{
652 if ( mX.count() <= 3 )
653 return false; // don't create degenerate lines
654 bool result = false;
655 double prevX = mX.at( 0 );
656 double prevY = mY.at( 0 );
657 bool hasZ = is3D();
658 bool useZ = hasZ && useZValues;
659 double prevZ = useZ ? mZ.at( 0 ) : 0;
660 int i = 1;
661 int remaining = mX.count();
662 // we have to consider points in pairs, since a segment can validly have the same start and
663 // end if it has a different curve point
664 while ( i + 1 < remaining )
665 {
666 double currentCurveX = mX.at( i );
667 double currentCurveY = mY.at( i );
668 double currentX = mX.at( i + 1 );
669 double currentY = mY.at( i + 1 );
670 double currentZ = useZ ? mZ.at( i + 1 ) : 0;
671 if ( qgsDoubleNear( currentCurveX, prevX, epsilon ) &&
672 qgsDoubleNear( currentCurveY, prevY, epsilon ) &&
673 qgsDoubleNear( currentX, prevX, epsilon ) &&
674 qgsDoubleNear( currentY, prevY, epsilon ) &&
675 ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
676 {
677 result = true;
678 // remove point
679 mX.removeAt( i );
680 mX.removeAt( i );
681 mY.removeAt( i );
682 mY.removeAt( i );
683 if ( hasZ )
684 {
685 mZ.removeAt( i );
686 mZ.removeAt( i );
687 }
688 remaining -= 2;
689 }
690 else
691 {
692 prevX = currentX;
693 prevY = currentY;
694 prevZ = currentZ;
695 i += 2;
696 }
697 }
698 return result;
699}
700
702{
703 return std::min( mX.size(), mY.size() );
704}
705
706int QgsCircularString::indexOf( const QgsPoint &point ) const
707{
708 const int size = mX.size();
709 if ( size == 0 )
710 return -1;
711
712 const double *x = mX.constData();
713 const double *y = mY.constData();
714 const bool useZ = is3D();
715 const bool useM = isMeasure();
716 const double *z = useZ ? mZ.constData() : nullptr;
717 const double *m = useM ? mM.constData() : nullptr;
718
719 for ( int i = 0; i < size; i += 2 )
720 {
721 if ( qgsDoubleNear( *x, point.x() )
722 && qgsDoubleNear( *y, point.y() )
723 && ( !useZ || qgsDoubleNear( *z, point.z() ) )
724 && ( !useM || qgsDoubleNear( *m, point.m() ) ) )
725 return i;
726
727 // we skip over curve points!
728 x++;
729 x++;
730 y++;
731 y++;
732 if ( useZ )
733 {
734 z++;
735 z++;
736 }
737 if ( useM )
738 {
739 m++;
740 m++;
741 }
742 }
743 return -1;
744}
745
747{
748 if ( i < 0 || std::min( mX.size(), mY.size() ) <= i )
749 {
750 return QgsPoint();
751 }
752
753 double x = mX.at( i );
754 double y = mY.at( i );
755 double z = 0;
756 double m = 0;
757
758 if ( is3D() )
759 {
760 z = mZ.at( i );
761 }
762 if ( isMeasure() )
763 {
764 m = mM.at( i );
765 }
766
768 if ( is3D() && isMeasure() )
769 {
771 }
772 else if ( is3D() )
773 {
775 }
776 else if ( isMeasure() )
777 {
779 }
780 return QgsPoint( t, x, y, z, m );
781}
782
783double QgsCircularString::xAt( int index ) const
784{
785 if ( index >= 0 && index < mX.size() )
786 return mX.at( index );
787 else
788 return 0.0;
789}
790
791double QgsCircularString::yAt( int index ) const
792{
793 if ( index >= 0 && index < mY.size() )
794 return mY.at( index );
795 else
796 return 0.0;
797}
798
799double QgsCircularString::zAt( int index ) const
800{
801 if ( index >= 0 && index < mZ.size() )
802 return mZ.at( index );
803 else
804 return 0.0;
805}
806
807double QgsCircularString::mAt( int index ) const
808{
809 if ( index >= 0 && index < mM.size() )
810 return mM.at( index );
811 else
812 return 0.0;
813}
814
816{
817 if ( !transformer )
818 return false;
819
820 bool hasZ = is3D();
821 bool hasM = isMeasure();
822 int size = mX.size();
823
824 double *srcX = mX.data();
825 double *srcY = mY.data();
826 double *srcM = hasM ? mM.data() : nullptr;
827 double *srcZ = hasZ ? mZ.data() : nullptr;
828
829 bool res = true;
830 for ( int i = 0; i < size; ++i )
831 {
832 double x = *srcX;
833 double y = *srcY;
834 double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
835 double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
836 if ( !transformer->transformPoint( x, y, z, m ) )
837 {
838 res = false;
839 break;
840 }
841
842 *srcX++ = x;
843 *srcY++ = y;
844 if ( hasM )
845 *srcM++ = m;
846 if ( hasZ )
847 *srcZ++ = z;
848
849 if ( feedback && feedback->isCanceled() )
850 {
851 res = false;
852 break;
853 }
854 }
855 clearCache();
856 return res;
857}
858
859void QgsCircularString::filterVertices( const std::function<bool ( const QgsPoint & )> &filter )
860{
861 bool hasZ = is3D();
862 bool hasM = isMeasure();
863 int size = mX.size();
864
865 double *srcX = mX.data(); // clazy:exclude=detaching-member
866 double *srcY = mY.data(); // clazy:exclude=detaching-member
867 double *srcM = hasM ? mM.data() : nullptr; // clazy:exclude=detaching-member
868 double *srcZ = hasZ ? mZ.data() : nullptr; // clazy:exclude=detaching-member
869
870 double *destX = srcX;
871 double *destY = srcY;
872 double *destM = srcM;
873 double *destZ = srcZ;
874
875 int filteredPoints = 0;
876 for ( int i = 0; i < size; ++i )
877 {
878 double x = *srcX++;
879 double y = *srcY++;
880 double z = hasZ ? *srcZ++ : std::numeric_limits<double>::quiet_NaN();
881 double m = hasM ? *srcM++ : std::numeric_limits<double>::quiet_NaN();
882
883 if ( filter( QgsPoint( x, y, z, m ) ) )
884 {
885 filteredPoints++;
886 *destX++ = x;
887 *destY++ = y;
888 if ( hasM )
889 *destM++ = m;
890 if ( hasZ )
891 *destZ++ = z;
892 }
893 }
894
895 mX.resize( filteredPoints );
896 mY.resize( filteredPoints );
897 if ( hasZ )
898 mZ.resize( filteredPoints );
899 if ( hasM )
900 mM.resize( filteredPoints );
901
902 clearCache();
903}
904
905void QgsCircularString::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
906{
907 bool hasZ = is3D();
908 bool hasM = isMeasure();
909 int size = mX.size();
910
911 double *srcX = mX.data();
912 double *srcY = mY.data();
913 double *srcM = hasM ? mM.data() : nullptr;
914 double *srcZ = hasZ ? mZ.data() : nullptr;
915
916 for ( int i = 0; i < size; ++i )
917 {
918 double x = *srcX;
919 double y = *srcY;
920 double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
921 double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
922 QgsPoint res = transform( QgsPoint( x, y, z, m ) );
923 *srcX++ = res.x();
924 *srcY++ = res.y();
925 if ( hasM )
926 *srcM++ = res.m();
927 if ( hasZ )
928 *srcZ++ = res.z();
929 }
930 clearCache();
931}
932
933std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsCircularString::splitCurveAtVertex( int index ) const
934{
935 const bool useZ = is3D();
936 const bool useM = isMeasure();
937
938 const int size = mX.size();
939 if ( size == 0 )
940 return std::make_tuple( std::make_unique< QgsCircularString >(), std::make_unique< QgsCircularString >() );
941
942 index = std::clamp( index, 0, size - 1 );
943
944 const int part1Size = index + 1;
945 QVector< double > x1( part1Size );
946 QVector< double > y1( part1Size );
947 QVector< double > z1( useZ ? part1Size : 0 );
948 QVector< double > m1( useM ? part1Size : 0 );
949
950 const double *sourceX = mX.constData();
951 const double *sourceY = mY.constData();
952 const double *sourceZ = useZ ? mZ.constData() : nullptr;
953 const double *sourceM = useM ? mM.constData() : nullptr;
954
955 double *destX = x1.data();
956 double *destY = y1.data();
957 double *destZ = useZ ? z1.data() : nullptr;
958 double *destM = useM ? m1.data() : nullptr;
959
960 std::copy( sourceX, sourceX + part1Size, destX );
961 std::copy( sourceY, sourceY + part1Size, destY );
962 if ( useZ )
963 std::copy( sourceZ, sourceZ + part1Size, destZ );
964 if ( useM )
965 std::copy( sourceM, sourceM + part1Size, destM );
966
967 const int part2Size = size - index;
968 if ( part2Size < 2 )
969 return std::make_tuple( std::make_unique< QgsCircularString >( x1, y1, z1, m1 ), std::make_unique< QgsCircularString >() );
970
971 QVector< double > x2( part2Size );
972 QVector< double > y2( part2Size );
973 QVector< double > z2( useZ ? part2Size : 0 );
974 QVector< double > m2( useM ? part2Size : 0 );
975 destX = x2.data();
976 destY = y2.data();
977 destZ = useZ ? z2.data() : nullptr;
978 destM = useM ? m2.data() : nullptr;
979 std::copy( sourceX + index, sourceX + size, destX );
980 std::copy( sourceY + index, sourceY + size, destY );
981 if ( useZ )
982 std::copy( sourceZ + index, sourceZ + size, destZ );
983 if ( useM )
984 std::copy( sourceM + index, sourceM + size, destM );
985
986 if ( part1Size < 2 )
987 return std::make_tuple( std::make_unique< QgsCircularString >(), std::make_unique< QgsCircularString >( x2, y2, z2, m2 ) );
988 else
989 return std::make_tuple( std::make_unique< QgsCircularString >( x1, y1, z1, m1 ), std::make_unique< QgsCircularString >( x2, y2, z2, m2 ) );
990}
991
993{
994 pts.clear();
995 int nPts = numPoints();
996 for ( int i = 0; i < nPts; ++i )
997 {
998 pts.push_back( pointN( i ) );
999 }
1000}
1001
1003{
1004 clearCache();
1005
1006 if ( points.empty() )
1007 {
1009 mX.clear();
1010 mY.clear();
1011 mZ.clear();
1012 mM.clear();
1013 return;
1014 }
1015
1016 //get wkb type from first point
1017 const QgsPoint &firstPt = points.at( 0 );
1018 bool hasZ = firstPt.is3D();
1019 bool hasM = firstPt.isMeasure();
1020
1022
1023 mX.resize( points.size() );
1024 mY.resize( points.size() );
1025 if ( hasZ )
1026 {
1027 mZ.resize( points.size() );
1028 }
1029 else
1030 {
1031 mZ.clear();
1032 }
1033 if ( hasM )
1034 {
1035 mM.resize( points.size() );
1036 }
1037 else
1038 {
1039 mM.clear();
1040 }
1041
1042 for ( int i = 0; i < points.size(); ++i )
1043 {
1044 mX[i] = points[i].x();
1045 mY[i] = points[i].y();
1046 if ( hasZ )
1047 {
1048 double z = points.at( i ).z();
1049 mZ[i] = std::isnan( z ) ? 0 : z;
1050 }
1051 if ( hasM )
1052 {
1053 double m = points.at( i ).m();
1054 mM[i] = std::isnan( m ) ? 0 : m;
1055 }
1056 }
1057}
1058
1060{
1061 if ( !line || line->isEmpty() )
1062 {
1063 return;
1064 }
1065
1066 if ( numPoints() < 1 )
1067 {
1069 }
1070
1071 // do not store duplicate points
1072 if ( numPoints() > 0 &&
1073 line->numPoints() > 0 &&
1074 qgsDoubleNear( endPoint().x(), line->startPoint().x() ) &&
1075 qgsDoubleNear( endPoint().y(), line->startPoint().y() ) &&
1076 ( !is3D() || !line->is3D() || qgsDoubleNear( endPoint().z(), line->startPoint().z() ) ) &&
1077 ( !isMeasure() || !line->isMeasure() || qgsDoubleNear( endPoint().m(), line->startPoint().m() ) ) )
1078 {
1079 mX.pop_back();
1080 mY.pop_back();
1081
1082 if ( is3D() && line->is3D() )
1083 {
1084 mZ.pop_back();
1085 }
1086 if ( isMeasure() && line->isMeasure() )
1087 {
1088 mM.pop_back();
1089 }
1090 }
1091
1092 mX += line->mX;
1093 mY += line->mY;
1094
1095 if ( is3D() )
1096 {
1097 if ( line->is3D() )
1098 {
1099 mZ += line->mZ;
1100 }
1101 else
1102 {
1103 // if append line does not have z coordinates, fill with NaN to match number of points in final line
1104 mZ.insert( mZ.count(), mX.size() - mZ.size(), std::numeric_limits<double>::quiet_NaN() );
1105 }
1106 }
1107
1108 if ( isMeasure() )
1109 {
1110 if ( line->isMeasure() )
1111 {
1112 mM += line->mM;
1113 }
1114 else
1115 {
1116 // if append line does not have m values, fill with NaN to match number of points in final line
1117 mM.insert( mM.count(), mX.size() - mM.size(), std::numeric_limits<double>::quiet_NaN() );
1118 }
1119 }
1120
1121 clearCache(); //set bounding box invalid
1122}
1123
1124void QgsCircularString::draw( QPainter &p ) const
1125{
1126 QPainterPath path;
1127 addToPainterPath( path );
1128 p.drawPath( path );
1129}
1130
1132{
1133 clearCache();
1134
1135 double *zArray = nullptr;
1136 bool hasZ = is3D();
1137 int nPoints = numPoints();
1138
1139 // it's possible that transformCoords will throw an exception - so we need to use
1140 // a smart pointer for the dummy z values in order to ensure that they always get cleaned up
1141 std::unique_ptr< double[] > dummyZ;
1142 if ( !hasZ || !transformZ )
1143 {
1144 dummyZ = std::make_unique<double[]>( nPoints );
1145 zArray = dummyZ.get();
1146 }
1147 else
1148 {
1149 zArray = mZ.data();
1150 }
1151 ct.transformCoords( nPoints, mX.data(), mY.data(), zArray, d );
1152}
1153
1154void QgsCircularString::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
1155{
1156 clearCache();
1157
1158 int nPoints = numPoints();
1159 bool hasZ = is3D();
1160 bool hasM = isMeasure();
1161 for ( int i = 0; i < nPoints; ++i )
1162 {
1163 qreal x, y;
1164 t.map( mX.at( i ), mY.at( i ), &x, &y );
1165 mX[i] = x;
1166 mY[i] = y;
1167 if ( hasZ )
1168 {
1169 mZ[i] = mZ.at( i ) * zScale + zTranslate;
1170 }
1171 if ( hasM )
1172 {
1173 mM[i] = mM.at( i ) * mScale + mTranslate;
1174 }
1175 }
1176}
1177
1178void arcTo( QPainterPath &path, QPointF pt1, QPointF pt2, QPointF pt3 )
1179{
1180 double centerX, centerY, radius;
1181 QgsGeometryUtils::circleCenterRadius( QgsPoint( pt1.x(), pt1.y() ), QgsPoint( pt2.x(), pt2.y() ), QgsPoint( pt3.x(), pt3.y() ),
1182 radius, centerX, centerY );
1183
1184 double p1Angle = QgsGeometryUtilsBase::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
1185 double sweepAngle = QgsGeometryUtilsBase::sweepAngle( centerX, centerY, pt1.x(), pt1.y(), pt2.x(), pt2.y(), pt3.x(), pt3.y() );
1186
1187 double diameter = 2 * radius;
1188 path.arcTo( centerX - radius, centerY - radius, diameter, diameter, -p1Angle, -sweepAngle );
1189}
1190
1191void QgsCircularString::addToPainterPath( QPainterPath &path ) const
1192{
1193 int nPoints = numPoints();
1194 if ( nPoints < 1 )
1195 {
1196 return;
1197 }
1198
1199 if ( path.isEmpty() || path.currentPosition() != QPointF( mX[0], mY[0] ) )
1200 {
1201 path.moveTo( QPointF( mX[0], mY[0] ) );
1202 }
1203
1204 for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
1205 {
1206 arcTo( path, QPointF( mX[i], mY[i] ), QPointF( mX[i + 1], mY[i + 1] ), QPointF( mX[i + 2], mY[i + 2] ) );
1207 }
1208
1209 //if number of points is even, connect to last point with straight line (even though the circular string is not valid)
1210 if ( nPoints % 2 == 0 )
1211 {
1212 path.lineTo( mX[ nPoints - 1 ], mY[ nPoints - 1 ] );
1213 }
1214}
1215
1216void QgsCircularString::drawAsPolygon( QPainter &p ) const
1217{
1218 draw( p );
1219}
1220
1222{
1223 if ( position.vertex >= mX.size() || position.vertex < 1 )
1224 {
1225 return false;
1226 }
1227
1228 mX.insert( position.vertex, vertex.x() );
1229 mY.insert( position.vertex, vertex.y() );
1230 if ( is3D() )
1231 {
1232 mZ.insert( position.vertex, vertex.z() );
1233 }
1234 if ( isMeasure() )
1235 {
1236 mM.insert( position.vertex, vertex.m() );
1237 }
1238
1239 bool vertexNrEven = ( position.vertex % 2 == 0 );
1240 if ( vertexNrEven )
1241 {
1242 insertVertexBetween( position.vertex - 2, position.vertex - 1, position.vertex );
1243 }
1244 else
1245 {
1246 insertVertexBetween( position.vertex, position.vertex + 1, position.vertex - 1 );
1247 }
1248 clearCache(); //set bounding box invalid
1249 return true;
1250}
1251
1253{
1254 if ( position.vertex < 0 || position.vertex >= mX.size() )
1255 {
1256 return false;
1257 }
1258
1259 mX[position.vertex] = newPos.x();
1260 mY[position.vertex] = newPos.y();
1261 if ( is3D() && newPos.is3D() )
1262 {
1263 mZ[position.vertex] = newPos.z();
1264 }
1265 if ( isMeasure() && newPos.isMeasure() )
1266 {
1267 mM[position.vertex] = newPos.m();
1268 }
1269 clearCache(); //set bounding box invalid
1270 return true;
1271}
1272
1274{
1275 int nVertices = this->numPoints();
1276 if ( nVertices < 4 ) //circular string must have at least 3 vertices
1277 {
1278 clear();
1279 return true;
1280 }
1281 if ( position.vertex < 0 || position.vertex > ( nVertices - 1 ) )
1282 {
1283 return false;
1284 }
1285
1286 if ( position.vertex < ( nVertices - 2 ) )
1287 {
1288 //remove this and the following vertex
1289 deleteVertex( position.vertex + 1 );
1290 deleteVertex( position.vertex );
1291 }
1292 else //remove this and the preceding vertex
1293 {
1294 deleteVertex( position.vertex );
1295 deleteVertex( position.vertex - 1 );
1296 }
1297
1298 clearCache(); //set bounding box invalid
1299 return true;
1300}
1301
1303{
1304 mX.remove( i );
1305 mY.remove( i );
1306 if ( is3D() )
1307 {
1308 mZ.remove( i );
1309 }
1310 if ( isMeasure() )
1311 {
1312 mM.remove( i );
1313 }
1314 clearCache();
1315}
1316
1317double QgsCircularString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1318{
1319 double minDist = std::numeric_limits<double>::max();
1320 QgsPoint minDistSegmentPoint;
1321 QgsVertexId minDistVertexAfter;
1322 int minDistLeftOf = 0;
1323
1324 double currentDist = 0.0;
1325
1326 int nPoints = numPoints();
1327 for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
1328 {
1329 currentDist = closestPointOnArc( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2], pt, segmentPt, vertexAfter, leftOf, epsilon );
1330 if ( currentDist < minDist )
1331 {
1332 minDist = currentDist;
1333 minDistSegmentPoint = segmentPt;
1334 minDistVertexAfter.vertex = vertexAfter.vertex + i;
1335 if ( leftOf )
1336 {
1337 minDistLeftOf = *leftOf;
1338 }
1339 }
1340 }
1341
1342 if ( minDist == std::numeric_limits<double>::max() )
1343 return -1; // error: no segments
1344
1345 segmentPt = minDistSegmentPoint;
1346 vertexAfter = minDistVertexAfter;
1347 vertexAfter.part = 0;
1348 vertexAfter.ring = 0;
1349 if ( leftOf )
1350 {
1351 *leftOf = qgsDoubleNear( minDist, 0.0 ) ? 0 : minDistLeftOf;
1352 }
1353 return minDist;
1354}
1355
1356bool QgsCircularString::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
1357{
1358 if ( node < 0 || node >= numPoints() )
1359 {
1360 return false;
1361 }
1362 point = pointN( node );
1363 type = ( node % 2 == 0 ) ? Qgis::VertexType::Segment : Qgis::VertexType::Curve;
1364 return true;
1365}
1366
1367void QgsCircularString::sumUpArea( double &sum ) const
1368{
1370 {
1371 sum += mSummedUpArea;
1372 return;
1373 }
1374
1375 int maxIndex = numPoints() - 2;
1376 mSummedUpArea = 0;
1377 for ( int i = 0; i < maxIndex; i += 2 )
1378 {
1379 QgsPoint p1( mX[i], mY[i] );
1380 QgsPoint p2( mX[i + 1], mY[i + 1] );
1381 QgsPoint p3( mX[i + 2], mY[i + 2] );
1382
1383 //segment is a full circle, p2 is the center point
1384 if ( p1 == p3 )
1385 {
1386 double r2 = QgsGeometryUtils::sqrDistance2D( p1, p2 ) / 4.0;
1387 mSummedUpArea += M_PI * r2;
1388 continue;
1389 }
1390
1391 mSummedUpArea += 0.5 * ( mX[i] * mY[i + 2] - mY[i] * mX[i + 2] );
1392
1393 //calculate area between circle and chord, then sum / subtract from total area
1394 double midPointX = ( p1.x() + p3.x() ) / 2.0;
1395 double midPointY = ( p1.y() + p3.y() ) / 2.0;
1396
1397 double radius, centerX, centerY;
1398 QgsGeometryUtils::circleCenterRadius( p1, p2, p3, radius, centerX, centerY );
1399
1400 double d = std::sqrt( QgsGeometryUtils::sqrDistance2D( QgsPoint( centerX, centerY ), QgsPoint( midPointX, midPointY ) ) );
1401 double r2 = radius * radius;
1402
1403 if ( d > radius )
1404 {
1405 //d cannot be greater than radius, something must be wrong...
1406 continue;
1407 }
1408
1409 bool circlePointLeftOfLine = QgsGeometryUtilsBase::leftOfLine( p2.x(), p2.y(), p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
1410 bool centerPointLeftOfLine = QgsGeometryUtilsBase::leftOfLine( centerX, centerY, p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
1411
1412 double cov = 0.5 - d * std::sqrt( r2 - d * d ) / ( M_PI * r2 ) - M_1_PI * std::asin( d / radius );
1413 double circleChordArea = 0;
1414 if ( circlePointLeftOfLine == centerPointLeftOfLine )
1415 {
1416 circleChordArea = M_PI * r2 * ( 1 - cov );
1417 }
1418 else
1419 {
1420 circleChordArea = M_PI * r2 * cov;
1421 }
1422
1423 if ( !circlePointLeftOfLine )
1424 {
1425 mSummedUpArea += circleChordArea;
1426 }
1427 else
1428 {
1429 mSummedUpArea -= circleChordArea;
1430 }
1431 }
1432
1434 sum += mSummedUpArea;
1435}
1436
1438{
1439 return true;
1440}
1441
1442double QgsCircularString::closestPointOnArc( double x1, double y1, double x2, double y2, double x3, double y3,
1443 const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon )
1444{
1445 double radius, centerX, centerY;
1446 QgsPoint pt1( x1, y1 );
1447 QgsPoint pt2( x2, y2 );
1448 QgsPoint pt3( x3, y3 );
1449
1450 QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
1451 double angle = QgsGeometryUtilsBase::ccwAngle( pt.y() - centerY, pt.x() - centerX );
1452 double angle1 = QgsGeometryUtilsBase::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
1453 double angle2 = QgsGeometryUtilsBase::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
1454 double angle3 = QgsGeometryUtilsBase::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
1455
1456 bool clockwise = QgsGeometryUtilsBase::circleClockwise( angle1, angle2, angle3 );
1457
1458 if ( QgsGeometryUtilsBase::angleOnCircle( angle, angle1, angle2, angle3 ) )
1459 {
1460 //get point on line center -> pt with distance radius
1461 segmentPt = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), pt, radius );
1462
1463 //vertexAfter
1464 vertexAfter.vertex = QgsGeometryUtilsBase::circleAngleBetween( angle, angle1, angle2, clockwise ) ? 1 : 2;
1465 }
1466 else
1467 {
1468 double distPtPt1 = QgsGeometryUtils::sqrDistance2D( pt, pt1 );
1469 double distPtPt3 = QgsGeometryUtils::sqrDistance2D( pt, pt3 );
1470 segmentPt = ( distPtPt1 <= distPtPt3 ) ? pt1 : pt3;
1471 vertexAfter.vertex = ( distPtPt1 <= distPtPt3 ) ? 1 : 2;
1472 }
1473
1474 double sqrDistance = QgsGeometryUtils::sqrDistance2D( segmentPt, pt );
1475 //prevent rounding errors if the point is directly on the segment
1476 if ( qgsDoubleNear( sqrDistance, 0.0, epsilon ) )
1477 {
1478 segmentPt.setX( pt.x() );
1479 segmentPt.setY( pt.y() );
1480 sqrDistance = 0.0;
1481 }
1482
1483 if ( leftOf )
1484 {
1485 double sqrDistancePointToCenter = pt.distanceSquared( centerX, centerY );
1486 *leftOf = clockwise ? ( sqrDistancePointToCenter > radius * radius ? -1 : 1 )
1487 : ( sqrDistancePointToCenter < radius * radius ? -1 : 1 );
1488 }
1489
1490 return sqrDistance;
1491}
1492
1493void QgsCircularString::insertVertexBetween( int after, int before, int pointOnCircle )
1494{
1495 double xAfter = mX.at( after );
1496 double yAfter = mY.at( after );
1497 double xBefore = mX.at( before );
1498 double yBefore = mY.at( before );
1499 double xOnCircle = mX.at( pointOnCircle );
1500 double yOnCircle = mY.at( pointOnCircle );
1501
1502 double radius, centerX, centerY;
1503 QgsGeometryUtils::circleCenterRadius( QgsPoint( xAfter, yAfter ), QgsPoint( xBefore, yBefore ), QgsPoint( xOnCircle, yOnCircle ), radius, centerX, centerY );
1504
1505 double x = ( xAfter + xBefore ) / 2.0;
1506 double y = ( yAfter + yBefore ) / 2.0;
1507
1508 QgsPoint newVertex = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), QgsPoint( x, y ), radius );
1509 mX.insert( before, newVertex.x() );
1510 mY.insert( before, newVertex.y() );
1511
1512 if ( is3D() )
1513 {
1514 mZ.insert( before, ( mZ[after] + mZ[before] ) / 2.0 );
1515 }
1516 if ( isMeasure() )
1517 {
1518 mM.insert( before, ( mM[after] + mM[before] ) / 2.0 );
1519 }
1520 clearCache();
1521}
1522
1524{
1525 if ( numPoints() < 3 )
1526 {
1527 //undefined
1528 return 0.0;
1529 }
1530
1531 int before = vId.vertex - 1;
1532 int vertex = vId.vertex;
1533 int after = vId.vertex + 1;
1534
1535 if ( vId.vertex % 2 != 0 ) // a curve vertex
1536 {
1537 if ( vId.vertex >= 1 && vId.vertex < numPoints() - 1 )
1538 {
1539 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[before], mY[before] ),
1540 QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[after], mY[after] ) );
1541 }
1542 }
1543 else //a point vertex
1544 {
1545 if ( vId.vertex == 0 )
1546 {
1547 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[0], mY[0] ), QgsPoint( mX[0], mY[0] ),
1548 QgsPoint( mX[1], mY[1] ), QgsPoint( mX[2], mY[2] ) );
1549 }
1550 if ( vId.vertex >= numPoints() - 1 )
1551 {
1552 int a = numPoints() - 3;
1553 int b = numPoints() - 2;
1554 int c = numPoints() - 1;
1555 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[c], mY[c] ), QgsPoint( mX[a], mY[a] ),
1556 QgsPoint( mX[b], mY[b] ), QgsPoint( mX[c], mY[c] ) );
1557 }
1558 else
1559 {
1560 if ( vId.vertex + 2 > numPoints() - 1 )
1561 {
1562 return 0.0;
1563 }
1564
1565 int vertex1 = vId.vertex - 2;
1566 int vertex2 = vId.vertex - 1;
1567 int vertex3 = vId.vertex;
1568 double angle1 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ),
1569 QgsPoint( mX[vertex1], mY[vertex1] ), QgsPoint( mX[vertex2], mY[vertex2] ), QgsPoint( mX[vertex3], mY[vertex3] ) );
1570 int vertex4 = vId.vertex + 1;
1571 int vertex5 = vId.vertex + 2;
1572 double angle2 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ),
1573 QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex4], mY[vertex4] ), QgsPoint( mX[vertex5], mY[vertex5] ) );
1574 return QgsGeometryUtilsBase::averageAngle( angle1, angle2 );
1575 }
1576 }
1577 return 0.0;
1578}
1579
1581{
1582 if ( startVertex.vertex % 2 == 1 )
1583 return 0.0; // curve point?
1584
1585 if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 2 )
1586 return 0.0;
1587
1588 double x1 = mX.at( startVertex.vertex );
1589 double y1 = mY.at( startVertex.vertex );
1590 double x2 = mX.at( startVertex.vertex + 1 );
1591 double y2 = mY.at( startVertex.vertex + 1 );
1592 double x3 = mX.at( startVertex.vertex + 2 );
1593 double y3 = mY.at( startVertex.vertex + 2 );
1594 return QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1595}
1596
1598{
1599 // Ensure fromVertex < toVertex for simplicity
1600 if ( fromVertex.vertex > toVertex.vertex )
1601 {
1602 return distanceBetweenVertices( toVertex, fromVertex );
1603 }
1604
1605 // Convert QgsVertexId to simple vertex numbers for curves (single ring, single part)
1606 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
1607 return -1.0;
1608
1609 const int fromVertexNumber = fromVertex.vertex;
1610 const int toVertexNumber = toVertex.vertex;
1611
1612 const int nPoints = numPoints();
1613 if ( fromVertexNumber < 0 || fromVertexNumber >= nPoints || toVertexNumber < 0 || toVertexNumber >= nPoints )
1614 return -1.0;
1615
1616 if ( fromVertexNumber == toVertexNumber )
1617 return 0.0;
1618
1619 const double *xData = mX.constData();
1620 const double *yData = mY.constData();
1621 double totalDistance = 0.0;
1622
1623 // Start iteration from the arc containing fromVertex
1624 // Each arc starts at an even index (0, 2, 4, ...) and spans 3 vertices
1625 const int startArc = ( fromVertexNumber / 2 ) * 2;
1626
1627 // Iterate through the arcs, accumulating distance between fromVertex and toVertex
1628 for ( int i = startArc; i < nPoints - 2; i += 2 )
1629 {
1630 // Arc segment from i to i+2, with curve point at i+1
1631 double x1 = xData[i]; // Start point
1632 double y1 = yData[i];
1633 double x2 = xData[i + 1]; // Curve point
1634 double y2 = yData[i + 1];
1635 double x3 = xData[i + 2]; // End point
1636 double y3 = yData[i + 2];
1637
1638 // Check if both vertices are in this arc segment
1639 if ( fromVertexNumber >= i && toVertexNumber <= i + 2 )
1640 {
1641 if ( fromVertexNumber == i && toVertexNumber == i + 2 )
1642 {
1643 // Full arc from start to end
1644 return QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1645 }
1646 else if ( fromVertexNumber == i && toVertexNumber == i + 1 )
1647 {
1648 // Arc from start point to curve point
1649 double centerX, centerY, radius;
1650 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1651 // Calculate arc length from vertex 0 to vertex 1
1652 return QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 0, 1 );
1653 }
1654 else if ( fromVertexNumber == i + 1 && toVertexNumber == i + 2 )
1655 {
1656 // Arc from curve point to end point
1657 double centerX, centerY, radius;
1658 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1659 // Calculate arc length from vertex 1 to vertex 2
1660 return QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 1, 2 );
1661 }
1662 else if ( fromVertexNumber == i + 1 && toVertexNumber == i + 1 )
1663 {
1664 return 0.0; // Same point
1665 }
1666 }
1667
1668 // Handle cases where vertices span multiple segments
1669 bool startInThisSegment = ( fromVertexNumber >= i && fromVertexNumber <= i + 2 );
1670 bool endInThisSegment = ( toVertexNumber >= i && toVertexNumber <= i + 2 );
1671 bool segmentInRange = ( fromVertexNumber < i && toVertexNumber > i + 2 );
1672
1673 if ( startInThisSegment && !endInThisSegment )
1674 {
1675 // fromVertex is in this segment, toVertex is beyond
1676 if ( fromVertexNumber == i )
1677 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1678 else if ( fromVertexNumber == i + 1 )
1679 {
1680 // From curve point to end of segment
1681 double centerX, centerY, radius;
1682 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1683 totalDistance += QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 1, 2 );
1684 }
1685 }
1686 else if ( !startInThisSegment && endInThisSegment )
1687 {
1688 // fromVertex is before this segment, toVertex is in this segment
1689 if ( toVertexNumber == i + 1 )
1690 {
1691 // From start of segment to curve point
1692 double centerX, centerY, radius;
1693 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1694 totalDistance += QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 0, 1 );
1695 }
1696 else if ( toVertexNumber == i + 2 )
1697 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1698 break;
1699 }
1700 else if ( segmentInRange )
1701 {
1702 // This entire segment is between fromVertex and toVertex
1703 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1704 }
1705 }
1706
1707 return totalDistance;
1708}
1709
1710
1712{
1713 QgsCircularString *copy = clone();
1714 std::reverse( copy->mX.begin(), copy->mX.end() );
1715 std::reverse( copy->mY.begin(), copy->mY.end() );
1716 if ( is3D() )
1717 {
1718 std::reverse( copy->mZ.begin(), copy->mZ.end() );
1719 }
1720 if ( isMeasure() )
1721 {
1722 std::reverse( copy->mM.begin(), copy->mM.end() );
1723 }
1724
1726 return copy;
1727}
1728
1729QgsPoint *QgsCircularString::interpolatePoint( const double distance ) const
1730{
1731 if ( distance < 0 )
1732 return nullptr;
1733
1734 double distanceTraversed = 0;
1735 const int totalPoints = numPoints();
1736 if ( totalPoints == 0 )
1737 return nullptr;
1738
1740 if ( is3D() )
1741 pointType = Qgis::WkbType::PointZ;
1742 if ( isMeasure() )
1743 pointType = QgsWkbTypes::addM( pointType );
1744
1745 const double *x = mX.constData();
1746 const double *y = mY.constData();
1747 const double *z = is3D() ? mZ.constData() : nullptr;
1748 const double *m = isMeasure() ? mM.constData() : nullptr;
1749
1750 double prevX = *x++;
1751 double prevY = *y++;
1752 double prevZ = z ? *z++ : 0.0;
1753 double prevM = m ? *m++ : 0.0;
1754
1755 if ( qgsDoubleNear( distance, 0.0 ) )
1756 {
1757 return new QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1758 }
1759
1760 for ( int i = 0; i < ( totalPoints - 2 ) ; i += 2 )
1761 {
1762 double x1 = prevX;
1763 double y1 = prevY;
1764 double z1 = prevZ;
1765 double m1 = prevM;
1766
1767 double x2 = *x++;
1768 double y2 = *y++;
1769 double z2 = z ? *z++ : 0.0;
1770 double m2 = m ? *m++ : 0.0;
1771
1772 double x3 = *x++;
1773 double y3 = *y++;
1774 double z3 = z ? *z++ : 0.0;
1775 double m3 = m ? *m++ : 0.0;
1776
1777 const double segmentLength = QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1778 if ( distance < distanceTraversed + segmentLength || qgsDoubleNear( distance, distanceTraversed + segmentLength ) )
1779 {
1780 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1781 const double distanceToPoint = std::min( distance - distanceTraversed, segmentLength );
1782 return new QgsPoint( QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1783 QgsPoint( pointType, x2, y2, z2, m2 ),
1784 QgsPoint( pointType, x3, y3, z3, m3 ), distanceToPoint ) );
1785 }
1786
1787 distanceTraversed += segmentLength;
1788
1789 prevX = x3;
1790 prevY = y3;
1791 prevZ = z3;
1792 prevM = m3;
1793 }
1794
1795 return nullptr;
1796}
1797
1798QgsCircularString *QgsCircularString::curveSubstring( double startDistance, double endDistance ) const
1799{
1800 if ( startDistance < 0 && endDistance < 0 )
1801 return createEmptyWithSameType();
1802
1803 endDistance = std::max( startDistance, endDistance );
1804
1805 const int totalPoints = numPoints();
1806 if ( totalPoints == 0 )
1807 return clone();
1808
1809 QVector< QgsPoint > substringPoints;
1810 substringPoints.reserve( totalPoints );
1811
1813 if ( is3D() )
1814 pointType = Qgis::WkbType::PointZ;
1815 if ( isMeasure() )
1816 pointType = QgsWkbTypes::addM( pointType );
1817
1818 const double *x = mX.constData();
1819 const double *y = mY.constData();
1820 const double *z = is3D() ? mZ.constData() : nullptr;
1821 const double *m = isMeasure() ? mM.constData() : nullptr;
1822
1823 double distanceTraversed = 0;
1824 double prevX = *x++;
1825 double prevY = *y++;
1826 double prevZ = z ? *z++ : 0.0;
1827 double prevM = m ? *m++ : 0.0;
1828 bool foundStart = false;
1829
1830 if ( startDistance < 0 )
1831 startDistance = 0;
1832
1833 for ( int i = 0; i < ( totalPoints - 2 ) ; i += 2 )
1834 {
1835 double x1 = prevX;
1836 double y1 = prevY;
1837 double z1 = prevZ;
1838 double m1 = prevM;
1839
1840 double x2 = *x++;
1841 double y2 = *y++;
1842 double z2 = z ? *z++ : 0.0;
1843 double m2 = m ? *m++ : 0.0;
1844
1845 double x3 = *x++;
1846 double y3 = *y++;
1847 double z3 = z ? *z++ : 0.0;
1848 double m3 = m ? *m++ : 0.0;
1849
1850 bool addedSegmentEnd = false;
1851 const double segmentLength = QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1852 if ( distanceTraversed <= startDistance && startDistance < distanceTraversed + segmentLength )
1853 {
1854 // start point falls on this segment
1855 const double distanceToStart = startDistance - distanceTraversed;
1856 const QgsPoint startPoint = QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1857 QgsPoint( pointType, x2, y2, z2, m2 ),
1858 QgsPoint( pointType, x3, y3, z3, m3 ), distanceToStart );
1859
1860 // does end point also fall on this segment?
1861 const bool endPointOnSegment = distanceTraversed + segmentLength > endDistance;
1862 if ( endPointOnSegment )
1863 {
1864 const double distanceToEnd = endDistance - distanceTraversed;
1865 const double midPointDistance = ( distanceToEnd - distanceToStart ) * 0.5 + distanceToStart;
1866 substringPoints << startPoint
1867 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1868 QgsPoint( pointType, x2, y2, z2, m2 ),
1869 QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1870 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1871 QgsPoint( pointType, x2, y2, z2, m2 ),
1872 QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1873 addedSegmentEnd = true;
1874 }
1875 else
1876 {
1877 const double midPointDistance = ( segmentLength - distanceToStart ) * 0.5 + distanceToStart;
1878 substringPoints << startPoint
1879 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1880 QgsPoint( pointType, x2, y2, z2, m2 ),
1881 QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1882 << QgsPoint( pointType, x3, y3, z3, m3 );
1883 addedSegmentEnd = true;
1884 }
1885 foundStart = true;
1886 }
1887 if ( !addedSegmentEnd && foundStart && ( distanceTraversed + segmentLength > endDistance ) )
1888 {
1889 // end point falls on this segment
1890 const double distanceToEnd = endDistance - distanceTraversed;
1891 // add mid point, at half way along this arc, then add the interpolated end point
1892 substringPoints << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1893 QgsPoint( pointType, x2, y2, z2, m2 ),
1894 QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd / 2.0 )
1895
1896 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1897 QgsPoint( pointType, x2, y2, z2, m2 ),
1898 QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1899 }
1900 else if ( !addedSegmentEnd && foundStart )
1901 {
1902 substringPoints << QgsPoint( pointType, x2, y2, z2, m2 )
1903 << QgsPoint( pointType, x3, y3, z3, m3 );
1904 }
1905
1906 prevX = x3;
1907 prevY = y3;
1908 prevZ = z3;
1909 prevM = m3;
1910 distanceTraversed += segmentLength;
1911 if ( distanceTraversed >= endDistance )
1912 break;
1913 }
1914
1915 // start point is the last node
1916 if ( !foundStart && qgsDoubleNear( distanceTraversed, startDistance ) )
1917 {
1918 substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM )
1919 << QgsPoint( pointType, prevX, prevY, prevZ, prevM )
1920 << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1921 }
1922
1923 auto result = std::make_unique< QgsCircularString >();
1924 result->setPoints( substringPoints );
1925 return result.release();
1926}
1927
1928bool QgsCircularString::addZValue( double zValue )
1929{
1930 if ( QgsWkbTypes::hasZ( mWkbType ) )
1931 return false;
1932
1933 clearCache();
1935
1936 int nPoints = numPoints();
1937 mZ.clear();
1938 mZ.reserve( nPoints );
1939 for ( int i = 0; i < nPoints; ++i )
1940 {
1941 mZ << zValue;
1942 }
1943 return true;
1944}
1945
1946bool QgsCircularString::addMValue( double mValue )
1947{
1948 if ( QgsWkbTypes::hasM( mWkbType ) )
1949 return false;
1950
1951 clearCache();
1953
1954 int nPoints = numPoints();
1955 mM.clear();
1956 mM.reserve( nPoints );
1957 for ( int i = 0; i < nPoints; ++i )
1958 {
1959 mM << mValue;
1960 }
1961 return true;
1962}
1963
1965{
1966 if ( !QgsWkbTypes::hasZ( mWkbType ) )
1967 return false;
1968
1969 clearCache();
1970
1972 mZ.clear();
1973 return true;
1974}
1975
1977{
1978 if ( !QgsWkbTypes::hasM( mWkbType ) )
1979 return false;
1980
1981 clearCache();
1982
1984 mM.clear();
1985 return true;
1986}
1987
1989{
1990 std::swap( mX, mY );
1991 clearCache();
1992}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2075
VertexType
Types of vertex.
Definition qgis.h:3066
@ Curve
An intermediate point on a segment defining the curvature of the segment.
Definition qgis.h:3068
@ Segment
The actual start or end point of a segment.
Definition qgis.h:3067
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:277
@ Point
Point.
Definition qgis.h:279
@ PointM
PointM.
Definition qgis.h:310
@ CircularString
CircularString.
Definition qgis.h:287
@ PointZ
PointZ.
Definition qgis.h:295
@ PointZM
PointZM.
Definition qgis.h:325
@ CircularStringZ
CircularStringZ.
Definition qgis.h:303
TransformDirection
Indicates the direction (forward or inverse) of a transform.
Definition qgis.h:2671
An abstract base class for classes which transform geometries by transforming input points to output ...
virtual bool transformPoint(double &x, double &y, double &z, double &m)=0
Transforms the point defined by the coordinates (x, y, z) and the specified m value.
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle.
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.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
void setZMTypeFromSubGeometry(const QgsAbstractGeometry *subggeom, Qgis::WkbType baseGeomType)
Updates the geometry type based on whether sub geometries contain z or m values.
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:42
void combineWith(const QgsBox3D &box)
Expands the bbox so that it covers both the original rectangle and the given rectangle.
Definition qgsbox3d.cpp:210
QgsCircularString * 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.
double length() const override
Returns the planar, 2-dimensional length of the geometry.
QString geometryType() const override
Returns a unique string representing the geometry type.
void points(QgsPointSequence &pts) const override
Returns a list of points within the curve.
bool moveVertex(QgsVertexId position, const QgsPoint &newPos) override
Moves a vertex within the geometry.
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
QgsCircularString * clone() const override
Clones the geometry by performing a deep copy.
QgsPoint endPoint() const override
Returns the end point of the curve.
void append(const QgsCircularString *string)
Appends the contents of another circular string to the end of this circular string.
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
void draw(QPainter &p) const override
Draws the geometry using the specified QPainter.
QgsCircularString * createEmptyWithSameType() const override
Creates a new geometry with the same class and same WKB type as the original and transfers ownership.
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.
void swapXy() override
Swaps the x and y coordinates from the geometry.
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
double distanceBetweenVertices(QgsVertexId fromVertex, QgsVertexId toVertex) const override
Returns the distance along the curve between two vertices.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
static QgsCircularString fromTwoPointsAndCenter(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &center, bool useShortestArc=true)
Creates a circular string with a single arc representing the curve from p1 to p2 with the specified c...
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...
double segmentLength(QgsVertexId startVertex) const override
Returns the length of the segment of the geometry which begins at startVertex.
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...
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
bool fromWkt(const QString &wkt) override
Sets the geometry from a WKT string.
void transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection d=Qgis::TransformDirection::Forward, bool transformZ=false) override
Transforms the geometry using a coordinate transform.
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.
void sumUpArea(double &sum) const override
Sums up the area of the curve by iterating over the vertices (shoelace formula).
QgsCircularString * reversed() const override
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
QgsBox3D calculateBoundingBox3D() const override
Calculates the minimal 3D bounding box for the geometry.
QgsPoint startPoint() const override
Returns the starting point of the curve.
bool pointAt(int node, QgsPoint &point, Qgis::VertexType &type) const override
Returns the point and vertex id of a point within the curve.
bool isEmpty() const override
Returns true if the geometry is empty.
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...
bool dropMValue() override
Drops any measure values which exist in the geometry.
int numPoints() const override
Returns the number of points in the curve.
void addToPainterPath(QPainterPath &path) const override
Adds a curve to a painter path.
int wkbSize(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns the length of the QByteArray returned by asWkb().
QgsCircularString * curveSubstring(double startDistance, double endDistance) const override
Returns a new curve representing a substring of this curve.
void drawAsPolygon(QPainter &p) const override
Draws the curve as a polygon on the specified QPainter.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
int dimension() const override
Returns the inherent dimension of the geometry.
void setPoints(const QgsPointSequence &points)
Sets the circular string's points.
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.
double vertexAngle(QgsVertexId vertex) const override
Returns approximate angle at a vertex.
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
QgsAbstractGeometry * simplifyByDistance(double tolerance) const override
Simplifies the geometry by applying the Douglas Peucker simplification by distance algorithm.
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.
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.
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
bool hasCurvedSegments() const override
Returns true if the geometry contains curved segments.
void scroll(int firstVertexIndex) final
Scrolls the curve vertices so that they start with the vertex at the given index.
QgsPoint * interpolatePoint(double distance) const override
Returns an interpolated point on the curve at the specified distance.
bool isValid(QString &error, Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const override
Checks validity of the geometry, and returns true if the geometry is valid.
double zAt(int index) const override
Returns the z-coordinate of the specified node in the line string.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into the geometry.
QgsCircularString()
Constructs an empty circular string.
QgsPoint pointN(int i) const
Returns the point at index i within the circular string.
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...
double mAt(int index) const override
Returns the m-coordinate of the specified node in the line string.
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...
json asJsonObject(int precision=17) const override
Returns a json object representation of the geometry.
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.
A const WKB pointer.
Definition qgswkbptr.h:139
Qgis::WkbType readHeader() const
readHeader
Definition qgswkbptr.cpp:56
Handles coordinate transforms between two coordinate systems.
void transformCoords(int numPoint, double *x, double *y, double *z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform an array of coordinates to the destination CRS.
void clearCache() const override
Clears any cached parameters associated with the geometry, e.g., bounding boxes.
Definition qgscurve.cpp:294
bool mHasCachedSummedUpArea
Definition qgscurve.h:394
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:248
bool snapToGridPrivate(double hSpacing, double vSpacing, double dSpacing, double mSpacing, const QVector< double > &srcX, const QVector< double > &srcY, const QVector< double > &srcZ, const QVector< double > &srcM, QVector< double > &outX, QVector< double > &outY, QVector< double > &outZ, QVector< double > &outM, bool removeRedundantPoints) const
Helper function for QgsCurve subclasses to snap to grids.
Definition qgscurve.cpp:318
double mSummedUpArea
Definition qgscurve.h:395
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:53
static double circleLength(double x1, double y1, double x2, double y2, double x3, double y3)
Length of a circular string segment defined by pt1, pt2, pt3.
static double calculateArcLength(double centerX, double centerY, double radius, double x1, double y1, double x2, double y2, double x3, double y3, int fromVertex, int toVertex)
Calculates the precise arc length between two vertices on a circular arc.
static double ccwAngle(double dy, double dx)
Returns the counter clockwise angle between a line with components dx, dy and the line with dx > 0 an...
static bool circleClockwise(double angle1, double angle2, double angle3)
Returns true if the circle defined by three angles is ordered clockwise.
static double sweepAngle(double centerX, double centerY, double x1, double y1, double x2, double y2, double x3, double y3)
Calculates angle of a circular string part defined by pt1, pt2, pt3.
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 bool circleAngleBetween(double angle, double angle1, double angle2, bool clockwise)
Returns true if, in a circle, angle is between angle1 and angle2.
static bool angleOnCircle(double angle, double angle1, double angle2, double angle3)
Returns true if an angle is between angle1 and angle3 on a circle described by angle1,...
static int leftOfLine(const double x, const double y, const double x1, const double y1, const double x2, const double y2)
Returns a value < 0 if the point (x, y) is left of the line from (x1, y1) -> (x2, y2).
static void circleCenterRadius(double x1, double y1, double x2, double y2, double x3, double y3, double &radius, double &centerX, double &centerY)
Returns radius and center of the circle through (x1 y1), (x2 y2), (x3 y3).
static double circleTangentDirection(const QgsPoint &tangentPoint, const QgsPoint &cp1, const QgsPoint &cp2, const QgsPoint &cp3)
Calculates the direction angle of a circle tangent (clockwise from north in radians).
static QgsPoint pointOnLineWithDistance(const QgsPoint &startPoint, const QgsPoint &directionPoint, double distance)
Returns a point a specified distance toward a second point.
static void pointsToWKB(QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure, QgsAbstractGeometry::WkbFlags flags)
Returns a LinearRing { uint32 numPoints; Point points[numPoints]; }.
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 void circleCenterRadius(const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double &radius, double &centerX, double &centerY)
Returns radius and center of the circle through pt1, pt2, pt3.
static QgsPoint interpolatePointOnArc(const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double distance)
Interpolates a point on an arc defined by three points, pt1, pt2 and pt3.
static QgsPointSequence pointsFromWKT(const QString &wktCoordinateList, bool is3D, bool isMeasure)
Returns a list of points contained in a WKT string.
static void segmentizeArc(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, QgsPointSequence &points, double tolerance=M_PI_2/90, QgsAbstractGeometry::SegmentationToleranceType toleranceType=QgsAbstractGeometry::MaximumAngle, bool hasZ=false, bool hasM=false)
Convert circular arc defined by p1, p2, p3 (p1/p3 being start resp.
static Q_DECL_DEPRECATED double sqrDistance2D(double x1, double y1, double x2, double y2)
Returns the squared 2D distance between (x1, y1) and (x2, y2).
static QDomElement pointsToGML3(const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, bool is3D, QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY)
Returns a gml::posList DOM element.
static QString pointsToWKT(const QgsPointSequence &points, int precision, bool is3D, bool isMeasure)
Returns a WKT coordinate list.
static QgsPoint segmentMidPointFromCenter(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &center, bool useShortestArc=true)
Calculates the midpoint on the circle passing through p1 and p2, with the specified center coordinate...
Line string geometry type, with support for z-dimension and m-values.
void setPoints(size_t size, const double *x, const double *y, const double *z=nullptr, const double *m=nullptr)
Resets the line string to match the specified point data.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
void setY(double y)
Sets the point's y-coordinate.
Definition qgspoint.h:337
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:326
double z
Definition qgspoint.h:54
double x
Definition qgspoint.h:52
double distanceSquared(double x, double y) const
Returns the Cartesian 2D squared distance between this point a specified x, y coordinate.
Definition qgspoint.h:409
double m
Definition qgspoint.h:55
double y
Definition qgspoint.h:53
A rectangle specified with double values.
WKB pointer handler.
Definition qgswkbptr.h:45
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.
double ANALYSIS_EXPORT leftOf(const QgsPoint &thepoint, const QgsPoint *p1, const QgsPoint *p2)
Returns whether 'thepoint' is left or right of the line from 'p1' to 'p2'. Negative values mean left ...
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPoint > QgsPointSequence
void arcTo(QPainterPath &path, QPointF pt1, QPointF pt2, QPointF pt3)
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:30
int vertex
Vertex number.
Definition qgsvertexid.h:94
int part
Part number.
Definition qgsvertexid.h:88
int ring
Ring number.
Definition qgsvertexid.h:91