Skip to content

Commit 4bfc808

Browse files
committed
Add some circle intersection and tangent utilities to QgsGeometryUtils
1 parent 8e08bda commit 4bfc808

File tree

4 files changed

+287
-0
lines changed

4 files changed

+287
-0
lines changed

python/core/geometry/qgsgeometryutils.sip.in

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,53 @@ the closest point to the initial ``intersection`` point is returned.
153153
@param linePoint2 a second point on the line
154154
@param intersection the initial point and the returned intersection point
155155
@return true if an intersection has been found
156+
%End
157+
158+
static int circleCircleIntersections( QgsPointXY center1, double radius1,
159+
QgsPointXY center2, double radius2,
160+
QgsPointXY &intersection1, QgsPointXY &intersection2 );
161+
%Docstring
162+
Calculates the intersections points between the circle with center ``center1`` and
163+
radius ``radius1`` and the circle with center ``center2`` and radius ``radius2``.
164+
165+
If found, the intersection points will be stored in ``intersection1`` and ``intersection2``.
166+
167+
:return: number of intersection points found.
168+
169+
.. versionadded:: 3.2
170+
%End
171+
172+
static bool tangentPointAndCircle( const QgsPointXY &center, double radius,
173+
const QgsPointXY &p, QgsPointXY &pt1, QgsPointXY &pt2 );
174+
%Docstring
175+
Calculates the tangent points between the circle with the specified ``center`` and ``radius``
176+
and the point ``p``.
177+
178+
If found, the tangent points will be stored in ``pt1`` and ``pt2``.
179+
180+
.. versionadded:: 3.2
181+
%End
182+
183+
static int circleCircleOuterTangents(
184+
const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2,
185+
QgsPointXY &line1P1, QgsPointXY &line1P2,
186+
QgsPointXY &line2P1, QgsPointXY &line2P2 );
187+
%Docstring
188+
Calculates the outer tangent points for two circles, centered at ``center1`` and
189+
``center2`` and with radii of ``radius1`` and ``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 ``line1P1`` and ``line1P2``,
197+
and the second line is described by the points stored in ``line2P1``
198+
and ``line2P2``.
199+
200+
Returns the number of tangents (either 0 or 2).
201+
202+
.. versionadded:: 3.2
156203
%End
157204

158205
static QgsPoint projectPointOnSegment( const QgsPoint &p, const QgsPoint &s1, const QgsPoint &s2 );

src/core/geometry/qgsgeometryutils.cpp

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,131 @@ bool QgsGeometryUtils::lineCircleIntersection( const QgsPointXY &center, const d
363363
}
364364
}
365365

