Skip to content
Permalink
Browse files

Add API to calculate innerTangents for 2 circles

  • Loading branch information
raymondnijssen authored and nyalldawson committed Jan 13, 2019
1 parent aafd848 commit 0ba9a326ec77bb67a1bcae61de21a9cbcafee660
@@ -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;
@@ -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 );
@@ -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() );
@@ -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;
@@ -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;

@@ -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;
@@ -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
@@ -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 );
@@ -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()
@@ -64,6 +64,7 @@ class TestQgsGeometryUtils: public QObject
void testCircleCircleIntersection();
void testTangentPointAndCircle();
void testCircleCircleOuterTangents();
void testCircleCircleInnerTangents();
void testGml();
void testInterpolatePointOnLineQgsPoint();
void testInterpolatePointOnLine();
@@ -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 );

0 comments on commit 0ba9a32

Please sign in to comment.
You can’t perform that action at this time.