GeographicLib 1.52
Loading...
Searching...
No Matches
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
17
18namespace 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>
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