366+
// based on public domain work by 3/26/2005 Tim Voght
367+
// see http://paulbourke.net/geometry/circlesphere/tvoght.c
368+
int QgsGeometryUtils::circleCircleIntersections( QgsPointXY center0, const double r0, QgsPointXY center1, const double r1, QgsPointXY &intersection1, QgsPointXY &intersection2 )
369+
{
370+
// determine the straight-line distance between the centers
371+
const double d = center0.distance( center1 );
372+
373+
// check for solvability
374+
if ( d > ( r0 + r1 ) )
375+
{
376+
// no solution. circles do not intersect.
377+
return 0;
378+
}
379+
else if ( d < std::fabs( r0 - r1 ) )
380+
{
381+
// no solution. one circle is contained in the other
382+
return 0;
383+
}
384+
else if ( qgsDoubleNear( d, 0 ) && ( qgsDoubleNear( r0, r1 ) ) )
385+
{
386+
// no solutions, the circles coincide
387+
return 0;
388+
}
389+
390+
/* 'point 2' is the point where the line through the circle
391+
* intersection points crosses the line between the circle
392+
* centers.
393+
*/
394+
395+
// Determine the distance from point 0 to point 2.
396+
const double a = ( ( r0 * r0 ) - ( r1 * r1 ) + ( d * d ) ) / ( 2.0 * d ) ;
397+
398+
/* dx and dy are the vertical and horizontal distances between
399+
* the circle centers.
400+
*/
401+
const double dx = center1.x() - center0.x();
402+
const double dy = center1.y() - center0.y();
403+
404+
// Determine the coordinates of point 2.
405+
const double x2 = center0.x() + ( dx * a / d );
406+
const double y2 = center0.y() + ( dy * a / d );
407+
408+
/* Determine the distance from point 2 to either of the
409+
* intersection points.
410+
*/
411+
const double h = std::sqrt( ( r0 * r0 ) - ( a * a ) );
412+
413+
/* Now determine the offsets of the intersection points from
414+
* point 2.
415+
*/
416+
const double rx = dy * ( h / d );
417+
const double ry = dx * ( h / d );
418+
419+
// determine the absolute intersection points
420+
intersection1 = QgsPointXY( x2 + rx, y2 - ry );
421+
intersection2 = QgsPointXY( x2 - rx, y2 + ry );
422+
423+
// see if we have 1 or 2 solutions
424+
if ( qgsDoubleNear( d, r0 + r1 ) )
425+
return 1;
426+
427+
return 2;
428+
}
429+
430+
// Using https://stackoverflow.com/a/1351794/1861260
431+
// and inspired by http://csharphelper.com/blog/2014/11/find-the-tangent-lines-between-a-point-and-a-circle-in-c/
432+
bool QgsGeometryUtils::tangentPointAndCircle( const QgsPointXY &center, double radius, const QgsPointXY &p, QgsPointXY &pt1, QgsPointXY &pt2 )
433+
{
434+
// distance from point to center of circle
435+
const double dx = center.x() - p.x();
436+
const double dy = center.y() - p.y();
437+
const double distanceSquared = dx * dx + dy * dy;
438+
const double radiusSquared = radius * radius;
439+
if ( distanceSquared < radiusSquared )
440+
{
441+
// point is inside circle!
442+
return false;
443+
}
444+
445+
// distance from point to tangent point, using pythagoras
446+
const double distanceToTangent = std::sqrt( distanceSquared - radiusSquared );
447+
448+
// tangent points are those where the original circle intersects a circle centered
449+
// on p with radius distanceToTangent
450+
circleCircleIntersections( center, radius, p, distanceToTangent, pt1, pt2 );
451+
452+
return true;
453+
}
454+
455+
// inspired by http://csharphelper.com/blog/2014/12/find-the-tangent-lines-between-two-circles-in-c/
456+
int QgsGeometryUtils::circleCircleOuterTangents( const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2 )
457+
{
458+
if ( radius1 > radius2 )
459+
return circleCircleOuterTangents( center2, radius2, center1, radius1, line1P1, line1P2, line2P1, line2P2 );
460+
461+
const double radius2a = radius2 - radius1;
462+
if ( !tangentPointAndCircle( center2, radius2a, center1, line1P2, line2P2 ) )
463+
{
464+
// there are no tangents
465+
return 0;
466+
}
467+
468+
// get the vector perpendicular to the
469+
// first tangent with length radius1
470+
QgsVector v1( -( line1P2.y() - center1.y() ), line1P2.x() - center1.x() );
471+
const double v1Length = v1.length();
472+
v1 = v1 * ( radius1 / v1Length );
473+
474+
// offset the tangent vector's points
475+
line1P1 = center1 + v1;
476+
line1P2 = line1P2 + v1;
477+
478+
// get the vector perpendicular to the
479+
// second tangent with length radius1
480+
QgsVector v2( line2P2.y() - center1.y(), -( line2P2.x() - center1.x() ) );
481+
const double v2Length = v2.length();
482+
v2 = v2 * ( radius1 / v2Length );
483+
484+
// offset the tangent vector's points
485+
line2P1 = center1 + v2;
486+
line2P2 = line2P2 + v2;
487+
488+
return 2;
489+
}
490+
366491
QVector<QgsGeometryUtils::SelfIntersection> QgsGeometryUtils::selfIntersections( const QgsAbstractGeometry *geom, int part, int ring, double tolerance )
367492
{
368493
QVector<SelfIntersection> intersections;

src/core/geometry/qgsgeometryutils.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,53 @@ class CORE_EXPORT QgsGeometryUtils
154154
const QgsPointXY &linePoint1, const QgsPointXY &linePoint2,
155155
QgsPointXY &intersection SIP_INOUT );
156156

