Skip to content

Commit

Permalink
Add API to calculate innerTangents for 2 circles
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondnijssen authored and nyalldawson committed Jan 13, 2019
1 parent aafd848 commit 0ba9a32
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 3 deletions.
25 changes: 25 additions & 0 deletions python/core/auto_generated/geometry/qgscircle.sip.in
Expand Up @@ -188,6 +188,31 @@ Note that this method is 2D only and does not consider the z-value of the circle
.. seealso:: :py:func:`tangentToPoint`

.. versionadded:: 3.2
%End

int innerTangents( const QgsCircle &other,
QgsPointXY &line1P1 /Out/, QgsPointXY &line1P2 /Out/,
QgsPointXY &line2P1 /Out/, QgsPointXY &line2P2 /Out/ ) const;
%Docstring
Calculates the inner tangent points between this circle
and an ``other`` circle.

The inner tangent points correspond to the points at which the two lines
which are drawn so that they are tangential to both circles but on
different sides, touching the circles and crossing each other.

The first tangent line is described by the points
stored in ``line1P1`` and ``line1P2``,
and the second line is described by the points stored in ``line2P1``
and ``line2P2``.

Returns the number of tangents (either 0 or 2).

Note that this method is 2D only and does not consider the z-value of the circle.

.. seealso:: :py:func:`tangentToPoint`

.. versionadded:: 3.6
%End

virtual double area() const;
Expand Down
23 changes: 23 additions & 0 deletions python/core/auto_generated/geometry/qgsgeometryutils.sip.in
Expand Up @@ -202,6 +202,29 @@ and ``line2P2``.
Returns the number of tangents (either 0 or 2).

.. versionadded:: 3.2
%End

static int circleCircleInnerTangents(
const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2,
QgsPointXY &line1P1 /Out/, QgsPointXY &line1P2 /Out/,
QgsPointXY &line2P1 /Out/, QgsPointXY &line2P2 /Out/ );
%Docstring
Calculates the inner tangent points for two circles, centered at \a
center1 and ``center2`` and with radii of ``radius1`` and ``radius2``
respectively.

The inner tangent points correspond to the points at which the two lines
which are drawn so that they are tangential to both circles and are
crossing each other.

The first tangent line is described by the points
stored in ``line1P1`` and ``line1P2``,
and the second line is described by the points stored in ``line2P1``
and ``line2P2``.

Returns the number of tangents (either 0 or 2).

.. versionadded:: 3.6
%End

static QgsPoint projectPointOnSegment( const QgsPoint &p, const QgsPoint &s1, const QgsPoint &s2 );
Expand Down
6 changes: 6 additions & 0 deletions src/core/geometry/qgscircle.cpp
Expand Up @@ -270,6 +270,12 @@ int QgsCircle::outerTangents( const QgsCircle &other, QgsPointXY &line1P1, QgsPo
QgsPointXY( other.center() ), other.radius(), line1P1, line1P2, line2P1, line2P2 );
}

int QgsCircle::innerTangents( const QgsCircle &other, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2 ) const
{
return QgsGeometryUtils::circleCircleInnerTangents( QgsPointXY( mCenter ), radius(),
QgsPointXY( other.center() ), other.radius(), line1P1, line1P2, line2P1, line2P2 );
}

