diff --git a/include/geos/algorithm/Angle.h b/include/geos/algorithm/Angle.h index a2ea53a48d..bb1d8757d6 100644 --- a/include/geos/algorithm/Angle.h +++ b/include/geos/algorithm/Angle.h @@ -204,6 +204,15 @@ class GEOS_DLL Angle { /// static double normalizePositive(double angle); + /// Returns true if angle x is within the counterclockwise + /// arc from angle a to angle b + /// + /// @param angle angle to test + /// @param from starting angle of arc + /// @param to ending angle of arc + /// + /// @return true if `angle` is within [from, to] + static bool isWithinCCW(double angle, double from, double to); /// Computes the unoriented smallest difference between two angles. /// diff --git a/include/geos/algorithm/CircularArcIntersector.h b/include/geos/algorithm/CircularArcIntersector.h new file mode 100644 index 0000000000..db7d40cd2a --- /dev/null +++ b/include/geos/algorithm/CircularArcIntersector.h @@ -0,0 +1,97 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace geos::algorithm { + +class GEOS_DLL CircularArcIntersector { +public: + using CoordinateXY = geom::CoordinateXY; + using CircularArc = geom::CircularArc; + using Envelope = geom::Envelope; + + enum intersection_type : uint8_t { + NO_INTERSECTION = 0, + ONE_POINT_INTERSECTION = 1, + TWO_POINT_INTERSECTION = 2, + COCIRCULAR_INTERSECTION = 3, + }; + + intersection_type getResult() const + { + return result; + } + + const CoordinateXY& getPoint(std::uint8_t i) const + { + return intPt[i]; + } + + const std::array& getArc(std::uint8_t i) const + { + return intArc[i]; + } + + std::uint8_t getNumPoints() const + { + return nPt; + } + + std::uint8_t getNumArcs() const + { + return nArc; + } + + /// Determines whether and where a circular arc intersects a line segment. + /// + /// Sets the appropriate value of intersection_type and stores the intersection + /// points, if any. + void intersects(const CircularArc& arc, const CoordinateXY& p0, const CoordinateXY& p1); + + void intersects(const CircularArc& arc, const geom::LineSegment& seg) + { + intersects(arc, seg.p0, seg.p1); + } + + /// Determines whether and where two circular arcs intersect. + /// + /// Sets the appropriate value of intesection_type and stores the intersection + /// points and/or arcs, if any. + void intersects(const CircularArc& arc1, const CircularArc& arc2); + + static int + circleIntersects(const CoordinateXY& center, double r, const CoordinateXY& p0, const CoordinateXY& p1, CoordinateXY& isect0, CoordinateXY& isect1); + +private: + + void intersects(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& q0, const CoordinateXY& q1); + + std::array intPt; + std::array, 2> intArc; + intersection_type result; + std::uint8_t nPt = 0; + std::uint8_t nArc = 0; + +}; + +} diff --git a/include/geos/algorithm/CircularArcs.h b/include/geos/algorithm/CircularArcs.h index 54f0a9b7d1..c0c2bd9cdf 100644 --- a/include/geos/algorithm/CircularArcs.h +++ b/include/geos/algorithm/CircularArcs.h @@ -14,6 +14,8 @@ #pragma once +#include + #include #include #include @@ -31,6 +33,15 @@ class GEOS_DLL CircularArcs { /// Expand an envelope to include an arc defined by three points static void expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, const geom::CoordinateXY& p2); + + + /// Return three points defining a arc defined by a circle center, radius, and start/end angles + static std::array + createArc(const geom::CoordinateXY& center, double radius, double start, double end, bool ccw); + + /// Return the point defined by a circle center, radius, and angle + static geom::CoordinateXY createPoint(const geom::CoordinateXY& center, double radius, double theta); + }; } diff --git a/include/geos/geom/CircularArc.h b/include/geos/geom/CircularArc.h index 283eaf3b08..3ab365c043 100644 --- a/include/geos/geom/CircularArc.h +++ b/include/geos/geom/CircularArc.h @@ -83,7 +83,7 @@ class GEOS_DLL CircularArc { /// Returns whether this arc forms a straight line (p0, p1, and p2 are collinear) bool isLinear() const { - return std::isnan(getRadius()); + return !std::isfinite(getRadius()); } /// Return the inner angle of the sector associated with this arc diff --git a/include/geos/math/DD.h b/include/geos/math/DD.h index 61e3a08a72..7be00a6f7c 100644 --- a/include/geos/math/DD.h +++ b/include/geos/math/DD.h @@ -158,6 +158,7 @@ class GEOS_DLL DD { friend GEOS_DLL DD operator* (const DD &lhs, double rhs); friend GEOS_DLL DD operator/ (const DD &lhs, const DD &rhs); friend GEOS_DLL DD operator/ (const DD &lhs, double rhs); + friend GEOS_DLL DD operator- (const DD& x); static DD determinant(const DD &x1, const DD &y1, const DD &x2, const DD &y2); static DD determinant(double x1, double y1, double x2, double y2); diff --git a/src/algorithm/Angle.cpp b/src/algorithm/Angle.cpp index 7069607f55..8b7108005d 100644 --- a/src/algorithm/Angle.cpp +++ b/src/algorithm/Angle.cpp @@ -181,6 +181,15 @@ Angle::normalizePositive(double angle) return angle; } +bool +Angle::isWithinCCW(double x, double a, double b) { + if (b > a) { + return x >= a && x <= b; + } else { + return x >= a || x <= b; + } +} + /* public static */ double Angle::diff(double ang1, double ang2) diff --git a/src/algorithm/CircularArcIntersector.cpp b/src/algorithm/CircularArcIntersector.cpp new file mode 100644 index 0000000000..3af44bf0e3 --- /dev/null +++ b/src/algorithm/CircularArcIntersector.cpp @@ -0,0 +1,299 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include + +namespace geos::algorithm { + +static double +nextAngleCCW(double from, double a, double b) +{ + if (Angle::normalizePositive(a - from) < Angle::normalizePositive(b - from)) { + return a; + } + else { + return b; + } +} + +int +CircularArcIntersector::circleIntersects(const CoordinateXY& center, double r, const CoordinateXY& p0, const CoordinateXY& p1, CoordinateXY& ret0, CoordinateXY& ret1) +{ + const double& x0 = center.x; + const double& y0 = center.y; + + Envelope segEnv(p0, p1); + + CoordinateXY isect0, isect1; + int n = 0; + + if (p1.x == p0.x) { + // vertical line + double x = p1.x; + + double A = 1; + double B = 2*y0; + double C = x*x - 2*x*x0 + x0*x0 + y0*y0 - r*r; + + double d = std::sqrt(B*B - 4*A*C); + double Y1 = (-B + d)/(2*A); + double Y2 = (-B - d)/(2*A); + + isect0 = {x, Y1}; + isect1 = {x, Y2}; + } + else { + double m = (p1.y - p0.y) / (p1.x - p0.x); + double b = p1.y - p1.x*m; + + // Ax^2 + Bx + C = 0 + double A = 1 + m*m; + double B = -2*x0 + 2*m*b - 2*m*y0; + double C = x0*x0 + b*b - 2*b*y0 + y0*y0 - r*r; + + // TODO robust quadratic equation + double d = std::sqrt(B*B - 4*A*C); + double X1 = (-B + d)/(2*A); + double X2 = (-B - d)/(2*A); + + isect0 = {X1, m* X1 + b}; + isect1 = {X2, m* X2 + b}; + } + + if (segEnv.intersects(isect0)) { + ret0 = isect0; + if (segEnv.intersects(isect1) && !isect1.equals2D(isect0)) { + ret1 = isect1; + n = 2; + } else { + n = 1; + } + } else if (segEnv.intersects(isect1)) { + ret0 = isect1; + n = 1; + } + + return n; +} + +void +CircularArcIntersector::intersects(const CircularArc& arc, const CoordinateXY& p0, const CoordinateXY& p1) +{ + if (arc.isLinear()) { + intersects(arc.p0, arc.p2, p0, p1); + return; + } + + // TODO: envelope check? + const CoordinateXY& c = arc.getCenter(); + const double r = arc.getRadius(); + + CoordinateXY isect0, isect1; + auto n = circleIntersects(c, r, p0, p1, isect0, isect1); + + if (n > 0 && arc.containsPointOnCircle(isect0)) { + intPt[nPt++] = isect0; + } + + if (n > 1 && arc.containsPointOnCircle(isect1)) { + intPt[nPt++] = isect1; + } + + switch (nPt) { + case 2: + result = TWO_POINT_INTERSECTION; + break; + case 1: + result = ONE_POINT_INTERSECTION; + break; + default: + result = NO_INTERSECTION; + } +} + +void +CircularArcIntersector::intersects(const CoordinateXY& p0, const CoordinateXY& p1, + const CoordinateXY& q0, const CoordinateXY& q1) +{ + algorithm::LineIntersector li; + li.computeIntersection(p0, p1, q0, q1); + if (li.getIntersectionNum() == 2) { + // FIXME this means a collinear intersection, so we should report as cocircular? + intPt[0] = li.getIntersection(0); + intPt[1] = li.getIntersection(1); + result = TWO_POINT_INTERSECTION; + } else if (li.getIntersectionNum() == 1) { + intPt[0] = li.getIntersection(0); + nPt = 1; + result = ONE_POINT_INTERSECTION; + } else { + result = NO_INTERSECTION; + } +} + +void +CircularArcIntersector::intersects(const CircularArc& arc1, const CircularArc& arc2) +{ + // Handle cases where one or both arcs are degenerate + if (arc1.isLinear()) { + if (arc2.isLinear()) { + intersects(arc1.p0, arc1.p2, arc2.p0, arc2.p2); + return; + } else { + intersects(arc2, arc1.p0, arc1.p2); + return; + } + } else if (arc2.isLinear()) { + intersects(arc1, arc2.p0, arc2.p2); + return; + } + + const auto& c1 = arc1.getCenter(); + const auto& c2 = arc2.getCenter(); + + const auto r1 = arc1.getRadius(); + const auto r2 = arc2.getRadius(); + + auto d = c1.distance(c2); + + if (d > r1 + r2) { + // Circles are disjoint + result = NO_INTERSECTION; + return; + } + + if (d < std::abs(r1-r2)) { + // One circle contained within the other; arcs cannot intersect + result = NO_INTERSECTION; + return; + } + + // a: the distance from c1 to the "radical line", which connects the two intersection points + double a = (d*d + r1*r1 - r2*r2) / (2*d); + + // FIXME shouldn't be possible for a to be more than d, and yet it can happen for + // arcs that are very nearly cocircular + //if (a > d) { + // std::cerr << "a > d" << std::endl; + //} + + // FIXME because the circle center calculation is inexact we need some kind of tolerance here. + // Take a PrecisionModel like LineIntersector? + if (a == 0 || (d == 0 && r1 == r2)) { + // COCIRCULAR + + double ap0 = arc1.theta0(); + double ap1 = arc1.theta2(); + double bp0 = arc2.theta0(); + double bp1 = arc2.theta2(); + + bool resultArcIsCCW = true; + + if (arc1.orientation() != Orientation::COUNTERCLOCKWISE) { + std::swap(ap0, ap1); + resultArcIsCCW = false; + } + if (arc2.orientation() != Orientation::COUNTERCLOCKWISE) { + std::swap(bp0, bp1); + } + ap0 = Angle::normalizePositive(ap0); + ap1 = Angle::normalizePositive(ap1); + bp0 = Angle::normalizePositive(bp0); + bp1 = Angle::normalizePositive(bp1); + + bool checkBp1inA = true; + + // check start of B within A? + if (Angle::isWithinCCW(bp0, ap0, ap1)) { + double start = bp0; + double end = nextAngleCCW(start, bp1, ap1); + + if (end == bp1) { + checkBp1inA = false; + } + + if (start == end) { + intPt[nPt++] = CircularArcs::createPoint(c1, r1, start); + } + else { + if (resultArcIsCCW) { + intArc[nArc++] = CircularArcs::createArc(c1, r1, start, end, true); + } + else { + intArc[nArc++] = CircularArcs::createArc(c1, r1, end, start, false); + } + } + } + + if (checkBp1inA && Angle::isWithinCCW(bp1, ap0, ap1)) { + // end of B within A? + double start = ap0; + double end = bp1; + if (start == end) { + intPt[nPt++] = CircularArcs::createPoint(c1, r1, start); + } + else { + if (resultArcIsCCW) { + intArc[nArc++] = CircularArcs::createArc(c1, r1, start, end, true); + } + else { + intArc[nArc++] = CircularArcs::createArc(c1, r1, end, start, false); + } + } + } + } else { + // NOT COCIRCULAR + + double dx = c2.x-c1.x; + double dy = c2.y-c1.y; + + // point where a line between the two circle center points intersects + // the radical line + CoordinateXY p{c1.x + a* dx/d, c1.y+a* dy/d}; + + // distance from p to the intersection points + double h = std::sqrt(r1*r1 - a*a); + + CoordinateXY isect0{p.x + h* dy/d, p.y - h* dx/d }; + CoordinateXY isect1{p.x - h* dy/d, p.y + h* dx/d }; + + if (arc1.containsPointOnCircle(isect0) && arc2.containsPointOnCircle(isect0)) { + intPt[nPt++] = isect0; + } + if (!isect1.equals2D(isect0) && arc1.containsPointOnCircle(isect1) && arc2.containsPointOnCircle(isect1)) { + intPt[nPt++] = isect1; + } + } + + if (nArc) { + result = COCIRCULAR_INTERSECTION; + } + else { + switch (nPt) { + case 2: + result = TWO_POINT_INTERSECTION; + break; + case 1: + result = ONE_POINT_INTERSECTION; + break; + case 0: + result = NO_INTERSECTION; + break; + } + } +} + +} diff --git a/src/algorithm/CircularArcs.cpp b/src/algorithm/CircularArcs.cpp index 949f185090..b36e85e31f 100644 --- a/src/algorithm/CircularArcs.cpp +++ b/src/algorithm/CircularArcs.cpp @@ -12,16 +12,92 @@ * **********************************************************************/ +#include #include #include #include #include +#include using geos::geom::CoordinateXY; namespace geos { namespace algorithm { +std::array +CircularArcs::createArc(const geom::CoordinateXY& center, double radius, double start, double end, bool ccw) +{ + if (!ccw) { + std::swap(start, end); + } + + double mid = (start + end) / 2; + if (!Angle::isWithinCCW(mid, start, end)) { + mid += MATH_PI; + } + + if (ccw) { + return { + createPoint(center, radius, start), + createPoint(center, radius, mid), + createPoint(center, radius, end), + }; + } else { + return { + createPoint(center, radius, end), + createPoint(center, radius, mid), + createPoint(center, radius, start), + }; + } +} + +CoordinateXY +CircularArcs::createPoint(const CoordinateXY& center, double radius, double theta) +{ + return { center.x + radius* std::cos(theta), center.y + radius* std::sin(theta) }; +} + +template +CoordinateXY getCenterImpl(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) +{ + // Circumcenter formulas from Graphics Gems III + T p0x{p0.x}; + T p0y{p0.y}; + T p1x{p1.x}; + T p1y{p1.y}; + T p2x{p2.x}; + T p2y{p2.y}; + + T ax = p1x - p2x; + T ay = p1y - p2y; + T bx = p2x - p0x; + T by = p2y - p0y; + T cx = p0x - p1x; + T cy = p0y - p1y; + + T d1 = -(bx*cx + by*cy); + T d2 = -(cx*ax + cy*ay); + T d3 = -(ax*bx + ay*by); + + T e1 = d2*d3; + T e2 = d3*d1; + T e3 = d1*d2; + T e = e1 + e2 + e3; + + T G3x = p0.x + p1.x + p2.x; + T G3y = p0.y + p1.y + p2.y; + T Hx = (e1*p0.x + e2*p1.x + e3*p2.x) / e; + T Hy = (e1*p0.y + e2*p1.y + e3*p2.y) / e; + + T rx = 0.5*(G3x - Hx); + T ry = 0.5*(G3y - Hy); + + if constexpr (std::is_same_v) { + return {rx.doubleValue(), ry.doubleValue()}; + } else { + return {rx, ry}; + } +} CoordinateXY CircularArcs::getCenter(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) @@ -31,26 +107,7 @@ CircularArcs::getCenter(const CoordinateXY& p0, const CoordinateXY& p1, const Co return { 0.5*(p0.x + p1.x), 0.5*(p0.y + p1.y) }; } - // Circumcenter formulas from Graphics Gems III - CoordinateXY a{p1.x - p2.x, p1.y - p2.y}; - CoordinateXY b{p2.x - p0.x, p2.y - p0.y}; - CoordinateXY c{p0.x - p1.x, p0.y - p1.y}; - - double d1 = -(b.x*c.x + b.y*c.y); - double d2 = -(c.x*a.x + c.y*a.y); - double d3 = -(a.x*b.x + a.y*b.y); - - double e1 = d2*d3; - double e2 = d3*d1; - double e3 = d1*d2; - double e = e1 + e2 + e3; - - CoordinateXY G3{p0.x + p1.x + p2.x, p0.y + p1.y + p2.y}; - CoordinateXY H {(e1*p0.x + e2*p1.x + e3*p2.x) / e, (e1*p0.y + e2*p1.y + e3*p2.y) / e}; - - CoordinateXY center = {0.5*(G3.x - H.x), 0.5*(G3.y - H.y)}; - - return center; + return getCenterImpl(p0, p1, p2); } void diff --git a/src/math/DD.cpp b/src/math/DD.cpp index c620c1b621..662f1f1d34 100644 --- a/src/math/DD.cpp +++ b/src/math/DD.cpp @@ -236,6 +236,11 @@ DD operator/(const DD &lhs, double rhs) return rv; } +DD operator- (const DD& x) +{ + return x.negate(); +} + /* public */ DD DD::negate() const { diff --git a/tests/unit/algorithm/AngleTest.cpp b/tests/unit/algorithm/AngleTest.cpp index cc889c5e98..2ff097dc31 100644 --- a/tests/unit/algorithm/AngleTest.cpp +++ b/tests/unit/algorithm/AngleTest.cpp @@ -7,9 +7,9 @@ #include #include // std -#include #include -#include + +using geos::MATH_PI; namespace tut { // @@ -198,7 +198,40 @@ void object::test<6> ensure_equals(std::to_string(angrad), rCos, std::cos(angrad)); } +} + +template<> +template<> +void object::test<7>() +{ + set_test_name("isWithinCCW"); + + // interval from 0 to pi + { + double from = 0, to = MATH_PI; + ensure("pi/2 in [0, pi]", Angle::isWithinCCW(Angle::PI_OVER_2, from, to)); + ensure("0 in [0, pi]", Angle::isWithinCCW(0, from, to)); + ensure("pi in [0, pi]", Angle::isWithinCCW(MATH_PI, from, to)); + ensure("-pi/2 not in [0, pi]", !Angle::isWithinCCW(Angle::normalizePositive(-Angle::PI_OVER_2), from, to)); + } + + // interval from pi to 0 + { + double from = MATH_PI, to = 0; + ensure("pi/2 not in [pi, 0]", !Angle::isWithinCCW(Angle::PI_OVER_2, from, to)); + ensure("0 in [pi, 0]", Angle::isWithinCCW(0, from, to)); + ensure("pi in [pi, 0]", Angle::isWithinCCW(MATH_PI, from, to)); + ensure("-pi/2 in [pi, 0]", Angle::isWithinCCW(Angle::normalizePositive(-Angle::PI_OVER_2), from, to)); + } + // interval from -pi/2 to pi/2 + { + double from = Angle::normalizePositive(-Angle::PI_OVER_2), to = Angle::PI_OVER_2; + ensure("0 in [-pi/2, pi/2]", Angle::isWithinCCW(0, from, to)); + ensure("pi/2 in [-pi/2, pi/2]", Angle::isWithinCCW(Angle::PI_OVER_2, from, to)); + ensure("-pi/2 in [-pi/2, pi/2]", Angle::isWithinCCW(Angle::normalizePositive(-Angle::PI_OVER_2), from, to)); + ensure("pi not in [-pi/2, pi/2]", !Angle::isWithinCCW(MATH_PI, from, to)); + } } diff --git a/tests/unit/algorithm/CircularArcIntersectorTest.cpp b/tests/unit/algorithm/CircularArcIntersectorTest.cpp new file mode 100644 index 0000000000..b5b50964d5 --- /dev/null +++ b/tests/unit/algorithm/CircularArcIntersectorTest.cpp @@ -0,0 +1,632 @@ +#include + +#include +#include +#include +#include + +using geos::algorithm::CircularArcIntersector; +using geos::geom::CoordinateXY; +using geos::geom::CircularArc; +using geos::MATH_PI; + +namespace tut { + +struct test_circulararcintersector_data { + + using Arc = std::array; + using ArcOrPoint = std::variant; + + static std::string to_string(CircularArcIntersector::intersection_type t) + { + switch (t) { + case geos::algorithm::CircularArcIntersector::NO_INTERSECTION: + return "no intersection"; + case geos::algorithm::CircularArcIntersector::ONE_POINT_INTERSECTION: + return "one-point intersection"; + case geos::algorithm::CircularArcIntersector::TWO_POINT_INTERSECTION: + return "two-point intersection"; + case geos::algorithm::CircularArcIntersector::COCIRCULAR_INTERSECTION: + return "cocircular intersection"; + break; + } + + return ""; + } + + static std::string toWKT(const CoordinateXY& pt) + { + return "POINT (" + pt.toString() + ")"; + } + + static std::string toWKT(const Arc& arc) + { + return "CIRCULARSTRING (" + arc[0].toString() + ", " + arc[1].toString() + ", " + arc[2].toString() + ")"; + } + + static std::string toWKT(const CircularArc& arc) + { + return "CIRCULARSTRING (" + arc.p0.toString() + ", " + arc.p1.toString() + ", " + arc.p2.toString() + ")"; + } + + static std::string toWKT(const geos::geom::LineSegment& seg) + { + return "LINESTRING (" + seg.p0.toString() + ", " + seg.p1.toString() + ")"; + } + + static void checkIntersection(CoordinateXY p0, CoordinateXY p1, CoordinateXY p2, + CoordinateXY q0, CoordinateXY q1, CoordinateXY q2, + CircularArcIntersector::intersection_type result, + ArcOrPoint i0 = CoordinateXY::getNull(), + ArcOrPoint i1 = CoordinateXY::getNull()) + { + CircularArc a0(p0, p1, p2); + CircularArc a1(q0, q1, q2); + + checkIntersection(a0, a1, result, i0, i1); + } + + static void checkIntersection(CoordinateXY p0, CoordinateXY p1, CoordinateXY p2, + CoordinateXY q0, CoordinateXY q1, + CircularArcIntersector::intersection_type result, + CoordinateXY i0 = CoordinateXY::getNull(), + CoordinateXY i1 = CoordinateXY::getNull()) + { + CircularArc a(p0, p1, p2); + geos::geom::LineSegment s(geos::geom::Coordinate{q0}, geos::geom::Coordinate{q1}); + + checkIntersection(a, s, result, i0, i1); + } + + template + static void checkIntersection(const CircularArc& a0, + const CircularArcOrLineSegment& a1, + CircularArcIntersector::intersection_type result, + ArcOrPoint p0 = CoordinateXY::getNull(), + ArcOrPoint p1 = CoordinateXY::getNull()) + { + CircularArcIntersector cai; + cai.intersects(a0, a1); + + checkIntersectionResult(cai, result, p0, p1); + } + + static void checkIntersectionResult(const CircularArcIntersector& cai, + CircularArcIntersector::intersection_type result, + ArcOrPoint p0, + ArcOrPoint p1) + { + ensure_equals("incorrect intersection type", to_string(cai.getResult()), to_string(result)); + + std::vector expectedPoints; + std::vector expectedArcs; + + for (const auto& intersection : { + p0, p1 + }) { + if (std::holds_alternative(intersection)) { + const CoordinateXY& pt = std::get(intersection); + if (!pt.isNull()) { + expectedPoints.push_back(pt); + } + } + else { + expectedArcs.push_back(std::get(intersection)); + } + } + + std::vector actualPoints; + std::vector actualArcs; + + for (std::uint8_t i = 0; i < cai.getNumPoints(); i++) { + actualPoints.push_back(cai.getPoint(i)); + } + + for (std::uint8_t i = 0; i < cai.getNumArcs(); i++) { + actualArcs.push_back(cai.getArc(i)); + } + + std::sort(actualPoints.begin(), actualPoints.end()); + std::sort(actualArcs.begin(), actualArcs.end()); + std::sort(expectedPoints.begin(), expectedPoints.end()); + std::sort(expectedArcs.begin(), expectedArcs.end()); + + bool equal = true; + if (actualPoints.size() != expectedPoints.size()) { + equal = false; + } + if (actualArcs.size() != expectedArcs.size()) { + equal = false; + } + + constexpr double eps = 1e-8; + + if (equal) { + for (std::size_t i = 0; i < actualPoints.size(); i++) { + if (actualPoints[i].distance(expectedPoints[i]) > eps) { + equal = false; + } + } + for (std::size_t i = 0; i < actualArcs.size(); i++) { + for (std::size_t j = 0; j < actualArcs[i].size(); j++) { + if (actualArcs[i][j].distance(expectedArcs[i][j]) > eps) { + equal = false; + } + } + } + } + + if (equal) { + return; + } + + std::string actual; + for (const auto& pt : actualPoints) { + if (!actual.empty()) { + actual += ", "; + } + actual += toWKT(pt); + } + for (const auto& arc : actualArcs) { + if (!actual.empty()) { + actual += ", "; + } + actual += toWKT(arc); + } + + std::string expected; + for (const auto& pt : expectedPoints) { + if (!expected.empty()) { + expected += ", "; + } + expected += toWKT(pt); + } + for (const auto& arc : expectedArcs) { + if (!expected.empty()) { + expected += ", "; + } + expected += toWKT(arc); + } + + ensure_equals(actual, expected); + } + + const CoordinateXY _NW = { -std::sqrt(2)/2, std::sqrt(2)/2 }; + const CoordinateXY _N = { 0, 1}; + const CoordinateXY _NE = { std::sqrt(2)/2, std::sqrt(2)/2 }; + const CoordinateXY _E = { 1, 0}; + const CoordinateXY _SE = { std::sqrt(2)/2, -std::sqrt(2)/2 }; + const CoordinateXY _S = { 0, -1}; + const CoordinateXY _SW = { -std::sqrt(2)/2, -std::sqrt(2)/2 }; + const CoordinateXY _W = { -1, 0}; +}; + +using group = test_group; +using object = group::object; + +group test_circulararcintersector_group("geos::algorithm::CircularArcIntersector"); + +template<> +template<> +void object::test<1>() +{ + set_test_name("interior/interior intersection (one point)"); + + checkIntersection({0, 0}, {1, std::sqrt(3)}, {2, 2}, + {0, 2}, {1, std::sqrt(3)}, {2, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{1, std::sqrt(3)}); +} + +template<> +template<> +void object::test<2>() +{ + set_test_name("interior/interior intersection (two points)"); + + // result from CGAL 5.4 + checkIntersection({0, 0}, {2, 2}, {4, 0}, + {0, 1}, {2, -1}, {4, 1}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + CoordinateXY{0.0635083268962914893, 0.5}, + CoordinateXY{3.93649167310370851, 0.5}); +} + +template<> +template<> +void object::test<3>() +{ + set_test_name("single endpoint-endpoint intersection"); + + checkIntersection({0, 0}, {1, 1}, {2, 0}, + {2, 0}, {3, -1}, {4, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{2, 0}); +} + +template<> +template<> +void object::test<4>() +{ + set_test_name("single interior-interior intersection at point of tangency"); + + checkIntersection({0, 0}, {1, 1}, {2, 0}, + {0, 2}, {1, 1}, {2, 2}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{1, 1}); +} + +template<> +template<> +void object::test<5>() +{ + set_test_name("supporting circles intersect but arcs do not"); + + checkIntersection({0, 0}, {2, 2}, {4, 0}, + {1, 1}, {0, -1}, {-1, 1}, + CircularArcIntersector::NO_INTERSECTION); + +} + +template<> +template<> +void object::test<6>() +{ + set_test_name("one circle contained within other"); + + checkIntersection({0, 0}, {4, 4}, {8, 0}, + {2, 0}, {4, 2}, {6, 0}, + CircularArcIntersector::NO_INTERSECTION); +} + +template<> +template<> +void object::test<7>() +{ + set_test_name("cocircular with double endpoint intersection"); + + checkIntersection({0, 0}, {1, 1}, {2, 0}, + {0, 0}, {1, -1}, {2, 0}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + CoordinateXY{0, 0}, CoordinateXY{2, 0}); +} + +template<> +template<> +void object::test<8>() +{ + set_test_name("cocircular with single endpoint intersection"); + + checkIntersection({-2, 0}, {0, 2}, {2, 0}, + {0, -2}, {std::sqrt(2), -std::sqrt(2)}, {2, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{2, 0}); +} + +template<> +template<> +void object::test<9>() +{ + set_test_name("cocircular disjoint"); + + checkIntersection(_NW, _N, _NE, + _SW, _S, _SE, + CircularArcIntersector::NO_INTERSECTION); +} + +template<> +template<> +void object::test<10>() +{ + set_test_name("cocircular with single arc intersection (clockwise)"); + + checkIntersection({-5, 0}, {0, 5}, {5, 0}, // CW + {-4, 3}, {0, 5}, {4, 3}, // CW + CircularArcIntersector::COCIRCULAR_INTERSECTION, + Arc{{{-4, 3}, {0, 5}, {4, 3}}}); // CW +} + +template<> +template<> +void object::test<11>() +{ + set_test_name("cocircular with single arc intersection (counter-clockwise)"); + + checkIntersection({5, 0}, {0, 5}, {-5, 0}, // CCW + {-4, 3}, {0, 5}, {4, 3}, // CW + CircularArcIntersector::COCIRCULAR_INTERSECTION, + Arc{{{4, 3}, {0, 5}, {-4, 3}}}); // CCW +} + +template<> +template<> +void object::test<12>() +{ + set_test_name("cocircular with arc and point intersections"); + + checkIntersection({-5, 0}, {0, 5}, {5, 0}, + {5, 0}, {0, -5}, {0, 5}, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + Arc{{{-5, 0}, {-5*std::sqrt(2)/2, 5*std::sqrt(2)/2}, {0, 5}}}, + CoordinateXY{5, 0}); +} + +template<> +template<> +void object::test<13>() +{ + set_test_name("cocircular with two arc intersections"); + + checkIntersection({-5, 0}, {0, 5}, {5, 0}, + {3, 4}, {0, -5}, {-3, 4}, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + Arc{{{3, 4}, {4.4721359549995796, 2.2360679774997898}, {5, 0}}}, + Arc{{{-5, 0}, {-4.4721359549995796, 2.2360679774997907}, {-3, 4}}}); +} + +template<> +template<> +void object::test<20>() +{ + set_test_name("arc - degenerate arc with single interior intersection"); + + checkIntersection({0, 0}, {2, 2}, {4, 0}, // CW arc + {-1, -4}, {1, 0}, {3, 4}, // degenerate arc + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{2, 2}); + + checkIntersection({-1, -4}, {1, 0}, {3, 4}, // degenerate arc + {0, 0}, {2, 2}, {4, 0}, // CW arc + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{2, 2}); +} + +template<> +template<> +void object::test<21>() +{ + set_test_name("two degenerate arcs with single interior intersection"); + + checkIntersection({0, 0}, {4, 4}, {10, 10}, + {10, 0}, {1, 9}, {0, 10}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{5, 5}); +} + +template<> +template<> +void object::test<30>() +{ + set_test_name("arc-segment with single interior intersection"); + + checkIntersection({0, 0}, {2, 2}, {4, 0}, + {1, 0}, {3, 4}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {2, 2}); +} + +template<> +template<> +void object::test<31>() +{ + set_test_name("arc-vertical segment with single interior intersection"); + + checkIntersection({-2, 0}, {0, 2}, {2, 0}, + {0, 0}, {0, 4}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {0, 2}); +} + +template<> +template<> +void object::test<32>() +{ + set_test_name("arc-segment with two interior intersections"); + + checkIntersection(_W, _E, _SW, + {-10, 10}, {10, -10}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + _NW, _SE); +} + +template<> +template<> +void object::test<33>() +{ + set_test_name("arc-vertical segment with two interior intersections"); + + checkIntersection(_W, _E, _SW, + {0, -2}, {0, 2}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + _S, _N); +} + +template<> +template<> +void object::test<34>() +{ + set_test_name("arc-segment disjoint with bbox containment"); + + checkIntersection(_W, _N, _E, + {0, 0}, {0.2, 0.2}, + CircularArcIntersector::NO_INTERSECTION); +} + +template<> +template<> +void object::test<35>() +{ + set_test_name("degenerate arc-segment with interior intersection"); + + checkIntersection({-5, -5}, {0, 0}, {5, 5}, + {-5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {0, 0}); +} + +template<> +template<> +void object::test<36>() +{ + set_test_name("intersection between a segment and a degenerate arc (radius = Infinity)"); + + checkIntersection({-5, -5}, {0, 0}, {5, 5 + 1e-14}, + {-5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{0, 0}); +} + +template<> +template<> +void object::test<37>() +{ + set_test_name("intersection between a segment and a nearly-degenerate arc (radius ~= 1e5)"); + + checkIntersection({-5, -5}, {0, 0}, {5, 5 + 1e-4}, + {-5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{0, 0}); +} + +#if 0 +// failed assertion: Values are not equal: expected `POINT (0 0)` actual `POINT (-5.4568517953157425e-06 5.4568517953157425e-06)` +template<> +template<> +void object::test<38>() +{ + set_test_name("intersection between a segment and a nearly-degenerate arc (radius ~= 2e6)"); + + checkIntersection({-5, -5}, {0, 0}, {5, 5 + 1e-9}, + {-5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{0, 0}); +} +#endif + +template<> +template<> +void object::test<38>() +{ + set_test_name("arc-segment tests from ILI validator"); + // https://github.com/claeis/iox-ili/blob/master/jtsext/src/test/java/ch/interlis/iom_j/itf/impl/hrg/ISCILRTest.java + + // test_1a + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {20, 5}, {20, -5}, + CircularArcIntersector::NO_INTERSECTION), + + // test_2a + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {5, 5}, {5, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {5, 0}); + + // test_2b + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {5, 0}); + + // test_2c + checkIntersection({0, 5}, {4, 3}, {0, -5}, + {5, 5}, {5, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {5, 0}); + + // test_2d + checkIntersection({0, 5}, {4, 3}, {0, -5}, + {5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {5, 0}); + + // test_3a + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {4, 5}, {4, -5}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + {4, 3}, {4, -3}); + + // test_3b + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {-4, 5}, {-4, -5}, + CircularArcIntersector::NO_INTERSECTION); + + // test_3c + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {4, 10}, {4, 5}, + CircularArcIntersector::NO_INTERSECTION); + + + // test_3d + checkIntersection({0, 5}, {3, 4}, {5, 0}, + {4, 5}, {4, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {4, 3}); + + // test_3e + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {4, 5}, {4, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {4, 3}); +} + +template<> +template<> +void object::test<39>() +{ + set_test_name("arc-arc tests from ILI validator"); + // https://github.com/claeis/iox-ili/blob/master/jtsext/src/test/java/ch/interlis/iom_j/itf/impl/hrg/ISCICRTest.java + + // test_1: circles do not overlap + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {20, 5}, {15, 0}, {20, -5}, + CircularArcIntersector::NO_INTERSECTION); + + // test_2a: arcs overlap at a point + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {10, 5}, {5, 0}, {10, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{5, 0}); + // test_2b: arcs overlap at a point that is not a definition point of either arc + checkIntersection({0, 5}, {4, 3}, {0, -5}, + {10, 5}, {6, 3}, {10, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{5, 0}); + + // test_3a: circles overlap at two points that are within both arcs + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {8, 5}, {3, 0}, {8, -5}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + CoordinateXY{4, 3}, CoordinateXY{4, -3}); + + // test_3b: circles overlap at two points but neither is on the first arc + checkIntersection({0, 5}, {-5, 0}, {0, -5}, + {8, 5}, {3, 0}, {8, -5}, + CircularArcIntersector::NO_INTERSECTION); + + // test_3c: circles overlap at two points but neither is on the first or second arc + checkIntersection({0, 5}, {-5, 0}, {0, -5}, + {8, 5}, {13, 0}, {8, -5}, + CircularArcIntersector::NO_INTERSECTION); + + // test_3d: circles overlap at two points but one is not on the first arc + checkIntersection({5, 0}, {3, -4}, {0, -5}, + {8, 5}, {3, 0}, {8, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{4, -3}); + + // test_3e: circles overlap at two points but one is not on the second arc + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {3, 0}, {5, -4}, {8, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{4, -3}); + + // test_4a: cocircular + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {4, 3}, {5, 0}, {4, -3}, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + Arc{{{4, 3}, {5, 0}, {4, -3}}}); +} + + + +} diff --git a/tests/unit/algorithm/CircularArcsTest.cpp b/tests/unit/algorithm/CircularArcsTest.cpp index 475261d4fb..4e4b39ff3b 100644 --- a/tests/unit/algorithm/CircularArcsTest.cpp +++ b/tests/unit/algorithm/CircularArcsTest.cpp @@ -6,6 +6,7 @@ using geos::geom::CoordinateXY; using geos::algorithm::CircularArcs; using geos::geom::Envelope; +using geos::MATH_PI; namespace tut { @@ -35,6 +36,25 @@ struct test_circulararcs_data { ensure_equals("p2-p1-p0 ymax", e.getMaxY(), ymax, eps); } } + + static std::string toWKT(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) + { + std::stringstream ss; + ss << "CIRCULARSTRING (" << p0 << ", " << p1 << ", " << p2 << ")"; + return ss.str(); + } + + void checkArc(std::string message, + const CoordinateXY& center, double radius, bool ccw, double from, double to, + const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) + { + auto [q0, q1, q2] = CircularArcs::createArc(center, radius, from, to, ccw); + + if (q0.distance(p0) > eps || q1.distance(p1) > eps || q2.distance(p2) > eps) { + ensure_equals(message, toWKT(q0, q1, q2), toWKT(p0, p1, p2)); + } + } + }; using group = test_group; @@ -188,11 +208,11 @@ void object::test<11>() -1, -1, 2, 2); } -// collinear template<> template<> void object::test<12>() { + set_test_name("envelope: arc defined by three collinear points"); CoordinateXY p0{1, 2}; CoordinateXY p1{2, 3}; @@ -202,11 +222,12 @@ void object::test<12>() 1, 2, 3, 4); } -// repeated template<> template<> void object::test<13>() { + set_test_name("envelope: arc defined by three repeated points"); + CoordinateXY p0{3, 4}; CoordinateXY p1{3, 4}; CoordinateXY p2{3, 4}; @@ -215,5 +236,25 @@ void object::test<13>() 3, 4, 3, 4); } +template<> +template<> +void object::test<14>() +{ + set_test_name("createArc"); + + constexpr bool CCW = true; + constexpr bool CW = false; + + checkArc("CCW: upper half-circle", {0, 0}, 1, CCW, 0, MATH_PI, {1, 0}, {0, 1}, {-1, 0}); + checkArc("CCW: lower half-circle", {0, 0}, 1, CCW, MATH_PI, 0, {-1, 0}, {0, -1}, {1, 0}); + checkArc("CCW: left half-circle", {0, 0}, 1, CCW, MATH_PI/2, -MATH_PI/2, {0, 1}, {-1, 0}, {0, -1}); + checkArc("CCW: right half-circle", {0, 0}, 1, CCW, -MATH_PI/2, MATH_PI/2, {0, -1}, {1, 0}, {0, 1}); + + checkArc("CW: upper half-circle", {0, 0}, 1, CW, MATH_PI, 0, {-1, 0}, {0, 1}, {1, 0}); + checkArc("CW: lower half-circle", {0, 0}, 1, CW, 0, MATH_PI, {1, 0}, {0, -1}, {-1, 0}); + checkArc("CW: left half-circle", {0, 0}, 1, CW, -MATH_PI/2, MATH_PI/2, {0, -1}, {-1, 0}, {0, 1}); + checkArc("CW: right half-circle", {0, 0}, 1, CW, MATH_PI/2, -MATH_PI/2, {0, 1}, {1, 0}, {0, -1}); +} + } diff --git a/tests/unit/geom/CircularArcTest.cpp b/tests/unit/geom/CircularArcTest.cpp index a1f7300913..024820bb64 100644 --- a/tests/unit/geom/CircularArcTest.cpp +++ b/tests/unit/geom/CircularArcTest.cpp @@ -14,7 +14,8 @@ struct test_circulararc_data { const double eps = 1e-8; - void checkAngle(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2, double expected) { + void checkAngle(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2, double expected) + { CircularArc arc(p0, p1, p2); ensure_equals(p0.toString() + " / " + p1.toString() + " / " + p2.toString(), arc.getAngle(), expected, eps); @@ -22,7 +23,8 @@ struct test_circulararc_data { ensure_equals(p2.toString() + " / " + p1.toString() + " / " + p0.toString(), rev.getAngle(), expected, eps); } - void checkLength(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2, double expected) { + void checkLength(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2, double expected) + { CircularArc arc(p0, p1, p2); ensure_equals(p0.toString() + " / " + p1.toString() + " / " + p2.toString(), arc.getLength(), expected, eps); @@ -36,11 +38,12 @@ using object = group::object; group test_circulararc_group("geos::geom::CircularArc"); -// test angle() on unit circle template<> template<> void object::test<1>() { + set_test_name("CircularArc::getAngle() on a unit circle"); + auto x = std::sqrt(2.0)/2; // full circle @@ -65,27 +68,57 @@ void object::test<1>() checkAngle({x, -x}, {-1, 0}, {x, x}, 1.5*MATH_PI); // mouth right } -// test length() template<> template<> void object::test<2>() { + set_test_name("CircularArc::getLength()"); + checkLength({1.6, 0.4}, {1.6, 0.5}, {1.7, 1}, 0.6122445326877711); } - -// test getArea() template<> template<> void object::test<3>() { + set_test_name("CircularArc::getArea()"); + ensure_equals("half circle, R=2", CircularArc({-2, 0}, {0, 2}, {2, 0}).getArea(), MATH_PI*2); ensure_equals("full circle, R=3", CircularArc({-3, 0}, {3, 0}, {-3, 0}).getArea(), MATH_PI*3*3); - ensure_equals("3/4, mouth up, R=2", CircularArc({-std::sqrt(2), std::sqrt(2)}, {0, -2}, {std::sqrt(2), std::sqrt(2)}).getArea(), MATH_PI*4 - 2*(MATH_PI/2-1), 1e-8); + ensure_equals("3/4, mouth up, R=2", CircularArc({-std::sqrt(2), std::sqrt(2)}, {0, -2}, {std::sqrt(2), std::sqrt(2)}).getArea(), + MATH_PI*4 - 2*(MATH_PI/2-1), 1e-8); + + ensure_equals("1/4, pointing up, R=2", CircularArc({-std::sqrt(2), std::sqrt(2)}, {0, 2}, {std::sqrt(2), std::sqrt(2)}).getArea(), + 2*(MATH_PI/2-1), 1e-8); +} + +template<> +template<> +void object::test<4>() +{ + set_test_name("CircularArc::isLinear()"); + + ensure_equals("not linear", CircularArc({-1, 0}, {0, 1}, {1, 0}).isLinear(), false); + ensure_equals("linear", CircularArc({0, 0}, {1, 1}, {2, 2}).isLinear(), true); +} + +template<> +template<> +void object::test<5>() +{ + set_test_name("CircularArc::containsPointOnCircle"); + + // complete circle + CircularArc({5, 0}, {-5, 0}, {5, 0}).containsPointOnCircle({5, 0}); + CircularArc({5, 0}, {-5, 0}, {5, 0}).containsPointOnCircle({4, 3}); + + // lower semi-circle + CircularArc({-5, 0}, {0, -5}, {5, 0}).containsPointOnCircle({5, 0}); - ensure_equals("1/4, pointing up, R=2", CircularArc({-std::sqrt(2), std::sqrt(2)}, {0, 2}, {std::sqrt(2), std::sqrt(2)}).getArea(), 2*(MATH_PI/2-1), 1e-8); + // upper semi-circle + CircularArc({-5, 0}, {0, 5}, {5, 0}).containsPointOnCircle({5, 0}); } } diff --git a/tests/unit/utility.h b/tests/unit/utility.h index d0311655c8..2bc5164ee8 100644 --- a/tests/unit/utility.h +++ b/tests/unit/utility.h @@ -97,16 +97,16 @@ instanceOf(InstanceType const* instance) } inline void -ensure_equals_xy(geos::geom::Coordinate const& actual, - geos::geom::Coordinate const& expected) +ensure_equals_xy(geos::geom::CoordinateXY const& actual, + geos::geom::CoordinateXY const& expected) { ensure_equals("Coordinate X", actual.x, expected.x ); ensure_equals("Coordinate Y", actual.y, expected.y ); } inline void -ensure_equals_xy(geos::geom::Coordinate const& actual, - geos::geom::Coordinate const& expected, +ensure_equals_xy(geos::geom::CoordinateXY const& actual, + geos::geom::CoordinateXY const& expected, double tol) { ensure_equals("Coordinate X", actual.x, expected.x, tol );