1 /**
2  * \file GeoCoords.hpp
3  * \brief Header for GeographicLib::GeoCoords class
4  *
5  * Copyright (c) Charles Karney (2008-2020) <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_GEOCOORDS_HPP)
11 #define GEOGRAPHICLIB_GEOCOORDS_HPP 1
12 
13 #include <GeographicLib/UTMUPS.hpp>
14 #include <GeographicLib/Constants.hpp>
15 
16 namespace GeographicLib {
17 
18   /**
19    * \brief Conversion between geographic coordinates
20    *
21    * This class stores a geographic position which may be set via the
22    * constructors or Reset via
23    * - latitude and longitude
24    * - UTM or UPS coordinates
25    * - a string representation of these or an MGRS coordinate string
26    *
27    * The state consists of the latitude and longitude and the supplied UTM or
28    * UPS coordinates (possibly derived from the MGRS coordinates).  If latitude
29    * and longitude were given then the UTM/UPS coordinates follows the standard
30    * conventions.
31    *
32    * The mutable state consists of the UTM or UPS coordinates for a alternate
33    * zone.  A method SetAltZone is provided to set the alternate UPS/UTM zone.
34    *
35    * Methods are provided to return the geographic coordinates, the input UTM
36    * or UPS coordinates (and associated meridian convergence and scale), or
37    * alternate UTM or UPS coordinates (and their associated meridian
38    * convergence and scale).
39    *
40    * Once the input string has been parsed, you can print the result out in any
41    * of the formats, decimal degrees, degrees minutes seconds, MGRS, UTM/UPS.
42    *
43    * Example of use:
44    * \include example-GeoCoords.cpp
45    *
46    * <a href="GeoConvert.1.html">GeoConvert</a> is a command-line utility
47    * providing access to the functionality of GeoCoords.
48    **********************************************************************/
49   class GEOGRAPHICLIB_EXPORT GeoCoords {
50   private:
51     typedef Math::real real;
52     real _lat, _long, _easting, _northing, _gamma, _k;
53     bool _northp;
54     int _zone;                  // See UTMUPS::zonespec
55     mutable real _alt_easting, _alt_northing, _alt_gamma, _alt_k;
56     mutable int _alt_zone;
57 
CopyToAlt() const58     void CopyToAlt() const {
59       _alt_easting = _easting;
60       _alt_northing = _northing;
61       _alt_gamma = _gamma;
62       _alt_k = _k;
63       _alt_zone = _zone;
64     }
65     static void UTMUPSString(int zone, bool northp,
66                              real easting, real northing,
67                              int prec, bool abbrev, std::string& utm);
68     void FixHemisphere();
69   public:
70 
71     /** \name Initializing the GeoCoords object
72      **********************************************************************/
73     ///@{
74     /**
75      * The default constructor sets the coordinate as undefined.
76      **********************************************************************/
GeoCoords()77     GeoCoords()
78       : _lat(Math::NaN())
79       , _long(Math::NaN())
80       , _easting(Math::NaN())
81       , _northing(Math::NaN())
82       , _gamma(Math::NaN())
83       , _k(Math::NaN())
84       , _northp(false)
85       , _zone(UTMUPS::INVALID)
86     { CopyToAlt(); }
87 
88     /**
89      * Construct from a string.
90      *
91      * @param[in] s 1-element, 2-element, or 3-element string representation of
92      *   the position.
93      * @param[in] centerp governs the interpretation of MGRS coordinates (see
94      *   below).
95      * @param[in] longfirst governs the interpretation of geographic
96      *   coordinates (see below).
97      * @exception GeographicErr if the \e s is malformed (see below).
98      *
99      * Parse as a string and interpret it as a geographic position.  The input
100      * string is broken into space (or comma) separated pieces and Basic
101      * decision on which format is based on number of components
102      * -# MGRS
103      * -# "Lat Long" or "Long Lat"
104      * -# "Zone Easting Northing" or "Easting Northing Zone"
105      *
106      * The following inputs are approximately the same (Ar Ramadi Bridge, Iraq)
107      * - Latitude and Longitude
108      *   -  33.44      43.27
109      *   -  N33d26.4'  E43d16.2'
110      *   -  43d16'12&quot;E 33d26'24&quot;N
111      *   -  43:16:12E  33:26:24
112      * - MGRS
113      *   -  38SLC30
114      *   -  38SLC391014
115      *   -  38SLC3918701405
116      *   -  37SHT9708
117      * - UTM
118      *   -  38n 339188 3701405
119      *   -  897039 3708229 37n
120      *
121      * <b>Latitude and Longitude parsing</b>: Latitude precedes longitude,
122      * unless a N, S, E, W hemisphere designator is used on one or both
123      * coordinates.  If \e longfirst = true (default is false), then
124      * longitude precedes latitude in the absence of a hemisphere designator.
125      * Thus (with \e longfirst = false)
126      * - 40 -75
127      * - N40 W75
128      * - -75 N40
129      * - 75W 40N
130      * - E-75 -40S
131      * .
132      * are all the same position.  The coordinates may be given in
133      * decimal degrees, degrees and decimal minutes, degrees, minutes,
134      * seconds, etc.  Use d, ', and &quot; to mark off the degrees,
135      * minutes and seconds.  Various alternative symbols for degrees, minutes,
136      * and seconds are allowed.  Alternatively, use : to separate these
137      * components.  A single addition or subtraction is allowed.  (See
138      * DMS::Decode for details.)  Thus
139      * - 40d30'30&quot;
140      * - 40d30'30
141      * - 40&deg;30'30
142      * - 40d30.5'
143      * - 40d30.5
144      * - 40:30:30
145      * - 40:30.5
146      * - 40.508333333
147      * - 40:30+0:0:30
148      * - 40:31-0:0.5
149      * .
150      * all specify the same angle.  The leading sign applies to the following
151      * components so -1d30 is -(1+30/60) = &minus;1.5.  However, note
152      * that -1:30-0:0:15 is parsed as (-1:30) + (-0:0:15) = &minus;(1+30/60)
153      * &minus; (15/3600).  Latitudes must be in the range [&minus;90&deg;,
154      * 90&deg;].  Internally longitudes are reduced to the range
155      * [&minus;180&deg;, 180&deg;].
156      *
157      * <b>UTM/UPS parsing</b>: For UTM zones (&minus;80&deg; &le; Lat <
158      * 84&deg;), the zone designator is made up of a zone number (for 1 to 60)
159      * and a hemisphere letter (n or s), e.g., 38n (38north can also be used).
160      * The latitude band designer ([C--M] in the southern hemisphere and [N--X]
161      * in the northern) should NOT be used.  (This is part of the MGRS
162      * coordinate.)  The zone designator for the poles (where UPS is employed)
163      * is a hemisphere letter by itself, i.e., n or s (north or south can also
164      * be used).
165      *
166      * <b>MGRS parsing</b> interprets the grid references as square area at the
167      * specified precision (1m, 10m, 100m, etc.).  If \e centerp = true (the
168      * default), the center of this square is then taken to be the precise
169      * position; thus:
170      * - 38SMB           = 38n 450000 3650000
171      * - 38SMB4484       = 38n 444500 3684500
172      * - 38SMB44148470   = 38n 444145 3684705
173      * .
174      * Otherwise, the "south-west" corner of the square is used, i.e.,
175      * - 38SMB           = 38n 400000 3600000
176      * - 38SMB4484       = 38n 444000 3684000
177      * - 38SMB44148470   = 38n 444140 3684700
178      **********************************************************************/
GeoCoords(const std::string & s,bool centerp=true,bool longfirst=false)179     explicit GeoCoords(const std::string& s,
180                        bool centerp = true, bool longfirst = false)
181     { Reset(s, centerp, longfirst); }
182 
183     /**
184      * Construct from geographic coordinates.
185      *
186      * @param[in] latitude (degrees).
187      * @param[in] longitude (degrees).
188      * @param[in] zone if specified, force the UTM/UPS representation to use a
189      *   specified zone using the rules given in UTMUPS::zonespec.
190      * @exception GeographicErr if \e latitude is not in [&minus;90&deg;,
191      *   90&deg;].
192      * @exception GeographicErr if \e zone cannot be used for this location.
193      **********************************************************************/
GeoCoords(real latitude,real longitude,int zone=UTMUPS::STANDARD)194     GeoCoords(real latitude, real longitude, int zone = UTMUPS::STANDARD) {
195       Reset(latitude, longitude, zone);
196     }
197 
198     /**
199      * Construct from UTM/UPS coordinates.
200      *
201      * @param[in] zone UTM zone (zero means UPS).
202      * @param[in] northp hemisphere (true means north, false means south).
203      * @param[in] easting (meters).
204      * @param[in] northing (meters).
205      * @exception GeographicErr if \e zone, \e easting, or \e northing is
206      *   outside its allowed range.
207      **********************************************************************/
GeoCoords(int zone,bool northp,real easting,real northing)208     GeoCoords(int zone, bool northp, real easting, real northing) {
209       Reset(zone, northp, easting, northing);
210     }
211 
212     /**
213      * Reset the location from a string.  See
214      * GeoCoords(const std::string& s, bool centerp, bool longfirst).
215      *
216      * @param[in] s 1-element, 2-element, or 3-element string representation of
217      *   the position.
218      * @param[in] centerp governs the interpretation of MGRS coordinates.
219      * @param[in] longfirst governs the interpretation of geographic
220      *   coordinates.
221      * @exception GeographicErr if the \e s is malformed.
222      **********************************************************************/
223     void Reset(const std::string& s,
224                bool centerp = true, bool longfirst = false);
225 
226     /**
227      * Reset the location in terms of geographic coordinates.  See
228      * GeoCoords(real latitude, real longitude, int zone).
229      *
230      * @param[in] latitude (degrees).
231      * @param[in] longitude (degrees).
232      * @param[in] zone if specified, force the UTM/UPS representation to use a
233      *   specified zone using the rules given in UTMUPS::zonespec.
234      * @exception GeographicErr if \e latitude is not in [&minus;90&deg;,
235      *   90&deg;].
236      * @exception GeographicErr if \e zone cannot be used for this location.
237      **********************************************************************/
Reset(real latitude,real longitude,int zone=UTMUPS::STANDARD)238     void Reset(real latitude, real longitude, int zone = UTMUPS::STANDARD) {
239       UTMUPS::Forward(latitude, longitude,
240                       _zone, _northp, _easting, _northing, _gamma, _k,
241                       zone);
242       _lat = latitude;
243       _long = longitude;
244       if (_long >= 180) _long -= 360;
245       else if (_long < -180) _long += 360;
246       CopyToAlt();
247     }
248 
249     /**
250      * Reset the location in terms of UPS/UPS coordinates.  See
251      * GeoCoords(int zone, bool northp, real easting, real northing).
252      *
253      * @param[in] zone UTM zone (zero means UPS).
254      * @param[in] northp hemisphere (true means north, false means south).
255      * @param[in] easting (meters).
256      * @param[in] northing (meters).
257      * @exception GeographicErr if \e zone, \e easting, or \e northing is
258      *   outside its allowed range.
259      **********************************************************************/
Reset(int zone,bool northp,real easting,real northing)260     void Reset(int zone, bool northp, real easting, real northing) {
261       UTMUPS::Reverse(zone, northp, easting, northing,
262                       _lat, _long, _gamma, _k);
263       _zone = zone;
264       _northp = northp;
265       _easting = easting;
266       _northing = northing;
267       FixHemisphere();
268       CopyToAlt();
269     }
270     ///@}
271 
272     /** \name Querying the GeoCoords object
273      **********************************************************************/
274     ///@{
275     /**
276      * @return latitude (degrees)
277      **********************************************************************/
Latitude() const278     Math::real Latitude() const { return _lat; }
279 
280     /**
281      * @return longitude (degrees)
282      **********************************************************************/
Longitude() const283     Math::real Longitude() const { return _long; }
284 
285     /**
286      * @return easting (meters)
287      **********************************************************************/
Easting() const288     Math::real Easting() const { return _easting; }
289 
290     /**
291      * @return northing (meters)
292      **********************************************************************/
Northing() const293     Math::real Northing() const { return _northing; }
294 
295     /**
296      * @return meridian convergence (degrees) for the UTM/UPS projection.
297      **********************************************************************/
Convergence() const298     Math::real Convergence() const { return _gamma; }
299 
300     /**
301      * @return scale for the UTM/UPS projection.
302      **********************************************************************/
Scale() const303     Math::real Scale() const { return _k; }
304 
305     /**
306      * @return hemisphere (false means south, true means north).
307      **********************************************************************/
Northp() const308     bool Northp() const { return _northp; }
309 
310     /**
311      * @return hemisphere letter n or s.
312      **********************************************************************/
Hemisphere() const313     char Hemisphere() const { return _northp ? 'n' : 's'; }
314 
315     /**
316      * @return the zone corresponding to the input (return 0 for UPS).
317      **********************************************************************/
Zone() const318     int Zone() const { return _zone; }
319 
320     ///@}
321 
322     /** \name Setting and querying the alternate zone
323      **********************************************************************/
324     ///@{
325     /**
326      * Specify alternate zone number.
327      *
328      * @param[in] zone zone number for the alternate representation.
329      * @exception GeographicErr if \e zone cannot be used for this location.
330      *
331      * See UTMUPS::zonespec for more information on the interpretation of \e
332      * zone.  Note that \e zone == UTMUPS::STANDARD (the default) use the
333      * standard UPS or UTM zone, UTMUPS::MATCH does nothing retaining the
334      * existing alternate representation.  Before this is called the alternate
335      * zone is the input zone.
336      **********************************************************************/
SetAltZone(int zone=UTMUPS::STANDARD) const337     void SetAltZone(int zone = UTMUPS::STANDARD) const {
338       if (zone == UTMUPS::MATCH)
339         return;
340       zone = UTMUPS::StandardZone(_lat, _long, zone);
341       if (zone == _zone)
342         CopyToAlt();
343       else {
344         bool northp;
345         UTMUPS::Forward(_lat, _long,
346                         _alt_zone, northp,
347                         _alt_easting, _alt_northing, _alt_gamma, _alt_k,
348                         zone);
349       }
350     }
351 
352     /**
353      * @return current alternate zone (return 0 for UPS).
354      **********************************************************************/
AltZone() const355     int AltZone() const { return _alt_zone; }
356 
357     /**
358      * @return easting (meters) for alternate zone.
359      **********************************************************************/
AltEasting() const360     Math::real AltEasting() const { return _alt_easting; }
361 
362     /**
363      * @return northing (meters) for alternate zone.
364      **********************************************************************/
AltNorthing() const365     Math::real AltNorthing() const { return _alt_northing; }
366 
367     /**
368      * @return meridian convergence (degrees) for alternate zone.
369      **********************************************************************/
AltConvergence() const370     Math::real AltConvergence() const { return _alt_gamma; }
371 
372     /**
373      * @return scale for alternate zone.
374      **********************************************************************/
AltScale() const375     Math::real AltScale() const { return _alt_k; }
376     ///@}
377 
378     /** \name String representations of the GeoCoords object
379      **********************************************************************/
380     ///@{
381     /**
382      * String representation with latitude and longitude as signed decimal
383      * degrees.
384      *
385      * @param[in] prec precision (relative to about 1m).
386      * @param[in] longfirst if true give longitude first (default = false)
387      * @exception std::bad_alloc if memory for the string can't be allocated.
388      * @return decimal latitude/longitude string representation.
389      *
390      * Precision specifies accuracy of representation as follows:
391      * - prec = &minus;5 (min), 1&deg;
392      * - prec = 0, 10<sup>&minus;5</sup>&deg; (about 1m)
393      * - prec = 3, 10<sup>&minus;8</sup>&deg;
394      * - prec = 9 (max), 10<sup>&minus;14</sup>&deg;
395      **********************************************************************/
396     std::string GeoRepresentation(int prec = 0, bool longfirst = false) const;
397 
398     /**
399      * String representation with latitude and longitude as degrees, minutes,
400      * seconds, and hemisphere.
401      *
402      * @param[in] prec precision (relative to about 1m)
403      * @param[in] longfirst if true give longitude first (default = false)
404      * @param[in] dmssep if non-null, use as the DMS separator character
405      *   (instead of d, ', &quot; delimiters).
406      * @exception std::bad_alloc if memory for the string can't be allocated.
407      * @return DMS latitude/longitude string representation.
408      *
409      * Precision specifies accuracy of representation as follows:
410      * - prec = &minus;5 (min), 1&deg;
411      * - prec = &minus;4, 0.1&deg;
412      * - prec = &minus;3, 1'
413      * - prec = &minus;2, 0.1'
414      * - prec = &minus;1, 1&quot;
415      * - prec = 0, 0.1&quot; (about 3m)
416      * - prec = 1, 0.01&quot;
417      * - prec = 10 (max), 10<sup>&minus;11</sup>&quot;
418      **********************************************************************/
419     std::string DMSRepresentation(int prec = 0, bool longfirst = false,
420                                   char dmssep = char(0))
421       const;
422 
423     /**
424      * MGRS string.
425      *
426      * @param[in] prec precision (relative to about 1m).
427      * @exception std::bad_alloc if memory for the string can't be allocated.
428      * @return MGRS string.
429      *
430      * This gives the coordinates of the enclosing grid square with size given
431      * by the precision.  Thus 38n 444180 3684790 converted to a MGRS
432      * coordinate at precision &minus;2 (100m) is 38SMB441847 and not
433      * 38SMB442848.  \e prec specifies the precision of the MGRS string as
434      * follows:
435      * - prec = &minus;6 (min), only the grid zone is returned, e.g., 38S
436      * - prec = &minus;5, 100km, e.g., 38SMB
437      * - prec = &minus;4, 10km
438      * - prec = &minus;3, 1km
439      * - prec = &minus;2, 100m
440      * - prec = &minus;1, 10m
441      * - prec = 0, 1m
442      * - prec = 1, 0.1m
443      * - prec = 6 (max), 1&mu;m
444      **********************************************************************/
445     std::string MGRSRepresentation(int prec = 0) const;
446 
447     /**
448      * UTM/UPS string.
449      *
450      * @param[in] prec precision (relative to about 1m)
451      * @param[in] abbrev if true (the default) use abbreviated (n/s) notation
452      *   for hemisphere; otherwise spell out the hemisphere (north/south)
453      * @exception std::bad_alloc if memory for the string can't be allocated.
454      * @return UTM/UPS string representation: zone designator, easting, and
455      *   northing.
456      *
457      * Precision specifies accuracy of representation as follows:
458      * - prec = &minus;5 (min), 100km
459      * - prec = &minus;3, 1km
460      * - prec = 0, 1m
461      * - prec = 3, 1mm
462      * - prec = 6, 1&mu;m
463      * - prec = 9 (max), 1nm
464      **********************************************************************/
465     std::string UTMUPSRepresentation(int prec = 0, bool abbrev = true) const;
466 
467     /**
468      * UTM/UPS string with hemisphere override.
469      *
470      * @param[in] northp hemisphere override
471      * @param[in] prec precision (relative to about 1m)
472      * @param[in] abbrev if true (the default) use abbreviated (n/s) notation
473      *   for hemisphere; otherwise spell out the hemisphere (north/south)
474      * @exception GeographicErr if the hemisphere override attempts to change
475      *   UPS N to UPS S or vice versa.
476      * @exception std::bad_alloc if memory for the string can't be allocated.
477      * @return UTM/UPS string representation: zone designator, easting, and
478      *   northing.
479      **********************************************************************/
480     std::string UTMUPSRepresentation(bool northp, int prec = 0,
481                                      bool abbrev = true) const;
482 
483     /**
484      * MGRS string for the alternate zone.  See GeoCoords::MGRSRepresentation.
485      *
486      * @param[in] prec precision (relative to about 1m).
487      * @exception std::bad_alloc if memory for the string can't be allocated.
488      * @return MGRS string.
489      **********************************************************************/
490     std::string AltMGRSRepresentation(int prec = 0) const;
491 
492     /**
493      * UTM/UPS string for the alternate zone.  See
494      * GeoCoords::UTMUPSRepresentation.
495      *
496      * @param[in] prec precision (relative to about 1m)
497      * @param[in] abbrev if true (the default) use abbreviated (n/s) notation
498      *   for hemisphere; otherwise spell out the hemisphere (north/south)
499      * @exception std::bad_alloc if memory for the string can't be allocated.
500      * @return UTM/UPS string representation: zone designator, easting, and
501      *   northing.
502      **********************************************************************/
503     std::string AltUTMUPSRepresentation(int prec = 0, bool abbrev = true)
504       const;
505 
506     /**
507      * UTM/UPS string for the alternate zone, with hemisphere override.
508      *
509      * @param[in] northp hemisphere override
510      * @param[in] prec precision (relative to about 1m)
511      * @param[in] abbrev if true (the default) use abbreviated (n/s) notation
512      *   for hemisphere; otherwise spell out the hemisphere (north/south)
513      * @exception GeographicErr if the hemisphere override attempts to change
514      *   UPS n to UPS s or vice verse.
515      * @exception std::bad_alloc if memory for the string can't be allocated.
516      * @return UTM/UPS string representation: zone designator, easting, and
517      *   northing.
518      **********************************************************************/
519     std::string AltUTMUPSRepresentation(bool northp, int prec = 0,
520                                         bool abbrev = true) const;
521     ///@}
522 
523     /** \name Inspector functions
524      **********************************************************************/
525     ///@{
526     /**
527      * @return \e a the equatorial radius of the WGS84 ellipsoid (meters).
528      *
529      * (The WGS84 value is returned because the UTM and UPS projections are
530      * based on this ellipsoid.)
531      **********************************************************************/
EquatorialRadius() const532     Math::real EquatorialRadius() const { return UTMUPS::EquatorialRadius(); }
533 
534     /**
535      * @return \e f the flattening of the WGS84 ellipsoid.
536      *
537      * (The WGS84 value is returned because the UTM and UPS projections are
538      * based on this ellipsoid.)
539      **********************************************************************/
Flattening() const540     Math::real Flattening() const { return UTMUPS::Flattening(); }
541 
542     /**
543      * \deprecated An old name for EquatorialRadius().
544      **********************************************************************/
545     GEOGRAPHICLIB_DEPRECATED("Use EquatorialRadius()")
MajorRadius() const546     Math::real MajorRadius() const { return EquatorialRadius(); }
547     ///@}
548 
549   };
550 
551 } // namespace GeographicLib
552 
553 #endif  // GEOGRAPHICLIB_GEOCOORDS_HPP
554