QgsCircle QgsCircle::fromExtent( const QgsPoint &pt1, const QgsPoint &pt2 )
{
double delta_x = std::fabs( pt1.x() - pt2.x() );
Expand Down
29 changes: 27 additions & 2 deletions src/core/geometry/qgscircle.h
Expand Up @@ -166,7 +166,7 @@ class CORE_EXPORT QgsCircle : public QgsEllipse
* \returns true if tangent was found.
*
*
* \see outerTangents()
* \see outerTangents() and innerTangents()
* \since QGIS 3.2
*/
bool tangentToPoint( const QgsPointXY &p, QgsPointXY &pt1 SIP_OUT, QgsPointXY &pt2 SIP_OUT ) const;
Expand All @@ -189,13 +189,38 @@ class CORE_EXPORT QgsCircle : public QgsEllipse
* Note that this method is 2D only and does not consider the z-value of the circle.
*
*
* \see tangentToPoint()
* \see tangentToPoint() and innerTangents()
* \since QGIS 3.2
*/
int outerTangents( const QgsCircle &other,
QgsPointXY &line1P1 SIP_OUT, QgsPointXY &line1P2 SIP_OUT,
QgsPointXY &line2P1 SIP_OUT, QgsPointXY &line2P2 SIP_OUT ) const;

/**
* Calculates the inner tangent points between this circle
* and an \a other circle.
*
* The inner tangent points correspond to the points at which the two lines
* which are drawn so that they are tangential to both circles but on
* different sides, touching the circles and crossing each other.
*
* The first tangent line is described by the points
* stored in \a line1P1 and \a line1P2,
* and the second line is described by the points stored in \a line2P1
* and \a line2P2.
*
* Returns the number of tangents (either 0 or 2).
*
* Note that this method is 2D only and does not consider the z-value of the circle.
*
*
* \see tangentToPoint() and outerTangents()
* \since QGIS 3.6
*/
int innerTangents( const QgsCircle &other,
QgsPointXY &line1P1 SIP_OUT, QgsPointXY &line1P2 SIP_OUT,
QgsPointXY &line2P1 SIP_OUT, QgsPointXY &line2P2 SIP_OUT ) const;

double area() const override;
double perimeter() const override;

Expand Down
46 changes: 46 additions & 0 deletions src/core/geometry/qgsgeometryutils.cpp
Expand Up @@ -489,6 +489,52 @@ int QgsGeometryUtils::circleCircleOuterTangents( const QgsPointXY &center1, doub
return 2;
}

// inspired by http://csharphelper.com/blog/2014/12/find-the-tangent-lines-between-two-circles-in-c/
int QgsGeometryUtils::circleCircleInnerTangents( const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2 )
{
if ( radius1 > radius2 )
return circleCircleInnerTangents( center2, radius2, center1, radius1, line1P1, line1P2, line2P1, line2P2 );

// determine the straight-line distance between the centers
const double d = center1.distance( center2 );

// check for solvability
if ( ( d <= ( radius1 + radius2 ) ) or ( qgsDoubleNear( d, ( radius1 + radius2 ) ) ) )
{
// no solution. circles intersect or touch.
return 0;
}

const double radius1a = radius1 + radius2;
if ( !tangentPointAndCircle( center1, radius1a, center2, line1P2, line2P2 ) )
{
// there are no tangents
return 0;
}

// get the vector perpendicular to the
// first tangent with length radius2
QgsVector v1( ( line1P2.y() - center2.y() ), -( line1P2.x() - center2.x() ) );
const double v1Length = v1.length();
v1 = v1 * ( radius2 / v1Length );

// offset the tangent vector's points
line1P1 = center2 + v1;
line1P2 = line1P2 + v1;

// get the vector perpendicular to the
// second tangent with length radius2
QgsVector v2( -( line2P2.y() - center2.y() ), line2P2.x() - center2.x() );
const double v2Length = v2.length();
v2 = v2 * ( radius2 / v2Length );

// offset the tangent vector's points in opposite direction
line2P1 = center2 + v2;
line2P2 = line2P2 + v2;

return 2;
}

QVector<QgsGeometryUtils::SelfIntersection> QgsGeometryUtils::selfIntersections( const QgsAbstractGeometry *geom, int part, int ring, double tolerance )
{
QVector<SelfIntersection> intersections;
Expand Down
23 changes: 23 additions & 0 deletions src/core/geometry/qgsgeometryutils.h
Expand Up @@ -202,6 +202,29 @@ class CORE_EXPORT QgsGeometryUtils
QgsPointXY &line1P1 SIP_OUT, QgsPointXY &line1P2 SIP_OUT,
QgsPointXY &line2P1 SIP_OUT, QgsPointXY &line2P2 SIP_OUT );

/**
* Calculates the inner tangent points for two circles, centered at \a
* center1 and \a center2 and with radii of \a radius1 and \a radius2
* respectively.
*
* The inner tangent points correspond to the points at which the two lines
* which are drawn so that they are tangential to both circles and are
* crossing each other.
*
* The first tangent line is described by the points
* stored in \a line1P1 and \a line1P2,
* and the second line is described by the points stored in \a line2P1
* and \a line2P2.
*
* Returns the number of tangents (either 0 or 2).
*
* \since QGIS 3.6
*/
static int circleCircleInnerTangents(
const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2,
QgsPointXY &line1P1 SIP_OUT, QgsPointXY &line1P2 SIP_OUT,
QgsPointXY &line2P1 SIP_OUT, QgsPointXY &line2P2 SIP_OUT );

/**
* \brief Project the point on a segment
* \param p The point
Expand Down
16 changes: 15 additions & 1 deletion tests/src/core/testqgsgeometry.cpp
Expand Up @@ -7812,7 +7812,7 @@ void TestQgsGeometry::circle()
QGSCOMPARENEAR( t2.x(), 2.2, 0.01 );
QGSCOMPARENEAR( t2.y(), 5.82, 0.01 );

// two circle tangents
// two outer circle tangents
QgsPointXY l1p1, l1p2, l2p1, l2p2;
QCOMPARE( QgsCircle( QgsPoint( 1, 2 ), 4 ).outerTangents( QgsCircle( QgsPoint( 2, 3 ), 1 ), l1p1, l1p2, l2p1, l2p2 ), 0 );
QCOMPARE( QgsCircle( QgsPoint( 1, 2 ), 1 ).outerTangents( QgsCircle( QgsPoint( 10, 3 ), 4 ), l1p1, l1p2, l2p1, l2p2 ), 2 );
Expand All @@ -7824,6 +7824,20 @@ void TestQgsGeometry::circle()
QGSCOMPARENEAR( l2p1.y(), 1.025, 0.01 );
QGSCOMPARENEAR( l2p2.x(), 9.099, 0.01 );
QGSCOMPARENEAR( l2p2.y(), -0.897, 0.01 );

// two inner circle tangents
QCOMPARE( QgsCircle( QgsPoint( 1, 2 ), 4 ).innerTangents( QgsCircle( QgsPoint( 2, 3 ), 1 ), l1p1, l1p2, l2p1, l2p2 ), 0 );
QCOMPARE( QgsCircle( QgsPoint( 0, 0 ), 4 ).innerTangents( QgsCircle( QgsPoint( 8, 0 ), 5 ), l1p1, l1p2, l2p1, l2p2 ), 0 );
QCOMPARE( QgsCircle( QgsPoint( 0, 0 ), 4 ).innerTangents( QgsCircle( QgsPoint( 8, 0 ), 4 ), l1p1, l1p2, l2p1, l2p2 ), 0 );
QCOMPARE( QgsCircle( QgsPoint( 1, 2 ), 1 ).innerTangents( QgsCircle( QgsPoint( 10, 3 ), 4 ), l1p1, l1p2, l2p1, l2p2 ), 2 );
QGSCOMPARENEAR( l1p1.x(), 7.437, 0.01 );
QGSCOMPARENEAR( l1p1.y(), 6.071, 0.01 );
QGSCOMPARENEAR( l1p2.x(), 1.641, 0.01 );
QGSCOMPARENEAR( l1p2.y(), 1.232, 0.01 );
QGSCOMPARENEAR( l2p1.x(), 8.173, 0.01 );
QGSCOMPARENEAR( l2p1.y(), -0.558, 0.01 );
QGSCOMPARENEAR( l2p2.x(), 1.457, 0.01 );
QGSCOMPARENEAR( l2p2.y(), 2.89, 0.01 );
}

void TestQgsGeometry::regularPolygon()
Expand Down
40 changes: 40 additions & 0 deletions tests/src/core/testqgsgeometryutils.cpp
Expand Up @@ -64,6 +64,7 @@ class TestQgsGeometryUtils: public QObject
void testCircleCircleIntersection();
void testTangentPointAndCircle();
void testCircleCircleOuterTangents();
void testCircleCircleInnerTangents();
void testGml();
void testInterpolatePointOnLineQgsPoint();
void testInterpolatePointOnLine();
Expand Down Expand Up @@ -970,6 +971,45 @@ void TestQgsGeometryUtils::testCircleCircleOuterTangents()
QGSCOMPARENEAR( l2p2.y(), -0.897, 0.01 );
}

void TestQgsGeometryUtils::testCircleCircleInnerTangents()
{
QgsPointXY l1p1;
QgsPointXY l1p2;
QgsPointXY l2p1;
QgsPointXY l2p2;

// no tangents, intersecting circles
QCOMPARE( QgsGeometryUtils::circleCircleInnerTangents( QgsPointXY( 1, 2 ), 4, QgsPointXY( 2, 3 ), 1, l1p1, l1p2, l2p1, l2p2 ), 0 );

// no tangents, same circles
QCOMPARE( QgsGeometryUtils::circleCircleInnerTangents( QgsPointXY( 1, 2 ), 4, QgsPointXY( 1, 2 ), 4, l1p1, l1p2, l2p1, l2p2 ), 0 );

// no tangents, touching circles
QCOMPARE( QgsGeometryUtils::circleCircleInnerTangents( QgsPointXY( 0, 0 ), 4, QgsPointXY( 0, 8 ), 4, l1p1, l1p2, l2p1, l2p2 ), 0 );

// tangents
QCOMPARE( QgsGeometryUtils::circleCircleInnerTangents( QgsPointXY( 1, 2 ), 1, QgsPointXY( 10, 3 ), 4, l1p1, l1p2, l2p1, l2p2 ), 2 );
QGSCOMPARENEAR( l1p1.x(), 7.437, 0.01 );
QGSCOMPARENEAR( l1p1.y(), 6.071, 0.01 );
QGSCOMPARENEAR( l1p2.x(), 1.641, 0.01 );
QGSCOMPARENEAR( l1p2.y(), 1.232, 0.01 );
QGSCOMPARENEAR( l2p1.x(), 8.173, 0.01 );
QGSCOMPARENEAR( l2p1.y(), -0.558, 0.01 );
QGSCOMPARENEAR( l2p2.x(), 1.457, 0.01 );
QGSCOMPARENEAR( l2p2.y(), 2.89, 0.01 );

// tangents, larger circle first
QCOMPARE( QgsGeometryUtils::circleCircleInnerTangents( QgsPointXY( 10, 3 ), 4, QgsPointXY( 1, 2 ), 1, l1p1, l1p2, l2p1, l2p2 ), 2 );
QGSCOMPARENEAR( l1p1.x(), 7.437, 0.01 );
QGSCOMPARENEAR( l1p1.y(), 6.071, 0.01 );
QGSCOMPARENEAR( l1p2.x(), 1.641, 0.01 );
QGSCOMPARENEAR( l1p2.y(), 1.232, 0.01 );
QGSCOMPARENEAR( l2p1.x(), 8.173, 0.01 );
QGSCOMPARENEAR( l2p1.y(), -0.558, 0.01 );
QGSCOMPARENEAR( l2p2.x(), 1.457, 0.01 );
QGSCOMPARENEAR( l2p2.y(), 2.89, 0.01 );
}

void TestQgsGeometryUtils::testGml()
{
QgsPoint point = QgsPoint( 1, 2 );
Expand Down

0 comments on commit 0ba9a32

Please sign in to comment.