Skip to content

Commit

Permalink
Add intersection/tangent methods to QgsCircle
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Mar 29, 2018
1 parent 716ba9b commit 5d4e1bb
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 0 deletions.
57 changes: 57 additions & 0 deletions python/core/geometry/qgscircle.sip.in
Expand Up @@ -131,6 +131,63 @@ If the points are colinear an empty circle is returned.
:param pt2: Second point.
:param pt3: Third point.
:param epsilon: Value used to compare point.
%End

int intersections( const QgsCircle &other, QgsPoint &intersection1 /Out/, QgsPoint &intersection2 /Out/, bool useZ = false ) const;
%Docstring
Calculates the intersections points between this circle and an ``other`` circle.

If found, the intersection points will be stored in ``intersection1`` and ``intersection2``.

By default this method does not consider any z values and instead treats the circles as 2-dimensional.
If ``useZ`` is set to true, then an intersection will only occur if the z values of both circles are
equal. In this case the points returned for ``intersection1`` and ``intersection2`` will contain
the z value of the circle intersections.

:return: number of intersection points found.

.. versionadded:: 3.2
%End

bool tangentToPoint( const QgsPointXY &p, QgsPointXY &pt1 /Out/, QgsPointXY &pt2 /Out/ ) const;
%Docstring
Calculates the tangent points between this circle and the point ``p``.

If found, the tangent points will be stored in ``pt1`` and ``pt2``.

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

:return: true if tangent was found.

.. versionadded:: 3.2


.. seealso:: :py:func:`outerTangents`
%End

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

The outer tangent points correspond to the points at which the two lines
which are drawn so that they are tangential to both circles touch
the circles.

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.

.. versionadded:: 3.2

.. seealso:: :py:func:`tangentToPoint`
%End

virtual double area() const;
Expand Down
34 changes: 34 additions & 0 deletions src/core/geometry/qgscircle.cpp
Expand Up @@ -236,6 +236,40 @@ QgsCircle QgsCircle::minimalCircleFrom3Points( const QgsPoint &pt1, const QgsPoi
return QgsCircle().from3Points( pt1, pt2, pt3, epsilon );
}

int QgsCircle::intersections( const QgsCircle &other, QgsPoint &intersection1, QgsPoint &intersection2, bool useZ ) const
{
if ( useZ && mCenter.is3D() && other.center().is3D() && !qgsDoubleNear( mCenter.z(), other.center().z() ) )
return 0;

QgsPointXY int1, int2;

int res = QgsGeometryUtils::circleCircleIntersections( QgsPointXY( mCenter ), radius(),
QgsPointXY( other.center() ), other.radius(),
int1, int2 );
if ( res == 0 )
return 0;

intersection1 = QgsPoint( int1.x(), int1.y() );
intersection2 = QgsPoint( int2.x(), int2.y() );
if ( useZ && mCenter.is3D() )
{
intersection1.addZValue( mCenter.z() );
intersection2.addZValue( mCenter.z() );
}
return res;
}

bool QgsCircle::tangentToPoint( const QgsPointXY &p, QgsPointXY &pt1, QgsPointXY &pt2 ) const
{
return QgsGeometryUtils::tangentPointAndCircle( QgsPointXY( mCenter ), radius(), p, pt1, pt2 );
}

