QGIS API Documentation 4.1.0-Master (3fcefe620d1)
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 "qgsbox3d.h"
25#include "qgsgeometryutils.h"
27#include "qgslinestring.h"
28#include "qgspoint.h"
29#include "qgspolygon.h"
30#include "qgsrectangle.h"
31
32#include <QJsonObject>
33#include <QPainter>
34#include <QPainterPath>
35#include <QString>
36
37using namespace Qt::StringLiterals;
38
43
45{
46 //get wkb type from first point
47 bool hasZ = p1.is3D();
48 bool hasM = p1.isMeasure();
50
51 mX.resize( 3 );
52 mX[0] = p1.x();
53 mX[1] = p2.x();
54 mX[2] = p3.x();
55 mY.resize( 3 );
56 mY[0] = p1.y();
57 mY[1] = p2.y();
58 mY[2] = p3.y();
59 if ( hasZ )
60 {
62 mZ.resize( 3 );
63 mZ[0] = p1.z();
64 mZ[1] = p2.z();
65 mZ[2] = p3.z();
66 }
67 if ( hasM )
68 {
70 mM.resize( 3 );
71 mM[0] = p1.m();
72 mM[1] = p2.m();
73 mM[2] = p3.m();
74 }
75}
76
77QgsCircularString::QgsCircularString( const QVector<double> &x, const QVector<double> &y, const QVector<double> &z, const QVector<double> &m )
78{
80 int pointCount = std::min( x.size(), y.size() );
81 if ( x.size() == pointCount )
82 {
83 mX = x;
84 }
85 else
86 {
87 mX = x.mid( 0, pointCount );
88 }
89 if ( y.size() == pointCount )
90 {
91 mY = y;
92 }
93 else
94 {
95 mY = y.mid( 0, pointCount );
96 }
97 if ( !z.isEmpty() && z.count() >= pointCount )
98 {
100 if ( z.size() == pointCount )
101 {
102 mZ = z;
103 }
104 else
105 {
106 mZ = z.mid( 0, pointCount );
107 }
108 }
109 if ( !m.isEmpty() && m.count() >= pointCount )
110 {
112 if ( m.size() == pointCount )
113 {
114 mM = m;
115 }
116 else
117 {
118 mM = m.mid( 0, pointCount );
119 }
120 }
121}
122
123QgsCircularString QgsCircularString::fromTwoPointsAndCenter( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &center, const bool useShortestArc )
124{
125 const QgsPoint midPoint = QgsGeometryUtils::segmentMidPointFromCenter( p1, p2, center, useShortestArc );
126 return QgsCircularString( p1, midPoint, p2 );
127}
128
130{
131 auto result = std::make_unique< QgsCircularString >();
132 result->mWkbType = mWkbType;
133 return result.release();
134}
135
137{
138 return u"CircularString"_s;
139}
140
142{
143 return new QgsCircularString( *this );
144}
145
151
153{
154 QgsBox3D bbox;
155 int nPoints = numPoints();
156 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
157 {
158 QgsRectangle box2d = segmentBoundingBox( QgsPoint( mX[i], mY[i] ), QgsPoint( mX[i + 1], mY[i + 1] ), QgsPoint( mX[i + 2], mY[i + 2] ) );
159 double zMin = std::numeric_limits<double>::quiet_NaN();
160 double zMax = std::numeric_limits<double>::quiet_NaN();
161 if ( is3D() )
162 {
163 zMin = *std::min_element( mZ.begin() + i, mZ.begin() + i + 3 );
164 zMax = *std::max_element( mZ.begin() + i, mZ.begin() + i + 3 );
165 }
166 if ( i == 0 )
167 {
168 bbox = QgsBox3D( box2d, zMin, zMax );
169 }
170 else
171 {
172 bbox.combineWith( QgsBox3D( box2d, zMin, zMax ) );
173 }
174 }
175
176 if ( nPoints > 0 && nPoints % 2 == 0 )
177 {
178 double z = std::numeric_limits<double>::quiet_NaN();
179 if ( nPoints == 2 )
180 {
181 if ( is3D() )
182 {
183 z = mZ[0];
184 }
185 bbox.combineWith( mX[0], mY[0], z );
186 }
187 if ( is3D() )
188 {
189 z = mZ[nPoints - 1];
190 }
191 bbox.combineWith( mX[nPoints - 1], mY[nPoints - 1], z );
192 }
193 return bbox;
194}
195
196QgsRectangle QgsCircularString::segmentBoundingBox( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3 )
197{
198 double centerX, centerY, radius;
199 QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
200
201 double p1Angle = QgsGeometryUtilsBase::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
202 double p2Angle = QgsGeometryUtilsBase::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
203 double p3Angle = QgsGeometryUtilsBase::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
204
205 //start point, end point and compass points in between can be on bounding box
206 QgsRectangle bbox( pt1.x(), pt1.y(), pt1.x(), pt1.y() );
207 bbox.combineExtentWith( pt3.x(), pt3.y() );
208
209 QgsPointSequence compassPoints = compassPointsOnSegment( p1Angle, p2Angle, p3Angle, centerX, centerY, radius );
210 QgsPointSequence::const_iterator cpIt = compassPoints.constBegin();
211 for ( ; cpIt != compassPoints.constEnd(); ++cpIt )
212 {
213 bbox.combineExtentWith( cpIt->x(), cpIt->y() );
214 }
215 return bbox;
216}
217
218QgsPointSequence QgsCircularString::compassPointsOnSegment( double p1Angle, double p2Angle, double p3Angle, double centerX, double centerY, double radius )
219{
220 QgsPointSequence pointList;
221
222 QgsPoint nPoint( centerX, centerY + radius );
223 QgsPoint ePoint( centerX + radius, centerY );
224 QgsPoint sPoint( centerX, centerY - radius );
225 QgsPoint wPoint( centerX - radius, centerY );
226
227 if ( p3Angle >= p1Angle )
228 {
229 if ( p2Angle > p1Angle && p2Angle < p3Angle )
230 {
231 if ( p1Angle <= 90 && p3Angle >= 90 )
232 {
233 pointList.append( nPoint );
234 }
235 if ( p1Angle <= 180 && p3Angle >= 180 )
236 {
237 pointList.append( wPoint );
238 }
239 if ( p1Angle <= 270 && p3Angle >= 270 )
240 {
241 pointList.append( sPoint );
242 }
243 }
244 else
245 {
246 pointList.append( ePoint );
247 if ( p1Angle >= 90 || p3Angle <= 90 )
248 {
249 pointList.append( nPoint );
250 }
251 if ( p1Angle >= 180 || p3Angle <= 180 )
252 {
253 pointList.append( wPoint );
254 }
255 if ( p1Angle >= 270 || p3Angle <= 270 )
256 {
257 pointList.append( sPoint );
258 }
259 }
260 }
261 else
262 {
263 if ( p2Angle < p1Angle && p2Angle > p3Angle )
264 {
265 if ( p1Angle >= 270 && p3Angle <= 270 )
266 {
267 pointList.append( sPoint );
268 }
269 if ( p1Angle >= 180 && p3Angle <= 180 )
270 {
271 pointList.append( wPoint );
272 }
273 if ( p1Angle >= 90 && p3Angle <= 90 )
274 {
275 pointList.append( nPoint );
276 }
277 }
278 else
279 {
280 pointList.append( ePoint );
281 if ( p1Angle <= 270 || p3Angle >= 270 )
282 {
283 pointList.append( sPoint );
284 }
285 if ( p1Angle <= 180 || p3Angle >= 180 )
286 {
287 pointList.append( wPoint );
288 }
289 if ( p1Angle <= 90 || p3Angle >= 90 )
290 {
291 pointList.append( nPoint );
292 }
293 }
294 }
295 return pointList;
296}
297
298QDomElement QgsCircularString::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
299{
300 // GML2 does not support curves
301 std::unique_ptr< QgsLineString > line( curveToLine() );
302 QDomElement gml = line->asGml2( doc, precision, ns, axisOrder );
303 return gml;
304}
305
306QDomElement QgsCircularString::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
307{
309 points( pts );
310
311 QDomElement elemCurve = doc.createElementNS( ns, u"Curve"_s );
312
313 if ( isEmpty() )
314 return elemCurve;
315
316 QDomElement elemSegments = doc.createElementNS( ns, u"segments"_s );
317 QDomElement elemArcString = doc.createElementNS( ns, u"ArcString"_s );
318 elemArcString.appendChild( QgsGeometryUtils::pointsToGML3( pts, doc, precision, ns, is3D(), axisOrder ) );
319 elemSegments.appendChild( elemArcString );
320 elemCurve.appendChild( elemSegments );
321 return elemCurve;
322}
323
324
325json QgsCircularString::asJsonObject( int precision ) const
326{
327 // GeoJSON does not support curves
328 std::unique_ptr< QgsLineString > line( curveToLine() );
329 return line->asJsonObject( precision );
330}
331
333{
334 if ( !isEmpty() && ( numPoints() < 3 ) )
335 {
336 error = QObject::tr( "CircularString has less than 3 points and is not empty." );
337 return false;
338 }
339 return QgsCurve::isValid( error, flags );
340}
341
342//curve interface
344{
345 int nPoints = numPoints();
346 double length = 0;
347 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
348 {
349 length += QgsGeometryUtilsBase::circleLength( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2] );
350 }
351 return length;
352}
353
355{
356 QgsLineString *line = new QgsLineString();
358 int nPoints = numPoints();
359
360 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
361 {
362 QgsGeometryUtils::segmentizeArc( pointN( i ), pointN( i + 1 ), pointN( i + 2 ), points, tolerance, toleranceType, is3D(), isMeasure() );
363 }
364
365 line->setPoints( points );
366 return line;
367}
368
369QgsCircularString *QgsCircularString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool ) const
370{
371 // prepare result
372 std::unique_ptr<QgsCircularString> result { createEmptyWithSameType() };
373
374 // remove redundant not supported for circular strings
375 bool res = snapToGridPrivate( hSpacing, vSpacing, dSpacing, mSpacing, mX, mY, mZ, mM, result->mX, result->mY, result->mZ, result->mM, false );
376 if ( res )
377 return result.release();
378 else
379 return nullptr;
380}
381
383{
384 std::unique_ptr< QgsLineString > line( curveToLine() );
385 return line->simplifyByDistance( tolerance );
386}
387
388bool QgsCircularString::removeDuplicateNodes( double epsilon, bool useZValues )
389{
390 if ( mX.count() <= 3 )
391 return false; // don't create degenerate lines
392 bool result = false;
393 double prevX = mX.at( 0 );
394 double prevY = mY.at( 0 );
395 bool hasZ = is3D();
396 bool useZ = hasZ && useZValues;
397 double prevZ = useZ ? mZ.at( 0 ) : 0;
398 int i = 1;
399 int remaining = mX.count();
400 // we have to consider points in pairs, since a segment can validly have the same start and
401 // end if it has a different curve point
402 while ( i + 1 < remaining )
403 {
404 double currentCurveX = mX.at( i );
405 double currentCurveY = mY.at( i );
406 double currentX = mX.at( i + 1 );
407 double currentY = mY.at( i + 1 );
408 double currentZ = useZ ? mZ.at( i + 1 ) : 0;
409 if ( qgsDoubleNear( currentCurveX, prevX, epsilon )
410 && qgsDoubleNear( currentCurveY, prevY, epsilon )
411 && qgsDoubleNear( currentX, prevX, epsilon )
412 && qgsDoubleNear( currentY, prevY, epsilon )
413 && ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
414 {
415 result = true;
416 // remove point
417 mX.removeAt( i );
418 mX.removeAt( i );
419 mY.removeAt( i );
420 mY.removeAt( i );
421 if ( hasZ )
422 {
423 mZ.removeAt( i );
424 mZ.removeAt( i );
425 }
426 remaining -= 2;
427 }
428 else
429 {
430 prevX = currentX;
431 prevY = currentY;
432 prevZ = currentZ;
433 i += 2;
434 }
435 }
436 return result;
437}
438
439int QgsCircularString::indexOf( const QgsPoint &point ) const
440{
441 const int size = mX.size();
442 if ( size == 0 )
443 return -1;
444
445 const double *x = mX.constData();
446 const double *y = mY.constData();
447 const bool useZ = is3D();
448 const bool useM = isMeasure();
449 const double *z = useZ ? mZ.constData() : nullptr;
450 const double *m = useM ? mM.constData() : nullptr;
451
452 for ( int i = 0; i < size; i += 2 )
453 {
454 if ( qgsDoubleNear( *x, point.x() ) && qgsDoubleNear( *y, point.y() ) && ( !useZ || qgsDoubleNear( *z, point.z() ) ) && ( !useM || qgsDoubleNear( *m, point.m() ) ) )
455 return i;
456
457 // we skip over curve points!
458 x++;
459 x++;
460 y++;
461 y++;
462 if ( useZ )
463 {
464 z++;
465 z++;
466 }
467 if ( useM )
468 {
469 m++;
470 m++;
471 }
472 }
473 return -1;
474}
475
476std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsCircularString::splitCurveAtVertex( int index ) const
477{
478 QVector< double > x1, y1, z1, m1;
479 QVector< double > x2, y2, z2, m2;
480 QgsSimpleCurve::splitCurveAtVertexProtected( index, x1, y1, z1, m1, x2, y2, z2, m2 );
481
482 std::unique_ptr< QgsCircularString > first;
483 if ( x1.isEmpty() || ( x1.size() < 2 && x2.size() >= 2 ) )
484 first = std::make_unique< QgsCircularString >();
485 else
486 first = std::make_unique< QgsCircularString >( x1, y1, z1, m1 );
487
488 std::unique_ptr< QgsCircularString > second;
489 if ( x2.isEmpty() || x2.size() < 2 )
490 second = std::make_unique< QgsCircularString >();
491 else
492 second = std::make_unique< QgsCircularString >( x2, y2, z2, m2 );
493
494 return std::make_tuple( std::move( first ), std::move( second ) );
495}
496
497void QgsCircularString::draw( QPainter &p ) const
498{
499 QPainterPath path;
500 addToPainterPath( path );
501 p.drawPath( path );
502}
503
504void arcTo( QPainterPath &path, QPointF pt1, QPointF pt2, QPointF pt3 )
505{
506 double centerX, centerY, radius;
507 QgsGeometryUtils::circleCenterRadius( QgsPoint( pt1.x(), pt1.y() ), QgsPoint( pt2.x(), pt2.y() ), QgsPoint( pt3.x(), pt3.y() ), radius, centerX, centerY );
508
509 double p1Angle = QgsGeometryUtilsBase::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
510 double sweepAngle = QgsGeometryUtilsBase::sweepAngle( centerX, centerY, pt1.x(), pt1.y(), pt2.x(), pt2.y(), pt3.x(), pt3.y() );
511
512 double diameter = 2 * radius;
513 path.arcTo( centerX - radius, centerY - radius, diameter, diameter, -p1Angle, -sweepAngle );
514}
515
516void QgsCircularString::addToPainterPath( QPainterPath &path ) const
517{
518 int nPoints = numPoints();
519 if ( nPoints < 1 )
520 {
521 return;
522 }
523
524 if ( path.isEmpty() || path.currentPosition() != QPointF( mX[0], mY[0] ) )
525 {
526 path.moveTo( QPointF( mX[0], mY[0] ) );
527 }
528
529 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
530 {
531 arcTo( path, QPointF( mX[i], mY[i] ), QPointF( mX[i + 1], mY[i + 1] ), QPointF( mX[i + 2], mY[i + 2] ) );
532 }
533
534 //if number of points is even, connect to last point with straight line (even though the circular string is not valid)
535 if ( nPoints % 2 == 0 )
536 {
537 path.lineTo( mX[nPoints - 1], mY[nPoints - 1] );
538 }
539}
540
541void QgsCircularString::drawAsPolygon( QPainter &p ) const
542{
543 draw( p );
544}
545
547{
548 if ( position.vertex >= mX.size() || position.vertex < 1 )
549 {
550 return false;
551 }
552
553 mX.insert( position.vertex, vertex.x() );
554 mY.insert( position.vertex, vertex.y() );
555 if ( is3D() )
556 {
557 mZ.insert( position.vertex, vertex.z() );
558 }
559 if ( isMeasure() )
560 {
561 mM.insert( position.vertex, vertex.m() );
562 }
563
564 bool vertexNrEven = ( position.vertex % 2 == 0 );
565 if ( vertexNrEven )
566 {
567 insertVertexBetween( position.vertex - 2, position.vertex - 1, position.vertex );
568 }
569 else
570 {
571 insertVertexBetween( position.vertex, position.vertex + 1, position.vertex - 1 );
572 }
573 clearCache(); //set bounding box invalid
574 return true;
575}
576
578{
579 int nVertices = this->numPoints();
580
581 if ( position.vertex < 0 || position.vertex > ( nVertices - 1 ) )
582 {
583 return false;
584 }
585
586 if ( nVertices < 4 ) //circular string must have at least 3 vertices
587 {
588 clear();
589 return true;
590 }
591
592 if ( position.vertex < ( nVertices - 2 ) )
593 {
594 //remove this and the following vertex
595 deleteVertex( position.vertex + 1 );
596 deleteVertex( position.vertex );
597 }
598 else //remove this and the preceding vertex
599 {
600 deleteVertex( position.vertex );
601 deleteVertex( position.vertex - 1 );
602 }
603
604 clearCache(); //set bounding box invalid
605 return true;
606}
607
609{
610 mX.remove( i );
611 mY.remove( i );
612 if ( is3D() )
613 {
614 mZ.remove( i );
615 }
616 if ( isMeasure() )
617 {
618 mM.remove( i );
619 }
620 clearCache();
621}
622
623bool QgsCircularString::deleteVertices( const QSet<QgsVertexId> &positions )
624{
625 if ( positions.empty() )
626 {
627 return false;
628 }
629
630 for ( QgsVertexId pos : positions )
631 {
632 if ( !hasVertex( pos ) )
633 {
634 return false;
635 }
636 }
637
638 int nVertices = this->numPoints();
639
640 QList<QgsVertexId> vertices( positions.begin(), positions.end() );
641
642 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex < b.vertex; } );
643
644 // remove adjacent vertices as deleting one will also delete the other
645 for ( size_t i = vertices.size() - 1; i >= 1; i-- )
646 {
647 int vertexNr = vertices[i].vertex;
648 int prevVertexNr = vertices[i - 1].vertex;
649
650 if ( vertexNr - 1 == prevVertexNr )
651 {
652 if ( vertexNr < nVertices - 2 )
653 {
654 vertices.removeAt( i );
655 }
656 else
657 {
658 vertices.removeAt( i - 1 );
659 }
660
661 i--; // adjacent vertices handled, we can skip the next one as well
662
663 if ( i == 0 )
664 break;
665 }
666 }
667
668 // this check cannot be moved further up, we need to check adjacent vertices first
669 if ( nVertices - vertices.size() * 2 < 3 )
670 {
671 clear();
672 return true;
673 }
674
675 QListIterator<QgsVertexId> positionsIt( vertices );
676 positionsIt.toBack();
677 while ( positionsIt.hasPrevious() )
678 {
679 int currentVertexNr = positionsIt.previous().vertex;
680
681 if ( currentVertexNr < nVertices - 2 )
682 {
683 deleteVertex( currentVertexNr + 1 );
684 deleteVertex( currentVertexNr );
685 }
686 else
687 {
688 deleteVertex( currentVertexNr );
689 deleteVertex( currentVertexNr - 1 );
690 }
691 nVertices -= 2;
692 }
693
694 return true;
695}
696
697double QgsCircularString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
698{
699 double minDist = std::numeric_limits<double>::max();
700 QgsPoint minDistSegmentPoint;
701 QgsVertexId minDistVertexAfter;
702 int minDistLeftOf = 0;
703
704 double currentDist = 0.0;
705
706 int nPoints = numPoints();
707 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
708 {
709 currentDist = closestPointOnArc( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2], pt, segmentPt, vertexAfter, leftOf, epsilon );
710 if ( currentDist < minDist )
711 {
712 minDist = currentDist;
713 minDistSegmentPoint = segmentPt;
714 minDistVertexAfter.vertex = vertexAfter.vertex + i;
715 if ( leftOf )
716 {
717 minDistLeftOf = *leftOf;
718 }
719 }
720 }
721
722 if ( minDist == std::numeric_limits<double>::max() )
723 return -1; // error: no segments
724
725 segmentPt = minDistSegmentPoint;
726 vertexAfter = minDistVertexAfter;
727 vertexAfter.part = 0;
728 vertexAfter.ring = 0;
729 if ( leftOf )
730 {
731 *leftOf = qgsDoubleNear( minDist, 0.0 ) ? 0 : minDistLeftOf;
732 }
733 return minDist;
734}
735
736bool QgsCircularString::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
737{
738 if ( node < 0 || node >= numPoints() )
739 {
740 return false;
741 }
742 point = pointN( node );
743 type = ( node % 2 == 0 ) ? Qgis::VertexType::Segment : Qgis::VertexType::Curve;
744 return true;
745}
746
747void QgsCircularString::sumUpArea( double &sum ) const
748{
750 {
751 sum += mSummedUpArea;
752 return;
753 }
754
755 int maxIndex = numPoints() - 2;
756 mSummedUpArea = 0;
757 for ( int i = 0; i < maxIndex; i += 2 )
758 {
759 QgsPoint p1( mX[i], mY[i] );
760 QgsPoint p2( mX[i + 1], mY[i + 1] );
761 QgsPoint p3( mX[i + 2], mY[i + 2] );
762
763 //segment is a full circle, p2 is the center point
764 if ( p1 == p3 )
765 {
766 double r2 = QgsGeometryUtils::sqrDistance2D( p1, p2 ) / 4.0;
767 mSummedUpArea += M_PI * r2;
768 continue;
769 }
770
771 mSummedUpArea += 0.5 * ( mX[i] * mY[i + 2] - mY[i] * mX[i + 2] );
772
773 //calculate area between circle and chord, then sum / subtract from total area
774 double midPointX = ( p1.x() + p3.x() ) / 2.0;
775 double midPointY = ( p1.y() + p3.y() ) / 2.0;
776
777 double radius, centerX, centerY;
778 QgsGeometryUtils::circleCenterRadius( p1, p2, p3, radius, centerX, centerY );
779
780 double d = std::sqrt( QgsGeometryUtils::sqrDistance2D( QgsPoint( centerX, centerY ), QgsPoint( midPointX, midPointY ) ) );
781 double r2 = radius * radius;
782
783 if ( d > radius )
784 {
785 //d cannot be greater than radius, something must be wrong...
786 continue;
787 }
788
789 bool circlePointLeftOfLine = QgsGeometryUtilsBase::leftOfLine( p2.x(), p2.y(), p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
790 bool centerPointLeftOfLine = QgsGeometryUtilsBase::leftOfLine( centerX, centerY, p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
791
792 double cov = 0.5 - d * std::sqrt( r2 - d * d ) / ( M_PI * r2 ) - M_1_PI * std::asin( d / radius );
793 double circleChordArea = 0;
794 if ( circlePointLeftOfLine == centerPointLeftOfLine )
795 {
796 circleChordArea = M_PI * r2 * ( 1 - cov );
797 }
798 else
799 {
800 circleChordArea = M_PI * r2 * cov;
801 }
802
803 if ( !circlePointLeftOfLine )
804 {
805 mSummedUpArea += circleChordArea;
806 }
807 else
808 {
809 mSummedUpArea -= circleChordArea;
810 }
811 }
812
814 sum += mSummedUpArea;
815}
816
817void QgsCircularString::sumUpArea3D( double &sum ) const
818{
820 {
821 sum += mSummedUpArea3D;
822 return;
823 }
824
825 // No Z component. Fallback to the 2D version
826 if ( mZ.isEmpty() )
827 {
828 double area2D = 0;
829 sumUpArea( area2D );
830 mSummedUpArea3D = area2D;
832 sum += mSummedUpArea3D;
833 return;
834 }
835
836 // FIXME: Implement proper 3D shoelace formula for circular strings
837 // workaround: project points to 2D plane and apply standard 2D shoelace formula
838 mSummedUpArea3D = 0;
839
840 // Build an orthonormal reference frame (ux, uy, uz) from three 3D points
841 QgsPoint ptA;
842 QgsPoint ptB;
843 QgsPoint ptC;
844 if ( !QgsGeometryUtils::checkWeaklyFor3DPlane( this, ptA, ptB, ptC ) )
845 {
847 return;
848 }
849
850 QgsVector3D ux( ptB.x() - ptA.x(), ptB.y() - ptA.y(), ptB.z() - ptA.z() );
851 QgsVector3D uz = QgsVector3D::crossProduct( ux, QgsVector3D( ptC.x() - ptA.x(), ptC.y() - ptA.y(), ptC.z() - ptA.z() ) );
852 ux.normalize();
853 uz.normalize();
855
856 double normalSign = 1.0;
857 // Ensure a consistent orientation: prioritize Z+, then Y+, then X+
858 if ( !qgsDoubleNear( uz.z(), 0.0 ) )
859 {
860 if ( uz.z() < 0 )
861 normalSign = -1.0;
862 }
863 else if ( !qgsDoubleNear( uz.y(), 0.0 ) )
864 {
865 if ( uz.y() < 0 )
866 normalSign = -1.0;
867 }
868 else
869 {
870 if ( uz.x() < 0 )
871 normalSign = -1.0;
872 }
873
874 // Project points onto the orthonormal plane (ux, uy) and compute 2D sumUpArea
875 const int nrPoints = numPoints();
876 QVector<double> projX;
877 QVector<double> projY;
878 projX.reserve( nrPoints );
879 projY.reserve( nrPoints );
880 for ( int i = 0; i < nrPoints; i++ )
881 {
882 const double vecAX = mX[i] - ptA.x();
883 const double vecAY = mY[i] - ptA.y();
884 const double vecAZ = mZ[i] - ptA.z();
885
886 projX.push_back( vecAX * ux.x() + vecAY * ux.y() + vecAZ * ux.z() );
887 projY.push_back( vecAX * uy.x() + vecAY * uy.y() + vecAZ * uy.z() );
888 }
889
890 QgsCircularString projectedCurve( projX, projY );
891 projectedCurve.sumUpArea( mSummedUpArea3D );
892
893 // take into account normal sign
894 mSummedUpArea3D *= normalSign;
896 sum += mSummedUpArea3D;
897}
898
900{
901 return true;
902}
903
904double 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 )
905{
906 double radius, centerX, centerY;
907 QgsPoint pt1( x1, y1 );
908 QgsPoint pt2( x2, y2 );
909 QgsPoint pt3( x3, y3 );
910
911 QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
912 double angle = QgsGeometryUtilsBase::ccwAngle( pt.y() - centerY, pt.x() - centerX );
913 double angle1 = QgsGeometryUtilsBase::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
914 double angle2 = QgsGeometryUtilsBase::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
915 double angle3 = QgsGeometryUtilsBase::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
916
917 bool clockwise = QgsGeometryUtilsBase::circleClockwise( angle1, angle2, angle3 );
918
919 if ( QgsGeometryUtilsBase::angleOnCircle( angle, angle1, angle2, angle3 ) )
920 {
921 //get point on line center -> pt with distance radius
922 segmentPt = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), pt, radius );
923
924 //vertexAfter
925 vertexAfter.vertex = QgsGeometryUtilsBase::circleAngleBetween( angle, angle1, angle2, clockwise ) ? 1 : 2;
926 }
927 else
928 {
929 double distPtPt1 = QgsGeometryUtils::sqrDistance2D( pt, pt1 );
930 double distPtPt3 = QgsGeometryUtils::sqrDistance2D( pt, pt3 );
931 segmentPt = ( distPtPt1 <= distPtPt3 ) ? pt1 : pt3;
932 vertexAfter.vertex = ( distPtPt1 <= distPtPt3 ) ? 1 : 2;
933 }
934
935 double sqrDistance = QgsGeometryUtils::sqrDistance2D( segmentPt, pt );
936 //prevent rounding errors if the point is directly on the segment
937 if ( qgsDoubleNear( sqrDistance, 0.0, epsilon ) )
938 {
939 segmentPt.setX( pt.x() );
940 segmentPt.setY( pt.y() );
941 sqrDistance = 0.0;
942 }
943
944 if ( leftOf )
945 {
946 double sqrDistancePointToCenter = pt.distanceSquared( centerX, centerY );
947 *leftOf = clockwise ? ( sqrDistancePointToCenter > radius * radius ? -1 : 1 ) : ( sqrDistancePointToCenter < radius * radius ? -1 : 1 );
948 }
949
950 return sqrDistance;
951}
952
953void QgsCircularString::insertVertexBetween( int after, int before, int pointOnCircle )
954{
955 double xAfter = mX.at( after );
956 double yAfter = mY.at( after );
957 double xBefore = mX.at( before );
958 double yBefore = mY.at( before );
959 double xOnCircle = mX.at( pointOnCircle );
960 double yOnCircle = mY.at( pointOnCircle );
961
962 double radius, centerX, centerY;
963 QgsGeometryUtils::circleCenterRadius( QgsPoint( xAfter, yAfter ), QgsPoint( xBefore, yBefore ), QgsPoint( xOnCircle, yOnCircle ), radius, centerX, centerY );
964
965 double x = ( xAfter + xBefore ) / 2.0;
966 double y = ( yAfter + yBefore ) / 2.0;
967
968 QgsPoint newVertex = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), QgsPoint( x, y ), radius );
969 mX.insert( before, newVertex.x() );
970 mY.insert( before, newVertex.y() );
971
972 if ( is3D() )
973 {
974 mZ.insert( before, ( mZ[after] + mZ[before] ) / 2.0 );
975 }
976 if ( isMeasure() )
977 {
978 mM.insert( before, ( mM[after] + mM[before] ) / 2.0 );
979 }
980 clearCache();
981}
982
984{
985 if ( numPoints() < 3 )
986 {
987 //undefined
988 return 0.0;
989 }
990
991 int before = vId.vertex - 1;
992 int vertex = vId.vertex;
993 int after = vId.vertex + 1;
994
995 if ( vId.vertex % 2 != 0 ) // a curve vertex
996 {
997 if ( vId.vertex >= 1 && vId.vertex < numPoints() - 1 )
998 {
999 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[before], mY[before] ), QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[after], mY[after] ) );
1000 }
1001 }
1002 else //a point vertex
1003 {
1004 if ( vId.vertex == 0 )
1005 {
1006 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[0], mY[0] ), QgsPoint( mX[0], mY[0] ), QgsPoint( mX[1], mY[1] ), QgsPoint( mX[2], mY[2] ) );
1007 }
1008 if ( vId.vertex >= numPoints() - 1 )
1009 {
1010 int a = numPoints() - 3;
1011 int b = numPoints() - 2;
1012 int c = numPoints() - 1;
1013 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[c], mY[c] ), QgsPoint( mX[a], mY[a] ), QgsPoint( mX[b], mY[b] ), QgsPoint( mX[c], mY[c] ) );
1014 }
1015 else
1016 {
1017 if ( vId.vertex + 2 > numPoints() - 1 )
1018 {
1019 return 0.0;
1020 }
1021
1022 int vertex1 = vId.vertex - 2;
1023 int vertex2 = vId.vertex - 1;
1024 int vertex3 = vId.vertex;
1025 double angle1
1026 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex1], mY[vertex1] ), QgsPoint( mX[vertex2], mY[vertex2] ), QgsPoint( mX[vertex3], mY[vertex3] ) );
1027 int vertex4 = vId.vertex + 1;
1028 int vertex5 = vId.vertex + 2;
1029 double angle2
1030 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex4], mY[vertex4] ), QgsPoint( mX[vertex5], mY[vertex5] ) );
1031 return QgsGeometryUtilsBase::averageAngle( angle1, angle2 );
1032 }
1033 }
1034 return 0.0;
1035}
1036
1038{
1039 if ( startVertex.vertex % 2 == 1 )
1040 return 0.0; // curve point?
1041
1042 if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 2 )
1043 return 0.0;
1044
1045 double x1 = mX.at( startVertex.vertex );
1046 double y1 = mY.at( startVertex.vertex );
1047 double x2 = mX.at( startVertex.vertex + 1 );
1048 double y2 = mY.at( startVertex.vertex + 1 );
1049 double x3 = mX.at( startVertex.vertex + 2 );
1050 double y3 = mY.at( startVertex.vertex + 2 );
1051 return QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1052}
1053
1055{
1056 // Ensure fromVertex < toVertex for simplicity
1057 if ( fromVertex.vertex > toVertex.vertex )
1058 {
1059 return distanceBetweenVertices( toVertex, fromVertex );
1060 }
1061
1062 // Convert QgsVertexId to simple vertex numbers for curves (single ring, single part)
1063 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
1064 return -1.0;
1065
1066 const int fromVertexNumber = fromVertex.vertex;
1067 const int toVertexNumber = toVertex.vertex;
1068
1069 const int nPoints = numPoints();
1070 if ( fromVertexNumber < 0 || fromVertexNumber >= nPoints || toVertexNumber < 0 || toVertexNumber >= nPoints )
1071 return -1.0;
1072
1073 if ( fromVertexNumber == toVertexNumber )
1074 return 0.0;
1075
1076 const double *xData = mX.constData();
1077 const double *yData = mY.constData();
1078 double totalDistance = 0.0;
1079
1080 // Start iteration from the arc containing fromVertex
1081 // Each arc starts at an even index (0, 2, 4, ...) and spans 3 vertices
1082 const int startArc = ( fromVertexNumber / 2 ) * 2;
1083
1084 // Iterate through the arcs, accumulating distance between fromVertex and toVertex
1085 for ( int i = startArc; i < nPoints - 2; i += 2 )
1086 {
1087 // Arc segment from i to i+2, with curve point at i+1
1088 double x1 = xData[i]; // Start point
1089 double y1 = yData[i];
1090 double x2 = xData[i + 1]; // Curve point
1091 double y2 = yData[i + 1];
1092 double x3 = xData[i + 2]; // End point
1093 double y3 = yData[i + 2];
1094
1095 // Check if both vertices are in this arc segment
1096 if ( fromVertexNumber >= i && toVertexNumber <= i + 2 )
1097 {
1098 if ( fromVertexNumber == i && toVertexNumber == i + 2 )
1099 {
1100 // Full arc from start to end
1101 return QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1102 }
1103 else if ( fromVertexNumber == i && toVertexNumber == i + 1 )
1104 {
1105 // Arc from start point to curve point
1106 double centerX, centerY, radius;
1107 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1108 // Calculate arc length from vertex 0 to vertex 1
1109 return QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 0, 1 );
1110 }
1111 else if ( fromVertexNumber == i + 1 && toVertexNumber == i + 2 )
1112 {
1113 // Arc from curve point to end point
1114 double centerX, centerY, radius;
1115 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1116 // Calculate arc length from vertex 1 to vertex 2
1117 return QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 1, 2 );
1118 }
1119 else if ( fromVertexNumber == i + 1 && toVertexNumber == i + 1 )
1120 {
1121 return 0.0; // Same point
1122 }
1123 }
1124
1125 // Handle cases where vertices span multiple segments
1126 bool startInThisSegment = ( fromVertexNumber >= i && fromVertexNumber <= i + 2 );
1127 bool endInThisSegment = ( toVertexNumber >= i && toVertexNumber <= i + 2 );
1128 bool segmentInRange = ( fromVertexNumber < i && toVertexNumber > i + 2 );
1129
1130 if ( startInThisSegment && !endInThisSegment )
1131 {
1132 // fromVertex is in this segment, toVertex is beyond
1133 if ( fromVertexNumber == i )
1134 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1135 else if ( fromVertexNumber == i + 1 )
1136 {
1137 // From curve point to end of segment
1138 double centerX, centerY, radius;
1139 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1140 totalDistance += QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 1, 2 );
1141 }
1142 }
1143 else if ( !startInThisSegment && endInThisSegment )
1144 {
1145 // fromVertex is before this segment, toVertex is in this segment
1146 if ( toVertexNumber == i + 1 )
1147 {
1148 // From start of segment to curve point
1149 double centerX, centerY, radius;
1150 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1151 totalDistance += QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 0, 1 );
1152 }
1153 else if ( toVertexNumber == i + 2 )
1154 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1155 break;
1156 }
1157 else if ( segmentInRange )
1158 {
1159 // This entire segment is between fromVertex and toVertex
1160 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1161 }
1162 }
1163
1164 return totalDistance;
1165}
1166
1167
1169{
1170 return qgis::down_cast< QgsCircularString *>( QgsSimpleCurve::reversed() );
1171}
1172
1173QgsPoint *QgsCircularString::interpolatePoint( const double distance ) const
1174{
1175 if ( distance < 0 )
1176 return nullptr;
1177
1178 double distanceTraversed = 0;
1179 const int totalPoints = numPoints();
1180 if ( totalPoints == 0 )
1181 return nullptr;
1182
1184 if ( is3D() )
1185 pointType = Qgis::WkbType::PointZ;
1186 if ( isMeasure() )
1187 pointType = QgsWkbTypes::addM( pointType );
1188
1189 const double *x = mX.constData();
1190 const double *y = mY.constData();
1191 const double *z = is3D() ? mZ.constData() : nullptr;
1192 const double *m = isMeasure() ? mM.constData() : nullptr;
1193
1194 double prevX = *x++;
1195 double prevY = *y++;
1196 double prevZ = z ? *z++ : 0.0;
1197 double prevM = m ? *m++ : 0.0;
1198
1199 if ( qgsDoubleNear( distance, 0.0 ) )
1200 {
1201 return new QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1202 }
1203
1204 for ( int i = 0; i < ( totalPoints - 2 ); i += 2 )
1205 {
1206 double x1 = prevX;
1207 double y1 = prevY;
1208 double z1 = prevZ;
1209 double m1 = prevM;
1210
1211 double x2 = *x++;
1212 double y2 = *y++;
1213 double z2 = z ? *z++ : 0.0;
1214 double m2 = m ? *m++ : 0.0;
1215
1216 double x3 = *x++;
1217 double y3 = *y++;
1218 double z3 = z ? *z++ : 0.0;
1219 double m3 = m ? *m++ : 0.0;
1220
1221 const double segmentLength = QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1222 if ( distance < distanceTraversed + segmentLength || qgsDoubleNear( distance, distanceTraversed + segmentLength ) )
1223 {
1224 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1225 const double distanceToPoint = std::min( distance - distanceTraversed, segmentLength );
1226 return new QgsPoint( QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToPoint ) );
1227 }
1228
1229 distanceTraversed += segmentLength;
1230
1231 prevX = x3;
1232 prevY = y3;
1233 prevZ = z3;
1234 prevM = m3;
1235 }
1236
1237 return nullptr;
1238}
1239
1240QgsCircularString *QgsCircularString::curveSubstring( double startDistance, double endDistance ) const
1241{
1242 if ( startDistance < 0 && endDistance < 0 )
1243 return createEmptyWithSameType();
1244
1245 endDistance = std::max( startDistance, endDistance );
1246
1247 const int totalPoints = numPoints();
1248 if ( totalPoints == 0 )
1249 return clone();
1250
1251 QVector< QgsPoint > substringPoints;
1252 substringPoints.reserve( totalPoints );
1253
1255 if ( is3D() )
1256 pointType = Qgis::WkbType::PointZ;
1257 if ( isMeasure() )
1258 pointType = QgsWkbTypes::addM( pointType );
1259
1260 const double *x = mX.constData();
1261 const double *y = mY.constData();
1262 const double *z = is3D() ? mZ.constData() : nullptr;
1263 const double *m = isMeasure() ? mM.constData() : nullptr;
1264
1265 double distanceTraversed = 0;
1266 double prevX = *x++;
1267 double prevY = *y++;
1268 double prevZ = z ? *z++ : 0.0;
1269 double prevM = m ? *m++ : 0.0;
1270 bool foundStart = false;
1271
1272 if ( startDistance < 0 )
1273 startDistance = 0;
1274
1275 for ( int i = 0; i < ( totalPoints - 2 ); i += 2 )
1276 {
1277 double x1 = prevX;
1278 double y1 = prevY;
1279 double z1 = prevZ;
1280 double m1 = prevM;
1281
1282 double x2 = *x++;
1283 double y2 = *y++;
1284 double z2 = z ? *z++ : 0.0;
1285 double m2 = m ? *m++ : 0.0;
1286
1287 double x3 = *x++;
1288 double y3 = *y++;
1289 double z3 = z ? *z++ : 0.0;
1290 double m3 = m ? *m++ : 0.0;
1291
1292 bool addedSegmentEnd = false;
1293 const double segmentLength = QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1294 if ( distanceTraversed <= startDistance && startDistance < distanceTraversed + segmentLength )
1295 {
1296 // start point falls on this segment
1297 const double distanceToStart = startDistance - distanceTraversed;
1298 const QgsPoint startPoint
1299 = QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToStart );
1300
1301 // does end point also fall on this segment?
1302 const bool endPointOnSegment = distanceTraversed + segmentLength > endDistance;
1303 if ( endPointOnSegment )
1304 {
1305 const double distanceToEnd = endDistance - distanceTraversed;
1306 const double midPointDistance = ( distanceToEnd - distanceToStart ) * 0.5 + distanceToStart;
1307 substringPoints
1308 << startPoint
1309 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1310 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1311 addedSegmentEnd = true;
1312 }
1313 else
1314 {
1315 const double midPointDistance = ( segmentLength - distanceToStart ) * 0.5 + distanceToStart;
1316 substringPoints
1317 << startPoint
1318 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1319 << QgsPoint( pointType, x3, y3, z3, m3 );
1320 addedSegmentEnd = true;
1321 }
1322 foundStart = true;
1323 }
1324 if ( !addedSegmentEnd && foundStart && ( distanceTraversed + segmentLength > endDistance ) )
1325 {
1326 // end point falls on this segment
1327 const double distanceToEnd = endDistance - distanceTraversed;
1328 // add mid point, at half way along this arc, then add the interpolated end point
1329 substringPoints
1330 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd / 2.0 )
1331
1332 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1333 }
1334 else if ( !addedSegmentEnd && foundStart )
1335 {
1336 substringPoints << QgsPoint( pointType, x2, y2, z2, m2 ) << QgsPoint( pointType, x3, y3, z3, m3 );
1337 }
1338
1339 prevX = x3;
1340 prevY = y3;
1341 prevZ = z3;
1342 prevM = m3;
1343 distanceTraversed += segmentLength;
1344 if ( distanceTraversed >= endDistance )
1345 break;
1346 }
1347
1348 // start point is the last node
1349 if ( !foundStart && qgsDoubleNear( distanceTraversed, startDistance ) )
1350 {
1351 substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM ) << QgsPoint( pointType, prevX, prevY, prevZ, prevM ) << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1352 }
1353
1354 auto result = std::make_unique< QgsCircularString >();
1355 result->setPoints( substringPoints );
1356 return result.release();
1357}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2197
VertexType
Types of vertex.
Definition qgis.h:3247
@ Curve
An intermediate point on a segment defining the curvature of the segment.
Definition qgis.h:3249
@ Segment
The actual start or end point of a segment.
Definition qgis.h:3248
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ Point
Point.
Definition qgis.h:296
@ CircularString
CircularString.
Definition qgis.h:304
@ PointZ
PointZ.
Definition qgis.h:313
@ CircularStringZ
CircularStringZ.
Definition qgis.h:321
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.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
AxisOrder
Axis order for GML generation.
QgsAbstractGeometry()=default
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.
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
QgsCircularString * clone() const override
Clones the geometry by performing a deep copy.
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.
double distanceBetweenVertices(QgsVertexId fromVertex, QgsVertexId toVertex) const override
Returns the distance along the curve between two vertices.
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...
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...
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.
bool pointAt(int node, QgsPoint &point, Qgis::VertexType &type) const override
Returns the point and vertex type of a point within the curve.
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...
void addToPainterPath(QPainterPath &path) const override
Adds a curve to a painter path.
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.
bool deleteVertices(const QSet< QgsVertexId > &positions) override
Deletes vertices within the geometry.
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.
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.
void clear() override
Clears the geometry, ie reset it to a null geometry.
bool hasCurvedSegments() const override
Returns true if the geometry contains curved segments.
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.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into the geometry.
QgsCircularString()
Constructs an empty 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...
json asJsonObject(int precision=17) const override
Returns a json object representation of the geometry.
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
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 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 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 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.
QVector< double > mM
int numPoints() const override
Returns the number of points in the curve.
bool isEmpty() const override
Returns true if the geometry is empty.
void splitCurveAtVertexProtected(int index, QVector< double > &x1, QVector< double > &y1, QVector< double > &z1, QVector< double > &m1, QVector< double > &x2, QVector< double > &y2, QVector< double > &z2, QVector< double > &m2) const
Returns coordinate vectors for the split curves.
void points(QgsPointSequence &pts) const override
Returns a list of points within the curve.
void clear() override
Clears the geometry, ie reset it to a null geometry.
QVector< double > mZ
const double * yData() const
Returns a const pointer to the y vertex data.
QgsPoint startPoint() const override
Returns the starting point of the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the simple curve.
QVector< double > mX
QgsSimpleCurve * reversed() const override
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
const double * xData() const
Returns a const pointer to the x vertex data.
QVector< double > mY
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.
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.
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:7340
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