PolygonArea.hpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /**
  2. * \file PolygonArea.hpp
  3. * \brief Header for GeographicLib::PolygonAreaT class
  4. *
  5. * Copyright (c) Charles Karney (2010-2021) <charles@karney.com> and licensed
  6. * under the MIT/X11 License. For more information, see
  7. * https://geographiclib.sourceforge.io/
  8. **********************************************************************/
  9. #if !defined(GEOGRAPHICLIB_POLYGONAREA_HPP)
  10. #define GEOGRAPHICLIB_POLYGONAREA_HPP 1
  11. #include <GeographicLib/Geodesic.hpp>
  12. #include <GeographicLib/GeodesicExact.hpp>
  13. #include <GeographicLib/Rhumb.hpp>
  14. #include <GeographicLib/Accumulator.hpp>
  15. namespace GeographicLib {
  16. /**
  17. * \brief Polygon areas
  18. *
  19. * This computes the area of a polygon whose edges are geodesics using the
  20. * method given in Section 6 of
  21. * - C. F. F. Karney,
  22. * <a href="https://doi.org/10.1007/s00190-012-0578-z">
  23. * Algorithms for geodesics</a>,
  24. * J. Geodesy <b>87</b>, 43--55 (2013);
  25. * DOI: <a href="https://doi.org/10.1007/s00190-012-0578-z">
  26. * 10.1007/s00190-012-0578-z</a>;
  27. * addenda:
  28. * <a href="https://geographiclib.sourceforge.io/geod-addenda.html">
  29. * geod-addenda.html</a>.
  30. *
  31. * Arbitrarily complex polygons are allowed. In the case self-intersecting
  32. * of polygons the area is accumulated "algebraically", e.g., the areas of
  33. * the 2 loops in a figure-8 polygon will partially cancel.
  34. *
  35. * This class lets you add vertices and edges one at a time to the polygon.
  36. * The sequence must start with a vertex and thereafter vertices and edges
  37. * can be added in any order. Any vertex after the first creates a new edge
  38. * which is the \e shortest geodesic from the previous vertex. In some
  39. * cases there may be two or many such shortest geodesics and the area is
  40. * then not uniquely defined. In this case, either add an intermediate
  41. * vertex or add the edge \e as an edge (by defining its direction and
  42. * length).
  43. *
  44. * The area and perimeter are accumulated at two times the standard floating
  45. * point precision to guard against the loss of accuracy with many-sided
  46. * polygons. At any point you can ask for the perimeter and area so far.
  47. * There's an option to treat the points as defining a polyline instead of a
  48. * polygon; in that case, only the perimeter is computed.
  49. *
  50. * This is a templated class to allow it to be used with Geodesic,
  51. * GeodesicExact, and Rhumb. GeographicLib::PolygonArea,
  52. * GeographicLib::PolygonAreaExact, and GeographicLib::PolygonAreaRhumb are
  53. * typedefs for these cases.
  54. *
  55. * For GeographicLib::PolygonArea (edges defined by Geodesic), an upper bound
  56. * on the error is about 0.1 m<sup>2</sup> per vertex. However this is a
  57. * wildly pessimistic estimate in most cases. A more realistic estimate of
  58. * the error is given by a test involving 10<sup>7</sup> approximately
  59. * regular polygons on the WGS84 ellipsoid. The centers and the orientations
  60. * of the polygons were uniformly distributed, the number of vertices was
  61. * log-uniformly distributed in [3, 300], and the center to vertex distance
  62. * log-uniformly distributed in [0.1 m, 9000 km].
  63. *
  64. * Using double precision (the standard precision for GeographicLib), the
  65. * maximum error in the perimeter was 200 nm, and the maximum error in the
  66. * area was<pre>
  67. * 0.0013 m^2 for perimeter < 10 km
  68. * 0.0070 m^2 for perimeter < 100 km
  69. * 0.070 m^2 for perimeter < 1000 km
  70. * 0.11 m^2 for all perimeters
  71. * </pre>
  72. * The errors are given in terms of the perimeter, because it is expected
  73. * that the errors depend mainly on the number of edges and the edge lengths.
  74. *
  75. * Using long doubles (GEOGRPAHICLIB_PRECISION = 3), the maximum error in the
  76. * perimeter was 200 pm, and the maximum error in the area was<pre>
  77. * 0.7 mm^2 for perim < 10 km
  78. * 3.2 mm^2 for perimeter < 100 km
  79. * 21 mm^2 for perimeter < 1000 km
  80. * 45 mm^2 for all perimeters
  81. * </pre>
  82. *
  83. * @tparam GeodType the geodesic class to use.
  84. *
  85. * Example of use:
  86. * \include example-PolygonArea.cpp
  87. *
  88. * <a href="Planimeter.1.html">Planimeter</a> is a command-line utility
  89. * providing access to the functionality of PolygonAreaT.
  90. **********************************************************************/
  91. template <class GeodType = Geodesic>
  92. class PolygonAreaT {
  93. private:
  94. typedef Math::real real;
  95. GeodType _earth;
  96. real _area0; // Full ellipsoid area
  97. bool _polyline; // Assume polyline (don't close and skip area)
  98. unsigned _mask;
  99. unsigned _num;
  100. int _crossings;
  101. Accumulator<> _areasum, _perimetersum;
  102. real _lat0, _lon0, _lat1, _lon1;
  103. static int transit(real lon1, real lon2) {
  104. // Return 1 or -1 if crossing prime meridian in east or west direction.
  105. // Otherwise return zero.
  106. // Compute lon12 the same way as Geodesic::Inverse.
  107. lon1 = Math::AngNormalize(lon1);
  108. lon2 = Math::AngNormalize(lon2);
  109. real lon12 = Math::AngDiff(lon1, lon2);
  110. // Treat 0 as negative in these tests. This balances +/- 180 being
  111. // treated as positive, i.e., +180.
  112. int cross =
  113. lon1 <= 0 && lon2 > 0 && lon12 > 0 ? 1 :
  114. (lon2 <= 0 && lon1 > 0 && lon12 < 0 ? -1 : 0);
  115. return cross;
  116. }
  117. // an alternate version of transit to deal with longitudes in the direct
  118. // problem.
  119. static int transitdirect(real lon1, real lon2) {
  120. // Compute exactly the parity of
  121. // int(ceil(lon2 / 360)) - int(ceil(lon1 / 360))
  122. using std::remainder;
  123. lon1 = remainder(lon1, real(720));
  124. lon2 = remainder(lon2, real(720));
  125. return ( (lon2 <= 0 && lon2 > -360 ? 1 : 0) -
  126. (lon1 <= 0 && lon1 > -360 ? 1 : 0) );
  127. }
  128. void Remainder(Accumulator<>& a) const { a.remainder(_area0); }
  129. void Remainder(real& a) const {
  130. using std::remainder;
  131. a = remainder(a, _area0);
  132. }
  133. template <typename T>
  134. void AreaReduce(T& area, int crossings, bool reverse, bool sign) const;
  135. public:
  136. /**
  137. * Constructor for PolygonAreaT.
  138. *
  139. * @param[in] earth the Geodesic object to use for geodesic calculations.
  140. * @param[in] polyline if true that treat the points as defining a polyline
  141. * instead of a polygon (default = false).
  142. **********************************************************************/
  143. PolygonAreaT(const GeodType& earth, bool polyline = false)
  144. : _earth(earth)
  145. , _area0(_earth.EllipsoidArea())
  146. , _polyline(polyline)
  147. , _mask(GeodType::LATITUDE | GeodType::LONGITUDE | GeodType::DISTANCE |
  148. (_polyline ? GeodType::NONE :
  149. GeodType::AREA | GeodType::LONG_UNROLL))
  150. { Clear(); }
  151. /**
  152. * Clear PolygonAreaT, allowing a new polygon to be started.
  153. **********************************************************************/
  154. void Clear() {
  155. _num = 0;
  156. _crossings = 0;
  157. _areasum = 0;
  158. _perimetersum = 0;
  159. _lat0 = _lon0 = _lat1 = _lon1 = Math::NaN();
  160. }
  161. /**
  162. * Add a point to the polygon or polyline.
  163. *
  164. * @param[in] lat the latitude of the point (degrees).
  165. * @param[in] lon the longitude of the point (degrees).
  166. *
  167. * \e lat should be in the range [&minus;90&deg;, 90&deg;].
  168. **********************************************************************/
  169. void AddPoint(real lat, real lon);
  170. /**
  171. * Add an edge to the polygon or polyline.
  172. *
  173. * @param[in] azi azimuth at current point (degrees).
  174. * @param[in] s distance from current point to next point (meters).
  175. *
  176. * This does nothing if no points have been added yet. Use
  177. * PolygonAreaT::CurrentPoint to determine the position of the new vertex.
  178. **********************************************************************/
  179. void AddEdge(real azi, real s);
  180. /**
  181. * Return the results so far.
  182. *
  183. * @param[in] reverse if true then clockwise (instead of counter-clockwise)
  184. * traversal counts as a positive area.
  185. * @param[in] sign if true then return a signed result for the area if
  186. * the polygon is traversed in the "wrong" direction instead of returning
  187. * the area for the rest of the earth.
  188. * @param[out] perimeter the perimeter of the polygon or length of the
  189. * polyline (meters).
  190. * @param[out] area the area of the polygon (meters<sup>2</sup>); only set
  191. * if \e polyline is false in the constructor.
  192. * @return the number of points.
  193. *
  194. * More points can be added to the polygon after this call.
  195. **********************************************************************/
  196. unsigned Compute(bool reverse, bool sign,
  197. real& perimeter, real& area) const;
  198. /**
  199. * Return the results assuming a tentative final test point is added;
  200. * however, the data for the test point is not saved. This lets you report
  201. * a running result for the perimeter and area as the user moves the mouse
  202. * cursor. Ordinary floating point arithmetic is used to accumulate the
  203. * data for the test point; thus the area and perimeter returned are less
  204. * accurate than if PolygonAreaT::AddPoint and PolygonAreaT::Compute are
  205. * used.
  206. *
  207. * @param[in] lat the latitude of the test point (degrees).
  208. * @param[in] lon the longitude of the test point (degrees).
  209. * @param[in] reverse if true then clockwise (instead of counter-clockwise)
  210. * traversal counts as a positive area.
  211. * @param[in] sign if true then return a signed result for the area if
  212. * the polygon is traversed in the "wrong" direction instead of returning
  213. * the area for the rest of the earth.
  214. * @param[out] perimeter the approximate perimeter of the polygon or length
  215. * of the polyline (meters).
  216. * @param[out] area the approximate area of the polygon
  217. * (meters<sup>2</sup>); only set if polyline is false in the
  218. * constructor.
  219. * @return the number of points.
  220. *
  221. * \e lat should be in the range [&minus;90&deg;, 90&deg;].
  222. **********************************************************************/
  223. unsigned TestPoint(real lat, real lon, bool reverse, bool sign,
  224. real& perimeter, real& area) const;
  225. /**
  226. * Return the results assuming a tentative final test point is added via an
  227. * azimuth and distance; however, the data for the test point is not saved.
  228. * This lets you report a running result for the perimeter and area as the
  229. * user moves the mouse cursor. Ordinary floating point arithmetic is used
  230. * to accumulate the data for the test point; thus the area and perimeter
  231. * returned are less accurate than if PolygonAreaT::AddEdge and
  232. * PolygonAreaT::Compute are used.
  233. *
  234. * @param[in] azi azimuth at current point (degrees).
  235. * @param[in] s distance from current point to final test point (meters).
  236. * @param[in] reverse if true then clockwise (instead of counter-clockwise)
  237. * traversal counts as a positive area.
  238. * @param[in] sign if true then return a signed result for the area if
  239. * the polygon is traversed in the "wrong" direction instead of returning
  240. * the area for the rest of the earth.
  241. * @param[out] perimeter the approximate perimeter of the polygon or length
  242. * of the polyline (meters).
  243. * @param[out] area the approximate area of the polygon
  244. * (meters<sup>2</sup>); only set if polyline is false in the
  245. * constructor.
  246. * @return the number of points.
  247. **********************************************************************/
  248. unsigned TestEdge(real azi, real s, bool reverse, bool sign,
  249. real& perimeter, real& area) const;
  250. /** \name Inspector functions
  251. **********************************************************************/
  252. ///@{
  253. /**
  254. * @return \e a the equatorial radius of the ellipsoid (meters). This is
  255. * the value inherited from the Geodesic object used in the constructor.
  256. **********************************************************************/
  257. Math::real EquatorialRadius() const { return _earth.EquatorialRadius(); }
  258. /**
  259. * @return \e f the flattening of the ellipsoid. This is the value
  260. * inherited from the Geodesic object used in the constructor.
  261. **********************************************************************/
  262. Math::real Flattening() const { return _earth.Flattening(); }
  263. /**
  264. * Report the previous vertex added to the polygon or polyline.
  265. *
  266. * @param[out] lat the latitude of the point (degrees).
  267. * @param[out] lon the longitude of the point (degrees).
  268. *
  269. * If no points have been added, then NaNs are returned. Otherwise, \e lon
  270. * will be in the range [&minus;180&deg;, 180&deg;].
  271. **********************************************************************/
  272. void CurrentPoint(real& lat, real& lon) const
  273. { lat = _lat1; lon = _lon1; }
  274. /**
  275. * \deprecated An old name for EquatorialRadius().
  276. **********************************************************************/
  277. GEOGRAPHICLIB_DEPRECATED("Use EquatorialRadius()")
  278. Math::real MajorRadius() const { return EquatorialRadius(); }
  279. ///@}
  280. };
  281. /**
  282. * @relates PolygonAreaT
  283. *
  284. * Polygon areas using Geodesic. This should be used if the flattening is
  285. * small.
  286. **********************************************************************/
  287. typedef PolygonAreaT<Geodesic> PolygonArea;
  288. /**
  289. * @relates PolygonAreaT
  290. *
  291. * Polygon areas using GeodesicExact. (But note that the implementation of
  292. * areas in GeodesicExact uses a high order series and this is only accurate
  293. * for modest flattenings.)
  294. **********************************************************************/
  295. typedef PolygonAreaT<GeodesicExact> PolygonAreaExact;
  296. /**
  297. * @relates PolygonAreaT
  298. *
  299. * Polygon areas using Rhumb.
  300. **********************************************************************/
  301. typedef PolygonAreaT<Rhumb> PolygonAreaRhumb;
  302. } // namespace GeographicLib
  303. #endif // GEOGRAPHICLIB_POLYGONAREA_HPP