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