QGIS API Documentation 4.3.0-Master (58212645307)
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, Qgis::GeoJsonProfile profile ) const
326{
327 switch ( profile )
328 {
331 {
332 std::unique_ptr< QgsLineString > line( curveToLine() );
333 return line->asJsonObject( precision, profile );
334 }
337 {
339 points( pts );
340 // Work around the 11 points limit
341 constexpr int MAX_POINT = 11;
342 if ( pts.size() > MAX_POINT )
343 {
344 json geometries = json::array();
345 json subCs = { { "type", "CircularString" } };
346 subCs["coordinates"] = json::array();
347 for ( int i = 0; i < pts.size(); i++ )
348 {
349 const json pointCoords = pts[i].asJsonObject( precision, profile )["coordinates"];
350 subCs["coordinates"].push_back( pointCoords );
351 if ( subCs["coordinates"].size() == MAX_POINT )
352 {
353 geometries.push_back( subCs );
354 // Clear array
355 subCs["coordinates"] = json::array();
356 subCs["coordinates"].push_back( pointCoords );
357 }
358 }
359 // Last one (if any)
360 if ( subCs["coordinates"].size() > 1 )
361 {
362 geometries.push_back( subCs );
363 }
364 return { { "type", "CompoundCurve" }, { "geometries", geometries } };
365 }
366 else
367 {
368 return { { "type", "CircularString" }, { "coordinates", QgsGeometryUtils::pointsToJson( pts, precision, profile ) } };
369 }
370 }
371 }
373}
374
376{
377 if ( !isEmpty() && ( numPoints() < 3 ) )
378 {
379 error = QObject::tr( "CircularString has less than 3 points and is not empty." );
380 return false;
381 }
382 return QgsCurve::isValid( error, flags );
383}
384
385//curve interface
387{
388 int nPoints = numPoints();
389 double length = 0;
390 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
391 {
392 length += QgsGeometryUtilsBase::circleLength( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2] );
393 }
394 return length;
395}
396
398{
399 QgsLineString *line = new QgsLineString();
401 int nPoints = numPoints();
402
403 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
404 {
405 QgsGeometryUtils::segmentizeArc( pointN( i ), pointN( i + 1 ), pointN( i + 2 ), points, tolerance, toleranceType, is3D(), isMeasure() );
406 }
407
408 line->setPoints( points );
409 return line;
410}
411
412QgsCircularString *QgsCircularString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool ) const
413{
414 // prepare result
415 std::unique_ptr<QgsCircularString> result { createEmptyWithSameType() };
416
417 // remove redundant not supported for circular strings
418 bool res = snapToGridPrivate( hSpacing, vSpacing, dSpacing, mSpacing, mX, mY, mZ, mM, result->mX, result->mY, result->mZ, result->mM, false );
419 if ( res )
420 return result.release();
421 else
422 return nullptr;
423}
424
426{
427 std::unique_ptr< QgsLineString > line( curveToLine() );
428 return line->simplifyByDistance( tolerance );
429}
430
431bool QgsCircularString::removeDuplicateNodes( double epsilon, bool useZValues )
432{
433 if ( mX.count() <= 3 )
434 return false; // don't create degenerate lines
435 bool result = false;
436 double prevX = mX.at( 0 );
437 double prevY = mY.at( 0 );
438 bool hasZ = is3D();
439 bool useZ = hasZ && useZValues;
440 double prevZ = useZ ? mZ.at( 0 ) : 0;
441 int i = 1;
442 int remaining = mX.count();
443 // we have to consider points in pairs, since a segment can validly have the same start and
444 // end if it has a different curve point
445 while ( i + 1 < remaining )
446 {
447 double currentCurveX = mX.at( i );
448 double currentCurveY = mY.at( i );
449 double currentX = mX.at( i + 1 );
450 double currentY = mY.at( i + 1 );
451 double currentZ = useZ ? mZ.at( i + 1 ) : 0;
452 if ( qgsDoubleNear( currentCurveX, prevX, epsilon )
453 && qgsDoubleNear( currentCurveY, prevY, epsilon )
454 && qgsDoubleNear( currentX, prevX, epsilon )
455 && qgsDoubleNear( currentY, prevY, epsilon )
456 && ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
457 {
458 result = true;
459 // remove point
460 mX.removeAt( i );
461 mX.removeAt( i );
462 mY.removeAt( i );
463 mY.removeAt( i );
464 if ( hasZ )
465 {
466 mZ.removeAt( i );
467 mZ.removeAt( i );
468 }
469 remaining -= 2;
470 }
471 else
472 {
473 prevX = currentX;
474 prevY = currentY;
475 prevZ = currentZ;
476 i += 2;
477 }
478 }
479 return result;
480}
481
482int QgsCircularString::indexOf( const QgsPoint &point ) const
483{
484 const int size = mX.size();
485 if ( size == 0 )
486 return -1;
487
488 const double *x = mX.constData();
489 const double *y = mY.constData();
490 const bool useZ = is3D();
491 const bool useM = isMeasure();
492 const double *z = useZ ? mZ.constData() : nullptr;
493 const double *m = useM ? mM.constData() : nullptr;
494
495 for ( int i = 0; i < size; i += 2 )
496 {
497 if ( qgsDoubleNear( *x, point.x() ) && qgsDoubleNear( *y, point.y() ) && ( !useZ || qgsDoubleNear( *z, point.z() ) ) && ( !useM || qgsDoubleNear( *m, point.m() ) ) )
498 return i;
499
500 // we skip over curve points!
501 x++;
502 x++;
503 y++;
504 y++;
505 if ( useZ )
506 {
507 z++;
508 z++;
509 }
510 if ( useM )
511 {
512 m++;
513 m++;
514 }
515 }
516 return -1;
517}
518
519std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsCircularString::splitCurveAtVertex( int index ) const
520{
521 QVector< double > x1, y1, z1, m1;
522 QVector< double > x2, y2, z2, m2;
523 QgsSimpleCurve::splitCurveAtVertexProtected( index, x1, y1, z1, m1, x2, y2, z2, m2 );
524
525 std::unique_ptr< QgsCircularString > first;
526 if ( x1.isEmpty() || ( x1.size() < 2 && x2.size() >= 2 ) )
527 first = std::make_unique< QgsCircularString >();
528 else
529 first = std::make_unique< QgsCircularString >( x1, y1, z1, m1 );
530
531 std::unique_ptr< QgsCircularString > second;
532 if ( x2.isEmpty() || x2.size() < 2 )
533 second = std::make_unique< QgsCircularString >();
534 else
535 second = std::make_unique< QgsCircularString >( x2, y2, z2, m2 );
536
537 return std::make_tuple( std::move( first ), std::move( second ) );
538}
539
540void QgsCircularString::draw( QPainter &p ) const
541{
542 QPainterPath path;
543 addToPainterPath( path );
544 p.drawPath( path );
545}
546
547void arcTo( QPainterPath &path, QPointF pt1, QPointF pt2, QPointF pt3 )
548{
549 double centerX, centerY, radius;
550 QgsGeometryUtils::circleCenterRadius( QgsPoint( pt1.x(), pt1.y() ), QgsPoint( pt2.x(), pt2.y() ), QgsPoint( pt3.x(), pt3.y() ), radius, centerX, centerY );
551
552 double p1Angle = QgsGeometryUtilsBase::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
553 double sweepAngle = QgsGeometryUtilsBase::sweepAngle( centerX, centerY, pt1.x(), pt1.y(), pt2.x(), pt2.y(), pt3.x(), pt3.y() );
554
555 double diameter = 2 * radius;
556 path.arcTo( centerX - radius, centerY - radius, diameter, diameter, -p1Angle, -sweepAngle );
557}
558
559void QgsCircularString::addToPainterPath( QPainterPath &path ) const
560{
561 int nPoints = numPoints();
562 if ( nPoints < 1 )
563 {
564 return;
565 }
566
567 if ( path.isEmpty() || path.currentPosition() != QPointF( mX[0], mY[0] ) )
568 {
569 path.moveTo( QPointF( mX[0], mY[0] ) );
570 }
571
572 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
573 {
574 arcTo( path, QPointF( mX[i], mY[i] ), QPointF( mX[i + 1], mY[i + 1] ), QPointF( mX[i + 2], mY[i + 2] ) );
575 }
576
577 //if number of points is even, connect to last point with straight line (even though the circular string is not valid)
578 if ( nPoints % 2 == 0 )
579 {
580 path.lineTo( mX[nPoints - 1], mY[nPoints - 1] );
581 }
582}
583
584void QgsCircularString::drawAsPolygon( QPainter &p ) const
585{
586 draw( p );
587}
588
590{
591 if ( position.vertex >= mX.size() || position.vertex < 1 )
592 {
593 return false;
594 }
595
596 mX.insert( position.vertex, vertex.x() );
597 mY.insert( position.vertex, vertex.y() );
598 if ( is3D() )
599 {
600 mZ.insert( position.vertex, vertex.z() );
601 }
602 if ( isMeasure() )
603 {
604 mM.insert( position.vertex, vertex.m() );
605 }
606
607 bool vertexNrEven = ( position.vertex % 2 == 0 );
608 if ( vertexNrEven )
609 {
610 insertVertexBetween( position.vertex - 2, position.vertex - 1, position.vertex );
611 }
612 else
613 {
614 insertVertexBetween( position.vertex, position.vertex + 1, position.vertex - 1 );
615 }
616 clearCache(); //set bounding box invalid
617 return true;
618}
619
621{
622 int nVertices = this->numPoints();
623
624 if ( position.vertex < 0 || position.vertex > ( nVertices - 1 ) )
625 {
626 return false;
627 }
628
629 if ( nVertices < 4 ) //circular string must have at least 3 vertices
630 {
631 clear();
632 return true;
633 }
634
635 if ( position.vertex < ( nVertices - 2 ) )
636 {
637 //remove this and the following vertex
638 deleteVertex( position.vertex + 1 );
639 deleteVertex( position.vertex );
640 }
641 else //remove this and the preceding vertex
642 {
643 deleteVertex( position.vertex );
644 deleteVertex( position.vertex - 1 );
645 }
646
647 clearCache(); //set bounding box invalid
648 return true;
649}
650
652{
653 mX.remove( i );
654 mY.remove( i );
655 if ( is3D() )
656 {
657 mZ.remove( i );
658 }
659 if ( isMeasure() )
660 {
661 mM.remove( i );
662 }
663 clearCache();
664}
665
666bool QgsCircularString::deleteVertices( const QSet<QgsVertexId> &positions )
667{
668 if ( positions.empty() )
669 {
670 return false;
671 }
672
673 for ( QgsVertexId pos : positions )
674 {
675 if ( !hasVertex( pos ) )
676 {
677 return false;
678 }
679 }
680
681 int nVertices = this->numPoints();
682
683 QList<QgsVertexId> vertices( positions.begin(), positions.end() );
684
685 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex < b.vertex; } );
686
687 // remove adjacent vertices as deleting one will also delete the other
688 for ( size_t i = vertices.size() - 1; i >= 1; i-- )
689 {
690 int vertexNr = vertices[i].vertex;
691 int prevVertexNr = vertices[i - 1].vertex;
692
693 if ( vertexNr - 1 == prevVertexNr )
694 {
695 if ( vertexNr < nVertices - 2 )
696 {
697 vertices.removeAt( i );
698 }
699 else
700 {
701 vertices.removeAt( i - 1 );
702 }
703
704 i--; // adjacent vertices handled, we can skip the next one as well
705
706 if ( i == 0 )
707 break;
708 }
709 }
710
711 // this check cannot be moved further up, we need to check adjacent vertices first
712 if ( nVertices - vertices.size() * 2 < 3 )
713 {
714 clear();
715 return true;
716 }
717
718 QListIterator<QgsVertexId> positionsIt( vertices );
719 positionsIt.toBack();
720 while ( positionsIt.hasPrevious() )
721 {
722 int currentVertexNr = positionsIt.previous().vertex;
723
724 if ( currentVertexNr < nVertices - 2 )
725 {
726 deleteVertex( currentVertexNr + 1 );
727 deleteVertex( currentVertexNr );
728 }
729 else
730 {
731 deleteVertex( currentVertexNr );
732 deleteVertex( currentVertexNr - 1 );
733 }
734 nVertices -= 2;
735 }
736
737 return true;
738}
739
740double QgsCircularString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
741{
742 double minDist = std::numeric_limits<double>::max();
743 QgsPoint minDistSegmentPoint;
744 QgsVertexId minDistVertexAfter;
745 int minDistLeftOf = 0;
746
747 double currentDist = 0.0;
748
749 int nPoints = numPoints();
750 for ( int i = 0; i < ( nPoints - 2 ); i += 2 )
751 {
752 currentDist = closestPointOnArc( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2], pt, segmentPt, vertexAfter, leftOf, epsilon );
753 if ( currentDist < minDist )
754 {
755 minDist = currentDist;
756 minDistSegmentPoint = segmentPt;
757 minDistVertexAfter.vertex = vertexAfter.vertex + i;
758 if ( leftOf )
759 {
760 minDistLeftOf = *leftOf;
761 }
762 }
763 }
764
765 if ( minDist == std::numeric_limits<double>::max() )
766 return -1; // error: no segments
767
768 segmentPt = minDistSegmentPoint;
769 vertexAfter = minDistVertexAfter;
770 vertexAfter.part = 0;
771 vertexAfter.ring = 0;
772 if ( leftOf )
773 {
774 *leftOf = qgsDoubleNear( minDist, 0.0 ) ? 0 : minDistLeftOf;
775 }
776 return minDist;
777}
778
779bool QgsCircularString::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
780{
781 if ( node < 0 || node >= numPoints() )
782 {
783 return false;
784 }
785 point = pointN( node );
786 type = ( node % 2 == 0 ) ? Qgis::VertexType::Segment : Qgis::VertexType::Curve;
787 return true;
788}
789
790void QgsCircularString::sumUpArea( double &sum ) const
791{
793 {
794 sum += mSummedUpArea;
795 return;
796 }
797
798 int maxIndex = numPoints() - 2;
799 mSummedUpArea = 0;
800 for ( int i = 0; i < maxIndex; i += 2 )
801 {
802 QgsPoint p1( mX[i], mY[i] );
803 QgsPoint p2( mX[i + 1], mY[i + 1] );
804 QgsPoint p3( mX[i + 2], mY[i + 2] );
805
806 //segment is a full circle, p2 is the center point
807 if ( p1 == p3 )
808 {
809 double r2 = QgsGeometryUtils::sqrDistance2D( p1, p2 ) / 4.0;
810 mSummedUpArea += M_PI * r2;
811 continue;
812 }
813
814 mSummedUpArea += 0.5 * ( mX[i] * mY[i + 2] - mY[i] * mX[i + 2] );
815
816 //calculate area between circle and chord, then sum / subtract from total area
817 double midPointX = ( p1.x() + p3.x() ) / 2.0;
818 double midPointY = ( p1.y() + p3.y() ) / 2.0;
819
820 double radius, centerX, centerY;
821 QgsGeometryUtils::circleCenterRadius( p1, p2, p3, radius, centerX, centerY );
822
823 double d = std::sqrt( QgsGeometryUtils::sqrDistance2D( QgsPoint( centerX, centerY ), QgsPoint( midPointX, midPointY ) ) );
824 double r2 = radius * radius;
825
826 if ( d > radius )
827 {
828 //d cannot be greater than radius, something must be wrong...
829 continue;
830 }
831
832 bool circlePointLeftOfLine = QgsGeometryUtilsBase::leftOfLine( p2.x(), p2.y(), p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
833 bool centerPointLeftOfLine = QgsGeometryUtilsBase::leftOfLine( centerX, centerY, p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
834
835 double cov = 0.5 - d * std::sqrt( r2 - d * d ) / ( M_PI * r2 ) - M_1_PI * std::asin( d / radius );
836 double circleChordArea = 0;
837 if ( circlePointLeftOfLine == centerPointLeftOfLine )
838 {
839 circleChordArea = M_PI * r2 * ( 1 - cov );
840 }
841 else
842 {
843 circleChordArea = M_PI * r2 * cov;
844 }
845
846 if ( !circlePointLeftOfLine )
847 {
848 mSummedUpArea += circleChordArea;
849 }
850 else
851 {
852 mSummedUpArea -= circleChordArea;
853 }
854 }
855
857 sum += mSummedUpArea;
858}
859
860void QgsCircularString::sumUpArea3D( double &sum ) const
861{
863 {
864 sum += mSummedUpArea3D;
865 return;
866 }
867
868 // No Z component. Fallback to the 2D version
869 if ( mZ.isEmpty() )
870 {
871 double area2D = 0;
872 sumUpArea( area2D );
873 mSummedUpArea3D = area2D;
875 sum += mSummedUpArea3D;
876 return;
877 }
878
879 // FIXME: Implement proper 3D shoelace formula for circular strings
880 // workaround: project points to 2D plane and apply standard 2D shoelace formula
881 mSummedUpArea3D = 0;
882
883 // Build an orthonormal reference frame (ux, uy, uz) from three 3D points
884 QgsPoint ptA;
885 QgsPoint ptB;
886 QgsPoint ptC;
887 if ( !QgsGeometryUtils::checkWeaklyFor3DPlane( this, ptA, ptB, ptC ) )
888 {
890 return;
891 }
892
893 QgsVector3D ux( ptB.x() - ptA.x(), ptB.y() - ptA.y(), ptB.z() - ptA.z() );
894 QgsVector3D uz = QgsVector3D::crossProduct( ux, QgsVector3D( ptC.x() - ptA.x(), ptC.y() - ptA.y(), ptC.z() - ptA.z() ) );
895 ux.normalize();
896 uz.normalize();
898
899 double normalSign = 1.0;
900 // Ensure a consistent orientation: prioritize Z+, then Y+, then X+
901 if ( !qgsDoubleNear( uz.z(), 0.0 ) )
902 {
903 if ( uz.z() < 0 )
904 normalSign = -1.0;
905 }
906 else if ( !qgsDoubleNear( uz.y(), 0.0 ) )
907 {
908 if ( uz.y() < 0 )
909 normalSign = -1.0;
910 }
911 else
912 {
913 if ( uz.x() < 0 )
914 normalSign = -1.0;
915 }
916
917 // Project points onto the orthonormal plane (ux, uy) and compute 2D sumUpArea
918 const int nrPoints = numPoints();
919 QVector<double> projX;
920 QVector<double> projY;
921 projX.reserve( nrPoints );
922 projY.reserve( nrPoints );
923 for ( int i = 0; i < nrPoints; i++ )
924 {
925 const double vecAX = mX[i] - ptA.x();
926 const double vecAY = mY[i] - ptA.y();
927 const double vecAZ = mZ[i] - ptA.z();
928
929 projX.push_back( vecAX * ux.x() + vecAY * ux.y() + vecAZ * ux.z() );
930 projY.push_back( vecAX * uy.x() + vecAY * uy.y() + vecAZ * uy.z() );
931 }
932
933 QgsCircularString projectedCurve( projX, projY );
934 projectedCurve.sumUpArea( mSummedUpArea3D );
935
936 // take into account normal sign
937 mSummedUpArea3D *= normalSign;
939 sum += mSummedUpArea3D;
940}
941
943{
944 return true;
945}
946
947double 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 )
948{
949 double radius, centerX, centerY;
950 QgsPoint pt1( x1, y1 );
951 QgsPoint pt2( x2, y2 );
952 QgsPoint pt3( x3, y3 );
953
954 QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
955 double angle = QgsGeometryUtilsBase::ccwAngle( pt.y() - centerY, pt.x() - centerX );
956 double angle1 = QgsGeometryUtilsBase::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
957 double angle2 = QgsGeometryUtilsBase::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
958 double angle3 = QgsGeometryUtilsBase::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
959
960 bool clockwise = QgsGeometryUtilsBase::circleClockwise( angle1, angle2, angle3 );
961
962 if ( QgsGeometryUtilsBase::angleOnCircle( angle, angle1, angle2, angle3 ) )
963 {
964 //get point on line center -> pt with distance radius
965 segmentPt = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), pt, radius );
966
967 //vertexAfter
968 vertexAfter.vertex = QgsGeometryUtilsBase::circleAngleBetween( angle, angle1, angle2, clockwise ) ? 1 : 2;
969 }
970 else
971 {
972 double distPtPt1 = QgsGeometryUtils::sqrDistance2D( pt, pt1 );
973 double distPtPt3 = QgsGeometryUtils::sqrDistance2D( pt, pt3 );
974 segmentPt = ( distPtPt1 <= distPtPt3 ) ? pt1 : pt3;
975 vertexAfter.vertex = ( distPtPt1 <= distPtPt3 ) ? 1 : 2;
976 }
977
978 double sqrDistance = QgsGeometryUtils::sqrDistance2D( segmentPt, pt );
979 //prevent rounding errors if the point is directly on the segment
980 if ( qgsDoubleNear( sqrDistance, 0.0, epsilon ) )
981 {
982 segmentPt.setX( pt.x() );
983 segmentPt.setY( pt.y() );
984 sqrDistance = 0.0;
985 }
986
987 if ( leftOf )
988 {
989 double sqrDistancePointToCenter = pt.distanceSquared( centerX, centerY );
990 *leftOf = clockwise ? ( sqrDistancePointToCenter > radius * radius ? -1 : 1 ) : ( sqrDistancePointToCenter < radius * radius ? -1 : 1 );
991 }
992
993 return sqrDistance;
994}
995
996void QgsCircularString::insertVertexBetween( int after, int before, int pointOnCircle )
997{
998 double xAfter = mX.at( after );
999 double yAfter = mY.at( after );
1000 double xBefore = mX.at( before );
1001 double yBefore = mY.at( before );
1002 double xOnCircle = mX.at( pointOnCircle );
1003 double yOnCircle = mY.at( pointOnCircle );
1004
1005 double radius, centerX, centerY;
1006 QgsGeometryUtils::circleCenterRadius( QgsPoint( xAfter, yAfter ), QgsPoint( xBefore, yBefore ), QgsPoint( xOnCircle, yOnCircle ), radius, centerX, centerY );
1007
1008 double x = ( xAfter + xBefore ) / 2.0;
1009 double y = ( yAfter + yBefore ) / 2.0;
1010
1011 QgsPoint newVertex = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), QgsPoint( x, y ), radius );
1012 mX.insert( before, newVertex.x() );
1013 mY.insert( before, newVertex.y() );
1014
1015 if ( is3D() )
1016 {
1017 mZ.insert( before, ( mZ[after] + mZ[before] ) / 2.0 );
1018 }
1019 if ( isMeasure() )
1020 {
1021 mM.insert( before, ( mM[after] + mM[before] ) / 2.0 );
1022 }
1023 clearCache();
1024}
1025
1027{
1028 if ( numPoints() < 3 )
1029 {
1030 //undefined
1031 return 0.0;
1032 }
1033
1034 int before = vId.vertex - 1;
1035 int vertex = vId.vertex;
1036 int after = vId.vertex + 1;
1037
1038 if ( vId.vertex % 2 != 0 ) // a curve vertex
1039 {
1040 if ( vId.vertex >= 1 && vId.vertex < numPoints() - 1 )
1041 {
1042 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[before], mY[before] ), QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[after], mY[after] ) );
1043 }
1044 }
1045 else //a point vertex
1046 {
1047 if ( vId.vertex == 0 )
1048 {
1049 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[0], mY[0] ), QgsPoint( mX[0], mY[0] ), QgsPoint( mX[1], mY[1] ), QgsPoint( mX[2], mY[2] ) );
1050 }
1051 if ( vId.vertex >= numPoints() - 1 )
1052 {
1053 int a = numPoints() - 3;
1054 int b = numPoints() - 2;
1055 int c = numPoints() - 1;
1056 return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[c], mY[c] ), QgsPoint( mX[a], mY[a] ), QgsPoint( mX[b], mY[b] ), QgsPoint( mX[c], mY[c] ) );
1057 }
1058 else
1059 {
1060 if ( vId.vertex + 2 > numPoints() - 1 )
1061 {
1062 return 0.0;
1063 }
1064
1065 int vertex1 = vId.vertex - 2;
1066 int vertex2 = vId.vertex - 1;
1067 int vertex3 = vId.vertex;
1068 double angle1
1069 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex1], mY[vertex1] ), QgsPoint( mX[vertex2], mY[vertex2] ), QgsPoint( mX[vertex3], mY[vertex3] ) );
1070 int vertex4 = vId.vertex + 1;
1071 int vertex5 = vId.vertex + 2;
1072 double angle2
1073 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex4], mY[vertex4] ), QgsPoint( mX[vertex5], mY[vertex5] ) );
1074 return QgsGeometryUtilsBase::averageAngle( angle1, angle2 );
1075 }
1076 }
1077 return 0.0;
1078}
1079
1081{
1082 if ( startVertex.vertex % 2 == 1 )
1083 return 0.0; // curve point?
1084
1085 if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 2 )
1086 return 0.0;
1087
1088 double x1 = mX.at( startVertex.vertex );
1089 double y1 = mY.at( startVertex.vertex );
1090 double x2 = mX.at( startVertex.vertex + 1 );
1091 double y2 = mY.at( startVertex.vertex + 1 );
1092 double x3 = mX.at( startVertex.vertex + 2 );
1093 double y3 = mY.at( startVertex.vertex + 2 );
1094 return QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1095}
1096
1098{
1099 // Ensure fromVertex < toVertex for simplicity
1100 if ( fromVertex.vertex > toVertex.vertex )
1101 {
1102 return distanceBetweenVertices( toVertex, fromVertex );
1103 }
1104
1105 // Convert QgsVertexId to simple vertex numbers for curves (single ring, single part)
1106 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
1107 return -1.0;
1108
1109 const int fromVertexNumber = fromVertex.vertex;
1110 const int toVertexNumber = toVertex.vertex;
1111
1112 const int nPoints = numPoints();
1113 if ( fromVertexNumber < 0 || fromVertexNumber >= nPoints || toVertexNumber < 0 || toVertexNumber >= nPoints )
1114 return -1.0;
1115
1116 if ( fromVertexNumber == toVertexNumber )
1117 return 0.0;
1118
1119 const double *xData = mX.constData();
1120 const double *yData = mY.constData();
1121 double totalDistance = 0.0;
1122
1123 // Start iteration from the arc containing fromVertex
1124 // Each arc starts at an even index (0, 2, 4, ...) and spans 3 vertices
1125 const int startArc = ( fromVertexNumber / 2 ) * 2;
1126
1127 // Iterate through the arcs, accumulating distance between fromVertex and toVertex
1128 for ( int i = startArc; i < nPoints - 2; i += 2 )
1129 {
1130 // Arc segment from i to i+2, with curve point at i+1
1131 double x1 = xData[i]; // Start point
1132 double y1 = yData[i];
1133 double x2 = xData[i + 1]; // Curve point
1134 double y2 = yData[i + 1];
1135 double x3 = xData[i + 2]; // End point
1136 double y3 = yData[i + 2];
1137
1138 // Check if both vertices are in this arc segment
1139 if ( fromVertexNumber >= i && toVertexNumber <= i + 2 )
1140 {
1141 if ( fromVertexNumber == i && toVertexNumber == i + 2 )
1142 {
1143 // Full arc from start to end
1144 return QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1145 }
1146 else if ( fromVertexNumber == i && toVertexNumber == i + 1 )
1147 {
1148 // Arc from start point to curve point
1149 double centerX, centerY, radius;
1150 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1151 // Calculate arc length from vertex 0 to vertex 1
1152 return QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 0, 1 );
1153 }
1154 else if ( fromVertexNumber == i + 1 && toVertexNumber == i + 2 )
1155 {
1156 // Arc from curve point to end point
1157 double centerX, centerY, radius;
1158 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1159 // Calculate arc length from vertex 1 to vertex 2
1160 return QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 1, 2 );
1161 }
1162 else if ( fromVertexNumber == i + 1 && toVertexNumber == i + 1 )
1163 {
1164 return 0.0; // Same point
1165 }
1166 }
1167
1168 // Handle cases where vertices span multiple segments
1169 bool startInThisSegment = ( fromVertexNumber >= i && fromVertexNumber <= i + 2 );
1170 bool endInThisSegment = ( toVertexNumber >= i && toVertexNumber <= i + 2 );
1171 bool segmentInRange = ( fromVertexNumber < i && toVertexNumber > i + 2 );
1172
1173 if ( startInThisSegment && !endInThisSegment )
1174 {
1175 // fromVertex is in this segment, toVertex is beyond
1176 if ( fromVertexNumber == i )
1177 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1178 else if ( fromVertexNumber == i + 1 )
1179 {
1180 // From curve point to end of segment
1181 double centerX, centerY, radius;
1182 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1183 totalDistance += QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 1, 2 );
1184 }
1185 }
1186 else if ( !startInThisSegment && endInThisSegment )
1187 {
1188 // fromVertex is before this segment, toVertex is in this segment
1189 if ( toVertexNumber == i + 1 )
1190 {
1191 // From start of segment to curve point
1192 double centerX, centerY, radius;
1193 QgsGeometryUtilsBase::circleCenterRadius( x1, y1, x2, y2, x3, y3, radius, centerX, centerY );
1194 totalDistance += QgsGeometryUtilsBase::calculateArcLength( centerX, centerY, radius, x1, y1, x2, y2, x3, y3, 0, 1 );
1195 }
1196 else if ( toVertexNumber == i + 2 )
1197 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1198 break;
1199 }
1200 else if ( segmentInRange )
1201 {
1202 // This entire segment is between fromVertex and toVertex
1203 totalDistance += QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1204 }
1205 }
1206
1207 return totalDistance;
1208}
1209
1210
1212{
1213 return qgis::down_cast< QgsCircularString *>( QgsSimpleCurve::reversed() );
1214}
1215
1216QgsPoint *QgsCircularString::interpolatePoint( const double distance ) const
1217{
1218 if ( distance < 0 )
1219 return nullptr;
1220
1221 double distanceTraversed = 0;
1222 const int totalPoints = numPoints();
1223 if ( totalPoints == 0 )
1224 return nullptr;
1225
1227 if ( is3D() )
1228 pointType = Qgis::WkbType::PointZ;
1229 if ( isMeasure() )
1230 pointType = QgsWkbTypes::addM( pointType );
1231
1232 const double *x = mX.constData();
1233 const double *y = mY.constData();
1234 const double *z = is3D() ? mZ.constData() : nullptr;
1235 const double *m = isMeasure() ? mM.constData() : nullptr;
1236
1237 double prevX = *x++;
1238 double prevY = *y++;
1239 double prevZ = z ? *z++ : 0.0;
1240 double prevM = m ? *m++ : 0.0;
1241
1242 if ( qgsDoubleNear( distance, 0.0 ) )
1243 {
1244 return new QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1245 }
1246
1247 for ( int i = 0; i < ( totalPoints - 2 ); i += 2 )
1248 {
1249 double x1 = prevX;
1250 double y1 = prevY;
1251 double z1 = prevZ;
1252 double m1 = prevM;
1253
1254 double x2 = *x++;
1255 double y2 = *y++;
1256 double z2 = z ? *z++ : 0.0;
1257 double m2 = m ? *m++ : 0.0;
1258
1259 double x3 = *x++;
1260 double y3 = *y++;
1261 double z3 = z ? *z++ : 0.0;
1262 double m3 = m ? *m++ : 0.0;
1263
1264 const double segmentLength = QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1265 if ( distance < distanceTraversed + segmentLength || qgsDoubleNear( distance, distanceTraversed + segmentLength ) )
1266 {
1267 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1268 const double distanceToPoint = std::min( distance - distanceTraversed, segmentLength );
1269 return new QgsPoint( QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToPoint ) );
1270 }
1271
1272 distanceTraversed += segmentLength;
1273
1274 prevX = x3;
1275 prevY = y3;
1276 prevZ = z3;
1277 prevM = m3;
1278 }
1279
1280 return nullptr;
1281}
1282
1283QgsCircularString *QgsCircularString::curveSubstring( double startDistance, double endDistance ) const
1284{
1285 if ( startDistance < 0 && endDistance < 0 )
1286 return createEmptyWithSameType();
1287
1288 endDistance = std::max( startDistance, endDistance );
1289
1290 const int totalPoints = numPoints();
1291 if ( totalPoints == 0 )
1292 return clone();
1293
1294 QVector< QgsPoint > substringPoints;
1295 substringPoints.reserve( totalPoints );
1296
1298 if ( is3D() )
1299 pointType = Qgis::WkbType::PointZ;
1300 if ( isMeasure() )
1301 pointType = QgsWkbTypes::addM( pointType );
1302
1303 const double *x = mX.constData();
1304 const double *y = mY.constData();
1305 const double *z = is3D() ? mZ.constData() : nullptr;
1306 const double *m = isMeasure() ? mM.constData() : nullptr;
1307
1308 double distanceTraversed = 0;
1309 double prevX = *x++;
1310 double prevY = *y++;
1311 double prevZ = z ? *z++ : 0.0;
1312 double prevM = m ? *m++ : 0.0;
1313 bool foundStart = false;
1314
1315 if ( startDistance < 0 )
1316 startDistance = 0;
1317
1318 for ( int i = 0; i < ( totalPoints - 2 ); i += 2 )
1319 {
1320 double x1 = prevX;
1321 double y1 = prevY;
1322 double z1 = prevZ;
1323 double m1 = prevM;
1324
1325 double x2 = *x++;
1326 double y2 = *y++;
1327 double z2 = z ? *z++ : 0.0;
1328 double m2 = m ? *m++ : 0.0;
1329
1330 double x3 = *x++;
1331 double y3 = *y++;
1332 double z3 = z ? *z++ : 0.0;
1333 double m3 = m ? *m++ : 0.0;
1334
1335 bool addedSegmentEnd = false;
1336 const double segmentLength = QgsGeometryUtilsBase::circleLength( x1, y1, x2, y2, x3, y3 );
1337 if ( distanceTraversed <= startDistance && startDistance < distanceTraversed + segmentLength )
1338 {
1339 // start point falls on this segment
1340 const double distanceToStart = startDistance - distanceTraversed;
1341 const QgsPoint startPoint
1342 = QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToStart );
1343
1344 // does end point also fall on this segment?
1345 const bool endPointOnSegment = distanceTraversed + segmentLength > endDistance;
1346 if ( endPointOnSegment )
1347 {
1348 const double distanceToEnd = endDistance - distanceTraversed;
1349 const double midPointDistance = ( distanceToEnd - distanceToStart ) * 0.5 + distanceToStart;
1350 substringPoints
1351 << startPoint
1352 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1353 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1354 addedSegmentEnd = true;
1355 }
1356 else
1357 {
1358 const double midPointDistance = ( segmentLength - distanceToStart ) * 0.5 + distanceToStart;
1359 substringPoints
1360 << startPoint
1361 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1362 << QgsPoint( pointType, x3, y3, z3, m3 );
1363 addedSegmentEnd = true;
1364 }
1365 foundStart = true;
1366 }
1367 if ( !addedSegmentEnd && foundStart && ( distanceTraversed + segmentLength > endDistance ) )
1368 {
1369 // end point falls on this segment
1370 const double distanceToEnd = endDistance - distanceTraversed;
1371 // add mid point, at half way along this arc, then add the interpolated end point
1372 substringPoints
1373 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd / 2.0 )
1374
1375 << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ), QgsPoint( pointType, x2, y2, z2, m2 ), QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1376 }
1377 else if ( !addedSegmentEnd && foundStart )
1378 {
1379 substringPoints << QgsPoint( pointType, x2, y2, z2, m2 ) << QgsPoint( pointType, x3, y3, z3, m3 );
1380 }
1381
1382 prevX = x3;
1383 prevY = y3;
1384 prevZ = z3;
1385 prevM = m3;
1386 distanceTraversed += segmentLength;
1387 if ( distanceTraversed >= endDistance )
1388 break;
1389 }
1390
1391 // start point is the last node
1392 if ( !foundStart && qgsDoubleNear( distanceTraversed, startDistance ) )
1393 {
1394 substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM ) << QgsPoint( pointType, prevX, prevY, prevZ, prevM ) << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1395 }
1396
1397 auto result = std::make_unique< QgsCircularString >();
1398 result->setPoints( substringPoints );
1399 return result.release();
1400}
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
GeoJsonProfile
GeoJson export Profile according to OGC Features and Geometries JSON - Part 1: Core https://docs....
Definition qgis.h:5032
@ Legacy
Legacy GeoJson profile used in QGIS prior to 4.2, which included some non-standard extensions and dev...
Definition qgis.h:5033
@ Rfc7946
GeoJson profile compliant with RFC7946 standard "http://www.opengis.net/def/profile/OGC/0/rfc7946".
Definition qgis.h:5034
@ JsonFg
GeoJson profile from OGC Features and Geometries JSON Part 1: core "http://www.opengis....
Definition qgis.h:5035
@ JsonFgPlus
GeoJson profile from OGC Features and Geometries JSON Part 1: core "http://www.opengis....
Definition qgis.h:5036
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.
json asJsonObject(int precision=17, Qgis::GeoJsonProfile profile=Qgis::GeoJsonProfile::Legacy) const override
Returns a json object representation of the geometry with the given precision and profile.
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...
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...
static json pointsToJson(const QgsPointSequence &points, int precision, Qgis::GeoJsonProfile profile)
Returns coordinates as json object.
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
#define BUILTIN_UNREACHABLE
Definition qgis.h:8002
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7368
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