1 /***************************************************************************
2                         qgsgeometryutils.h
3   -------------------------------------------------------------------
4 Date                 : 21 Nov 2014
5 Copyright            : (C) 2014 by Marco Hugentobler
6 email                : 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 #ifndef QGSGEOMETRYUTILS_H
17 #define QGSGEOMETRYUTILS_H
18 
19 #include <limits>
20 
21 #include "qgis_core.h"
22 #include "qgis_sip.h"
23 #include "qgspoint.h"
24 #include "qgsvertexid.h"
25 #include "qgsgeometry.h"
26 #include "qgsvector3d.h"
27 
28 #include <QJsonArray>
29 
30 class QgsLineString;
31 
32 /**
33  * \ingroup core
34  * \class QgsGeometryUtils
35  * \brief Contains various geometry utility functions.
36  * \since QGIS 2.10
37  */
38 class CORE_EXPORT QgsGeometryUtils
39 {
40   public:
41 
42     /**
43      * Returns list of linestrings extracted from the passed geometry. The returned objects
44      *  have to be deleted by the caller.
45      */
46     static QVector<QgsLineString *> extractLineStrings( const QgsAbstractGeometry *geom ) SIP_FACTORY;
47 
48     /**
49      * Returns the closest vertex to a geometry for a specified point.
50      * On error null point will be returned and "id" argument will be invalid.
51      */
52     static QgsPoint closestVertex( const QgsAbstractGeometry &geom, const QgsPoint &pt, QgsVertexId &id SIP_OUT );
53 
54     /**
55      * Returns the nearest point on a segment of a \a geometry
56      * for the specified \a point. The z and m values will be linearly interpolated between
57      * the two neighbouring vertices.
58      */
59     static QgsPoint closestPoint( const QgsAbstractGeometry &geometry, const QgsPoint &point );
60 
61     /**
62      * Returns the distance along a geometry from its first vertex to the specified vertex.
63      * \param geom geometry
64      * \param id vertex id to find distance to
65      * \returns distance to vertex (following geometry)
66      * \since QGIS 2.16
67      */
68     static double distanceToVertex( const QgsAbstractGeometry &geom, QgsVertexId id );
69 
70     /**
71      * Retrieves the vertices which are before and after the interpolated point at a specified distance along a linestring
72      * (or polygon boundary).
73      * \param geometry line or polygon geometry
74      * \param distance distance to traverse along geometry
75      * \param previousVertex will be set to previous vertex ID
76      * \param nextVertex will be set to next vertex ID
77      * \returns TRUE if vertices were successfully retrieved
78      * \note if the distance coincides exactly with a vertex, then both previousVertex and nextVertex will be set to this vertex
79      * \since QGIS 3.0
80      */
81     static bool verticesAtDistance( const QgsAbstractGeometry &geometry,
82                                     double distance,
83                                     QgsVertexId &previousVertex SIP_OUT,
84                                     QgsVertexId &nextVertex SIP_OUT );
85 
86     /**
87      * Returns the squared 2D distance between two points.
88      */
89     static double sqrDistance2D( const QgsPoint &pt1, const QgsPoint &pt2 ) SIP_HOLDGIL;
90 
91     /**
92      * Returns the squared distance between a point and a line.
93      */
94     static double sqrDistToLine( double ptX, double ptY, double x1, double y1, double x2, double y2, double &minDistX SIP_OUT, double &minDistY SIP_OUT, double epsilon ) SIP_HOLDGIL;
95 
96     /**
97      * Computes the intersection between two lines. Z dimension is
98      * supported and is retrieved from the first 3D point amongst \a p1 and
99      * \a p2.
100      * \param p1 Point on the first line
101      * \param v1 Direction vector of the first line
102      * \param p2 Point on the second line
103      * \param v2 Direction vector of the second line
104      * \param intersection Output parameter, the intersection point
105      * \returns Whether the lines intersect
106      */
107     static bool lineIntersection( const QgsPoint &p1, QgsVector v1, const QgsPoint &p2, QgsVector v2, QgsPoint &intersection SIP_OUT ) SIP_HOLDGIL;
108 
109     /**
110      * \brief Compute the intersection between two segments
111      * \param p1 First segment start point
112      * \param p2 First segment end point
113      * \param q1 Second segment start point
114      * \param q2 Second segment end point
115      * \param intersectionPoint Output parameter, the intersection point
116      * \param isIntersection Output parameter, return TRUE if an intersection is found
117      * \param tolerance The tolerance to use
118      * \param acceptImproperIntersection By default, this method returns TRUE only if segments have proper intersection. If set true, returns also TRUE if segments have improper intersection (end of one segment on other segment ; continuous segments).
119      * \returns  Whether the segments intersect
120      *
121      * ### Example
122      *
123      * \code{.py}
124      *   ret = QgsGeometryUtils.segmentIntersection( QgsPoint( 0, 0 ), QgsPoint( 0, 1 ), QgsPoint( 1, 1 ), QgsPoint( 1, 0 ) )
125      *   ret[0], ret[1].asWkt(), ret[2]
126      *   # Whether the segments intersect, the intersection point, is intersect
127      *   # (False, 'Point (0 0)', False)
128      *   ret = QgsGeometryUtils.segmentIntersection( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 0, 5 ), QgsPoint( 1, 5 ) )
129      *   ret[0], ret[1].asWkt(), ret[2]
130      *   # (False, 'Point (0 5)', True)
131      *   ret = QgsGeometryUtils.segmentIntersection( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 0, 5 ), QgsPoint( 1, 5 ), acceptImproperIntersection=True )
132      *   ret[0], ret[1].asWkt(), ret[2]
133      *   # (True, 'Point (0 5)', True)
134      *   ret = QgsGeometryUtils.segmentIntersection( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 0, 2 ), QgsPoint( 1, 5 ) )
135      *   ret[0], ret[1].asWkt(), ret[2]
136      *   # (False, 'Point (0 2)', True)
137      *   ret = QgsGeometryUtils.segmentIntersection( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 0, 2 ), QgsPoint( 1, 5 ), acceptImproperIntersection=True )
138      *   ret[0], ret[1].asWkt(), ret[2]
139      *   # (True, 'Point (0 2)', True)
140      *   ret = QgsGeometryUtils.segmentIntersection( QgsPoint( 0, -5 ), QgsPoint( 0, 5 ), QgsPoint( 2, 0 ), QgsPoint( -1, 0 ) )
141      *   ret[0], ret[1].asWkt(), ret[2]
142      *   # (True, 'Point (0 0)', True)
143      * \endcode
144      */
145     static bool segmentIntersection( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &q1, const QgsPoint &q2, QgsPoint &intersectionPoint SIP_OUT, bool &isIntersection SIP_OUT, double tolerance = 1e-8, bool acceptImproperIntersection = false ) SIP_HOLDGIL;
146 
147     /**
148      * \brief Compute the intersection of a line and a circle.
149      * If the intersection has two solutions (points),
150      * the closest point to the initial \a intersection point is returned.
151      * \param center the center of the circle
152      * \param radius the radius of the circle
153      * \param linePoint1 a first point on the line
154      * \param linePoint2 a second point on the line
155      * \param intersection the initial point and the returned intersection point
156      * \return TRUE if an intersection has been found
157      */
158     static bool lineCircleIntersection( const QgsPointXY &center, double radius,
159                                         const QgsPointXY &linePoint1, const QgsPointXY &linePoint2,
160                                         QgsPointXY &intersection SIP_INOUT ) SIP_HOLDGIL;
161 
162     /**
163      * Calculates the intersections points between the circle with center \a center1 and
164      * radius \a radius1 and the circle with center \a center2 and radius \a radius2.
165      *
166      * If found, the intersection points will be stored in \a intersection1 and \a intersection2.
167      *
168      * \returns number of intersection points found.
169      *
170      * \since QGIS 3.2
171      */
172     static int circleCircleIntersections( const QgsPointXY &center1, double radius1,
173                                           const QgsPointXY &center2, double radius2,
174                                           QgsPointXY &intersection1 SIP_OUT, QgsPointXY &intersection2 SIP_OUT ) SIP_HOLDGIL;
175 
176     /**
177      * Calculates the tangent points between the circle with the specified \a center and \a radius
178      * and the point \a p.
179      *
180      * If found, the tangent points will be stored in \a pt1 and \a pt2.
181      *
182      * \since QGIS 3.2
183      */
184     static bool tangentPointAndCircle( const QgsPointXY &center, double radius,
185                                        const QgsPointXY &p, QgsPointXY &pt1 SIP_OUT, QgsPointXY &pt2 SIP_OUT ) SIP_HOLDGIL;
186 
187     /**
188      * Calculates the outer tangent points for two circles, centered at \a center1 and
189      * \a center2 and with radii of \a radius1 and \a radius2 respectively.
190      *
191      * The outer tangent points correspond to the points at which the two lines
192      * which are drawn so that they are tangential to both circles touch
193      * the circles.
194      *
195      * The first tangent line is described by the points
196      * stored in \a line1P1 and \a line1P2,
197      * and the second line is described by the points stored in \a line2P1
198      * and \a line2P2.
199      *
200      * Returns the number of tangents (either 0 or 2).
201      *
202      * \since QGIS 3.2
203      */
204     static int circleCircleOuterTangents(
205       const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2,
206       QgsPointXY &line1P1 SIP_OUT, QgsPointXY &line1P2 SIP_OUT,
207       QgsPointXY &line2P1 SIP_OUT, QgsPointXY &line2P2 SIP_OUT ) SIP_HOLDGIL;
208 
209     /**
210      * Calculates the inner tangent points for two circles, centered at \a
211      * center1 and \a center2 and with radii of \a radius1 and \a radius2
212      * respectively.
213      *
214      * The inner tangent points correspond to the points at which the two lines
215      * which are drawn so that they are tangential to both circles and are
216      * crossing each other.
217      *
218      * The first tangent line is described by the points
219      * stored in \a line1P1 and \a line1P2,
220      * and the second line is described by the points stored in \a line2P1
221      * and \a line2P2.
222      *
223      * Returns the number of tangents (either 0 or 2).
224      *
225      * \since QGIS 3.6
226      */
227     static int circleCircleInnerTangents(
228       const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2,
229       QgsPointXY &line1P1 SIP_OUT, QgsPointXY &line1P2 SIP_OUT,
230       QgsPointXY &line2P1 SIP_OUT, QgsPointXY &line2P2 SIP_OUT ) SIP_HOLDGIL;
231 
232     /**
233      * \brief Project the point on a segment
234      * \param p The point
235      * \param s1 The segment start point
236      * \param s2 The segment end point
237      * \returns The projection of the point on the segment
238      */
projectPointOnSegment(const QgsPoint & p,const QgsPoint & s1,const QgsPoint & s2)239     static QgsPoint projectPointOnSegment( const QgsPoint &p, const QgsPoint &s1, const QgsPoint &s2 ) SIP_HOLDGIL
240     {
241       const double nx = s2.y() - s1.y();
242       const double ny = -( s2.x() - s1.x() );
243       const double t = ( p.x() * ny - p.y() * nx - s1.x() * ny + s1.y() * nx ) / ( ( s2.x() - s1.x() ) * ny - ( s2.y() - s1.y() ) * nx );
244       return t < 0. ? s1 : t > 1. ? s2 : QgsPoint( s1.x() + ( s2.x() - s1.x() ) * t, s1.y() + ( s2.y() - s1.y() ) * t );
245     }
246 
247     //! \note not available in Python bindings
248     struct SelfIntersection SIP_SKIP
249     {
250       int segment1;
251       int segment2;
252       QgsPoint point;
253     };
254 
255     /**
256      * \brief Find self intersections in a polyline
257      * \param geom The geometry to check
258      * \param part The part of the geometry to check
259      * \param ring The ring of the geometry part to check
260      * \param tolerance The tolerance to use
261      * \returns The list of self intersections
262      * \note not available in Python bindings
263      * \since QGIS 2.12
264      */
265     static QVector<SelfIntersection> selfIntersections( const QgsAbstractGeometry *geom, int part, int ring, double tolerance ) SIP_SKIP;
266 
267     /**
268      * Returns a value < 0 if the point (\a x, \a y) is left of the line from (\a x1, \a y1) -> (\a x2, \a y2).
269      * A positive return value indicates the point is to the right of the line.
270      *
271      * If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
272      * on the line, or exactly in line with the segment) and the result is undefined.
273      */
274     static int leftOfLine( const double x, const double y, const double x1, const double y1, const double x2, const double y2 ) SIP_HOLDGIL;
275 
276     /**
277      * Returns a value < 0 if the point \a point is left of the line from \a p1 -> \a p2.
278      * A positive return value indicates the point is to the right of the line.
279      *
280      * If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
281      * on the line, or exactly in line with the segment) and the result is undefined.
282      *
283      * \since QGIS 3.6
284      */
285     static int leftOfLine( const QgsPoint &point, const QgsPoint &p1, const QgsPoint &p2 ) SIP_HOLDGIL;
286 
287     /**
288      * Returns a point a specified \a distance toward a second point.
289      */
290     static QgsPoint pointOnLineWithDistance( const QgsPoint &startPoint, const QgsPoint &directionPoint, double distance ) SIP_HOLDGIL;
291 
292     /**
293      * Calculates the point a specified \a distance from (\a x1, \a y1) toward a second point (\a x2, \a y2).
294      *
295      * Optionally, interpolated z and m values can be obtained by specifying the \a z1, \a z2 and \a z arguments
296      * and/or the \a m1, \a m2, \a m arguments.
297      *
298      * \note Not available in Python bindings
299      * \since QGIS 3.4
300      */
301     static void pointOnLineWithDistance( double x1, double y1, double x2, double y2, double distance, double &x, double &y,
302                                          double *z1 = nullptr, double *z2 = nullptr, double *z = nullptr,
303                                          double *m1 = nullptr, double *m2 = nullptr, double *m = nullptr ) SIP_SKIP;
304 
305     /**
306      * Calculates a point a certain \a proportion of the way along the segment from (\a x1, \a y1) to (\a x2, \a y2),
307      * offset from the segment by the specified \a offset amount.
308      *
309      * \param x1 x-coordinate of start of segment
310      * \param y1 y-coordinate of start of segment
311      * \param x2 x-coordinate of end of segment
312      * \param y2 y-coordinate of end of segment
313      * \param proportion proportion of the segment's length at which to place the point (between 0.0 and 1.0)
314      * \param offset perpendicular offset from segment to apply to point. A negative \a offset shifts the point to the left of the segment, while a positive \a offset will shift it to the right of the segment.
315      * \param x calculated point x-coordinate
316      * \param y calculated point y-coordinate
317      *
318      * ### Example
319      *
320      * \code{.py}
321      *   # Offset point at center of segment by 2 units to the right
322      *   x, y = QgsGeometryUtils.perpendicularOffsetPointAlongSegment( 1, 5, 11, 5, 0.5, 2 )
323      *   # (6.0, 3.0)
324      *
325      *   # Offset point at center of segment by 2 units to the left
326      *   x, y = QgsGeometryUtils.perpendicularOffsetPointAlongSegment( 1, 5, 11, 5, 0.5, -2 )
327      *   # (6.0, 7.0)
328      * \endcode
329      *
330      * \since QGIS 3.20
331      */
332     static void perpendicularOffsetPointAlongSegment( double x1, double y1, double x2, double y2, double proportion, double offset, double *x SIP_OUT, double *y  SIP_OUT );
333 
334     /**
335      * Interpolates a point on an arc defined by three points, \a pt1, \a pt2 and \a pt3. The arc will be
336      * interpolated by the specified \a distance from \a pt1.
337      *
338      * Any z or m values present in the points will also be linearly interpolated in the output.
339      *
340      * \since QGIS 3.4
341      */
342     static QgsPoint interpolatePointOnArc( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double distance ) SIP_HOLDGIL;
343 
344     //! Returns the counter clockwise angle between a line with components dx, dy and the line with dx > 0 and dy = 0
345     static double ccwAngle( double dy, double dx ) SIP_HOLDGIL;
346 
347     //! Returns radius and center of the circle through pt1, pt2, pt3
348     static void circleCenterRadius( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double &radius SIP_OUT,
349                                     double &centerX SIP_OUT, double &centerY SIP_OUT ) SIP_HOLDGIL;
350 
351     /**
352      * Returns TRUE if the circle defined by three angles is ordered clockwise.
353      *
354      * The angles are defined counter-clockwise from the origin, i.e. using
355      * Euclidean angles as opposed to geographic "North up" angles.
356      */
357     static bool circleClockwise( double angle1, double angle2, double angle3 ) SIP_HOLDGIL;
358 
359     //! Returns TRUE if, in a circle, angle is between angle1 and angle2
360     static bool circleAngleBetween( double angle, double angle1, double angle2, bool clockwise ) SIP_HOLDGIL;
361 
362     /**
363      * Returns TRUE if an angle is between angle1 and angle3 on a circle described by
364      * angle1, angle2 and angle3.
365      */
366     static bool angleOnCircle( double angle, double angle1, double angle2, double angle3 ) SIP_HOLDGIL;
367 
368     //! Length of a circular string segment defined by pt1, pt2, pt3
369     static double circleLength( double x1, double y1, double x2, double y2, double x3, double y3 ) SIP_HOLDGIL;
370 
371     //! Calculates angle of a circular string part defined by pt1, pt2, pt3
372     static double sweepAngle( double centerX, double centerY, double x1, double y1, double x2, double y2, double x3, double y3 ) SIP_HOLDGIL;
373 
374     /**
375      * Calculates midpoint on circle passing through \a p1 and \a p2, closest to
376      * the given coordinate \a mousePos. Z dimension is supported and is retrieved from the
377      * first 3D point amongst \a p1 and \a p2.
378      * \see segmentMidPointFromCenter()
379      */
380     static bool segmentMidPoint( const QgsPoint &p1, const QgsPoint &p2, QgsPoint &result SIP_OUT, double radius, const QgsPoint &mousePos ) SIP_HOLDGIL;
381 
382     /**
383      * Calculates the midpoint on the circle passing through \a p1 and \a p2,
384      * with the specified \a center coordinate.
385      *
386      * If \a useShortestArc is TRUE, then the midpoint returned will be that corresponding
387      * to the shorter arc from \a p1 to \a p2. If it is FALSE, the longer arc from \a p1
388      * to \a p2 will be used (i.e. winding the other way around the circle).
389      *
390      * \see segmentMidPoint()
391      * \since QGIS 3.2
392      */
393     static QgsPoint segmentMidPointFromCenter( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &center, bool useShortestArc = true ) SIP_HOLDGIL;
394 
395     //! Calculates the direction angle of a circle tangent (clockwise from north in radians)
396     static double circleTangentDirection( const QgsPoint &tangentPoint, const QgsPoint &cp1, const QgsPoint &cp2, const QgsPoint &cp3 ) SIP_HOLDGIL;
397 
398     /**
399      * Convert circular arc defined by p1, p2, p3 (p1/p3 being start resp. end point, p2 lies on the arc) into a sequence of points.
400      * \since 3.0
401      */
402     static void segmentizeArc( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3,
403                                QgsPointSequence SIP_PYALTERNATIVETYPE( QVector<QgsPoint> ) &points SIP_OUT, double tolerance = M_PI_2 / 90,
404                                QgsAbstractGeometry::SegmentationToleranceType toleranceType = QgsAbstractGeometry::MaximumAngle,
405                                bool hasZ = false, bool hasM = false );
406 
407     /**
408      * Returns TRUE if point \a b is on the arc formed by points \a a1, \a a2, and \a a3, but not within
409      * that arc portion already described by \a a1, \a a2 and \a a3.
410      *
411      * The \a distanceTolerance specifies the maximum deviation allowed between the original location
412      * of point \b and where it would fall on the candidate arc.
413      *
414      * This method only consider a segments as continuing an arc if the points are all regularly spaced
415      * on the candidate arc. The \a pointSpacingAngleTolerance parameter specifies the maximum
416      * angular deviation (in radians) allowed when testing for regular point spacing.
417      *
418      * \note The API is considered EXPERIMENTAL and can be changed without a notice
419      *
420      * \since QGIS 3.14
421      */
422     static bool pointContinuesArc( const QgsPoint &a1, const QgsPoint &a2, const QgsPoint &a3, const QgsPoint &b, double distanceTolerance,
423                                    double pointSpacingAngleTolerance ) SIP_HOLDGIL;
424 
425     /**
426      * For line defined by points pt1 and pt3, find out on which side of the line is point pt3.
427      * Returns -1 if pt3 on the left side, 1 if pt3 is on the right side or 0 if pt3 lies on the line.
428      * \since 3.0
429      */
430     static int segmentSide( const QgsPoint &pt1, const QgsPoint &pt3, const QgsPoint &pt2 ) SIP_HOLDGIL;
431 
432     /**
433      * Interpolate a value at given angle on circular arc given values (zm1, zm2, zm3) at three different angles (a1, a2, a3).
434      * \since 3.0
435      */
436     static double interpolateArcValue( double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3 ) SIP_HOLDGIL;
437 
438     /**
439      * Returns a list of points contained in a WKT string.
440      * \note not available in Python bindings
441      */
442     static QgsPointSequence pointsFromWKT( const QString &wktCoordinateList, bool is3D, bool isMeasure ) SIP_SKIP;
443 
444     /**
445      * Returns a LinearRing { uint32 numPoints; Point points[numPoints]; }
446      * \note not available in Python bindings
447      */
448     static void pointsToWKB( QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure ) SIP_SKIP;
449 
450     /**
451      * Returns a WKT coordinate list
452      * \note not available in Python bindings
453      */
454     static QString pointsToWKT( const QgsPointSequence &points, int precision, bool is3D, bool isMeasure ) SIP_SKIP;
455 
456     /**
457      * Returns a gml::coordinates DOM element.
458      * \note not available in Python bindings
459      */
460     static QDomElement pointsToGML2( const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, QgsAbstractGeometry::AxisOrder axisOrder = QgsAbstractGeometry::AxisOrder::XY ) SIP_SKIP;
461 
462     /**
463      * Returns a gml::posList DOM element.
464      * \note not available in Python bindings
465      */
466     static QDomElement pointsToGML3( const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, bool is3D, QgsAbstractGeometry::AxisOrder axisOrder = QgsAbstractGeometry::AxisOrder::XY ) SIP_SKIP;
467 
468     /**
469      * Returns a geoJSON coordinates string.
470      * \note not available in Python bindings
471      */
472     static QString pointsToJSON( const QgsPointSequence &points, int precision ) SIP_SKIP;
473 
474     /**
475      * Returns coordinates as json object.
476      * \note not available in Python bindings
477      */
478     static json pointsToJson( const QgsPointSequence &points, int precision ) SIP_SKIP;
479 
480     /**
481      * Ensures that an angle is in the range 0 <= angle < 2 pi.
482      * \param angle angle in radians
483      * \returns equivalent angle within the range [0, 2 pi)
484      */
485     static double normalizedAngle( double angle ) SIP_HOLDGIL;
486 
487     /**
488      * Calculates the direction of line joining two points in radians, clockwise from the north direction.
489      * \param x1 x-coordinate of line start
490      * \param y1 y-coordinate of line start
491      * \param x2 x-coordinate of line end
492      * \param y2 y-coordinate of line end
493      * \returns angle in radians. Returned value is undefined if start and end point are the same.
494      */
495     static double lineAngle( double x1, double y1, double x2, double y2 ) SIP_HOLDGIL;
496 
497     /**
498      * Calculates the angle between the lines AB and BC, where AB and BC described
499      * by points a, b and b, c.
500      * \param x1 x-coordinate of point a
501      * \param y1 y-coordinate of point a
502      * \param x2 x-coordinate of point b
503      * \param y2 y-coordinate of point b
504      * \param x3 x-coordinate of point c
505      * \param y3 y-coordinate of point c
506      * \returns angle between lines in radians. Returned value is undefined if two or more points are equal.
507      */
508     static double angleBetweenThreePoints( double x1, double y1, double x2, double y2,
509                                            double x3, double y3 ) SIP_HOLDGIL;
510 
511     /**
512      * Calculates the perpendicular angle to a line joining two points. Returned angle is in radians,
513      * clockwise from the north direction.
514      * \param x1 x-coordinate of line start
515      * \param y1 y-coordinate of line start
516      * \param x2 x-coordinate of line end
517      * \param y2 y-coordinate of line end
518      * \returns angle in radians. Returned value is undefined if start and end point are the same.
519      */
520     static double linePerpendicularAngle( double x1, double y1, double x2, double y2 ) SIP_HOLDGIL;
521 
522     /**
523      * Calculates the average angle (in radians) between the two linear segments from
524      * (\a x1, \a y1) to (\a x2, \a y2) and (\a x2, \a y2) to (\a x3, \a y3).
525      */
526     static double averageAngle( double x1, double y1, double x2, double y2, double x3, double y3 ) SIP_HOLDGIL;
527 
528     /**
529      * Averages two angles, correctly handling negative angles and ensuring the result is between 0 and 2 pi.
530      * \param a1 first angle (in radians)
531      * \param a2 second angle (in radians)
532      * \returns average angle (in radians)
533      */
534     static double averageAngle( double a1, double a2 ) SIP_HOLDGIL;
535 
536     /**
537      * Parses a WKT block of the format "TYPE( contents )" and returns a pair of geometry type to contents ("Pair(wkbType, "contents")")
538      * \note not available in Python bindings
539      */
540     static QPair<QgsWkbTypes::Type, QString> wktReadBlock( const QString &wkt ) SIP_SKIP;
541 
542     /**
543      * Parses a WKT string and returns of list of blocks contained in the WKT.
544      * \param wkt WKT string in the format "TYPE1 (contents1), TYPE2 (TYPE3 (contents3), TYPE4 (contents4))"
545      * \param defaultType default geometry type for children
546      * \returns list of WKT child block strings, e.g., List("TYPE1 (contents1)", "TYPE2 (TYPE3 (contents3), TYPE4 (contents4))")
547      * \note not available in Python bindings
548      */
549     static QStringList wktGetChildBlocks( const QString &wkt, const QString &defaultType = QString() ) SIP_SKIP;
550 
551     /**
552      * Returns a number representing the closest side of a rectangle defined by /a right,
553      * \a bottom, \a left, \a top to the point at (\a x, \a y), where
554      * the point may be in the interior of the rectangle or outside it.
555      *
556      * The returned value may be:
557      *
558      * 1. Point is closest to top side of rectangle
559      * 2. Point is located on the top-right diagonal of rectangle, equally close to the top and right sides
560      * 3. Point is closest to right side of rectangle
561      * 4. Point is located on the bottom-right diagonal of rectangle, equally close to the bottom and right sides
562      * 5. Point is closest to bottom side of rectangle
563      * 6. Point is located on the bottom-left diagonal of rectangle, equally close to the bottom and left sides
564      * 7. Point is closest to left side of rectangle
565      * 8. Point is located on the top-left diagonal of rectangle, equally close to the top and left sides
566      *
567      * \note This method effectively partitions the space outside of the rectangle into Voronoi cells, so a point
568      * to the top left of the rectangle may be assigned to the left or top sides based on its position relative
569      * to the diagonal line extended from the rectangle's top-left corner.
570      *
571      * \since QGIS 3.20
572      */
573     static int closestSideOfRectangle( double right, double bottom, double left, double top, double x, double y );
574 
575     /**
576      * Returns a middle point between points pt1 and pt2.
577      * Z value is computed if one of this point have Z.
578      * M value is computed if one of this point have M.
579      * \param pt1 first point.
580      * \param pt2 second point.
581      * \returns New point at middle between points pt1 and pt2.
582      *
583      * ### Example
584      *
585      * \code{.py}
586      *   p = QgsPoint( 4, 6 ) # 2D point
587      *   pr = midpoint ( p, QgsPoint( 2, 2 ) )
588      *   # pr is a 2D point: 'Point (3 4)'
589      *   pr = midpoint ( p, QgsPoint( QgsWkbTypes.PointZ, 2, 2, 2 ) )
590      *   # pr is a 3D point: 'PointZ (3 4 1)'
591      *   pr = midpoint ( p, QgsPoint( QgsWkbTypes.PointM, 2, 2, 0, 2 ) )
592      *   # pr is a 3D point: 'PointM (3 4 1)'
593      *   pr = midpoint ( p, QgsPoint( QgsWkbTypes.PointZM, 2, 2, 2, 2 ) )
594      *   # pr is a 3D point: 'PointZM (3 4 1 1)'
595      * \endcode
596      * \since QGIS 3.0
597      */
598     static QgsPoint midpoint( const QgsPoint &pt1, const QgsPoint &pt2 ) SIP_HOLDGIL;
599 
600     /**
601      * Interpolates the position of a point a \a fraction of the way along
602      * the line from (\a x1, \a y1) to (\a x2, \a y2).
603      *
604      * Usually the \a fraction should be between 0 and 1, where 0 represents the
605      * point at the start of the line (\a x1, \a y1) and 1 represents
606      * the end of the line (\a x2, \a y2). However, it is possible to
607      * use a \a fraction < 0 or > 1, in which case the returned point
608      * is extrapolated from the supplied line.
609      *
610      * \see interpolatePointOnLineByValue()
611      * \since QGIS 3.0.2
612      */
613     static QgsPointXY interpolatePointOnLine( double x1, double y1, double x2, double y2, double fraction ) SIP_HOLDGIL;
614 
615     /**
616      * Interpolates the position of a point a \a fraction of the way along
617      * the line from \a p1 to \a p2.
618      *
619      * Usually the \a fraction should be between 0 and 1, where 0 represents the
620      * point at the start of the line (\a p1) and 1 represents
621      * the end of the line (\a p2). However, it is possible to
622      * use a \a fraction < 0 or > 1, in which case the returned point
623      * is extrapolated from the supplied line.
624      *
625      * Any Z or M values present in the input points will also be interpolated
626      * and present in the returned point.
627      *
628      * \see interpolatePointOnLineByValue()
629      * \since QGIS 3.0.2
630      */
631     static QgsPoint interpolatePointOnLine( const QgsPoint &p1, const QgsPoint &p2, double fraction ) SIP_HOLDGIL;
632 
633     /**
634      * Interpolates the position of a point along the line from (\a x1, \a y1)
635      * to (\a x2, \a y2).
636      *
637      * The position is interpolated using a supplied target \a value and the value
638      * at the start of the line (\a v1) and end of the line (\a v2). The returned
639      * point will be linearly interpolated to match position corresponding to
640      * the target \a value.
641      *
642      * \see interpolatePointOnLine()
643      * \since QGIS 3.0.2
644      */
645     static QgsPointXY interpolatePointOnLineByValue( double x1, double y1, double v1, double x2, double y2, double v2, double value ) SIP_HOLDGIL;
646 
647     /**
648      * Returns the gradient of a line defined by points \a pt1 and \a pt2.
649      * \param pt1 first point.
650      * \param pt2 second point.
651      * \returns The gradient of this linear entity, or infinity if vertical
652      * \since QGIS 3.0
653      */
654     static double gradient( const QgsPoint &pt1, const QgsPoint &pt2 ) SIP_HOLDGIL;
655 
656     /**
657      * Returns the coefficients (a, b, c for equation "ax + by + c = 0") of a line defined by points \a pt1 and \a pt2.
658      * \param pt1 first point.
659      * \param pt2 second point.
660      * \param a Output parameter, a coefficient of the equation.
661      * \param b Output parameter, b coefficient of the equation.
662      * \param c Output parameter, c coefficient of the equation.
663      * \since QGIS 3.0
664      */
665     static void coefficients( const QgsPoint &pt1, const QgsPoint &pt2,
666                               double &a SIP_OUT, double &b SIP_OUT, double &c SIP_OUT ) SIP_HOLDGIL;
667 
668     /**
669      * \brief Create a perpendicular line segment from p to segment [s1, s2]
670      * \param p The point
671      * \param s1 The segment start point
672      * \param s2 The segment end point
673      * \returns A line (segment) from p to perpendicular point on segment [s1, s2]
674      */
675     static QgsLineString perpendicularSegment( const QgsPoint &p, const QgsPoint &s1, const QgsPoint &s2 ) SIP_HOLDGIL;
676 
677 
678     /**
679      * An algorithm to calculate the shortest distance between two skew lines.
680      * \param P1 is the first point of the first line,
681      * \param P12 is the second point on the first line,
682      * \param P2 is the first point on the second line,
683      * \param P22 is the second point on the second line.
684      * \return the shortest distance
685      */
686     static double skewLinesDistance( const QgsVector3D &P1, const QgsVector3D &P12,
687                                      const QgsVector3D &P2, const QgsVector3D &P22 ) SIP_HOLDGIL;
688 
689     /**
690      * A method to project one skew line onto another.
691      * \param P1 is a first point that belonds to first skew line,
692      * \param P12 is the second point that belongs to first skew line,
693      * \param P2 is the first point that belongs to second skew line,
694      * \param P22 is the second point that belongs to second skew line,
695      * \param X1 is the result projection point of line P2P22 onto line P1P12,
696      * \param epsilon the tolerance to use.
697      * \return TRUE if such point exists, FALSE - otherwise.
698      */
699     static bool skewLinesProjection( const QgsVector3D &P1, const QgsVector3D &P12,
700                                      const QgsVector3D &P2, const QgsVector3D &P22,
701                                      QgsVector3D &X1  SIP_OUT,
702                                      double epsilon = 0.0001 ) SIP_HOLDGIL;
703 
704     /**
705      * An algorithm to calculate an (approximate) intersection of two lines in 3D.
706      * \param La1 is the first point on the first line,
707      * \param La2 is the second point on the first line,
708      * \param Lb1 is the first point on the second line,
709      * \param Lb2 is the second point on the second line,
710      * \param intersection is the result intersection, of it can be found.
711      * \return TRUE if the intersection can be found, FALSE - otherwise.
712      *
713      * ### Example
714      *
715      * \code{.py}
716      *   QgsGeometryUtils.linesIntersection3D(QgsVector3D(0,0,0), QgsVector3D(5,0,0), QgsVector3D(2,1,0), QgsVector3D(2,3,0))
717      *   # (True, PyQt5.QtGui.QgsVector3D(2.0, 0.0, 0.0))
718      *   QgsGeometryUtils.linesIntersection3D(QgsVector3D(0,0,0), QgsVector3D(5,0,0), QgsVector3D(2,1,0), QgsVector3D(2,0,0))
719      *   # (True, PyQt5.QtGui.QgsVector3D(2.0, 0.0, 0.0))
720      *   QgsGeometryUtils.linesIntersection3D(QgsVector3D(0,0,0), QgsVector3D(5,0,0), QgsVector3D(0,1,0), QgsVector3D(0,3,0))
721      *   # (True, PyQt5.QtGui.QgsVector3D(0.0, 0.0, 0.0))
722      *   QgsGeometryUtils.linesIntersection3D(QgsVector3D(0,0,0), QgsVector3D(5,0,0), QgsVector3D(0,1,0), QgsVector3D(0,0,0))
723      *   # (True, PyQt5.QtGui.QgsVector3D(0.0, 0.0, 0.0))
724      *   QgsGeometryUtils.linesIntersection3D(QgsVector3D(0,0,0), QgsVector3D(5,0,0), QgsVector3D(5,1,0), QgsVector3D(5,3,0))
725      *   # (False, PyQt5.QtGui.QgsVector3D(0.0, 0.0, 0.0))
726      *   QgsGeometryUtils.linesIntersection3D(QgsVector3D(0,0,0), QgsVector3D(5,0,0), QgsVector3D(5,1,0), QgsVector3D(5,0,0))
727      *   # (False, PyQt5.QtGui.QgsVector3D(0.0, 0.0, 0.0))
728      *   QgsGeometryUtils.linesIntersection3D(QgsVector3D(1,1,0), QgsVector3D(2,2,0), QgsVector3D(3,1,0), QgsVector3D(3,2,0))
729      *   # (True, PyQt5.QtGui.QgsVector3D(3.0, 3.0, 0.0))
730      *   QgsGeometryUtils.linesIntersection3D(QgsVector3D(1,1,0), QgsVector3D(2,2,0), QgsVector3D(3,2,0), QgsVector3D(3,1,0))
731      *   # (True, PyQt5.QtGui.QgsVector3D(3.0, 3.0, 0.0))
732      *   QgsGeometryUtils.linesIntersection3D(QgsVector3D(5,5,5), QgsVector3D(0,0,0), QgsVector3D(0,5,5), QgsVector3D(5,0,0))
733      *   # (True, PyQt5.QtGui.QgsVector3D(2.5, 2.5, 2.5))
734      *   QgsGeometryUtils.linesIntersection3D(QgsVector3D(2.5,2.5,2.5), QgsVector3D(0,5,0), QgsVector3D(2.5,2.5,2.5), QgsVector3D(5,0,0))
735      *   # (True, PyQt5.QtGui.QgsVector3D(2.5, 2.5, 2.5))
736      *   QgsGeometryUtils.linesIntersection3D(QgsVector3D(2.5,2.5,2.5), QgsVector3D(5,0,0), QgsVector3D(0,5,5), QgsVector3D(5,5,5))
737      *   # (True, PyQt5.QtGui.QgsVector3D(0.0, 5.0, 5.0))
738      *   \endcode
739      */
740     static bool linesIntersection3D( const QgsVector3D &La1, const QgsVector3D &La2,
741                                      const QgsVector3D &Lb1, const QgsVector3D &Lb2,
742                                      QgsVector3D &intersection  SIP_OUT ) SIP_HOLDGIL;
743 
744     /**
745      * Returns the area of the triangle denoted by the points (\a aX, \a aY), (\a bX, \a bY) and
746      * (\a cX, \a cY).
747      *
748      * \since QGIS 3.10
749      */
750     static double triangleArea( double aX, double aY, double bX, double bY, double cX, double cY ) SIP_HOLDGIL;
751 
752     /**
753      * Returns a weighted point inside the triangle denoted by the points (\a aX, \a aY), (\a bX, \a bY) and
754      * (\a cX, \a cY).
755      *
756      * \param aX x-coordinate of first vertex in triangle
757      * \param aY y-coordinate of first vertex in triangle
758      * \param bX x-coordinate of second vertex in triangle
759      * \param bY y-coordinate of second vertex in triangle
760      * \param cX x-coordinate of third vertex in triangle
761      * \param cY y-coordinate of third vertex in triangle
762      * \param weightB weighting factor along axis A-B (between 0 and 1)
763      * \param weightC weighting factor along axis A-C (between 0 and 1)
764      * \param pointX x-coordinate of generated point
765      * \param pointY y-coordinate of generated point
766      *
767      * \since QGIS 3.10
768      */
769     static void weightedPointInTriangle( double aX, double aY, double bX, double bY, double cX, double cY,
770                                          double weightB, double weightC, double &pointX SIP_OUT, double &pointY SIP_OUT ) SIP_HOLDGIL;
771 
772     /**
773      * A Z dimension is added to \a point if one of the point in the list
774      * \a points is in 3D. Moreover, the Z value of \a point is updated
775      * with the first Z value found in list \a points even if \a point
776      * already contains a Z value.
777      *
778      * \param points List of points in which a 3D point is searched.
779      * \param point The point to update with Z dimension and value.
780      * \returns TRUE if the point is updated, FALSE otherwise
781      *
782      * \warning This method does not copy the z value of the coordinate from the
783      * points whose z value is closest to the original x/y point, but only the first one found.
784      *
785      * \since QGIS 3.0
786      * \deprecated since QGIS 3.20 use transferFirstZValueToPoint( const QgsPointSequence &points, QgsPoint &point ) instead
787      */
setZValueFromPoints(const QgsPointSequence & points,QgsPoint & point)788     Q_DECL_DEPRECATED static bool setZValueFromPoints( const QgsPointSequence &points, QgsPoint &point ) SIP_DEPRECATED
789     {
790       return transferFirstZValueToPoint( points, point );
791     }
792 
793     /**
794      * A Z dimension is added to \a point if one of the point in the list
795      * \a points is in 3D. Moreover, the Z value of \a point is updated
796      * with the first Z value found in list \a points even if \a point
797      * already contains a Z value.
798      *
799      * \param points List of points in which a 3D point is searched.
800      * \param point The point to update with Z dimension and value.
801      * \returns TRUE if the point is updated, FALSE otherwise
802      *
803      * \warning This method does not copy the z value of the coordinate from the
804      * points whose z value is closest to the original x/y point, but only the first one found.
805      *
806      * \since QGIS 3.20
807      */
808     static bool transferFirstZValueToPoint( const QgsPointSequence &points, QgsPoint &point );
809 
810     /**
811      * A M dimension is added to \a point if one of the points in the list
812      * \a points contains an M value. Moreover, the M value of \a point is
813      * updated with the first M value found in list \a points even if \a point
814      * already contains a M value.
815      *
816      * \param points List of points in which a M point is searched.
817      * \param point The point to update with M dimension and value.
818      * \returns TRUE if the point is updated, FALSE otherwise
819      *
820      * \warning This method does not copy the m value of the coordinate from the
821      * points whose m value is closest to the original x/y point, but only the first one found.
822      *
823      * \since QGIS 3.20
824      */
825     static bool transferFirstMValueToPoint( const QgsPointSequence &points, QgsPoint &point );
826 
827     /**
828      * A Z or M dimension is added to \a point if one of the points in the list
829      * \a points contains Z or M value.
830      *
831      * This method is equivalent to successively calling Z and M but avoiding
832      * looping twice over the set of points.
833      *
834      * \param verticesBegin begin vertex which a Z or M point is searched.
835      * \param verticesEnd end vertex which a Z or M point is searched.
836      * \param point The point to update with Z or M dimension and value.
837      * \returns TRUE if the point is updated, FALSE otherwise
838      *
839      * \warning This method does not copy the z or m value of the coordinate from the
840      * points whose z or m value is closest to the original x/y point, but only the first one found.
841      *
842      * \note Not available in Python bindings
843      * \since QGIS 3.20
844      */
transferFirstZOrMValueToPoint(Iterator verticesBegin,Iterator verticesEnd,QgsPoint & point)845     template <class Iterator> static bool transferFirstZOrMValueToPoint( Iterator verticesBegin, Iterator verticesEnd, QgsPoint &point ) SIP_SKIP
846     {
847       bool zFound = false;
848       bool mFound = false;
849 
850       for ( auto it = verticesBegin ; it != verticesEnd ; ++it )
851       {
852         if ( !mFound && ( *it ).isMeasure() )
853         {
854           point.convertTo( QgsWkbTypes::addM( point.wkbType() ) );
855           point.setM( ( *it ).m() );
856           mFound = true;
857         }
858         if ( !zFound && ( *it ).is3D() )
859         {
860           point.convertTo( QgsWkbTypes::addZ( point.wkbType() ) );
861           point.setZ( ( *it ).z() );
862           zFound = true;
863         }
864         if ( zFound && mFound )
865           break;
866       }
867 
868       return zFound || mFound;
869     }
870 
871     /**
872      * A Z or M dimension is added to \a point if one of the points in the list
873      * \a points contains Z or M value.
874      *
875      * This method is equivalent to successively calling Z and M but avoiding
876      * looping twice over the set of points.
877      *
878      * \param points List of points in which a M point is searched.
879      * \param point The point to update with Z or M dimension and value.
880      * \returns TRUE if the point is updated, FALSE otherwise
881      *
882      * \warning This method does not copy the z or m value of the coordinate from the
883      * points whose z or m value is closest to the original x/y point, but only the first one found.
884      *
885      * \since QGIS 3.20
886      */
transferFirstZOrMValueToPoint(const QgsPointSequence & points,QgsPoint & point)887     static bool transferFirstZOrMValueToPoint( const QgsPointSequence &points, QgsPoint &point )
888     {
889       return QgsGeometryUtils::transferFirstZOrMValueToPoint( points.constBegin(), points.constEnd(), point );
890     }
891 
892     /**
893      * A Z or M dimension is added to \a point if one of the points in the list
894      * \a points contains Z or M value.
895      *
896      * This method is equivalent to successively calling Z and M but avoiding
897      * looping twice over the set of points.
898      *
899      * \param geom geometry in which a M point is searched.
900      * \param point The point to update with Z or M dimension and value.
901      * \returns TRUE if the point is updated, FALSE otherwise
902      *
903      * \warning This method does not copy the z or m value of the coordinate from the
904      * points whose z or m value is closest to the original x/y point, but only the first one found.
905      *
906      * \since QGIS 3.20
907      */
transferFirstZOrMValueToPoint(const QgsGeometry & geom,QgsPoint & point)908     static bool transferFirstZOrMValueToPoint( const QgsGeometry &geom, QgsPoint &point )
909     {
910       return QgsGeometryUtils::transferFirstZOrMValueToPoint( geom.vertices_begin(), geom.vertices_end(), point );
911     }
912 
913     /**
914      * Returns the point (\a pointX, \a pointY) forming the bisector from segment (\a aX \a aY) (\a bX \a bY)
915      * and segment (\a bX, \a bY) (\a dX, \a dY).
916      * The bisector segment of AB-CD is (point, projection of point by \a angle)
917      *
918      * \param aX x-coordinate of first vertex of the segment ab
919      * \param aY y-coordinate of first vertex of the segment ab
920      * \param bX x-coordinate of second vertex of the segment ab
921      * \param bY y-coordinate of second vertex of the segment ab
922      * \param cX x-coordinate of first vertex of the segment cd
923      * \param cY y-coordinate of first vertex of the segment cd
924      * \param dX x-coordinate of second vertex of the segment cd
925      * \param dY y-coordinate of second vertex of the segment cd
926      * \param pointX x-coordinate of generated point
927      * \param pointY y-coordinate of generated point
928      * \param angle angle of the bisector from pointX, pointY origin on [ab-cd]
929      * \returns TRUE if the bisector exists (A B and C D are not collinear)
930      *
931      * \since QGIS 3.18
932      */
933 
934     static bool angleBisector( double aX, double aY, double bX, double bY, double cX, double cY, double dX, double dY,
935                                double &pointX SIP_OUT, double &pointY SIP_OUT, double &angle SIP_OUT ) SIP_HOLDGIL;
936 
937     /**
938      * Returns the point (\a pointX, \a pointY) forming the bisector from point (\a aX, \a aY) to the segment (\a bX, \a bY) (\a cX, \a cY).
939      * The bisector segment of ABC is (A-point)
940      *
941      * \param aX x-coordinate of first vertex in triangle
942      * \param aY y-coordinate of first vertex in triangle
943      * \param bX x-coordinate of second vertex in triangle
944      * \param bY y-coordinate of second vertex in triangle
945      * \param cX x-coordinate of third vertex in triangle
946      * \param cY y-coordinate of third vertex in triangle
947      * \param pointX x-coordinate of generated point
948      * \param pointY y-coordinate of generated point
949      * \returns TRUE if the bisector exists (A B and C are not collinear)
950      *
951      * \since QGIS 3.18
952      */
953     static bool bisector( double aX, double aY, double bX, double bY, double cX, double cY,
954                           double &pointX SIP_OUT, double &pointY SIP_OUT ) SIP_HOLDGIL;
955 
956     //! \note not available in Python bindings
957     enum ComponentType SIP_SKIP
958     {
959       Vertex,
960       Ring,
961       Part
962     };
963 
964     //! \note not available in Python bindings
closestSegmentFromComponents(T & container,ComponentType ctype,const QgsPoint & pt,QgsPoint & segmentPt,QgsVertexId & vertexAfter,int * leftOf,double epsilon)965     template<class T> static double closestSegmentFromComponents( T &container, ComponentType ctype, const QgsPoint &pt, QgsPoint &segmentPt,  QgsVertexId &vertexAfter, int *leftOf, double epsilon ) SIP_SKIP
966     {
967       double minDist = std::numeric_limits<double>::max();
968       double minDistSegmentX = 0.0, minDistSegmentY = 0.0;
969       QgsVertexId minDistVertexAfter;
970       int minDistLeftOf = 0;
971       double sqrDist = 0.0;
972       int vertexOffset = 0;
973       int ringOffset = 0;
974       int partOffset = 0;
975 
976       for ( int i = 0; i < container.size(); ++i )
977       {
978         sqrDist = container.at( i )->closestSegment( pt, segmentPt, vertexAfter, leftOf, epsilon );
979         if ( sqrDist >= 0 && sqrDist < minDist )
980         {
981           minDist = sqrDist;
982           minDistSegmentX = segmentPt.x();
983           minDistSegmentY = segmentPt.y();
984           minDistVertexAfter = vertexAfter;
985           minDistVertexAfter.vertex = vertexAfter.vertex + vertexOffset;
986           minDistVertexAfter.part = vertexAfter.part + partOffset;
987           minDistVertexAfter.ring = vertexAfter.ring + ringOffset;
988           if ( leftOf )
989           {
990             minDistLeftOf = *leftOf;
991           }
992         }
993 
994         if ( ctype == Vertex )
995         {
996           //-1 because compoundcurve counts duplicated vertices of neighbour curves as one node
997           vertexOffset += container.at( i )->nCoordinates() - 1;
998         }
999         else if ( ctype == Ring )
1000         {
1001           ringOffset += 1;
1002         }
1003         else if ( ctype == Part )
1004         {
1005           partOffset += 1;
1006         }
1007       }
1008 
1009       if ( minDist == std::numeric_limits<double>::max() )
1010         return -1;  // error: no segments
1011 
1012       segmentPt.setX( minDistSegmentX );
1013       segmentPt.setY( minDistSegmentY );
1014       vertexAfter = minDistVertexAfter;
1015       if ( leftOf )
1016       {
1017         *leftOf = minDistLeftOf;
1018       }
1019       return minDist;
1020     }
1021 };
1022 
1023 #endif // QGSGEOMETRYUTILS_H
1024