int QgsCircle::outerTangents( const QgsCircle &other, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2 ) const
{
return QgsGeometryUtils::circleCircleOuterTangents( 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
56 changes: 56 additions & 0 deletions src/core/geometry/qgscircle.h
Expand Up @@ -139,6 +139,62 @@ class CORE_EXPORT QgsCircle : public QgsEllipse
*/
static QgsCircle minimalCircleFrom3Points( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double epsilon = 1E-8 );

/**
* Calculates the intersections points between this circle and an \a other circle.
*
* If found, the intersection points will be stored in \a intersection1 and \a intersection2.
*
* By default this method does not consider any z values and instead treats the circles as 2-dimensional.
* If \a useZ is set to true, then an intersection will only occur if the z values of both circles are
* equal. In this case the points returned for \a intersection1 and \a intersection2 will contain
* the z value of the circle intersections.
*
* \returns number of intersection points found.
*
* \since QGIS 3.2
*/
int intersections( const QgsCircle &other, QgsPoint &intersection1 SIP_OUT, QgsPoint &intersection2 SIP_OUT, bool useZ = false ) const;

/**
* Calculates the tangent points between this circle and the point \a p.
*
* If found, the tangent points will be stored in \a pt1 and \a pt2.
*
* Note that this method is 2D only and does not consider the z-value of the circle.
*
* \returns true if tangent was found.
*
* \since QGIS 3.2
*
* \see outerTangents()
*/
bool tangentToPoint( const QgsPointXY &p, QgsPointXY &pt1 SIP_OUT, QgsPointXY &pt2 SIP_OUT ) const;

/**
* Calculates the outer tangent points between this circle
* and an \a other circle.
*
* The outer tangent points correspond to the points at which the two lines
* which are drawn so that they are tangential to both circles touch
* the circles.
*
* 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.
*
* \since QGIS 3.2
*
* \see tangentToPoint()
*/
int outerTangents( 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
55 changes: 55 additions & 0 deletions tests/src/core/testqgsgeometry.cpp
Expand Up @@ -7181,6 +7181,61 @@ void TestQgsGeometry::circle()
QVERIFY( QgsCircle( QgsPoint( 0, 0 ), 5 ).contains( pc ) );
pc = QgsPoint( 6, 1 );
QVERIFY( !QgsCircle( QgsPoint( 0, 0 ), 5 ).contains( pc ) );

// intersections
QgsCircle ci1( QgsPoint( 0, 0 ), 1 );
QgsPoint int1;
QgsPoint int2;
QCOMPARE( ci1.intersections( QgsCircle( QgsPoint( 2, 0 ), 0.5 ), int1, int2 ), 0 );
QCOMPARE( ci1.intersections( QgsCircle( QgsPoint( 0.5, 0.1 ), 0.2 ), int1, int2 ), 0 );
// one intersection
QCOMPARE( ci1.intersections( QgsCircle( QgsPoint( 3, 0 ), 2 ), int1, int2 ), 1 );
QCOMPARE( int1, QgsPoint( 1, 0 ) );
QCOMPARE( int2, QgsPoint( 1, 0 ) );
// two intersections
ci1 = QgsCircle( QgsPoint( 5, 3 ), 2 );
QCOMPARE( ci1.intersections( QgsCircle( QgsPoint( 7, -1 ), 4 ), int1, int2 ), 2 );
QCOMPARE( int1.wkbType(), QgsWkbTypes::Point );
QGSCOMPARENEAR( int1.x(), 3.8, 0.001 );
QGSCOMPARENEAR( int1.y(), 1.4, 0.001 );
QCOMPARE( int2.wkbType(), QgsWkbTypes::Point );
QGSCOMPARENEAR( int2.x(), 7.0, 0.001 );
QGSCOMPARENEAR( int2.y(), 3.0, 0.001 );
// with z
ci1 = QgsCircle( QgsPoint( 5, 3, 11 ), 2 );
QCOMPARE( ci1.intersections( QgsCircle( QgsPoint( 7, -1, 5 ), 4 ), int1, int2, true ), 0 );
QCOMPARE( ci1.intersections( QgsCircle( QgsPoint( 7, -1, 11 ), 4 ), int1, int2, true ), 2 );
QCOMPARE( int1.wkbType(), QgsWkbTypes::PointZ );
QGSCOMPARENEAR( int1.x(), 3.8, 0.001 );
QGSCOMPARENEAR( int1.y(), 1.4, 0.001 );
QGSCOMPARENEAR( int1.z(), 11.0, 0.001 );
QCOMPARE( int2.wkbType(), QgsWkbTypes::PointZ );
QGSCOMPARENEAR( int2.x(), 7.0, 0.001 );
QGSCOMPARENEAR( int2.y(), 3.0, 0.001 );
QGSCOMPARENEAR( int2.z(), 11.0, 0.001 );

// tangent to point
QgsPointXY t1;
QgsPointXY t2;
QVERIFY( !QgsCircle( QgsPoint( 1, 2 ), 4 ).tangentToPoint( QgsPointXY( 1, 2 ), t1, t2 ) );
QVERIFY( QgsCircle( QgsPoint( 1, 2 ), 4 ).tangentToPoint( QgsPointXY( 8, 4 ), t1, t2 ) );
QGSCOMPARENEAR( t1.x(), 4.03, 0.01 );
QGSCOMPARENEAR( t1.y(), -0.61, 0.01 );
QGSCOMPARENEAR( t2.x(), 2.2, 0.01 );
QGSCOMPARENEAR( t2.y(), 5.82, 0.01 );

// two 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 );
QGSCOMPARENEAR( l1p1.x(), 0.566, 0.01 );
QGSCOMPARENEAR( l1p1.y(), 2.901, 0.01 );
QGSCOMPARENEAR( l1p2.x(), 8.266, 0.01 );
QGSCOMPARENEAR( l1p2.y(), 6.604, 0.01 );
QGSCOMPARENEAR( l2p1.x(), 0.7749, 0.01 );
QGSCOMPARENEAR( l2p1.y(), 1.025, 0.01 );
QGSCOMPARENEAR( l2p2.x(), 9.099, 0.01 );
QGSCOMPARENEAR( l2p2.y(), -0.897, 0.01 );
}

void TestQgsGeometry::regularPolygon()
Expand Down

0 comments on commit 5d4e1bb

Please sign in to comment.