libcuproj  24.04.00
Loading...
Searching...
No Matches
transverse_mercator.cuh
Go to the documentation of this file.
1/*
2 * Copyright (c) 2023, NVIDIA CORPORATION.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// Code in this file is originally from the [OSGeo/PROJ project](https://github.com/OSGeo/PROJ)
18// and has been modified to run on the GPU using CUDA.
19//
20// PROJ License from https://github.com/OSGeo/PROJ/blob/9.2/COPYING:
21// Note however that the file it is taken from did not have a copyright notice.
22/*
23 Copyright information can be found in source files.
24
25 --------------
26
27 Permission is hereby granted, free of charge, to any person obtaining a
28 copy of this software and associated documentation files (the "Software"),
29 to deal in the Software without restriction, including without limitation
30 the rights to use, copy, modify, merge, publish, distribute, sublicense,
31 and/or sell copies of the Software, and to permit persons to whom the
32 Software is furnished to do so, subject to the following conditions:
33
34 The above copyright notice and this permission notice shall be included
35 in all copies or substantial portions of the Software.
36
37 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
38 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
40 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
41 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
42 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
43 DEALINGS IN THE SOFTWARE.
44*/
45
46// The following text is taken from the PROJ file
47// [tmerc.cpp](https://github.com/OSGeo/PROJ/blob/9.2/src/projections/tmerc.cpp)
48// see the file LICENSE_PROJ in the cuspatial repository root directory for the original PROJ
49// license text.
50/*****************************************************************************/
51//
52// Exact Transverse Mercator functions
53//
54//
55// The code in this file is largly based upon procedures:
56//
57// Written by: Knud Poder and Karsten Engsager
58//
59// Based on math from: R.Koenig and K.H. Weise, "Mathematische
60// Grundlagen der hoeheren Geodaesie und Kartographie,
61// Springer-Verlag, Berlin/Goettingen" Heidelberg, 1951.
62//
63// Modified and used here by permission of Reference Networks
64// Division, Kort og Matrikelstyrelsen (KMS), Copenhagen, Denmark
65//
66/*****************************************************************************/
67
68#pragma once
69
70#include <cuproj/detail/utility/cuda.hpp>
71#include <cuproj/detail/wrap_to_pi.hpp>
72#include <cuproj/ellipsoid.hpp>
75
76#include <thrust/iterator/transform_iterator.h>
77
78#include <assert.h>
79
80namespace cuproj {
81
87namespace detail {
88
100template <typename T>
101inline static CUPROJ_HOST_DEVICE T gatg(T const* p1, int len_p1, T B, T cos_2B, T sin_2B)
102{
103 T h = 0, h1, h2 = 0;
104
105 T const two_cos_2B = 2 * cos_2B;
106 T const* p = p1 + len_p1;
107 h1 = *--p;
108 while (p - p1) {
109 h = -h2 + two_cos_2B * h1 + *--p;
110 h2 = h1;
111 h1 = h;
112 }
113 return (B + h * sin_2B);
114}
115
132template <typename T>
133inline static CUPROJ_HOST_DEVICE T clenshaw_complex(
134 T const* a, int size, T sin_arg_r, T cos_arg_r, T sinh_arg_i, T cosh_arg_i, T* R, T* I)
135{
136 T r, i, hr, hr1, hr2, hi, hi1, hi2;
137
138 // arguments
139 T const* p = a + size;
140 r = 2 * cos_arg_r * cosh_arg_i;
141 i = -2 * sin_arg_r * sinh_arg_i;
142
143 // summation loop
144 hi1 = hr1 = hi = 0;
145 hr = *--p;
146 for (; a - p;) {
147 hr2 = hr1;
148 hi2 = hi1;
149 hr1 = hr;
150 hi1 = hi;
151 hr = -hr2 + r * hr1 - i * hi1 + *--p;
152 hi = -hi2 + i * hr1 + r * hi1;
153 }
154
155 r = sin_arg_r * cosh_arg_i;
156 i = cos_arg_r * sinh_arg_i;
157 *R = r * hr - i * hi;
158 *I = r * hi + i * hr;
159 return *R;
160}
161
173template <typename T>
174static CUPROJ_HOST_DEVICE T clenshaw_real(T const* a, int size, T arg_r)
175{
176 T r, hr, hr1, hr2, cos_arg_r;
177
178 T const* p = a + size;
179 cos_arg_r = cos(arg_r);
180 r = 2 * cos_arg_r;
181
182 // summation loop
183 hr1 = 0;
184 hr = *--p;
185 for (; a - p;) {
186 hr2 = hr1;
187 hr1 = hr;
188 hr = -hr2 + r * hr1 + *--p;
189 }
190 return sin(arg_r) * hr;
191}
192} // namespace detail
193
194template <typename Coordinate, typename T = typename Coordinate::value_type>
195class transverse_mercator : operation<Coordinate> {
196 public:
197 static constexpr int ETMERC_ORDER = 6;
198
204 CUPROJ_HOST_DEVICE transverse_mercator(projection_parameters<T> const& params) : params_(params)
205 {
206 }
207
215 CUPROJ_HOST_DEVICE Coordinate operator()(Coordinate const& coord, direction dir) const
216 {
217 if (dir == direction::FORWARD)
218 return forward(coord);
219 else
220 return inverse(coord);
221 }
222
223 static constexpr T utm_central_meridian = T{500000}; // false easting center of UTM zone
224 // false northing center of UTM zone
225 static constexpr T utm_central_parallel(hemisphere h)
226 {
227 return (h == hemisphere::NORTH) ? T{0} : T{10000000};
228 }
229
237 {
238 params_ = input_params;
239
240 // so we don't have to qualify the class name everywhere.
241 auto& params = params_;
242 auto& tmerc_params = params_.tmerc_params_;
243 auto& ellipsoid = params_.ellipsoid_;
244
245 assert(ellipsoid.es > 0);
246
247 params.x0 = utm_central_meridian;
248 params.y0 = utm_central_parallel(params.utm_hemisphere_);
249
250 if (params.utm_zone_ > 0 && params.utm_zone_ <= 60) {
251 --params.utm_zone_;
252 } else {
253 params.utm_zone_ = lround((floor((detail::wrap_to_pi(params.lam0_) + M_PI) * 30. / M_PI)));
254 params.utm_zone_ = std::clamp(params.utm_zone_, 0, 59);
255 }
256
257 params.lam0_ = (params.utm_zone_ + .5) * M_PI / 30. - M_PI;
258 params.k0 = T{0.9996};
259 params.phi0 = T{0};
260
261 // third flattening
262 T const n = ellipsoid.n;
263 T np = n;
264
265 // COEFFICIENTS OF TRIG SERIES GEO <-> GAUSS
266 // cgb := Gaussian -> Geodetic, KW p190 - 191 (61) - (62)
267 // cbg := Geodetic -> Gaussian, KW p186 - 187 (51) - (52)
268 // ETMERC_ORDER = 6th degree : Engsager and Poder: ICC2007
269
270 tmerc_params.cgb[0] =
271 n *
272 (2 + n * (-2 / 3.0 + n * (-2 + n * (116 / 45.0 + n * (26 / 45.0 + n * (-2854 / 675.0))))));
273 tmerc_params.cbg[0] =
274 n * (-2 + n * (2 / 3.0 +
275 n * (4 / 3.0 + n * (-82 / 45.0 + n * (32 / 45.0 + n * (4642 / 4725.0))))));
276 np *= n;
277 tmerc_params.cgb[1] =
278 np * (7 / 3.0 + n * (-8 / 5.0 + n * (-227 / 45.0 + n * (2704 / 315.0 + n * (2323 / 945.0)))));
279 tmerc_params.cbg[1] =
280 np * (5 / 3.0 + n * (-16 / 15.0 + n * (-13 / 9.0 + n * (904 / 315.0 + n * (-1522 / 945.0)))));
281 np *= n;
282 // n^5 coeff corrected from 1262/105 -> -1262/105
283 tmerc_params.cgb[2] =
284 np * (56 / 15.0 + n * (-136 / 35.0 + n * (-1262 / 105.0 + n * (73814 / 2835.0))));
285 tmerc_params.cbg[2] =
286 np * (-26 / 15.0 + n * (34 / 21.0 + n * (8 / 5.0 + n * (-12686 / 2835.0))));
287 np *= n;
288 // n^5 coeff corrected from 322/35 -> 332/35
289 tmerc_params.cgb[3] = np * (4279 / 630.0 + n * (-332 / 35.0 + n * (-399572 / 14175.0)));
290 tmerc_params.cbg[3] = np * (1237 / 630.0 + n * (-12 / 5.0 + n * (-24832 / 14175.0)));
291 np *= n;
292 tmerc_params.cgb[4] = np * (4174 / 315.0 + n * (-144838 / 6237.0));
293 tmerc_params.cbg[4] = np * (-734 / 315.0 + n * (109598 / 31185.0));
294 np *= n;
295 tmerc_params.cgb[5] = np * (601676 / 22275.0);
296 tmerc_params.cbg[5] = np * (444337 / 155925.0);
297
298 // Constants of the projections
299 // Transverse Mercator (UTM, ITM, etc)
300 np = n * n;
301 // Norm. meridian quadrant, K&W p.50 (96), p.19 (38b), p.5 (2)
302 tmerc_params.Qn = params.k0 / (1 + n) * (1 + np * (1 / 4.0 + np * (1 / 64.0 + np / 256.0)));
303 // coef of trig series
304 // utg := ell. N, E -> sph. N, E, KW p194 (65)
305 // gtu := sph. N, E -> ell. N, E, KW p196 (69)
306 tmerc_params.utg[0] =
307 n * (-0.5 +
308 n * (2 / 3.0 +
309 n * (-37 / 96.0 + n * (1 / 360.0 + n * (81 / 512.0 + n * (-96199 / 604800.0))))));
310 tmerc_params.gtu[0] =
311 n * (0.5 + n * (-2 / 3.0 + n * (5 / 16.0 + n * (41 / 180.0 +
312 n * (-127 / 288.0 + n * (7891 / 37800.0))))));
313 tmerc_params.utg[1] =
314 np * (-1 / 48.0 +
315 n * (-1 / 15.0 + n * (437 / 1440.0 + n * (-46 / 105.0 + n * (1118711 / 3870720.0)))));
316 tmerc_params.gtu[1] =
317 np * (13 / 48.0 +
318 n * (-3 / 5.0 + n * (557 / 1440.0 + n * (281 / 630.0 + n * (-1983433 / 1935360.0)))));
319 np *= n;
320 tmerc_params.utg[2] =
321 np * (-17 / 480.0 + n * (37 / 840.0 + n * (209 / 4480.0 + n * (-5569 / 90720.0))));
322 tmerc_params.gtu[2] =
323 np * (61 / 240.0 + n * (-103 / 140.0 + n * (15061 / 26880.0 + n * (167603 / 181440.0))));
324 np *= n;
325 tmerc_params.utg[3] = np * (-4397 / 161280.0 + n * (11 / 504.0 + n * (830251 / 7257600.0)));
326 tmerc_params.gtu[3] = np * (49561 / 161280.0 + n * (-179 / 168.0 + n * (6601661 / 7257600.0)));
327 np *= n;
328 tmerc_params.utg[4] = np * (-4583 / 161280.0 + n * (108847 / 3991680.0));
329 tmerc_params.gtu[4] = np * (34729 / 80640.0 + n * (-3418889 / 1995840.0));
330 np *= n;
331 tmerc_params.utg[5] = np * (-20648693 / 638668800.0);
332 tmerc_params.gtu[5] = np * (212378941 / 319334400.0);
333
334 // Gaussian latitude value of the origin latitude
335 T const Z = detail::gatg(
336 tmerc_params.cbg, ETMERC_ORDER, params.phi0, cos(2 * params.phi0), sin(2 * params.phi0));
337
338 // Origin northing minus true northing at the origin latitude
339 // i.e. true northing = N - P->Zb
340 tmerc_params.Zb =
341 -tmerc_params.Qn * (Z + detail::clenshaw_real(tmerc_params.gtu, ETMERC_ORDER, 2 * Z));
342
343 return params;
344 }
345
346 private:
353 CUPROJ_HOST_DEVICE Coordinate forward(Coordinate const& coord) const
354 {
355 // so we don't have to qualify the class name everywhere.
356 auto& tmerc_params = this->params_.tmerc_params_;
357 auto& ellipsoid = this->params_.ellipsoid_;
358
359 // ell. LAT, LNG -> Gaussian LAT, LNG
360 T Cn =
361 detail::gatg(tmerc_params.cbg, ETMERC_ORDER, coord.y, cos(2 * coord.y), sin(2 * coord.y));
362
363 // Gaussian LAT, LNG -> compl. sph. LAT
364 T const sin_Cn = sin(Cn);
365 T const cos_Cn = cos(Cn);
366 T const sin_Ce = sin(coord.x);
367 T const cos_Ce = cos(coord.x);
368
369 T const cos_Cn_cos_Ce = cos_Cn * cos_Ce;
370 Cn = atan2(sin_Cn, cos_Cn_cos_Ce);
371
372 T const inv_denom_tan_Ce = 1. / hypot(sin_Cn, cos_Cn_cos_Ce);
373 T const tan_Ce = sin_Ce * cos_Cn * inv_denom_tan_Ce;
374
375 // Variant of the above: found not to be measurably faster
376 // T const sin_Ce_cos_Cn = sin_Ce*cos_Cn;
377 // T const denom = sqrt(1 - sin_Ce_cos_Cn * sin_Ce_cos_Cn);
378 // T const tan_Ce = sin_Ce_cos_Cn / denom;
379
380 // compl. sph. N, E -> ell. norm. N, E
381 T Ce = asinh(tan_Ce); /* Replaces: Ce = log(tan(FORTPI + Ce*0.5)); */
382
383 // Non-optimized version:
384 // T const sin_arg_r = sin(2*Cn);
385 // T const cos_arg_r = cos(2*Cn);
386 //
387 // Given:
388 // sin(2 * Cn) = 2 sin(Cn) cos(Cn)
389 // sin(atan(y)) = y / sqrt(1 + y^2)
390 // cos(atan(y)) = 1 / sqrt(1 + y^2)
391 // ==> sin(2 * Cn) = 2 tan_Cn / (1 + tan_Cn^2)
392 //
393 // cos(2 * Cn) = 2cos^2(Cn) - 1
394 // = 2 / (1 + tan_Cn^2) - 1
395 //
396 T const two_inv_denom_tan_Ce = 2 * inv_denom_tan_Ce;
397 T const two_inv_denom_tan_Ce_square = two_inv_denom_tan_Ce * inv_denom_tan_Ce;
398 T const tmp_r = cos_Cn_cos_Ce * two_inv_denom_tan_Ce_square;
399 T const sin_arg_r = sin_Cn * tmp_r;
400 T const cos_arg_r = cos_Cn_cos_Ce * tmp_r - 1;
401
402 // Non-optimized version:
403 // T const sinh_arg_i = sinh(2*Ce);
404 // T const cosh_arg_i = cosh(2*Ce);
405 //
406 // Given
407 // sinh(2 * Ce) = 2 sinh(Ce) cosh(Ce)
408 // sinh(asinh(y)) = y
409 // cosh(asinh(y)) = sqrt(1 + y^2)
410 // ==> sinh(2 * Ce) = 2 tan_Ce sqrt(1 + tan_Ce^2)
411 //
412 // cosh(2 * Ce) = 2cosh^2(Ce) - 1
413 // = 2 * (1 + tan_Ce^2) - 1
414 //
415 // and 1+tan_Ce^2 = 1 + sin_Ce^2 * cos_Cn^2 / (sin_Cn^2 + cos_Cn^2 *
416 // cos_Ce^2) = (sin_Cn^2 + cos_Cn^2 * cos_Ce^2 + sin_Ce^2 * cos_Cn^2) /
417 // (sin_Cn^2 + cos_Cn^2 * cos_Ce^2) = 1. / (sin_Cn^2 + cos_Cn^2 * cos_Ce^2)
418 // = inv_denom_tan_Ce^2
419
420 T const sinh_arg_i = tan_Ce * two_inv_denom_tan_Ce;
421 T const cosh_arg_i = two_inv_denom_tan_Ce_square - 1;
422
423 T dCn, dCe;
424 Cn += detail::clenshaw_complex(
425 tmerc_params.gtu, ETMERC_ORDER, sin_arg_r, cos_arg_r, sinh_arg_i, cosh_arg_i, &dCn, &dCe);
426
427 Ce += dCe;
428
429 CUPROJ_HOST_DEVICE_EXPECTS(fabs(Ce) <= 2.623395162778, // value comes from PROJ
430 "Coordinate transform outside projection domain");
431 Coordinate xy{0.0, 0.0};
432 xy.y = tmerc_params.Qn * Cn + tmerc_params.Zb; // Northing
433 xy.x = tmerc_params.Qn * Ce; // Easting
434
435 return xy;
436 }
437
443 CUPROJ_HOST_DEVICE Coordinate inverse(Coordinate const& coord) const
444 {
445 // so we don't have to qualify the class name everywhere.
446 auto& tmerc_params = this->params_.tmerc_params_;
447 auto& ellipsoid = this->params_.ellipsoid_;
448
449 // normalize N, E
450 T Cn = (coord.y - tmerc_params.Zb) / tmerc_params.Qn;
451 T Ce = coord.x / tmerc_params.Qn;
452
453 CUPROJ_HOST_DEVICE_EXPECTS(fabs(Ce) <= 2.623395162778, // value comes from PROJ
454 "Coordinate transform outside projection domain");
455
456 // norm. N, E -> compl. sph. LAT, LNG
457 T const sin_arg_r = sin(2 * Cn);
458 T const cos_arg_r = cos(2 * Cn);
459
460 // T const sinh_arg_i = sinh(2*Ce);
461 // T const cosh_arg_i = cosh(2*Ce);
462 T const exp_2_Ce = exp(2 * Ce);
463 T const half_inv_exp_2_Ce = T{0.5} / exp_2_Ce;
464 T const sinh_arg_i = T{0.5} * exp_2_Ce - half_inv_exp_2_Ce;
465 T const cosh_arg_i = T{0.5} * exp_2_Ce + half_inv_exp_2_Ce;
466
467 T dCn_ignored, dCe;
468 Cn += detail::clenshaw_complex(tmerc_params.utg,
470 sin_arg_r,
471 cos_arg_r,
472 sinh_arg_i,
473 cosh_arg_i,
474 &dCn_ignored,
475 &dCe);
476 Ce += dCe;
477
478 // compl. sph. LAT -> Gaussian LAT, LNG
479 T const sin_Cn = sin(Cn);
480 T const cos_Cn = cos(Cn);
481
482#if 0
483 // Non-optimized version:
484 T sin_Ce, cos_Ce;
485 Ce = atan (sinh (Ce)); // Replaces: Ce = 2*(atan(exp(Ce)) - FORTPI);
486 sin_Ce = sin (Ce);
487 cos_Ce = cos (Ce);
488 Ce = atan2 (sin_Ce, cos_Ce*cos_Cn);
489 Cn = atan2 (sin_Cn*cos_Ce, hypot (sin_Ce, cos_Ce*cos_Cn));
490#else
491 // One can divide both member of Ce = atan2(...) by cos_Ce, which
492 // gives: Ce = atan2 (tan_Ce, cos_Cn) = atan2(sinh(Ce), cos_Cn)
493 //
494 // and the same for Cn = atan2(...)
495 // Cn = atan2 (sin_Cn, hypot (sin_Ce, cos_Ce*cos_Cn)/cos_Ce)
496 // = atan2 (sin_Cn, hypot (sin_Ce/cos_Ce, cos_Cn))
497 // = atan2 (sin_Cn, hypot (tan_Ce, cos_Cn))
498 // = atan2 (sin_Cn, hypot (sinhCe, cos_Cn))
499 T const sinhCe = sinh(Ce);
500 Ce = atan2(sinhCe, cos_Cn);
501 T const modulus_Ce = hypot(sinhCe, cos_Cn);
502 Cn = atan2(sin_Cn, modulus_Ce);
503#endif
504
505 // Gaussian LAT, LNG -> ell. LAT, LNG
506
507 // Optimization of the computation of cos(2*Cn) and sin(2*Cn)
508 T const tmp = 2 * modulus_Ce / (sinhCe * sinhCe + 1);
509 T const sin_2_Cn = sin_Cn * tmp;
510 T const cos_2_Cn = tmp * modulus_Ce - 1.;
511 // T const cos_2_Cn = cos(2 * Cn);
512 // T const sin_2_Cn = sin(2 * Cn);
513
514 return Coordinate{Ce, detail::gatg(tmerc_params.cgb, ETMERC_ORDER, Cn, cos_2_Cn, sin_2_Cn)};
515 }
516
522 T lam0() const { return this->params_.lam0_; }
523
524 private:
525 projection_parameters<T> params_{};
526};
527
532} // namespace cuproj
Base class for all transform operations.
Definition operation.cuh:63
CUPROJ_HOST_DEVICE Coordinate operator()(Coordinate const &coord, direction dir) const
Perform UTM projection for a single coordinate.
CUPROJ_HOST_DEVICE transverse_mercator(projection_parameters< T > const &params)
construct a transverse mercator projection
projection_parameters< T > setup(projection_parameters< T > const &input_params)
Set up the projection parameters for transverse mercator projection.
static constexpr int ETMERC_ORDER
6th order series expansion
#define CUPROJ_HOST_DEVICE_EXPECTS(cond, reason)
Macro for checking (pre-)conditions that throws an exception when a condition is violated.
Definition error.hpp:100
direction
Enumerates the direction of a transform operation.
Definition operation.cuh:44
hemisphere
Hemisphere identifier for projections.
Ellipsoid parameters.
Definition ellipsoid.hpp:35