qsc.hpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. // Boost.Geometry - gis-projections (based on PROJ4)
  2. // Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.
  3. // This file was modified by Oracle on 2017, 2018.
  4. // Modifications copyright (c) 2017-2018, Oracle and/or its affiliates.
  5. // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.
  6. // Use, modification and distribution is subject to the Boost Software License,
  7. // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
  8. // http://www.boost.org/LICENSE_1_0.txt)
  9. // This file is converted from PROJ4, http://trac.osgeo.org/proj
  10. // PROJ4 is originally written by Gerald Evenden (then of the USGS)
  11. // PROJ4 is maintained by Frank Warmerdam
  12. // PROJ4 is converted to Boost.Geometry by Barend Gehrels
  13. // Last updated version of proj: 5.0.0
  14. // Original copyright notice:
  15. // This implements the Quadrilateralized Spherical Cube (QSC) projection.
  16. // Copyright (c) 2011, 2012 Martin Lambers <marlam@marlam.de>
  17. // Permission is hereby granted, free of charge, to any person obtaining a
  18. // copy of this software and associated documentation files (the "Software"),
  19. // to deal in the Software without restriction, including without limitation
  20. // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  21. // and/or sell copies of the Software, and to permit persons to whom the
  22. // Software is furnished to do so, subject to the following conditions:
  23. // The above copyright notice and this permission notice shall be included
  24. // in all copies or substantial portions of the Software.
  25. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  26. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  27. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  28. // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  29. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  30. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  31. // DEALINGS IN THE SOFTWARE.
  32. // The QSC projection was introduced in:
  33. // [OL76]
  34. // E.M. O'Neill and R.E. Laubscher, "Extended Studies of a Quadrilateralized
  35. // Spherical Cube Earth Data Base", Naval Environmental Prediction Research
  36. // Facility Tech. Report NEPRF 3-76 (CSC), May 1976.
  37. // The preceding shift from an ellipsoid to a sphere, which allows to apply
  38. // this projection to ellipsoids as used in the Ellipsoidal Cube Map model,
  39. // is described in
  40. // [LK12]
  41. // M. Lambers and A. Kolb, "Ellipsoidal Cube Maps for Accurate Rendering of
  42. // Planetary-Scale Terrain Data", Proc. Pacfic Graphics (Short Papers), Sep.
  43. // 2012
  44. // You have to choose one of the following projection centers,
  45. // corresponding to the centers of the six cube faces:
  46. // phi0 = 0.0, lam0 = 0.0 ("front" face)
  47. // phi0 = 0.0, lam0 = 90.0 ("right" face)
  48. // phi0 = 0.0, lam0 = 180.0 ("back" face)
  49. // phi0 = 0.0, lam0 = -90.0 ("left" face)
  50. // phi0 = 90.0 ("top" face)
  51. // phi0 = -90.0 ("bottom" face)
  52. // Other projection centers will not work!
  53. // In the projection code below, each cube face is handled differently.
  54. // See the computation of the face parameter in the ENTRY0(qsc) function
  55. // and the handling of different face values (FACE_*) in the forward and
  56. // inverse projections.
  57. // Furthermore, the projection is originally only defined for theta angles
  58. // between (-1/4 * PI) and (+1/4 * PI) on the current cube face. This area
  59. // of definition is named AREA_0 in the projection code below. The other
  60. // three areas of a cube face are handled by rotation of AREA_0.
  61. #ifndef BOOST_GEOMETRY_PROJECTIONS_QSC_HPP
  62. #define BOOST_GEOMETRY_PROJECTIONS_QSC_HPP
  63. #include <boost/core/ignore_unused.hpp>
  64. #include <boost/geometry/util/math.hpp>
  65. #include <boost/geometry/srs/projections/impl/base_static.hpp>
  66. #include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
  67. #include <boost/geometry/srs/projections/impl/projects.hpp>
  68. #include <boost/geometry/srs/projections/impl/factory_entry.hpp>
  69. namespace boost { namespace geometry
  70. {
  71. namespace srs { namespace par4
  72. {
  73. struct qsc {}; // Quadrilateralized Spherical Cube
  74. }} //namespace srs::par4
  75. namespace projections
  76. {
  77. #ifndef DOXYGEN_NO_DETAIL
  78. namespace detail { namespace qsc
  79. {
  80. /* The six cube faces. */
  81. enum face_type {
  82. face_front = 0,
  83. face_right = 1,
  84. face_back = 2,
  85. face_left = 3,
  86. face_top = 4,
  87. face_bottom = 5
  88. };
  89. template <typename T>
  90. struct par_qsc
  91. {
  92. face_type face;
  93. T a_squared;
  94. T b;
  95. T one_minus_f;
  96. T one_minus_f_squared;
  97. };
  98. static const double epsilon10 = 1.e-10;
  99. /* The four areas on a cube face. AREA_0 is the area of definition,
  100. * the other three areas are counted counterclockwise. */
  101. enum area_type {
  102. area_0 = 0,
  103. area_1 = 1,
  104. area_2 = 2,
  105. area_3 = 3
  106. };
  107. /* Helper function for forward projection: compute the theta angle
  108. * and determine the area number. */
  109. template <typename T>
  110. inline T qsc_fwd_equat_face_theta(T const& phi, T const& y, T const& x, area_type *area)
  111. {
  112. static const T fourth_pi = detail::fourth_pi<T>();
  113. static const T half_pi = detail::half_pi<T>();
  114. static const T pi = detail::pi<T>();
  115. T theta;
  116. if (phi < epsilon10) {
  117. *area = area_0;
  118. theta = 0.0;
  119. } else {
  120. theta = atan2(y, x);
  121. if (fabs(theta) <= fourth_pi) {
  122. *area = area_0;
  123. } else if (theta > fourth_pi && theta <= half_pi + fourth_pi) {
  124. *area = area_1;
  125. theta -= half_pi;
  126. } else if (theta > half_pi + fourth_pi || theta <= -(half_pi + fourth_pi)) {
  127. *area = area_2;
  128. theta = (theta >= 0.0 ? theta - pi : theta + pi);
  129. } else {
  130. *area = area_3;
  131. theta += half_pi;
  132. }
  133. }
  134. return theta;
  135. }
  136. /* Helper function: shift the longitude. */
  137. template <typename T>
  138. inline T qsc_shift_lon_origin(T const& lon, T const& offset)
  139. {
  140. static const T pi = detail::pi<T>();
  141. static const T two_pi = detail::two_pi<T>();
  142. T slon = lon + offset;
  143. if (slon < -pi) {
  144. slon += two_pi;
  145. } else if (slon > +pi) {
  146. slon -= two_pi;
  147. }
  148. return slon;
  149. }
  150. /* Forward projection, ellipsoid */
  151. // template class, using CRTP to implement forward/inverse
  152. template <typename T, typename Parameters>
  153. struct base_qsc_ellipsoid
  154. : public base_t_fi<base_qsc_ellipsoid<T, Parameters>, T, Parameters>
  155. {
  156. par_qsc<T> m_proj_parm;
  157. inline base_qsc_ellipsoid(const Parameters& par)
  158. : base_t_fi<base_qsc_ellipsoid<T, Parameters>, T, Parameters>(*this, par)
  159. {}
  160. // FORWARD(e_forward)
  161. // Project coordinates from geographic (lon, lat) to cartesian (x, y)
  162. inline void fwd(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y) const
  163. {
  164. static const T fourth_pi = detail::fourth_pi<T>();
  165. static const T half_pi = detail::half_pi<T>();
  166. static const T pi = detail::pi<T>();
  167. T lat, lon;
  168. T theta, phi;
  169. T t, mu; /* nu; */
  170. area_type area;
  171. /* Convert the geodetic latitude to a geocentric latitude.
  172. * This corresponds to the shift from the ellipsoid to the sphere
  173. * described in [LK12]. */
  174. if (this->m_par.es != 0.0) {
  175. lat = atan(this->m_proj_parm.one_minus_f_squared * tan(lp_lat));
  176. } else {
  177. lat = lp_lat;
  178. }
  179. /* Convert the input lat, lon into theta, phi as used by QSC.
  180. * This depends on the cube face and the area on it.
  181. * For the top and bottom face, we can compute theta and phi
  182. * directly from phi, lam. For the other faces, we must use
  183. * unit sphere cartesian coordinates as an intermediate step. */
  184. lon = lp_lon;
  185. if (this->m_proj_parm.face == face_top) {
  186. phi = half_pi - lat;
  187. if (lon >= fourth_pi && lon <= half_pi + fourth_pi) {
  188. area = area_0;
  189. theta = lon - half_pi;
  190. } else if (lon > half_pi + fourth_pi || lon <= -(half_pi + fourth_pi)) {
  191. area = area_1;
  192. theta = (lon > 0.0 ? lon - pi : lon + pi);
  193. } else if (lon > -(half_pi + fourth_pi) && lon <= -fourth_pi) {
  194. area = area_2;
  195. theta = lon + half_pi;
  196. } else {
  197. area = area_3;
  198. theta = lon;
  199. }
  200. } else if (this->m_proj_parm.face == face_bottom) {
  201. phi = half_pi + lat;
  202. if (lon >= fourth_pi && lon <= half_pi + fourth_pi) {
  203. area = area_0;
  204. theta = -lon + half_pi;
  205. } else if (lon < fourth_pi && lon >= -fourth_pi) {
  206. area = area_1;
  207. theta = -lon;
  208. } else if (lon < -fourth_pi && lon >= -(half_pi + fourth_pi)) {
  209. area = area_2;
  210. theta = -lon - half_pi;
  211. } else {
  212. area = area_3;
  213. theta = (lon > 0.0 ? -lon + pi : -lon - pi);
  214. }
  215. } else {
  216. T q, r, s;
  217. T sinlat, coslat;
  218. T sinlon, coslon;
  219. if (this->m_proj_parm.face == face_right) {
  220. lon = qsc_shift_lon_origin(lon, +half_pi);
  221. } else if (this->m_proj_parm.face == face_back) {
  222. lon = qsc_shift_lon_origin(lon, +pi);
  223. } else if (this->m_proj_parm.face == face_left) {
  224. lon = qsc_shift_lon_origin(lon, -half_pi);
  225. }
  226. sinlat = sin(lat);
  227. coslat = cos(lat);
  228. sinlon = sin(lon);
  229. coslon = cos(lon);
  230. q = coslat * coslon;
  231. r = coslat * sinlon;
  232. s = sinlat;
  233. if (this->m_proj_parm.face == face_front) {
  234. phi = acos(q);
  235. theta = qsc_fwd_equat_face_theta(phi, s, r, &area);
  236. } else if (this->m_proj_parm.face == face_right) {
  237. phi = acos(r);
  238. theta = qsc_fwd_equat_face_theta(phi, s, -q, &area);
  239. } else if (this->m_proj_parm.face == face_back) {
  240. phi = acos(-q);
  241. theta = qsc_fwd_equat_face_theta(phi, s, -r, &area);
  242. } else if (this->m_proj_parm.face == face_left) {
  243. phi = acos(-r);
  244. theta = qsc_fwd_equat_face_theta(phi, s, q, &area);
  245. } else {
  246. /* Impossible */
  247. phi = theta = 0.0;
  248. area = area_0;
  249. }
  250. }
  251. /* Compute mu and nu for the area of definition.
  252. * For mu, see Eq. (3-21) in [OL76], but note the typos:
  253. * compare with Eq. (3-14). For nu, see Eq. (3-38). */
  254. mu = atan((12.0 / pi) * (theta + acos(sin(theta) * cos(fourth_pi)) - half_pi));
  255. // TODO: (cos(mu) * cos(mu)) could be replaced with sqr(cos(mu))
  256. t = sqrt((1.0 - cos(phi)) / (cos(mu) * cos(mu)) / (1.0 - cos(atan(1.0 / cos(theta)))));
  257. /* nu = atan(t); We don't really need nu, just t, see below. */
  258. /* Apply the result to the real area. */
  259. if (area == area_1) {
  260. mu += half_pi;
  261. } else if (area == area_2) {
  262. mu += pi;
  263. } else if (area == area_3) {
  264. mu += half_pi + pi;
  265. }
  266. /* Now compute x, y from mu and nu */
  267. /* t = tan(nu); */
  268. xy_x = t * cos(mu);
  269. xy_y = t * sin(mu);
  270. }
  271. /* Inverse projection, ellipsoid */
  272. // INVERSE(e_inverse)
  273. // Project coordinates from cartesian (x, y) to geographic (lon, lat)
  274. inline void inv(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat) const
  275. {
  276. static const T half_pi = detail::half_pi<T>();
  277. static const T pi = detail::pi<T>();
  278. T mu, nu, cosmu, tannu;
  279. T tantheta, theta, cosphi, phi;
  280. T t;
  281. int area;
  282. /* Convert the input x, y to the mu and nu angles as used by QSC.
  283. * This depends on the area of the cube face. */
  284. nu = atan(sqrt(xy_x * xy_x + xy_y * xy_y));
  285. mu = atan2(xy_y, xy_x);
  286. if (xy_x >= 0.0 && xy_x >= fabs(xy_y)) {
  287. area = area_0;
  288. } else if (xy_y >= 0.0 && xy_y >= fabs(xy_x)) {
  289. area = area_1;
  290. mu -= half_pi;
  291. } else if (xy_x < 0.0 && -xy_x >= fabs(xy_y)) {
  292. area = area_2;
  293. mu = (mu < 0.0 ? mu + pi : mu - pi);
  294. } else {
  295. area = area_3;
  296. mu += half_pi;
  297. }
  298. /* Compute phi and theta for the area of definition.
  299. * The inverse projection is not described in the original paper, but some
  300. * good hints can be found here (as of 2011-12-14):
  301. * http://fits.gsfc.nasa.gov/fitsbits/saf.93/saf.9302
  302. * (search for "Message-Id: <9302181759.AA25477 at fits.cv.nrao.edu>") */
  303. t = (pi / 12.0) * tan(mu);
  304. tantheta = sin(t) / (cos(t) - (1.0 / sqrt(2.0)));
  305. theta = atan(tantheta);
  306. cosmu = cos(mu);
  307. tannu = tan(nu);
  308. cosphi = 1.0 - cosmu * cosmu * tannu * tannu * (1.0 - cos(atan(1.0 / cos(theta))));
  309. if (cosphi < -1.0) {
  310. cosphi = -1.0;
  311. } else if (cosphi > +1.0) {
  312. cosphi = +1.0;
  313. }
  314. /* Apply the result to the real area on the cube face.
  315. * For the top and bottom face, we can compute phi and lam directly.
  316. * For the other faces, we must use unit sphere cartesian coordinates
  317. * as an intermediate step. */
  318. if (this->m_proj_parm.face == face_top) {
  319. phi = acos(cosphi);
  320. lp_lat = half_pi - phi;
  321. if (area == area_0) {
  322. lp_lon = theta + half_pi;
  323. } else if (area == area_1) {
  324. lp_lon = (theta < 0.0 ? theta + pi : theta - pi);
  325. } else if (area == area_2) {
  326. lp_lon = theta - half_pi;
  327. } else /* area == AREA_3 */ {
  328. lp_lon = theta;
  329. }
  330. } else if (this->m_proj_parm.face == face_bottom) {
  331. phi = acos(cosphi);
  332. lp_lat = phi - half_pi;
  333. if (area == area_0) {
  334. lp_lon = -theta + half_pi;
  335. } else if (area == area_1) {
  336. lp_lon = -theta;
  337. } else if (area == area_2) {
  338. lp_lon = -theta - half_pi;
  339. } else /* area == area_3 */ {
  340. lp_lon = (theta < 0.0 ? -theta - pi : -theta + pi);
  341. }
  342. } else {
  343. /* Compute phi and lam via cartesian unit sphere coordinates. */
  344. T q, r, s;
  345. q = cosphi;
  346. t = q * q;
  347. if (t >= 1.0) {
  348. s = 0.0;
  349. } else {
  350. s = sqrt(1.0 - t) * sin(theta);
  351. }
  352. t += s * s;
  353. if (t >= 1.0) {
  354. r = 0.0;
  355. } else {
  356. r = sqrt(1.0 - t);
  357. }
  358. /* Rotate q,r,s into the correct area. */
  359. if (area == area_1) {
  360. t = r;
  361. r = -s;
  362. s = t;
  363. } else if (area == area_2) {
  364. r = -r;
  365. s = -s;
  366. } else if (area == area_3) {
  367. t = r;
  368. r = s;
  369. s = -t;
  370. }
  371. /* Rotate q,r,s into the correct cube face. */
  372. if (this->m_proj_parm.face == face_right) {
  373. t = q;
  374. q = -r;
  375. r = t;
  376. } else if (this->m_proj_parm.face == face_back) {
  377. q = -q;
  378. r = -r;
  379. } else if (this->m_proj_parm.face == face_left) {
  380. t = q;
  381. q = r;
  382. r = -t;
  383. }
  384. /* Now compute phi and lam from the unit sphere coordinates. */
  385. lp_lat = acos(-s) - half_pi;
  386. lp_lon = atan2(r, q);
  387. if (this->m_proj_parm.face == face_right) {
  388. lp_lon = qsc_shift_lon_origin(lp_lon, -half_pi);
  389. } else if (this->m_proj_parm.face == face_back) {
  390. lp_lon = qsc_shift_lon_origin(lp_lon, -pi);
  391. } else if (this->m_proj_parm.face == face_left) {
  392. lp_lon = qsc_shift_lon_origin(lp_lon, +half_pi);
  393. }
  394. }
  395. /* Apply the shift from the sphere to the ellipsoid as described
  396. * in [LK12]. */
  397. if (this->m_par.es != 0.0) {
  398. int invert_sign;
  399. T tanphi, xa;
  400. invert_sign = (lp_lat < 0.0 ? 1 : 0);
  401. tanphi = tan(lp_lat);
  402. xa = this->m_proj_parm.b / sqrt(tanphi * tanphi + this->m_proj_parm.one_minus_f_squared);
  403. lp_lat = atan(sqrt(this->m_par.a * this->m_par.a - xa * xa) / (this->m_proj_parm.one_minus_f * xa));
  404. if (invert_sign) {
  405. lp_lat = -lp_lat;
  406. }
  407. }
  408. }
  409. static inline std::string get_name()
  410. {
  411. return "qsc_ellipsoid";
  412. }
  413. };
  414. // Quadrilateralized Spherical Cube
  415. template <typename Parameters, typename T>
  416. inline void setup_qsc(Parameters& par, par_qsc<T>& proj_parm)
  417. {
  418. static const T fourth_pi = detail::fourth_pi<T>();
  419. static const T half_pi = detail::half_pi<T>();
  420. /* Determine the cube face from the center of projection. */
  421. if (par.phi0 >= half_pi - fourth_pi / 2.0) {
  422. proj_parm.face = face_top;
  423. } else if (par.phi0 <= -(half_pi - fourth_pi / 2.0)) {
  424. proj_parm.face = face_bottom;
  425. } else if (fabs(par.lam0) <= fourth_pi) {
  426. proj_parm.face = face_front;
  427. } else if (fabs(par.lam0) <= half_pi + fourth_pi) {
  428. proj_parm.face = (par.lam0 > 0.0 ? face_right : face_left);
  429. } else {
  430. proj_parm.face = face_back;
  431. }
  432. /* Fill in useful values for the ellipsoid <-> sphere shift
  433. * described in [LK12]. */
  434. if (par.es != 0.0) {
  435. proj_parm.a_squared = par.a * par.a;
  436. proj_parm.b = par.a * sqrt(1.0 - par.es);
  437. proj_parm.one_minus_f = 1.0 - (par.a - proj_parm.b) / par.a;
  438. proj_parm.one_minus_f_squared = proj_parm.one_minus_f * proj_parm.one_minus_f;
  439. }
  440. }
  441. }} // namespace detail::qsc
  442. #endif // doxygen
  443. /*!
  444. \brief Quadrilateralized Spherical Cube projection
  445. \ingroup projections
  446. \tparam Geographic latlong point type
  447. \tparam Cartesian xy point type
  448. \tparam Parameters parameter type
  449. \par Projection characteristics
  450. - Azimuthal
  451. - Spheroid
  452. \par Example
  453. \image html ex_qsc.gif
  454. */
  455. template <typename T, typename Parameters>
  456. struct qsc_ellipsoid : public detail::qsc::base_qsc_ellipsoid<T, Parameters>
  457. {
  458. inline qsc_ellipsoid(const Parameters& par) : detail::qsc::base_qsc_ellipsoid<T, Parameters>(par)
  459. {
  460. detail::qsc::setup_qsc(this->m_par, this->m_proj_parm);
  461. }
  462. };
  463. #ifndef DOXYGEN_NO_DETAIL
  464. namespace detail
  465. {
  466. // Static projection
  467. BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::qsc, qsc_ellipsoid, qsc_ellipsoid)
  468. // Factory entry(s)
  469. template <typename T, typename Parameters>
  470. class qsc_entry : public detail::factory_entry<T, Parameters>
  471. {
  472. public :
  473. virtual base_v<T, Parameters>* create_new(const Parameters& par) const
  474. {
  475. return new base_v_fi<qsc_ellipsoid<T, Parameters>, T, Parameters>(par);
  476. }
  477. };
  478. template <typename T, typename Parameters>
  479. inline void qsc_init(detail::base_factory<T, Parameters>& factory)
  480. {
  481. factory.add_to_factory("qsc", new qsc_entry<T, Parameters>);
  482. }
  483. } // namespace detail
  484. #endif // doxygen
  485. } // namespace projections
  486. }} // namespace boost::geometry
  487. #endif // BOOST_GEOMETRY_PROJECTIONS_QSC_HPP