You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

utm.cpp 13 kB

2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. // UTM.cpp
  2. // Original Javascript by Chuck Taylor
  3. // Port to C++ by Alex Hajnal
  4. // Adapted for HDmaps usage by Juan Zaratiegui
  5. //
  6. // (Port license follows)
  7. // This is a simple port of the code on the Geographic/UTM Coordinate Converter
  8. // (1) page from Javascript to C++. Using this you can easily convert between
  9. // UTM and WGS84 (latitude and longitude). Accuracy seems to be around 50cm (I
  10. // suspect rounding errors are limiting precision). This code is provided as-is
  11. // and has been minimally tested; enjoy but use at your own risk! The license
  12. // for UTM.cpp and UTM.h is the same as the original Javascript: "The C++ source
  13. // code in UTM.cpp and UTM.h may be copied and reused without restriction."
  14. //
  15. // 1) http://home.hiwaay.net/~taylorc/toolbox/geography/geoutm.html
  16. #include "geo/private/coord/utm.h"
  17. #include "geo/private/util/angle_util.h"
  18. namespace ns
  19. {
  20. namespace geo
  21. {
  22. namespace
  23. {
  24. // WGS84 ellipsoid model constants
  25. constexpr double semiMajorAxis = 6378137.0;
  26. constexpr double semiMinorAxis = 6356752.31424518;
  27. // metric adjusments
  28. constexpr double x_adjust = 500000.0;
  29. constexpr double y_sh_adjust = 10000000.0;
  30. // Precomputed
  31. constexpr auto semiMinorAxis2 = semiMinorAxis * semiMinorAxis;
  32. constexpr auto semiMajorAxis2 = semiMajorAxis * semiMajorAxis;
  33. constexpr auto ep2 = (semiMajorAxis2 - semiMinorAxis2) / semiMinorAxis2;
  34. constexpr auto semiAxisDiff = semiMajorAxis - semiMinorAxis;
  35. constexpr auto semiAxisSum = semiMajorAxis + semiMinorAxis;
  36. constexpr auto n = semiAxisDiff / semiAxisSum;
  37. constexpr auto n2 = n * n;
  38. constexpr auto n3 = n2 * n;
  39. constexpr auto n4 = n2 * n2;
  40. constexpr auto n5 = n3 * n2;
  41. constexpr auto alpha = (semiAxisSum / 2.0) * (1.0 + (n2 / 4.0) + (n4 / 64.0));
  42. constexpr auto beta = (-3.0 * n / 2.0) + (9.0 * n3 / 16.0) + (-3.0 * n5 / 32.0);
  43. constexpr auto gamma = (15.0 * n2 / 16.0) + (-15.0 * n4 / 32.0);
  44. constexpr auto delta = (-35.0 * n3 / 48.0) + (105.0 * n5 / 256.0);
  45. constexpr auto epsilon = (315.0 * n4 / 512.0);
  46. constexpr auto alpha_ = alpha;
  47. constexpr auto beta_ =
  48. (3.0 * n / 2.0) + (-27.0 * n3 / 32.0) + (269.0 * n5 / 512.0);
  49. constexpr auto gamma_ = (21.0 * n2 / 16.0) + (-55.0 * n4 / 32.0);
  50. constexpr auto delta_ = (151.0 * n3 / 96.0) + (-417.0 * n5 / 128.0);
  51. constexpr auto epsilon_ = (1097.0 * n4 / 512.0);
  52. constexpr double UTMScaleFactor = 0.9996;
  53. constexpr double DegToRad(const double deg)
  54. {
  55. return (deg / 180.0) * c_pi();
  56. }
  57. constexpr double RadToDeg(const double rad)
  58. {
  59. return (rad / c_pi()) * 180.0;
  60. }
  61. // UTMCentralMeridian
  62. // Determines the central meridian for the given UTM zone.
  63. //
  64. // Inputs:
  65. // zone - An integer value designating the UTM zone, range [1,60].
  66. //
  67. // Returns:
  68. // The central meridian for the given UTM zone, in radians
  69. // Range of the central meridian is the radian equivalent of [-177,+177].
  70. constexpr double UTMCentralMeridian(const unsigned zone)
  71. {
  72. return DegToRad(-183.0 + (static_cast<double>(zone) * 6.0));
  73. }
  74. // ArcLengthOfMeridian
  75. // Computes the ellipsoidal distance from the equator to a point at a
  76. // given latitude.
  77. //
  78. // Reference: Hoffmann-Wellenhof, B., Lichtenegger, H., and Collins, J.,
  79. // GPS: Theory and Practice, 3rd ed. New York: Springer-Verlag Wien, 1994.
  80. //
  81. // Inputs:
  82. // phi - Latitude of the point, in radians.
  83. //
  84. // Globals:
  85. // semiMajorAxis - Ellipsoid model major axis.
  86. // semiMinorAxis - Ellipsoid model minor axis.
  87. //
  88. // Returns:
  89. // The ellipsoidal distance of the point from the equator, in meters.
  90. double ArcLengthOfMeridian(const double phi)
  91. {
  92. /* Now calculate the sum of the series and return */
  93. return alpha *
  94. (phi + (beta * std::sin(2.0 * phi)) + (gamma * std::sin(4.0 * phi)) +
  95. (delta * std::sin(6.0 * phi)) + (epsilon * std::sin(8.0 * phi)));
  96. }
  97. // MapLatLonToXY
  98. // Converts a latitude/longitude pair to x and y coordinates in the
  99. // Transverse Mercator projection. Note that Transverse Mercator is not
  100. // the same as UTM; a scale factor is required to convert between them.
  101. //
  102. // Reference: Hoffmann-Wellenhof, B., Lichtenegger, H., and Collins, J.,
  103. // GPS: Theory and Practice, 3rd ed. New York: Springer-Verlag Wien, 1994.
  104. //
  105. // Inputs:
  106. // phi - Latitude of the point, in radians.
  107. // lambda - Longitude of the point, in radians.
  108. // lambda0 - Longitude of the central meridian to be used, in radians.
  109. //
  110. // Returns a tuple of the values:
  111. // x - The x coordinate of the computed point.
  112. // y - The y coordinate of the computed point.
  113. //
  114. std::tuple<double, double> MapLatLonToXY(const double phi, const double lambda,
  115. const double lambda0)
  116. {
  117. const double cosphi = std::cos(phi);
  118. const double cosphi2 = cosphi * cosphi;
  119. const double cosphi4 = cosphi2 * cosphi2;
  120. const double cosphi6 = cosphi4 * cosphi2;
  121. /* Precalculate nu2 */
  122. const double nu2 = ep2 * cosphi2;
  123. const double nu2_2 = nu2 * nu2;
  124. /* Precalculate N */
  125. const double N = semiMajorAxis2 / (semiMinorAxis * std::sqrt(1 + nu2));
  126. /* Precalculate t */
  127. const double t = std::tan(phi);
  128. const double t2 = t * t;
  129. const double t4 = t2 * t2;
  130. const double t6 = t4 * t2;
  131. const double t2nu2 = t2 * nu2;
  132. /* Precalculate l */
  133. const double l = lambda - lambda0;
  134. const double l2 = l * l;
  135. const double l4 = l2 * l2;
  136. const double l6 = l2 * l4;
  137. /* Precalculate coefficients for l**n in the equations below
  138. so a normal human being can read the expressions for easting
  139. and northing
  140. -- l**1 and l**2 have coefficients of 1.0 */
  141. const double l3coef = 1.0 - t2 + nu2;
  142. const double l4coef = 5.0 - t2 + 9 * nu2 + 4.0 * nu2_2;
  143. const double l5coef = 5.0 - 18.0 * t2 + t4 + 14.0 * nu2 - 58.0 * t2nu2;
  144. const double l6coef = 61.0 - 58.0 * t2 + t4 + 270.0 * nu2 - 330.0 * t2nu2;
  145. const double l7coef = 61.0 - 479.0 * t2 + 179.0 * t4 - t6;
  146. const double l8coef = 1385.0 - 3111.0 * t2 + 543.0 * t4 - t6;
  147. /* Calculate easting (x) */
  148. const double x =
  149. N * cosphi * l *
  150. (1.0 + (cosphi2 * l3coef * l2 / 6.0) + (cosphi4 * l5coef * l4 / 120.0) +
  151. (cosphi6 * l7coef * l6 / 5040.0));
  152. /* Calculate northing (y) */
  153. const double y =
  154. ArcLengthOfMeridian(phi) +
  155. (t * N * cosphi2 * l2 / 2.0) * (1.0 + (cosphi2 * l4coef * l2 / 12.0) +
  156. (cosphi4 * l6coef * l4 / 360.0) +
  157. (cosphi6 * l8coef * l6 / 20160.0));
  158. return std::make_tuple(x, y);
  159. }
  160. // FootpointLatitude
  161. //
  162. // Computes the footpoint latitude for use in converting transverse
  163. // Mercator coordinates to ellipsoidal coordinates.
  164. //
  165. // Reference: Hoffmann-Wellenhof, B., Lichtenegger, H., and Collins, J.,
  166. // GPS: Theory and Practice, 3rd ed. New York: Springer-Verlag Wien, 1994.
  167. //
  168. // Inputs:
  169. // y - The UTM northing coordinate, in meters.
  170. //
  171. // Returns:
  172. // The footpoint latitude, in radians.
  173. double FootpointLatitude(const double y)
  174. {
  175. /* Precalculate y_ (Eq. 10.23) */
  176. const auto y_ = y / alpha_;
  177. /* Now calculate the sum of the series (Eq. 10.21) */
  178. return y_ + (beta_ * std::sin(2.0 * y_)) + (gamma_ * std::sin(4.0 * y_)) +
  179. (delta_ * std::sin(6.0 * y_)) + (epsilon_ * std::sin(8.0 * y_));
  180. }
  181. // MapXYToLatLon
  182. // Converts x and y coordinates in the Transverse Mercator projection to
  183. // a latitude/longitude pair. Note that Transverse Mercator is not
  184. // the same as UTM; a scale factor is required to convert between them.
  185. //
  186. // Reference: Hoffmann-Wellenhof, B., Lichtenegger, H., and Collins, J.,
  187. // GPS: Theory and Practice, 3rd ed. New York: Springer-Verlag Wien, 1994.
  188. //
  189. // Inputs:
  190. // x - The easting of the point, in meters.
  191. // y - The northing of the point, in meters.
  192. // lambda0 - Longitude of the central meridian to be used, in radians.
  193. //
  194. // Returns a tuple of the values:
  195. // phi - Latitude in radians.
  196. // lambda - Longitude in radians.
  197. //
  198. std::tuple<double, double> MapXYToLatLon(const double x, const double y,
  199. const double lambda0)
  200. {
  201. /* Get the value of phif, the footpoint latitude. */
  202. const auto phif = FootpointLatitude(y);
  203. const double cosphi = std::cos(phif);
  204. const double cosphi2 = cosphi * cosphi;
  205. /* Precalculate nu2 */
  206. const double nuf2 = ep2 * cosphi2;
  207. const double nuf22 = nuf2 * nuf2;
  208. /* Precalculate Nf and initialize Nfpow */
  209. const double Nf = semiMajorAxis2 / (semiMinorAxis * std::sqrt(1 + nuf2));
  210. double Nfpow = Nf;
  211. const double tanphi = std::tan(phif);
  212. const double tanphi2 = tanphi * tanphi;
  213. const double tanphi4 = tanphi2 * tanphi2;
  214. const double tanphi6 = tanphi4 * tanphi2;
  215. /* Precalculate fractional coefficients for x**n in the equations
  216. below to simplify the expressions for latitude and longitude. */
  217. const auto x1frac = 1.0 / (Nfpow * cosphi);
  218. Nfpow *= Nf; /* now equals Nf**2) */
  219. const auto x2frac = tanphi / (2.0 * Nfpow);
  220. Nfpow *= Nf; /* now equals Nf**3) */
  221. const auto x3frac = 1.0 / (6.0 * Nfpow * cosphi);
  222. Nfpow *= Nf; /* now equals Nf**4) */
  223. const auto x4frac = tanphi / (24.0 * Nfpow);
  224. Nfpow *= Nf; /* now equals Nf**5) */
  225. const auto x5frac = 1.0 / (120.0 * Nfpow * cosphi);
  226. Nfpow *= Nf; /* now equals Nf**6) */
  227. const auto x6frac = tanphi / (720.0 * Nfpow);
  228. Nfpow *= Nf; /* now equals Nf**7) */
  229. const auto x7frac = 1.0 / (5040.0 * Nfpow * cosphi);
  230. Nfpow *= Nf; /* now equals Nf**8) */
  231. const auto x8frac = tanphi / (40320.0 * Nfpow);
  232. /* Precalculate polynomial coefficients for x**n.
  233. -- x**1 does not have a polynomial coefficient. */
  234. const auto x2poly = -1.0 - nuf2;
  235. const auto x3poly = -1.0 - 2 * tanphi2 - nuf2;
  236. const auto x4poly = 5.0 + 3.0 * tanphi2 + 6.0 * nuf2 -
  237. 6.0 * tanphi2 * nuf2 - 3.0 * nuf22 -
  238. 9.0 * tanphi2 * nuf22;
  239. const auto x5poly = 5.0 + 28.0 * tanphi2 + 24.0 * tanphi4 + 6.0 * nuf2 +
  240. 8.0 * tanphi2 * nuf2;
  241. const auto x6poly = -61.0 - 90.0 * tanphi2 - 45.0 * tanphi4 - 107.0 * nuf2 +
  242. 162.0 * tanphi2 * nuf2;
  243. const auto x7poly =
  244. -61.0 - 662.0 * tanphi2 - 1320.0 * tanphi4 - 720.0 * tanphi6;
  245. const auto x8poly =
  246. 1385.0 + 3633.0 * tanphi2 + 4095.0 * tanphi4 + 1575 * tanphi6;
  247. const auto x2 = x * x;
  248. const auto x3 = x2 * x;
  249. const auto x4 = x2 * x2;
  250. const auto x5 = x3 * x2;
  251. const auto x6 = x3 * x3;
  252. const auto x7 = x4 * x3;
  253. const auto x8 = x4 * x4;
  254. /* Calculate latitude */
  255. const auto phi = phif + x2frac * x2poly * x2 + x4frac * x4poly * x4 +
  256. x6frac * x6poly * x6 + x8frac * x8poly * x8;
  257. /* Calculate longitude */
  258. const auto lambda = lambda0 + x1frac * x + x3frac * x3poly * x3 +
  259. x5frac * x5poly * x5 + x7frac * x7poly * x7;
  260. return std::make_tuple(phi, lambda);
  261. }
  262. } // namespace
  263. std::tuple<unsigned, double, double> LonLatToUTMXY(const double lon,
  264. const double lat,
  265. unsigned zone)
  266. {
  267. if (zone == 0)
  268. {
  269. zone = static_cast<unsigned>(std::floor((lon + 180.0) / 6.0)) + 1;
  270. }
  271. double x, y;
  272. std::tie(x, y) =
  273. MapLatLonToXY(DegToRad(lat), DegToRad(lon), UTMCentralMeridian(zone));
  274. // Adjust easting and northing for UTM system.
  275. x *= UTMScaleFactor;
  276. x += x_adjust;
  277. y *= UTMScaleFactor;
  278. if (y < 0.0)
  279. {
  280. y = y + y_sh_adjust;
  281. }
  282. return std::make_tuple(zone, x, y);
  283. }
  284. std::tuple<double, double> UTMXYToLonLat(const unsigned zone,
  285. const bool southen_hemisphere,
  286. double x, double y)
  287. {
  288. x -= x_adjust;
  289. x /= UTMScaleFactor;
  290. if (southen_hemisphere)
  291. {
  292. y -= y_sh_adjust;
  293. }
  294. y /= UTMScaleFactor;
  295. const double central_meridian = UTMCentralMeridian(zone);
  296. const auto& latlon = MapXYToLatLon(x, y, central_meridian);
  297. return std::make_tuple(RadToDeg(std::get<1>(latlon)),
  298. RadToDeg(std::get<0>(latlon)));
  299. }
  300. std::tuple<bool, double, double> getUTMZoneBorderLongitude(unsigned zone)
  301. {
  302. if (zone >= 1 && zone <= 60)
  303. {
  304. double left = 180.0 + (zone - 1) * 6.0;
  305. if (left > 360)
  306. left -= 360;
  307. double right = 180 + (zone)*6.0;
  308. if (right > 360)
  309. right -= 360;
  310. return std::make_tuple(true, left, right);
  311. }
  312. return std::make_tuple(false, 0, 0);
  313. }
  314. } // namespace geo
  315. } // namespace ns