1 /***************************************************************************
2 qgsgeometryutils.cpp
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 #include "qgsgeometryutils.h"
17
18 #include "qgscurve.h"
19 #include "qgscurvepolygon.h"
20 #include "qgsgeometrycollection.h"
21 #include "qgslinestring.h"
22 #include "qgswkbptr.h"
23 #include "qgslogger.h"
24
25 #include <memory>
26 #include <QStringList>
27 #include <QVector>
28 #include <QRegularExpression>
29 #include <nlohmann/json.hpp>
30
extractLineStrings(const QgsAbstractGeometry * geom)31 QVector<QgsLineString *> QgsGeometryUtils::extractLineStrings( const QgsAbstractGeometry *geom )
32 {
33 QVector< QgsLineString * > linestrings;
34 if ( !geom )
35 return linestrings;
36
37 QVector< const QgsAbstractGeometry * > geometries;
38 geometries << geom;
39 while ( ! geometries.isEmpty() )
40 {
41 const QgsAbstractGeometry *g = geometries.takeFirst();
42 if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( g ) )
43 {
44 linestrings << static_cast< QgsLineString * >( curve->segmentize() );
45 }
46 else if ( const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( g ) )
47 {
48 for ( int i = 0; i < collection->numGeometries(); ++i )
49 {
50 geometries.append( collection->geometryN( i ) );
51 }
52 }
53 else if ( const QgsCurvePolygon *curvePolygon = qgsgeometry_cast< const QgsCurvePolygon * >( g ) )
54 {
55 if ( curvePolygon->exteriorRing() )
56 linestrings << static_cast< QgsLineString * >( curvePolygon->exteriorRing()->segmentize() );
57
58 for ( int i = 0; i < curvePolygon->numInteriorRings(); ++i )
59 {
60 linestrings << static_cast< QgsLineString * >( curvePolygon->interiorRing( i )->segmentize() );
61 }
62 }
63 }
64 return linestrings;
65 }
66
closestVertex(const QgsAbstractGeometry & geom,const QgsPoint & pt,QgsVertexId & id)67 QgsPoint QgsGeometryUtils::closestVertex( const QgsAbstractGeometry &geom, const QgsPoint &pt, QgsVertexId &id )
68 {
69 double minDist = std::numeric_limits<double>::max();
70 double currentDist = 0;
71 QgsPoint minDistPoint;
72 id = QgsVertexId(); // set as invalid
73
74 if ( geom.isEmpty() || pt.isEmpty() )
75 return minDistPoint;
76
77 QgsVertexId vertexId;
78 QgsPoint vertex;
79 while ( geom.nextVertex( vertexId, vertex ) )
80 {
81 currentDist = QgsGeometryUtils::sqrDistance2D( pt, vertex );
82 // The <= is on purpose: for geometries with closing vertices, this ensures
83 // that the closing vertex is returned. For the vertex tool, the rubberband
84 // of the closing vertex is above the opening vertex, hence with the <=
85 // situations where the covered opening vertex rubberband is selected are
86 // avoided.
87 if ( currentDist <= minDist )
88 {
89 minDist = currentDist;
90 minDistPoint = vertex;
91 id.part = vertexId.part;
92 id.ring = vertexId.ring;
93 id.vertex = vertexId.vertex;
94 id.type = vertexId.type;
95 }
96 }
97
98 return minDistPoint;
99 }
100
closestPoint(const QgsAbstractGeometry & geometry,const QgsPoint & point)101 QgsPoint QgsGeometryUtils::closestPoint( const QgsAbstractGeometry &geometry, const QgsPoint &point )
102 {
103 QgsPoint closestPoint;
104 QgsVertexId vertexAfter;
105 geometry.closestSegment( point, closestPoint, vertexAfter, nullptr, DEFAULT_SEGMENT_EPSILON );
106 if ( vertexAfter.isValid() )
107 {
108 const QgsPoint pointAfter = geometry.vertexAt( vertexAfter );
109 if ( vertexAfter.vertex > 0 )
110 {
111 QgsVertexId vertexBefore = vertexAfter;
112 vertexBefore.vertex--;
113 const QgsPoint pointBefore = geometry.vertexAt( vertexBefore );
114 const double length = pointBefore.distance( pointAfter );
115 const double distance = pointBefore.distance( closestPoint );
116
117 if ( qgsDoubleNear( distance, 0.0 ) )
118 closestPoint = pointBefore;
119 else if ( qgsDoubleNear( distance, length ) )
120 closestPoint = pointAfter;
121 else
122 {
123 if ( QgsWkbTypes::hasZ( geometry.wkbType() ) && length )
124 closestPoint.addZValue( pointBefore.z() + ( pointAfter.z() - pointBefore.z() ) * distance / length );
125 if ( QgsWkbTypes::hasM( geometry.wkbType() ) )
126 closestPoint.addMValue( pointBefore.m() + ( pointAfter.m() - pointBefore.m() ) * distance / length );
127 }
128 }
129 }
130
131 return closestPoint;
132 }
133
distanceToVertex(const QgsAbstractGeometry & geom,QgsVertexId id)134 double QgsGeometryUtils::distanceToVertex( const QgsAbstractGeometry &geom, QgsVertexId id )
135 {
136 double currentDist = 0;
137 QgsVertexId vertexId;
138 QgsPoint vertex;
139 while ( geom.nextVertex( vertexId, vertex ) )
140 {
141 if ( vertexId == id )
142 {
143 //found target vertex
144 return currentDist;
145 }
146 currentDist += geom.segmentLength( vertexId );
147 }
148
149 //could not find target vertex
150 return -1;
151 }
152
verticesAtDistance(const QgsAbstractGeometry & geometry,double distance,QgsVertexId & previousVertex,QgsVertexId & nextVertex)153 bool QgsGeometryUtils::verticesAtDistance( const QgsAbstractGeometry &geometry, double distance, QgsVertexId &previousVertex, QgsVertexId &nextVertex )
154 {
155 double currentDist = 0;
156 previousVertex = QgsVertexId();
157 nextVertex = QgsVertexId();
158
159 QgsPoint point;
160 QgsPoint previousPoint;
161
162 if ( qgsDoubleNear( distance, 0.0 ) )
163 {
164 geometry.nextVertex( previousVertex, point );
165 nextVertex = previousVertex;
166 return true;
167 }
168
169 bool first = true;
170 while ( currentDist < distance && geometry.nextVertex( nextVertex, point ) )
171 {
172 if ( !first && nextVertex.part == previousVertex.part && nextVertex.ring == previousVertex.ring )
173 {
174 currentDist += std::sqrt( QgsGeometryUtils::sqrDistance2D( previousPoint, point ) );
175 }
176
177 if ( qgsDoubleNear( currentDist, distance ) )
178 {
179 // exact hit!
180 previousVertex = nextVertex;
181 return true;
182 }
183
184 if ( currentDist > distance )
185 {
186 return true;
187 }
188
189 previousVertex = nextVertex;
190 previousPoint = point;
191 first = false;
192 }
193
194 //could not find target distance
195 return false;
196 }
197
sqrDistance2D(const QgsPoint & pt1,const QgsPoint & pt2)198 double QgsGeometryUtils::sqrDistance2D( const QgsPoint &pt1, const QgsPoint &pt2 )
199 {
200 return ( pt1.x() - pt2.x() ) * ( pt1.x() - pt2.x() ) + ( pt1.y() - pt2.y() ) * ( pt1.y() - pt2.y() );
201 }
202
sqrDistToLine(double ptX,double ptY,double x1,double y1,double x2,double y2,double & minDistX,double & minDistY,double epsilon)203 double QgsGeometryUtils::sqrDistToLine( double ptX, double ptY, double x1, double y1, double x2, double y2, double &minDistX, double &minDistY, double epsilon )
204 {
205 minDistX = x1;
206 minDistY = y1;
207
208 double dx = x2 - x1;
209 double dy = y2 - y1;
210
211 if ( !qgsDoubleNear( dx, 0.0 ) || !qgsDoubleNear( dy, 0.0 ) )
212 {
213 const double t = ( ( ptX - x1 ) * dx + ( ptY - y1 ) * dy ) / ( dx * dx + dy * dy );
214 if ( t > 1 )
215 {
216 minDistX = x2;
217 minDistY = y2;
218 }
219 else if ( t > 0 )
220 {
221 minDistX += dx * t;
222 minDistY += dy * t;
223 }
224 }
225
226 dx = ptX - minDistX;
227 dy = ptY - minDistY;
228
229 const double dist = dx * dx + dy * dy;
230
231 //prevent rounding errors if the point is directly on the segment
232 if ( qgsDoubleNear( dist, 0.0, epsilon ) )
233 {
234 minDistX = ptX;
235 minDistY = ptY;
236 return 0.0;
237 }
238
239 return dist;
240 }
241
lineIntersection(const QgsPoint & p1,QgsVector v1,const QgsPoint & p2,QgsVector v2,QgsPoint & intersection)242 bool QgsGeometryUtils::lineIntersection( const QgsPoint &p1, QgsVector v1, const QgsPoint &p2, QgsVector v2, QgsPoint &intersection )
243 {
244 const double d = v1.y() * v2.x() - v1.x() * v2.y();
245
246 if ( qgsDoubleNear( d, 0 ) )
247 return false;
248
249 const double dx = p2.x() - p1.x();
250 const double dy = p2.y() - p1.y();
251 const double k = ( dy * v2.x() - dx * v2.y() ) / d;
252
253 intersection = QgsPoint( p1.x() + v1.x() * k, p1.y() + v1.y() * k );
254
255 // z and m support for intersection point
256 QgsGeometryUtils::transferFirstZOrMValueToPoint( QgsPointSequence() << p1 << p2, intersection );
257
258 return true;
259 }
260
segmentIntersection(const QgsPoint & p1,const QgsPoint & p2,const QgsPoint & q1,const QgsPoint & q2,QgsPoint & intersectionPoint,bool & isIntersection,const double tolerance,bool acceptImproperIntersection)261 bool QgsGeometryUtils::segmentIntersection( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &q1, const QgsPoint &q2, QgsPoint &intersectionPoint, bool &isIntersection, const double tolerance, bool acceptImproperIntersection )
262 {
263 isIntersection = false;
264
265 QgsVector v( p2.x() - p1.x(), p2.y() - p1.y() );
266 QgsVector w( q2.x() - q1.x(), q2.y() - q1.y() );
267 const double vl = v.length();
268 const double wl = w.length();
269
270 if ( qgsDoubleNear( vl, 0.0, tolerance ) || qgsDoubleNear( wl, 0.0, tolerance ) )
271 {
272 return false;
273 }
274 v = v / vl;
275 w = w / wl;
276
277 if ( !QgsGeometryUtils::lineIntersection( p1, v, q1, w, intersectionPoint ) )
278 {
279 return false;
280 }
281
282 isIntersection = true;
283 if ( acceptImproperIntersection )
284 {
285 if ( ( p1 == q1 ) || ( p1 == q2 ) )
286 {
287 intersectionPoint = p1;
288 return true;
289 }
290 else if ( ( p2 == q1 ) || ( p2 == q2 ) )
291 {
292 intersectionPoint = p2;
293 return true;
294 }
295
296 double x, y;
297 if (
298 // intersectionPoint = p1
299 qgsDoubleNear( QgsGeometryUtils::sqrDistToLine( p1.x(), p1.y(), q1.x(), q1.y(), q2.x(), q2.y(), x, y, tolerance ), 0.0, tolerance ) ||
300 // intersectionPoint = p2
301 qgsDoubleNear( QgsGeometryUtils::sqrDistToLine( p2.x(), p2.y(), q1.x(), q1.y(), q2.x(), q2.y(), x, y, tolerance ), 0.0, tolerance ) ||
302 // intersectionPoint = q1
303 qgsDoubleNear( QgsGeometryUtils::sqrDistToLine( q1.x(), q1.y(), p1.x(), p1.y(), p2.x(), p2.y(), x, y, tolerance ), 0.0, tolerance ) ||
304 // intersectionPoint = q2
305 qgsDoubleNear( QgsGeometryUtils::sqrDistToLine( q2.x(), q2.y(), p1.x(), p1.y(), p2.x(), p2.y(), x, y, tolerance ), 0.0, tolerance )
306 )
307 {
308 return true;
309 }
310 }
311
312 const double lambdav = QgsVector( intersectionPoint.x() - p1.x(), intersectionPoint.y() - p1.y() ) * v;
313 if ( lambdav < 0. + tolerance || lambdav > vl - tolerance )
314 return false;
315
316 const double lambdaw = QgsVector( intersectionPoint.x() - q1.x(), intersectionPoint.y() - q1.y() ) * w;
317 return !( lambdaw < 0. + tolerance || lambdaw >= wl - tolerance );
318
319 }
320
lineCircleIntersection(const QgsPointXY & center,const double radius,const QgsPointXY & linePoint1,const QgsPointXY & linePoint2,QgsPointXY & intersection)321 bool QgsGeometryUtils::lineCircleIntersection( const QgsPointXY ¢er, const double radius,
322 const QgsPointXY &linePoint1, const QgsPointXY &linePoint2,
323 QgsPointXY &intersection )
324 {
325 // formula taken from http://mathworld.wolfram.com/Circle-LineIntersection.html
326
327 const double x1 = linePoint1.x() - center.x();
328 const double y1 = linePoint1.y() - center.y();
329 const double x2 = linePoint2.x() - center.x();
330 const double y2 = linePoint2.y() - center.y();
331 const double dx = x2 - x1;
332 const double dy = y2 - y1;
333
334 const double dr2 = std::pow( dx, 2 ) + std::pow( dy, 2 );
335 const double d = x1 * y2 - x2 * y1;
336
337 const double disc = std::pow( radius, 2 ) * dr2 - std::pow( d, 2 );
338
339 if ( disc < 0 )
340 {
341 //no intersection or tangent
342 return false;
343 }
344 else
345 {
346 // two solutions
347 const int sgnDy = dy < 0 ? -1 : 1;
348
349 const double sqrDisc = std::sqrt( disc );
350
351 const double ax = center.x() + ( d * dy + sgnDy * dx * sqrDisc ) / dr2;
352 const double ay = center.y() + ( -d * dx + std::fabs( dy ) * sqrDisc ) / dr2;
353 const QgsPointXY p1( ax, ay );
354
355 const double bx = center.x() + ( d * dy - sgnDy * dx * sqrDisc ) / dr2;
356 const double by = center.y() + ( -d * dx - std::fabs( dy ) * sqrDisc ) / dr2;
357 const QgsPointXY p2( bx, by );
358
359 // snap to nearest intersection
360
361 if ( intersection.sqrDist( p1 ) < intersection.sqrDist( p2 ) )
362 {
363 intersection.set( p1.x(), p1.y() );
364 }
365 else
366 {
367 intersection.set( p2.x(), p2.y() );
368 }
369 return true;
370 }
371 }
372
373 // based on public domain work by 3/26/2005 Tim Voght
374 // see http://paulbourke.net/geometry/circlesphere/tvoght.c
circleCircleIntersections(const QgsPointXY & center1,const double r1,const QgsPointXY & center2,const double r2,QgsPointXY & intersection1,QgsPointXY & intersection2)375 int QgsGeometryUtils::circleCircleIntersections( const QgsPointXY ¢er1, const double r1, const QgsPointXY ¢er2, const double r2, QgsPointXY &intersection1, QgsPointXY &intersection2 )
376 {
377 // determine the straight-line distance between the centers
378 const double d = center1.distance( center2 );
379
380 // check for solvability
381 if ( d > ( r1 + r2 ) )
382 {
383 // no solution. circles do not intersect.
384 return 0;
385 }
386 else if ( d < std::fabs( r1 - r2 ) )
387 {
388 // no solution. one circle is contained in the other
389 return 0;
390 }
391 else if ( qgsDoubleNear( d, 0 ) && ( qgsDoubleNear( r1, r2 ) ) )
392 {
393 // no solutions, the circles coincide
394 return 0;
395 }
396
397 /* 'point 2' is the point where the line through the circle
398 * intersection points crosses the line between the circle
399 * centers.
400 */
401
402 // Determine the distance from point 0 to point 2.
403 const double a = ( ( r1 * r1 ) - ( r2 * r2 ) + ( d * d ) ) / ( 2.0 * d ) ;
404
405 /* dx and dy are the vertical and horizontal distances between
406 * the circle centers.
407 */
408 const double dx = center2.x() - center1.x();
409 const double dy = center2.y() - center1.y();
410
411 // Determine the coordinates of point 2.
412 const double x2 = center1.x() + ( dx * a / d );
413 const double y2 = center1.y() + ( dy * a / d );
414
415 /* Determine the distance from point 2 to either of the
416 * intersection points.
417 */
418 const double h = std::sqrt( ( r1 * r1 ) - ( a * a ) );
419
420 /* Now determine the offsets of the intersection points from
421 * point 2.
422 */
423 const double rx = dy * ( h / d );
424 const double ry = dx * ( h / d );
425
426 // determine the absolute intersection points
427 intersection1 = QgsPointXY( x2 + rx, y2 - ry );
428 intersection2 = QgsPointXY( x2 - rx, y2 + ry );
429
430 // see if we have 1 or 2 solutions
431 if ( qgsDoubleNear( d, r1 + r2 ) )
432 return 1;
433
434 return 2;
435 }
436
437 // Using https://stackoverflow.com/a/1351794/1861260
438 // and inspired by http://csharphelper.com/blog/2014/11/find-the-tangent-lines-between-a-point-and-a-circle-in-c/
tangentPointAndCircle(const QgsPointXY & center,double radius,const QgsPointXY & p,QgsPointXY & pt1,QgsPointXY & pt2)439 bool QgsGeometryUtils::tangentPointAndCircle( const QgsPointXY ¢er, double radius, const QgsPointXY &p, QgsPointXY &pt1, QgsPointXY &pt2 )
440 {
441 // distance from point to center of circle
442 const double dx = center.x() - p.x();
443 const double dy = center.y() - p.y();
444 const double distanceSquared = dx * dx + dy * dy;
445 const double radiusSquared = radius * radius;
446 if ( distanceSquared < radiusSquared )
447 {
448 // point is inside circle!
449 return false;
450 }
451
452 // distance from point to tangent point, using pythagoras
453 const double distanceToTangent = std::sqrt( distanceSquared - radiusSquared );
454
455 // tangent points are those where the original circle intersects a circle centered
456 // on p with radius distanceToTangent
457 circleCircleIntersections( center, radius, p, distanceToTangent, pt1, pt2 );
458
459 return true;
460 }
461
462 // inspired by http://csharphelper.com/blog/2014/12/find-the-tangent-lines-between-two-circles-in-c/
circleCircleOuterTangents(const QgsPointXY & center1,double radius1,const QgsPointXY & center2,double radius2,QgsPointXY & line1P1,QgsPointXY & line1P2,QgsPointXY & line2P1,QgsPointXY & line2P2)463 int QgsGeometryUtils::circleCircleOuterTangents( const QgsPointXY ¢er1, double radius1, const QgsPointXY ¢er2, double radius2, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2 )
464 {
465 if ( radius1 > radius2 )
466 return circleCircleOuterTangents( center2, radius2, center1, radius1, line1P1, line1P2, line2P1, line2P2 );
467
468 const double radius2a = radius2 - radius1;
469 if ( !tangentPointAndCircle( center2, radius2a, center1, line1P2, line2P2 ) )
470 {
471 // there are no tangents
472 return 0;
473 }
474
475 // get the vector perpendicular to the
476 // first tangent with length radius1
477 QgsVector v1( -( line1P2.y() - center1.y() ), line1P2.x() - center1.x() );
478 const double v1Length = v1.length();
479 v1 = v1 * ( radius1 / v1Length );
480
481 // offset the tangent vector's points
482 line1P1 = center1 + v1;
483 line1P2 = line1P2 + v1;
484
485 // get the vector perpendicular to the
486 // second tangent with length radius1
487 QgsVector v2( line2P2.y() - center1.y(), -( line2P2.x() - center1.x() ) );
488 const double v2Length = v2.length();
489 v2 = v2 * ( radius1 / v2Length );
490
491 // offset the tangent vector's points
492 line2P1 = center1 + v2;
493 line2P2 = line2P2 + v2;
494
495 return 2;
496 }
497
498 // inspired by http://csharphelper.com/blog/2014/12/find-the-tangent-lines-between-two-circles-in-c/
circleCircleInnerTangents(const QgsPointXY & center1,double radius1,const QgsPointXY & center2,double radius2,QgsPointXY & line1P1,QgsPointXY & line1P2,QgsPointXY & line2P1,QgsPointXY & line2P2)499 int QgsGeometryUtils::circleCircleInnerTangents( const QgsPointXY ¢er1, double radius1, const QgsPointXY ¢er2, double radius2, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2 )
500 {
501 if ( radius1 > radius2 )
502 return circleCircleInnerTangents( center2, radius2, center1, radius1, line1P1, line1P2, line2P1, line2P2 );
503
504 // determine the straight-line distance between the centers
505 const double d = center1.distance( center2 );
506 const double radius1a = radius1 + radius2;
507
508 // check for solvability
509 if ( d <= radius1a || qgsDoubleNear( d, radius1a ) )
510 {
511 // no solution. circles intersect or touch.
512 return 0;
513 }
514
515 if ( !tangentPointAndCircle( center1, radius1a, center2, line1P2, line2P2 ) )
516 {
517 // there are no tangents
518 return 0;
519 }
520
521 // get the vector perpendicular to the
522 // first tangent with length radius2
523 QgsVector v1( ( line1P2.y() - center2.y() ), -( line1P2.x() - center2.x() ) );
524 const double v1Length = v1.length();
525 v1 = v1 * ( radius2 / v1Length );
526
527 // offset the tangent vector's points
528 line1P1 = center2 + v1;
529 line1P2 = line1P2 + v1;
530
531 // get the vector perpendicular to the
532 // second tangent with length radius2
533 QgsVector v2( -( line2P2.y() - center2.y() ), line2P2.x() - center2.x() );
534 const double v2Length = v2.length();
535 v2 = v2 * ( radius2 / v2Length );
536
537 // offset the tangent vector's points in opposite direction
538 line2P1 = center2 + v2;
539 line2P2 = line2P2 + v2;
540
541 return 2;
542 }
543
selfIntersections(const QgsAbstractGeometry * geom,int part,int ring,double tolerance)544 QVector<QgsGeometryUtils::SelfIntersection> QgsGeometryUtils::selfIntersections( const QgsAbstractGeometry *geom, int part, int ring, double tolerance )
545 {
546 QVector<SelfIntersection> intersections;
547
548 const int n = geom->vertexCount( part, ring );
549 const bool isClosed = geom->vertexAt( QgsVertexId( part, ring, 0 ) ) == geom->vertexAt( QgsVertexId( part, ring, n - 1 ) );
550
551 // Check every pair of segments for intersections
552 for ( int i = 0, j = 1; j < n; i = j++ )
553 {
554 const QgsPoint pi = geom->vertexAt( QgsVertexId( part, ring, i ) );
555 const QgsPoint pj = geom->vertexAt( QgsVertexId( part, ring, j ) );
556 if ( QgsGeometryUtils::sqrDistance2D( pi, pj ) < tolerance * tolerance ) continue;
557
558 // Don't test neighboring edges
559 const int start = j + 1;
560 const int end = i == 0 && isClosed ? n - 1 : n;
561 for ( int k = start, l = start + 1; l < end; k = l++ )
562 {
563 const QgsPoint pk = geom->vertexAt( QgsVertexId( part, ring, k ) );
564 const QgsPoint pl = geom->vertexAt( QgsVertexId( part, ring, l ) );
565
566 QgsPoint inter;
567 bool intersection = false;
568 if ( !QgsGeometryUtils::segmentIntersection( pi, pj, pk, pl, inter, intersection, tolerance ) ) continue;
569
570 SelfIntersection s;
571 s.segment1 = i;
572 s.segment2 = k;
573 if ( s.segment1 > s.segment2 )
574 {
575 std::swap( s.segment1, s.segment2 );
576 }
577 s.point = inter;
578 intersections.append( s );
579 }
580 }
581 return intersections;
582 }
583
leftOfLine(const QgsPoint & point,const QgsPoint & p1,const QgsPoint & p2)584 int QgsGeometryUtils::leftOfLine( const QgsPoint &point, const QgsPoint &p1, const QgsPoint &p2 )
585 {
586 return leftOfLine( point.x(), point.y(), p1.x(), p1.y(), p2.x(), p2.y() );
587 }
588
leftOfLine(const double x,const double y,const double x1,const double y1,const double x2,const double y2)589 int QgsGeometryUtils::leftOfLine( const double x, const double y, const double x1, const double y1, const double x2, const double y2 )
590 {
591 const double f1 = x - x1;
592 const double f2 = y2 - y1;
593 const double f3 = y - y1;
594 const double f4 = x2 - x1;
595 const double test = ( f1 * f2 - f3 * f4 );
596 // return -1, 0, or 1
597 return qgsDoubleNear( test, 0.0 ) ? 0 : ( test < 0 ? -1 : 1 );
598 }
599
pointOnLineWithDistance(const QgsPoint & startPoint,const QgsPoint & directionPoint,double distance)600 QgsPoint QgsGeometryUtils::pointOnLineWithDistance( const QgsPoint &startPoint, const QgsPoint &directionPoint, double distance )
601 {
602 double x, y;
603 pointOnLineWithDistance( startPoint.x(), startPoint.y(), directionPoint.x(), directionPoint.y(), distance, x, y );
604 return QgsPoint( x, y );
605 }
606
pointOnLineWithDistance(double x1,double y1,double x2,double y2,double distance,double & x,double & y,double * z1,double * z2,double * z,double * m1,double * m2,double * m)607 void QgsGeometryUtils::pointOnLineWithDistance( double x1, double y1, double x2, double y2, double distance, double &x, double &y, double *z1, double *z2, double *z, double *m1, double *m2, double *m )
608 {
609 const double dx = x2 - x1;
610 const double dy = y2 - y1;
611 const double length = std::sqrt( dx * dx + dy * dy );
612
613 if ( qgsDoubleNear( length, 0.0 ) )
614 {
615 x = x1;
616 y = y1;
617 if ( z && z1 )
618 *z = *z1;
619 if ( m && m1 )
620 *m = *m1;
621 }
622 else
623 {
624 const double scaleFactor = distance / length;
625 x = x1 + dx * scaleFactor;
626 y = y1 + dy * scaleFactor;
627 if ( z && z1 && z2 )
628 *z = *z1 + ( *z2 - *z1 ) * scaleFactor;
629 if ( m && m1 && m2 )
630 *m = *m1 + ( *m2 - *m1 ) * scaleFactor;
631 }
632 }
633
perpendicularOffsetPointAlongSegment(double x1,double y1,double x2,double y2,double proportion,double offset,double * x,double * y)634 void QgsGeometryUtils::perpendicularOffsetPointAlongSegment( double x1, double y1, double x2, double y2, double proportion, double offset, double *x, double *y )
635 {
636 // calculate point along segment
637 const double mX = x1 + ( x2 - x1 ) * proportion;
638 const double mY = y1 + ( y2 - y1 ) * proportion;
639 const double pX = x1 - x2;
640 const double pY = y1 - y2;
641 double normalX = -pY;
642 double normalY = pX; //#spellok
643 const double normalLength = sqrt( ( normalX * normalX ) + ( normalY * normalY ) ); //#spellok
644 normalX /= normalLength;
645 normalY /= normalLength; //#spellok
646
647 *x = mX + offset * normalX;
648 *y = mY + offset * normalY; //#spellok
649 }
650
interpolatePointOnArc(const QgsPoint & pt1,const QgsPoint & pt2,const QgsPoint & pt3,double distance)651 QgsPoint QgsGeometryUtils::interpolatePointOnArc( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double distance )
652 {
653 double centerX, centerY, radius;
654 circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
655
656 const double theta = distance / radius; // angle subtended
657 const double anglePt1 = std::atan2( pt1.y() - centerY, pt1.x() - centerX );
658 const double anglePt2 = std::atan2( pt2.y() - centerY, pt2.x() - centerX );
659 const double anglePt3 = std::atan2( pt3.y() - centerY, pt3.x() - centerX );
660 const bool isClockwise = circleClockwise( anglePt1, anglePt2, anglePt3 );
661 const double angleDest = anglePt1 + ( isClockwise ? -theta : theta );
662
663 const double x = centerX + radius * ( std::cos( angleDest ) );
664 const double y = centerY + radius * ( std::sin( angleDest ) );
665
666 const double z = pt1.is3D() ?
667 interpolateArcValue( angleDest, anglePt1, anglePt2, anglePt3, pt1.z(), pt2.z(), pt3.z() )
668 : 0;
669 const double m = pt1.isMeasure() ?
670 interpolateArcValue( angleDest, anglePt1, anglePt2, anglePt3, pt1.m(), pt2.m(), pt3.m() )
671 : 0;
672
673 return QgsPoint( pt1.wkbType(), x, y, z, m );
674 }
675
ccwAngle(double dy,double dx)676 double QgsGeometryUtils::ccwAngle( double dy, double dx )
677 {
678 const double angle = std::atan2( dy, dx ) * 180 / M_PI;
679 if ( angle < 0 )
680 {
681 return 360 + angle;
682 }
683 else if ( angle > 360 )
684 {
685 return 360 - angle;
686 }
687 return angle;
688 }
689
circleCenterRadius(const QgsPoint & pt1,const QgsPoint & pt2,const QgsPoint & pt3,double & radius,double & centerX,double & centerY)690 void QgsGeometryUtils::circleCenterRadius( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double &radius, double ¢erX, double ¢erY )
691 {
692 double dx21, dy21, dx31, dy31, h21, h31, d;
693
694 //closed circle
695 if ( qgsDoubleNear( pt1.x(), pt3.x() ) && qgsDoubleNear( pt1.y(), pt3.y() ) )
696 {
697 centerX = ( pt1.x() + pt2.x() ) / 2.0;
698 centerY = ( pt1.y() + pt2.y() ) / 2.0;
699 radius = std::sqrt( std::pow( centerX - pt1.x(), 2.0 ) + std::pow( centerY - pt1.y(), 2.0 ) );
700 return;
701 }
702
703 // Using Cartesian circumcenter eguations from page https://en.wikipedia.org/wiki/Circumscribed_circle
704 dx21 = pt2.x() - pt1.x();
705 dy21 = pt2.y() - pt1.y();
706 dx31 = pt3.x() - pt1.x();
707 dy31 = pt3.y() - pt1.y();
708
709 h21 = std::pow( dx21, 2.0 ) + std::pow( dy21, 2.0 );
710 h31 = std::pow( dx31, 2.0 ) + std::pow( dy31, 2.0 );
711
712 // 2*Cross product, d<0 means clockwise and d>0 counterclockwise sweeping angle
713 d = 2 * ( dx21 * dy31 - dx31 * dy21 );
714
715 // Check colinearity, Cross product = 0
716 if ( qgsDoubleNear( std::fabs( d ), 0.0, 0.00000000001 ) )
717 {
718 radius = -1.0;
719 return;
720 }
721
722 // Calculate centroid coordinates and radius
723 centerX = pt1.x() + ( h21 * dy31 - h31 * dy21 ) / d;
724 centerY = pt1.y() - ( h21 * dx31 - h31 * dx21 ) / d;
725 radius = std::sqrt( std::pow( centerX - pt1.x(), 2.0 ) + std::pow( centerY - pt1.y(), 2.0 ) );
726 }
727
circleClockwise(double angle1,double angle2,double angle3)728 bool QgsGeometryUtils::circleClockwise( double angle1, double angle2, double angle3 )
729 {
730 if ( angle3 >= angle1 )
731 {
732 return !( angle2 > angle1 && angle2 < angle3 );
733 }
734 else
735 {
736 return !( angle2 > angle1 || angle2 < angle3 );
737 }
738 }
739
circleAngleBetween(double angle,double angle1,double angle2,bool clockwise)740 bool QgsGeometryUtils::circleAngleBetween( double angle, double angle1, double angle2, bool clockwise )
741 {
742 if ( clockwise )
743 {
744 if ( angle2 < angle1 )
745 {
746 return ( angle <= angle1 && angle >= angle2 );
747 }
748 else
749 {
750 return ( angle <= angle1 || angle >= angle2 );
751 }
752 }
753 else
754 {
755 if ( angle2 > angle1 )
756 {
757 return ( angle >= angle1 && angle <= angle2 );
758 }
759 else
760 {
761 return ( angle >= angle1 || angle <= angle2 );
762 }
763 }
764 }
765
angleOnCircle(double angle,double angle1,double angle2,double angle3)766 bool QgsGeometryUtils::angleOnCircle( double angle, double angle1, double angle2, double angle3 )
767 {
768 const bool clockwise = circleClockwise( angle1, angle2, angle3 );
769 return circleAngleBetween( angle, angle1, angle3, clockwise );
770 }
771
circleLength(double x1,double y1,double x2,double y2,double x3,double y3)772 double QgsGeometryUtils::circleLength( double x1, double y1, double x2, double y2, double x3, double y3 )
773 {
774 double centerX, centerY, radius;
775 circleCenterRadius( QgsPoint( x1, y1 ), QgsPoint( x2, y2 ), QgsPoint( x3, y3 ), radius, centerX, centerY );
776 double length = M_PI / 180.0 * radius * sweepAngle( centerX, centerY, x1, y1, x2, y2, x3, y3 );
777 if ( length < 0 )
778 {
779 length = -length;
780 }
781 return length;
782 }
783
sweepAngle(double centerX,double centerY,double x1,double y1,double x2,double y2,double x3,double y3)784 double QgsGeometryUtils::sweepAngle( double centerX, double centerY, double x1, double y1, double x2, double y2, double x3, double y3 )
785 {
786 const double p1Angle = QgsGeometryUtils::ccwAngle( y1 - centerY, x1 - centerX );
787 const double p2Angle = QgsGeometryUtils::ccwAngle( y2 - centerY, x2 - centerX );
788 const double p3Angle = QgsGeometryUtils::ccwAngle( y3 - centerY, x3 - centerX );
789
790 if ( p3Angle >= p1Angle )
791 {
792 if ( p2Angle > p1Angle && p2Angle < p3Angle )
793 {
794 return ( p3Angle - p1Angle );
795 }
796 else
797 {
798 return ( - ( p1Angle + ( 360 - p3Angle ) ) );
799 }
800 }
801 else
802 {
803 if ( p2Angle < p1Angle && p2Angle > p3Angle )
804 {
805 return ( -( p1Angle - p3Angle ) );
806 }
807 else
808 {
809 return ( p3Angle + ( 360 - p1Angle ) );
810 }
811 }
812 }
813
segmentMidPoint(const QgsPoint & p1,const QgsPoint & p2,QgsPoint & result,double radius,const QgsPoint & mousePos)814 bool QgsGeometryUtils::segmentMidPoint( const QgsPoint &p1, const QgsPoint &p2, QgsPoint &result, double radius, const QgsPoint &mousePos )
815 {
816 const QgsPoint midPoint( ( p1.x() + p2.x() ) / 2.0, ( p1.y() + p2.y() ) / 2.0 );
817 const double midDist = std::sqrt( sqrDistance2D( p1, midPoint ) );
818 if ( radius < midDist )
819 {
820 return false;
821 }
822 const double centerMidDist = std::sqrt( radius * radius - midDist * midDist );
823 const double dist = radius - centerMidDist;
824
825 const double midDx = midPoint.x() - p1.x();
826 const double midDy = midPoint.y() - p1.y();
827
828 //get the four possible midpoints
829 QVector<QgsPoint> possibleMidPoints;
830 possibleMidPoints.append( pointOnLineWithDistance( midPoint, QgsPoint( midPoint.x() - midDy, midPoint.y() + midDx ), dist ) );
831 possibleMidPoints.append( pointOnLineWithDistance( midPoint, QgsPoint( midPoint.x() - midDy, midPoint.y() + midDx ), 2 * radius - dist ) );
832 possibleMidPoints.append( pointOnLineWithDistance( midPoint, QgsPoint( midPoint.x() + midDy, midPoint.y() - midDx ), dist ) );
833 possibleMidPoints.append( pointOnLineWithDistance( midPoint, QgsPoint( midPoint.x() + midDy, midPoint.y() - midDx ), 2 * radius - dist ) );
834
835 //take the closest one
836 double minDist = std::numeric_limits<double>::max();
837 int minDistIndex = -1;
838 for ( int i = 0; i < possibleMidPoints.size(); ++i )
839 {
840 const double currentDist = sqrDistance2D( mousePos, possibleMidPoints.at( i ) );
841 if ( currentDist < minDist )
842 {
843 minDistIndex = i;
844 minDist = currentDist;
845 }
846 }
847
848 if ( minDistIndex == -1 )
849 {
850 return false;
851 }
852
853 result = possibleMidPoints.at( minDistIndex );
854
855 // add z and m support if necessary
856 QgsGeometryUtils::transferFirstZOrMValueToPoint( QgsPointSequence() << p1 << p2, result );
857
858 return true;
859 }
860
segmentMidPointFromCenter(const QgsPoint & p1,const QgsPoint & p2,const QgsPoint & center,const bool useShortestArc)861 QgsPoint QgsGeometryUtils::segmentMidPointFromCenter( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint ¢er, const bool useShortestArc )
862 {
863 double midPointAngle = averageAngle( lineAngle( center.x(), center.y(), p1.x(), p1.y() ),
864 lineAngle( center.x(), center.y(), p2.x(), p2.y() ) );
865 if ( !useShortestArc )
866 midPointAngle += M_PI;
867 return center.project( center.distance( p1 ), midPointAngle * 180 / M_PI );
868 }
869
circleTangentDirection(const QgsPoint & tangentPoint,const QgsPoint & cp1,const QgsPoint & cp2,const QgsPoint & cp3)870 double QgsGeometryUtils::circleTangentDirection( const QgsPoint &tangentPoint, const QgsPoint &cp1,
871 const QgsPoint &cp2, const QgsPoint &cp3 )
872 {
873 //calculate circle midpoint
874 double mX, mY, radius;
875 circleCenterRadius( cp1, cp2, cp3, radius, mX, mY );
876
877 const double p1Angle = QgsGeometryUtils::ccwAngle( cp1.y() - mY, cp1.x() - mX );
878 const double p2Angle = QgsGeometryUtils::ccwAngle( cp2.y() - mY, cp2.x() - mX );
879 const double p3Angle = QgsGeometryUtils::ccwAngle( cp3.y() - mY, cp3.x() - mX );
880 double angle = 0;
881 if ( circleClockwise( p1Angle, p2Angle, p3Angle ) )
882 {
883 angle = lineAngle( tangentPoint.x(), tangentPoint.y(), mX, mY ) - M_PI_2;
884 }
885 else
886 {
887 angle = lineAngle( mX, mY, tangentPoint.x(), tangentPoint.y() ) - M_PI_2;
888 }
889 if ( angle < 0 )
890 angle += 2 * M_PI;
891 return angle;
892 }
893
894 // Ported from PostGIS' pt_continues_arc
pointContinuesArc(const QgsPoint & a1,const QgsPoint & a2,const QgsPoint & a3,const QgsPoint & b,double distanceTolerance,double pointSpacingAngleTolerance)895 bool QgsGeometryUtils::pointContinuesArc( const QgsPoint &a1, const QgsPoint &a2, const QgsPoint &a3, const QgsPoint &b, double distanceTolerance, double pointSpacingAngleTolerance )
896 {
897 double centerX = 0;
898 double centerY = 0;
899 double radius = 0;
900 circleCenterRadius( a1, a2, a3, radius, centerX, centerY );
901
902 // Co-linear a1/a2/a3
903 if ( radius < 0.0 )
904 return false;
905
906 // distance of candidate point to center of arc a1-a2-a3
907 const double bDistance = std::sqrt( ( b.x() - centerX ) * ( b.x() - centerX ) +
908 ( b.y() - centerY ) * ( b.y() - centerY ) );
909
910 double diff = std::fabs( radius - bDistance );
911
912 auto arcAngle = []( const QgsPoint & a, const QgsPoint & b, const QgsPoint & c )->double
913 {
914 const double abX = b.x() - a.x();
915 const double abY = b.y() - a.y();
916
917 const double cbX = b.x() - c.x();
918 const double cbY = b.y() - c.y();
919
920 const double dot = ( abX * cbX + abY * cbY ); /* dot product */
921 const double cross = ( abX * cbY - abY * cbX ); /* cross product */
922
923 const double alpha = std::atan2( cross, dot );
924
925 return alpha;
926 };
927
928 // Is the point b on the circle?
929 if ( diff < distanceTolerance )
930 {
931 const double angle1 = arcAngle( a1, a2, a3 );
932 const double angle2 = arcAngle( a2, a3, b );
933
934 // Is the sweep angle similar to the previous one?
935 // We only consider a segment replaceable by an arc if the points within
936 // it are regularly spaced
937 diff = std::fabs( angle1 - angle2 );
938 if ( diff > pointSpacingAngleTolerance )
939 {
940 return false;
941 }
942
943 const int a2Side = leftOfLine( a2.x(), a2.y(), a1.x(), a1.y(), a3.x(), a3.y() );
944 const int bSide = leftOfLine( b.x(), b.y(), a1.x(), a1.y(), a3.x(), a3.y() );
945
946 // Is the point b on the same side of a1/a3 as the mid-point a2 is?
947 // If not, it's in the unbounded part of the circle, so it continues the arc, return true.
948 if ( bSide != a2Side )
949 return true;
950 }
951 return false;
952 }
953
segmentizeArc(const QgsPoint & p1,const QgsPoint & p2,const QgsPoint & p3,QgsPointSequence & points,double tolerance,QgsAbstractGeometry::SegmentationToleranceType toleranceType,bool hasZ,bool hasM)954 void QgsGeometryUtils::segmentizeArc( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, QgsPointSequence &points, double tolerance, QgsAbstractGeometry::SegmentationToleranceType toleranceType, bool hasZ, bool hasM )
955 {
956 bool reversed = false;
957 const int segSide = segmentSide( p1, p3, p2 );
958
959 QgsPoint circlePoint1;
960 const QgsPoint &circlePoint2 = p2;
961 QgsPoint circlePoint3;
962
963 if ( segSide == -1 )
964 {
965 // Reverse !
966 circlePoint1 = p3;
967 circlePoint3 = p1;
968 reversed = true;
969 }
970 else
971 {
972 circlePoint1 = p1;
973 circlePoint3 = p3;
974 }
975
976 //adapted code from PostGIS
977 double radius = 0;
978 double centerX = 0;
979 double centerY = 0;
980 circleCenterRadius( circlePoint1, circlePoint2, circlePoint3, radius, centerX, centerY );
981
982 if ( circlePoint1 != circlePoint3 && ( radius < 0 || qgsDoubleNear( segSide, 0.0 ) ) ) //points are colinear
983 {
984 points.append( p1 );
985 points.append( p2 );
986 points.append( p3 );
987 return;
988 }
989
990 double increment = tolerance; //one segment per degree
991 if ( toleranceType == QgsAbstractGeometry::MaximumDifference )
992 {
993 // Ensure tolerance is not higher than twice the radius
994 tolerance = std::min( tolerance, radius * 2 );
995 const double halfAngle = std::acos( -tolerance / radius + 1 );
996 increment = 2 * halfAngle;
997 }
998
999 //angles of pt1, pt2, pt3
1000 const double a1 = std::atan2( circlePoint1.y() - centerY, circlePoint1.x() - centerX );
1001 double a2 = std::atan2( circlePoint2.y() - centerY, circlePoint2.x() - centerX );
1002 double a3 = std::atan2( circlePoint3.y() - centerY, circlePoint3.x() - centerX );
1003
1004 // Make segmentation symmetric
1005 const bool symmetric = true;
1006 if ( symmetric )
1007 {
1008 double angle = a3 - a1;
1009 // angle == 0 when full circle
1010 if ( angle <= 0 ) angle += M_PI * 2;
1011
1012 /* Number of segments in output */
1013 const int segs = ceil( angle / increment );
1014 /* Tweak increment to be regular for all the arc */
1015 increment = angle / segs;
1016 }
1017
1018 /* Adjust a3 up so we can increment from a1 to a3 cleanly */
1019 // a3 == a1 when full circle
1020 if ( a3 <= a1 )
1021 a3 += 2.0 * M_PI;
1022 if ( a2 < a1 )
1023 a2 += 2.0 * M_PI;
1024
1025 double x, y;
1026 double z = 0;
1027 double m = 0;
1028
1029 QVector<QgsPoint> stringPoints;
1030 stringPoints.insert( 0, circlePoint1 );
1031 if ( circlePoint2 != circlePoint3 && circlePoint1 != circlePoint2 ) //draw straight line segment if two points have the same position
1032 {
1033 QgsWkbTypes::Type pointWkbType = QgsWkbTypes::Point;
1034 if ( hasZ )
1035 pointWkbType = QgsWkbTypes::addZ( pointWkbType );
1036 if ( hasM )
1037 pointWkbType = QgsWkbTypes::addM( pointWkbType );
1038
1039 // As we're adding the last point in any case, we'll avoid
1040 // including a point which is at less than 1% increment distance
1041 // from it (may happen to find them due to numbers approximation).
1042 // NOTE that this effectively allows in output some segments which
1043 // are more distant than requested. This is at most 1% off
1044 // from requested MaxAngle and less for MaxError.
1045 const double tolError = increment / 100;
1046 const double stopAngle = a3 - tolError;
1047 for ( double angle = a1 + increment; angle < stopAngle; angle += increment )
1048 {
1049 x = centerX + radius * std::cos( angle );
1050 y = centerY + radius * std::sin( angle );
1051
1052 if ( hasZ )
1053 {
1054 z = interpolateArcValue( angle, a1, a2, a3, circlePoint1.z(), circlePoint2.z(), circlePoint3.z() );
1055 }
1056 if ( hasM )
1057 {
1058 m = interpolateArcValue( angle, a1, a2, a3, circlePoint1.m(), circlePoint2.m(), circlePoint3.m() );
1059 }
1060
1061 stringPoints.insert( stringPoints.size(), QgsPoint( pointWkbType, x, y, z, m ) );
1062 }
1063 }
1064 stringPoints.insert( stringPoints.size(), circlePoint3 );
1065
1066 // TODO: check if or implement QgsPointSequence directly taking an iterator to append
1067 if ( reversed )
1068 {
1069 std::reverse( stringPoints.begin(), stringPoints.end() );
1070 }
1071 if ( ! points.empty() && stringPoints.front() == points.back() ) stringPoints.pop_front();
1072 points.append( stringPoints );
1073 }
1074
segmentSide(const QgsPoint & pt1,const QgsPoint & pt3,const QgsPoint & pt2)1075 int QgsGeometryUtils::segmentSide( const QgsPoint &pt1, const QgsPoint &pt3, const QgsPoint &pt2 )
1076 {
1077 const double side = ( ( pt2.x() - pt1.x() ) * ( pt3.y() - pt1.y() ) - ( pt3.x() - pt1.x() ) * ( pt2.y() - pt1.y() ) );
1078 if ( side == 0.0 )
1079 {
1080 return 0;
1081 }
1082 else
1083 {
1084 if ( side < 0 )
1085 {
1086 return -1;
1087 }
1088 if ( side > 0 )
1089 {
1090 return 1;
1091 }
1092 return 0;
1093 }
1094 }
1095
interpolateArcValue(double angle,double a1,double a2,double a3,double zm1,double zm2,double zm3)1096 double QgsGeometryUtils::interpolateArcValue( double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3 )
1097 {
1098 /* Counter-clockwise sweep */
1099 if ( a1 < a2 )
1100 {
1101 if ( angle <= a2 )
1102 return zm1 + ( zm2 - zm1 ) * ( angle - a1 ) / ( a2 - a1 );
1103 else
1104 return zm2 + ( zm3 - zm2 ) * ( angle - a2 ) / ( a3 - a2 );
1105 }
1106 /* Clockwise sweep */
1107 else
1108 {
1109 if ( angle >= a2 )
1110 return zm1 + ( zm2 - zm1 ) * ( a1 - angle ) / ( a1 - a2 );
1111 else
1112 return zm2 + ( zm3 - zm2 ) * ( a2 - angle ) / ( a2 - a3 );
1113 }
1114 }
1115
pointsFromWKT(const QString & wktCoordinateList,bool is3D,bool isMeasure)1116 QgsPointSequence QgsGeometryUtils::pointsFromWKT( const QString &wktCoordinateList, bool is3D, bool isMeasure )
1117 {
1118 const int dim = 2 + is3D + isMeasure;
1119 QgsPointSequence points;
1120
1121 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1122 const QStringList coordList = wktCoordinateList.split( ',', QString::SkipEmptyParts );
1123 #else
1124 const QStringList coordList = wktCoordinateList.split( ',', Qt::SkipEmptyParts );
1125 #endif
1126
1127 //first scan through for extra unexpected dimensions
1128 bool foundZ = false;
1129 bool foundM = false;
1130 const thread_local QRegularExpression rx( QStringLiteral( "\\s" ) );
1131 const thread_local QRegularExpression rxIsNumber( QStringLiteral( "^[+-]?(\\d\\.?\\d*[Ee][+\\-]?\\d+|(\\d+\\.\\d*|\\d*\\.\\d+)|\\d+)$" ) );
1132 for ( const QString &pointCoordinates : coordList )
1133 {
1134 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1135 QStringList coordinates = pointCoordinates.split( rx, QString::SkipEmptyParts );
1136 #else
1137 const QStringList coordinates = pointCoordinates.split( rx, Qt::SkipEmptyParts );
1138 #endif
1139
1140 // exit with an empty set if one list contains invalid value.
1141 if ( coordinates.filter( rxIsNumber ).size() != coordinates.size() )
1142 return points;
1143
1144 if ( coordinates.size() == 3 && !foundZ && !foundM && !is3D && !isMeasure )
1145 {
1146 // 3 dimensional coordinates, but not specifically marked as such. We allow this
1147 // anyway and upgrade geometry to have Z dimension
1148 foundZ = true;
1149 }
1150 else if ( coordinates.size() >= 4 && ( !( is3D || foundZ ) || !( isMeasure || foundM ) ) )
1151 {
1152 // 4 (or more) dimensional coordinates, but not specifically marked as such. We allow this
1153 // anyway and upgrade geometry to have Z&M dimensions
1154 foundZ = true;
1155 foundM = true;
1156 }
1157 }
1158
1159 for ( const QString &pointCoordinates : coordList )
1160 {
1161 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1162 QStringList coordinates = pointCoordinates.split( rx, QString::SkipEmptyParts );
1163 #else
1164 QStringList coordinates = pointCoordinates.split( rx, Qt::SkipEmptyParts );
1165 #endif
1166 if ( coordinates.size() < dim )
1167 continue;
1168
1169 int idx = 0;
1170 const double x = coordinates[idx++].toDouble();
1171 const double y = coordinates[idx++].toDouble();
1172
1173 double z = 0;
1174 if ( ( is3D || foundZ ) && coordinates.length() > idx )
1175 z = coordinates[idx++].toDouble();
1176
1177 double m = 0;
1178 if ( ( isMeasure || foundM ) && coordinates.length() > idx )
1179 m = coordinates[idx++].toDouble();
1180
1181 QgsWkbTypes::Type t = QgsWkbTypes::Point;
1182 if ( is3D || foundZ )
1183 {
1184 if ( isMeasure || foundM )
1185 t = QgsWkbTypes::PointZM;
1186 else
1187 t = QgsWkbTypes::PointZ;
1188 }
1189 else
1190 {
1191 if ( isMeasure || foundM )
1192 t = QgsWkbTypes::PointM;
1193 else
1194 t = QgsWkbTypes::Point;
1195 }
1196
1197 points.append( QgsPoint( t, x, y, z, m ) );
1198 }
1199
1200 return points;
1201 }
1202
pointsToWKB(QgsWkbPtr & wkb,const QgsPointSequence & points,bool is3D,bool isMeasure)1203 void QgsGeometryUtils::pointsToWKB( QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure )
1204 {
1205 wkb << static_cast<quint32>( points.size() );
1206 for ( const QgsPoint &point : points )
1207 {
1208 wkb << point.x() << point.y();
1209 if ( is3D )
1210 {
1211 wkb << point.z();
1212 }
1213 if ( isMeasure )
1214 {
1215 wkb << point.m();
1216 }
1217 }
1218 }
1219
pointsToWKT(const QgsPointSequence & points,int precision,bool is3D,bool isMeasure)1220 QString QgsGeometryUtils::pointsToWKT( const QgsPointSequence &points, int precision, bool is3D, bool isMeasure )
1221 {
1222 QString wkt = QStringLiteral( "(" );
1223 for ( const QgsPoint &p : points )
1224 {
1225 wkt += qgsDoubleToString( p.x(), precision );
1226 wkt += ' ' + qgsDoubleToString( p.y(), precision );
1227 if ( is3D )
1228 wkt += ' ' + qgsDoubleToString( p.z(), precision );
1229 if ( isMeasure )
1230 wkt += ' ' + qgsDoubleToString( p.m(), precision );
1231 wkt += QLatin1String( ", " );
1232 }
1233 if ( wkt.endsWith( QLatin1String( ", " ) ) )
1234 wkt.chop( 2 ); // Remove last ", "
1235 wkt += ')';
1236 return wkt;
1237 }
1238
pointsToGML2(const QgsPointSequence & points,QDomDocument & doc,int precision,const QString & ns,QgsAbstractGeometry::AxisOrder axisOrder)1239 QDomElement QgsGeometryUtils::pointsToGML2( const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, QgsAbstractGeometry::AxisOrder axisOrder )
1240 {
1241 QDomElement elemCoordinates = doc.createElementNS( ns, QStringLiteral( "coordinates" ) );
1242
1243 // coordinate separator
1244 const QString cs = QStringLiteral( "," );
1245 // tuple separator
1246 const QString ts = QStringLiteral( " " );
1247
1248 elemCoordinates.setAttribute( QStringLiteral( "cs" ), cs );
1249 elemCoordinates.setAttribute( QStringLiteral( "ts" ), ts );
1250
1251 QString strCoordinates;
1252
1253 for ( const QgsPoint &p : points )
1254 if ( axisOrder == QgsAbstractGeometry::AxisOrder::XY )
1255 strCoordinates += qgsDoubleToString( p.x(), precision ) + cs + qgsDoubleToString( p.y(), precision ) + ts;
1256 else
1257 strCoordinates += qgsDoubleToString( p.y(), precision ) + cs + qgsDoubleToString( p.x(), precision ) + ts;
1258
1259 if ( strCoordinates.endsWith( ts ) )
1260 strCoordinates.chop( 1 ); // Remove trailing space
1261
1262 elemCoordinates.appendChild( doc.createTextNode( strCoordinates ) );
1263 return elemCoordinates;
1264 }
1265
pointsToGML3(const QgsPointSequence & points,QDomDocument & doc,int precision,const QString & ns,bool is3D,QgsAbstractGeometry::AxisOrder axisOrder)1266 QDomElement QgsGeometryUtils::pointsToGML3( const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, bool is3D, QgsAbstractGeometry::AxisOrder axisOrder )
1267 {
1268 QDomElement elemPosList = doc.createElementNS( ns, QStringLiteral( "posList" ) );
1269 elemPosList.setAttribute( QStringLiteral( "srsDimension" ), is3D ? 3 : 2 );
1270
1271 QString strCoordinates;
1272 for ( const QgsPoint &p : points )
1273 {
1274 if ( axisOrder == QgsAbstractGeometry::AxisOrder::XY )
1275 strCoordinates += qgsDoubleToString( p.x(), precision ) + ' ' + qgsDoubleToString( p.y(), precision ) + ' ';
1276 else
1277 strCoordinates += qgsDoubleToString( p.y(), precision ) + ' ' + qgsDoubleToString( p.x(), precision ) + ' ';
1278 if ( is3D )
1279 strCoordinates += qgsDoubleToString( p.z(), precision ) + ' ';
1280 }
1281 if ( strCoordinates.endsWith( ' ' ) )
1282 strCoordinates.chop( 1 ); // Remove trailing space
1283
1284 elemPosList.appendChild( doc.createTextNode( strCoordinates ) );
1285 return elemPosList;
1286 }
1287
pointsToJSON(const QgsPointSequence & points,int precision)1288 QString QgsGeometryUtils::pointsToJSON( const QgsPointSequence &points, int precision )
1289 {
1290 QString json = QStringLiteral( "[ " );
1291 for ( const QgsPoint &p : points )
1292 {
1293 json += '[' + qgsDoubleToString( p.x(), precision ) + QLatin1String( ", " ) + qgsDoubleToString( p.y(), precision ) + QLatin1String( "], " );
1294 }
1295 if ( json.endsWith( QLatin1String( ", " ) ) )
1296 {
1297 json.chop( 2 ); // Remove last ", "
1298 }
1299 json += ']';
1300 return json;
1301 }
1302
1303
pointsToJson(const QgsPointSequence & points,int precision)1304 json QgsGeometryUtils::pointsToJson( const QgsPointSequence &points, int precision )
1305 {
1306 json coordinates( json::array() );
1307 for ( const QgsPoint &p : points )
1308 {
1309 if ( p.is3D() )
1310 {
1311 coordinates.push_back( { qgsRound( p.x(), precision ), qgsRound( p.y(), precision ), qgsRound( p.z(), precision ) } );
1312 }
1313 else
1314 {
1315 coordinates.push_back( { qgsRound( p.x(), precision ), qgsRound( p.y(), precision ) } );
1316 }
1317 }
1318 return coordinates;
1319 }
1320
normalizedAngle(double angle)1321 double QgsGeometryUtils::normalizedAngle( double angle )
1322 {
1323 double clippedAngle = angle;
1324 if ( clippedAngle >= M_PI * 2 || clippedAngle <= -2 * M_PI )
1325 {
1326 clippedAngle = std::fmod( clippedAngle, 2 * M_PI );
1327 }
1328 if ( clippedAngle < 0.0 )
1329 {
1330 clippedAngle += 2 * M_PI;
1331 }
1332 return clippedAngle;
1333 }
1334
wktReadBlock(const QString & wkt)1335 QPair<QgsWkbTypes::Type, QString> QgsGeometryUtils::wktReadBlock( const QString &wkt )
1336 {
1337 QString wktParsed = wkt;
1338 QString contents;
1339 if ( wkt.contains( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) )
1340 {
1341 const thread_local QRegularExpression sWktRegEx( QStringLiteral( "^\\s*(\\w+)\\s+(\\w+)\\s*$" ), QRegularExpression::DotMatchesEverythingOption );
1342 const QRegularExpressionMatch match = sWktRegEx.match( wkt );
1343 if ( match.hasMatch() )
1344 {
1345 wktParsed = match.captured( 1 );
1346 contents = match.captured( 2 ).toUpper();
1347 }
1348 }
1349 else
1350 {
1351 const int openedParenthesisCount = wktParsed.count( '(' );
1352 const int closedParenthesisCount = wktParsed.count( ')' );
1353 // closes missing parentheses
1354 for ( int i = 0 ; i < openedParenthesisCount - closedParenthesisCount; ++i )
1355 wktParsed.push_back( ')' );
1356 // removes extra parentheses
1357 wktParsed.truncate( wktParsed.size() - ( closedParenthesisCount - openedParenthesisCount ) );
1358
1359 const thread_local QRegularExpression cooRegEx( QStringLiteral( "^[^\\(]*\\((.*)\\)[^\\)]*$" ), QRegularExpression::DotMatchesEverythingOption );
1360 const QRegularExpressionMatch match = cooRegEx.match( wktParsed );
1361 contents = match.hasMatch() ? match.captured( 1 ) : QString();
1362 }
1363 const QgsWkbTypes::Type wkbType = QgsWkbTypes::parseType( wktParsed );
1364 return qMakePair( wkbType, contents );
1365 }
1366
wktGetChildBlocks(const QString & wkt,const QString & defaultType)1367 QStringList QgsGeometryUtils::wktGetChildBlocks( const QString &wkt, const QString &defaultType )
1368 {
1369 int level = 0;
1370 QString block;
1371 block.reserve( wkt.size() );
1372 QStringList blocks;
1373
1374 const QChar *wktData = wkt.data();
1375 const int wktLength = wkt.length();
1376 for ( int i = 0, n = wktLength; i < n; ++i, ++wktData )
1377 {
1378 if ( ( wktData->isSpace() || *wktData == '\n' || *wktData == '\t' ) && level == 0 )
1379 continue;
1380
1381 if ( *wktData == ',' && level == 0 )
1382 {
1383 if ( !block.isEmpty() )
1384 {
1385 if ( block.startsWith( '(' ) && !defaultType.isEmpty() )
1386 block.prepend( defaultType + ' ' );
1387 blocks.append( block );
1388 }
1389 block.resize( 0 );
1390 continue;
1391 }
1392 if ( *wktData == '(' )
1393 ++level;
1394 else if ( *wktData == ')' )
1395 --level;
1396 block += *wktData;
1397 }
1398 if ( !block.isEmpty() )
1399 {
1400 if ( block.startsWith( '(' ) && !defaultType.isEmpty() )
1401 block.prepend( defaultType + ' ' );
1402 blocks.append( block );
1403 }
1404 return blocks;
1405 }
1406
closestSideOfRectangle(double right,double bottom,double left,double top,double x,double y)1407 int QgsGeometryUtils::closestSideOfRectangle( double right, double bottom, double left, double top, double x, double y )
1408 {
1409 // point outside rectangle
1410 if ( x <= left && y <= bottom )
1411 {
1412 const double dx = left - x;
1413 const double dy = bottom - y;
1414 if ( qgsDoubleNear( dx, dy ) )
1415 return 6;
1416 else if ( dx < dy )
1417 return 5;
1418 else
1419 return 7;
1420 }
1421 else if ( x >= right && y >= top )
1422 {
1423 const double dx = x - right;
1424 const double dy = y - top;
1425 if ( qgsDoubleNear( dx, dy ) )
1426 return 2;
1427 else if ( dx < dy )
1428 return 1;
1429 else
1430 return 3;
1431 }
1432 else if ( x >= right && y <= bottom )
1433 {
1434 const double dx = x - right;
1435 const double dy = bottom - y;
1436 if ( qgsDoubleNear( dx, dy ) )
1437 return 4;
1438 else if ( dx < dy )
1439 return 5;
1440 else
1441 return 3;
1442 }
1443 else if ( x <= left && y >= top )
1444 {
1445 const double dx = left - x;
1446 const double dy = y - top;
1447 if ( qgsDoubleNear( dx, dy ) )
1448 return 8;
1449 else if ( dx < dy )
1450 return 1;
1451 else
1452 return 7;
1453 }
1454 else if ( x <= left )
1455 return 7;
1456 else if ( x >= right )
1457 return 3;
1458 else if ( y <= bottom )
1459 return 5;
1460 else if ( y >= top )
1461 return 1;
1462
1463 // point is inside rectangle
1464 const double smallestX = std::min( right - x, x - left );
1465 const double smallestY = std::min( top - y, y - bottom );
1466 if ( smallestX < smallestY )
1467 {
1468 // closer to left/right side
1469 if ( right - x < x - left )
1470 return 3; // closest to right side
1471 else
1472 return 7;
1473 }
1474 else
1475 {
1476 // closer to top/bottom side
1477 if ( top - y < y - bottom )
1478 return 1; // closest to top side
1479 else
1480 return 5;
1481 }
1482 }
1483
midpoint(const QgsPoint & pt1,const QgsPoint & pt2)1484 QgsPoint QgsGeometryUtils::midpoint( const QgsPoint &pt1, const QgsPoint &pt2 )
1485 {
1486 QgsWkbTypes::Type pType( QgsWkbTypes::Point );
1487
1488
1489 const double x = ( pt1.x() + pt2.x() ) / 2.0;
1490 const double y = ( pt1.y() + pt2.y() ) / 2.0;
1491 double z = std::numeric_limits<double>::quiet_NaN();
1492 double m = std::numeric_limits<double>::quiet_NaN();
1493
1494 if ( pt1.is3D() || pt2.is3D() )
1495 {
1496 pType = QgsWkbTypes::addZ( pType );
1497 z = ( pt1.z() + pt2.z() ) / 2.0;
1498 }
1499
1500 if ( pt1.isMeasure() || pt2.isMeasure() )
1501 {
1502 pType = QgsWkbTypes::addM( pType );
1503 m = ( pt1.m() + pt2.m() ) / 2.0;
1504 }
1505
1506 return QgsPoint( pType, x, y, z, m );
1507 }
1508
interpolatePointOnLine(const QgsPoint & p1,const QgsPoint & p2,const double fraction)1509 QgsPoint QgsGeometryUtils::interpolatePointOnLine( const QgsPoint &p1, const QgsPoint &p2, const double fraction )
1510 {
1511 const double _fraction = 1 - fraction;
1512 return QgsPoint( p1.wkbType(),
1513 p1.x() * _fraction + p2.x() * fraction,
1514 p1.y() * _fraction + p2.y() * fraction,
1515 p1.is3D() ? p1.z() * _fraction + p2.z() * fraction : std::numeric_limits<double>::quiet_NaN(),
1516 p1.isMeasure() ? p1.m() * _fraction + p2.m() * fraction : std::numeric_limits<double>::quiet_NaN() );
1517 }
1518
interpolatePointOnLine(const double x1,const double y1,const double x2,const double y2,const double fraction)1519 QgsPointXY QgsGeometryUtils::interpolatePointOnLine( const double x1, const double y1, const double x2, const double y2, const double fraction )
1520 {
1521 const double deltaX = ( x2 - x1 ) * fraction;
1522 const double deltaY = ( y2 - y1 ) * fraction;
1523 return QgsPointXY( x1 + deltaX, y1 + deltaY );
1524 }
1525
interpolatePointOnLineByValue(const double x1,const double y1,const double v1,const double x2,const double y2,const double v2,const double value)1526 QgsPointXY QgsGeometryUtils::interpolatePointOnLineByValue( const double x1, const double y1, const double v1, const double x2, const double y2, const double v2, const double value )
1527 {
1528 if ( qgsDoubleNear( v1, v2 ) )
1529 return QgsPointXY( x1, y1 );
1530
1531 const double fraction = ( value - v1 ) / ( v2 - v1 );
1532 return interpolatePointOnLine( x1, y1, x2, y2, fraction );
1533 }
1534
gradient(const QgsPoint & pt1,const QgsPoint & pt2)1535 double QgsGeometryUtils::gradient( const QgsPoint &pt1, const QgsPoint &pt2 )
1536 {
1537 const double delta_x = pt2.x() - pt1.x();
1538 const double delta_y = pt2.y() - pt1.y();
1539 if ( qgsDoubleNear( delta_x, 0.0 ) )
1540 {
1541 return INFINITY;
1542 }
1543
1544 return delta_y / delta_x;
1545 }
1546
coefficients(const QgsPoint & pt1,const QgsPoint & pt2,double & a,double & b,double & c)1547 void QgsGeometryUtils::coefficients( const QgsPoint &pt1, const QgsPoint &pt2, double &a, double &b, double &c )
1548 {
1549 if ( qgsDoubleNear( pt1.x(), pt2.x() ) )
1550 {
1551 a = 1;
1552 b = 0;
1553 c = -pt1.x();
1554 }
1555 else if ( qgsDoubleNear( pt1.y(), pt2.y() ) )
1556 {
1557 a = 0;
1558 b = 1;
1559 c = -pt1.y();
1560 }
1561 else
1562 {
1563 a = pt1.y() - pt2.y();
1564 b = pt2.x() - pt1.x();
1565 c = pt1.x() * pt2.y() - pt1.y() * pt2.x();
1566 }
1567
1568 }
1569
perpendicularSegment(const QgsPoint & p,const QgsPoint & s1,const QgsPoint & s2)1570 QgsLineString QgsGeometryUtils::perpendicularSegment( const QgsPoint &p, const QgsPoint &s1, const QgsPoint &s2 )
1571 {
1572 QgsLineString line;
1573 QgsPoint p2;
1574
1575 if ( ( p == s1 ) || ( p == s2 ) )
1576 {
1577 return line;
1578 }
1579
1580 double a, b, c;
1581 coefficients( s1, s2, a, b, c );
1582
1583 if ( qgsDoubleNear( a, 0 ) )
1584 {
1585 p2 = QgsPoint( p.x(), s1.y() );
1586 }
1587 else if ( qgsDoubleNear( b, 0 ) )
1588 {
1589 p2 = QgsPoint( s1.x(), p.y() );
1590 }
1591 else
1592 {
1593 const double y = ( -c - a * p.x() ) / b;
1594 const double m = gradient( s1, s2 );
1595 const double d2 = 1 + m * m;
1596 const double H = p.y() - y;
1597 const double dx = m * H / d2;
1598 const double dy = m * dx;
1599 p2 = QgsPoint( p.x() + dx, y + dy );
1600 }
1601
1602 line.addVertex( p );
1603 line.addVertex( p2 );
1604
1605 return line;
1606 }
1607
lineAngle(double x1,double y1,double x2,double y2)1608 double QgsGeometryUtils::lineAngle( double x1, double y1, double x2, double y2 )
1609 {
1610 const double at = std::atan2( y2 - y1, x2 - x1 );
1611 const double a = -at + M_PI_2;
1612 return normalizedAngle( a );
1613 }
1614
angleBetweenThreePoints(double x1,double y1,double x2,double y2,double x3,double y3)1615 double QgsGeometryUtils::angleBetweenThreePoints( double x1, double y1, double x2, double y2, double x3, double y3 )
1616 {
1617 const double angle1 = std::atan2( y1 - y2, x1 - x2 );
1618 const double angle2 = std::atan2( y3 - y2, x3 - x2 );
1619 return normalizedAngle( angle1 - angle2 );
1620 }
1621
linePerpendicularAngle(double x1,double y1,double x2,double y2)1622 double QgsGeometryUtils::linePerpendicularAngle( double x1, double y1, double x2, double y2 )
1623 {
1624 double a = lineAngle( x1, y1, x2, y2 );
1625 a += M_PI_2;
1626 return normalizedAngle( a );
1627 }
1628
averageAngle(double x1,double y1,double x2,double y2,double x3,double y3)1629 double QgsGeometryUtils::averageAngle( double x1, double y1, double x2, double y2, double x3, double y3 )
1630 {
1631 // calc average angle between the previous and next point
1632 const double a1 = lineAngle( x1, y1, x2, y2 );
1633 const double a2 = lineAngle( x2, y2, x3, y3 );
1634 return averageAngle( a1, a2 );
1635 }
1636
averageAngle(double a1,double a2)1637 double QgsGeometryUtils::averageAngle( double a1, double a2 )
1638 {
1639 a1 = normalizedAngle( a1 );
1640 a2 = normalizedAngle( a2 );
1641 double clockwiseDiff = 0.0;
1642 if ( a2 >= a1 )
1643 {
1644 clockwiseDiff = a2 - a1;
1645 }
1646 else
1647 {
1648 clockwiseDiff = a2 + ( 2 * M_PI - a1 );
1649 }
1650 const double counterClockwiseDiff = 2 * M_PI - clockwiseDiff;
1651
1652 double resultAngle = 0;
1653 if ( clockwiseDiff <= counterClockwiseDiff )
1654 {
1655 resultAngle = a1 + clockwiseDiff / 2.0;
1656 }
1657 else
1658 {
1659 resultAngle = a1 - counterClockwiseDiff / 2.0;
1660 }
1661 return normalizedAngle( resultAngle );
1662 }
1663
skewLinesDistance(const QgsVector3D & P1,const QgsVector3D & P12,const QgsVector3D & P2,const QgsVector3D & P22)1664 double QgsGeometryUtils::skewLinesDistance( const QgsVector3D &P1, const QgsVector3D &P12,
1665 const QgsVector3D &P2, const QgsVector3D &P22 )
1666 {
1667 const QgsVector3D u1 = P12 - P1;
1668 const QgsVector3D u2 = P22 - P2;
1669 QgsVector3D u3 = QgsVector3D::crossProduct( u1, u2 );
1670 if ( u3.length() == 0 ) return 1;
1671 u3.normalize();
1672 const QgsVector3D dir = P1 - P2;
1673 return std::fabs( ( QgsVector3D::dotProduct( dir, u3 ) ) ); // u3 is already normalized
1674 }
1675
skewLinesProjection(const QgsVector3D & P1,const QgsVector3D & P12,const QgsVector3D & P2,const QgsVector3D & P22,QgsVector3D & X1,double epsilon)1676 bool QgsGeometryUtils::skewLinesProjection( const QgsVector3D &P1, const QgsVector3D &P12,
1677 const QgsVector3D &P2, const QgsVector3D &P22,
1678 QgsVector3D &X1, double epsilon )
1679 {
1680 const QgsVector3D d = P2 - P1;
1681 QgsVector3D u1 = P12 - P1;
1682 u1.normalize();
1683 QgsVector3D u2 = P22 - P2;
1684 u2.normalize();
1685 const QgsVector3D u3 = QgsVector3D::crossProduct( u1, u2 );
1686
1687 if ( std::fabs( u3.x() ) <= epsilon &&
1688 std::fabs( u3.y() ) <= epsilon &&
1689 std::fabs( u3.z() ) <= epsilon )
1690 {
1691 // The rays are almost parallel.
1692 return false;
1693 }
1694
1695 // X1 and X2 are the closest points on lines
1696 // we want to find X1 (lies on u1)
1697 // solving the linear equation in r1 and r2: Xi = Pi + ri*ui
1698 // we are only interested in X1 so we only solve for r1.
1699 float a1 = QgsVector3D::dotProduct( u1, u1 ), b1 = QgsVector3D::dotProduct( u1, u2 ), c1 = QgsVector3D::dotProduct( u1, d );
1700 float a2 = QgsVector3D::dotProduct( u1, u2 ), b2 = QgsVector3D::dotProduct( u2, u2 ), c2 = QgsVector3D::dotProduct( u2, d );
1701 if ( !( std::fabs( b1 ) > epsilon ) )
1702 {
1703 // Denominator is close to zero.
1704 return false;
1705 }
1706 if ( !( a2 != -1 && a2 != 1 ) )
1707 {
1708 // Lines are parallel
1709 return false;
1710 }
1711
1712 const double r1 = ( c2 - b2 * c1 / b1 ) / ( a2 - b2 * a1 / b1 );
1713 X1 = P1 + u1 * r1;
1714
1715 return true;
1716 }
1717
linesIntersection3D(const QgsVector3D & La1,const QgsVector3D & La2,const QgsVector3D & Lb1,const QgsVector3D & Lb2,QgsVector3D & intersection)1718 bool QgsGeometryUtils::linesIntersection3D( const QgsVector3D &La1, const QgsVector3D &La2,
1719 const QgsVector3D &Lb1, const QgsVector3D &Lb2,
1720 QgsVector3D &intersection )
1721 {
1722
1723 // if all Vector are on the same plane (have the same Z), use the 2D intersection
1724 // else return a false result
1725 if ( qgsDoubleNear( La1.z(), La2.z() ) && qgsDoubleNear( La1.z(), Lb1.z() ) && qgsDoubleNear( La1.z(), Lb2.z() ) )
1726 {
1727 QgsPoint ptInter;
1728 bool isIntersection;
1729 segmentIntersection( QgsPoint( La1.x(), La1.y() ),
1730 QgsPoint( La2.x(), La2.y() ),
1731 QgsPoint( Lb1.x(), Lb1.y() ),
1732 QgsPoint( Lb2.x(), Lb2.y() ),
1733 ptInter,
1734 isIntersection,
1735 1e-8,
1736 true );
1737 intersection.set( ptInter.x(), ptInter.y(), La1.z() );
1738 return true;
1739 }
1740
1741 // first check if lines have an exact intersection point
1742 // do it by checking if the shortest distance is exactly 0
1743 const float distance = skewLinesDistance( La1, La2, Lb1, Lb2 );
1744 if ( qgsDoubleNear( distance, 0.0 ) )
1745 {
1746 // 3d lines have exact intersection point.
1747 const QgsVector3D C = La2;
1748 const QgsVector3D D = Lb2;
1749 const QgsVector3D e = La1 - La2;
1750 const QgsVector3D f = Lb1 - Lb2;
1751 const QgsVector3D g = D - C;
1752 if ( qgsDoubleNear( ( QgsVector3D::crossProduct( f, g ) ).length(), 0.0 ) || qgsDoubleNear( ( QgsVector3D::crossProduct( f, e ) ).length(), 0.0 ) )
1753 {
1754 // Lines have no intersection, are they parallel?
1755 return false;
1756 }
1757
1758 QgsVector3D fgn = QgsVector3D::crossProduct( f, g );
1759 fgn.normalize();
1760
1761 QgsVector3D fen = QgsVector3D::crossProduct( f, e );
1762 fen.normalize();
1763
1764 int di = -1;
1765 if ( fgn == fen ) // same direction?
1766 di *= -1;
1767
1768 intersection = C + e * di * ( QgsVector3D::crossProduct( f, g ).length() / QgsVector3D::crossProduct( f, e ).length() );
1769 return true;
1770 }
1771
1772 // try to calculate the approximate intersection point
1773 QgsVector3D X1, X2;
1774 const bool firstIsDone = skewLinesProjection( La1, La2, Lb1, Lb2, X1 );
1775 const bool secondIsDone = skewLinesProjection( Lb1, Lb2, La1, La2, X2 );
1776
1777 if ( !firstIsDone || !secondIsDone )
1778 {
1779 // Could not obtain projection point.
1780 return false;
1781 }
1782
1783 intersection = ( X1 + X2 ) / 2.0;
1784 return true;
1785 }
1786
triangleArea(double aX,double aY,double bX,double bY,double cX,double cY)1787 double QgsGeometryUtils::triangleArea( double aX, double aY, double bX, double bY, double cX, double cY )
1788 {
1789 return 0.5 * std::abs( ( aX - cX ) * ( bY - aY ) - ( aX - bX ) * ( cY - aY ) );
1790 }
1791
weightedPointInTriangle(const double aX,const double aY,const double bX,const double bY,const double cX,const double cY,double weightB,double weightC,double & pointX,double & pointY)1792 void QgsGeometryUtils::weightedPointInTriangle( const double aX, const double aY, const double bX, const double bY, const double cX, const double cY,
1793 double weightB, double weightC, double &pointX, double &pointY )
1794 {
1795 // if point will be outside of the triangle, invert weights
1796 if ( weightB + weightC > 1 )
1797 {
1798 weightB = 1 - weightB;
1799 weightC = 1 - weightC;
1800 }
1801
1802 const double rBx = weightB * ( bX - aX );
1803 const double rBy = weightB * ( bY - aY );
1804 const double rCx = weightC * ( cX - aX );
1805 const double rCy = weightC * ( cY - aY );
1806
1807 pointX = rBx + rCx + aX;
1808 pointY = rBy + rCy + aY;
1809 }
1810
transferFirstMValueToPoint(const QgsPointSequence & points,QgsPoint & point)1811 bool QgsGeometryUtils::transferFirstMValueToPoint( const QgsPointSequence &points, QgsPoint &point )
1812 {
1813 bool rc = false;
1814
1815 for ( const QgsPoint &pt : points )
1816 {
1817 if ( pt.isMeasure() )
1818 {
1819 point.convertTo( QgsWkbTypes::addM( point.wkbType() ) );
1820 point.setM( pt.m() );
1821 rc = true;
1822 break;
1823 }
1824 }
1825
1826 return rc;
1827 }
1828
transferFirstZValueToPoint(const QgsPointSequence & points,QgsPoint & point)1829 bool QgsGeometryUtils::transferFirstZValueToPoint( const QgsPointSequence &points, QgsPoint &point )
1830 {
1831 bool rc = false;
1832
1833 for ( const QgsPoint &pt : points )
1834 {
1835 if ( pt.is3D() )
1836 {
1837 point.convertTo( QgsWkbTypes::addZ( point.wkbType() ) );
1838 point.setZ( pt.z() );
1839 rc = true;
1840 break;
1841 }
1842 }
1843
1844 return rc;
1845 }
1846
angleBisector(double aX,double aY,double bX,double bY,double cX,double cY,double dX,double dY,double & pointX SIP_OUT,double & pointY SIP_OUT,double & angle SIP_OUT)1847 bool QgsGeometryUtils::angleBisector( double aX, double aY, double bX, double bY, double cX, double cY, double dX, double dY,
1848 double &pointX SIP_OUT, double &pointY SIP_OUT, double &angle SIP_OUT )
1849 {
1850 const QgsPoint pA = QgsPoint( aX, aY );
1851 const QgsPoint pB = QgsPoint( bX, bY );
1852 const QgsPoint pC = QgsPoint( cX, cY );
1853 const QgsPoint pD = QgsPoint( dX, dY );
1854 angle = ( pA.azimuth( pB ) + pC.azimuth( pD ) ) / 2.0;
1855
1856 QgsPoint pOut;
1857 bool intersection = false;
1858 QgsGeometryUtils::segmentIntersection( pA, pB, pC, pD, pOut, intersection );
1859
1860 pointX = pOut.x();
1861 pointY = pOut.y();
1862
1863 return intersection;
1864 }
1865
bisector(double aX,double aY,double bX,double bY,double cX,double cY,double & pointX SIP_OUT,double & pointY SIP_OUT)1866 bool QgsGeometryUtils::bisector( double aX, double aY, double bX, double bY, double cX, double cY,
1867 double &pointX SIP_OUT, double &pointY SIP_OUT )
1868 {
1869 const QgsPoint pA = QgsPoint( aX, aY );
1870 const QgsPoint pB = QgsPoint( bX, bY );
1871 const QgsPoint pC = QgsPoint( cX, cY );
1872 const double angle = ( pA.azimuth( pB ) + pA.azimuth( pC ) ) / 2.0;
1873
1874 QgsPoint pOut;
1875 bool intersection = false;
1876 QgsGeometryUtils::segmentIntersection( pB, pC, pA, pA.project( 1.0, angle ), pOut, intersection );
1877
1878 pointX = pOut.x();
1879 pointY = pOut.y();
1880
1881 return intersection;
1882 }
1883