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