/********************************************************************** * * 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 { namespace geom { /// A CircularArc is a reference to three points that define a circular arc. /// It provides for the lazy calculation of various arc properties such as the center, radius, and orientation class GEOS_DLL CircularArc { public: using CoordinateXY = geom::CoordinateXY; CircularArc(const CoordinateXY& q0, const CoordinateXY& q1, const CoordinateXY& q2) : p0(q0) , p1(q1) , p2(q2) , m_center_known(false) , m_radius_known(false) , m_orientation_known(false) {} const CoordinateXY& p0; const CoordinateXY& p1; const CoordinateXY& p2; /// Return the orientation of the arc as one of: /// - algorithm::Orientation::CLOCKWISE, /// - algorithm::Orientation::COUNTERCLOCKWISE /// - algorithm::Orientation::COLLINEAR int orientation() const { if (!m_orientation_known) { m_orientation = algorithm::Orientation::index(p0, p1, p2); m_orientation_known = true; } return m_orientation; } /// Return the center point of the circle associated with this arc const CoordinateXY& getCenter() const { if (!m_center_known) { m_center = algorithm::CircularArcs::getCenter(p0, p1, p2); m_center_known = true; } return m_center; } /// Return the radius of the circle associated with this arc double getRadius() const { if (!m_radius_known) { m_radius = getCenter().distance(p0); m_radius_known = true; } return m_radius; } /// Return whether this arc forms a complete circle bool isCircle() const { return p0.equals(p2); } /// Returns whether this arc forms a straight line (p0, p1, and p2 are collinear) bool isLinear() const { return std::isnan(getRadius()); } /// Return the inner angle of the sector associated with this arc double getAngle() const { if (isCircle()) { return 2*MATH_PI; } /// Even Rouault: /// potential optimization?: using crossproduct(p0 - center, p2 - center) = radius * radius * sin(angle) /// could yield the result by just doing a single asin(), instead of 2 atan2() /// actually one should also likely compute dotproduct(p0 - center, p2 - center) = radius * radius * cos(angle), /// and thus angle = atan2(crossproduct(p0 - center, p2 - center) , dotproduct(p0 - center, p2 - center) ) auto t0 = theta0(); auto t2 = theta2(); if (orientation() == algorithm::Orientation::COUNTERCLOCKWISE) { std::swap(t0, t2); } if (t0 < t2) { t0 += 2*MATH_PI; } auto diff = t0-t2; return diff; } /// Return the length of the arc double getLength() const { if (isLinear()) { return p0.distance(p2); } return getAngle()*getRadius(); } /// Return the area enclosed by the arc p0-p1-p2 and the line segment p2-p0 double getArea() const { if (isLinear()) { return 0; } auto R = getRadius(); auto theta = getAngle(); return R*R/2*(theta - std::sin(theta)); } /// Return the angle of p0 double theta0() const { return std::atan2(p0.y - getCenter().y, p0.x - getCenter().x); } /// Return the angle of p2 double theta2() const { return std::atan2(p2.y - getCenter().y, p2.x - getCenter().x); } /// Check to see if a coordinate lies on the arc /// Only the angle is checked, so it is assumed that the point lies on /// the circle of which this arc is a part. bool containsPointOnCircle(const CoordinateXY& q) const { double theta = std::atan2(q.y - getCenter().y, q.x - getCenter().x); return containsAngle(theta); } /// Check to see if a coordinate lies on the arc, after testing whether /// it lies on the circle. bool containsPoint(const CoordinateXY& q) { if (q == p0 || q == p1 || q == p2) { return true; } auto dist = std::abs(q.distance(getCenter()) - getRadius()); if (dist > 1e-8) { return false; } if (triangulate::quadedge::TrianglePredicate::isInCircleNormalized(p0, p1, p2, q) != geom::Location::BOUNDARY) { return false; } return containsPointOnCircle(q); } /// Check to see if a given angle lies on this arc bool containsAngle(double theta) const { auto t0 = theta0(); auto t2 = theta2(); if (theta == t0 || theta == t2) { return true; } if (orientation() == algorithm::Orientation::COUNTERCLOCKWISE) { std::swap(t0, t2); } t2 -= t0; theta -= t0; if (t2 < 0) { t2 += 2*MATH_PI; } if (theta < 0) { theta += 2*MATH_PI; } return theta >= t2; } /// Return true if the arc is pointing positive in the y direction /// at the location of a specified point. The point is assumed to /// be on the arc. bool isUpwardAtPoint(const CoordinateXY& q) const { auto quad = geom::Quadrant::quadrant(getCenter(), q); bool isUpward; if (orientation() == algorithm::Orientation::CLOCKWISE) { isUpward = (quad == geom::Quadrant::SW || quad == geom::Quadrant::NW); } else { isUpward = (quad == geom::Quadrant::SE || quad == geom::Quadrant::NE); } return isUpward; } class Iterator { public: using iterator_category = std::forward_iterator_tag; using difference_type = std::ptrdiff_t; using value_type = geom::CoordinateXY; using pointer = const geom::CoordinateXY*; using reference = const geom::CoordinateXY&; Iterator(const CircularArc& arc, int i) : m_arc(arc), m_i(i) {} reference operator*() const { return m_i == 0 ? m_arc.p0 : (m_i == 1 ? m_arc.p1 : m_arc.p2); } Iterator& operator++() { m_i++; return *this; } Iterator operator++(int) { Iterator ret = *this; m_i++; return ret; } bool operator==(const Iterator& other) const { return m_i == other.m_i; } bool operator!=(const Iterator& other) const { return !(*this == other); } private: const CircularArc& m_arc; int m_i; }; Iterator begin() const { return Iterator(*this, 0); } Iterator end() const { return Iterator(*this, 3); } private: mutable CoordinateXY m_center; mutable double m_radius; mutable int m_orientation; mutable bool m_center_known = false; mutable bool m_radius_known = false; mutable bool m_orientation_known = false; }; } }