1 /**
2  * \file GARS.cpp
3  * \brief Implementation for GeographicLib::GARS class
4  *
5  * Copyright (c) Charles Karney (2015-2020) <charles@karney.com> and licensed
6  * under the MIT/X11 License.  For more information, see
7  * https://geographiclib.sourceforge.io/
8  **********************************************************************/
9 
10 #include <GeographicLib/GARS.hpp>
11 #include <GeographicLib/Utility.hpp>
12 
13 namespace GeographicLib {
14 
15   using namespace std;
16 
17   const char* const GARS::digits_ = "0123456789";
18   const char* const GARS::letters_ = "ABCDEFGHJKLMNPQRSTUVWXYZ";
19 
Forward(real lat,real lon,int prec,string & gars)20   void GARS::Forward(real lat, real lon, int prec, string& gars) {
21     using std::isnan;           // Needed for Centos 7, ubuntu 14
22     if (abs(lat) > 90)
23       throw GeographicErr("Latitude " + Utility::str(lat)
24                           + "d not in [-90d, 90d]");
25     if (isnan(lat) || isnan(lon)) {
26       gars = "INVALID";
27       return;
28     }
29     lon = Math::AngNormalize(lon);
30     if (lon == 180) lon = -180; // lon now in [-180,180)
31     if (lat == 90) lat *= (1 - numeric_limits<real>::epsilon() / 2);
32     prec = max(0, min(int(maxprec_), prec));
33     int
34       x = int(floor(lon * m_)) - lonorig_ * m_,
35       y = int(floor(lat * m_)) - latorig_ * m_,
36       ilon = x * mult1_ / m_,
37       ilat = y * mult1_ / m_;
38     x -= ilon * m_ / mult1_; y -= ilat * m_ / mult1_;
39     char gars1[maxlen_];
40     ++ilon;
41     for (int c = lonlen_; c--;) {
42       gars1[c] = digits_[ ilon % baselon_]; ilon /= baselon_;
43     }
44     for (int c = latlen_; c--;) {
45       gars1[lonlen_ + c] = letters_[ilat % baselat_]; ilat /= baselat_;
46     }
47     if (prec > 0) {
48       ilon = x / mult3_; ilat = y / mult3_;
49       gars1[baselen_] = digits_[mult2_ * (mult2_ - 1 - ilat) + ilon + 1];
50       if (prec > 1) {
51         ilon = x % mult3_; ilat = y % mult3_;
52         gars1[baselen_ + 1] = digits_[mult3_ * (mult3_ - 1 - ilat) + ilon + 1];
53       }
54     }
55     gars.resize(baselen_ + prec);
56     copy(gars1, gars1 + baselen_ + prec, gars.begin());
57   }
58 
Reverse(const string & gars,real & lat,real & lon,int & prec,bool centerp)59   void GARS::Reverse(const string& gars, real& lat, real& lon,
60                      int& prec, bool centerp) {
61     int len = int(gars.length());
62     if (len >= 3 &&
63         toupper(gars[0]) == 'I' &&
64         toupper(gars[1]) == 'N' &&
65         toupper(gars[2]) == 'V') {
66       lat = lon = Math::NaN();
67       return;
68     }
69     if (len < baselen_)
70       throw GeographicErr("GARS must have at least 5 characters " + gars);
71     if (len > maxlen_)
72       throw GeographicErr("GARS can have at most 7 characters " + gars);
73     int prec1 = len - baselen_;
74     int ilon = 0;
75     for (int c = 0; c < lonlen_; ++c) {
76       int k = Utility::lookup(digits_, gars[c]);
77       if (k < 0)
78         throw GeographicErr("GARS must start with 3 digits " + gars);
79       ilon = ilon * baselon_ + k;
80     }
81     if (!(ilon >= 1 && ilon <= 720))
82         throw GeographicErr("Initial digits in GARS must lie in [1, 720] " +
83                             gars);
84     --ilon;
85     int ilat = 0;
86     for (int c = 0; c < latlen_; ++c) {
87       int k = Utility::lookup(letters_, gars[lonlen_ + c]);
88       if (k < 0)
89         throw GeographicErr("Illegal letters in GARS " + gars.substr(3,2));
90       ilat = ilat * baselat_ + k;
91     }
92     if (!(ilat < 360))
93       throw  GeographicErr("GARS letters must lie in [AA, QZ] " + gars);
94     real
95       unit = mult1_,
96       lat1 = ilat + latorig_ * unit,
97       lon1 = ilon + lonorig_ * unit;
98     if (prec1 > 0) {
99       int k = Utility::lookup(digits_, gars[baselen_]);
100       if (!(k >= 1 && k <= mult2_ * mult2_))
101         throw GeographicErr("6th character in GARS must [1, 4] " + gars);
102       --k;
103       unit *= mult2_;
104       lat1 = mult2_ * lat1 + (mult2_ - 1 - k / mult2_);
105       lon1 = mult2_ * lon1 + (k % mult2_);
106       if (prec1 > 1) {
107         k = Utility::lookup(digits_, gars[baselen_ + 1]);
108         if (!(k >= 1 /* && k <= mult3_ * mult3_ */))
109           throw GeographicErr("7th character in GARS must [1, 9] " + gars);
110         --k;
111         unit *= mult3_;
112         lat1 = mult3_ * lat1 + (mult3_ - 1 - k / mult3_);
113         lon1 = mult3_ * lon1 + (k % mult3_);
114       }
115     }
116     if (centerp) {
117       unit *= 2; lat1 = 2 * lat1 + 1; lon1 = 2 * lon1 + 1;
118     }
119     lat = lat1 / unit;
120     lon = lon1 / unit;
121     prec = prec1;
122   }
123 
124 } // namespace GeographicLib
125