GeographicLib 2.1.2
MGRS.hpp
Go to the documentation of this file.
1/**
2 * \file MGRS.hpp
3 * \brief Header for GeographicLib::MGRS class
4 *
5 * Copyright (c) Charles Karney (2008-2022) <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_MGRS_HPP)
11#define GEOGRAPHICLIB_MGRS_HPP 1
12
15
16#if defined(_MSC_VER)
17// Squelch warnings about dll vs string
18# pragma warning (push)
19# pragma warning (disable: 4251)
20#endif
21
22namespace GeographicLib {
23
24 /**
25 * \brief Convert between UTM/UPS and %MGRS
26 *
27 * MGRS is defined in Chapter 3 of
28 * - J. W. Hager, L. L. Fry, S. S. Jacks, D. R. Hill,
29 * <a href="https://web.archive.org/web/20161214054445/http://earth-info.nga.mil/GandG/publications/tm8358.1/pdf/TM8358_1.pdf">
30 * Datums, Ellipsoids, Grids, and Grid Reference Systems</a>,
31 * Defense Mapping Agency, Technical Manual TM8358.1 (1990).
32 * .
33 * This document has been updated by the two NGA documents
34 * - <a href="https://earth-info.nga.mil/php/download.php?file=coord-grids">
35 * Universal Grids and Grid Reference Systems</a>,
36 * NGA.STND.0037 (2014).
37 * - <a href="https://earth-info.nga.mil/php/download.php?file=coord-utmups">
38 * The Universal Grids and the Transverse Mercator and Polar Stereographic
39 * Map Projections</a>, NGA.SIG.0012 (2014).
40 *
41 * This implementation has the following properties:
42 * - The conversions are closed, i.e., output from Forward is legal input for
43 * Reverse and vice versa. Conversion in both directions preserve the
44 * UTM/UPS selection and the UTM zone.
45 * - Forward followed by Reverse and vice versa is approximately the
46 * identity. (This is affected in predictable ways by errors in
47 * determining the latitude band and by loss of precision in the MGRS
48 * coordinates.)
49 * - The trailing digits produced by Forward are consistent as the precision
50 * is varied. Specifically, the digits are obtained by operating on the
51 * easting with &lfloor;10<sup>6</sup> <i>x</i>&rfloor; and extracting the
52 * required digits from the resulting number (and similarly for the
53 * northing).
54 * - All MGRS coordinates truncate to legal 100 km blocks. All MGRS
55 * coordinates with a legal 100 km block prefix are legal (even though the
56 * latitude band letter may now belong to a neighboring band).
57 * - The range of UTM/UPS coordinates allowed for conversion to MGRS
58 * coordinates is the maximum consistent with staying within the letter
59 * ranges of the MGRS scheme.
60 * - All the transformations are implemented as static methods in the MGRS
61 * class.
62 *
63 * The <a href="http://www.nga.mil">NGA</a> software package
64 * <a href="https://earth-info.nga.mil/index.php?dir=wgs84&action=wgs84#tab_geotrans">geotrans</a>
65 * also provides conversions to and from MGRS. Version 3.0 (and earlier)
66 * suffers from some drawbacks:
67 * - Inconsistent rules are used to determine the whether a particular MGRS
68 * coordinate is legal. A more systematic approach is taken here.
69 * - The underlying projections are not very accurately implemented.
70 *
71 * Example of use:
72 * \include example-MGRS.cpp
73 **********************************************************************/
75 private:
76 typedef Math::real real;
77 static const char* const hemispheres_;
78 static const char* const utmcols_[3];
79 static const char* const utmrow_;
80 static const char* const upscols_[4];
81 static const char* const upsrows_[2];
82 static const char* const latband_;
83 static const char* const upsband_;
84 static const char* const digits_;
85 static const char* const alpha_;
86
87 static const int mineasting_[4];
88 static const int maxeasting_[4];
89 static const int minnorthing_[4];
90 static const int maxnorthing_[4];
91#if GEOGRAPHICLIB_PRECISION == 4
92 // Work around an enum lossage introduced in boost 1.76
93 // https://github.com/boostorg/multiprecision/issues/324
94 // and fixed in
95 // https://github.com/boostorg/multiprecision/pull/333
96 static const int
97#else
98 enum {
99#endif
100 base_ = 10,
101 // Top-level tiles are 10^5 m = 100 km on a side
102 tilelevel_ = 5,
103 // Period of UTM row letters
104 utmrowperiod_ = 20,
105 // Row letters are shifted by 5 for even zones
106 utmevenrowshift_ = 5,
107 // Maximum precision is um
108 maxprec_ = 5 + 6,
109 // For generating digits at maxprec
110 mult_ = 1000000
111#if GEOGRAPHICLIB_PRECISION == 4
112 ;
113#else
114 };
115#endif
116 static void CheckCoords(bool utmp, bool& northp, real& x, real& y);
117 static int UTMRow(int iband, int icol, int irow);
118
119 friend class UTMUPS; // UTMUPS::StandardZone calls LatitudeBand
120 // Return latitude band number [-10, 10) for the given latitude (degrees).
121 // The bands are reckoned in include their southern edges.
122 static int LatitudeBand(real lat) {
123 using std::floor;
124 int ilat = int(floor(lat));
125 return (std::max)(-10, (std::min)(9, (ilat + 80)/8 - 10));
126 }
127 // Return approximate latitude band number [-10, 10) for the given northing
128 // (meters). With this rule, each 100km tile would have a unique band
129 // letter corresponding to the latitude at the center of the tile. This
130 // function isn't currently used.
131 static int ApproxLatitudeBand(real y) {
132 // northing at tile center in units of tile = 100km
133 using std::floor; using std::fabs; using std::fmin;
134 real ya = floor( fmin(real(88), fabs(y / real(tile_))) ) + real(0.5);
135 // convert to lat (mult by 90/100) and then to band (divide by 8)
136 // the +1 fine tunes the boundary between bands 3 and 4
137 int b = int(floor( ((ya * 9 + 1) / 10) / 8 ));
138 // For the northern hemisphere we have
139 // band rows num
140 // N 0 0:8 9
141 // P 1 9:17 9
142 // Q 2 18:26 9
143 // R 3 27:34 8
144 // S 4 35:43 9
145 // T 5 44:52 9
146 // U 6 53:61 9
147 // V 7 62:70 9
148 // W 8 71:79 9
149 // X 9 80:94 15
150 return y >= 0 ? b : -(b + 1);
151 }
152 // UTMUPS accesses these enums
153#if GEOGRAPHICLIB_PRECISION == 4
154 // Work around an enum lossage introduced in boost 1.76
155 // https://github.com/boostorg/multiprecision/issues/324
156 // and fixed in
157 // https://github.com/boostorg/multiprecision/pull/333
158 static const int
159#else
160 enum {
161#endif
162 tile_ = 100000, // Size MGRS blocks
163 minutmcol_ = 1,
164 maxutmcol_ = 9,
165 minutmSrow_ = 10,
166 maxutmSrow_ = 100, // Also used for UTM S false northing
167 minutmNrow_ = 0, // Also used for UTM N false northing
168 maxutmNrow_ = 95,
169 minupsSind_ = 8, // These 4 ind's apply to easting and northing
170 maxupsSind_ = 32,
171 minupsNind_ = 13,
172 maxupsNind_ = 27,
173 upseasting_ = 20, // Also used for UPS false northing
174 utmeasting_ = 5, // UTM false easting
175 // Difference between S hemisphere northing and N hemisphere northing
176 utmNshift_ = (maxutmSrow_ - minutmNrow_) * tile_
178 ;
179#else
180 };
181#endif
182 MGRS() = delete; // Disable constructor
183
184 public:
185
186 /**
187 * Convert UTM or UPS coordinate to an MGRS coordinate.
188 *
189 * @param[in] zone UTM zone (zero means UPS).
190 * @param[in] northp hemisphere (true means north, false means south).
191 * @param[in] x easting of point (meters).
192 * @param[in] y northing of point (meters).
193 * @param[in] prec precision relative to 100 km.
194 * @param[out] mgrs MGRS string.
195 * @exception GeographicErr if \e zone, \e x, or \e y is outside its
196 * allowed range.
197 * @exception GeographicErr if the memory for the MGRS string can't be
198 * allocated.
199 *
200 * \e prec specifies the precision of the MGRS string as follows:
201 * - \e prec = &minus;1 (min), only the grid zone is returned
202 * - \e prec = 0, 100 km
203 * - \e prec = 1, 10 km
204 * - \e prec = 2, 1 km
205 * - \e prec = 3, 100 m
206 * - \e prec = 4, 10 m
207 * - \e prec = 5, 1 m
208 * - \e prec = 6, 0.1 m
209 * - &hellip;
210 * - \e prec = 11 (max), 1 &mu;m
211 *
212 * UTM eastings are allowed to be in the range [100 km, 900 km], northings
213 * are allowed to be in in [0 km, 9500 km] for the northern hemisphere and
214 * in [1000 km, 10000 km] for the southern hemisphere. (However UTM
215 * northings can be continued across the equator. So the actual limits on
216 * the northings are [&minus;9000 km, 9500 km] for the "northern"
217 * hemisphere and [1000 km, 19500 km] for the "southern" hemisphere.)
218 *
219 * UPS eastings/northings are allowed to be in the range [1300 km, 2700 km]
220 * in the northern hemisphere and in [800 km, 3200 km] in the southern
221 * hemisphere.
222 *
223 * The ranges are 100 km more restrictive than for the conversion between
224 * geographic coordinates and UTM and UPS given by UTMUPS. These
225 * restrictions are dictated by the allowed letters in MGRS coordinates.
226 * The choice of 9500 km for the maximum northing for northern hemisphere
227 * and of 1000 km as the minimum northing for southern hemisphere provide
228 * at least 0.5 degree extension into standard UPS zones. The upper ends
229 * of the ranges for the UPS coordinates is dictated by requiring symmetry
230 * about the meridians 0E and 90E.
231 *
232 * All allowed UTM and UPS coordinates may now be converted to legal MGRS
233 * coordinates with the proviso that eastings and northings on the upper
234 * boundaries are silently reduced by about 4 nm (4 nanometers) to place
235 * them \e within the allowed range. (This includes reducing a southern
236 * hemisphere northing of 10000 km by 4 nm so that it is placed in latitude
237 * band M.) The UTM or UPS coordinates are truncated to requested
238 * precision to determine the MGRS coordinate. Thus in UTM zone 38n, the
239 * square area with easting in [444 km, 445 km) and northing in [3688 km,
240 * 3689 km) maps to MGRS coordinate 38SMB4488 (at \e prec = 2, 1 km),
241 * Khulani Sq., Baghdad.
242 *
243 * The UTM/UPS selection and the UTM zone is preserved in the conversion to
244 * MGRS coordinate. Thus for \e zone > 0, the MGRS coordinate begins with
245 * the zone number followed by one of [C--M] for the southern
246 * hemisphere and [N--X] for the northern hemisphere. For \e zone =
247 * 0, the MGRS coordinates begins with one of [AB] for the southern
248 * hemisphere and [XY] for the northern hemisphere.
249 *
250 * The conversion to the MGRS is exact for prec in [0, 5] except that a
251 * neighboring latitude band letter may be given if the point is within 5nm
252 * of a band boundary. For prec in [6, 11], the conversion is accurate to
253 * roundoff.
254 *
255 * If \e prec = &minus;1, then the "grid zone designation", e.g., 18T, is
256 * returned. This consists of the UTM zone number (absent for UPS) and the
257 * first letter of the MGRS string which labels the latitude band for UTM
258 * and the hemisphere for UPS.
259 *
260 * If \e x or \e y is NaN or if \e zone is UTMUPS::INVALID, the returned
261 * MGRS string is "INVALID".
262 *
263 * Return the result via a reference argument to avoid the overhead of
264 * allocating a potentially large number of small strings. If an error is
265 * thrown, then \e mgrs is unchanged.
266 **********************************************************************/
267 static void Forward(int zone, bool northp, real x, real y,
268 int prec, std::string& mgrs);
269
270 /**
271 * Convert UTM or UPS coordinate to an MGRS coordinate when the latitude is
272 * known.
273 *
274 * @param[in] zone UTM zone (zero means UPS).
275 * @param[in] northp hemisphere (true means north, false means south).
276 * @param[in] x easting of point (meters).
277 * @param[in] y northing of point (meters).
278 * @param[in] lat latitude (degrees).
279 * @param[in] prec precision relative to 100 km.
280 * @param[out] mgrs MGRS string.
281 * @exception GeographicErr if \e zone, \e x, or \e y is outside its
282 * allowed range.
283 * @exception GeographicErr if \e lat is inconsistent with the given UTM
284 * coordinates.
285 * @exception std::bad_alloc if the memory for \e mgrs can't be allocated.
286 *
287 * The latitude is ignored for \e zone = 0 (UPS); otherwise the latitude is
288 * used to determine the latitude band and this is checked for consistency
289 * using the same tests as Reverse.
290 **********************************************************************/
291 static void Forward(int zone, bool northp, real x, real y, real lat,
292 int prec, std::string& mgrs);
293
294 /**
295 * Convert a MGRS coordinate to UTM or UPS coordinates.
296 *
297 * @param[in] mgrs MGRS string.
298 * @param[out] zone UTM zone (zero means UPS).
299 * @param[out] northp hemisphere (true means north, false means south).
300 * @param[out] x easting of point (meters).
301 * @param[out] y northing of point (meters).
302 * @param[out] prec precision relative to 100 km.
303 * @param[in] centerp if true (default), return center of the MGRS square,
304 * else return SW (lower left) corner.
305 * @exception GeographicErr if \e mgrs is illegal.
306 *
307 * All conversions from MGRS to UTM/UPS are permitted provided the MGRS
308 * coordinate is a possible result of a conversion in the other direction.
309 * (The leading 0 may be dropped from an input MGRS coordinate for UTM
310 * zones 1--9.) In addition, MGRS coordinates with a neighboring
311 * latitude band letter are permitted provided that some portion of the
312 * 100 km block is within the given latitude band. Thus
313 * - 38VLS and 38WLS are allowed (latitude 64N intersects the square
314 * 38[VW]LS); but 38VMS is not permitted (all of 38WMS is north of 64N)
315 * - 38MPE and 38NPF are permitted (they straddle the equator); but 38NPE
316 * and 38MPF are not permitted (the equator does not intersect either
317 * block).
318 * - Similarly ZAB and YZB are permitted (they straddle the prime
319 * meridian); but YAB and ZZB are not (the prime meridian does not
320 * intersect either block).
321 *
322 * The UTM/UPS selection and the UTM zone is preserved in the conversion
323 * from MGRS coordinate. The conversion is exact for prec in [0, 5]. With
324 * \e centerp = true, the conversion from MGRS to geographic and back is
325 * stable. This is not assured if \e centerp = false.
326 *
327 * If a "grid zone designation" (for example, 18T or A) is given, then some
328 * suitable (but essentially arbitrary) point within that grid zone is
329 * returned. The main utility of the conversion is to allow \e zone and \e
330 * northp to be determined. In this case, the \e centerp parameter is
331 * ignored and \e prec is set to &minus;1.
332 *
333 * If the first 3 characters of \e mgrs are "INV", then \e x and \e y are
334 * set to NaN, \e zone is set to UTMUPS::INVALID, and \e prec is set to
335 * &minus;2.
336 *
337 * If an exception is thrown, then the arguments are unchanged.
338 **********************************************************************/
339 static void Reverse(const std::string& mgrs,
340 int& zone, bool& northp, real& x, real& y,
341 int& prec, bool centerp = true);
342
343 /**
344 * Split a MGRS grid reference into its components.
345 *
346 * @param[in] mgrs MGRS string, e.g., 38SMB4488.
347 * @param[out] gridzone the grid zone, e.g., 38S.
348 * @param[out] block the 100km block id, e.g., MB.
349 * @param[out] easting the leading digits of the block easting, e.g., 44.
350 * @param[out] northing the leading digits of the block easting, e.g., 88.
351 * @exception GeographicErr if \e mgrs is illegal.
352 *
353 * Only the most rudimentary checking of MGRS grid ref is done: it is
354 * expected to consist of 0-2 digits followed by 1 or 3 letters, followed
355 * (in the case of 3 letters) by an even number (possibly 0) of digits. In
356 * reporting errors, the letters I and O (illegal in MSRS) are regarded as
357 * non-alphabetic. The returned \e gridzone will always be non-empty. The
358 * other output arguments may be empty strings.
359 *
360 * If the first 3 characters of \e mgrs are "INV", then \e gridzone is set
361 * to those 3 characters and the other return arguments are set to empty
362 * strings..
363 *
364 * If an exception is thrown, then the arguments are unchanged.
365 **********************************************************************/
366 static void Decode(const std::string& mgrs,
367 std::string& gridzone, std::string& block,
368 std::string& easting, std::string& northing);
369
370 /** \name Inspector functions
371 **********************************************************************/
372 ///@{
373 /**
374 * @return \e a the equatorial radius of the WGS84 ellipsoid (meters).
375 *
376 * (The WGS84 value is returned because the UTM and UPS projections are
377 * based on this ellipsoid.)
378 **********************************************************************/
380
381 /**
382 * @return \e f the flattening of the WGS84 ellipsoid.
383 *
384 * (The WGS84 value is returned because the UTM and UPS projections are
385 * based on this ellipsoid.)
386 **********************************************************************/
388 ///@}
389
390 /**
391 * Perform some checks on the UTMUPS coordinates on this ellipsoid. Throw
392 * an error if any of the assumptions made in the MGRS class is not true.
393 * This check needs to be carried out if the ellipsoid parameters (or the
394 * UTM/UPS scales) are ever changed.
395 **********************************************************************/
396 static void Check();
397
398 };
399
400} // namespace GeographicLib
401
402#if defined(_MSC_VER)
403# pragma warning (pop)
404#endif
405
406#endif // GEOGRAPHICLIB_MGRS_HPP
Header for GeographicLib::Constants class.
#define GEOGRAPHICLIB_EXPORT
Definition: Constants.hpp:67
GeographicLib::Math::real real
Definition: GeodSolve.cpp:31
#define GEOGRAPHICLIB_PRECISION
Definition: Math.hpp:35
Header for GeographicLib::UTMUPS class.
Convert between UTM/UPS and MGRS.
Definition: MGRS.hpp:74
static Math::real EquatorialRadius()
Definition: MGRS.hpp:379
static Math::real Flattening()
Definition: MGRS.hpp:387
Convert between geographic coordinates and UTM/UPS.
Definition: UTMUPS.hpp:75
static Math::real Flattening()
Definition: UTMUPS.hpp:414
static Math::real EquatorialRadius()
Definition: UTMUPS.hpp:405
Namespace for GeographicLib.
Definition: Accumulator.cpp:12