Coverage Report

Created: 2026-06-30 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/transformations/horner.cpp
Line
Count
Source
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
90
#define horner_dealloc(x) free(x)
95
70
#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
20
constexpr uint32_t horner_number_of_real_coefficients(uint32_t order) {
126
20
    return (order + 1) * (order + 2) / 2;
127
20
}
128
129
0
constexpr uint32_t horner_number_of_complex_coefficients(uint32_t order) {
130
0
    return 2 * order + 2;
131
0
}
132
133
10
static void horner_free(HORNER *h) {
134
10
    horner_dealloc(h->inv_v);
135
10
    horner_dealloc(h->inv_u);
136
10
    horner_dealloc(h->fwd_v);
137
10
    horner_dealloc(h->fwd_u);
138
10
    horner_dealloc(h->fwd_c);
139
10
    horner_dealloc(h->inv_c);
140
10
    horner_dealloc(h->fwd_origin);
141
10
    horner_dealloc(h->inv_origin);
142
10
    horner_dealloc(h);
143
10
}
144
145
10
static HORNER *horner_alloc(uint32_t order, bool complex_polynomia) {
146
    /* uint32_t is unsigned, so we need not check for order > 0 */
147
10
    bool polynomia_ok = false;
148
10
    HORNER *h = static_cast<HORNER *>(horner_calloc(1, sizeof(HORNER)));
149
150
10
    if (nullptr == h)
151
0
        return nullptr;
152
153
10
    uint32_t n = complex_polynomia
154
10
                     ? horner_number_of_complex_coefficients(order)
155
10
                     : horner_number_of_real_coefficients(order);
156
157
10
    h->order = order;
158
159
10
    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
10
    } else {
165
10
        h->fwd_u = static_cast<double *>(horner_calloc(n, sizeof(double)));
166
10
        h->fwd_v = static_cast<double *>(horner_calloc(n, sizeof(double)));
167
10
        h->inv_u = static_cast<double *>(horner_calloc(n, sizeof(double)));
168
10
        h->inv_v = static_cast<double *>(horner_calloc(n, sizeof(double)));
169
10
        if (h->fwd_u && h->fwd_v && h->inv_u && h->inv_v)
170
10
            polynomia_ok = true;
171
10
    }
172
173
10
    h->fwd_origin = static_cast<PJ_UV *>(horner_calloc(1, sizeof(PJ_UV)));
174
10
    h->inv_origin = static_cast<PJ_UV *>(horner_calloc(1, sizeof(PJ_UV)));
175
176
10
    if (polynomia_ok && h->fwd_origin && h->inv_origin)
177
10
        return h;
178
179
    /* safe, since all pointers are null-initialized (by calloc) */
180
0
    horner_free(h);
181
0
    return nullptr;
182
10
}
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
22
static PJ *horner_freeup(PJ *P, int errlev) { /* Destructor */
508
22
    if (nullptr == P)
509
0
        return nullptr;
510
22
    if (nullptr == P->opaque)
511
12
        return pj_default_destructor(P, errlev);
512
10
    horner_free((HORNER *)P->opaque);
513
10
    P->opaque = nullptr;
514
10
    return pj_default_destructor(P, errlev);
515
22
}
516
517
10
static int parse_coefs(PJ *P, double *coefs, const char *param, int ncoefs) {
518
10
    char *buf, *init, *next = nullptr;
519
10
    int i;
520
521
10
    size_t buf_size = strlen(param) + 2;
522
10
    buf = static_cast<char *>(calloc(buf_size, sizeof(char)));
523
10
    if (nullptr == buf) {
524
0
        proj_log_error(P, "No memory left");
525
0
        return 0;
526
0
    }
527
528
10
    snprintf(buf, buf_size, "t%s", param);
529
10
    if (0 == pj_param(P->ctx, P->params, buf).i) {
530
10
        free(buf);
531
10
        return 0;
532
10
    }
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
22
PJ *PJ_PROJECTION(horner) {
553
    /*********************************************************************/
554
22
    int degree = 0;
555
22
    HORNER *Q;
556
22
    P->fwd3d = nullptr;
557
22
    P->inv3d = nullptr;
558
22
    P->fwd = nullptr;
559
22
    P->inv = nullptr;
560
22
    P->left = P->right = PJ_IO_UNITS_WHATEVER;
561
22
    P->destructor = horner_freeup;
562
563
    /* Polynomial degree specified? */
564
22
    if (pj_param(P->ctx, P->params, "tdeg").i) { /* degree specified? */
565
20
        degree = pj_param(P->ctx, P->params, "ideg").i;
566
20
        if (degree < 0 || degree > 10000) {
567
            /* What are reasonable minimum and maximums for degree? */
568
10
            proj_log_error(P, _("Degree is unreasonable: %d"), degree);
569
10
            return horner_freeup(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
570
10
        }
571
20
    } else {
572
2
        proj_log_error(P, _("Must specify polynomial degree, (+deg=n)"));
573
2
        return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
574
2
    }
575
576
10
    bool complex_polynomia = false;
577
10
    if (pj_param(P->ctx, P->params, "tfwd_c").i ||
578
10
        pj_param(P->ctx, P->params, "tinv_c").i) /* complex polynomium? */
579
0
        complex_polynomia = true;
580
581
10
    Q = horner_alloc(degree, complex_polynomia);
582
10
    if (Q == nullptr)
583
0
        return horner_freeup(P, PROJ_ERR_OTHER /*ENOMEM*/);
584
10
    P->opaque = Q;
585
586
10
    bool has_inv = false;
587
10
    if (!complex_polynomia) {
588
10
        has_inv = pj_param_exists(P->params, "inv_u") ||
589
10
                  pj_param_exists(P->params, "inv_v") ||
590
10
                  pj_param_exists(P->params, "inv_origin");
591
10
    } else {
592
0
        has_inv = pj_param_exists(P->params, "inv_c") ||
593
0
                  pj_param_exists(P->params, "inv_origin");
594
0
    }
595
10
    Q->has_inv = has_inv;
596
597
    // setup callbacks
598
10
    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
10
    } else {
603
10
        P->fwd4d = horner_forward_4d;
604
10
        P->inv4d = has_inv ? horner_inverse_4d : horner_iterative_inverse_4d;
605
10
    }
606
607
10
    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
10
    } else {
623
10
        const int n =
624
10
            static_cast<int>(horner_number_of_real_coefficients(degree));
625
10
        if (0 == parse_coefs(P, Q->fwd_u, "fwd_u", n)) {
626
10
            proj_log_error(P, _("missing fwd_u"));
627
10
            return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
628
10
        }
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
}