/src/PROJ/src/transformations/horner.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /*********************************************************************** |
2 | | |
3 | | Interfacing to a classic piece of geodetic software |
4 | | |
5 | | ************************************************************************ |
6 | | |
7 | | gen_pol is a highly efficient, classic implementation of a generic |
8 | | 2D Horner's Scheme polynomial evaluation routine by Knud Poder and |
9 | | Karsten Engsager, originating in the vivid geodetic environment at |
10 | | what was then (1960-ish) the Danish Geodetic Institute. |
11 | | |
12 | | The original Poder/Engsager gen_pol implementation (where |
13 | | the polynomial degree and two sets of polynomial coefficients |
14 | | are packed together in one compound array, handled via a plain |
15 | | double pointer) is compelling and "true to the code history": |
16 | | |
17 | | It has a beautiful classical 1960s ring to it, not unlike the |
18 | | original fft implementations, which revolutionized spectral |
19 | | analysis in twenty lines of code. |
20 | | |
21 | | The Poder coding sound, as classic 1960s as Phil Spector's Wall |
22 | | of Sound, is beautiful and inimitable. |
23 | | |
24 | | On the other hand: For the uninitiated, the gen_pol code is hard |
25 | | to follow, despite being compact. |
26 | | |
27 | | Also, since adding metadata and improving maintainability |
28 | | of the code are among the implied goals of a current SDFE/DTU Space |
29 | | project, the material in this file introduces a version with a |
30 | | more modern (or at least 1990s) look, introducing a "double 2D |
31 | | polynomial" data type, HORNER. |
32 | | |
33 | | Despite introducing a new data type for handling the polynomial |
34 | | coefficients, great care has been taken to keep the coefficient |
35 | | array organization identical to that of gen_pol. |
36 | | |
37 | | Hence, on one hand, the HORNER data type helps improving the |
38 | | long term maintainability of the code by making the data |
39 | | organization more mentally accessible. |
40 | | |
41 | | On the other hand, it allows us to preserve the business end of |
42 | | the original gen_pol implementation - although not including the |
43 | | famous "Poder dual autocheck" in all its enigmatic elegance. |
44 | | |
45 | | ********************************************************************** |
46 | | |
47 | | The material included here was written by Knud Poder, starting |
48 | | around 1960, and Karsten Engsager, starting around 1970. It was |
49 | | originally written in Algol 60, later (1980s) reimplemented in C. |
50 | | |
51 | | The HORNER data type interface, and the organization as a header |
52 | | library was implemented by Thomas Knudsen, starting around 2015. |
53 | | |
54 | | *********************************************************************** |
55 | | * |
56 | | * Copyright (c) 2016, SDFE http://www.sdfe.dk / Thomas Knudsen / Karsten |
57 | | Engsager |
58 | | * |
59 | | * Permission is hereby granted, free of charge, to any person obtaining a |
60 | | * copy of this software and associated documentation files (the "Software"), |
61 | | * to deal in the Software without restriction, including without limitation |
62 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
63 | | * and/or sell copies of the Software, and to permit persons to whom the |
64 | | * Software is furnished to do so, subject to the following conditions: |
65 | | * |
66 | | * The above copyright notice and this permission notice shall be included |
67 | | * in all copies or substantial portions of the Software. |
68 | | * |
69 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
70 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
71 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
72 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
73 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
74 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
75 | | * DEALINGS IN THE SOFTWARE. |
76 | | * |
77 | | *****************************************************************************/ |
78 | | |
79 | | #include <cassert> |
80 | | #include <complex> |
81 | | #include <cstdint> |
82 | | #include <errno.h> |
83 | | #include <math.h> |
84 | | #include <stddef.h> |
85 | | #include <stdio.h> |
86 | | #include <string.h> |
87 | | |
88 | | #include "proj.h" |
89 | | #include "proj_internal.h" |
90 | | |
91 | | PROJ_HEAD(horner, "Horner polynomial evaluation"); |
92 | | |
93 | | /* make horner.h interface with proj's memory management */ |
94 | 72 | #define horner_dealloc(x) free(x) |
95 | 56 | #define horner_calloc(n, x) calloc(n, x) |
96 | | |
97 | | namespace { // anonymous namespace |
98 | | struct horner { |
99 | | int uneg; /* u axis negated? */ |
100 | | int vneg; /* v axis negated? */ |
101 | | uint32_t order; /* maximum degree of polynomium */ |
102 | | double range; /* radius of the region of validity */ |
103 | | bool has_inv; /* inv parameters are specified */ |
104 | | double inverse_tolerance; /* in the units of the destination coords, |
105 | | specifies when to stop iterating if !has_inv |
106 | | and direction is reverse */ |
107 | | |
108 | | double *fwd_u; /* coefficients for the forward transformations */ |
109 | | double *fwd_v; /* i.e. latitude/longitude to northing/easting */ |
110 | | |
111 | | double *inv_u; /* coefficients for the inverse transformations */ |
112 | | double *inv_v; /* i.e. northing/easting to latitude/longitude */ |
113 | | |
114 | | double *fwd_c; /* coefficients for the complex forward transformations */ |
115 | | double *inv_c; /* coefficients for the complex inverse transformations */ |
116 | | |
117 | | PJ_UV *fwd_origin; /* False longitude/latitude */ |
118 | | PJ_UV *inv_origin; /* False easting/northing */ |
119 | | }; |
120 | | } // anonymous namespace |
121 | | |
122 | | typedef struct horner HORNER; |
123 | | |
124 | | /* e.g. degree = 2: a + bx + cy + dxx + eyy + fxy, i.e. 6 coefficients */ |
125 | 16 | constexpr uint32_t horner_number_of_real_coefficients(uint32_t order) { |
126 | 16 | return (order + 1) * (order + 2) / 2; |
127 | 16 | } |
128 | | |
129 | 0 | constexpr uint32_t horner_number_of_complex_coefficients(uint32_t order) { |
130 | 0 | return 2 * order + 2; |
131 | 0 | } |
132 | | |
133 | 8 | static void horner_free(HORNER *h) { |
134 | 8 | horner_dealloc(h->inv_v); |
135 | 8 | horner_dealloc(h->inv_u); |
136 | 8 | horner_dealloc(h->fwd_v); |
137 | 8 | horner_dealloc(h->fwd_u); |
138 | 8 | horner_dealloc(h->fwd_c); |
139 | 8 | horner_dealloc(h->inv_c); |
140 | 8 | horner_dealloc(h->fwd_origin); |
141 | 8 | horner_dealloc(h->inv_origin); |
142 | 8 | horner_dealloc(h); |
143 | 8 | } |
144 | | |
145 | 8 | static HORNER *horner_alloc(uint32_t order, bool complex_polynomia) { |
146 | | /* uint32_t is unsigned, so we need not check for order > 0 */ |
147 | 8 | bool polynomia_ok = false; |
148 | 8 | HORNER *h = static_cast<HORNER *>(horner_calloc(1, sizeof(HORNER))); |
149 | | |
150 | 8 | if (nullptr == h) |
151 | 0 | return nullptr; |
152 | | |
153 | 8 | uint32_t n = complex_polynomia |
154 | 8 | ? horner_number_of_complex_coefficients(order) |
155 | 8 | : horner_number_of_real_coefficients(order); |
156 | | |
157 | 8 | h->order = order; |
158 | | |
159 | 8 | if (complex_polynomia) { |
160 | 0 | h->fwd_c = static_cast<double *>(horner_calloc(n, sizeof(double))); |
161 | 0 | h->inv_c = static_cast<double *>(horner_calloc(n, sizeof(double))); |
162 | 0 | if (h->fwd_c && h->inv_c) |
163 | 0 | polynomia_ok = true; |
164 | 8 | } else { |
165 | 8 | h->fwd_u = static_cast<double *>(horner_calloc(n, sizeof(double))); |
166 | 8 | h->fwd_v = static_cast<double *>(horner_calloc(n, sizeof(double))); |
167 | 8 | h->inv_u = static_cast<double *>(horner_calloc(n, sizeof(double))); |
168 | 8 | h->inv_v = static_cast<double *>(horner_calloc(n, sizeof(double))); |
169 | 8 | if (h->fwd_u && h->fwd_v && h->inv_u && h->inv_v) |
170 | 8 | polynomia_ok = true; |
171 | 8 | } |
172 | | |
173 | 8 | h->fwd_origin = static_cast<PJ_UV *>(horner_calloc(1, sizeof(PJ_UV))); |
174 | 8 | h->inv_origin = static_cast<PJ_UV *>(horner_calloc(1, sizeof(PJ_UV))); |
175 | | |
176 | 8 | if (polynomia_ok && h->fwd_origin && h->inv_origin) |
177 | 8 | return h; |
178 | | |
179 | | /* safe, since all pointers are null-initialized (by calloc) */ |
180 | 0 | horner_free(h); |
181 | 0 | return nullptr; |
182 | 8 | } |
183 | | |
184 | | inline static PJ_UV double_real_horner_eval(uint32_t order, const double *cx, |
185 | | const double *cy, PJ_UV en, |
186 | 0 | uint32_t order_offset = 0) { |
187 | | /* |
188 | | The melody of this block is straight out of the great Engsager/Poder |
189 | | songbook. For numerical stability, the summation is carried out |
190 | | backwards, summing the tiny high order elements first. Double Horner's |
191 | | scheme: N = n*Cy*e -> yout, E = e*Cx*n -> xout |
192 | | */ |
193 | 0 | const double n = en.v; |
194 | 0 | const double e = en.u; |
195 | 0 | const uint32_t sz = horner_number_of_real_coefficients(order); |
196 | 0 | cx += sz; |
197 | 0 | cy += sz; |
198 | 0 | double N = *--cy; |
199 | 0 | double E = *--cx; |
200 | 0 | for (uint32_t r = order; r > order_offset; r--) { |
201 | 0 | double u = *--cy; |
202 | 0 | double v = *--cx; |
203 | 0 | for (uint32_t c = order; c >= r; c--) { |
204 | 0 | u = n * u + *--cy; |
205 | 0 | v = e * v + *--cx; |
206 | 0 | } |
207 | 0 | N = e * N + u; |
208 | 0 | E = n * E + v; |
209 | 0 | } |
210 | 0 | return {E, N}; |
211 | 0 | } |
212 | | |
213 | | inline static double single_real_horner_eval(uint32_t order, const double *cx, |
214 | | double x, |
215 | 0 | uint32_t order_offset = 0) { |
216 | 0 | const uint32_t sz = order + 1; /* Number of coefficients per polynomial */ |
217 | 0 | cx += sz; |
218 | 0 | double u = *--cx; |
219 | 0 | for (uint32_t r = order; r > order_offset; r--) { |
220 | 0 | u = x * u + *--cx; |
221 | 0 | } |
222 | 0 | return u; |
223 | 0 | } |
224 | | |
225 | | inline static PJ_UV complex_horner_eval(uint32_t order, const double *c, |
226 | 0 | PJ_UV en, uint32_t order_offset = 0) { |
227 | | // the coefficients are ordered like this: |
228 | | // (Cn0+i*Ce0, Cn1+i*Ce1, ...) |
229 | 0 | const uint32_t sz = horner_number_of_complex_coefficients(order); |
230 | 0 | const double e = en.u; |
231 | 0 | const double n = en.v; |
232 | 0 | const double *cbeg = c + order_offset * 2; |
233 | 0 | c += sz; |
234 | |
|
235 | 0 | double E = *--c; |
236 | 0 | double N = *--c; |
237 | 0 | double w; |
238 | 0 | while (c > cbeg) { |
239 | 0 | w = n * E + e * N + *--c; |
240 | 0 | N = n * N - e * E + *--c; |
241 | 0 | E = w; |
242 | 0 | } |
243 | 0 | return {E, N}; |
244 | 0 | } |
245 | | |
246 | 0 | inline static PJ_UV generate_error_coords() { |
247 | 0 | PJ_UV uv_error; |
248 | 0 | uv_error.u = uv_error.v = HUGE_VAL; |
249 | 0 | return uv_error; |
250 | 0 | } |
251 | | |
252 | | inline static bool coords_out_of_range(PJ *P, const HORNER *transformation, |
253 | 0 | double n, double e) { |
254 | 0 | const double range = transformation->range; |
255 | 0 | if ((fabs(n) > range) || (fabs(e) > range)) { |
256 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); |
257 | 0 | return true; |
258 | 0 | } |
259 | 0 | return false; |
260 | 0 | } |
261 | | |
262 | | static PJ_UV real_default_impl(PJ *P, const HORNER *transformation, |
263 | 0 | PJ_DIRECTION direction, PJ_UV position) { |
264 | | /*********************************************************************** |
265 | | |
266 | | A reimplementation of the classic Engsager/Poder 2D Horner polynomial |
267 | | evaluation engine "gen_pol". |
268 | | |
269 | | This version omits the inimitable Poder "dual autocheck"-machinery, |
270 | | which here is intended to be implemented at a higher level of the |
271 | | library: We separate the polynomial evaluation from the quality |
272 | | control (which, given the limited MTBF for "computing machinery", |
273 | | typical when Knud Poder invented the dual autocheck method, |
274 | | was not defensible at that time). |
275 | | |
276 | | Another difference from the original version is that we return the |
277 | | result on the stack, rather than accepting pointers to result variables |
278 | | as input. This results in code that is easy to read: |
279 | | |
280 | | projected = horner (s34j, 1, geographic); |
281 | | geographic = horner (s34j, -1, projected ); |
282 | | |
283 | | and experiments have shown that on contemporary architectures, the time |
284 | | taken for returning even comparatively large objects on the stack (and |
285 | | the UV is not that large - typically only 16 bytes) is negligibly |
286 | | different from passing two pointers (i.e. typically also 16 bytes) the |
287 | | other way. |
288 | | |
289 | | The polynomium has the form: |
290 | | |
291 | | P = sum (i = [0 : order]) |
292 | | sum (j = [0 : order - i]) |
293 | | pow(par_1, i) * pow(par_2, j) * coef(index(order, i, j)) |
294 | | |
295 | | ***********************************************************************/ |
296 | 0 | assert(direction == PJ_FWD || direction == PJ_INV); |
297 | |
|
298 | 0 | double n, e; |
299 | 0 | if (direction == PJ_FWD) { /* forward */ |
300 | 0 | e = position.u - transformation->fwd_origin->u; |
301 | 0 | n = position.v - transformation->fwd_origin->v; |
302 | 0 | } else { /* inverse */ |
303 | 0 | e = position.u - transformation->inv_origin->u; |
304 | 0 | n = position.v - transformation->inv_origin->v; |
305 | 0 | } |
306 | |
|
307 | 0 | if (coords_out_of_range(P, transformation, n, e)) { |
308 | 0 | return generate_error_coords(); |
309 | 0 | } |
310 | | |
311 | 0 | const double *tcx = |
312 | 0 | direction == PJ_FWD ? transformation->fwd_u : transformation->inv_u; |
313 | 0 | const double *tcy = |
314 | 0 | direction == PJ_FWD ? transformation->fwd_v : transformation->inv_v; |
315 | 0 | PJ_UV en = {e, n}; |
316 | 0 | position = double_real_horner_eval(transformation->order, tcx, tcy, en); |
317 | |
|
318 | 0 | return position; |
319 | 0 | } |
320 | | |
321 | | static PJ_UV real_iterative_inverse_impl(PJ *P, const HORNER *transformation, |
322 | 0 | PJ_UV position) { |
323 | |
|
324 | 0 | double n, e; |
325 | | // in this case fwd_origin needs to be added in the end |
326 | 0 | e = position.u; |
327 | 0 | n = position.v; |
328 | |
|
329 | 0 | if (coords_out_of_range(P, transformation, n, e)) { |
330 | 0 | return generate_error_coords(); |
331 | 0 | } |
332 | | |
333 | | /* |
334 | | * solve iteratively |
335 | | * |
336 | | * | E | | u00 | | u01 + u02*x + ... ' u10 + u11*x + u20*y + ... |
337 | | * | | x | | | = | | + |-------------------------- ' |
338 | | * --------------------------| | | | N | | v00 | | v10 + v11*y + v20*x |
339 | | * + |
340 | | * ... ' v01 + v02*y + ... | | y | |
341 | | * |
342 | | * | x | | Ma ' Mb |-1 | E-u00 | |
343 | | * | | = |-------- | | | |
344 | | * | y | | Mc ' Md | | N-v00 | |
345 | | */ |
346 | 0 | const uint32_t order = transformation->order; |
347 | 0 | const double tol = transformation->inverse_tolerance; |
348 | 0 | const double de = e - transformation->fwd_u[0]; |
349 | 0 | const double dn = n - transformation->fwd_v[0]; |
350 | 0 | double x0 = 0.0; |
351 | 0 | double y0 = 0.0; |
352 | 0 | int loops = 32; // usually converges really fast (1-2 loops) |
353 | 0 | bool converged = false; |
354 | 0 | while (loops-- > 0 && !converged) { |
355 | 0 | double Ma = 0.0; |
356 | 0 | double Mb = 0.0; |
357 | 0 | double Mc = 0.0; |
358 | 0 | double Md = 0.0; |
359 | 0 | { |
360 | 0 | const double *tcx = transformation->fwd_u; |
361 | 0 | const double *tcy = transformation->fwd_v; |
362 | 0 | PJ_UV x0y0 = {x0, y0}; |
363 | | // sum the i > 0 coefficients |
364 | 0 | PJ_UV Mbc = double_real_horner_eval(order, tcx, tcy, x0y0, 1); |
365 | 0 | Mb = Mbc.u; |
366 | 0 | Mc = Mbc.v; |
367 | | // sum the i = 0, j > 0 coefficients |
368 | 0 | Ma = single_real_horner_eval(order, tcx, x0, 1); |
369 | 0 | Md = single_real_horner_eval(order, tcy, y0, 1); |
370 | 0 | } |
371 | 0 | double idet = 1.0 / (Ma * Md - Mb * Mc); |
372 | 0 | double x = idet * (Md * de - Mb * dn); |
373 | 0 | double y = idet * (Ma * dn - Mc * de); |
374 | 0 | converged = (fabs(x - x0) < tol) && (fabs(y - y0) < tol); |
375 | 0 | x0 = x; |
376 | 0 | y0 = y; |
377 | 0 | } |
378 | | // if loops have been exhausted and we have not converged yet, |
379 | | // we are never going to converge |
380 | 0 | if (!converged) { |
381 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM); |
382 | 0 | return generate_error_coords(); |
383 | 0 | } else { |
384 | 0 | position.u = x0 + transformation->fwd_origin->u; |
385 | 0 | position.v = y0 + transformation->fwd_origin->v; |
386 | 0 | return position; |
387 | 0 | } |
388 | 0 | } |
389 | | |
390 | 0 | static void horner_forward_4d(PJ_COORD &point, PJ *P) { |
391 | 0 | const HORNER *transformation = reinterpret_cast<const HORNER *>(P->opaque); |
392 | 0 | point.uv = real_default_impl(P, transformation, PJ_FWD, point.uv); |
393 | 0 | } |
394 | | |
395 | 0 | static void horner_inverse_4d(PJ_COORD &point, PJ *P) { |
396 | 0 | const HORNER *transformation = reinterpret_cast<const HORNER *>(P->opaque); |
397 | 0 | point.uv = real_default_impl(P, transformation, PJ_INV, point.uv); |
398 | 0 | } |
399 | | |
400 | 0 | static void horner_iterative_inverse_4d(PJ_COORD &point, PJ *P) { |
401 | 0 | const HORNER *transformation = reinterpret_cast<const HORNER *>(P->opaque); |
402 | 0 | point.uv = real_iterative_inverse_impl(P, transformation, point.uv); |
403 | 0 | } |
404 | | |
405 | | static PJ_UV complex_default_impl(PJ *P, const HORNER *transformation, |
406 | 0 | PJ_DIRECTION direction, PJ_UV position) { |
407 | | /*********************************************************************** |
408 | | |
409 | | A reimplementation of a classic Engsager/Poder Horner complex |
410 | | polynomial evaluation engine. |
411 | | |
412 | | ***********************************************************************/ |
413 | 0 | assert(direction == PJ_FWD || direction == PJ_INV); |
414 | |
|
415 | 0 | double n, e; |
416 | 0 | if (direction == PJ_FWD) { /* forward */ |
417 | 0 | e = position.u - transformation->fwd_origin->u; |
418 | 0 | n = position.v - transformation->fwd_origin->v; |
419 | 0 | } else { /* inverse */ |
420 | 0 | e = position.u - transformation->inv_origin->u; |
421 | 0 | n = position.v - transformation->inv_origin->v; |
422 | 0 | } |
423 | 0 | if (transformation->uneg) |
424 | 0 | e = -e; |
425 | 0 | if (transformation->vneg) |
426 | 0 | n = -n; |
427 | |
|
428 | 0 | if (coords_out_of_range(P, transformation, n, e)) { |
429 | 0 | return generate_error_coords(); |
430 | 0 | } |
431 | | |
432 | | // coefficient pointers |
433 | 0 | double *cb = |
434 | 0 | direction == PJ_FWD ? transformation->fwd_c : transformation->inv_c; |
435 | 0 | PJ_UV en = {e, n}; |
436 | 0 | position = complex_horner_eval(transformation->order, cb, en); |
437 | 0 | return position; |
438 | 0 | } |
439 | | |
440 | | static PJ_UV complex_iterative_inverse_impl(PJ *P, const HORNER *transformation, |
441 | 0 | PJ_UV position) { |
442 | |
|
443 | 0 | double n, e; |
444 | | // in this case fwd_origin and any existing flipping needs to be added in |
445 | | // the end |
446 | 0 | e = position.u; |
447 | 0 | n = position.v; |
448 | |
|
449 | 0 | if (coords_out_of_range(P, transformation, n, e)) { |
450 | 0 | return generate_error_coords(); |
451 | 0 | } |
452 | | |
453 | 0 | { |
454 | | // complex real part corresponds to Northing, imag part to Easting |
455 | 0 | const double tol = transformation->inverse_tolerance; |
456 | 0 | const std::complex<double> dZ(n - transformation->fwd_c[0], |
457 | 0 | e - transformation->fwd_c[1]); |
458 | 0 | std::complex<double> w0(0.0, 0.0); |
459 | 0 | int loops = 32; // usually converges really fast (1-2 loops) |
460 | 0 | bool converged = false; |
461 | 0 | while (loops-- > 0 && !converged) { |
462 | | // sum coefficient pointers from back to front until the first |
463 | | // complex pair (fwd_c0+i*fwd_c1) |
464 | 0 | const double *c = transformation->fwd_c; |
465 | 0 | PJ_UV en = {w0.imag(), w0.real()}; |
466 | 0 | en = complex_horner_eval(transformation->order, c, en, 1); |
467 | 0 | std::complex<double> det(en.v, en.u); |
468 | 0 | std::complex<double> w1 = dZ / det; |
469 | 0 | converged = (fabs(w1.real() - w0.real()) < tol) && |
470 | 0 | (fabs(w1.imag() - w0.imag()) < tol); |
471 | 0 | w0 = w1; |
472 | 0 | } |
473 | | // if loops have been exhausted and we have not converged yet, |
474 | | // we are never going to converge |
475 | 0 | if (!converged) { |
476 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM); |
477 | 0 | position = generate_error_coords(); |
478 | 0 | } else { |
479 | 0 | double E = w0.imag(); |
480 | 0 | double N = w0.real(); |
481 | 0 | if (transformation->uneg) |
482 | 0 | E = -E; |
483 | 0 | if (transformation->vneg) |
484 | 0 | N = -N; |
485 | 0 | position.u = E + transformation->fwd_origin->u; |
486 | 0 | position.v = N + transformation->fwd_origin->v; |
487 | 0 | } |
488 | 0 | return position; |
489 | 0 | } |
490 | 0 | } |
491 | | |
492 | 0 | static void complex_horner_forward_4d(PJ_COORD &point, PJ *P) { |
493 | 0 | const HORNER *transformation = reinterpret_cast<const HORNER *>(P->opaque); |
494 | 0 | point.uv = complex_default_impl(P, transformation, PJ_FWD, point.uv); |
495 | 0 | } |
496 | | |
497 | 0 | static void complex_horner_inverse_4d(PJ_COORD &point, PJ *P) { |
498 | 0 | const HORNER *transformation = reinterpret_cast<const HORNER *>(P->opaque); |
499 | 0 | point.uv = complex_default_impl(P, transformation, PJ_INV, point.uv); |
500 | 0 | } |
501 | | |
502 | 0 | static void complex_horner_iterative_inverse_4d(PJ_COORD &point, PJ *P) { |
503 | 0 | const HORNER *transformation = reinterpret_cast<const HORNER *>(P->opaque); |
504 | 0 | point.uv = complex_iterative_inverse_impl(P, transformation, point.uv); |
505 | 0 | } |
506 | | |
507 | 19 | static PJ *horner_freeup(PJ *P, int errlev) { /* Destructor */ |
508 | 19 | if (nullptr == P) |
509 | 0 | return nullptr; |
510 | 19 | if (nullptr == P->opaque) |
511 | 11 | return pj_default_destructor(P, errlev); |
512 | 8 | horner_free((HORNER *)P->opaque); |
513 | 8 | P->opaque = nullptr; |
514 | 8 | return pj_default_destructor(P, errlev); |
515 | 19 | } |
516 | | |
517 | 8 | static int parse_coefs(PJ *P, double *coefs, const char *param, int ncoefs) { |
518 | 8 | char *buf, *init, *next = nullptr; |
519 | 8 | int i; |
520 | | |
521 | 8 | size_t buf_size = strlen(param) + 2; |
522 | 8 | buf = static_cast<char *>(calloc(buf_size, sizeof(char))); |
523 | 8 | if (nullptr == buf) { |
524 | 0 | proj_log_error(P, "No memory left"); |
525 | 0 | return 0; |
526 | 0 | } |
527 | | |
528 | 8 | snprintf(buf, buf_size, "t%s", param); |
529 | 8 | if (0 == pj_param(P->ctx, P->params, buf).i) { |
530 | 8 | free(buf); |
531 | 8 | return 0; |
532 | 8 | } |
533 | 0 | snprintf(buf, buf_size, "s%s", param); |
534 | 0 | init = pj_param(P->ctx, P->params, buf).s; |
535 | 0 | free(buf); |
536 | |
|
537 | 0 | for (i = 0; i < ncoefs; i++) { |
538 | 0 | if (i > 0) { |
539 | 0 | if (next == nullptr || ',' != *next) { |
540 | 0 | proj_log_error(P, "Malformed polynomium set %s. need %d coefs", |
541 | 0 | param, ncoefs); |
542 | 0 | return 0; |
543 | 0 | } |
544 | 0 | init = ++next; |
545 | 0 | } |
546 | 0 | coefs[i] = pj_strtod(init, &next); |
547 | 0 | } |
548 | 0 | return 1; |
549 | 0 | } |
550 | | |
551 | | /*********************************************************************/ |
552 | 19 | PJ *PJ_PROJECTION(horner) { |
553 | | /*********************************************************************/ |
554 | 19 | int degree = 0; |
555 | 19 | HORNER *Q; |
556 | 19 | P->fwd3d = nullptr; |
557 | 19 | P->inv3d = nullptr; |
558 | 19 | P->fwd = nullptr; |
559 | 19 | P->inv = nullptr; |
560 | 19 | P->left = P->right = PJ_IO_UNITS_WHATEVER; |
561 | 19 | P->destructor = horner_freeup; |
562 | | |
563 | | /* Polynomial degree specified? */ |
564 | 19 | if (pj_param(P->ctx, P->params, "tdeg").i) { /* degree specified? */ |
565 | 16 | degree = pj_param(P->ctx, P->params, "ideg").i; |
566 | 16 | if (degree < 0 || degree > 10000) { |
567 | | /* What are reasonable minimum and maximums for degree? */ |
568 | 8 | proj_log_error(P, _("Degree is unreasonable: %d"), degree); |
569 | 8 | return horner_freeup(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
570 | 8 | } |
571 | 16 | } else { |
572 | 3 | proj_log_error(P, _("Must specify polynomial degree, (+deg=n)")); |
573 | 3 | return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
574 | 3 | } |
575 | | |
576 | 8 | bool complex_polynomia = false; |
577 | 8 | if (pj_param(P->ctx, P->params, "tfwd_c").i || |
578 | 8 | pj_param(P->ctx, P->params, "tinv_c").i) /* complex polynomium? */ |
579 | 0 | complex_polynomia = true; |
580 | | |
581 | 8 | Q = horner_alloc(degree, complex_polynomia); |
582 | 8 | if (Q == nullptr) |
583 | 0 | return horner_freeup(P, PROJ_ERR_OTHER /*ENOMEM*/); |
584 | 8 | P->opaque = Q; |
585 | | |
586 | 8 | bool has_inv = false; |
587 | 8 | if (!complex_polynomia) { |
588 | 8 | has_inv = pj_param_exists(P->params, "inv_u") || |
589 | 8 | pj_param_exists(P->params, "inv_v") || |
590 | 8 | pj_param_exists(P->params, "inv_origin"); |
591 | 8 | } else { |
592 | 0 | has_inv = pj_param_exists(P->params, "inv_c") || |
593 | 0 | pj_param_exists(P->params, "inv_origin"); |
594 | 0 | } |
595 | 8 | Q->has_inv = has_inv; |
596 | | |
597 | | // setup callbacks |
598 | 8 | if (complex_polynomia) { |
599 | 0 | P->fwd4d = complex_horner_forward_4d; |
600 | 0 | P->inv4d = has_inv ? complex_horner_inverse_4d |
601 | 0 | : complex_horner_iterative_inverse_4d; |
602 | 8 | } else { |
603 | 8 | P->fwd4d = horner_forward_4d; |
604 | 8 | P->inv4d = has_inv ? horner_inverse_4d : horner_iterative_inverse_4d; |
605 | 8 | } |
606 | | |
607 | 8 | if (complex_polynomia) { |
608 | | /* Westings and/or southings? */ |
609 | 0 | Q->uneg = pj_param_exists(P->params, "uneg") ? 1 : 0; |
610 | 0 | Q->vneg = pj_param_exists(P->params, "vneg") ? 1 : 0; |
611 | |
|
612 | 0 | const int n = |
613 | 0 | static_cast<int>(horner_number_of_complex_coefficients(degree)); |
614 | 0 | if (0 == parse_coefs(P, Q->fwd_c, "fwd_c", n)) { |
615 | 0 | proj_log_error(P, _("missing fwd_c")); |
616 | 0 | return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
617 | 0 | } |
618 | 0 | if (has_inv && 0 == parse_coefs(P, Q->inv_c, "inv_c", n)) { |
619 | 0 | proj_log_error(P, _("missing inv_c")); |
620 | 0 | return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
621 | 0 | } |
622 | 8 | } else { |
623 | 8 | const int n = |
624 | 8 | static_cast<int>(horner_number_of_real_coefficients(degree)); |
625 | 8 | if (0 == parse_coefs(P, Q->fwd_u, "fwd_u", n)) { |
626 | 8 | proj_log_error(P, _("missing fwd_u")); |
627 | 8 | return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
628 | 8 | } |
629 | 0 | if (0 == parse_coefs(P, Q->fwd_v, "fwd_v", n)) { |
630 | 0 | proj_log_error(P, _("missing fwd_v")); |
631 | 0 | return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
632 | 0 | } |
633 | 0 | if (has_inv && 0 == parse_coefs(P, Q->inv_u, "inv_u", n)) { |
634 | 0 | proj_log_error(P, _("missing inv_u")); |
635 | 0 | return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
636 | 0 | } |
637 | 0 | if (has_inv && 0 == parse_coefs(P, Q->inv_v, "inv_v", n)) { |
638 | 0 | proj_log_error(P, _("missing inv_v")); |
639 | 0 | return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
640 | 0 | } |
641 | 0 | } |
642 | | |
643 | 0 | if (0 == parse_coefs(P, &(Q->fwd_origin->u), "fwd_origin", 2)) { |
644 | 0 | proj_log_error(P, _("missing fwd_origin")); |
645 | 0 | return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
646 | 0 | } |
647 | 0 | if (has_inv && 0 == parse_coefs(P, &(Q->inv_origin->u), "inv_origin", 2)) { |
648 | 0 | proj_log_error(P, _("missing inv_origin")); |
649 | 0 | return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
650 | 0 | } |
651 | 0 | if (0 == parse_coefs(P, &Q->range, "range", 1)) |
652 | 0 | Q->range = 500000; |
653 | 0 | if (0 == parse_coefs(P, &Q->inverse_tolerance, "inv_tolerance", 1)) |
654 | 0 | Q->inverse_tolerance = 0.001; |
655 | |
|
656 | 0 | return P; |
657 | 0 | } |