123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- /**
- * \file PolygonArea.hpp
- * \brief Header for GeographicLib::PolygonAreaT class
- *
- * Copyright (c) Charles Karney (2010-2021) <charles@karney.com> and licensed
- * under the MIT/X11 License. For more information, see
- * https://geographiclib.sourceforge.io/
- **********************************************************************/
- #if !defined(GEOGRAPHICLIB_POLYGONAREA_HPP)
- #define GEOGRAPHICLIB_POLYGONAREA_HPP 1
- #include <GeographicLib/Geodesic.hpp>
- #include <GeographicLib/GeodesicExact.hpp>
- #include <GeographicLib/Rhumb.hpp>
- #include <GeographicLib/Accumulator.hpp>
- namespace GeographicLib {
- /**
- * \brief Polygon areas
- *
- * This computes the area of a polygon whose edges are geodesics using the
- * method given in Section 6 of
- * - C. F. F. Karney,
- * <a href="https://doi.org/10.1007/s00190-012-0578-z">
- * Algorithms for geodesics</a>,
- * J. Geodesy <b>87</b>, 43--55 (2013);
- * DOI: <a href="https://doi.org/10.1007/s00190-012-0578-z">
- * 10.1007/s00190-012-0578-z</a>;
- * addenda:
- * <a href="https://geographiclib.sourceforge.io/geod-addenda.html">
- * geod-addenda.html</a>.
- *
- * Arbitrarily complex polygons are allowed. In the case self-intersecting
- * of polygons the area is accumulated "algebraically", e.g., the areas of
- * the 2 loops in a figure-8 polygon will partially cancel.
- *
- * This class lets you add vertices and edges one at a time to the polygon.
- * The sequence must start with a vertex and thereafter vertices and edges
- * can be added in any order. Any vertex after the first creates a new edge
- * which is the \e shortest geodesic from the previous vertex. In some
- * cases there may be two or many such shortest geodesics and the area is
- * then not uniquely defined. In this case, either add an intermediate
- * vertex or add the edge \e as an edge (by defining its direction and
- * length).
- *
- * The area and perimeter are accumulated at two times the standard floating
- * point precision to guard against the loss of accuracy with many-sided
- * polygons. At any point you can ask for the perimeter and area so far.
- * There's an option to treat the points as defining a polyline instead of a
- * polygon; in that case, only the perimeter is computed.
- *
- * This is a templated class to allow it to be used with Geodesic,
- * GeodesicExact, and Rhumb. GeographicLib::PolygonArea,
- * GeographicLib::PolygonAreaExact, and GeographicLib::PolygonAreaRhumb are
- * typedefs for these cases.
- *
- * For GeographicLib::PolygonArea (edges defined by Geodesic), an upper bound
- * on the error is about 0.1 m<sup>2</sup> per vertex. However this is a
- * wildly pessimistic estimate in most cases. A more realistic estimate of
- * the error is given by a test involving 10<sup>7</sup> approximately
- * regular polygons on the WGS84 ellipsoid. The centers and the orientations
- * of the polygons were uniformly distributed, the number of vertices was
- * log-uniformly distributed in [3, 300], and the center to vertex distance
- * log-uniformly distributed in [0.1 m, 9000 km].
- *
- * Using double precision (the standard precision for GeographicLib), the
- * maximum error in the perimeter was 200 nm, and the maximum error in the
- * area was<pre>
- * 0.0013 m^2 for perimeter < 10 km
- * 0.0070 m^2 for perimeter < 100 km
- * 0.070 m^2 for perimeter < 1000 km
- * 0.11 m^2 for all perimeters
- * </pre>
- * The errors are given in terms of the perimeter, because it is expected
- * that the errors depend mainly on the number of edges and the edge lengths.
- *
- * Using long doubles (GEOGRPAHICLIB_PRECISION = 3), the maximum error in the
- * perimeter was 200 pm, and the maximum error in the area was<pre>
- * 0.7 mm^2 for perim < 10 km
- * 3.2 mm^2 for perimeter < 100 km
- * 21 mm^2 for perimeter < 1000 km
- * 45 mm^2 for all perimeters
- * </pre>
- *
- * @tparam GeodType the geodesic class to use.
- *
- * Example of use:
- * \include example-PolygonArea.cpp
- *
- * <a href="Planimeter.1.html">Planimeter</a> is a command-line utility
- * providing access to the functionality of PolygonAreaT.
- **********************************************************************/
- template <class GeodType = Geodesic>
- class PolygonAreaT {
- private:
- typedef Math::real real;
- GeodType _earth;
- real _area0; // Full ellipsoid area
- bool _polyline; // Assume polyline (don't close and skip area)
- unsigned _mask;
- unsigned _num;
- int _crossings;
- Accumulator<> _areasum, _perimetersum;
- real _lat0, _lon0, _lat1, _lon1;
- static int transit(real lon1, real lon2) {
- // Return 1 or -1 if crossing prime meridian in east or west direction.
- // Otherwise return zero.
- // Compute lon12 the same way as Geodesic::Inverse.
- lon1 = Math::AngNormalize(lon1);
- lon2 = Math::AngNormalize(lon2);
- real lon12 = Math::AngDiff(lon1, lon2);
- // Treat 0 as negative in these tests. This balances +/- 180 being
- // treated as positive, i.e., +180.
- int cross =
- lon1 <= 0 && lon2 > 0 && lon12 > 0 ? 1 :
- (lon2 <= 0 && lon1 > 0 && lon12 < 0 ? -1 : 0);
- return cross;
- }
- // an alternate version of transit to deal with longitudes in the direct
- // problem.
- static int transitdirect(real lon1, real lon2) {
- // Compute exactly the parity of
- // int(ceil(lon2 / 360)) - int(ceil(lon1 / 360))
- using std::remainder;
- lon1 = remainder(lon1, real(720));
- lon2 = remainder(lon2, real(720));
- return ( (lon2 <= 0 && lon2 > -360 ? 1 : 0) -
- (lon1 <= 0 && lon1 > -360 ? 1 : 0) );
- }
- void Remainder(Accumulator<>& a) const { a.remainder(_area0); }
- void Remainder(real& a) const {
- using std::remainder;
- a = remainder(a, _area0);
- }
- template <typename T>
- void AreaReduce(T& area, int crossings, bool reverse, bool sign) const;
- public:
- /**
- * Constructor for PolygonAreaT.
- *
- * @param[in] earth the Geodesic object to use for geodesic calculations.
- * @param[in] polyline if true that treat the points as defining a polyline
- * instead of a polygon (default = false).
- **********************************************************************/
- PolygonAreaT(const GeodType& earth, bool polyline = false)
- : _earth(earth)
- , _area0(_earth.EllipsoidArea())
- , _polyline(polyline)
- , _mask(GeodType::LATITUDE | GeodType::LONGITUDE | GeodType::DISTANCE |
- (_polyline ? GeodType::NONE :
- GeodType::AREA | GeodType::LONG_UNROLL))
- { Clear(); }
- /**
- * Clear PolygonAreaT, allowing a new polygon to be started.
- **********************************************************************/
- void Clear() {
- _num = 0;
- _crossings = 0;
- _areasum = 0;
- _perimetersum = 0;
- _lat0 = _lon0 = _lat1 = _lon1 = Math::NaN();
- }
- /**
- * Add a point to the polygon or polyline.
- *
- * @param[in] lat the latitude of the point (degrees).
- * @param[in] lon the longitude of the point (degrees).
- *
- * \e lat should be in the range [−90°, 90°].
- **********************************************************************/
- void AddPoint(real lat, real lon);
- /**
- * Add an edge to the polygon or polyline.
- *
- * @param[in] azi azimuth at current point (degrees).
- * @param[in] s distance from current point to next point (meters).
- *
- * This does nothing if no points have been added yet. Use
- * PolygonAreaT::CurrentPoint to determine the position of the new vertex.
- **********************************************************************/
- void AddEdge(real azi, real s);
- /**
- * Return the results so far.
- *
- * @param[in] reverse if true then clockwise (instead of counter-clockwise)
- * traversal counts as a positive area.
- * @param[in] sign if true then return a signed result for the area if
- * the polygon is traversed in the "wrong" direction instead of returning
- * the area for the rest of the earth.
- * @param[out] perimeter the perimeter of the polygon or length of the
- * polyline (meters).
- * @param[out] area the area of the polygon (meters<sup>2</sup>); only set
- * if \e polyline is false in the constructor.
- * @return the number of points.
- *
- * More points can be added to the polygon after this call.
- **********************************************************************/
- unsigned Compute(bool reverse, bool sign,
- real& perimeter, real& area) const;
- /**
- * Return the results assuming a tentative final test point is added;
- * however, the data for the test point is not saved. This lets you report
- * a running result for the perimeter and area as the user moves the mouse
- * cursor. Ordinary floating point arithmetic is used to accumulate the
- * data for the test point; thus the area and perimeter returned are less
- * accurate than if PolygonAreaT::AddPoint and PolygonAreaT::Compute are
- * used.
- *
- * @param[in] lat the latitude of the test point (degrees).
- * @param[in] lon the longitude of the test point (degrees).
- * @param[in] reverse if true then clockwise (instead of counter-clockwise)
- * traversal counts as a positive area.
- * @param[in] sign if true then return a signed result for the area if
- * the polygon is traversed in the "wrong" direction instead of returning
- * the area for the rest of the earth.
- * @param[out] perimeter the approximate perimeter of the polygon or length
- * of the polyline (meters).
- * @param[out] area the approximate area of the polygon
- * (meters<sup>2</sup>); only set if polyline is false in the
- * constructor.
- * @return the number of points.
- *
- * \e lat should be in the range [−90°, 90°].
- **********************************************************************/
- unsigned TestPoint(real lat, real lon, bool reverse, bool sign,
- real& perimeter, real& area) const;
- /**
- * Return the results assuming a tentative final test point is added via an
- * azimuth and distance; however, the data for the test point is not saved.
- * This lets you report a running result for the perimeter and area as the
- * user moves the mouse cursor. Ordinary floating point arithmetic is used
- * to accumulate the data for the test point; thus the area and perimeter
- * returned are less accurate than if PolygonAreaT::AddEdge and
- * PolygonAreaT::Compute are used.
- *
- * @param[in] azi azimuth at current point (degrees).
- * @param[in] s distance from current point to final test point (meters).
- * @param[in] reverse if true then clockwise (instead of counter-clockwise)
- * traversal counts as a positive area.
- * @param[in] sign if true then return a signed result for the area if
- * the polygon is traversed in the "wrong" direction instead of returning
- * the area for the rest of the earth.
- * @param[out] perimeter the approximate perimeter of the polygon or length
- * of the polyline (meters).
- * @param[out] area the approximate area of the polygon
- * (meters<sup>2</sup>); only set if polyline is false in the
- * constructor.
- * @return the number of points.
- **********************************************************************/
- unsigned TestEdge(real azi, real s, bool reverse, bool sign,
- real& perimeter, real& area) const;
- /** \name Inspector functions
- **********************************************************************/
- ///@{
- /**
- * @return \e a the equatorial radius of the ellipsoid (meters). This is
- * the value inherited from the Geodesic object used in the constructor.
- **********************************************************************/
- Math::real EquatorialRadius() const { return _earth.EquatorialRadius(); }
- /**
- * @return \e f the flattening of the ellipsoid. This is the value
- * inherited from the Geodesic object used in the constructor.
- **********************************************************************/
- Math::real Flattening() const { return _earth.Flattening(); }
- /**
- * Report the previous vertex added to the polygon or polyline.
- *
- * @param[out] lat the latitude of the point (degrees).
- * @param[out] lon the longitude of the point (degrees).
- *
- * If no points have been added, then NaNs are returned. Otherwise, \e lon
- * will be in the range [−180°, 180°].
- **********************************************************************/
- void CurrentPoint(real& lat, real& lon) const
- { lat = _lat1; lon = _lon1; }
- /**
- * \deprecated An old name for EquatorialRadius().
- **********************************************************************/
- GEOGRAPHICLIB_DEPRECATED("Use EquatorialRadius()")
- Math::real MajorRadius() const { return EquatorialRadius(); }
- ///@}
- };
- /**
- * @relates PolygonAreaT
- *
- * Polygon areas using Geodesic. This should be used if the flattening is
- * small.
- **********************************************************************/
- typedef PolygonAreaT<Geodesic> PolygonArea;
- /**
- * @relates PolygonAreaT
- *
- * Polygon areas using GeodesicExact. (But note that the implementation of
- * areas in GeodesicExact uses a high order series and this is only accurate
- * for modest flattenings.)
- **********************************************************************/
- typedef PolygonAreaT<GeodesicExact> PolygonAreaExact;
- /**
- * @relates PolygonAreaT
- *
- * Polygon areas using Rhumb.
- **********************************************************************/
- typedef PolygonAreaT<Rhumb> PolygonAreaRhumb;
- } // namespace GeographicLib
- #endif // GEOGRAPHICLIB_POLYGONAREA_HPP
|