QGIS API Documentation 4.1.0-Master (31622b25bb0)
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 ) || secondWithoutParentheses.isEmpty() )
494 return true;
495
497 if ( points.isEmpty() )
498 return false;
499
500 setPoints( points );
501 return true;
502}
503
505{
506 int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
507 binarySize += numPoints() * ( 2 + is3D() + isMeasure() ) * sizeof( double );
508 return binarySize;
509}
510
511QByteArray QgsCircularString::asWkb( WkbFlags flags ) const
512{
513 QByteArray wkbArray;
514 wkbArray.resize( QgsCircularString::wkbSize( flags ) );
515 QgsWkbPtr wkb( wkbArray );
516 wkb << static_cast<char>( QgsApplication::endian() );
517 wkb << static_cast<quint32>( wkbType() );
519 points( pts );
520 QgsGeometryUtils::pointsToWKB( wkb, pts, is3D(), isMeasure(), flags );
521 return wkbArray;
522}
523
524QString QgsCircularString::asWkt( int precision ) const
525{
526 QString wkt = wktTypeStr() + ' ';
527
528 if ( isEmpty() )
529 wkt += "EMPTY"_L1;
530 else
531 {
533 points( pts );
534 wkt += QgsGeometryUtils::pointsToWKT( pts, precision, is3D(), isMeasure() );
535 }
536 return wkt;
537}
538
539QDomElement QgsCircularString::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
540{
541 // GML2 does not support curves
542 std::unique_ptr< QgsLineString > line( curveToLine() );
543 QDomElement gml = line->asGml2( doc, precision, ns, axisOrder );
544 return gml;
545}
546
547QDomElement QgsCircularString::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
548{
550 points( pts );
551
552 QDomElement elemCurve = doc.createElementNS( ns, u"Curve"_s );
553
554 if ( isEmpty() )
555 return elemCurve;
556
557 QDomElement elemSegments = doc.createElementNS( ns, u"segments"_s );
558 QDomElement elemArcString = doc.createElementNS( ns, u"ArcString"_s );
559 elemArcString.appendChild( QgsGeometryUtils::pointsToGML3( pts, doc, precision, ns, is3D(), axisOrder ) );
560 elemSegments.appendChild( elemArcString );
561 elemCurve.appendChild( elemSegments );
562 return elemCurve;
563}
564
565
566json QgsCircularString::asJsonObject( int precision ) const
567{
568 // GeoJSON does not support curves
569 std::unique_ptr< QgsLineString > line( curveToLine() );
570 return line->asJsonObject( precision );
571}
572
574{
575 return mX.isEmpty();
576}
577
579{
580 if ( !isEmpty() && ( numPoints() < 3 ) )
581 {
582 error = QObject::tr( "CircularString has less than 3 points and is not empty." );
583 return false;
584 }
585 return QgsCurve::isValid( error, flags );
586}
587
588//curve interface
590{
591 int nPoints = numPoints();
592 double length = 0;
593 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
594 {
595 length += QgsGeometryUtilsBase::circleLength( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2] );
596 }
597 return length;
598}
599
601{
602 if ( numPoints() < 1 )
603 {
604 return QgsPoint();
605 }
606 return pointN( 0 );
607}
608
610{
611 if ( numPoints() < 1 )
612 {
613 return QgsPoint();
614 }
615 return pointN( numPoints() - 1 );
616}
617
619{
620 QgsLineString *line = new QgsLineString();
622 int nPoints = numPoints();
623
624 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
625 {
626 QgsGeometryUtils::segmentizeArc( pointN( i ), pointN( i + 1 ), pointN( i + 2 ), points, tolerance, toleranceType, is3D(), isMeasure() );
627 }
628
629 line->setPoints( points );
630 return line;
631}
632
633QgsCircularString *QgsCircularString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool ) const
634{
635 // prepare result
636 std::unique_ptr<QgsCircularString> result { createEmptyWithSameType() };
637
638 // remove redundant not supported for circular strings
639 bool res = snapToGridPrivate( hSpacing, vSpacing, dSpacing, mSpacing, mX, mY, mZ, mM, result->mX, result->mY, result->mZ, result->mM, false );
640 if ( res )
641 return result.release();
642 else
643 return nullptr;
644}
645
647{
648 std::unique_ptr< QgsLineString > line( curveToLine() );
649 return line->simplifyByDistance( tolerance );
650}
651
652bool QgsCircularString::removeDuplicateNodes( double epsilon, bool useZValues )
653{
654 if ( mX.count() <= 3 )
655 return false; // don't create degenerate lines
656 bool result = false;
657 double prevX = mX.at( 0 );
658 double prevY = mY.at( 0 );
659 bool hasZ = is3D();
660 bool useZ = hasZ && useZValues;
661 double prevZ = useZ ? mZ.at( 0 ) : 0;
662 int i = 1;
663 int remaining = mX.count();
664 // we have to consider points in pairs, since a segment can validly have the same start and
665 // end if it has a different curve point
666 while ( i + 1 < remaining )
667 {
668 double currentCurveX = mX.at( i );
669 double currentCurveY = mY.at( i );
670 double currentX = mX.at( i + 1 );
671 double currentY = mY.at( i + 1 );
672 double currentZ = useZ ? mZ.at( i + 1 ) : 0;
673 if ( qgsDoubleNear( currentCurveX, prevX, epsilon )
674 && qgsDoubleNear( currentCurveY, prevY, epsilon )
675 && qgsDoubleNear( currentX, prevX, epsilon )
676 && qgsDoubleNear( currentY, prevY, epsilon )
677 && ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
678 {
679 result = true;
680 // remove point
681 mX.removeAt( i );
682 mX.removeAt( i );
683 mY.removeAt( i );
684 mY.removeAt( i );
685 if ( hasZ )
686 {
687 mZ.removeAt( i );
688 mZ.removeAt( i );
689 }
690 remaining -= 2;
691 }
692 else
693 {
694 prevX = currentX;
695 prevY = currentY;
696 prevZ = currentZ;
697 i += 2;
698 }
699 }
700 return result;
701}
702
704{
705 return std::min( mX.size(), mY.size() );
706}
707
708int QgsCircularString::indexOf( const QgsPoint &point ) const
709{
710 const int size = mX.size();
711 if ( size == 0 )
712 return -1;
713
714 const double *x = mX.constData();
715 const double *y = mY.constData();
716 const bool useZ = is3D();
717 const bool useM = isMeasure();
718 const double *z = useZ ? mZ.constData() : nullptr;
719 const double *m = useM ? mM.constData() : nullptr;
720
721 for ( int i = 0; i < size; i += 2 )
722 {
723 if ( qgsDoubleNear( *x, point.x() ) && qgsDoubleNear( *y, point.y() ) && ( !useZ || qgsDoubleNear( *z, point.z() ) ) && ( !useM || qgsDoubleNear( *m, point.m() ) ) )
724 return i;
725
726 // we skip over curve points!
727 x++;
728 x++;
729 y++;
730 y++;
731 if ( useZ )
732 {
733 z++;
734 z++;
735 }
736 if ( useM )
737 {
738 m++;
739 m++;
740 }
741 }
742 return -1;
743}
744
746{
747 if ( i < 0 || std::min( mX.size(), mY.size() ) <= i )
748 {
749 return QgsPoint();
750 }
751
752 double x = mX.at( i );
753 double y = mY.at( i );
754 double z = 0;
755 double m = 0;
756
757 if ( is3D() )
758 {
759 z = mZ.at( i );
760 }
761 if ( isMeasure() )
762 {
763 m = mM.at( i );
764 }
765
767 if ( is3D() && isMeasure() )
768 {
770 }
771 else if ( is3D() )
772 {
774 }
775 else if ( isMeasure() )
776 {
778 }
779 return QgsPoint( t, x, y, z, m );
780}
781
782double QgsCircularString::xAt( int index ) const
783{
784 if ( index >= 0 && index < mX.size() )
785 return mX.at( index );
786 else
787 return 0.0;
788}
789
790double QgsCircularString::yAt( int index ) const
791{
792 if ( index >= 0 && index < mY.size() )
793 return mY.at( index );
794 else
795 return 0.0;
796}
797
798double QgsCircularString::zAt( int index ) const
799{
800 if ( index >= 0 && index < mZ.size() )
801 return mZ.at( index );
802 else
803 return 0.0;
804}
805
806double QgsCircularString::mAt( int index ) const
807{
808 if ( index >= 0 && index < mM.size() )
809 return mM.at( index );
810 else
811 return 0.0;
812}
813
815{
816 if ( !transformer )
817 return false;
818
819 bool hasZ = is3D();
820 bool hasM = isMeasure();
821 int size = mX.size();
822
823 double *srcX = mX.data();
824 double *srcY = mY.data();
825 double *srcM = hasM ? mM.data() : nullptr;
826 double *srcZ = hasZ ? mZ.data() : nullptr;
827
828 bool res = true;
829 for ( int i = 0; i < size; ++i )
830 {
831 double x = *srcX;
832 double y = *srcY;
833 double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
834 double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
835 if ( !transformer->transformPoint( x, y, z, m ) )
836 {
837 res = false;
838 break;
839 }
840
841 *srcX++ = x;
842 *srcY++ = y;
843 if ( hasM )
844 *srcM++ = m;
845 if ( hasZ )
846 *srcZ++ = z;
847
848 if ( feedback && feedback->isCanceled() )
849 {
850 res = false;
851 break;
852 }
853 }
854 clearCache();
855 return res;
856}
857
858void QgsCircularString::filterVertices( const std::function<bool( const QgsPoint & )> &filter )
859{
860 bool hasZ = is3D();
861 bool hasM = isMeasure();
862 int size = mX.size();
863
864 double *srcX = mX.data(); // clazy:exclude=detaching-member
865 double *srcY = mY.data(); // clazy:exclude=detaching-member
866 double *srcM = hasM ? mM.data() : nullptr; // clazy:exclude=detaching-member
867 double *srcZ = hasZ ? mZ.data() : nullptr; // clazy:exclude=detaching-member
868
869 double *destX = srcX;
870 double *destY = srcY;
871 double *destM = srcM;
872 double *destZ = srcZ;
873
874 int filteredPoints = 0;
875 for ( int i = 0; i < size; ++i )
876 {
877 double x = *srcX++;
878 double y = *srcY++;
879 double z = hasZ ? *srcZ++ : std::numeric_limits<double>::quiet_NaN();
880 double m = hasM ? *srcM++ : std::numeric_limits<double>::quiet_NaN();
881
882 if ( filter( QgsPoint( x, y, z, m ) ) )
883 {
884 filteredPoints++;
885 *destX++ = x;
886 *destY++ = y;
887 if ( hasM )
888 *destM++ = m;
889 if ( hasZ )
890 *destZ++ = z;
891 }
892 }
893
894 mX.resize( filteredPoints );
895 mY.resize( filteredPoints );
896 if ( hasZ )
897 mZ.resize( filteredPoints );
898 if ( hasM )
899 mM.resize( filteredPoints );
900
901 clearCache();
902}
903
904void QgsCircularString::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
905{
906 bool hasZ = is3D();
907 bool hasM = isMeasure();
908 int size = mX.size();
909
910 double *srcX = mX.data();
911 double *srcY = mY.data();
912 double *srcM = hasM ? mM.data() : nullptr;
913 double *srcZ = hasZ ? mZ.data() : nullptr;
914
915 for ( int i = 0; i < size; ++i )
916 {
917 double x = *srcX;
918 double y = *srcY;
919 double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
920 double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
921 QgsPoint res = transform( QgsPoint( x, y, z, m ) );
922 *srcX++ = res.x();
923 *srcY++ = res.y();
924 if ( hasM )
925 *srcM++ = res.m();
926 if ( hasZ )
927 *srcZ++ = res.z();
928 }
929 clearCache();
930}
931
932std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsCircularString::splitCurveAtVertex( int index ) const
933{
934 const bool useZ = is3D();
935 const bool useM = isMeasure();
936
937 const int size = mX.size();
938 if ( size == 0 )
939 return std::make_tuple( std::make_unique< QgsCircularString >(), std::make_unique< QgsCircularString >() );
940
941 index = std::clamp( index, 0, size - 1 );
942
943 const int part1Size = index + 1;
944 QVector< double > x1( part1Size );
945 QVector< double > y1( part1Size );
946 QVector< double > z1( useZ ? part1Size : 0 );
947 QVector< double > m1( useM ? part1Size : 0 );
948
949 const double *sourceX = mX.constData();
950 const double *sourceY = mY.constData();
951 const double *sourceZ = useZ ? mZ.constData() : nullptr;
952 const double *sourceM = useM ? mM.constData() : nullptr;
953
954 double *destX = x1.data();
955 double *destY = y1.data();
956 double *destZ = useZ ? z1.data() : nullptr;
957 double *destM = useM ? m1.data() : nullptr;
958
959 std::copy( sourceX, sourceX + part1Size, destX );
960 std::copy( sourceY, sourceY + part1Size, destY );
961 if ( useZ )
962 std::copy( sourceZ, sourceZ + part1Size, destZ );
963 if ( useM )
964 std::copy( sourceM, sourceM + part1Size, destM );
965
966 const int part2Size = size - index;
967 if ( part2Size < 2 )
968 return std::make_tuple( std::make_unique< QgsCircularString >( x1, y1, z1, m1 ), std::make_unique< QgsCircularString >() );
969
970 QVector< double > x2( part2Size );
971 QVector< double > y2( part2Size );
972 QVector< double > z2( useZ ? part2Size : 0 );
973 QVector< double > m2( useM ? part2Size : 0 );
974 destX = x2.data();
975 destY = y2.data();
976 destZ = useZ ? z2.data() : nullptr;
977 destM = useM ? m2.data() : nullptr;
978 std::copy( sourceX + index, sourceX + size, destX );
979 std::copy( sourceY + index, sourceY + size, destY );
980 if ( useZ )
981 std::copy( sourceZ + index, sourceZ + size, destZ );
982 if ( useM )
983 std::copy( sourceM + index, sourceM + size, destM );
984
985 if ( part1Size < 2 )
986 return std::make_tuple( std::make_unique< QgsCircularString >(), std::make_unique< QgsCircularString >( x2, y2, z2, m2 ) );
987 else
988 return std::make_tuple( std::make_unique< QgsCircularString >( x1, y1, z1, m1 ), std::make_unique< QgsCircularString >( x2, y2, z2, m2 ) );
989}
990
992{
993 pts.clear();
994 int nPts = numPoints();
995 for ( int i = 0; i < nPts; ++i )
996 {
997 pts.push_back( pointN( i ) );
998 }
999}
1000
1002{
1003 clearCache();
1004
1005 if ( points.empty() )
1006 {
1008 mX.clear();
1009 mY.clear();
1010 mZ.clear();
1011 mM.clear();
1012 return;
1013 }
1014
1015 //get wkb type from first point
1016 const QgsPoint &firstPt = points.at( 0 );
1017 bool hasZ = firstPt.is3D();
1018 bool hasM = firstPt.isMeasure();
1019
1021
1022 mX.resize( points.size() );
1023 mY.resize( points.size() );
1024 if ( hasZ )
1025 {
1026 mZ.resize( points.size() );
1027 }
1028 else
1029 {
1030 mZ.clear();
1031 }
1032 if ( hasM )
1033 {
1034 mM.resize( points.size() );
1035 }
1036 else
1037 {
1038 mM.clear();
1039 }
1040
1041 for ( int i = 0; i < points.size(); ++i )
1042 {
1043 mX[i] = points[i].x();
1044 mY[i] = points[i].y();
1045 if ( hasZ )
1046 {
1047 double z = points.at( i ).z();
1048 mZ[i] = std::isnan( z ) ? 0 : z;
1049 }
1050 if ( hasM )
1051 {
1052 double m = points.at( i ).m();
1053 mM[i] = std::isnan( m ) ? 0 : m;
1054 }
1055 }
1056}
1057
1059{
1060 if ( !line || line->isEmpty() )
1061 {
1062 return;
1063 }
1064
1065 if ( numPoints() < 1 )
1066 {
1068 }
1069
1070 // do not store duplicate points
1071 if ( numPoints() > 0
1072 && line->numPoints() > 0
1073 && qgsDoubleNear( endPoint().x(), line->startPoint().x() )
1074 && qgsDoubleNear( endPoint().y(), line->startPoint().y() )
1075 && ( !is3D() || !line->is3D() || qgsDoubleNear( endPoint().z(), line->startPoint().z() ) )
1076 && ( !isMeasure() || !line->isMeasure() || qgsDoubleNear( endPoint().m(), line->startPoint().m() ) ) )
1077 {
1078 mX.pop_back();
1079 mY.pop_back();
1080
1081 if ( is3D() && line->is3D() )
1082 {
1083 mZ.pop_back();
1084 }
1085 if ( isMeasure() && line->isMeasure() )
1086 {
1087 mM.pop_back();
1088 }
1089 }
1090
1091 mX += line->mX;
1092 mY += line->mY;
1093
1094 if ( is3D() )
1095 {
1096 if ( line->is3D() )
1097 {
1098 mZ += line->mZ;
1099 }
1100 else
1101 {
1102 // if append line does not have z coordinates, fill with NaN to match number of points in final line
1103 mZ.insert( mZ.count(), mX.size() - mZ.size(), std::numeric_limits<double>::quiet_NaN() );
1104 }
1105 }
1106
1107 if ( isMeasure() )
1108 {
1109 if ( line->isMeasure() )
1110 {
1111 mM += line->mM;
1112 }
1113 else
1114 {
1115 // if append line does not have m values, fill with NaN to match number of points in final line
1116 mM.insert( mM.count(), mX.size() - mM.size(), std::numeric_limits<double>::quiet_NaN() );
1117 }
1118 }
1119
1120 clearCache(); //set bounding box invalid
1121}
1122
1123void QgsCircularString::draw( QPainter &p ) const
1124{
1125 QPainterPath path;
1126 addToPainterPath( path );
1127 p.drawPath( path );
1128}
1129
1131{
1132 clearCache();
1133
1134 double *zArray = nullptr;
1135 bool hasZ = is3D();
1136 int nPoints = numPoints();
1137
1138 // it's possible that transformCoords will throw an exception - so we need to use
1139 // a smart pointer for the dummy z values in order to ensure that they always get cleaned up
1140 std::unique_ptr< double[] > dummyZ;
1141 if ( !hasZ || !transformZ )
1142 {
1143 dummyZ = std::make_unique<double[]>( nPoints );
1144 zArray = dummyZ.get();
1145 }
1146 else
1147 {
1148 zArray = mZ.data();
1149 }
1150 ct.transformCoords( nPoints, mX.data(), mY.data(), zArray, d );
1151}
1152
1153void QgsCircularString::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
1154{
1155 clearCache();
1156
1157 int nPoints = numPoints();
1158 bool hasZ = is3D();
1159 bool hasM = isMeasure();
1160 for ( int i = 0; i < nPoints; ++i )
1161 {
1162 qreal x, y;
1163 t.map( mX.at( i ), mY.at( i ), &x, &y );
1164 mX[i] = x;
1165 mY[i] = y;
1166 if ( hasZ )
1167 {
1168 mZ[i] = mZ.at( i ) * zScale + zTranslate;
1169 }
1170 if ( hasM )
1171 {
1172 mM[i] = mM.at( i ) * mScale + mTranslate;
1173 }
1174 }
1175}
1176
1177void arcTo( QPainterPath &path, QPointF pt1, QPointF pt2, QPointF pt3 )
1178{
1179 double centerX, centerY, radius;
1180 QgsGeometryUtils::circleCenterRadius( QgsPoint( pt1.x(), pt1.y() ), QgsPoint( pt2.x(), pt2.y() ), QgsPoint( pt3.x(), pt3.y() ), radius, centerX, centerY );
1181
1182 double p1Angle = QgsGeometryUtilsBase::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
1183 double sweepAngle = QgsGeometryUtilsBase::sweepAngle( centerX, centerY, pt1.x(), pt1.y(), pt2.x(), pt2.y(), pt3.x(), pt3.y() );
1184
1185 double diameter = 2 * radius;
1186 path.arcTo( centerX - radius, centerY - radius, diameter, diameter, -p1Angle, -sweepAngle );
1187}
1188
1189void QgsCircularString::addToPainterPath( QPainterPath &path ) const
1190{
1191 int nPoints = numPoints();
1192 if ( nPoints < 1 )
1193 {
1194 return;
1195 }
1196
1197 if ( path.isEmpty() || path.currentPosition() != QPointF( mX[0], mY[0] ) )
1198 {
1199 path.moveTo( QPointF( mX[0], mY[0] ) );
1200 }
1201
1202 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
1203 {
1204 arcTo( path, QPointF( mX[i], mY[i] ), QPointF( mX[i + 1], mY[i + 1] ), QPointF( mX[i + 2], mY[i + 2] ) );
1205 }
1206
1207 //if number of points is even, connect to last point with straight line (even though the circular string is not valid)
1208 if ( nPoints % 2 == 0 )
1209 {
1210 path.lineTo( mX[nPoints - 1], mY[nPoints - 1] );
1211 }
1212}
1213
1214void QgsCircularString::drawAsPolygon( QPainter &p ) const
1215{
1216 draw( p );
1217}
1218
1220{
1221 if ( position.vertex >= mX.size() || position.vertex < 1 )
1222 {
1223 return false;
1224 }
1225
1226 mX.insert( position.vertex, vertex.x() );
1227 mY.insert( position.vertex, vertex.y() );
1228 if ( is3D() )
1229 {
1230 mZ.insert( position.vertex, vertex.z() );
1231 }
1232 if ( isMeasure() )
1233 {
1234 mM.insert( position.vertex, vertex.m() );
1235 }
1236
1237 bool vertexNrEven = ( position.vertex % 2 == 0 );
1238 if ( vertexNrEven )
1239 {
1240 insertVertexBetween( position.vertex - 2, position.vertex - 1, position.vertex );
1241 }
1242 else
1243 {
1244 insertVertexBetween( position.vertex, position.vertex + 1, position.vertex - 1 );
1245 }
1246 clearCache(); //set bounding box invalid
1247 return true;
1248}
1249
1251{
1252 if ( position.vertex < 0 || position.vertex >= mX.size() )
1253 {
1254 return false;
1255 }
1256
1257 mX[position.vertex] = newPos.x();
1258 mY[position.vertex] = newPos.y();
1259 if ( is3D() && newPos.is3D() )
1260 {
1261 mZ[position.vertex] = newPos.z();
1262 }
1263 if ( isMeasure() && newPos.isMeasure() )
1264 {
1265 mM[position.vertex] = newPos.m();
1266 }
1267 clearCache(); //set bounding box invalid
1268 return true;
1269}
1270
1272{
1273 int nVertices = this->numPoints();
1274
1275 if ( position.vertex < 0 || position.vertex > ( nVertices - 1 ) )
1276 {
1277 return false;
1278 }
1279
1280 if ( nVertices < 4 ) //circular string must have at least 3 vertices
1281 {
1282 clear();
1283 return true;
1284 }
1285
1286 if ( position.vertex < ( nVertices - 2 ) )
1287 {
1288 //remove this and the following vertex
1289 deleteVertex( position.vertex + 1 );
1290 deleteVertex( position.vertex );
1291 }
1292 else //remove this and the preceding vertex
1293 {
1294 deleteVertex( position.vertex );
1295 deleteVertex( position.vertex - 1 );
1296 }
1297
1298 clearCache(); //set bounding box invalid
1299 return true;
1300}
1301
1303{
1304 mX.remove( i );
1305 mY.remove( i );
1306 if ( is3D() )
1307 {
1308 mZ.remove( i );
1309 }
1310 if ( isMeasure() )
1311 {
1312 mM.remove( i );
1313 }
1314 clearCache();
1315}
1316
1317bool QgsCircularString::deleteVertices( const QSet<QgsVertexId> &positions )
1318{
1319 if ( positions.empty() )
1320 {
1321 return false;
1322 }
1323
1324 for ( QgsVertexId pos : positions )
1325 {
1326 if ( !hasVertex( pos ) )
1327 {
1328 return false;
1329 }
1330 }
1331
1332 int nVertices = this->numPoints();
1333
1334 QList<QgsVertexId> vertices( positions.begin(), positions.end() );
1335
1336 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex < b.vertex; } );
1337
1338 // remove adjacent vertices as deleting one will also delete the other
1339 for ( size_t i = vertices.size() - 1; i >= 1; i-- )
1340 {
1341 int vertexNr = vertices[i].vertex;
1342 int prevVertexNr = vertices[i - 1].vertex;
1343
1344 if ( vertexNr - 1 == prevVertexNr )
1345 {
1346 if ( vertexNr < nVertices - 2 )
1347 {
1348 vertices.removeAt( i );
1349 }
1350 else
1351 {
1352 vertices.removeAt( i - 1 );
1353 }
1354
1355 i--; // adjacent vertices handled, we can skip the next one as well
1356
1357 if ( i == 0 )
1358 break;
1359 }
1360 }
1361
1362 // this check cannot be moved further up, we need to check adjacent vertices first
1363 if ( nVertices - vertices.size() * 2 < 3 )
1364 {
1365 clear();
1366 return true;
1367 }
1368
1369 QListIterator<QgsVertexId> positionsIt( vertices );
1370 positionsIt.toBack();
1371 while ( positionsIt.hasPrevious() )
1372 {
1373 int currentVertexNr = positionsIt.previous().vertex;
1374
1375 if ( currentVertexNr < nVertices - 2 )
1376 {
1377 deleteVertex( currentVertexNr + 1 );
1378 deleteVertex( currentVertexNr );
1379 }
1380 else
1381 {
1382 deleteVertex( currentVertexNr );
1383 deleteVertex( currentVertexNr - 1 );
1384 }
1385 nVertices -= 2;
1386 }
1387
1388 return true;
1389}
1390
1391double QgsCircularString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1392{
1393 double minDist = std::numeric_limits<double>::max();
1394 QgsPoint minDistSegmentPoint;
1395 QgsVertexId minDistVertexAfter;
1396 int minDistLeftOf = 0;
1397
1398 double currentDist = 0.0;
1399
1400 int nPoints = numPoints();
1401 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
1402 {
1403 currentDist = closestPointOnArc( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2], pt, segmentPt, vertexAfter, leftOf, epsilon );
1404 if ( currentDist < minDist )
1405 {
1406 minDist = currentDist;
1407 minDistSegmentPoint = segmentPt;
1408 minDistVertexAfter.vertex = vertexAfter.vertex + i;
1409 if ( leftOf )
1410 {
1411 minDistLeftOf = *leftOf;
1412 }
1413 }
1414 }
1415
1416 if ( minDist == std::numeric_limits<double>::max() )
1417 return -1; // error: no segments
1418
1419 segmentPt = minDistSegmentPoint;
1420 vertexAfter = minDistVertexAfter;
1421 vertexAfter.part = 0;
1422 vertexAfter.ring = 0;
1423 if ( leftOf )
1424 {
1425 *leftOf = qgsDoubleNear( minDist, 0.0 ) ? 0 : minDistLeftOf;
1426 }
1427 return minDist;
1428}
1429
1430bool QgsCircularString::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
1431{
1432 if ( node < 0 || node >= numPoints() )
1433 {
1434 return false;
1435 }
1436 point = pointN( node );
1437 type = ( node % 2 == 0 ) ? Qgis::VertexType::Segment : Qgis::VertexType::Curve;
1438 return true;
1439}
1440
1441void QgsCircularString::sumUpArea( double &sum ) const
1442{
1444 {
1445 sum += mSummedUpArea;
1446 return;
1447 }
1448
1449 int maxIndex = numPoints() - 2;
1450 mSummedUpArea = 0;
1451 for ( int i = 0; i < maxIndex; i += 2 )
1452 {
1453 QgsPoint p1( mX[i], mY[i] );
1454 QgsPoint p2( mX[i + 1], mY[i + 1] );
1455 QgsPoint p3( mX[i + 2], mY[i + 2] );
1456
1457 //segment is a full circle, p2 is the center point
1458 if ( p1 == p3 )
1459 {
1460 double r2 = QgsGeometryUtils::sqrDistance2D( p1, p2 ) / 4.0;
1461 mSummedUpArea += M_PI * r2;
1462 continue;
1463 }
1464
1465 mSummedUpArea += 0.5 * ( mX[i] * mY[i + 2] - mY[i] * mX[i + 2] );
1466
1467 //calculate area between circle and chord, then sum / subtract from total area
1468 double midPointX = ( p1.x() + p3.x() ) / 2.0;
1469 double midPointY = ( p1.y() + p3.y() ) / 2.0;
1470
1471 double radius, centerX, centerY;
1472 QgsGeometryUtils::circleCenterRadius( p1, p2, p3, radius, centerX, centerY );
1473
1474 double d = std::sqrt( QgsGeometryUtils::sqrDistance2D( QgsPoint( centerX, centerY ), QgsPoint( midPointX, midPointY ) ) );
1475 double r2 = radius * radius;
1476
1477 if ( d > radius )
1478 {
1479 //d cannot be greater than radius, something must be wrong...
1480 continue;
1481 }
1482
1483 bool circlePointLeftOfLine = QgsGeometryUtilsBase::leftOfLine( p2.x(), p2.y(), p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
1484 bool centerPointLeftOfLine = QgsGeometryUtilsBase::leftOfLine( centerX, centerY, p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
1485
1486 double cov = 0.5 - d * std::sqrt( r2 - d * d ) / ( M_PI * r2 ) - M_1_PI * std::asin( d / radius );
1487 double circleChordArea = 0;
1488 if ( circlePointLeftOfLine == centerPointLeftOfLine )
1489 {
1490 circleChordArea = M_PI * r2 * ( 1 - cov );
1491 }
1492 else
1493 {
1494 circleChordArea = M_PI * r2 * cov;
1495 }
1496
1497 if ( !circlePointLeftOfLine )
1498 {
1499 mSummedUpArea += circleChordArea;
1500 }
1501 else
1502 {
1503 mSummedUpArea -= circleChordArea;
1504 }
1505 }
1506
1508 sum += mSummedUpArea;
1509}
1510
1511void QgsCircularString::sumUpArea3D( double &sum ) const
1512{
1514 {
1515 sum += mSummedUpArea3D;
1516 return;
1517 }
1518
1519 // No Z component. Fallback to the 2D version
1520 if ( mZ.isEmpty() )
1521 {
1522 double area2D = 0;
1523 sumUpArea( area2D );
1524 mSummedUpArea3D = area2D;
1526 sum += mSummedUpArea3D;
1527 return;
1528 }
1529
1530 // FIXME: Implement proper 3D shoelace formula for circular strings
1531 // workaround: project points to 2D plane and apply standard 2D shoelace formula
1532 mSummedUpArea3D = 0;
1533
1534 // Build an orthonormal reference frame (ux, uy, uz) from three 3D points
1535 QgsPoint ptA;
1536 QgsPoint ptB;
1537 QgsPoint ptC;
1538 if ( !QgsGeometryUtils::checkWeaklyFor3DPlane( this, ptA, ptB, ptC ) )
1539 {
1541 return;
1542 }
1543
1544 QgsVector3D ux( ptB.x() - ptA.x(), ptB.y() - ptA.y(), ptB.z() - ptA.z() );
1545 QgsVector3D uz = QgsVector3D::crossProduct( ux, QgsVector3D( ptC.x() - ptA.x(), ptC.y() - ptA.y(), ptC.z() - ptA.z() ) );
1546 ux.normalize();
1547 uz.normalize();
1549
1550 double normalSign = 1.0;
1551 // Ensure a consistent orientation: prioritize Z+, then Y+, then X+
1552 if ( !qgsDoubleNear( uz.z(), 0.0 ) )
1553 {
1554 if ( uz.z() < 0 )
1555 normalSign = -1.0;
1556 }
1557 else if ( !qgsDoubleNear( uz.y(), 0.0 ) )
1558 {
1559 if ( uz.y() < 0 )
1560 normalSign = -1.0;
1561 }
1562 else
1563 {
1564 if ( uz.x() < 0 )
1565 normalSign = -1.0;
1566 }
1567
1568 // Project points onto the orthonormal plane (ux, uy) and compute 2D sumUpArea
1569 const int nrPoints = numPoints();
1570 QVector<double> projX;
1571 QVector<double> projY;
1572 projX.reserve( nrPoints );
1573 projY.reserve( nrPoints );
1574 for ( int i = 0; i < nrPoints; i++ )
1575 {
1576 const double vecAX = mX[i] - ptA.x();
1577 const double vecAY = mY[i] - ptA.y();
1578 const double vecAZ = mZ[i] - ptA.z();
1579
1580 projX.push_back( vecAX * ux.x() + vecAY * ux.y() + vecAZ * ux.z() );
1581 projY.push_back( vecAX * uy.x() + vecAY * uy.y() + vecAZ * uy.z() );
1582 }
1583
1584 QgsCircularString projectedCurve( projX, projY );
1585 projectedCurve.sumUpArea( mSummedUpArea3D );
1586
1587 // take into account normal sign
1588 mSummedUpArea3D *= normalSign;
1590 sum += mSummedUpArea3D;
1591}
1592
1594{
1595 return true;
1596}
1597
1598double QgsCircularString::closestPointOnArc( double x1, double y1, double x2, double y2, double x3, double y3, const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon )
1599{
1600 double radius, centerX, centerY;
1601 QgsPoint pt1( x1, y1 );
1602 QgsPoint pt2( x2, y2 );
1603 QgsPoint pt3( x3, y3 );
1604
1605 QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
1606 double angle = QgsGeometryUtilsBase::ccwAngle( pt.y() - centerY, pt.x() - centerX );
1607 double angle1 = QgsGeometryUtilsBase::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
1608 double angle2 = QgsGeometryUtilsBase::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
1609 double angle3 = QgsGeometryUtilsBase::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
1610
1611 bool clockwise = QgsGeometryUtilsBase::circleClockwise( angle1, angle2, angle3 );
1612
1613 if ( QgsGeometryUtilsBase::angleOnCircle( angle, angle1, angle2, angle3 ) )
1614 {
1615 //get point on line center -> pt with distance radius
1616 segmentPt = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), pt, radius );
1617
1618 //vertexAfter
1619 vertexAfter.vertex = QgsGeometryUtilsBase::circleAngleBetween( angle, angle1, angle2, clockwise ) ? 1 : 2;
1620 }
1621 else
1622 {
1623 double distPtPt1 = QgsGeometryUtils::sqrDistance2D( pt, pt1 );
1624 double distPtPt3 = QgsGeometryUtils::sqrDistance2D( pt, pt3 );
1625 segmentPt = ( distPtPt1 <= distPtPt3 ) ? pt1 : pt3;
1626 vertexAfter.vertex = ( distPtPt1 <= distPtPt3 ) ? 1 : 2;
1627 }
1628
1629 double sqrDistance = QgsGeometryUtils::sqrDistance2D( segmentPt, pt );
1630 //prevent rounding errors if the point is directly on the segment
1631 if ( qgsDoubleNear( sqrDistance, 0.0, epsilon ) )
1632 {
1633 segmentPt.setX( pt.x() );
1634 segmentPt.setY( pt.y() );
1635 sqrDistance = 0.0;
1636 }
1637
1638 if ( leftOf )
1639 {
1640 double sqrDistancePointToCenter = pt.distanceSquared( centerX, centerY );
1641 *leftOf = clockwise ? ( sqrDistancePointToCenter > radius * radius ? -1 : 1 ) : ( sqrDistancePointToCenter < radius * radius ? -1 : 1 );
1642 }
1643
1644 return sqrDistance;
1645}
1646
1647void QgsCircularString::insertVertexBetween( int after, int before, int pointOnCircle )
1648{
1649 double xAfter = mX.at( after );
1650 double yAfter = mY.at( after );
1651 double xBefore = mX.at( before );
1652 double yBefore = mY.at( before );
1653 double xOnCircle = mX.at( pointOnCircle );
1654 double yOnCircle = mY.at( pointOnCircle );
1655
1656 double radius, centerX, centerY;
1657 QgsGeometryUtils::circleCenterRadius( QgsPoint( xAfter, yAfter ), QgsPoint( xBefore, yBefore ), QgsPoint( xOnCircle, yOnCircle ), radius, centerX, centerY );
1658
1659 double x = ( xAfter + xBefore ) / 2.0;
1660 double y = ( yAfter + yBefore ) / 2.0;
1661
1662 QgsPoint newVertex = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), QgsPoint( x, y ), radius );
1663 mX.insert( before, newVertex.x() );
1664 mY.insert( before, newVertex.y() );
1665
1666 if ( is3D() )
1667 {
1668 mZ.insert( before, ( mZ[after] + mZ[before] ) / 2.0 );
1669 }
1670 if ( isMeasure() )
1671 {
1672 mM.insert( before, ( mM[after] + mM[before] ) / 2.0 );
1673 }
1674 clearCache();
1675}
1676
1678{
1679 if ( numPoints() < 3 )
1680 {
1681 //undefined
1682 return 0.0;
1683 }
1684
1685 int before = vId.vertex - 1;
1686 int vertex = vId.vertex;
1687 int after = vId.vertex + 1;
1688
1689 if ( vId.vertex % 2 != 0 ) // a curve vertex
1690 {
1691 if ( vId.vertex >= 1 && vId.vertex < numPoints() - 1 )
1692 {
1693 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[before], mY[before] ), QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[after], mY[after] ) );
1694 }
1695 }
1696 else //a point vertex
1697 {
1698 if ( vId.vertex == 0 )
1699 {
1700 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[0], mY[0] ), QgsPoint( mX[0], mY[0] ), QgsPoint( mX[1], mY[1] ), QgsPoint( mX[2], mY[2] ) );
1701 }
1702 if ( vId.vertex >= numPoints() - 1 )
1703 {
1704 int a = numPoints() - 3;
1705 int b = numPoints() - 2;
1706 int c = numPoints() - 1;
1707 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[c], mY[c] ), QgsPoint( mX[a], mY[a] ), QgsPoint( mX[b], mY[b] ), QgsPoint( mX[c], mY[c] ) );
1708 }
1709 else
1710 {
1711 if ( vId.vertex + 2 > numPoints() - 1 )
1712 {
1713 return 0.0;
1714 }
1715
1716 int vertex1 = vId.vertex - 2;
1717 int vertex2 = vId.vertex - 1;
1718 int vertex3 = vId.vertex;
1719 double angle1
1720 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex1], mY[vertex1] ), QgsPoint( mX[vertex2], mY[vertex2] ), QgsPoint( mX[vertex3], mY[vertex3] ) );
1721 int vertex4 = vId.vertex + 1;
1722 int vertex5 = vId.vertex + 2;
1723 double angle2
1724 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex4], mY[vertex4] ), QgsPoint( mX[vertex5], mY[vertex5] ) );
1725 return QgsGeometryUtilsBase::averageAngle( angle1, angle2 );
1726 }
1727 }
1728 return 0.0;
1729}
1730
1732{
1733 if ( startVertex.vertex % 2 == 1 )
1734 return 0.0; // curve point?
1735
1736 if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 2 )
1737 return 0.0;
1738
1739 double x1 = mX.at( startVertex.vertex );
1740 double y1 = mY.at( startVertex.vertex );
1741 double x2 = mX.at( startVertex.vertex + 1 );
1742 double y2 = mY.at( startVertex.vertex + 1 );
1743 double x3 = mX.at( startVertex.vertex + 2 );
1744 double y3 = mY.at( startVertex.vertex + 2 );
1745 return QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1746}
1747
1749{
1750 // Ensure fromVertex < toVertex for simplicity
1751 if ( fromVertex.vertex > toVertex.vertex )
1752 {
1753 return distanceBetweenVertices( toVertex, fromVertex );
1754 }
1755
1756 // Convert QgsVertexId to simple vertex numbers for curves (single ring, single part)
1757 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
1758 return -1.0;
1759
1760 const int fromVertexNumber = fromVertex.vertex;
1761 const int toVertexNumber = toVertex.vertex;
1762
1763 const int nPoints = numPoints();
1764 if ( fromVertexNumber < 0 || fromVertexNumber >= nPoints || toVertexNumber < 0 || toVertexNumber >= nPoints )
1765 return -1.0;
1766
1767 if ( fromVertexNumber == toVertexNumber )
1768 return 0.0;
1769
1770 const double *xData = mX.constData();
1771 const double *yData = mY.constData();
1772 double totalDistance = 0.0;
1773
1774 // Start iteration from the arc containing fromVertex
1775 // Each arc starts at an even index (0, 2, 4, ...) and spans 3 vertices
1776 const int startArc = ( fromVertexNumber / 2 ) * 2;
1777
1778 // Iterate through the arcs, accumulating distance between fromVertex and toVertex
1779 for ( int i = startArc; i < nPoints - 2; i += 2 )
1780 {
1781 // Arc segment from i to i+2, with curve point at i+1
1782 double x1 = xData[i]; // Start point
1783 double y1 = yData[i];
1784 double x2 = xData[i + 1]; // Curve point
1785 double y2 = yData[i + 1];
1786 double x3 = xData[i + 2]; // End point
1787 double y3 = yData[i + 2];
1788
1789 // Check if both vertices are in this arc segment
1790 if ( fromVertexNumber >= i && toVertexNumber <= i + 2 )
1791 {
1792 if ( fromVertexNumber == i && toVertexNumber == i + 2 )
1793 {
1794 // Full arc from start to end
1795 return QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1796 }
1797 else if ( fromVertexNumber == i && toVertexNumber == i + 1 )
1798 {
1799 // Arc from start point to curve point
1800 double centerX, centerY, radius;
1801 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1802 // Calculate arc length from vertex 0 to vertex 1
1803 return QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 0, 1 );
1804 }
1805 else if ( fromVertexNumber == i + 1 && toVertexNumber == i + 2 )
1806 {
1807 // Arc from curve point to end point
1808 double centerX, centerY, radius;
1809 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1810 // Calculate arc length from vertex 1 to vertex 2
1811 return QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 1, 2 );
1812 }
1813 else if ( fromVertexNumber == i + 1 && toVertexNumber == i + 1 )
1814 {
1815 return 0.0; // Same point
1816 }
1817 }
1818
1819 // Handle cases where vertices span multiple segments
1820 bool startInThisSegment = ( fromVertexNumber >= i && fromVertexNumber <= i + 2 );
1821 bool endInThisSegment = ( toVertexNumber >= i && toVertexNumber <= i + 2 );
1822 bool segmentInRange = ( fromVertexNumber < i && toVertexNumber > i + 2 );
1823
1824 if ( startInThisSegment && !endInThisSegment )
1825 {
1826 // fromVertex is in this segment, toVertex is beyond
1827 if ( fromVertexNumber == i )
1828 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1829 else if ( fromVertexNumber == i + 1 )
1830 {
1831 // From curve point to end of segment
1832 double centerX, centerY, radius;
1833 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1834 totalDistance += QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 1, 2 );
1835 }
1836 }
1837 else if ( !startInThisSegment && endInThisSegment )
1838 {
1839 // fromVertex is before this segment, toVertex is in this segment
1840 if ( toVertexNumber == i + 1 )
1841 {
1842 // From start of segment to curve point
1843 double centerX, centerY, radius;
1844 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1845 totalDistance += QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 0, 1 );
1846 }
1847 else if ( toVertexNumber == i + 2 )
1848 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1849 break;
1850 }
1851 else if ( segmentInRange )
1852 {
1853 // This entire segment is between fromVertex and toVertex
1854 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1855 }
1856 }
1857
1858 return totalDistance;
1859}
1860
1861
1863{
1864 QgsCircularString *copy = clone();
1865 std::reverse( copy->mX.begin(), copy->mX.end() );
1866 std::reverse( copy->mY.begin(), copy->mY.end() );
1867 if ( is3D() )
1868 {
1869 std::reverse( copy->mZ.begin(), copy->mZ.end() );
1870 }
1871 if ( isMeasure() )
1872 {
1873 std::reverse( copy->mM.begin(), copy->mM.end() );
1874 }
1875
1877 return copy;
1878}
1879
1880QgsPoint *QgsCircularString::interpolatePoint( const double distance ) const
1881{
1882 if ( distance < 0 )
1883 return nullptr;
1884
1885 double distanceTraversed = 0;
1886 const int totalPoints = numPoints();
1887 if ( totalPoints == 0 )
1888 return nullptr;
1889
1891 if ( is3D() )
1892 pointType = Qgis::WkbType::PointZ;
1893 if ( isMeasure() )
1894 pointType = QgsWkbTypes::addM( pointType );
1895
1896 const double *x = mX.constData();
1897 const double *y = mY.constData();
1898 const double *z = is3D() ? mZ.constData() : nullptr;
1899 const double *m = isMeasure() ? mM.constData() : nullptr;
1900
1901 double prevX = *x++;
1902 double prevY = *y++;
1903 double prevZ = z ? *z++ : 0.0;
1904 double prevM = m ? *m++ : 0.0;
1905
1906 if ( qgsDoubleNear( distance, 0.0 ) )
1907 {
1908 return new QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1909 }
1910
1911 for ( int i = 0; i < ( totalPoints - 2 ); i += 2 )
1912 {
1913 double x1 = prevX;
1914 double y1 = prevY;
1915 double z1 = prevZ;
1916 double m1 = prevM;
1917
1918 double x2 = *x++;
1919 double y2 = *y++;
1920 double z2 = z ? *z++ : 0.0;
1921 double m2 = m ? *m++ : 0.0;
1922
1923 double x3 = *x++;
1924 double y3 = *y++;
1925 double z3 = z ? *z++ : 0.0;
1926 double m3 = m ? *m++ : 0.0;
1927
1928 const double segmentLength = QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1929 if ( distance < distanceTraversed + segmentLength || qgsDoubleNear( distance, distanceTraversed + segmentLength ) )
1930 {
1931 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1932 const double distanceToPoint = std::min( distance - distanceTraversed, segmentLength );
1933 return new QgsPoint( QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToPoint ) );
1934 }
1935
1936 distanceTraversed += segmentLength;
1937
1938 prevX = x3;
1939 prevY = y3;
1940 prevZ = z3;
1941 prevM = m3;
1942 }
1943
1944 return nullptr;
1945}
1946
1947QgsCircularString *QgsCircularString::curveSubstring( double startDistance, double endDistance ) const
1948{
1949 if ( startDistance < 0 && endDistance < 0 )
1950 return createEmptyWithSameType();
1951
1952 endDistance = std::max( startDistance, endDistance );
1953
1954 const int totalPoints = numPoints();
1955 if ( totalPoints == 0 )
1956 return clone();
1957
1958 QVector< QgsPoint > substringPoints;
1959 substringPoints.reserve( totalPoints );
1960
1962 if ( is3D() )
1963 pointType = Qgis::WkbType::PointZ;
1964 if ( isMeasure() )
1965 pointType = QgsWkbTypes::addM( pointType );
1966
1967 const double *x = mX.constData();
1968 const double *y = mY.constData();
1969 const double *z = is3D() ? mZ.constData() : nullptr;
1970 const double *m = isMeasure() ? mM.constData() : nullptr;
1971
1972 double distanceTraversed = 0;
1973 double prevX = *x++;
1974 double prevY = *y++;
1975 double prevZ = z ? *z++ : 0.0;
1976 double prevM = m ? *m++ : 0.0;
1977 bool foundStart = false;
1978
1979 if ( startDistance < 0 )
1980 startDistance = 0;
1981
1982 for ( int i = 0; i < ( totalPoints - 2 ); i += 2 )
1983 {
1984 double x1 = prevX;
1985 double y1 = prevY;
1986 double z1 = prevZ;
1987 double m1 = prevM;
1988
1989 double x2 = *x++;
1990 double y2 = *y++;
1991 double z2 = z ? *z++ : 0.0;
1992 double m2 = m ? *m++ : 0.0;
1993
1994 double x3 = *x++;
1995 double y3 = *y++;
1996 double z3 = z ? *z++ : 0.0;
1997 double m3 = m ? *m++ : 0.0;
1998
1999 bool addedSegmentEnd = false;
2000 const double segmentLength = QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
2001 if ( distanceTraversed <= startDistance && startDistance < distanceTraversed + segmentLength )
2002 {
2003 // start point falls on this segment
2004 const double distanceToStart = startDistance - distanceTraversed;
2005 const QgsPoint startPoint
2006 = QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToStart );
2007
2008 // does end point also fall on this segment?
2009 const bool endPointOnSegment = distanceTraversed + segmentLength > endDistance;
2010 if ( endPointOnSegment )
2011 {
2012 const double distanceToEnd = endDistance - distanceTraversed;
2013 const double midPointDistance = ( distanceToEnd - distanceToStart ) * 0.5 + distanceToStart;
2014 substringPoints
2015 << startPoint
2016 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
2017 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
2018 addedSegmentEnd = true;
2019 }
2020 else
2021 {
2022 const double midPointDistance = ( segmentLength - distanceToStart ) * 0.5 + distanceToStart;
2023 substringPoints
2024 << startPoint
2025 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
2026 << QgsPoint( pointType, x3, y3, z3, m3 );
2027 addedSegmentEnd = true;
2028 }
2029 foundStart = true;
2030 }
2031 if ( !addedSegmentEnd && foundStart && ( distanceTraversed + segmentLength > endDistance ) )
2032 {
2033 // end point falls on this segment
2034 const double distanceToEnd = endDistance - distanceTraversed;
2035 // add mid point, at half way along this arc, then add the interpolated end point
2036 substringPoints
2037 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd / 2.0 )
2038
2039 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
2040 }
2041 else if ( !addedSegmentEnd && foundStart )
2042 {
2043 substringPoints << QgsPoint( pointType, x2, y2, z2, m2 ) << QgsPoint( pointType, x3, y3, z3, m3 );
2044 }
2045
2046 prevX = x3;
2047 prevY = y3;
2048 prevZ = z3;
2049 prevM = m3;
2050 distanceTraversed += segmentLength;
2051 if ( distanceTraversed >= endDistance )
2052 break;
2053 }
2054
2055 // start point is the last node
2056 if ( !foundStart && qgsDoubleNear( distanceTraversed, startDistance ) )
2057 {
2058 substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM ) << QgsPoint( pointType, prevX, prevY, prevZ, prevM ) << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
2059 }
2060
2061 auto result = std::make_unique< QgsCircularString >();
2062 result->setPoints( substringPoints );
2063 return result.release();
2064}
2065
2066bool QgsCircularString::addZValue( double zValue )
2067{
2068 if ( QgsWkbTypes::hasZ( mWkbType ) )
2069 return false;
2070
2071 clearCache();
2073
2074 int nPoints = numPoints();
2075 mZ.clear();
2076 mZ.reserve( nPoints );
2077 for ( int i = 0; i < nPoints; ++i )
2078 {
2079 mZ << zValue;
2080 }
2081 return true;
2082}
2083
2084bool QgsCircularString::addMValue( double mValue )
2085{
2086 if ( QgsWkbTypes::hasM( mWkbType ) )
2087 return false;
2088
2089 clearCache();
2091
2092 int nPoints = numPoints();
2093 mM.clear();
2094 mM.reserve( nPoints );
2095 for ( int i = 0; i < nPoints; ++i )
2096 {
2097 mM << mValue;
2098 }
2099 return true;
2100}
2101
2103{
2104 if ( !QgsWkbTypes::hasZ( mWkbType ) )
2105 return false;
2106
2107 clearCache();
2108
2110 mZ.clear();
2111 return true;
2112}
2113
2115{
2116 if ( !QgsWkbTypes::hasM( mWkbType ) )
2117 return false;
2118
2119 clearCache();
2120
2122 mM.clear();
2123 return true;
2124}
2125
2127{
2128 std::swap( mX, mY );
2129 clearCache();
2130}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2196
VertexType
Types of vertex.
Definition qgis.h:3246
@ Curve
An intermediate point on a segment defining the curvature of the segment.
Definition qgis.h:3248
@ Segment
The actual start or end point of a segment.
Definition qgis.h:3247
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ Point
Point.
Definition qgis.h:296
@ PointM
PointM.
Definition qgis.h:329
@ CircularString
CircularString.
Definition qgis.h:304
@ PointZ
PointZ.
Definition qgis.h:313
@ PointZM
PointZM.
Definition qgis.h:345
@ CircularStringZ
CircularStringZ.
Definition qgis.h:321
TransformDirection
Indicates the direction (forward or inverse) of a transform.
Definition qgis.h:2831
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.
QgsVertexIterator vertices() const
Returns a read-only, Java-style iterator for traversal of vertices of all the geometry,...
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:211
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.
bool deleteVertices(const QSet< QgsVertexId > &positions) override
Deletes vertices within 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:211
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:298
double mSummedUpArea3D
Definition qgscurve.h:408
bool mHasCachedSummedUpArea
Definition qgscurve.h:405
bool mHasCachedSummedUpArea3D
Definition qgscurve.h:407
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:247
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:323
bool hasVertex(QgsVertexId position) const override
Returns true if the geometry contains a vertex matching the given position.
Definition qgscurve.cpp:266
double mSummedUpArea
Definition qgscurve.h:406
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:56
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:387
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:376
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:488
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:60
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:62
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58
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:47
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:7222
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:35
int vertex
Vertex number.
int part
Part number.
Definition qgsvertexid.h:94
int ring
Ring number.
Definition qgsvertexid.h:97