MGRS.cpp
Go to the documentation of this file.
00001 /**
00002  * \file MGRS.cpp
00003  * \brief Implementation for GeographicLib::MGRS class
00004  *
00005  * Copyright (c) Charles Karney (2008) <charles@karney.com>
00006  * and licensed under the LGPL.
00007  **********************************************************************/
00008 
00009 #include "GeographicLib/MGRS.hpp"
00010 #include "GeographicLib/UTMUPS.hpp"
00011 #include <stdexcept>
00012 #include <limits>
00013 
00014 namespace {
00015   char RCSID[] = "$Id: MGRS.cpp 6553 2009-02-24 03:10:01Z ckarney $";
00016   char RCSID_H[] = MGRS_HPP;
00017 }
00018 
00019 namespace GeographicLib {
00020 
00021   using namespace std;
00022 
00023   const double MGRS::eps =
00024     // 25 = ceil(log_2(2e7)) -- use half circumference here because northing
00025     // 195e5 is a legal in the "southern" hemisphere.
00026     pow(0.5, numeric_limits<double>::digits - 25);
00027   const double MGRS::angeps =
00028     // 7 = ceil(log_2(90))
00029     pow(0.5, numeric_limits<double>::digits - 7);
00030   const string MGRS::hemispheres = "SN";
00031   const string MGRS::utmcols[3] =
00032     { "ABCDEFGH", "JKLMNPQR", "STUVWXYZ" };
00033   const string MGRS::utmrow  = "ABCDEFGHJKLMNPQRSTUV";
00034   const string MGRS::upscols[4] =
00035     { "JKLPQRSTUXYZ", "ABCFGHJKLPQR", "RSTUXYZ", "ABCFGHJ" };
00036   const string MGRS::upsrows[2] =
00037     { "ABCDEFGHJKLMNPQRSTUVWXYZ", "ABCDEFGHJKLMNP" };
00038   const string MGRS::latband = "CDEFGHJKLMNPQRSTUVWX";
00039   const string MGRS::upsband = "ABYZ";
00040   const string MGRS::digits  = "0123456789";
00041 
00042   const int MGRS::mineasting[4] =
00043     { minupsSind, minupsNind, minutmcol, minutmcol };
00044   const int MGRS::maxeasting[4] =
00045     { maxupsSind, maxupsNind, maxutmcol, maxutmcol };
00046   const int MGRS::minnorthing[4] =
00047     { minupsSind, minupsNind,
00048       minutmSrow,  minutmSrow - (maxutmSrow - minutmNrow) };
00049   const int MGRS::maxnorthing[4] =
00050     { maxupsSind, maxupsNind,
00051       maxutmNrow + (maxutmSrow - minutmNrow), maxutmNrow };
00052 
00053   void MGRS::Forward(int zone, bool northp, double x, double y, double lat,
00054                      int prec, std::string& mgrs) {
00055     bool utmp = zone != 0;
00056     CheckCoords(utmp, northp, x, y);
00057     if (!(zone >= 0 || zone <= 60))
00058       throw out_of_range("Zone " + str(zone) + " not in [0,60]");
00059     if (!(prec >= 0 || prec <= maxprec))
00060       throw out_of_range("MGRS precision " + str(prec) + " not in [0, "
00061                               + str(int(maxprec)) + "]");
00062     int
00063       zone1 = zone - 1,
00064       z = utmp ? 2 : 0;
00065     // Space for zone, 3 block letters, easting + northing
00066     mgrs.resize(z + 3 + 2 * prec);
00067     if (utmp) {
00068       mgrs[0] = digits[ zone / base ];
00069       mgrs[1] = digits[ zone % base ];
00070       // This isn't necessary...!  Keep y non-neg
00071       // if (!northp) y -= maxutmSrow * tile;
00072     }
00073     int
00074       xh = int(floor(x)) / tile,
00075       yh = int(floor(y)) / tile;
00076     double
00077       xf = x - tile * xh,
00078       yf = y - tile * yh;
00079     if (utmp) {
00080       int
00081         // Correct fuzziness in latitude near equator
00082         iband = abs(lat) > angeps ? LatitudeBand(lat) : (northp ? 0 : -1),
00083         icol = xh - minutmcol,
00084         irow = UTMRow(iband, icol, yh % utmrowperiod);
00085       if (irow != yh - (northp ? minutmNrow : maxutmSrow))
00086         throw out_of_range("Latitude " + str(lat)
00087                                 + " is inconsistent with UTM coordinates");
00088       mgrs[z++] = latband[10 + iband];
00089       mgrs[z++] = utmcols[zone1 % 3][icol];
00090       mgrs[z++] = utmrow[(yh + (zone1 & 1 ? utmevenrowshift : 0))
00091                          % utmrowperiod];
00092     } else {
00093       bool eastp = xh >= upseasting;
00094       int iband = (northp ? 2 : 0) + (eastp ? 1 : 0);
00095       mgrs[z++] = upsband[iband];
00096       mgrs[z++] = upscols[iband][xh - (eastp ? upseasting :
00097                                        northp ? minupsNind : minupsSind)];
00098       mgrs[z++] = upsrows[northp][yh - (northp ? minupsNind : minupsSind)];
00099     }
00100     double mult = pow(double(base), min(prec - tilelevel, 0));
00101     int
00102       ix = int(floor(xf * mult)),
00103       iy = int(floor(yf * mult));
00104     for (int c = min(prec, int(tilelevel)); c--;) {
00105       mgrs[z + c] = digits[ ix % base ];
00106       ix /= base;
00107       mgrs[z + c + prec] = digits[ iy % base ];
00108       iy /= base;
00109     }
00110     if (prec > tilelevel) {
00111       xf -= floor(xf * mult);
00112       yf -= floor(yf * mult);
00113       mult = pow(double(base), prec - tilelevel);
00114       ix = int(floor(xf * mult));
00115       iy = int(floor(yf * mult));
00116       for (int c = prec - tilelevel; c--;) {
00117         mgrs[z + c + tilelevel] = digits[ ix % base ];
00118         ix /= base;
00119         mgrs[z + c + tilelevel + prec] = digits[ iy % base ];
00120         iy /= base;
00121       }
00122     }
00123   }
00124 
00125   void MGRS::Forward(int zone, bool northp, double x, double y,
00126                      int prec, std::string& mgrs) {
00127     double lat, lon;
00128     if (zone)
00129       UTMUPS::Reverse(zone, northp, x, y, lat, lon);
00130     else
00131       // Latitude isn't needed for UPS specs.
00132       lat = 0;
00133     Forward(zone, northp, x, y, lat, prec, mgrs);
00134   }
00135 
00136   void MGRS::Reverse(const std::string& mgrs,
00137                      int& zone, bool& northp, double& x, double& y,
00138                      int& prec, bool centerp) {
00139     int
00140       p = 0,
00141       len = int(mgrs.size());
00142     zone = 0;
00143     while (p < len) {
00144       int i = lookup(digits, mgrs[p]);
00145       if (i < 0)
00146         break;
00147       zone = 10 * zone + i;
00148       ++p;
00149     }
00150     if (p > 0 && (zone == 0 || zone > 60))
00151       throw out_of_range("Zone " + str(zone) + " not in [1,60]");
00152     if (p > 2)
00153       throw out_of_range("More than 2 digits at start of MGRS "
00154                          + mgrs.substr(0, p));
00155     if (len - p < 3)
00156       throw out_of_range("MGRS string " + mgrs + " too short");
00157     bool utmp = zone != 0;
00158     int zone1 = zone - 1;
00159     const string& band = utmp ? latband : upsband;
00160     int iband = lookup(band, mgrs[p++]);
00161     if (iband < 0)
00162       throw out_of_range("Band letter " + str(mgrs[p-1])
00163                          + " not in " + (utmp ? "UTM" : "UPS")
00164                          + " set " + band);
00165     northp = iband >= (utmp ? 10 : 2);
00166     const string& col = utmp ? utmcols[zone1 % 3] : upscols[iband];
00167     const string& row = utmp ? utmrow : upsrows[northp];
00168     int icol = lookup(col, mgrs[p++]);
00169     if (icol < 0)
00170       throw out_of_range("Column letter " + str(mgrs[p-1])
00171                          + " not in "
00172                          + (utmp ? "zone " + mgrs.substr(0, p-2) :
00173                             "UPS band " + str(mgrs[p-2]))
00174                          + " set " + col );
00175     int irow = lookup(row, mgrs[p++]);
00176     if (irow < 0)
00177       throw out_of_range("Row letter " + str(mgrs[p-1])
00178                          + " not in "
00179                          + (utmp ? "UTM" :
00180                             "UPS " + str(hemispheres[northp]))
00181                          + " set " + row);
00182     if (utmp) {
00183       if (zone1 & 1)
00184         irow = (irow + utmrowperiod - utmevenrowshift) % utmrowperiod;
00185       iband -= 10;
00186       irow = UTMRow(iband, icol, irow);
00187       if (irow == maxutmSrow)
00188         throw out_of_range("Block " + mgrs.substr(p-2, 2)
00189                            + " not in zone/band " + mgrs.substr(0, p-2));
00190 
00191       irow = northp ? irow : irow + 100;
00192       icol = icol + minutmcol;
00193     } else {
00194       bool eastp = iband & 1;
00195       icol += eastp ? upseasting : northp ? minupsNind : minupsSind;
00196       irow += northp ? minupsNind : minupsSind;
00197     }
00198     prec = (len - p)/2;
00199     double unit = tile;
00200     x = unit * icol;
00201     y = unit * irow;
00202     for (int i = 0; i < prec; ++i) {
00203       unit /= base;
00204       int
00205         ix = lookup(digits, mgrs[p + i]),
00206         iy = lookup(digits, mgrs[p + i + prec]);
00207       if (ix < 0 || iy < 0)
00208         throw out_of_range("Encountered a non-digit in " + mgrs.substr(p));
00209       x += unit * ix;
00210       y += unit * iy;
00211     }
00212     if ((len - p) % 2) {
00213       if (lookup(digits, mgrs[len - 1]) < 0)
00214         throw out_of_range("Encountered a non-digit in " + mgrs.substr(p));
00215       else
00216         throw out_of_range("Not an even number of digits in "
00217                            + mgrs.substr(p));
00218     }
00219     if (prec > maxprec)
00220       throw out_of_range("More than " + str(2*maxprec) + " digits in "
00221                          + mgrs.substr(p));
00222     if (centerp) {
00223       x += unit/2;
00224       y += unit/2;
00225     }
00226   }
00227 
00228   void MGRS::CheckCoords(bool utmp, bool& northp, double& x, double& y) {
00229     // Limits are all multiples of 100km and are all closed on the lower end
00230     // and open on the upper end -- and this is reflected in the error
00231     // messages.  However if a coordinate lies on the excluded upper end (e.g.,
00232     // after rounding), it is shifted down by eps.  This also folds UTM
00233     // northings to the correct N/S hemisphere.
00234     int
00235       ix = int(floor(x / tile)),
00236       iy = int(floor(y / tile)),
00237       ind = (utmp ? 2 : 0) + (northp ? 1 : 0);
00238     if (! (ix >= mineasting[ind] && ix < maxeasting[ind]) ) {
00239       if (ix == maxeasting[ind] && x == maxeasting[ind] * tile)
00240         x -= eps;
00241       else
00242         throw out_of_range("Easting " + str(int(floor(x/1000)))
00243                            + "km not in MGRS/"
00244                            + (utmp ? "UTM" : "UPS") + " range for "
00245                            + (northp ? "N" : "S" )
00246                            + " hemisphere ["
00247                            + str(mineasting[ind]*tile/1000) + "km, "
00248                            + str(maxeasting[ind]*tile/1000) + "km)");
00249     }
00250     if (! (iy >= minnorthing[ind] && iy < maxnorthing[ind]) ) {
00251       if (iy == maxnorthing[ind] && y == maxnorthing[ind] * tile)
00252         y -= eps;
00253       else
00254         throw out_of_range("Northing " + str(int(floor(y/1000)))
00255                            + "km not in MGRS/"
00256                            + (utmp ? "UTM" : "UPS") + " range for "
00257                            + (northp ? "N" : "S" )
00258                            + " hemisphere ["
00259                            + str(minnorthing[ind]*tile/1000) + "km, "
00260                            + str(maxnorthing[ind]*tile/1000) + "km)");
00261     }
00262 
00263     // Correct the UTM northing and hemisphere if necessary
00264     if (utmp) {
00265       if (northp && iy < minutmNrow) {
00266         northp = false;
00267         y += utmNshift;
00268       } else if (!northp && iy >= maxutmSrow) {
00269         if (y == maxutmSrow * tile)
00270           // If on equator retain S hemisphere
00271           y -= eps;
00272         else {
00273           northp = true;
00274           y -= utmNshift;
00275         }
00276       }
00277     }
00278   }
00279 
00280   int MGRS::UTMRow(int iband, int icol, int irow) throw() {
00281     // Input is MGRS (periodic) row index and output is true row index.  Band
00282     // index is in [-10, 10) (as returned by LatitudeBand).  Column index
00283     // origin is easting = 100km.  Returns maxutmSrow if irow and iband are
00284     // incompatible.  Row index origin is equator.
00285 
00286     // Estimate center row number for latitude band
00287     // 90 deg = 100 tiles; 1 band = 8 deg = 100*8/90 tiles
00288     double c = 100 * (8 * iband + 4)/90.0;
00289     bool northp = iband >= 0;
00290     int
00291       minrow = iband > -10 ? int(floor(c - 4.3 - 0.1 * northp)) : -90,
00292       maxrow = iband <   9 ? int(floor(c + 4.4 - 0.1 * northp)) :  94,
00293       baserow = (minrow + maxrow) / 2 - utmrowperiod / 2;
00294     // Add maxutmSrow = 5 * utmrowperiod to ensure operand is positive
00295     irow = (irow - baserow + maxutmSrow) % utmrowperiod + baserow;
00296     if (irow < minrow || irow > maxrow) {
00297       // Northing = 71*100km and 80*100km intersect band boundaries
00298       // The following deals with these special cases.
00299       int
00300         // Fold [-10,-1] -> [9,0]
00301         sband = iband >= 0 ? iband : - iband  - 1,
00302         // Fold [-90,-1] -> [89,0]
00303         srow = irow >= 0 ? irow : -irow - 1,
00304         // Fold [4,7] -> [3,0]
00305         scol = icol < 4 ? icol : -icol + 7;
00306       if ( ! ( (srow == 70 && sband == 8 && scol >= 2) ||
00307                (srow == 71 && sband == 7 && scol <= 2) ||
00308                (srow == 79 && sband == 9 && scol >= 1) ||
00309                (srow == 80 && sband == 8 && scol <= 1) ) )
00310         irow = maxutmSrow;
00311     }
00312     return irow;
00313   }
00314 
00315 } // namespace GeographicLib