Coverage Report

Created: 2025-07-18 07:18

/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
}