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