QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgsgeometryutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgeometryutils.cpp
3-------------------------------------------------------------------
4Date : 21 Nov 2014
5Copyright : (C) 2014 by Marco Hugentobler
6email : marco.hugentobler at sourcepole dot com
7***************************************************************************
8* *
9* This program is free software; you can redistribute it and/or modify *
10* it under the terms of the GNU General Public License as published by *
11* the Free Software Foundation; either version 2 of the License, or *
12* (at your option) any later version. *
13* *
14***************************************************************************/
15
16#include "qgsgeometryutils.h"
17
18#include <limits>
19#include <memory>
20#include <nlohmann/json.hpp>
21
22#include "qgsabstractgeometry.h"
23#include "qgscircularstring.h"
24#include "qgscompoundcurve.h"
25#include "qgscurve.h"
26#include "qgscurvepolygon.h"
28#include "qgslinestring.h"
29#include "qgslogger.h"
30#include "qgspoint.h"
31#include "qgsvertexid.h"
32#include "qgswkbptr.h"
33
34#include <QRegularExpression>
35#include <QString>
36#include <QStringList>
37#include <QVector>
38
39using namespace Qt::StringLiterals;
40
41QVector<QgsLineString *> QgsGeometryUtils::extractLineStrings( const QgsAbstractGeometry *geom )
42{
43 QVector< QgsLineString * > linestrings;
44 if ( !geom )
45 return linestrings;
46
47 QVector< const QgsAbstractGeometry * > geometries;
48 geometries << geom;
49 while ( ! geometries.isEmpty() )
50 {
51 const QgsAbstractGeometry *g = geometries.takeFirst();
52 if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( g ) )
53 {
54 linestrings << static_cast< QgsLineString * >( curve->segmentize() );
55 }
57 {
58 for ( int i = 0; i < collection->numGeometries(); ++i )
59 {
60 geometries.append( collection->geometryN( i ) );
61 }
62 }
63 else if ( const QgsCurvePolygon *curvePolygon = qgsgeometry_cast< const QgsCurvePolygon * >( g ) )
64 {
65 if ( curvePolygon->exteriorRing() )
66 linestrings << static_cast< QgsLineString * >( curvePolygon->exteriorRing()->segmentize() );
67
68 for ( int i = 0; i < curvePolygon->numInteriorRings(); ++i )
69 {
70 linestrings << static_cast< QgsLineString * >( curvePolygon->interiorRing( i )->segmentize() );
71 }
72 }
73 }
74 return linestrings;
75}
76
78{
79 double minDist = std::numeric_limits<double>::max();
80 double currentDist = 0;
81 QgsPoint minDistPoint;
82 id = QgsVertexId(); // set as invalid
83
84 if ( geom.isEmpty() || pt.isEmpty() )
85 return minDistPoint;
86
87 QgsVertexId vertexId;
88 QgsPoint vertex;
89 while ( geom.nextVertex( vertexId, vertex ) )
90 {
91 currentDist = QgsGeometryUtils::sqrDistance2D( pt, vertex );
92 // The <= is on purpose: for geometries with closing vertices, this ensures
93 // that the closing vertex is returned. For the vertex tool, the rubberband
94 // of the closing vertex is above the opening vertex, hence with the <=
95 // situations where the covered opening vertex rubberband is selected are
96 // avoided.
97 if ( currentDist <= minDist )
98 {
99 minDist = currentDist;
100 minDistPoint = vertex;
101 id.part = vertexId.part;
102 id.ring = vertexId.ring;
103 id.vertex = vertexId.vertex;
104 id.type = vertexId.type;
105 }
106 }
107
108 return minDistPoint;
109}
110
112{
114 QgsVertexId vertexAfter;
115 geometry.closestSegment( point, closestPoint, vertexAfter, nullptr, Qgis::DEFAULT_SEGMENT_EPSILON );
116 if ( vertexAfter.isValid() )
117 {
118 const QgsPoint pointAfter = geometry.vertexAt( vertexAfter );
119 if ( vertexAfter.vertex > 0 )
120 {
121 QgsVertexId vertexBefore = vertexAfter;
122 vertexBefore.vertex--;
123 const QgsPoint pointBefore = geometry.vertexAt( vertexBefore );
124 const double length = pointBefore.distance( pointAfter );
125 const double distance = pointBefore.distance( closestPoint );
126
127 if ( qgsDoubleNear( distance, 0.0 ) )
128 closestPoint = pointBefore;
129 else if ( qgsDoubleNear( distance, length ) )
130 closestPoint = pointAfter;
131 else
132 {
133 if ( QgsWkbTypes::hasZ( geometry.wkbType() ) && length )
134 closestPoint.addZValue( pointBefore.z() + ( pointAfter.z() - pointBefore.z() ) * distance / length );
135 if ( QgsWkbTypes::hasM( geometry.wkbType() ) )
136 closestPoint.addMValue( pointBefore.m() + ( pointAfter.m() - pointBefore.m() ) * distance / length );
137 }
138 }
139 }
140
141 return closestPoint;
142}
143
145{
146 double currentDist = 0;
147 QgsVertexId vertexId;
148 QgsPoint vertex;
149 while ( geom.nextVertex( vertexId, vertex ) )
150 {
151 if ( vertexId == id )
152 {
153 //found target vertex
154 return currentDist;
155 }
156 currentDist += geom.segmentLength( vertexId );
157 }
158
159 //could not find target vertex
160 return -1;
161}
162
163bool QgsGeometryUtils::verticesAtDistance( const QgsAbstractGeometry &geometry, double distance, QgsVertexId &previousVertex, QgsVertexId &nextVertex )
164{
165 double currentDist = 0;
166 previousVertex = QgsVertexId();
167 nextVertex = QgsVertexId();
168
169 if ( geometry.isEmpty() )
170 {
171 return false;
172 }
173
174 QgsPoint point;
175 QgsPoint previousPoint;
176
177 if ( qgsDoubleNear( distance, 0.0 ) )
178 {
179 geometry.nextVertex( previousVertex, point );
180 nextVertex = previousVertex;
181 return true;
182 }
183
184 bool first = true;
185 while ( currentDist < distance && geometry.nextVertex( nextVertex, point ) )
186 {
187 if ( !first && nextVertex.part == previousVertex.part && nextVertex.ring == previousVertex.ring )
188 {
189 currentDist += std::sqrt( QgsGeometryUtils::sqrDistance2D( previousPoint, point ) );
190 }
191
192 if ( qgsDoubleNear( currentDist, distance ) )
193 {
194 // exact hit!
195 previousVertex = nextVertex;
196 return true;
197 }
198
199 if ( currentDist > distance )
200 {
201 return true;
202 }
203
204 previousVertex = nextVertex;
205 previousPoint = point;
206 first = false;
207 }
208
209 //could not find target distance
210 return false;
211}
212
213double QgsGeometryUtils::distToInfiniteLine( const QgsPoint &point, const QgsPoint &linePoint1, const QgsPoint &linePoint2, double epsilon )
214{
215 const double area = std::abs(
216 ( linePoint1.x() - linePoint2.x() ) * ( point.y() - linePoint2.y() ) -
217 ( linePoint1.y() - linePoint2.y() ) * ( point.x() - linePoint2.x() )
218 );
219
220 const double length = std::sqrt(
221 std::pow( linePoint1.x() - linePoint2.x(), 2 ) +
222 std::pow( linePoint1.y() - linePoint2.y(), 2 )
223 );
224
225 const double distance = area / length;
226 return qgsDoubleNear( distance, 0.0, epsilon ) ? 0.0 : distance;
227}
228
229bool QgsGeometryUtils::lineCircleIntersection( const QgsPointXY &center, const double radius,
230 const QgsPointXY &linePoint1, const QgsPointXY &linePoint2,
231 QgsPointXY &intersection )
232{
233 // formula taken from http://mathworld.wolfram.com/Circle-LineIntersection.html
234
235 const double x1 = linePoint1.x() - center.x();
236 const double y1 = linePoint1.y() - center.y();
237 const double x2 = linePoint2.x() - center.x();
238 const double y2 = linePoint2.y() - center.y();
239 const double dx = x2 - x1;
240 const double dy = y2 - y1;
241
242 const double dr2 = std::pow( dx, 2 ) + std::pow( dy, 2 );
243 const double d = x1 * y2 - x2 * y1;
244
245 const double disc = std::pow( radius, 2 ) * dr2 - std::pow( d, 2 );
246
247 if ( disc < 0 )
248 {
249 //no intersection or tangent
250 return false;
251 }
252 else
253 {
254 // two solutions
255 const int sgnDy = dy < 0 ? -1 : 1;
256
257 const double sqrDisc = std::sqrt( disc );
258
259 const double ax = center.x() + ( d * dy + sgnDy * dx * sqrDisc ) / dr2;
260 const double ay = center.y() + ( -d * dx + std::fabs( dy ) * sqrDisc ) / dr2;
261 const QgsPointXY p1( ax, ay );
262
263 const double bx = center.x() + ( d * dy - sgnDy * dx * sqrDisc ) / dr2;
264 const double by = center.y() + ( -d * dx - std::fabs( dy ) * sqrDisc ) / dr2;
265 const QgsPointXY p2( bx, by );
266
267 // snap to nearest intersection
268
269 if ( intersection.sqrDist( p1 ) < intersection.sqrDist( p2 ) )
270 {
271 intersection.set( p1.x(), p1.y() );
272 }
273 else
274 {
275 intersection.set( p2.x(), p2.y() );
276 }
277 return true;
278 }
279}
280
281// based on public domain work by 3/26/2005 Tim Voght
282// see http://paulbourke.net/geometry/circlesphere/tvoght.c
283int QgsGeometryUtils::circleCircleIntersections( const QgsPointXY &center1, const double r1, const QgsPointXY &center2, const double r2, QgsPointXY &intersection1, QgsPointXY &intersection2 )
284{
285 // determine the straight-line distance between the centers
286 const double d = center1.distance( center2 );
287
288 // check if the circles intersect at only 1 point, either "externally" or "internally"
289 const bool singleSolutionExt = qgsDoubleNear( d, r1 + r2 );
290 const bool singleSolutionInt = qgsDoubleNear( d, std::fabs( r1 - r2 ) );
291
292 // check for solvability
293 if ( !singleSolutionExt && d > ( r1 + r2 ) )
294 {
295 // no solution. circles do not intersect.
296 return 0;
297 }
298 else if ( !singleSolutionInt && d < std::fabs( r1 - r2 ) )
299 {
300 // no solution. one circle is contained in the other
301 return 0;
302 }
303 else if ( qgsDoubleNear( d, 0 ) && ( qgsDoubleNear( r1, r2 ) ) )
304 {
305 // no solutions, the circles coincide
306 return 0;
307 }
308
309 /* 'point 2' is the point where the line through the circle
310 * intersection points crosses the line between the circle
311 * centers.
312 */
313
314 /* Determine the distance 'a' from point 0 to point 2.
315 * In the general case, a = ( ( r1 * r1 ) - ( r2 * r2 ) + ( d * d ) ) / ( 2.0 * d ).
316 * If d = r1 + r2 or d = r1 - r2 (i.e. r1 > r2), then a = r1; if d = r2 - r1 (i.e. r2 > r1), then a = -r1.
317 */
318 const double a = singleSolutionExt ? r1 : ( singleSolutionInt ? ( r1 > r2 ? r1 : -r1 ) : ( ( r1 * r1 ) - ( r2 * r2 ) + ( d * d ) ) / ( 2.0 * d ) );
319
320 /* dx and dy are the vertical and horizontal distances between
321 * the circle centers.
322 */
323 const double dx = center2.x() - center1.x();
324 const double dy = center2.y() - center1.y();
325
326 // Determine the coordinates of point 2.
327 const double x2 = center1.x() + ( dx * a / d );
328 const double y2 = center1.y() + ( dy * a / d );
329
330 // only 1 solution
331 if ( singleSolutionExt || singleSolutionInt )
332 {
333 intersection1 = QgsPointXY( x2, y2 );
334 intersection2 = QgsPointXY( x2, y2 );
335
336 return 1;
337 }
338
339 // 2 solutions
340
341 /* Determine the distance from point 2 to either of the
342 * intersection points.
343 */
344 const double h = std::sqrt( ( r1 * r1 ) - ( a * a ) );
345
346 /* Now determine the offsets of the intersection points from
347 * point 2.
348 */
349 const double rx = dy * ( h / d );
350 const double ry = dx * ( h / d );
351
352 // determine the absolute intersection points
353 intersection1 = QgsPointXY( x2 + rx, y2 - ry );
354 intersection2 = QgsPointXY( x2 - rx, y2 + ry );
355
356 return 2;
357}
358
359// Using https://stackoverflow.com/a/1351794/1861260
360// and inspired by http://csharphelper.com/blog/2014/11/find-the-tangent-lines-between-a-point-and-a-circle-in-c/
361bool QgsGeometryUtils::tangentPointAndCircle( const QgsPointXY &center, double radius, const QgsPointXY &p, QgsPointXY &pt1, QgsPointXY &pt2 )
362{
363 // distance from point to center of circle
364 const double dx = center.x() - p.x();
365 const double dy = center.y() - p.y();
366 const double distanceSquared = dx * dx + dy * dy;
367 const double radiusSquared = radius * radius;
368 if ( distanceSquared < radiusSquared )
369 {
370 // point is inside circle!
371 return false;
372 }
373
374 // distance from point to tangent point, using pythagoras
375 const double distanceToTangent = std::sqrt( distanceSquared - radiusSquared );
376
377 // tangent points are those where the original circle intersects a circle centered
378 // on p with radius distanceToTangent
379 circleCircleIntersections( center, radius, p, distanceToTangent, pt1, pt2 );
380
381 return true;
382}
383
384// inspired by http://csharphelper.com/blog/2014/12/find-the-tangent-lines-between-two-circles-in-c/
385int QgsGeometryUtils::circleCircleOuterTangents( const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2 )
386{
387 if ( radius1 > radius2 )
388 return circleCircleOuterTangents( center2, radius2, center1, radius1, line1P1, line1P2, line2P1, line2P2 );
389
390 const double radius2a = radius2 - radius1;
391 if ( !tangentPointAndCircle( center2, radius2a, center1, line1P2, line2P2 ) )
392 {
393 // there are no tangents
394 return 0;
395 }
396
397 // get the vector perpendicular to the
398 // first tangent with length radius1
399 QgsVector v1( -( line1P2.y() - center1.y() ), line1P2.x() - center1.x() );
400 const double v1Length = v1.length();
401 v1 = v1 * ( radius1 / v1Length );
402
403 // offset the tangent vector's points
404 line1P1 = center1 + v1;
405 line1P2 = line1P2 + v1;
406
407 // get the vector perpendicular to the
408 // second tangent with length radius1
409 QgsVector v2( line2P2.y() - center1.y(), -( line2P2.x() - center1.x() ) );
410 const double v2Length = v2.length();
411 v2 = v2 * ( radius1 / v2Length );
412
413 // offset the tangent vector's points
414 line2P1 = center1 + v2;
415 line2P2 = line2P2 + v2;
416
417 return 2;
418}
419
420// inspired by http://csharphelper.com/blog/2014/12/find-the-tangent-lines-between-two-circles-in-c/
421int QgsGeometryUtils::circleCircleInnerTangents( const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2 )
422{
423 if ( radius1 > radius2 )
424 return circleCircleInnerTangents( center2, radius2, center1, radius1, line1P1, line1P2, line2P1, line2P2 );
425
426 // determine the straight-line distance between the centers
427 const double d = center1.distance( center2 );
428 const double radius1a = radius1 + radius2;
429
430 // check for solvability
431 if ( d <= radius1a || qgsDoubleNear( d, radius1a ) )
432 {
433 // no solution. circles intersect or touch.
434 return 0;
435 }
436
437 if ( !tangentPointAndCircle( center1, radius1a, center2, line1P2, line2P2 ) )
438 {
439 // there are no tangents
440 return 0;
441 }
442
443 // get the vector perpendicular to the
444 // first tangent with length radius2
445 QgsVector v1( ( line1P2.y() - center2.y() ), -( line1P2.x() - center2.x() ) );
446 const double v1Length = v1.length();
447 v1 = v1 * ( radius2 / v1Length );
448
449 // offset the tangent vector's points
450 line1P1 = center2 + v1;
451 line1P2 = line1P2 + v1;
452
453 // get the vector perpendicular to the
454 // second tangent with length radius2
455 QgsVector v2( -( line2P2.y() - center2.y() ), line2P2.x() - center2.x() );
456 const double v2Length = v2.length();
457 v2 = v2 * ( radius2 / v2Length );
458
459 // offset the tangent vector's points in opposite direction
460 line2P1 = center2 + v2;
461 line2P2 = line2P2 + v2;
462
463 return 2;
464}
465
466QVector<QgsGeometryUtils::SelfIntersection> QgsGeometryUtils::selfIntersections( const QgsAbstractGeometry *geom, int part, int ring, double tolerance )
467{
468 QVector<SelfIntersection> intersections;
469
470 const int n = geom->vertexCount( part, ring );
471 const bool isClosed = geom->vertexAt( QgsVertexId( part, ring, 0 ) ) == geom->vertexAt( QgsVertexId( part, ring, n - 1 ) );
472
473 // Check every pair of segments for intersections
474 for ( int i = 0, j = 1; j < n; i = j++ )
475 {
476 const QgsPoint pi = geom->vertexAt( QgsVertexId( part, ring, i ) );
477 const QgsPoint pj = geom->vertexAt( QgsVertexId( part, ring, j ) );
478 if ( QgsGeometryUtils::sqrDistance2D( pi, pj ) < tolerance * tolerance ) continue;
479
480 // Don't test neighboring edges
481 const int start = j + 1;
482 const int end = i == 0 && isClosed ? n - 1 : n;
483 for ( int k = start, l = start + 1; l < end; k = l++ )
484 {
485 const QgsPoint pk = geom->vertexAt( QgsVertexId( part, ring, k ) );
486 const QgsPoint pl = geom->vertexAt( QgsVertexId( part, ring, l ) );
487
488 QgsPoint inter;
489 bool intersection = false;
490 if ( !QgsGeometryUtils::segmentIntersection( pi, pj, pk, pl, inter, intersection, tolerance ) ) continue;
491
493 s.segment1 = i;
494 s.segment2 = k;
495 if ( s.segment1 > s.segment2 )
496 {
497 std::swap( s.segment1, s.segment2 );
498 }
499 s.point = inter;
500 intersections.append( s );
501 }
502 }
503 return intersections;
504}
505
506int QgsGeometryUtils::leftOfLine( const QgsPoint &point, const QgsPoint &p1, const QgsPoint &p2 )
507{
508 return QgsGeometryUtilsBase::leftOfLine( point.x(), point.y(), p1.x(), p1.y(), p2.x(), p2.y() );
509}
510
511QgsPoint QgsGeometryUtils::pointOnLineWithDistance( const QgsPoint &startPoint, const QgsPoint &directionPoint, double distance )
512{
513 double x, y;
514 QgsGeometryUtilsBase::pointOnLineWithDistance( startPoint.x(), startPoint.y(), directionPoint.x(), directionPoint.y(), distance, x, y );
515 return QgsPoint( x, y );
516}
517
518
519QgsPoint QgsGeometryUtils::interpolatePointOnArc( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double distance )
520{
521 double centerX, centerY, radius;
522 circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
523
524 const double theta = distance / radius; // angle subtended
525 const double anglePt1 = std::atan2( pt1.y() - centerY, pt1.x() - centerX );
526 const double anglePt2 = std::atan2( pt2.y() - centerY, pt2.x() - centerX );
527 const double anglePt3 = std::atan2( pt3.y() - centerY, pt3.x() - centerX );
528 const bool isClockwise = QgsGeometryUtilsBase::circleClockwise( anglePt1, anglePt2, anglePt3 );
529 const double angleDest = anglePt1 + ( isClockwise ? -theta : theta );
530
531 const double x = centerX + radius * ( std::cos( angleDest ) );
532 const double y = centerY + radius * ( std::sin( angleDest ) );
533
534 const double z = pt1.is3D() ?
535 QgsGeometryUtilsBase::interpolateArcValue( angleDest, anglePt1, anglePt2, anglePt3, pt1.z(), pt2.z(), pt3.z() )
536 : 0;
537 const double m = pt1.isMeasure() ?
538 QgsGeometryUtilsBase::interpolateArcValue( angleDest, anglePt1, anglePt2, anglePt3, pt1.m(), pt2.m(), pt3.m() )
539 : 0;
540
541 return QgsPoint( pt1.wkbType(), x, y, z, m );
542}
543
544QgsPoint QgsGeometryUtils::interpolatePointOnCubicBezier( const QgsPoint &p0, const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, double t )
545{
546 const bool hasZ = p0.is3D() && p1.is3D() && p2.is3D() && p3.is3D();
547 const bool hasM = p0.isMeasure() && p1.isMeasure() && p2.isMeasure() && p3.isMeasure();
548
549 double x, y;
550 double z = std::numeric_limits<double>::quiet_NaN();
551 double m = std::numeric_limits<double>::quiet_NaN();
552
554 p1.x(), p1.y(), p1.z(), p1.m(),
555 p2.x(), p2.y(), p2.z(), p2.m(),
556 p3.x(), p3.y(), p3.z(), p3.m(),
557 t, hasZ, hasM,
558 x, y, z, m );
559
561 if ( hasZ && hasM )
562 wkbType = Qgis::WkbType::PointZM;
563 else if ( hasZ )
564 wkbType = Qgis::WkbType::PointZ;
565 else if ( hasM )
566 wkbType = Qgis::WkbType::PointM;
567
568 return QgsPoint( wkbType, x, y, z, m );
569}
570
572 double p0x, double p0y, double p0z, double p0m,
573 double p1x, double p1y, double p1z, double p1m,
574 double p2x, double p2y, double p2z, double p2m,
575 double p3x, double p3y, double p3z, double p3m,
576 double t, bool hasZ, bool hasM,
577 double &outX, double &outY, double &outZ, double &outM )
578{
579 // Cubic Bézier formula: B(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃
580 const double t1 = 1.0 - t;
581 const double t1_2 = t1 * t1;
582 const double t1_3 = t1_2 * t1;
583 const double t_2 = t * t;
584 const double t_3 = t_2 * t;
585
586 outX = t1_3 * p0x + 3.0 * t1_2 * t * p1x + 3.0 * t1 * t_2 * p2x + t_3 * p3x;
587 outY = t1_3 * p0y + 3.0 * t1_2 * t * p1y + 3.0 * t1 * t_2 * p2y + t_3 * p3y;
588
589 if ( hasZ )
590 {
591 outZ = t1_3 * p0z + 3.0 * t1_2 * t * p1z + 3.0 * t1 * t_2 * p2z + t_3 * p3z;
592 }
593
594 if ( hasM )
595 {
596 outM = t1_3 * p0m + 3.0 * t1_2 * t * p1m + 3.0 * t1 * t_2 * p2m + t_3 * p3m;
597 }
598}
599
600bool QgsGeometryUtils::segmentMidPoint( const QgsPoint &p1, const QgsPoint &p2, QgsPoint &result, double radius, const QgsPoint &mousePos )
601{
602 const QgsPoint midPoint( ( p1.x() + p2.x() ) / 2.0, ( p1.y() + p2.y() ) / 2.0 );
603 const double midDist = std::sqrt( sqrDistance2D( p1, midPoint ) );
604 if ( radius < midDist )
605 {
606 return false;
607 }
608 const double centerMidDist = std::sqrt( radius * radius - midDist * midDist );
609 const double dist = radius - centerMidDist;
610
611 const double midDx = midPoint.x() - p1.x();
612 const double midDy = midPoint.y() - p1.y();
613
614 //get the four possible midpoints
615 QVector<QgsPoint> possibleMidPoints;
616 possibleMidPoints.append( pointOnLineWithDistance( midPoint, QgsPoint( midPoint.x() - midDy, midPoint.y() + midDx ), dist ) );
617 possibleMidPoints.append( pointOnLineWithDistance( midPoint, QgsPoint( midPoint.x() - midDy, midPoint.y() + midDx ), 2 * radius - dist ) );
618 possibleMidPoints.append( pointOnLineWithDistance( midPoint, QgsPoint( midPoint.x() + midDy, midPoint.y() - midDx ), dist ) );
619 possibleMidPoints.append( pointOnLineWithDistance( midPoint, QgsPoint( midPoint.x() + midDy, midPoint.y() - midDx ), 2 * radius - dist ) );
620
621 //take the closest one
622 double minDist = std::numeric_limits<double>::max();
623 int minDistIndex = -1;
624 for ( int i = 0; i < possibleMidPoints.size(); ++i )
625 {
626 const double currentDist = sqrDistance2D( mousePos, possibleMidPoints.at( i ) );
627 if ( currentDist < minDist )
628 {
629 minDistIndex = i;
630 minDist = currentDist;
631 }
632 }
633
634 if ( minDistIndex == -1 )
635 {
636 return false;
637 }
638
639 result = possibleMidPoints.at( minDistIndex );
640
641 // add z and m support if necessary
643
644 return true;
645}
646
647QgsPoint QgsGeometryUtils::segmentMidPointFromCenter( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &center, const bool useShortestArc )
648{
649 double midPointAngle = QgsGeometryUtilsBase::averageAngle( QgsGeometryUtilsBase::lineAngle( center.x(), center.y(), p1.x(), p1.y() ),
650 QgsGeometryUtilsBase::lineAngle( center.x(), center.y(), p2.x(), p2.y() ) );
651 if ( !useShortestArc )
652 midPointAngle += M_PI;
653 return center.project( center.distance( p1 ), midPointAngle * 180 / M_PI );
654}
655
656double QgsGeometryUtils::circleTangentDirection( const QgsPoint &tangentPoint, const QgsPoint &cp1,
657 const QgsPoint &cp2, const QgsPoint &cp3 )
658{
659 //calculate circle midpoint
660 double mX, mY, radius;
661 circleCenterRadius( cp1, cp2, cp3, radius, mX, mY );
662
663 const double p1Angle = QgsGeometryUtilsBase::ccwAngle( cp1.y() - mY, cp1.x() - mX );
664 const double p2Angle = QgsGeometryUtilsBase::ccwAngle( cp2.y() - mY, cp2.x() - mX );
665 const double p3Angle = QgsGeometryUtilsBase::ccwAngle( cp3.y() - mY, cp3.x() - mX );
666 double angle = 0;
667 if ( QgsGeometryUtilsBase::circleClockwise( p1Angle, p2Angle, p3Angle ) )
668 {
669 angle = QgsGeometryUtilsBase::lineAngle( tangentPoint.x(), tangentPoint.y(), mX, mY ) - M_PI_2;
670 }
671 else
672 {
673 angle = QgsGeometryUtilsBase::lineAngle( mX, mY, tangentPoint.x(), tangentPoint.y() ) - M_PI_2;
674 }
675 if ( angle < 0 )
676 angle += 2 * M_PI;
677 return angle;
678}
679
680// Ported from PostGIS' pt_continues_arc
681bool QgsGeometryUtils::pointContinuesArc( const QgsPoint &a1, const QgsPoint &a2, const QgsPoint &a3, const QgsPoint &b, double distanceTolerance, double pointSpacingAngleTolerance )
682{
683 double centerX = 0;
684 double centerY = 0;
685 double radius = 0;
686 circleCenterRadius( a1, a2, a3, radius, centerX, centerY );
687
688 // Co-linear a1/a2/a3
689 if ( radius < 0.0 )
690 return false;
691
692 // distance of candidate point to center of arc a1-a2-a3
693 const double bDistance = std::sqrt( ( b.x() - centerX ) * ( b.x() - centerX ) +
694 ( b.y() - centerY ) * ( b.y() - centerY ) );
695
696 double diff = std::fabs( radius - bDistance );
697
698 auto arcAngle = []( const QgsPoint & a, const QgsPoint & b, const QgsPoint & c )->double
699 {
700 const double abX = b.x() - a.x();
701 const double abY = b.y() - a.y();
702
703 const double cbX = b.x() - c.x();
704 const double cbY = b.y() - c.y();
705
706 const double dot = ( abX * cbX + abY * cbY ); /* dot product */
707 const double cross = ( abX * cbY - abY * cbX ); /* cross product */
708
709 const double alpha = std::atan2( cross, dot );
710
711 return alpha;
712 };
713
714 // Is the point b on the circle?
715 if ( diff < distanceTolerance )
716 {
717 const double angle1 = arcAngle( a1, a2, a3 );
718 const double angle2 = arcAngle( a2, a3, b );
719
720 // Is the sweep angle similar to the previous one?
721 // We only consider a segment replaceable by an arc if the points within
722 // it are regularly spaced
723 diff = std::fabs( angle1 - angle2 );
724 if ( diff > pointSpacingAngleTolerance )
725 {
726 return false;
727 }
728
729 const int a2Side = QgsGeometryUtilsBase::leftOfLine( a2.x(), a2.y(), a1.x(), a1.y(), a3.x(), a3.y() );
730 const int bSide = QgsGeometryUtilsBase::leftOfLine( b.x(), b.y(), a1.x(), a1.y(), a3.x(), a3.y() );
731
732 // Is the point b on the same side of a1/a3 as the mid-point a2 is?
733 // If not, it's in the unbounded part of the circle, so it continues the arc, return true.
734 if ( bSide != a2Side )
735 return true;
736 }
737 return false;
738}
739
740void QgsGeometryUtils::segmentizeArc( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, QgsPointSequence &points, double tolerance, QgsAbstractGeometry::SegmentationToleranceType toleranceType, bool hasZ, bool hasM )
741{
742 bool reversed = false;
743 const int segSide = segmentSide( p1, p3, p2 );
744
745 QgsPoint circlePoint1;
746 const QgsPoint &circlePoint2 = p2;
747 QgsPoint circlePoint3;
748
749 if ( segSide == -1 )
750 {
751 // Reverse !
752 circlePoint1 = p3;
753 circlePoint3 = p1;
754 reversed = true;
755 }
756 else
757 {
758 circlePoint1 = p1;
759 circlePoint3 = p3;
760 }
761
762 //adapted code from PostGIS
763 double radius = 0;
764 double centerX = 0;
765 double centerY = 0;
766 circleCenterRadius( circlePoint1, circlePoint2, circlePoint3, radius, centerX, centerY );
767
768 if ( circlePoint1 != circlePoint3 && ( radius < 0 || qgsDoubleNear( segSide, 0.0 ) ) ) //points are colinear
769 {
770 points.append( p1 );
771 points.append( p2 );
772 points.append( p3 );
773 return;
774 }
775
776 double increment = tolerance; //one segment per degree
777 if ( toleranceType == QgsAbstractGeometry::MaximumDifference )
778 {
779 // Ensure tolerance is not higher than twice the radius
780 tolerance = std::min( tolerance, radius * 2 );
781 const double halfAngle = std::acos( -tolerance / radius + 1 );
782 increment = 2 * halfAngle;
783 }
784
785 //angles of pt1, pt2, pt3
786 const double a1 = std::atan2( circlePoint1.y() - centerY, circlePoint1.x() - centerX );
787 double a2 = std::atan2( circlePoint2.y() - centerY, circlePoint2.x() - centerX );
788 double a3 = std::atan2( circlePoint3.y() - centerY, circlePoint3.x() - centerX );
789
790 // Make segmentation symmetric
791 const bool symmetric = true;
792 if ( symmetric )
793 {
794 double angle = a3 - a1;
795 // angle == 0 when full circle
796 if ( angle <= 0 ) angle += M_PI * 2;
797
798 /* Number of segments in output */
799 const int segs = ceil( angle / increment );
800 /* Tweak increment to be regular for all the arc */
801 increment = angle / segs;
802 }
803
804 /* Adjust a3 up so we can increment from a1 to a3 cleanly */
805 // a3 == a1 when full circle
806 if ( a3 <= a1 )
807 a3 += 2.0 * M_PI;
808 if ( a2 < a1 )
809 a2 += 2.0 * M_PI;
810
811 double x, y;
812 double z = 0;
813 double m = 0;
814
815 QVector<QgsPoint> stringPoints;
816 stringPoints.insert( 0, circlePoint1 );
817 if ( circlePoint2 != circlePoint3 && circlePoint1 != circlePoint2 ) //draw straight line segment if two points have the same position
818 {
819 Qgis::WkbType pointWkbType = Qgis::WkbType::Point;
820 if ( hasZ )
821 pointWkbType = QgsWkbTypes::addZ( pointWkbType );
822 if ( hasM )
823 pointWkbType = QgsWkbTypes::addM( pointWkbType );
824
825 // As we're adding the last point in any case, we'll avoid
826 // including a point which is at less than 1% increment distance
827 // from it (may happen to find them due to numbers approximation).
828 // NOTE that this effectively allows in output some segments which
829 // are more distant than requested. This is at most 1% off
830 // from requested MaxAngle and less for MaxError.
831 const double tolError = increment / 100;
832 const double stopAngle = a3 - tolError;
833 for ( double angle = a1 + increment; angle < stopAngle; angle += increment )
834 {
835 x = centerX + radius * std::cos( angle );
836 y = centerY + radius * std::sin( angle );
837
838 if ( hasZ )
839 {
840 z = QgsGeometryUtilsBase::interpolateArcValue( angle, a1, a2, a3, circlePoint1.z(), circlePoint2.z(), circlePoint3.z() );
841 }
842 if ( hasM )
843 {
844 m = QgsGeometryUtilsBase::interpolateArcValue( angle, a1, a2, a3, circlePoint1.m(), circlePoint2.m(), circlePoint3.m() );
845 }
846
847 stringPoints.insert( stringPoints.size(), QgsPoint( pointWkbType, x, y, z, m ) );
848 }
849 }
850 stringPoints.insert( stringPoints.size(), circlePoint3 );
851
852 // TODO: check if or implement QgsPointSequence directly taking an iterator to append
853 if ( reversed )
854 {
855 std::reverse( stringPoints.begin(), stringPoints.end() );
856 }
857 if ( ! points.empty() && stringPoints.front() == points.back() ) stringPoints.pop_front();
858 points.append( stringPoints );
859}
860
861int QgsGeometryUtils::segmentSide( const QgsPoint &pt1, const QgsPoint &pt3, const QgsPoint &pt2 )
862{
863 const double side = ( ( pt2.x() - pt1.x() ) * ( pt3.y() - pt1.y() ) - ( pt3.x() - pt1.x() ) * ( pt2.y() - pt1.y() ) );
864 if ( side == 0.0 )
865 {
866 return 0;
867 }
868 else
869 {
870 if ( side < 0 )
871 {
872 return -1;
873 }
874 if ( side > 0 )
875 {
876 return 1;
877 }
878 return 0;
879 }
880}
881
882QgsPointSequence QgsGeometryUtils::pointsFromWKT( const QString &wktCoordinateList, bool is3D, bool isMeasure )
883{
884 const int dim = 2 + is3D + isMeasure;
885 QgsPointSequence points;
886
887 const QStringList coordList = wktCoordinateList.split( ',', Qt::SkipEmptyParts );
888
889 //first scan through for extra unexpected dimensions
890 bool foundZ = false;
891 bool foundM = false;
892 const thread_local QRegularExpression rx( u"\\s"_s );
893 const thread_local QRegularExpression rxIsNumber( u"^[+-]?(\\d\\.?\\d*[Ee][+\\-]?\\d+|(\\d+\\.\\d*|\\d*\\.\\d+)|\\d+)$"_s );
894 for ( const QString &pointCoordinates : coordList )
895 {
896 const QStringList coordinates = pointCoordinates.split( rx, Qt::SkipEmptyParts );
897
898 // exit with an empty set if one list contains invalid value.
899 if ( coordinates.filter( rxIsNumber ).size() != coordinates.size() )
900 return points;
901
902 if ( coordinates.size() == 3 && !foundZ && !foundM && !is3D && !isMeasure )
903 {
904 // 3 dimensional coordinates, but not specifically marked as such. We allow this
905 // anyway and upgrade geometry to have Z dimension
906 foundZ = true;
907 }
908 else if ( coordinates.size() >= 4 && ( !( is3D || foundZ ) || !( isMeasure || foundM ) ) )
909 {
910 // 4 (or more) dimensional coordinates, but not specifically marked as such. We allow this
911 // anyway and upgrade geometry to have Z&M dimensions
912 foundZ = true;
913 foundM = true;
914 }
915 }
916
917 for ( const QString &pointCoordinates : coordList )
918 {
919 QStringList coordinates = pointCoordinates.split( rx, Qt::SkipEmptyParts );
920 if ( coordinates.size() < dim )
921 continue;
922
923 int idx = 0;
924 const double x = coordinates[idx++].toDouble();
925 const double y = coordinates[idx++].toDouble();
926
927 double z = 0;
928 if ( ( is3D || foundZ ) && coordinates.length() > idx )
929 z = coordinates[idx++].toDouble();
930
931 double m = 0;
932 if ( ( isMeasure || foundM ) && coordinates.length() > idx )
933 m = coordinates[idx++].toDouble();
934
936 if ( is3D || foundZ )
937 {
938 if ( isMeasure || foundM )
940 else
942 }
943 else
944 {
945 if ( isMeasure || foundM )
947 else
949 }
950
951 points.append( QgsPoint( t, x, y, z, m ) );
952 }
953
954 return points;
955}
956
957void QgsGeometryUtils::pointsToWKB( QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure, QgsAbstractGeometry::WkbFlags flags )
958{
959 wkb << static_cast<quint32>( points.size() );
960 for ( const QgsPoint &point : points )
961 {
962 wkb << point.x() << point.y();
963 if ( is3D )
964 {
965 double z = point.z();
967 && std::isnan( z ) )
968 z = -std::numeric_limits<double>::max();
969
970 wkb << z;
971 }
972 if ( isMeasure )
973 {
974 double m = point.m();
976 && std::isnan( m ) )
977 m = -std::numeric_limits<double>::max();
978
979 wkb << m;
980 }
981 }
982}
983
984QString QgsGeometryUtils::pointsToWKT( const QgsPointSequence &points, int precision, bool is3D, bool isMeasure )
985{
986 QString wkt = u"("_s;
987 for ( const QgsPoint &p : points )
988 {
989 wkt += qgsDoubleToString( p.x(), precision );
990 wkt += ' ' + qgsDoubleToString( p.y(), precision );
991 if ( is3D )
992 wkt += ' ' + qgsDoubleToString( p.z(), precision );
993 if ( isMeasure )
994 wkt += ' ' + qgsDoubleToString( p.m(), precision );
995 wkt += ", "_L1;
996 }
997 if ( wkt.endsWith( ", "_L1 ) )
998 wkt.chop( 2 ); // Remove last ", "
999 wkt += ')';
1000 return wkt;
1001}
1002
1003QDomElement QgsGeometryUtils::pointsToGML2( const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, QgsAbstractGeometry::AxisOrder axisOrder )
1004{
1005 QDomElement elemCoordinates = doc.createElementNS( ns, u"coordinates"_s );
1006
1007 // coordinate separator
1008 const QString cs = u","_s;
1009 // tuple separator
1010 const QString ts = u" "_s;
1011
1012 elemCoordinates.setAttribute( u"cs"_s, cs );
1013 elemCoordinates.setAttribute( u"ts"_s, ts );
1014
1015 QString strCoordinates;
1016
1017 for ( const QgsPoint &p : points )
1018 if ( axisOrder == QgsAbstractGeometry::AxisOrder::XY )
1019 strCoordinates += qgsDoubleToString( p.x(), precision ) + cs + qgsDoubleToString( p.y(), precision ) + ts;
1020 else
1021 strCoordinates += qgsDoubleToString( p.y(), precision ) + cs + qgsDoubleToString( p.x(), precision ) + ts;
1022
1023 if ( strCoordinates.endsWith( ts ) )
1024 strCoordinates.chop( 1 ); // Remove trailing space
1025
1026 elemCoordinates.appendChild( doc.createTextNode( strCoordinates ) );
1027 return elemCoordinates;
1028}
1029
1030QDomElement QgsGeometryUtils::pointsToGML3( const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, bool is3D, QgsAbstractGeometry::AxisOrder axisOrder )
1031{
1032 QDomElement elemPosList = doc.createElementNS( ns, u"posList"_s );
1033 elemPosList.setAttribute( u"srsDimension"_s, is3D ? 3 : 2 );
1034
1035 QString strCoordinates;
1036 for ( const QgsPoint &p : points )
1037 {
1038 if ( axisOrder == QgsAbstractGeometry::AxisOrder::XY )
1039 strCoordinates += qgsDoubleToString( p.x(), precision ) + ' ' + qgsDoubleToString( p.y(), precision ) + ' ';
1040 else
1041 strCoordinates += qgsDoubleToString( p.y(), precision ) + ' ' + qgsDoubleToString( p.x(), precision ) + ' ';
1042 if ( is3D )
1043 strCoordinates += qgsDoubleToString( p.z(), precision ) + ' ';
1044 }
1045 if ( strCoordinates.endsWith( ' ' ) )
1046 strCoordinates.chop( 1 ); // Remove trailing space
1047
1048 elemPosList.appendChild( doc.createTextNode( strCoordinates ) );
1049 return elemPosList;
1050}
1051
1052QString QgsGeometryUtils::pointsToJSON( const QgsPointSequence &points, int precision )
1053{
1054 QString json = u"[ "_s;
1055 for ( const QgsPoint &p : points )
1056 {
1057 json += '[' + qgsDoubleToString( p.x(), precision ) + ", "_L1 + qgsDoubleToString( p.y(), precision ) + "], "_L1;
1058 }
1059 if ( json.endsWith( ", "_L1 ) )
1060 {
1061 json.chop( 2 ); // Remove last ", "
1062 }
1063 json += ']';
1064 return json;
1065}
1066
1067
1068json QgsGeometryUtils::pointsToJson( const QgsPointSequence &points, int precision )
1069{
1070 json coordinates( json::array() );
1071 for ( const QgsPoint &p : points )
1072 {
1073 if ( p.is3D() )
1074 {
1075 coordinates.push_back( { qgsRound( p.x(), precision ), qgsRound( p.y(), precision ), qgsRound( p.z(), precision ) } );
1076 }
1077 else
1078 {
1079 coordinates.push_back( { qgsRound( p.x(), precision ), qgsRound( p.y(), precision ) } );
1080 }
1081 }
1082 return coordinates;
1083}
1084
1085QPair<Qgis::WkbType, QString> QgsGeometryUtils::wktReadBlock( const QString &wkt )
1086{
1087 QString wktParsed = wkt;
1088 QString contents;
1089 bool isEmpty = false;
1090 const QLatin1String empty { "EMPTY" };
1091 if ( wkt.contains( empty, Qt::CaseInsensitive ) )
1092 {
1093 const thread_local QRegularExpression whiteSpaces( "\\s" );
1094 wktParsed.remove( whiteSpaces );
1095 const int index = wktParsed.indexOf( empty, 0, Qt::CaseInsensitive );
1096
1097 if ( index == wktParsed.length() - empty.size() )
1098 {
1099 // "EMPTY" found at the end of the QString
1100 // Extract the part of the QString to the left of "EMPTY"
1101 wktParsed = wktParsed.left( index );
1102 contents = empty;
1103 isEmpty = true;
1104 }
1105 else
1106 {
1107 wktParsed = wkt; // reset to original content
1108 }
1109 }
1110 if ( !isEmpty )
1111 {
1112 const int openedParenthesisCount = wktParsed.count( '(' );
1113 const int closedParenthesisCount = wktParsed.count( ')' );
1114 // closes missing parentheses
1115 for ( int i = 0 ; i < openedParenthesisCount - closedParenthesisCount; ++i )
1116 wktParsed.push_back( ')' );
1117 // removes extra parentheses
1118 wktParsed.truncate( wktParsed.size() - ( closedParenthesisCount - openedParenthesisCount ) );
1119
1120 const thread_local QRegularExpression cooRegEx( u"^[^\\(]*\\((.*)\\)[^\\)]*$"_s, QRegularExpression::DotMatchesEverythingOption );
1121 const QRegularExpressionMatch match = cooRegEx.match( wktParsed );
1122 contents = match.hasMatch() ? match.captured( 1 ) : QString();
1123 }
1124 const Qgis::WkbType wkbType = QgsWkbTypes::parseType( wktParsed );
1125 return qMakePair( wkbType, contents );
1126}
1127
1128QStringList QgsGeometryUtils::wktGetChildBlocks( const QString &wkt, const QString &defaultType )
1129{
1130 int level = 0;
1131 QString block;
1132 block.reserve( wkt.size() );
1133 QStringList blocks;
1134
1135 const QChar *wktData = wkt.data();
1136 const int wktLength = wkt.length();
1137 for ( int i = 0, n = wktLength; i < n; ++i, ++wktData )
1138 {
1139 if ( ( wktData->isSpace() || *wktData == '\n' || *wktData == '\t' ) && level == 0 )
1140 continue;
1141
1142 if ( *wktData == ',' && level == 0 )
1143 {
1144 if ( !block.isEmpty() )
1145 {
1146 if ( block.startsWith( '(' ) && !defaultType.isEmpty() )
1147 block.prepend( defaultType + ' ' );
1148 blocks.append( block );
1149 }
1150 block.resize( 0 );
1151 continue;
1152 }
1153 if ( *wktData == '(' )
1154 ++level;
1155 else if ( *wktData == ')' )
1156 --level;
1157 block += *wktData;
1158 }
1159 if ( !block.isEmpty() )
1160 {
1161 if ( block.startsWith( '(' ) && !defaultType.isEmpty() )
1162 block.prepend( defaultType + ' ' );
1163 blocks.append( block );
1164 }
1165 return blocks;
1166}
1167
1169{
1171
1172
1173 const double x = ( pt1.x() + pt2.x() ) / 2.0;
1174 const double y = ( pt1.y() + pt2.y() ) / 2.0;
1175 double z = std::numeric_limits<double>::quiet_NaN();
1176 double m = std::numeric_limits<double>::quiet_NaN();
1177
1178 if ( pt1.is3D() || pt2.is3D() )
1179 {
1180 pType = QgsWkbTypes::addZ( pType );
1181 z = ( pt1.z() + pt2.z() ) / 2.0;
1182 }
1183
1184 if ( pt1.isMeasure() || pt2.isMeasure() )
1185 {
1186 pType = QgsWkbTypes::addM( pType );
1187 m = ( pt1.m() + pt2.m() ) / 2.0;
1188 }
1189
1190 return QgsPoint( pType, x, y, z, m );
1191}
1192
1193QgsPoint QgsGeometryUtils::interpolatePointOnLine( const QgsPoint &p1, const QgsPoint &p2, const double fraction )
1194{
1195 const double _fraction = 1 - fraction;
1196 return QgsPoint( p1.wkbType(),
1197 p1.x() * _fraction + p2.x() * fraction,
1198 p1.y() * _fraction + p2.y() * fraction,
1199 p1.is3D() ? p1.z() * _fraction + p2.z() * fraction : std::numeric_limits<double>::quiet_NaN(),
1200 p1.isMeasure() ? p1.m() * _fraction + p2.m() * fraction : std::numeric_limits<double>::quiet_NaN() );
1201}
1202
1203QgsPointXY QgsGeometryUtils::interpolatePointOnLine( const double x1, const double y1, const double x2, const double y2, const double fraction )
1204{
1205 const double deltaX = ( x2 - x1 ) * fraction;
1206 const double deltaY = ( y2 - y1 ) * fraction;
1207 return QgsPointXY( x1 + deltaX, y1 + deltaY );
1208}
1209
1210QgsPointXY QgsGeometryUtils::interpolatePointOnLineByValue( const double x1, const double y1, const double v1, const double x2, const double y2, const double v2, const double value )
1211{
1212 if ( qgsDoubleNear( v1, v2 ) )
1213 return QgsPointXY( x1, y1 );
1214
1215 const double fraction = ( value - v1 ) / ( v2 - v1 );
1216 return interpolatePointOnLine( x1, y1, x2, y2, fraction );
1217}
1218
1219double QgsGeometryUtils::gradient( const QgsPoint &pt1, const QgsPoint &pt2 )
1220{
1221 const double delta_x = pt2.x() - pt1.x();
1222 const double delta_y = pt2.y() - pt1.y();
1223 if ( qgsDoubleNear( delta_x, 0.0 ) )
1224 {
1225 return INFINITY;
1226 }
1227
1228 return delta_y / delta_x;
1229}
1230
1231void QgsGeometryUtils::coefficients( const QgsPoint &pt1, const QgsPoint &pt2, double &a, double &b, double &c )
1232{
1233 if ( qgsDoubleNear( pt1.x(), pt2.x() ) )
1234 {
1235 a = 1;
1236 b = 0;
1237 c = -pt1.x();
1238 }
1239 else if ( qgsDoubleNear( pt1.y(), pt2.y() ) )
1240 {
1241 a = 0;
1242 b = 1;
1243 c = -pt1.y();
1244 }
1245 else
1246 {
1247 a = pt1.y() - pt2.y();
1248 b = pt2.x() - pt1.x();
1249 c = pt1.x() * pt2.y() - pt1.y() * pt2.x();
1250 }
1251
1252}
1253
1255{
1256 QgsLineString line;
1257 QgsPoint p2;
1258
1259 if ( ( p == s1 ) || ( p == s2 ) )
1260 {
1261 return line;
1262 }
1263
1264 double a, b, c;
1265 coefficients( s1, s2, a, b, c );
1266
1267 if ( qgsDoubleNear( a, 0 ) )
1268 {
1269 p2 = QgsPoint( p.x(), s1.y() );
1270 }
1271 else if ( qgsDoubleNear( b, 0 ) )
1272 {
1273 p2 = QgsPoint( s1.x(), p.y() );
1274 }
1275 else
1276 {
1277 const double y = ( -c - a * p.x() ) / b;
1278 const double m = gradient( s1, s2 );
1279 const double d2 = 1 + m * m;
1280 const double H = p.y() - y;
1281 const double dx = m * H / d2;
1282 const double dy = m * dx;
1283 p2 = QgsPoint( p.x() + dx, y + dy );
1284 }
1285
1286 line.addVertex( p );
1287 line.addVertex( p2 );
1288
1289 return line;
1290}
1291
1293{
1294 bool rc = false;
1295
1296 for ( const QgsPoint &pt : points )
1297 {
1298 if ( pt.isMeasure() )
1299 {
1300 point.convertTo( QgsWkbTypes::addM( point.wkbType() ) );
1301 point.setM( pt.m() );
1302 rc = true;
1303 break;
1304 }
1305 }
1306
1307 return rc;
1308}
1309
1311{
1312 bool rc = false;
1313
1314 for ( const QgsPoint &pt : points )
1315 {
1316 if ( pt.is3D() )
1317 {
1318 point.convertTo( QgsWkbTypes::addZ( point.wkbType() ) );
1319 point.setZ( pt.z() );
1320 rc = true;
1321 break;
1322 }
1323 }
1324
1325 return rc;
1326}
1327
1329{
1330 if ( reference.is3D() && reference.isMeasure() )
1331 return QgsPoint( Qgis::WkbType::PointZM, x, y, 0.0, 0.0 );
1332 else if ( reference.is3D() )
1333 return QgsPoint( Qgis::WkbType::PointZ, x, y, 0.0 );
1334 else if ( reference.isMeasure() )
1335 return QgsPoint( Qgis::WkbType::PointM, x, y, 0.0, 0.0 );
1336 else
1337 return QgsPoint( x, y );
1338}
1339
1341 const QgsPoint &segmentStart, const QgsPoint &segmentEnd )
1342{
1343 QgsPoint result = createPointWithMatchingDimensions( x, y, segmentStart );
1344 const double distanceFromStart = QgsGeometryUtilsBase::distance2D( segmentStart.x(), segmentStart.y(), x, y );
1345
1346 if ( segmentStart.is3D() && segmentEnd.is3D() )
1347 {
1348 double z1 = segmentStart.z();
1349 double z2 = segmentEnd.z();
1350 double interpolatedZ;
1351 double tempX, tempY;
1353 segmentStart.x(), segmentStart.y(), segmentEnd.x(), segmentEnd.y(),
1354 distanceFromStart, tempX, tempY, &z1, &z2, &interpolatedZ );
1355 result.setZ( interpolatedZ );
1356 }
1357
1358 if ( segmentStart.isMeasure() && segmentEnd.isMeasure() )
1359 {
1360 double m1 = segmentStart.m();
1361 double m2 = segmentEnd.m();
1362 double interpolatedM;
1363 double tempX, tempY;
1365 segmentStart.x(), segmentStart.y(), segmentEnd.x(), segmentEnd.y(),
1366 distanceFromStart, tempX, tempY, nullptr, nullptr, nullptr, &m1, &m2, &interpolatedM );
1367 result.setM( interpolatedM );
1368 }
1369
1370 return result;
1371}
1372
1373bool QgsGeometryUtils::createChamfer( const QgsPoint &segment1Start, const QgsPoint &segment1End,
1374 const QgsPoint &segment2Start, const QgsPoint &segment2End,
1375 double distance1, double distance2,
1376 QgsPoint &chamferStart, QgsPoint &chamferEnd,
1377 double epsilon )
1378{
1379 // Create chamfer points using the utility function
1380 double chamferStartX, chamferStartY, chamferEndX, chamferEndY;
1381
1383 segment1Start.x(), segment1Start.y(), segment1End.x(), segment1End.y(),
1384 segment2Start.x(), segment2Start.y(), segment2End.x(), segment2End.y(),
1385 distance1, distance2,
1386 chamferStartX, chamferStartY,
1387 chamferEndX, chamferEndY,
1388 nullptr, nullptr, nullptr, nullptr,
1389 nullptr, nullptr, nullptr, nullptr,
1390 epsilon
1391 );
1392
1393 chamferStart = interpolatePointOnSegment( chamferStartX, chamferStartY, segment1Start, segment1End );
1394 chamferEnd = interpolatePointOnSegment( chamferEndX, chamferEndY, segment2Start, segment2End );
1395
1396 return true;
1397}
1398
1399bool QgsGeometryUtils::createFillet( const QgsPoint &segment1Start, const QgsPoint &segment1End,
1400 const QgsPoint &segment2Start, const QgsPoint &segment2End,
1401 double radius,
1402 QgsPoint &filletPoint1,
1403 QgsPoint &filletMidPoint,
1404 QgsPoint &filletPoint2,
1405 double epsilon )
1406{
1407 // Create fillet arc using the utility function
1408 double filletPointsX[3], filletPointsY[3];
1409
1411 segment1Start.x(), segment1Start.y(), segment1End.x(), segment1End.y(),
1412 segment2Start.x(), segment2Start.y(), segment2End.x(), segment2End.y(),
1413 radius,
1414 filletPointsX, filletPointsY,
1415 nullptr, nullptr, nullptr, nullptr,
1416 nullptr, nullptr, nullptr, nullptr,
1417 epsilon
1418 );
1419
1420 filletPoint1 = interpolatePointOnSegment( filletPointsX[0], filletPointsY[0], segment1Start, segment1End );
1421 filletMidPoint = createPointWithMatchingDimensions( filletPointsX[1], filletPointsY[1], segment1Start );
1422 filletPoint2 = interpolatePointOnSegment( filletPointsX[2], filletPointsY[2], segment2Start, segment2End );
1423
1424 // Interpolate Z and M for midpoint
1425 if ( segment1Start.is3D() && segment1End.is3D() && segment2Start.is3D() && segment2End.is3D() )
1426 {
1427 filletMidPoint.setZ( ( filletPoint1.z() + filletPoint2.z() ) / 2.0 );
1428 }
1429 if ( segment1Start.isMeasure() && segment1End.isMeasure() && segment2Start.isMeasure() && segment2End.isMeasure() )
1430 {
1431 filletMidPoint.setM( ( filletPoint1.m() + filletPoint2.m() ) / 2.0 );
1432 }
1433
1434 return true;
1435}
1436
1437bool QgsGeometryUtils::createFilletArray( const QgsPoint &segment1Start, const QgsPoint &segment1End,
1438 const QgsPoint &segment2Start, const QgsPoint &segment2End,
1439 double radius,
1440 QgsPoint filletPoints[3],
1441 double epsilon )
1442{
1443 QgsPoint p1, p2, p3;
1444 createFillet( segment1Start, segment1End, segment2Start, segment2End, radius, p1, p2, p3, epsilon );
1445 filletPoints[0] = p1;
1446 filletPoints[1] = p2;
1447 filletPoints[2] = p3;
1448 return true;
1449}
1450
1451std::unique_ptr<QgsLineString> QgsGeometryUtils::createChamferGeometry(
1452 const QgsPoint &segment1Start, const QgsPoint &segment1End,
1453 const QgsPoint &segment2Start, const QgsPoint &segment2End,
1454 double distance1, double distance2 )
1455{
1456 QgsPoint chamferStart, chamferEnd;
1457 createChamfer( segment1Start, segment1End, segment2Start, segment2End, distance1, distance2, chamferStart, chamferEnd );
1458
1459 return std::make_unique<QgsLineString>(
1460 QVector<QgsPoint> { segment1Start, chamferStart, chamferEnd, segment2Start } );
1461}
1462
1463std::unique_ptr<QgsAbstractGeometry> QgsGeometryUtils::createFilletGeometry(
1464 const QgsPoint &segment1Start, const QgsPoint &segment1End,
1465 const QgsPoint &segment2Start, const QgsPoint &segment2End,
1466 double radius, int segments )
1467{
1468 QgsPoint filletPoints[3];
1469 createFilletArray( segment1Start, segment1End, segment2Start, segment2End, radius, filletPoints );
1470
1471 // Calculate far endpoints for complete geometry
1472 double intersectionX, intersectionY;
1473 bool isIntersection;
1475 segment1Start.x(), segment1Start.y(), segment1End.x(), segment1End.y(),
1476 segment2Start.x(), segment2Start.y(), segment2End.x(), segment2End.y(),
1477 intersectionX, intersectionY, isIntersection, 1e-8, true );
1478
1479 if ( !isIntersection )
1480 {
1481 throw QgsInvalidArgumentException( "Segments do not intersect." );
1482 }
1483
1484 const double dist1ToStart = QgsGeometryUtilsBase::distance2D( intersectionX, intersectionY, segment1Start.x(), segment1Start.y() );
1485 const double dist1ToEnd = QgsGeometryUtilsBase::distance2D( intersectionX, intersectionY, segment1End.x(), segment1End.y() );
1486 const double dist2ToStart = QgsGeometryUtilsBase::distance2D( intersectionX, intersectionY, segment2Start.x(), segment2Start.y() );
1487 const double dist2ToEnd = QgsGeometryUtilsBase::distance2D( intersectionX, intersectionY, segment2End.x(), segment2End.y() );
1488
1489 const QgsPoint segment1FarEnd = ( dist1ToStart < dist1ToEnd ) ? segment1End : segment1Start;
1490 const QgsPoint segment2FarEnd = ( dist2ToStart < dist2ToEnd ) ? segment2End : segment2Start;
1491
1492 if ( segments <= 0 )
1493 {
1494 // Return CompoundCurve with circular arc
1495 auto completeCurve = std::make_unique<QgsCompoundCurve>();
1496
1497 // First linear segment
1498 auto firstSegment = std::make_unique<QgsLineString>(
1499 QVector<QgsPoint> { segment1FarEnd, filletPoints[0] } );
1500 completeCurve->addCurve( firstSegment.release() );
1501
1502 // Circular arc segment
1503 auto circularString = std::make_unique<QgsCircularString>();
1504 circularString->setPoints( {filletPoints[0], filletPoints[1], filletPoints[2]} );
1505 completeCurve->addCurve( circularString.release() );
1506
1507 // Last linear segment
1508 auto lastSegment = std::make_unique<QgsLineString>(
1509 QVector<QgsPoint> { filletPoints[2], segment2FarEnd } );
1510 completeCurve->addCurve( lastSegment.release() );
1511
1512 return completeCurve;
1513 }
1514 else
1515 {
1516 // Return segmented LineString
1517 QVector<QgsPoint> points;
1518 points.append( segment1FarEnd );
1519
1520 // Convert circular arc to line segments with specified number of segments
1521 QgsCircularString tempArc;
1522 tempArc.setPoints( {filletPoints[0], filletPoints[1], filletPoints[2]} );
1523
1524 // Calculate appropriate tolerance based on desired number of segments
1525 // Calculate the actual arc angle and divide by desired number of segments
1526 // Note: segmentizeArc uses ceil(angle/tolerance), so we need to ensure we get exactly the desired number of segments
1527 double angleTolerance = M_PI / 180.0; // Default to 1 degree
1528 if ( segments > 0 )
1529 {
1530 double radius, centerX, centerY;
1531 QgsGeometryUtils::circleCenterRadius( filletPoints[0], filletPoints[1], filletPoints[2], radius, centerX, centerY );
1532 const double arcAngle = std::abs( QgsGeometryUtilsBase::sweepAngle( centerX, centerY,
1533 filletPoints[0].x(), filletPoints[0].y(),
1534 filletPoints[1].x(), filletPoints[1].y(),
1535 filletPoints[2].x(), filletPoints[2].y() ) ) * M_PI / 180.0; // Convert to radians
1536 // Add small epsilon to avoid ceil() rounding up due to numerical precision
1537 angleTolerance = arcAngle / segments * ( 1.0 + 1e-10 );
1538 }
1539
1540 std::unique_ptr<QgsLineString> segmentizedArc( tempArc.curveToLine( angleTolerance, QgsAbstractGeometry::MaximumAngle ) );
1541
1542 for ( int i = 0; i < segmentizedArc->numPoints(); ++i )
1543 {
1544 points.append( segmentizedArc->vertexAt( QgsVertexId( 0, 0, i ) ) );
1545 }
1546
1547 points.append( segment2FarEnd );
1548
1549 return std::make_unique<QgsLineString>( points );
1550 }
1551}
1552
1553double QgsGeometryUtils::maxFilletRadius( const QgsPoint &segment1Start, const QgsPoint &segment1End,
1554 const QgsPoint &segment2Start, const QgsPoint &segment2End,
1555 double epsilon )
1556{
1557 return QgsGeometryUtilsBase::maximumFilletRadius( segment1Start.x(), segment1Start.y(), segment1End.x(), segment1End.y(), segment2Start.x(), segment2Start.y(), segment2End.x(), segment2End.y(), epsilon );
1558}
1559
1560std::unique_ptr<QgsAbstractGeometry> QgsGeometryUtils::chamferVertex(
1561 const QgsCurve *curve, int vertexIndex,
1562 double distance1, double distance2 )
1563{
1564 return doChamferFilletOnVertex( QgsGeometry::ChamferFilletOperationType::Chamfer, curve, vertexIndex, distance1, distance2, 0 );
1565}
1566
1567std::unique_ptr<QgsAbstractGeometry> QgsGeometryUtils::filletVertex(
1568 const QgsCurve *curve, int vertexIndex,
1569 double radius, int segments )
1570{
1571 return doChamferFilletOnVertex( QgsGeometry::ChamferFilletOperationType::Fillet, curve, vertexIndex, radius, 0.0, segments );
1572}
1573
1574std::unique_ptr< QgsAbstractGeometry > QgsGeometryUtils::doChamferFilletOnVertex(
1575 QgsGeometry::ChamferFilletOperationType operation, const QgsCurve *curve, int vertexIndex,
1576 double value1, double value2, int segments
1577)
1578{
1579 if ( !curve )
1580 throw QgsInvalidArgumentException( "Curve is null." );
1581 if ( curve->isClosed() )
1582 {
1583 if ( curve->numPoints() < 4 )
1584 throw QgsInvalidArgumentException( "Closed curve must have at least 4 vertex." );
1585 if ( vertexIndex < 0 || vertexIndex > curve->numPoints() - 1 )
1586 throw QgsInvalidArgumentException( u"Vertex index out of range. %1 must be in [0, %2]."_s.arg( vertexIndex ).arg( curve->numPoints() - 1 ) );
1587 }
1588 else
1589 {
1590 if ( curve->numPoints() < 3 )
1591 throw QgsInvalidArgumentException( "Opened curve must have at least 3 points." );
1592 if ( vertexIndex <= 0 || vertexIndex >= curve->numPoints() - 1 )
1593 throw QgsInvalidArgumentException( u"Vertex index out of range. %1 must be in (0, %2)."_s.arg( vertexIndex ).arg( curve->numPoints() - 1 ) );
1594 }
1595
1596 // Extract the three consecutive vertices
1597 QgsPoint pPrev = curve->vertexAt( QgsVertexId( 0, 0, vertexIndex - 1 ) );
1598 const QgsPoint p = curve->vertexAt( QgsVertexId( 0, 0, vertexIndex ) );
1599 QgsPoint pNext = curve->vertexAt( QgsVertexId( 0, 0, vertexIndex + 1 ) );
1600 if ( curve->isClosed() )
1601 {
1602 if ( vertexIndex - 1 < 0 )
1603 pPrev = curve->vertexAt( QgsVertexId( 0, 0, curve->numPoints() - 2 ) );
1604 if ( vertexIndex + 1 >= curve->numPoints() )
1605 pNext = curve->vertexAt( QgsVertexId( 0, 0, 1 ) );
1606 }
1607
1608 QgsPoint firstNewPoint, middlePoint, lastNewPoint;
1609
1611 {
1612 double rad = std::min( value1, pPrev.distance( p ) * 0.95 );
1613 rad = std::min( rad, pNext.distance( p ) * 0.95 );
1614
1615 // Create fillet
1616 QgsPoint filletPoints[3];
1617 createFilletArray( pPrev, p, p, pNext, rad, filletPoints );
1618
1619 firstNewPoint = filletPoints[0];
1620 middlePoint = filletPoints[1];
1621 lastNewPoint = filletPoints[2];
1622 }
1624 {
1625 // Create chamfer
1626 createChamfer( pPrev, p, p, pNext, value1, value2, firstNewPoint, lastNewPoint );
1627 }
1628 else
1629 throw QgsInvalidArgumentException( u"Operation '%1' is unknown."_s.arg( qgsEnumValueToKey( operation ) ) );
1630
1631 // Handle LineString geometries
1633 {
1634 QVector<QgsPoint> points;
1635
1636 int min = 0;
1637 if ( curve->isClosed() && vertexIndex == curve->numPoints() - 1 )
1638 min = 1;
1639
1640 // Add points before the operated vertex
1641 for ( int i = min; i < vertexIndex; ++i )
1642 {
1643 points.append( curve->vertexAt( QgsVertexId( 0, 0, i ) ) );
1644 }
1645
1647 {
1648 // Add fillet arc as line segments with proper segmentation
1649 if ( firstNewPoint != pPrev )
1650 points.append( firstNewPoint );
1651
1652 if ( segments > 0 )
1653 {
1654 QgsCircularString tempArc;
1655 tempArc.setPoints( { firstNewPoint, middlePoint, lastNewPoint } );
1656
1657 // Calculate the actual arc angle and divide by desired number of segments
1658 // Note: segmentizeArc uses ceil(angle/tolerance), so we need to ensure we get exactly the desired number of segments
1659 double radius, centerX, centerY;
1660 QgsGeometryUtils::circleCenterRadius( firstNewPoint, middlePoint, lastNewPoint, radius, centerX, centerY );
1661 const double arcAngle = std::abs( QgsGeometryUtilsBase::sweepAngle( centerX, centerY,
1662 firstNewPoint.x(), firstNewPoint.y(),
1663 middlePoint.x(), middlePoint.y(),
1664 lastNewPoint.x(), lastNewPoint.y() ) ) * M_PI / 180.0; // Convert to radians
1665 // Add small epsilon to avoid ceil() rounding up due to numerical precision
1666 const double angleTolerance = arcAngle / segments * ( 1.0 + 1e-10 );
1667 std::unique_ptr<QgsLineString> segmentizedArc( tempArc.curveToLine( angleTolerance, QgsAbstractGeometry::MaximumAngle ) );
1668
1669 for ( int i = 1; i < segmentizedArc->numPoints() - 1; ++i )
1670 {
1671 points.append( segmentizedArc->vertexAt( QgsVertexId( 0, 0, i ) ) );
1672 }
1673 }
1674 else
1675 {
1676 points.append( middlePoint );
1677 }
1678
1679 if ( lastNewPoint != pNext )
1680 points.append( lastNewPoint );
1681 }
1682 else
1683 {
1684 if ( firstNewPoint != pPrev )
1685 points.append( firstNewPoint );
1686 if ( lastNewPoint != pNext )
1687 points.append( lastNewPoint );
1688 }
1689
1690 int max = curve->numPoints();
1691 if ( curve->isClosed() && vertexIndex == 0 )
1692 max = curve->numPoints() - 1;
1693
1694 for ( int i = vertexIndex + 1; i < max; ++i )
1695 {
1696 points.append( curve->vertexAt( QgsVertexId( 0, 0, i ) ) );
1697 }
1698
1699 return std::make_unique<QgsLineString>( points );
1700 }
1701
1702 if ( const QgsCompoundCurve *compound = qgsgeometry_cast<const QgsCompoundCurve *>( curve ) )
1703 {
1704 auto newCompound = std::make_unique<QgsCompoundCurve>();
1705
1706 int globalVertexIndex = 0;
1707 int targetCurveIndex = -1;
1708 int vertexInCurve = -1;
1709
1710 for ( int curveIdx = 0; curveIdx < compound->nCurves(); ++curveIdx )
1711 {
1712 const QgsCurve *subcurve = compound->curveAt( curveIdx );
1713 const int subcurvePoints = subcurve->numPoints();
1714
1715 if ( globalVertexIndex + subcurvePoints > vertexIndex )
1716 {
1717 targetCurveIndex = curveIdx;
1718 vertexInCurve = vertexIndex - globalVertexIndex;
1719 break;
1720 }
1721 globalVertexIndex += subcurvePoints - 1;
1722 }
1723
1724 if ( targetCurveIndex == -1 )
1725 {
1726 throw QgsInvalidArgumentException( "While generating output: unable to find curve within compound." );
1727 }
1728
1729 const QgsCurve *targetCurve = compound->curveAt( targetCurveIndex );
1730
1731 // Add curves before the target curve
1732 for ( int i = 0; i < targetCurveIndex; ++i )
1733 {
1734 std::unique_ptr<QgsCurve> tmpCurv( compound->curveAt( i )->clone() );
1735 if ( curve->isClosed() && vertexIndex == curve->numPoints() - 1 )
1736 {
1737 tmpCurv->insertVertex( QgsVertexId( 0, 0, 1 ), lastNewPoint );
1738 tmpCurv->deleteVertex( QgsVertexId( 0, 0, 0 ) );
1739 }
1740 newCompound->addCurve( tmpCurv.release() );
1741 }
1742
1743 // Handle the curve containing the vertex
1744 if ( vertexInCurve > 0 )
1745 {
1746 QVector<QgsPoint> beforePoints;
1747 for ( int j = 0; j < vertexInCurve; ++j )
1748 {
1749 beforePoints.append( targetCurve->vertexAt( QgsVertexId( 0, 0, j ) ) );
1750 }
1751 beforePoints.append( firstNewPoint );
1752
1753 if ( beforePoints.size() > 1 )
1754 {
1755 auto beforeVertex = std::make_unique<QgsLineString>( beforePoints );
1756 newCompound->addCurve( beforeVertex.release() );
1757 }
1758 }
1759
1761 {
1762 // Add the fillet arc - for CompoundCurve, preserve circular nature unless segments > 0
1763 if ( segments <= 0 )
1764 {
1765 // Preserve circular arc
1766 auto filletArc = std::make_unique<QgsCircularString>();
1767 filletArc->setPoints( { firstNewPoint, middlePoint, lastNewPoint } );
1768 newCompound->addCurve( filletArc.release() );
1769 }
1770 else
1771 {
1772 // Segmentize the arc
1773 QgsCircularString tempArc;
1774 tempArc.setPoints( { firstNewPoint, middlePoint, lastNewPoint } );
1775
1776 const double angleTolerance = ( 2.0 * M_PI ) / ( 4.0 * segments );
1777 std::unique_ptr<QgsLineString> segmentizedArc( tempArc.curveToLine( angleTolerance, QgsAbstractGeometry::MaximumAngle ) );
1778
1779 newCompound->addCurve( segmentizedArc.release() );
1780 }
1781 }
1782 else
1783 {
1784 auto chamferLine = std::make_unique<QgsLineString>(
1785 QVector<QgsPoint> { firstNewPoint, lastNewPoint }
1786 );
1787 newCompound->addCurve( chamferLine.release() );
1788 }
1789
1790 if ( vertexInCurve < targetCurve->numPoints() - 1 )
1791 {
1792 QVector<QgsPoint> afterPoints;
1793 afterPoints.append( lastNewPoint );
1794 for ( int j = vertexInCurve + 1; j < targetCurve->numPoints(); ++j )
1795 {
1796 afterPoints.append( targetCurve->vertexAt( QgsVertexId( 0, 0, j ) ) );
1797 }
1798
1799 if ( afterPoints.size() > 1 )
1800 {
1801 auto afterVertex = std::make_unique<QgsLineString>( afterPoints );
1802 newCompound->addCurve( afterVertex.release() );
1803 }
1804 }
1805
1806 // Add curves after the target curve
1807 for ( int i = targetCurveIndex + 1; i < compound->nCurves(); ++i )
1808 {
1809 std::unique_ptr<QgsCurve> tmpCurv( compound->curveAt( i )->clone() );
1810 if ( curve->isClosed() && vertexIndex == 0 )
1811 {
1812 tmpCurv->insertVertex( QgsVertexId( 0, 0, tmpCurv->numPoints() - 1 ), firstNewPoint );
1813 tmpCurv->deleteVertex( QgsVertexId( 0, 0, tmpCurv->numPoints() - 1 ) );
1814 }
1815 newCompound->addCurve( tmpCurv.release() );
1816 }
1817
1818 return newCompound;
1819 }
1820
1821 throw QgsInvalidArgumentException( "While generating output: curse is not a QgsLineString nor a QgsCompoundCurve." );
1822}
1823
1824bool QgsGeometryUtils::pointsAreCollinear( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double epsilon )
1825{
1826 if ( pt1.is3D() )
1827 {
1828 return QgsGeometryUtilsBase::points3DAreCollinear( pt1.x(), pt1.y(), pt1.z(), pt2.x(), pt2.y(), pt2.z(), pt3.x(), pt3.y(), pt3.z(), epsilon );
1829 }
1830 else
1831 {
1832 return QgsGeometryUtilsBase::pointsAreCollinear( pt1.x(), pt1.y(), pt2.x(), pt2.y(), pt3.x(), pt3.y(), epsilon );
1833 }
1834}
1835
1836bool QgsGeometryUtils::checkWeaklyFor3DPlane( const QgsAbstractGeometry *geom, QgsPoint &pt1, QgsPoint &pt2, QgsPoint &pt3, double epsilon )
1837{
1838 if ( !geom || !geom->is3D() || geom->nCoordinates() < 3 || qgsgeometry_cast< const QgsGeometryCollection * >( geom ) )
1839 {
1840 return false;
1841 }
1842
1843 const QgsCurve *ring = nullptr;
1844 if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( geom ) )
1845 {
1846 ring = curve;
1847 }
1848 else if ( const QgsCurvePolygon *curvePolygon = qgsgeometry_cast< const QgsCurvePolygon * >( geom ) )
1849 {
1850 // curve polygon case, consider exterior ring only
1851 ring = curvePolygon->exteriorRing();
1852 }
1853
1854 if ( ring )
1855 {
1856 QgsVertexIterator ringIt = ring->vertices();
1857 pt1 = ringIt.next();
1858
1859 // Find a second distinct point
1860 while ( ringIt.hasNext() )
1861 {
1862 pt2 = ringIt.next();
1863 if ( !pt2.fuzzyEqual( pt1, epsilon ) )
1864 {
1865 // Find a third non-collinear point
1866 while ( ringIt.hasNext() )
1867 {
1868 pt3 = ringIt.next();
1869 if ( !QgsGeometryUtils::pointsAreCollinear( pt1, pt2, pt3, epsilon ) )
1870 {
1871 return true;
1872 }
1873 }
1874 }
1875 }
1876 }
1877
1878 return false;
1879}
static const double DEFAULT_SEGMENT_EPSILON
Default snapping tolerance for segments.
Definition qgis.h:6523
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:280
@ Point
Point.
Definition qgis.h:282
@ PointM
PointM.
Definition qgis.h:315
@ PointZ
PointZ.
Definition qgis.h:299
@ PointZM
PointZM.
Definition qgis.h:331
Abstract base class for all geometries.
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle.
@ MaximumDifference
Maximum distance between an arbitrary point on the original curve and closest point on its approximat...
@ MaximumAngle
Maximum angle between generating radii (lines from arc center to output vertices).
QgsVertexIterator vertices() const
Returns a read-only, Java-style iterator for traversal of vertices of all the geometry,...
virtual int vertexCount(int part=0, int ring=0) const =0
Returns the number of vertices of which this geometry is built.
bool isMeasure() const
Returns true if the geometry contains m values.
QFlags< WkbFlag > WkbFlags
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
AxisOrder
Axis order for GML generation.
@ XY
X comes before Y (or lon before lat).
virtual int nCoordinates() const
Returns the number of nodes contained in the geometry.
virtual QgsPoint vertexAt(QgsVertexId id) const =0
Returns the point corresponding to a specified vertex id.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
virtual bool isEmpty() const
Returns true if the geometry is empty.
@ FlagExportNanAsDoubleMin
Use -DOUBLE_MAX to represent NaN.
virtual double segmentLength(QgsVertexId startVertex) const =0
Returns the length of the segment of the geometry which begins at startVertex.
virtual bool nextVertex(QgsVertexId &id, QgsPoint &vertex) const =0
Returns next vertex id and coordinates.
virtual double closestSegment(const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf=nullptr, double epsilon=4 *std::numeric_limits< double >::epsilon()) const =0
Searches for the closest segment of the geometry to a given point.
Circular string geometry type.
void setPoints(const QgsPointSequence &points)
Sets the circular string's points.
QgsLineString * curveToLine(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a new line string geometry corresponding to a segmentized approximation of the curve.
Curve polygon geometry type.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
virtual int numPoints() const =0
Returns the number of points in the curve.
virtual bool isClosed() const
Returns true if the curve is closed.
Definition qgscurve.cpp:54
QgsPoint vertexAt(QgsVertexId id) const override
Returns the point corresponding to a specified vertex id.
Definition qgscurve.cpp:199
static void pointOnLineWithDistance(double x1, double y1, double x2, double y2, double distance, double &x, double &y, double *z1=nullptr, double *z2=nullptr, double *z=nullptr, double *m1=nullptr, double *m2=nullptr, double *m=nullptr)
Calculates the point a specified distance from (x1, y1) toward a second point (x2,...
static double maximumFilletRadius(const double segment1StartX, const double segment1StartY, const double segment1EndX, const double segment1EndY, const double segment2StartX, const double segment2StartY, const double segment2EndX, const double segment2EndY, double epsilon=1e-8)
Calculates the maximum allowed fillet radius for the given segment configuration.
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 distance2D(double x1, double y1, double x2, double y2)
Returns the 2D distance between (x1, y1) and (x2, y2).
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 bool createChamfer(const double segment1StartX, const double segment1StartY, const double segment1EndX, const double segment1EndY, const double segment2StartX, const double segment2StartY, const double segment2EndX, const double segment2EndY, const double distance1, const double distance2, double &chamferStartX, double &chamferStartY, double &chamferEndX, double &chamferEndY, double *trim1StartX=nullptr, double *trim1StartY=nullptr, double *trim1EndX=nullptr, double *trim1EndY=nullptr, double *trim2StartX=nullptr, double *trim2StartY=nullptr, double *trim2EndX=nullptr, double *trim2EndY=nullptr, const double epsilon=1e-8)
Creates a chamfer (angled corner) between two line segments.
static double lineAngle(double x1, double y1, double x2, double y2)
Calculates the direction of line joining two points in radians, clockwise from the north direction.
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 points3DAreCollinear(double x1, double y1, double z1, double x2, double y2, double z2, double x3, double y3, double z3, double epsilon)
Given the points (x1, y1, z1), (x2, y2, z2) and (x3, y3, z3) returns true if these points can be cons...
static double interpolateArcValue(double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3)
Interpolate a value at given angle on circular arc given values (zm1, zm2, zm3) at three different an...
static void interpolatePointOnCubicBezier(double p0x, double p0y, double p0z, double p0m, double p1x, double p1y, double p1z, double p1m, double p2x, double p2y, double p2z, double p2m, double p3x, double p3y, double p3z, double p3m, double t, bool hasZ, bool hasM, double &outX, double &outY, double &outZ, double &outM)
Evaluates a point on a cubic Bézier curve defined by four control points.
static bool segmentIntersection(double p1x, double p1y, double p2x, double p2y, double q1x, double q1y, double q2x, double q2y, double &intersectionPointX, double &intersectionPointY, bool &isIntersection, double tolerance=1e-8, bool acceptImproperIntersection=false)
Compute the intersection between two segments.
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 bool pointsAreCollinear(double x1, double y1, double x2, double y2, double x3, double y3, double epsilon)
Given the points (x1, y1), (x2, y2) and (x3, y3) returns true if these points can be considered colli...
static bool createFillet(const double segment1StartX, const double segment1StartY, const double segment1EndX, const double segment1EndY, const double segment2StartX, const double segment2StartY, const double segment2EndX, const double segment2EndY, const double radius, double *filletPointsX, double *filletPointsY, double *trim1StartX=nullptr, double *trim1StartY=nullptr, double *trim1EndX=nullptr, double *trim1EndY=nullptr, double *trim2StartX=nullptr, double *trim2StartY=nullptr, double *trim2EndX=nullptr, double *trim2EndY=nullptr, const double epsilon=1e-8)
Creates a fillet (rounded corner) between two line segments.
static int circleCircleIntersections(const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2, QgsPointXY &intersection1, QgsPointXY &intersection2)
Calculates the intersections points between the circle with center center1 and radius radius1 and the...
static std::unique_ptr< QgsLineString > createChamferGeometry(const QgsPoint &segment1Start, const QgsPoint &segment1End, const QgsPoint &segment2Start, const QgsPoint &segment2End, double distance1, double distance2)
Creates a complete chamfer geometry connecting two segments.
static QString pointsToJSON(const QgsPointSequence &points, int precision)
Returns a geoJSON coordinates string.
static QgsPointXY interpolatePointOnLine(double x1, double y1, double x2, double y2, double fraction)
Interpolates the position of a point a fraction of the way along the line from (x1,...
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 int leftOfLine(const QgsPoint &point, const QgsPoint &p1, const QgsPoint &p2)
Returns a value < 0 if the point point is left of the line from p1 -> p2.
static QgsPoint pointOnLineWithDistance(const QgsPoint &startPoint, const QgsPoint &directionPoint, double distance)
Returns a point a specified distance toward a second point.
static double gradient(const QgsPoint &pt1, const QgsPoint &pt2)
Returns the gradient of a line defined by points pt1 and pt2.
static json pointsToJson(const QgsPointSequence &points, int precision)
Returns coordinates as json object.
static double maxFilletRadius(const QgsPoint &segment1Start, const QgsPoint &segment1End, const QgsPoint &segment2Start, const QgsPoint &segment2End, double epsilon=1e-8)
Calculates the maximum allowed fillet radius for the given segment configuration.
static QgsPoint createPointWithMatchingDimensions(double x, double y, const QgsPoint &reference)
Creates a QgsPoint with dimensions matching a reference point.
static QVector< QgsLineString * > extractLineStrings(const QgsAbstractGeometry *geom)
Returns list of linestrings extracted from the passed geometry.
static std::unique_ptr< QgsAbstractGeometry > createFilletGeometry(const QgsPoint &segment1Start, const QgsPoint &segment1End, const QgsPoint &segment2Start, const QgsPoint &segment2End, double radius, int segments)
Creates a complete fillet geometry connecting two segments.
static QgsPoint interpolatePointOnSegment(double x, double y, const QgsPoint &segmentStart, const QgsPoint &segmentEnd)
Interpolates a point on a segment with proper Z and M value interpolation.
static QVector< SelfIntersection > selfIntersections(const QgsAbstractGeometry *geom, int part, int ring, double tolerance)
Find self intersections in a polyline.
static bool transferFirstZValueToPoint(const QgsPointSequence &points, QgsPoint &point)
A Z dimension is added to point if one of the point in the list points is in 3D.
static bool segmentMidPoint(const QgsPoint &p1, const QgsPoint &p2, QgsPoint &result, double radius, const QgsPoint &mousePos)
Calculates midpoint on circle passing through p1 and p2, closest to the given coordinate mousePos.
static QgsPointXY interpolatePointOnLineByValue(double x1, double y1, double v1, double x2, double y2, double v2, double value)
Interpolates the position of a point along the line from (x1, y1) to (x2, y2).
static QgsPoint closestPoint(const QgsAbstractGeometry &geometry, const QgsPoint &point)
Returns the nearest point on a segment of a geometry for the specified point.
static bool verticesAtDistance(const QgsAbstractGeometry &geometry, double distance, QgsVertexId &previousVertex, QgsVertexId &nextVertex)
Retrieves the vertices which are before and after the interpolated point at a specified distance alon...
static QStringList wktGetChildBlocks(const QString &wkt, const QString &defaultType=QString())
Parses a WKT string and returns of list of blocks contained in the WKT.
static bool segmentIntersection(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &q1, const QgsPoint &q2, QgsPoint &intersectionPoint, bool &isIntersection, double tolerance=1e-8, bool acceptImproperIntersection=false)
Compute the intersection between two segments.
static QgsLineString perpendicularSegment(const QgsPoint &p, const QgsPoint &s1, const QgsPoint &s2)
Create a perpendicular line segment from p to segment [s1, s2].
static int segmentSide(const QgsPoint &pt1, const QgsPoint &pt3, const QgsPoint &pt2)
For line defined by points pt1 and pt3, find out on which side of the line is point pt2.
static QgsPoint interpolatePointOnCubicBezier(const QgsPoint &p0, const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, double t)
Evaluates a point on a cubic Bézier curve defined by four control points.
static void pointsToWKB(QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure, QgsAbstractGeometry::WkbFlags flags)
Returns a LinearRing { uint32 numPoints; Point points[numPoints]; }.
static bool createChamfer(const QgsPoint &segment1Start, const QgsPoint &segment1End, const QgsPoint &segment2Start, const QgsPoint &segment2End, double distance1, double distance2, QgsPoint &chamferStart, QgsPoint &chamferEnd, double epsilon=1e-8)
Creates a chamfer between two line segments using QgsPoint.
static double distanceToVertex(const QgsAbstractGeometry &geom, QgsVertexId id)
Returns the distance along a geometry from its first vertex to the specified vertex.
static QPair< Qgis::WkbType, QString > wktReadBlock(const QString &wkt)
Parses a WKT block of the format "TYPE( contents )" and returns a pair of geometry type to contents (...
static bool lineCircleIntersection(const QgsPointXY &center, double radius, const QgsPointXY &linePoint1, const QgsPointXY &linePoint2, QgsPointXY &intersection)
Compute the intersection of a line and a circle.
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 double distToInfiniteLine(const QgsPoint &point, const QgsPoint &linePoint1, const QgsPoint &linePoint2, double epsilon=1e-7)
Returns the distance between a point and an infinite line.
static void coefficients(const QgsPoint &pt1, const QgsPoint &pt2, double &a, double &b, double &c)
Returns the coefficients (a, b, c for equation "ax + by + c = 0") of a line defined by points pt1 and...
static bool transferFirstMValueToPoint(const QgsPointSequence &points, QgsPoint &point)
A M dimension is added to point if one of the points in the list points contains an M value.
static QgsPoint midpoint(const QgsPoint &pt1, const QgsPoint &pt2)
Returns a middle point between points pt1 and pt2.
static bool createFillet(const QgsPoint &segment1Start, const QgsPoint &segment1End, const QgsPoint &segment2Start, const QgsPoint &segment2End, double radius, QgsPoint &filletPoint1, QgsPoint &filletMidPoint, QgsPoint &filletPoint2, double epsilon=1e-8)
Creates a fillet (rounded corner) between two line segments using QgsPoint.
static QgsPoint closestVertex(const QgsAbstractGeometry &geom, const QgsPoint &pt, QgsVertexId &id)
Returns the closest vertex to a geometry for a specified point.
static bool pointContinuesArc(const QgsPoint &a1, const QgsPoint &a2, const QgsPoint &a3, const QgsPoint &b, double distanceTolerance, double pointSpacingAngleTolerance)
Returns true if point b is on the arc formed by points a1, a2, and a3, but not within that arc portio...
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 Q_DECL_DEPRECATED bool pointsAreCollinear(double x1, double y1, double x2, double y2, double x3, double y3, double epsilon)
Given the points (x1, y1), (x2, y2) and (x3, y3) returns true if these points can be considered colli...
static bool tangentPointAndCircle(const QgsPointXY &center, double radius, const QgsPointXY &p, QgsPointXY &pt1, QgsPointXY &pt2)
Calculates the tangent points between the circle with the specified center and radius and the point p...
static int circleCircleOuterTangents(const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2)
Calculates the outer tangent points for two circles, centered at center1 and center2 and with radii o...
static QgsPointSequence pointsFromWKT(const QString &wktCoordinateList, bool is3D, bool isMeasure)
Returns a list of points contained in a WKT string.
static void segmentizeArc(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, QgsPointSequence &points, double tolerance=M_PI_2/90, QgsAbstractGeometry::SegmentationToleranceType toleranceType=QgsAbstractGeometry::MaximumAngle, bool hasZ=false, bool hasM=false)
Convert circular arc defined by p1, p2, p3 (p1/p3 being start resp.
static Q_DECL_DEPRECATED double sqrDistance2D(double x1, double y1, double x2, double y2)
Returns the squared 2D distance between (x1, y1) and (x2, y2).
static QDomElement pointsToGML2(const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY)
Returns a gml::coordinates DOM element.
static std::unique_ptr< QgsAbstractGeometry > chamferVertex(const QgsCurve *curve, int vertexIndex, double distance1, double distance2)
Applies chamfer to a vertex in a curve geometry.
static bool transferFirstZOrMValueToPoint(Iterator verticesBegin, Iterator verticesEnd, QgsPoint &point)
A Z or M dimension is added to point if one of the points in the list points contains Z or M value.
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 int circleCircleInnerTangents(const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2)
Calculates the inner tangent points for two circles, centered at center1 and center2 and with radii o...
static bool createFilletArray(const QgsPoint &segment1Start, const QgsPoint &segment1End, const QgsPoint &segment2Start, const QgsPoint &segment2End, double radius, QgsPoint filletPoints[3], double epsilon=1e-8)
Convenient method of createFillet using array output.
static QString pointsToWKT(const QgsPointSequence &points, int precision, bool is3D, bool isMeasure)
Returns a WKT coordinate list.
static QgsPoint segmentMidPointFromCenter(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &center, bool useShortestArc=true)
Calculates the midpoint on the circle passing through p1 and p2, with the specified center coordinate...
static std::unique_ptr< QgsAbstractGeometry > filletVertex(const QgsCurve *curve, int vertexIndex, double radius, int segments)
Applies fillet to a vertex in a curve geometry.
ChamferFilletOperationType
Privatly used in chamfer/fillet functions.
Custom exception class when argument are invalid.
Line string geometry type, with support for z-dimension and m-values.
void addVertex(const QgsPoint &pt)
Adds a new vertex to the end of the line string.
Represents a 2D point.
Definition qgspointxy.h:62
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition qgspointxy.h:188
double distance(double x, double y) const
Returns the distance between this point and a specified x, y coordinate.
Definition qgspointxy.h:208
void set(double x, double y)
Sets the x and y value of the point.
Definition qgspointxy.h:138
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
void setM(double m)
Sets the point's m-value.
Definition qgspoint.h:369
bool convertTo(Qgis::WkbType type) override
Converts the geometry to a specified type.
Definition qgspoint.cpp:633
bool fuzzyEqual(const QgsAbstractGeometry &other, double epsilon=1e-8) const override
Performs fuzzy comparison between this geometry and other using an epsilon.
Definition qgspoint.h:193
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:742
double distance(double x, double y) const
Returns the Cartesian 2D distance between this point and a specified x, y coordinate.
Definition qgspoint.h:391
void setZ(double z)
Sets the point's z-coordinate.
Definition qgspoint.h:354
double m
Definition qgspoint.h:59
QgsPoint project(double distance, double azimuth, double inclination=90.0) const
Returns a new point which corresponds to this point projected by a specified distance with specified ...
Definition qgspoint.cpp:710
double y
Definition qgspoint.h:57
Represent a 2-dimensional vector.
Definition qgsvector.h:34
double length() const
Returns the length of the vector.
Definition qgsvector.h:128
Java-style iterator for traversal of vertices of a geometry.
bool hasNext() const
Find out whether there are more vertices.
QgsPoint next()
Returns next vertex of the geometry (undefined behavior if hasNext() returns false before calling nex...
WKB pointer handler.
Definition qgswkbptr.h:45
static Qgis::WkbType parseType(const QString &wktStr)
Attempts to extract the WKB type from a WKT string.
static Qgis::WkbType addM(Qgis::WkbType type)
Adds the m dimension to a WKB type and returns the new type.
static Qgis::WkbType addZ(Qgis::WkbType type)
Adds the z dimension to a WKB type and returns the new type.
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
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
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6817
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7091
double qgsRound(double number, int places)
Returns a double number, rounded (as close as possible) to the specified number of places.
Definition qgis.h:6941
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6900
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPoint > QgsPointSequence
bool isClockwise(std::array< Direction, 4 > dirs)
Checks whether the 4 directions in dirs make up a clockwise rectangle.
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:34
int vertex
Vertex number.
Definition qgsvertexid.h:98
bool isValid() const
Returns true if the vertex id is valid.
Definition qgsvertexid.h:49
int part
Part number.
Definition qgsvertexid.h:92
Qgis::VertexType type
Vertex type.
int ring
Ring number.
Definition qgsvertexid.h:95