/src/PROJ/src/latitudes.cpp
Line | Count | Source |
1 | | /* |
2 | | * Project: PROJ |
3 | | * |
4 | | * Helper functions to compute latitudes |
5 | | * |
6 | | * Some from Map Projections - A Working Manual. 1987. John P. Snyder |
7 | | * https://neacsu.net/docs/geodesy/snyder/2-general/sect_3/ |
8 | | * |
9 | | * Copyright (c) 2025 Javier Jimenez Shaw |
10 | | */ |
11 | | |
12 | | #include <exception> |
13 | | #include <math.h> |
14 | | |
15 | | #include "proj_internal.h" |
16 | | |
17 | | /*****************************************************************************/ |
18 | 356 | double pj_conformal_lat(double phi, const PJ *P) { |
19 | | /*********************************** |
20 | | * The conformal latitude chi, in terms of the geodetic latitude, phi. |
21 | | * |
22 | | * phi: geodetic latitude, in radians |
23 | | * returns: conformal latitude, chi, in radians |
24 | | * Copied from merc.cpp |
25 | | ***********************************/ |
26 | 356 | if (P->e == 0.0) |
27 | 6 | return phi; |
28 | | |
29 | | // Instead of calling tan and sin, call sin and cos which the compiler |
30 | | // optimizes to a single call to sincos. |
31 | 350 | double sphi = sin(phi), cphi = cos(phi); |
32 | 350 | return atan(sinh(asinh(sphi / cphi) - P->e * atanh(P->e * sphi))); |
33 | 356 | } |
34 | | |
35 | | /*****************************************************************************/ |
36 | 0 | double pj_conformal_lat_inverse(double chi, const PJ *P) { |
37 | | /*********************************** |
38 | | * The geodetic latitude, phi, in terms of the conformal latitude, chi. |
39 | | * |
40 | | * chi: conformal latitude, in radians |
41 | | * returns: geodetic latitude, phi, in radians |
42 | | * Copied from merc.cpp |
43 | | ***********************************/ |
44 | 0 | if (P->e == 0.0) |
45 | 0 | return chi; |
46 | | |
47 | 0 | return atan(pj_sinhpsi2tanphi(P->ctx, tan(chi), P->e)); |
48 | 0 | } |
49 | | |
50 | | /*****************************************************************************/ |
51 | | |
52 | | // Computes coefficient q such that authalic_latitude = beta = asin(q / qp) |
53 | | // where qp is q at phi=90deg, i.e. qp = pj_authalic_lat_q(1, e, one_es) |
54 | | // Cf Snyder (3-11) and (3-12) |
55 | 14.2k | double pj_authalic_lat_q(double sinphi, const PJ *P) { |
56 | 14.2k | constexpr double EPSILON = 1e-7; |
57 | 14.2k | if (P->e >= EPSILON) { |
58 | 14.2k | const double e_sinphi = P->e * sinphi; |
59 | 14.2k | const double one_minus_e_sinphi_sq = 1.0 - e_sinphi * e_sinphi; |
60 | | |
61 | | /* avoid zero division, fail gracefully */ |
62 | 14.2k | if (one_minus_e_sinphi_sq == 0.0) |
63 | 0 | return HUGE_VAL; |
64 | | |
65 | | // Snyder uses 0.5 * ln((1-e*sinphi)/(1+e*sinphi) which is |
66 | | // -atanh(e*sinphi) |
67 | 14.2k | return P->one_es * |
68 | 14.2k | (sinphi / one_minus_e_sinphi_sq + atanh(e_sinphi) / P->e); |
69 | 14.2k | } else |
70 | 0 | return 2 * sinphi; |
71 | 14.2k | } |
72 | | |
73 | | /*****************************************************************************/ |
74 | | |
75 | | // Computes coefficients needed for conversions between geographic and authalic |
76 | | // latitude. These are preferred over the analytical expressions for |n| < |
77 | | // 0.01. However the inverse series is used to start the inverse method for |
78 | | // large |n|. |
79 | | |
80 | | // Ensure we use the cutoff in n consistently |
81 | 20.3k | #define PROJ_AUTHALIC_SERIES_VALID(n) (fabs(n) < 0.01) |
82 | 863 | double *pj_authalic_lat_compute_coeffs(double n) { |
83 | 863 | double *APA; |
84 | 863 | constexpr int Lmax = int(AuxLat::ORDER); |
85 | 863 | const int APA_SIZE = Lmax * (PROJ_AUTHALIC_SERIES_VALID(n) ? 2 : 1); |
86 | 863 | if ((APA = (double *)malloc(APA_SIZE * sizeof(double))) != nullptr) { |
87 | 863 | pj_auxlat_coeffs(n, AuxLat::AUTHALIC, AuxLat::GEOGRAPHIC, APA); |
88 | 863 | if (PROJ_AUTHALIC_SERIES_VALID(n)) |
89 | 831 | pj_auxlat_coeffs(n, AuxLat::GEOGRAPHIC, AuxLat::AUTHALIC, |
90 | 831 | APA + Lmax); |
91 | 863 | } |
92 | 863 | return APA; |
93 | 863 | } |
94 | | |
95 | | /*****************************************************************************/ |
96 | | |
97 | | // Computes authalic latitude from the geographic latitude. |
98 | | // qp is q at phi=90deg, i.e. qp = pj_authalic_lat_q(1, e, one_es) |
99 | | double pj_authalic_lat(double phi, double sinphi, double cosphi, |
100 | 18.5k | const double *APA, const PJ *P, double qp) { |
101 | 18.5k | if (PROJ_AUTHALIC_SERIES_VALID(P->n)) { |
102 | 17.2k | constexpr int Lmax = int(AuxLat::ORDER); |
103 | 17.2k | return pj_auxlat_convert(phi, sinphi, cosphi, APA + Lmax); |
104 | 17.2k | } else { |
105 | | // This result is ill-conditioned near the poles. So don't use this if |
106 | | // the series are accurate. |
107 | 1.34k | const double q = pj_authalic_lat_q(sinphi, P); |
108 | 1.34k | double ratio = q / qp; |
109 | | |
110 | 1.34k | if (fabs(ratio) > 1) { |
111 | | /* Rounding error. */ |
112 | 0 | ratio = ratio > 0 ? 1 : -1; |
113 | 0 | } |
114 | 1.34k | return asin(ratio); |
115 | 1.34k | } |
116 | 18.5k | } |
117 | | |
118 | | /*****************************************************************************/ |
119 | | |
120 | | // Compute the geographic latitude from beta = authalic_latitude |
121 | | // where APA = pj_compute_coefficients_for_inverse_authalic_lat() and |
122 | | // qp = pj_authalic_lat_q(1, P->e, P->one_es) |
123 | | double pj_authalic_lat_inverse(double beta, const double *APA, const PJ *P, |
124 | 0 | double qp) { |
125 | 0 | double phi = pj_auxlat_convert(beta, APA); |
126 | 0 | if (PROJ_AUTHALIC_SERIES_VALID(P->n)) |
127 | 0 | return phi; |
128 | | // If the flattening is large, solve |
129 | | // f(phi) = qp*sin(beta)/(1-e^2) - q(phi)/(1-e^2) = 0 |
130 | | // for phi, using Newton's method, where |
131 | | // q(phi)/(1-e^2) = sin(phi)/(1 - e^2*sin(phi)^2) + atanh(e*sin(phi))/e |
132 | | // and |
133 | | // df(phi)/dphi = - dq(phi)/dphi / (1-e^2) |
134 | | // = - 2 * (1-e^2) * cos(phi) / (1 - e^2*sinphi^2)^2 |
135 | | // This is subject to large roundoff errors near the poles, so only use |
136 | | // this if the series isn't accurate. |
137 | 0 | const double q = sin(beta) * qp; |
138 | 0 | const double q_div_one_minus_es = q / P->one_es; |
139 | 0 | for (int i = 0; i < 10; ++i) { |
140 | 0 | const double sinphi = sin(phi); |
141 | 0 | const double cosphi = cos(phi); |
142 | 0 | const double one_minus_es_sin2phi = 1 - P->es * (sinphi * sinphi); |
143 | | // Snyder uses 0.5 * ln((1-e*sinphi)/(1+e*sinphi) which is |
144 | | // -atanh(e*sinphi) |
145 | 0 | const double dphi = |
146 | 0 | (one_minus_es_sin2phi * one_minus_es_sin2phi) / (2 * cosphi) * |
147 | 0 | (q_div_one_minus_es - sinphi / one_minus_es_sin2phi - |
148 | 0 | atanh(P->e * sinphi) / P->e); |
149 | 0 | if (!(fabs(dphi) >= 1e-15)) |
150 | 0 | break; |
151 | 0 | phi += dphi; |
152 | 0 | } |
153 | 0 | return phi; |
154 | 0 | } |
155 | | #undef PROJ_AUTHALIC_SERIES_VALID |
156 | | |
157 | | // The following routines pj_auxlat_coeffs, pj_polyvol, pj_clenshaw, |
158 | | // pj_auxlat_convert (3 signatures) provide a uniform interface for converting |
159 | | // between any pair of auxiliary latitudes using series expansions in the third |
160 | | // flattening, n. There are 6 (= AuxLat::NUMBER) auxiliary latitudes |
161 | | // supported labeled by |
162 | | // |
163 | | // AuxLat::GEOGRAPHIC for geographic latitude, phi |
164 | | // AuxLat::PARAMETRIC for parametric latitude, beta |
165 | | // AuxLat::GEOCENTRIC for geocentric latitude, theta |
166 | | // AuxLat::RECTIFYING for rectifying latitude, mu |
167 | | // AuxLat::CONFORMAL for conformal latitude, chi |
168 | | // AuxLat::AUTHALIC for authlatic latitude, xi |
169 | | // |
170 | | // This is adapted from |
171 | | // |
172 | | // C. F. F. Karney, On auxiliary latitudes, |
173 | | // Survey Review 56, 165-180 (2024) |
174 | | // https://doi.org/10.1080/00396265.2023.2217604 |
175 | | // Preprint: https://arxiv.org/abs/2212.05818 |
176 | | // |
177 | | // The typical calling sequence is |
178 | | // |
179 | | // constexpr int L = int(AuxLat::ORDER); |
180 | | // // Managing the memory for the coefficient array is the |
181 | | // // responsibility of the calling routine. |
182 | | // double F[L]; |
183 | | // // Fill F[] with coefficients to convert conformal to geographic |
184 | | // pj_auxlat_coeffs(P->n, AuxLat::CONFORMAL, AuxLat::GEOGRAPHIC, F); |
185 | | // ... |
186 | | // double chi = 1; // known conformal latitude |
187 | | // // compute corresponding geographic latitude |
188 | | // double phi = pj_auxlat_convert(chi, F); |
189 | | // |
190 | | // The conversions are Fourier series in the auxiliary latitude where each |
191 | | // coefficient is given as a Taylor series in n truncated at order 6 (= |
192 | | // AuxLat::ORDER). This suffices to give full double precision accuracy for |
193 | | // |f| <= 1/150 and probably provide satisfactory results for |f| <= 1/50. The |
194 | | // coefficients for these Taylor series are given by matrices listed in |
195 | | // Eqs. (A1-A28) of this paper. |
196 | | // |
197 | | // These coefficients are bundled up into a single array coeffs in |
198 | | // pj_auxlat_coeffs. Only the upper triangular portion of the matrices are |
199 | | // included. Furthermore, half the coefficients for the conversions between |
200 | | // any of phi, bete, theta, and mu are zero (the Taylor series are expansions |
201 | | // in n^2), these zero elements are excluded. |
202 | | // |
203 | | // The coefficient array, coeffs, is machine-generated by the Maxima code |
204 | | // auxlat.mac bundled with GeographicLib. To use |
205 | | // |
206 | | // * Ensure that Lmax (set near the top of the file) is set to 6 (= |
207 | | // AuxLat::ORDER). |
208 | | // * run |
209 | | // $ maxima |
210 | | // Maxima 5.47.0 https://maxima.sourceforge.io |
211 | | // (%i1) load("auxlat.mac")$ |
212 | | // (%i2) writecppproj()$ |
213 | | // .... |
214 | | // #<CLOSED OUTPUT BUFFERED FILE-STREAM CHARACTER auxvalsproj6.cpp> |
215 | | // * The results are in the file auxvalsproj6.cpp |
216 | | // |
217 | | // Only a subset of the conversion matrices are written out. To add others, |
218 | | // include them in the list "required" in writecppproj(). The conversions |
219 | | // currently supported are |
220 | | // |
221 | | // phi <-> mu for meridian distance |
222 | | // phi <-> chi for tmerc |
223 | | // phi <-> xi for authalic latitude conversions |
224 | | // chi <-> mu for tmerc |
225 | | // |
226 | | // Because all the matrices are concatenated together into a single array, |
227 | | // coeff, an auxiliary array, ptrs, or length 37 = AUXNUMBER^2 + 1, is written |
228 | | // out to give the starting point of any particular matrix. |
229 | | // |
230 | | // Input: |
231 | | // n -- the third flattening (a-b)/(a+b) |
232 | | // auxin, auxout -- compute the coefficients for converting auxin (zeta) to |
233 | | // auxout (eta). |
234 | | // Output: |
235 | | // F -- F[eta,zeta] = C[eta,zeta] . P(n), where C is a matrix of constants |
236 | | // and P(n) = [n, n^2, n^3, ...]^T; the first AuxLat::ORDER elements of F |
237 | | // are filled. |
238 | 15.6k | void pj_auxlat_coeffs(double n, AuxLat auxin, AuxLat auxout, double F[]) { |
239 | | // Generated by Maxima on 2025-03-23 19:13:00-04:00 |
240 | 15.6k | constexpr int Lmax = 6; |
241 | 15.6k | static_assert(Lmax == int(AuxLat::ORDER), |
242 | 15.6k | "Mismatch between maxima and AuxLat::ORDER"); |
243 | 15.6k | static const double coeffs[] = { |
244 | | // C[phi,phi] skipped |
245 | | // C[phi,beta] skipped |
246 | | // C[phi,theta] skipped |
247 | | // C[phi,mu]; even coeffs only |
248 | 15.6k | 3.0 / 2.0, -27.0 / 32.0, 269.0 / 512.0, 21.0 / 16.0, -55.0 / 32.0, |
249 | 15.6k | 6759.0 / 4096.0, 151.0 / 96.0, -417.0 / 128.0, 1097.0 / 512.0, |
250 | 15.6k | -15543.0 / 2560.0, 8011.0 / 2560.0, 293393.0 / 61440.0, |
251 | | // C[phi,chi] |
252 | 15.6k | 2.0, -2.0 / 3.0, -2.0, 116.0 / 45.0, 26.0 / 45.0, -2854.0 / 675.0, |
253 | 15.6k | 7.0 / 3.0, -8.0 / 5.0, -227.0 / 45.0, 2704.0 / 315.0, 2323.0 / 945.0, |
254 | 15.6k | 56.0 / 15.0, -136.0 / 35.0, -1262.0 / 105.0, 73814.0 / 2835.0, |
255 | 15.6k | 4279.0 / 630.0, -332.0 / 35.0, -399572.0 / 14175.0, 4174.0 / 315.0, |
256 | 15.6k | -144838.0 / 6237.0, 601676.0 / 22275.0, |
257 | | // C[phi,xi] |
258 | 15.6k | 4.0 / 3.0, 4.0 / 45.0, -16.0 / 35.0, -2582.0 / 14175.0, |
259 | 15.6k | 60136.0 / 467775.0, 28112932.0 / 212837625.0, 46.0 / 45.0, |
260 | 15.6k | 152.0 / 945.0, -11966.0 / 14175.0, -21016.0 / 51975.0, |
261 | 15.6k | 251310128.0 / 638512875.0, 3044.0 / 2835.0, 3802.0 / 14175.0, |
262 | 15.6k | -94388.0 / 66825.0, -8797648.0 / 10945935.0, 6059.0 / 4725.0, |
263 | 15.6k | 41072.0 / 93555.0, -1472637812.0 / 638512875.0, 768272.0 / 467775.0, |
264 | 15.6k | 455935736.0 / 638512875.0, 4210684958.0 / 1915538625.0, |
265 | | // C[beta,phi] skipped |
266 | | // C[beta,beta] skipped |
267 | | // C[beta,theta] skipped |
268 | | // C[beta,mu] skipped |
269 | | // C[beta,chi] skipped |
270 | | // C[beta,xi] skipped |
271 | | // C[theta,phi] skipped |
272 | | // C[theta,beta] skipped |
273 | | // C[theta,theta] skipped |
274 | | // C[theta,mu] skipped |
275 | | // C[theta,chi] skipped |
276 | | // C[theta,xi] skipped |
277 | | // C[mu,phi]; even coeffs only |
278 | 15.6k | -3.0 / 2.0, 9.0 / 16.0, -3.0 / 32.0, 15.0 / 16.0, -15.0 / 32.0, |
279 | 15.6k | 135.0 / 2048.0, -35.0 / 48.0, 105.0 / 256.0, 315.0 / 512.0, |
280 | 15.6k | -189.0 / 512.0, -693.0 / 1280.0, 1001.0 / 2048.0, |
281 | | // C[mu,beta] skipped |
282 | | // C[mu,theta] skipped |
283 | | // C[mu,mu] skipped |
284 | | // C[mu,chi] |
285 | 15.6k | 1.0 / 2.0, -2.0 / 3.0, 5.0 / 16.0, 41.0 / 180.0, -127.0 / 288.0, |
286 | 15.6k | 7891.0 / 37800.0, 13.0 / 48.0, -3.0 / 5.0, 557.0 / 1440.0, |
287 | 15.6k | 281.0 / 630.0, -1983433.0 / 1935360.0, 61.0 / 240.0, -103.0 / 140.0, |
288 | 15.6k | 15061.0 / 26880.0, 167603.0 / 181440.0, 49561.0 / 161280.0, |
289 | 15.6k | -179.0 / 168.0, 6601661.0 / 7257600.0, 34729.0 / 80640.0, |
290 | 15.6k | -3418889.0 / 1995840.0, 212378941.0 / 319334400.0, |
291 | | // C[mu,xi] skipped |
292 | | // C[chi,phi] |
293 | 15.6k | -2.0, 2.0 / 3.0, 4.0 / 3.0, -82.0 / 45.0, 32.0 / 45.0, 4642.0 / 4725.0, |
294 | 15.6k | 5.0 / 3.0, -16.0 / 15.0, -13.0 / 9.0, 904.0 / 315.0, -1522.0 / 945.0, |
295 | 15.6k | -26.0 / 15.0, 34.0 / 21.0, 8.0 / 5.0, -12686.0 / 2835.0, 1237.0 / 630.0, |
296 | 15.6k | -12.0 / 5.0, -24832.0 / 14175.0, -734.0 / 315.0, 109598.0 / 31185.0, |
297 | 15.6k | 444337.0 / 155925.0, |
298 | | // C[chi,beta] skipped |
299 | | // C[chi,theta] skipped |
300 | | // C[chi,mu] |
301 | 15.6k | -1.0 / 2.0, 2.0 / 3.0, -37.0 / 96.0, 1.0 / 360.0, 81.0 / 512.0, |
302 | 15.6k | -96199.0 / 604800.0, -1.0 / 48.0, -1.0 / 15.0, 437.0 / 1440.0, |
303 | 15.6k | -46.0 / 105.0, 1118711.0 / 3870720.0, -17.0 / 480.0, 37.0 / 840.0, |
304 | 15.6k | 209.0 / 4480.0, -5569.0 / 90720.0, -4397.0 / 161280.0, 11.0 / 504.0, |
305 | 15.6k | 830251.0 / 7257600.0, -4583.0 / 161280.0, 108847.0 / 3991680.0, |
306 | 15.6k | -20648693.0 / 638668800.0, |
307 | | // C[chi,chi] skipped |
308 | | // C[chi,xi] skipped |
309 | | // C[xi,phi] |
310 | 15.6k | -4.0 / 3.0, -4.0 / 45.0, 88.0 / 315.0, 538.0 / 4725.0, |
311 | 15.6k | 20824.0 / 467775.0, -44732.0 / 2837835.0, 34.0 / 45.0, 8.0 / 105.0, |
312 | 15.6k | -2482.0 / 14175.0, -37192.0 / 467775.0, -12467764.0 / 212837625.0, |
313 | 15.6k | -1532.0 / 2835.0, -898.0 / 14175.0, 54968.0 / 467775.0, |
314 | 15.6k | 100320856.0 / 1915538625.0, 6007.0 / 14175.0, 24496.0 / 467775.0, |
315 | 15.6k | -5884124.0 / 70945875.0, -23356.0 / 66825.0, -839792.0 / 19348875.0, |
316 | 15.6k | 570284222.0 / 1915538625.0, |
317 | | // C[xi,beta] skipped |
318 | | // C[xi,theta] skipped |
319 | | // C[xi,mu] skipped |
320 | | // C[xi,chi] skipped |
321 | | // C[xi,xi] skipped |
322 | 15.6k | }; |
323 | 15.6k | static constexpr int ptrs[] = { |
324 | 15.6k | 0, 0, 0, 0, 12, 33, 54, 54, 54, 54, 54, 54, 54, |
325 | 15.6k | 54, 54, 54, 54, 54, 54, 66, 66, 66, 66, 87, 87, 108, |
326 | 15.6k | 108, 108, 129, 129, 129, 150, 150, 150, 150, 150, 150, |
327 | 15.6k | }; |
328 | 15.6k | static_assert(sizeof(ptrs) / sizeof(int) == |
329 | 15.6k | int(AuxLat::NUMBER) * int(AuxLat::NUMBER) + 1, |
330 | 15.6k | "Mismatch in size of ptrs array"); |
331 | 15.6k | static_assert(sizeof(coeffs) / sizeof(double) == |
332 | 15.6k | ptrs[int(AuxLat::NUMBER) * int(AuxLat::NUMBER)], |
333 | 15.6k | "Mismatch in size of coeffs array"); |
334 | 15.6k | if (!(auxin >= AuxLat(0) && auxin < AuxLat::NUMBER && auxout >= AuxLat(0) && |
335 | 15.6k | auxout < AuxLat::NUMBER)) |
336 | 0 | throw std::out_of_range("Bad specification for auxiliary latitude"); |
337 | 15.6k | int k = int(AuxLat::NUMBER) * int(auxout) + int(auxin), o = ptrs[k]; |
338 | 15.6k | if (o == ptrs[k + 1]) |
339 | 0 | throw std::out_of_range( |
340 | 0 | "Unsupported conversion between auxiliary latitudes"); |
341 | 15.6k | double d = n, n2 = n * n; |
342 | 15.6k | if (auxin <= AuxLat::RECTIFYING && auxout <= AuxLat::RECTIFYING) { |
343 | 30.9k | for (int l = 0; l < Lmax; ++l) { |
344 | 26.4k | int m = (Lmax - l - 1) / 2; // order of polynomial in n^2 |
345 | 26.4k | F[l] = d * pj_polyval(n2, coeffs + o, m); |
346 | 26.4k | o += m + 1; |
347 | | // coverity[copy_paste_error] |
348 | 26.4k | d *= n; |
349 | 26.4k | } |
350 | 11.2k | } else { |
351 | 78.6k | for (int l = 0; l < Lmax; ++l) { |
352 | 67.4k | int m = (Lmax - l - 1); // order of polynomial in n |
353 | 67.4k | F[l] = d * pj_polyval(n, coeffs + o, m); |
354 | 67.4k | o += m + 1; |
355 | 67.4k | d *= n; |
356 | 67.4k | } |
357 | 11.2k | } |
358 | | // assert (o == ptrs[k+1]); |
359 | 15.6k | } |
360 | | |
361 | | // Evaluation sum(p[i] * x^i, i, 0, N) via Horner's method. N.B. p is of |
362 | | // length N+1. |
363 | 98.5k | double pj_polyval(double x, const double p[], int N) { |
364 | 98.5k | double y = N < 0 ? 0 : p[N]; |
365 | 307k | for (; N > 0;) |
366 | 208k | y = y * x + p[--N]; |
367 | 98.5k | return y; |
368 | 98.5k | } |
369 | | |
370 | | // Evaluate y = sum(F[k] * sin((2*k+2) * zeta), k, 0, K-1) by Clenshaw |
371 | | // summation. zeta is specify by its sine and cosine, szeta and czeta. |
372 | 237k | double pj_clenshaw(double szeta, double czeta, const double F[], int K) { |
373 | | // Approx operation count = (K + 5) mult and (2 * K + 2) add |
374 | 237k | double u0 = 0, u1 = 0, // accumulators for sum |
375 | 237k | X = 2 * (czeta - szeta) * (czeta + szeta); // 2 * cos(2*zeta) |
376 | 1.66M | for (; K > 0;) { |
377 | 1.42M | double t = X * u0 - u1 + F[--K]; |
378 | 1.42M | u1 = u0; |
379 | 1.42M | u0 = t; |
380 | 1.42M | } |
381 | 237k | return 2 * szeta * czeta * u0; // sin(2*zeta) * u0 |
382 | 237k | } |
383 | | |
384 | | // Convert auxiliary latitude zeta to eta, given coefficients F produced by |
385 | | // pj_auxlats_coeffs(n, zeta, eta, F). K is the size of F (defaults to |
386 | | // AuxLat::ORDER). |
387 | 197k | double pj_auxlat_convert(double zeta, const double F[], int K) { |
388 | 197k | return pj_auxlat_convert(zeta, sin(zeta), cos(zeta), F, K); |
389 | 197k | } |
390 | | |
391 | | // Convert auxiliary latitude zeta to eta, given coefficients F produced by |
392 | | // pj_auxlats_coeffs(n, zeta, eta, F). In this signature, szeta and czeta (the |
393 | | // sine and cosine of zeta) are given as inputs. K is the size of F (defaults |
394 | | // to AuxLat::ORDER). |
395 | | double pj_auxlat_convert(double zeta, double szeta, double czeta, |
396 | 237k | const double F[], int K) { |
397 | 237k | return zeta + pj_clenshaw(szeta, czeta, F, K); |
398 | 237k | } |
399 | | |
400 | | // Convert auxiliary latitude zeta to eta, given coefficients F produced by |
401 | | // pj_auxlats_coeffs(n, zeta, eta, F). K is the size of F (defaults to |
402 | | // AuxLat::ORDER). In this signature, the sine and cosine of eta are returned. |
403 | | // This provides high relative accuracy near the poles. |
404 | | void pj_auxlat_convert(double szeta, double czeta, double &seta, double &ceta, |
405 | 0 | const double F[], int K) { |
406 | 0 | double delta = pj_clenshaw(szeta, czeta, F, K), sdelta = sin(delta), |
407 | 0 | cdelta = cos(delta); |
408 | 0 | seta = szeta * cdelta + czeta * sdelta; |
409 | 0 | ceta = czeta * cdelta - szeta * sdelta; |
410 | 0 | } |
411 | | |
412 | | // Compute the rectifying radius = quarter meridian / (pi/2 * a). The accuracy |
413 | | // of this series is the same as those used for the computation of the |
414 | | // auxiliary latitudes. |
415 | 4.59k | double pj_rectifying_radius(double n) { |
416 | | // Expansion of (quarter meridian) / ((a+b)/2 * pi/2) as series in n^2; |
417 | | // these coefficients are ( (2*k - 3)!! / (2*k)!! )^2 for k = 0..3 |
418 | 4.59k | static const double coeff_rad[] = {1, 1.0 / 4, 1.0 / 64, 1.0 / 256}; |
419 | 4.59k | return pj_polyval(n * n, coeff_rad, 3) / (1 + n); |
420 | 4.59k | } |