157+
/**
158+
* Calculates the intersections points between the circle with center \a center1 and
159+
* radius \a radius1 and the circle with center \a center2 and radius \a radius2.
160+
*
161+
* If found, the intersection points will be stored in \a intersection1 and \a intersection2.
162+
*
163+
* \returns number of intersection points found.
164+
*
165+
* \since QGIS 3.2
166+
*/
167+
static int circleCircleIntersections( QgsPointXY center1, double radius1,
168+
QgsPointXY center2, double radius2,
169+
QgsPointXY &intersection1, QgsPointXY &intersection2 );
170+
171+
/**
172+
* Calculates the tangent points between the circle with the specified \a center and \a radius
173+
* and the point \a p.
174+
*
175+
* If found, the tangent points will be stored in \a pt1 and \a pt2.
176+
*
177+
* \since QGIS 3.2
178+
*/
179+
static bool tangentPointAndCircle( const QgsPointXY &center, double radius,
180+
const QgsPointXY &p, QgsPointXY &pt1, QgsPointXY &pt2 );
181+
182+
/**
183+
* Calculates the outer tangent points for two circles, centered at \a center1 and
184+
* \a center2 and with radii of \a radius1 and \a radius2 respectively.
185+
*
186+
* The outer tangent points correspond to the points at which the two lines
187+
* which are drawn so that they are tangential to both circles touch
188+
* the circles.
189+
*
190+
* The first tangent line is described by the points
191+
* stored in \a line1P1 and \a line1P2,
192+
* and the second line is described by the points stored in \a line2P1
193+
* and \a line2P2.
194+
*
195+
* Returns the number of tangents (either 0 or 2).
196+
*
197+
* \since QGIS 3.2
198+
*/
199+
static int circleCircleOuterTangents(
200+
const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2,
201+
QgsPointXY &line1P1, QgsPointXY &line1P2,
202+
QgsPointXY &line2P1, QgsPointXY &line2P2 );
203+
157204
/**
158205
* \brief Project the point on a segment
159206
* \param p The point

tests/src/core/testqgsgeometryutils.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ class TestQgsGeometryUtils: public QObject
5959
void testClosestPoint();
6060
void testSegmentIntersection();
6161
void testLineCircleIntersection();
62+
void testCircleCircleIntersection();
63+
void testTangentPointAndCircle();
64+
void testCircleCircleOuterTangents();
6265
void testGml();
6366
};
6467

@@ -828,6 +831,71 @@ void TestQgsGeometryUtils::testLineCircleIntersection()
828831
QVERIFY( !isIntersection );
829832
}
830833

834+
void TestQgsGeometryUtils::testCircleCircleIntersection()
835+
{
836+
QgsPointXY int1;
837+
QgsPointXY int2;
838+
839+
// no intersections
840+
QCOMPARE( QgsGeometryUtils::circleCircleIntersections( QgsPointXY( 0, 0 ), 1, QgsPointXY( 2, 0 ), 0.5, int1, int2 ), 0 );
841+
QCOMPARE( QgsGeometryUtils::circleCircleIntersections( QgsPointXY( 0, 0 ), 1, QgsPointXY( 0.5, 0.1 ), 0.2, int1, int2 ), 0 );
842+
// one intersection
843+
QCOMPARE( QgsGeometryUtils::circleCircleIntersections( QgsPointXY( 0, 0 ), 1, QgsPointXY( 3, 0 ), 2, int1, int2 ), 1 );
844+
QCOMPARE( int1, QgsPointXY( 1, 0 ) );
845+
QCOMPARE( int2, QgsPointXY( 1, 0 ) );
846+
// two intersections
847+
QCOMPARE( QgsGeometryUtils::circleCircleIntersections( QgsPointXY( 5, 3 ), 2, QgsPointXY( 7, -1 ), 4, int1, int2 ), 2 );
848+
QGSCOMPARENEAR( int1.x(), 3.8, 0.001 );
849+
QGSCOMPARENEAR( int1.y(), 1.4, 0.001 );
850+
QGSCOMPARENEAR( int2.x(), 7.0, 0.001 );
851+
QGSCOMPARENEAR( int2.y(), 3.0, 0.001 );
852+
}
853+
854+
void TestQgsGeometryUtils::testTangentPointAndCircle()
855+
{
856+
QgsPointXY t1;
857+
QgsPointXY t2;
858+
QVERIFY( !QgsGeometryUtils::tangentPointAndCircle( QgsPointXY( 1, 2 ), 4, QgsPointXY( 1, 2 ), t1, t2 ) );
859+
QVERIFY( QgsGeometryUtils::tangentPointAndCircle( QgsPointXY( 1, 2 ), 4, QgsPointXY( 8, 4 ), t1, t2 ) );
860+
QGSCOMPARENEAR( t1.x(), 4.03, 0.01 );
861+
QGSCOMPARENEAR( t1.y(), -0.61, 0.01 );
862+
QGSCOMPARENEAR( t2.x(), 2.2, 0.01 );
863+
QGSCOMPARENEAR( t2.y(), 5.82, 0.01 );
864+
}
865+
866+
void TestQgsGeometryUtils::testCircleCircleOuterTangents()
867+
{
868+
QgsPointXY l1p1;
869+
QgsPointXY l1p2;
870+
QgsPointXY l2p1;
871+
QgsPointXY l2p2;
872+
873+
// no tangents
874+
QCOMPARE( QgsGeometryUtils::circleCircleOuterTangents( QgsPointXY( 1, 2 ), 4, QgsPointXY( 2, 3 ), 1, l1p1, l1p2, l2p1, l2p2 ), 0 );
875+
876+
// tangents
877+
QCOMPARE( QgsGeometryUtils::circleCircleOuterTangents( QgsPointXY( 1, 2 ), 1, QgsPointXY( 10, 3 ), 4, l1p1, l1p2, l2p1, l2p2 ), 2 );
878+
QGSCOMPARENEAR( l1p1.x(), 0.566, 0.01 );
879+
QGSCOMPARENEAR( l1p1.y(), 2.901, 0.01 );
880+
QGSCOMPARENEAR( l1p2.x(), 8.266, 0.01 );
881+
QGSCOMPARENEAR( l1p2.y(), 6.604, 0.01 );
882+
QGSCOMPARENEAR( l2p1.x(), 0.7749, 0.01 );
883+
QGSCOMPARENEAR( l2p1.y(), 1.025, 0.01 );
884+
QGSCOMPARENEAR( l2p2.x(), 9.099, 0.01 );
885+
QGSCOMPARENEAR( l2p2.y(), -0.897, 0.01 );
886+
887+
// larger circle first
888+
QCOMPARE( QgsGeometryUtils::circleCircleOuterTangents( QgsPointXY( 10, 3 ), 4, QgsPointXY( 1, 2 ), 1, l1p1, l1p2, l2p1, l2p2 ), 2 );
889+
QGSCOMPARENEAR( l1p1.x(), 0.566, 0.01 );
890+
QGSCOMPARENEAR( l1p1.y(), 2.901, 0.01 );
891+
QGSCOMPARENEAR( l1p2.x(), 8.266, 0.01 );
892+
QGSCOMPARENEAR( l1p2.y(), 6.604, 0.01 );
893+
QGSCOMPARENEAR( l2p1.x(), 0.7749, 0.01 );
894+
QGSCOMPARENEAR( l2p1.y(), 1.025, 0.01 );
895+
QGSCOMPARENEAR( l2p2.x(), 9.099, 0.01 );
896+
QGSCOMPARENEAR( l2p2.y(), -0.897, 0.01 );
897+
}
898+
831899
void TestQgsGeometryUtils::testGml()
832900
{
833901
QgsPoint point = QgsPoint( 1, 2 );

0 commit comments

Comments
 (0)