aboutsummaryrefslogtreecommitdiff
path: root/src/PJ_horner.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/PJ_horner.c')
-rw-r--r--src/PJ_horner.c953
1 files changed, 953 insertions, 0 deletions
diff --git a/src/PJ_horner.c b/src/PJ_horner.c
new file mode 100644
index 00000000..f39d2c2c
--- /dev/null
+++ b/src/PJ_horner.c
@@ -0,0 +1,953 @@
+#define PJ_LIB__
+#include <proj.h>
+#include <projects.h>
+#include <assert.h>
+#include <stddef.h>
+#include <math.h>
+#include <errno.h>
+PROJ_HEAD(horner, "Horner polynomial evaluation");
+#define HORNER_SILENCE
+
+/* The next few hundred lines comprises a direct cut-and-paste from the horner.h header library */
+
+/***********************************************************************
+
+ Interfacing to a classic piece of geodetic software
+
+************************************************************************
+
+ gen_pol is a highly efficient, classic implementation of a generic
+ 2D Horner's Scheme polynomial evaluation routine by Knud Poder and
+ Karsten Engsager, originating in the vivid geodetic environment at
+ what was then (1960-ish) the Danish Geodetic Institute.
+
+ The original Poder/Engsager gen_pol implementation (where
+ the polynomial degree and two sets of polynomial coefficients
+ are packed together in one compound array, handled via a simple
+ double pointer) is compelling and "true to the code history":
+
+ It has a beautiful classical 1960s ring to it, not unlike the
+ original fft implementations, which revolutionized spectral
+ analysis in twenty lines of code.
+
+ The Poder coding sound, as classic 1960s as Phil Spector's Wall
+ of Sound, is beautiful and inimitable.
+
+ On the other hand: For the uninitiated, the gen_pol code is hard
+ to follow, despite being compact.
+
+ Also, since adding metadata and improving maintainability
+ of the code are among the implied goals of a current SDFE/DTU Space
+ project, the material in this file introduces a version with a more
+ more modern (or at least 1990s) look, introducing a "double 2D
+ polynomial" data type, HORNER.
+
+ Despite introducing a new data type for handling the polynomial
+ coefficients, great care has been taken to keep the coefficient
+ array organization identical to that of gen_pol.
+
+ Hence, on one hand, the HORNER data type helps improving the
+ long term maintainability of the code by making the data
+ organization more mentally accessible.
+
+ On the other hand, it allows us to preserve the business end of
+ the original gen_pol implementation - although not including the
+ famous "Poder dual autocheck" in all its enigmatic elegance.
+
+ The original code has, however, been included in the conditionally
+ compiled TEST-section.
+
+ This is partially for validation of the revised version, partially
+ to enable more generic enjoyment of an interesting piece of
+ ingenious geodetic code - simplistic and enigmatic at the same time.
+
+ **********************************************************************
+
+ The material included here was written by Knud Poder, starting
+ around 1960, and Karsten Engsager, starting around 1970. It was
+ originally written in Algol 60, later (1980s) reimplemented in C.
+
+ The HORNER data type interface, and the organization as a header
+ library was implemented by Thomas Knudsen, starting around 2015.
+
+ ***********************************************************************
+ *
+ * The gen_pol routine comes with this legal statement (ISC/OpenBSD):
+ *
+ * Copyright (c) 2011, National Survey and Cadastre, Denmark
+ * (Kort- og Matrikelstyrelsen), kms@kms.dk
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ *
+ *******************************************************************************
+ *
+ * The remaining parts are...
+ *
+ * Copyright (c) 2016, Thomas Knudsen / Karsten Engsager / SDFE http://www.sdfe.dk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ *****************************************************************************/
+
+
+ #ifndef HORNER_H
+ #define HORNER_H
+ #ifdef __cplusplus
+ extern "C" {
+ #endif
+
+
+
+#if defined(PROJ_H) || defined(PROJECTS_H)
+#define horner_dealloc(x) pj_dealloc(x)
+#define horner_calloc(n,x) pj_calloc(n,x)
+#else
+#define horner_dealloc(x) free(x)
+#define horner_calloc(n,x) calloc(n,x)
+typedef struct {double u,v;} UV;
+#endif
+
+struct horner;
+typedef struct horner HORNER;
+static UV horner (const HORNER *transformation, int direction, UV position);
+static HORNER *horner_alloc (size_t order);
+static void horner_free (HORNER *h);
+
+struct horner {
+ int order; /* maximum degree of polynomium */
+ int coefs; /* number of coefficients for each polynomium */
+ double range; /* radius of the region of validity */
+
+ double *fwd_u; /* coefficients for the forward transformations */
+ double *fwd_v; /* i.e. latitude/longitude to northing/easting */
+
+ double *inv_u; /* coefficients for the inverse transformations */
+ double *inv_v; /* i.e. northing/easting to latitude/longitude */
+
+ UV *fwd_origin; /* False longitude/latitude */
+ UV *inv_origin; /* False easting/northing */
+};
+
+/* e.g. degree = 2: a + bx + cy + dxx + eyy + fxy, i.e. 6 coefficients */
+#define horner_number_of_coefficients(order) \
+ (((order + 1)*(order + 2)/2))
+
+static int horner_degree_u (int order, int index);
+static int horner_degree_v (int order, int index);
+static int horner_index (int order, int degree_u, int degree_v);
+
+#ifndef HORNER_HEADER_ONLY
+
+/***************************************************************************/
+static int horner_index (int order, int degree_1, int degree_2) {
+/****************************************************************************
+
+ Returns the index of the polynomial coefficient, C, for the element
+
+ C * pow (c_1, degree_2) * pow (c_2, degree_2),
+
+ given that degree_1 > -1, degree_2 > -1, degree_1 + degree_2 <= order.
+
+ Otherwise returns -1 and sets errno to EDOM.
+
+ The range of the index is [0 : (order + 1) * (order + 2) / 2 - 1].
+
+ A very important thing to note is that the order of the coordinates
+ c_1 and c_2 depend on the polynomium:
+
+ For the fwd and inv polynomia for the "u" coordinate,
+ u is first (degree_1), v is second (degree_2).
+ For the fwd and inv polynomia for the "v" coordinate,
+ v is first (degree_1), u is second (degree_2).
+
+****************************************************************************/
+ if ( (degree_1 < 0) || (degree_2 < 0) || (degree_1 + degree_2 > order) ) {
+ errno = EDOM;
+ return -1;
+ }
+
+ return ( horner_number_of_coefficients(order) - 1
+ - (order - degree_1)*(order - degree_1 + 1)/2
+ - (order - degree_1 - degree_2));
+}
+
+#define index_u(h, u, v) horner_index (h->order, u, v)
+#define index_v(h, u, v) horner_index (h->order, v, u)
+
+
+static int horner_degree_u (int order, int index) {
+ int n = horner_number_of_coefficients(order);
+ int i, j;
+ if ((order < 0) || (index >= n)) {
+ errno = EDOM;
+ return -1;
+ }
+ for (i = 0; i <= order; i++)
+ for (j = 0; j <= order - i; j++)
+ if (index == horner_index (order, i, j))
+ return i;
+ return -1;
+}
+
+
+static int horner_degree_v (int order, int index) {
+ int n = horner_number_of_coefficients(order);
+ int i, j;
+ if ((order < 0) || (index >= n)) {
+ errno = EDOM;
+ return -1;
+ }
+ for (i = 0; i <= order; i++)
+ for (j = 0; j <= order - i; j++)
+ if (index == horner_index (order, i, j))
+ return j;
+ return -1;
+}
+
+
+
+
+static void horner_free (HORNER *h) {
+ horner_dealloc (h->inv_v);
+ horner_dealloc (h->inv_u);
+ horner_dealloc (h->fwd_v);
+ horner_dealloc (h->fwd_u);
+ horner_dealloc (h);
+}
+
+
+static HORNER *horner_alloc (size_t order) {
+ /* size_t is unsigned, so we need not check for order > 0 */
+ int n = horner_number_of_coefficients(order);
+
+ HORNER *h = horner_calloc (1, sizeof (HORNER));
+
+ if (0==h)
+ return 0;
+
+ h->order = order;
+ h->coefs = n;
+
+ h->fwd_u = horner_calloc (n, sizeof(double));
+ h->fwd_v = horner_calloc (n, sizeof(double));
+ h->inv_u = horner_calloc (n, sizeof(double));
+ h->inv_v = horner_calloc (n, sizeof(double));
+
+ h->fwd_origin = horner_calloc (1, sizeof(UV));
+ h->inv_origin = horner_calloc (1, sizeof(UV));
+
+ if (h->fwd_u && h->fwd_v && h->inv_u && h->inv_v && h->fwd_origin && h->inv_origin)
+ return h;
+
+ /* safe, since all pointers are null-initialized (by calloc) */
+ horner_free (h);
+ return 0;
+}
+
+
+
+
+/**********************************************************************/
+static UV horner (const HORNER *transformation, int direction, UV position) {
+/***********************************************************************
+
+A reimplementation of the classic Engsager/Poder 2D Horner polynomial
+evaluation engine "gen_pol".
+
+This version omits the inimitable Poder "dual autocheck"-machinery,
+which here is intended to be implemented at a higher level of the
+library: We separate the polynomial evaluation from the quality
+control (which, given the limited MTBF for "computing machinery",
+typical when Knud Poder invented the dual autocheck method,
+was not defensible at that time).
+
+Another difference from the original version is that we return the
+result on the stack, rather than accepting pointers to result variables
+as input. This results in code that is easy to read:
+
+ projected = horner (s34j, 1, geographic);
+ geographic = horner (s34j, -1, projected );
+
+and experiments have shown that on contemporary architectures, the time
+taken for returning even comparatively large objects on the stack (and
+the UV is not that large - typically only 16 bytes) is negligibly
+different from passing two pointers (i.e. typically also 16 bytes) the
+other way.
+
+The polynomium has the form:
+
+P = sum (i = [0 : order])
+ sum (j = [0 : order - i])
+ pow(par_1, i) * pow(par_2, j) * coef(index(order, i, j))
+
+For numerical stability, the summation is carried out backwards,
+summing the tiny high order elements first.
+
+***********************************************************************/
+
+ /* These variable names follow the Engsager/Poder implementation */
+ int sz; /* Number of coefficients per polynomial */
+ double *tcx, *tcy; /* Coefficient pointers */
+ double range; /* Equivalent to the gen_pol's FLOATLIMIT constant */
+ double n, e;
+ UV uv_error;
+ uv_error.u = uv_error.v = HUGE_VAL;
+
+ if (0==transformation)
+ return uv_error;
+
+ /* Check for valid value of direction (-1, 0, 1) */
+ switch (direction) {
+ case 0: /* no-op */
+ return position;
+ case 1: /* forward */
+ case -1: /* inverse */
+ break;
+ default: /* invalid */
+ errno = EINVAL;
+ return uv_error;
+ }
+
+ /* Prepare for double Horner */
+ sz = horner_number_of_coefficients(transformation->order);
+ range = transformation->range;
+
+
+ if (direction==1) { /* forward */
+ tcx = transformation->fwd_u + sz;
+ tcy = transformation->fwd_v + sz;
+ e = position.u - transformation->fwd_origin->u;
+ n = position.v - transformation->fwd_origin->v;
+ } else { /* inverse */
+ tcx = transformation->inv_u + sz;
+ tcy = transformation->inv_v + sz;
+ e = position.u - transformation->inv_origin->u;
+ n = position.v - transformation->inv_origin->v;
+ }
+
+ if ((fabs(n) > range) || (fabs(e) > range)) {
+ errno = EDOM;
+ return uv_error;
+ }
+
+ /* The melody of this block is straight out of the great Engsager/Poder songbook */
+ else {
+ int g = transformation->order;
+ int r = g, c;
+ double u, v, N, E;
+
+ /* Double Horner's scheme: N = n*Cy*e -> yout, E = e*Cx*n -> xout */
+ for (N = *--tcy, E = *--tcx; r > 0; r--) {
+ for (c = g, u = *--tcy, v = *--tcx; c >= r; c--) {
+ u = n*u + *--tcy;
+ v = e*v + *--tcx;
+ }
+ N = e*N + u;
+ E = n*E + v;
+ }
+
+ position.u = E;
+ position.v = N;
+ }
+
+ return position;
+}
+
+
+#ifdef HORNER_SILENCE
+/**********************************************************************/
+static int horner_silence (int i) {
+/***********************************************************************
+ useless function that silences coompiler warnings about unused stuff
+***********************************************************************/
+ HORNER *Q;
+ UV uv_error;
+ if (i==0)
+ return i;
+ uv_error.u = uv_error.v = HUGE_VAL;
+ horner(0, 1, uv_error);
+ Q = horner_alloc (2);
+ if (Q)
+ horner_free (Q);
+ if (horner_degree_u (2,1))
+ return horner_degree_v (2,1);
+ return i;
+}
+#endif /* def HORNER_SILENCE */
+
+
+
+
+#ifdef HORNER_TEST_ORIGINAL_GEN_POL_CODE
+
+double fwd_u[] = {1,2,3};
+double fwd_v[] = {4,5,6};
+double inv_u[] = {4,6,5};
+double inv_v[] = {1,3,2};
+UV uv_origin_fwd = {0, 0};
+UV uv_origin_inv = {0, 0};
+HORNER uv = {1, 3, 500000.0, fwd_u, fwd_v, inv_u, inv_v, &uv_origin_fwd, &uv_origin_inv};
+
+void show_test (void);
+void tuut_b_test (void);
+
+
+int main (int argc, char **argv) {
+ int i, j, order = 10;
+ UV res = {1,1};
+
+ if (argc==2)
+ order = atoi (argv[1]);
+
+ printf ("Testing %d combinations\n", horner_number_of_coefficients(order));
+
+ for (i = 0; i <= order; i++)
+ for (j = 0; j <= order - i; j++) {
+ int hh = horner_index (order, i, j);
+ int ii = horner_degree_u (order, hh);
+ int jj = horner_degree_v (order, hh);
+ assert (i==ii);
+ assert (j==jj);
+ printf ("%2.2d %1d%1du %1d%1dv\n", hh, i, ii, j, jj);
+ }
+
+ tuut_b_test ();
+
+ puts ("Forward...");
+
+ printf ("inp = {%.4f, %.4f}\n", res.u, res.v);
+ res = horner (&uv, 1, res);
+ printf ("res = {%.4f, %.4f}\n", res.u, res.v);
+ assert ( 6==res.u); /* fwd_u: a + bu + cv = 1 + 2*1 + 3*1 = 6 */
+ assert (15==res.v); /* fwd_v: a + bu + cv = 4 + 5*1 + 6*1 = 15 */
+
+ res.u = 2;
+ printf ("inp = {%.4f, %.4f}\n", res.u, res.v);
+ res = horner (&uv, 1, res);
+ printf ("res = {%.4f, %.4f}\n", res.u, res.v); /* u = 2, v = 15 */
+ assert (50==res.u); /* fwd_u: a + bu + cv = 1 + 2*(u=2) + 3*(v=15) = 50 */
+ assert (91==res.v); /* fwd_v: a + bu + cv = 4 + 5*(v=15) + 6*(u=2) = 91 */
+
+ puts ("Inverse...");
+
+ res.u = 1;
+ res.v = 1;
+ printf ("inp = {%.4f, %.4f}\n", res.u, res.v);
+ res = horner (&uv, -1, res);
+ printf ("res = {%.4f, %.4f}\n", res.u, res.v); /* u = 1, v = 1 */
+ assert (15==res.u); /* inv_u: a + bu + cv = 4 + 5*1 + 6*1 = 15 */
+ assert ( 6==res.v); /* inv_v: a + bu + cv = 1 + 2*1 + 3*1 = 6 */
+
+ res.u = 2;
+ printf ("inp = {%.4f, %.4f}\n", res.u, res.v);
+ res = horner (&uv, -1, res);
+ printf ("res = {%.4f, %.4f}\n", res.u, res.v); /* u = 2, v = 6 */
+ assert (46==res.u); /* inv_u: a + bu + cv = 4 + 6*(u=2) + 5*(v=6) = 46 */
+ assert (23==res.v); /* inv_v: a + bu + cv = 1 + 2*(u=2) + 3*(v=6) = 23 */
+
+/* show_test ();*/
+ return 0;
+}
+
+
+
+
+
+
+/* ttu_n and ttu_e are based on static double C_ttu_b[] */
+static double ttu_n[] = {
+ /* tc32_ed50 -> utm32_ed50 : Bornholm */
+ /* m_lim_gen: 0.086 red = 0 OBS = 852 */
+ /* m = 1.38 cm my_loss = +2 y_enp = +10.5 */
+ /* m = 1.44 cm mx_loss = +2 x_enp = +10.4 */
+
+ /*deg 4.0,*/
+ /*Poly NORTH :: e-degree = 0 : n-degree = 4 */
+ /* 0*/ 6.1258112678e+06, 9.9999971567e-01, 1.5372750011e-10,
+ /* 3*/ 5.9300860915e-15, 2.2609497633e-19,
+
+ /*Poly NORTH :: e-degree = 1 : n-degree = 3 */
+ /* 5*/ 4.3188227445e-05, 2.8225130416e-10, 7.8740007114e-16,
+ /* 8*/ -1.7453997279e-19,
+
+ /*Poly NORTH :: e-degree = 2 : n-degree = 2 */
+ /* 9*/ 1.6877465415e-10, -1.1234649773e-14, -1.7042333358e-18,
+ /*Poly NORTH :: e-degree = 3 : n-degree = 1 */
+ /* 12*/ -7.9303467953e-15, -5.2906832535e-19,
+ /*Poly NORTH :: e-degree = 4 : n-degree = 0 */
+ /* 14*/ 3.9984284847e-19,
+
+ /*tcy 6125810.306769, */
+};
+
+static double ttu_e[] = {
+ /*Poly EAST :: n-degree = 0 : e-degree = 4 */
+ /* 0*/ 8.7760574982e+05, 9.9999752475e-01, 2.8817299305e-10,
+ /* 3*/ 5.5641310680e-15, -1.5544700949e-18,
+
+ /*Poly EAST :: n-degree = 1 : e-degree = 3 */
+ /* 5*/ -4.1357045890e-05, 4.2106213519e-11, 2.8525551629e-14,
+ /* 8*/ -1.9107771273e-18,
+
+ /*Poly EAST :: n-degree = 2 : e-degree = 2 */
+ /* 9*/ 3.3615590093e-10, 2.4380247154e-14, -2.0241230315e-18,
+ /*Poly EAST :: n-degree = 3 : e-degree = 1 */
+ /* 12*/ 1.2429019719e-15, 5.3886155968e-19,
+ /*Poly EAST :: n-degree = 4 : e-degree = 0 */
+ /* 14*/ -1.0167505000e-18,
+
+ /* tcx 877605.269066 */
+ };
+
+
+/* utt_n and utt_e are based on static double C_utt_b[] */
+static double utt_n[] = {
+ /* utm32_ed50 -> tc32_ed50 : Bornholm */
+ /* m_lim_gen: 0.086 red = 0 OBS = 852 */
+ /* m = 1.38 cm my_loss = +2 y_enp = +10.8 */
+ /* m = 1.44 cm mx_loss = +2 x_enp = +10.7 */
+
+ /*deg 4.0,*/
+ /*Poly NORTH :: e-degree = 0 : n-degree = 4 */
+ /* 0*/ 6.1258103208e+06, 1.0000002826e+00, -1.5372762184e-10,
+ /* 3*/ -5.9304261011e-15, -2.2612705361e-19,
+
+ /*Poly NORTH :: e-degree = 1 : n-degree = 3 */
+ /* 5*/ -4.3188331419e-05, -2.8225549995e-10, -7.8529116371e-16,
+ /* 8*/ 1.7476576773e-19,
+
+ /*Poly NORTH :: e-degree = 2 : n-degree = 2 */
+ /* 9*/ -1.6875687989e-10, 1.1236475299e-14, 1.7042518057e-18,
+ /*Poly NORTH :: e-degree = 3 : n-degree = 1 */
+ /* 12*/ 7.9300735257e-15, 5.2881862699e-19,
+ /*Poly NORTH :: e-degree = 4 : n-degree = 0 */
+ /* 14*/ -3.9990736798e-19,
+
+ /*tcy 6125811.281773,*/
+};
+
+static double utt_e[] = {
+ /*Poly EAST :: n-degree = 0 : e-degree = 4 */
+ /* 0*/ 8.7760527928e+05, 1.0000024735e+00, -2.8817540032e-10,
+ /* 3*/ -5.5627059451e-15, 1.5543637570e-18,
+
+ /*Poly EAST :: n-degree = 1 : e-degree = 3 */
+ /* 5*/ 4.1357152105e-05, -4.2114813612e-11, -2.8523713454e-14,
+ /* 8*/ 1.9109017837e-18,
+
+ /*Poly EAST :: n-degree = 2 : e-degree = 2 */
+ /* 9*/ -3.3616407783e-10, -2.4382678126e-14, 2.0245020199e-18,
+ /*Poly EAST :: n-degree = 3 : e-degree = 1 */
+ /* 12*/ -1.2441377565e-15, -5.3885232238e-19,
+ /*Poly EAST :: n-degree = 4 : e-degree = 0 */
+ /* 14*/ 1.0167203661e-18,
+
+ /*tcx 877605.760036 */
+};
+
+UV tuut_b_origin_fwd = {877605.269066, 6125810.306769};
+UV tuut_b_origin_inv = {877605.760036, 6125811.281773};
+HORNER tuut_b = {4, 15, 500000.0, ttu_e, ttu_n, utt_e, utt_n, &tuut_b_origin_fwd, &tuut_b_origin_inv};
+
+
+/* Prototype and forward declarations of the material needed for cross-check with original implementation */
+int gen_pol(double *C_f, double *C_r, double N_in, double E_in, double *Nout, double *Eout);
+static double C_ttu_b[];
+static double C_utt_b[];
+
+
+void gen_pol_roundtrip (double *C_ttu_b, double *C_utt_b, UV fwd) {
+ UV res, hrn;
+ int ret;
+
+ ret = gen_pol(C_ttu_b, C_utt_b, fwd.v, fwd.u, &res.v, &res.u);
+ printf ("\n------\n\n");
+ if (0!=ret) printf ("ret: %d\n", ret);
+ printf ("inp: %11.3f %11.3f\n", fwd.u, fwd.v);
+ printf ("res: %11.3f %11.3f\n", res.u, res.v);
+ hrn = horner (&tuut_b, 1, fwd);
+ printf ("hrn: %11.3f %11.3f\n", hrn.u, hrn.v);
+ assert (hrn.u==res.u);
+ assert (hrn.v==res.v);
+
+ ret = gen_pol(C_utt_b, C_ttu_b, res.v, res.u, &res.v, &res.u);
+ hrn = horner (&tuut_b, -1, hrn);
+ printf ("hrn: %11.3f %11.3f\n", hrn.u, hrn.v);
+ if (0!=ret) printf ("ret: %d\n", ret);
+ printf ("res: %11.3f %11.3f\n", res.u, res.v);
+ assert (hrn.u==res.u);
+ assert (hrn.v==res.v);
+ printf ("inp: %11.3f %11.3f (%.3g mm)\n", fwd.u, fwd.v, 1e3*hypot(fwd.u-res.u, fwd.v-res.v));
+}
+
+void tuut_b_test (void) {
+ UV fwd = tuut_b_origin_fwd;
+ UV res;
+
+ puts ("Bornholm");
+ printf ("fwd: %11.3f %11.3f\n", fwd.u, fwd.v);
+ res = horner (&tuut_b, 1, fwd);
+ printf ("res: %11.3f %11.3f\n", res.u, res.v);
+ res = horner (&tuut_b, -1, res);
+ printf ("res: %11.3f %11.3f\n", res.u, res.v);
+ printf ("fwd: %11.3f %11.3f\n", fwd.u, fwd.v);
+
+ gen_pol_roundtrip (C_ttu_b, C_utt_b, fwd);
+ fwd.u = 877000;
+ fwd.v = 6125000;
+ gen_pol_roundtrip (C_ttu_b, C_utt_b, fwd);
+ fwd.u = 800000;
+ fwd.v = 6100000;
+ gen_pol_roundtrip (C_ttu_b, C_utt_b, fwd);
+ fwd.u = 850000;
+ fwd.v = 6200000;
+ gen_pol_roundtrip (C_ttu_b, C_utt_b, fwd);
+
+}
+
+
+
+
+
+#define FLOATLIMIT 5.0e5
+#define TRF_AREA_ EDOM
+
+/********************************************************************************************/
+int gen_pol(double *C_f, double *C_r, double N_in, double E_in, double *Nout, double *Eout) {
+/*********************************************************************************************
+
+ This is the original Poder/Engsager implementation of gen_pol.
+ It is included here for test-comparison with the horner() routine.
+
+*********************************************************************************************/
+ double N, E, n, e;
+ double *Cp, *tcy, *tcx;
+ double tol = 1.0e-4;
+ int i;
+ int g;
+ int sz;
+ int r, c;
+ int res = 0;
+
+ /* Preserve input for reverse check */
+ N = N_in;
+ E = E_in;
+ Cp = C_f;
+
+ /* Transformation loop */
+ for (i = -1; i <= 1 && res == 0; ++i)
+ if (i) {
+
+ /* Prepare for double Horner */
+ g = (int) *Cp;
+ sz = (g + 1)*(g + 2)/2 + 1;
+ tcy = Cp + sz;
+ tcx = tcy + sz;
+ /* Double Horner's scheme */
+ /* N = n*Cy*e -> yout, E = e*Cx*n -> xout */
+ n = N - *tcy;
+ e = E - *tcx;
+ if ((fabs(n) < FLOATLIMIT) && (fabs(e) < FLOATLIMIT)) {
+
+ for ( r = g, N = *--tcy, E = *--tcx; r > 0; r--) {
+ double u, v;
+ for (c = g, u = *--tcy, v = *--tcx; c >= r; c--) {
+ u = n*u + *--tcy;
+ v = e*v + *--tcx;
+ }
+ N = e*N + u;
+ E = n*E + v;
+ }
+ } else res = TRF_AREA_;
+ }
+ else { /* collect output coord,switch to reverse checking */
+ *Nout = N;
+ *Eout = E;
+ Cp = C_r;
+ }
+
+ /* tol-check of results*/
+ if (res == 0 && (fabs(N - N_in) < tol && fabs(E - E_in) < tol))
+ return (0);
+ else if (res == 0) res = TRF_AREA_;
+ return(res);
+
+#undef FLOATLIMIT
+
+}
+
+
+/* s45b polynomia */
+ static double C_ttu_b[] = {
+ /* tc32_ed50 -> utm32_ed50 : Bornholm */
+ /* m_lim_gen: 0.086 red = 0 OBS = 852 */
+ /* m = 1.38 cm my_loss = +2 y_enp = +10.5 */
+ /* m = 1.44 cm mx_loss = +2 x_enp = +10.4 */
+
+ /*deg*/ 4.0,
+ /*Poly NORTH :: e-degree = 0 : n-degree = 4 */
+ /* 0*/ 6.1258112678e+06, 9.9999971567e-01, 1.5372750011e-10,
+ /* 3*/ 5.9300860915e-15, 2.2609497633e-19,
+
+ /*Poly NORTH :: e-degree = 1 : n-degree = 3 */
+ /* 5*/ 4.3188227445e-05, 2.8225130416e-10, 7.8740007114e-16,
+ /* 8*/ -1.7453997279e-19,
+
+ /*Poly NORTH :: e-degree = 2 : n-degree = 2 */
+ /* 9*/ 1.6877465415e-10, -1.1234649773e-14, -1.7042333358e-18,
+ /*Poly NORTH :: e-degree = 3 : n-degree = 1 */
+ /* 12*/ -7.9303467953e-15, -5.2906832535e-19,
+ /*Poly NORTH :: e-degree = 4 : n-degree = 0 */
+ /* 14*/ 3.9984284847e-19,
+
+ /*tcy*/ 6125810.306769,
+
+ /*Poly EAST :: n-degree = 0 : e-degree = 4 */
+ /* 0*/ 8.7760574982e+05, 9.9999752475e-01, 2.8817299305e-10,
+ /* 3*/ 5.5641310680e-15, -1.5544700949e-18,
+
+ /*Poly EAST :: n-degree = 1 : e-degree = 3 */
+ /* 5*/ -4.1357045890e-05, 4.2106213519e-11, 2.8525551629e-14,
+ /* 8*/ -1.9107771273e-18,
+
+ /*Poly EAST :: n-degree = 2 : e-degree = 2 */
+ /* 9*/ 3.3615590093e-10, 2.4380247154e-14, -2.0241230315e-18,
+ /*Poly EAST :: n-degree = 3 : e-degree = 1 */
+ /* 12*/ 1.2429019719e-15, 5.3886155968e-19,
+ /*Poly EAST :: n-degree = 4 : e-degree = 0 */
+ /* 14*/ -1.0167505000e-18,
+
+ /*tcx*/ 877605.269066
+ };
+
+ static double C_utt_b[] = {
+ /* utm32_ed50 -> tc32_ed50 : Bornholm */
+ /* m_lim_gen: 0.086 red = 0 OBS = 852 */
+ /* m = 1.38 cm my_loss = +2 y_enp = +10.8 */
+ /* m = 1.44 cm mx_loss = +2 x_enp = +10.7 */
+
+ /*deg*/ 4.0,
+ /*Poly NORTH :: e-degree = 0 : n-degree = 4 */
+ /* 0*/ 6.1258103208e+06, 1.0000002826e+00, -1.5372762184e-10,
+ /* 3*/ -5.9304261011e-15, -2.2612705361e-19,
+
+ /*Poly NORTH :: e-degree = 1 : n-degree = 3 */
+ /* 5*/ -4.3188331419e-05, -2.8225549995e-10, -7.8529116371e-16,
+ /* 8*/ 1.7476576773e-19,
+
+ /*Poly NORTH :: e-degree = 2 : n-degree = 2 */
+ /* 9*/ -1.6875687989e-10, 1.1236475299e-14, 1.7042518057e-18,
+ /*Poly NORTH :: e-degree = 3 : n-degree = 1 */
+ /* 12*/ 7.9300735257e-15, 5.2881862699e-19,
+ /*Poly NORTH :: e-degree = 4 : n-degree = 0 */
+ /* 14*/ -3.9990736798e-19,
+
+ /*tcy*/ 6125811.281773,
+
+ /*Poly EAST :: n-degree = 0 : e-degree = 4 */
+ /* 0*/ 8.7760527928e+05, 1.0000024735e+00, -2.8817540032e-10,
+ /* 3*/ -5.5627059451e-15, 1.5543637570e-18,
+
+ /*Poly EAST :: n-degree = 1 : e-degree = 3 */
+ /* 5*/ 4.1357152105e-05, -4.2114813612e-11, -2.8523713454e-14,
+ /* 8*/ 1.9109017837e-18,
+
+ /*Poly EAST :: n-degree = 2 : e-degree = 2 */
+ /* 9*/ -3.3616407783e-10, -2.4382678126e-14, 2.0245020199e-18,
+ /*Poly EAST :: n-degree = 3 : e-degree = 1 */
+ /* 12*/ -1.2441377565e-15, -5.3885232238e-19,
+ /*Poly EAST :: n-degree = 4 : e-degree = 0 */
+ /* 14*/ 1.0167203661e-18,
+
+ /*tcx*/ 877605.760036
+ };
+
+#endif /* HORNER_TEST_ORIGINAL_GEN_POL_CODE */
+
+
+#endif /* ndef HORNER_HEADER_ONLY */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ndef HORNER_H */
+
+
+
+
+
+
+
+static PJ_OBS horner_forward_obs (PJ_OBS point, PJ *P) {
+ point.coo.uv = horner ((HORNER *) P->opaque, 1, point.coo.uv);
+ return point;
+}
+
+static PJ_OBS horner_reverse_obs (PJ_OBS point, PJ *P) {
+ point.coo.uv = horner ((HORNER *) P->opaque, -1, point.coo.uv);
+ return point;
+}
+
+
+
+
+
+
+static void *horner_freeup (PJ *P) { /* Destructor */
+ if (0==P)
+ return 0;
+ if (0==P->opaque)
+ return pj_dealloc (P);
+ horner_free ((HORNER *) P->opaque);
+ return pj_dealloc(P);
+}
+
+static void freeup (PJ *P) {
+ horner_freeup (P);
+ return;
+}
+
+
+static int parse_coefs (PJ *P, double *coefs, char *param, int ncoefs) {
+ char buf[20], *init, *next;
+ int i;
+ sprintf (buf, "t%s", param);
+ if (0==pj_param (P->ctx, P->params, buf).i)
+ return 0;
+ sprintf (buf, "s%s", param);
+ init = pj_param(P->ctx, P->params, buf).s;
+
+ for (i = 0; i < ncoefs; i++) {
+ if (i > 0) {
+ if (','!=*next) {
+ pj_log_error (P, "Horner: Malformed polynomium set %s. need %d coefs", param, ncoefs);
+ return 0;
+ }
+ init = ++next;
+ }
+ coefs[i] = pj_strtod (init, &next);
+ }
+ return 1;
+}
+
+
+/*********************************************************************/
+PJ *PROJECTION(horner) {
+/*********************************************************************/
+ int degree = 0, n;
+ HORNER *Q;
+ P->fwdobs = horner_forward_obs;
+ P->invobs = horner_reverse_obs;
+ P->fwd3d = 0;
+ P->inv3d = 0;
+ P->fwd = 0;
+ P->inv = 0;
+ P->left = P->right = PJ_IO_UNITS_METERS;
+ /* silence a few compiler warnings */
+ horner_silence (0);
+
+ /* Polynomial degree specified? */
+ if (pj_param (P->ctx, P->params, "tdeg").i) /* degree specified? */
+ degree = pj_param(P->ctx, P->params, "ideg").i;
+ else {
+ pj_log_debug (P, "Horner: Need to specify polynomial degree, (+deg=n)");
+ return horner_freeup (P);
+ }
+
+ Q = horner_alloc (degree);
+ P->opaque = (void *) Q;
+
+ n = horner_number_of_coefficients (degree);
+
+ if (0==parse_coefs (P, Q->fwd_u, "fwd_u", n))
+ return horner_freeup (P);
+ if (0==parse_coefs (P, Q->fwd_v, "fwd_v", n))
+ return horner_freeup (P);
+ if (0==parse_coefs (P, Q->inv_u, "inv_u", n))
+ return horner_freeup (P);
+ if (0==parse_coefs (P, Q->inv_v, "inv_v", n))
+ return horner_freeup (P);
+
+ if (0==parse_coefs (P, (double *)(Q->fwd_origin), "fwd_origin", 2))
+ return horner_freeup (P);
+ if (0==parse_coefs (P, (double *)(Q->inv_origin), "inv_origin", 2))
+ return horner_freeup (P);
+ if (0==parse_coefs (P, &Q->range, "range", 1))
+ Q->range = 500000;
+
+ return P;
+}
+
+#ifndef PJ_SELFTEST
+/* selftest stub */
+int pj_horner_selftest (void) {return 0;}
+#else
+
+char tc32_utm32[] = {
+ " +proj=horner"
+ " +ellps=intl"
+ " +range=500000"
+ " +fwd_origin=877605.269066,6125810.306769"
+ " +inv_origin=877605.760036,6125811.281773"
+ " +deg=4"
+ " +fwd_v=6.1258112678e+06,9.9999971567e-01,1.5372750011e-10,5.9300860915e-15,2.2609497633e-19,4.3188227445e-05,2.8225130416e-10,7.8740007114e-16,-1.7453997279e-19,1.6877465415e-10,-1.1234649773e-14,-1.7042333358e-18,-7.9303467953e-15,-5.2906832535e-19,3.9984284847e-19"
+ " +fwd_u=8.7760574982e+05,9.9999752475e-01,2.8817299305e-10,5.5641310680e-15,-1.5544700949e-18,-4.1357045890e-05,4.2106213519e-11,2.8525551629e-14,-1.9107771273e-18,3.3615590093e-10,2.4380247154e-14,-2.0241230315e-18,1.2429019719e-15,5.3886155968e-19,-1.0167505000e-18"
+ " +inv_v=6.1258103208e+06,1.0000002826e+00,-1.5372762184e-10,-5.9304261011e-15,-2.2612705361e-19,-4.3188331419e-05,-2.8225549995e-10,-7.8529116371e-16,1.7476576773e-19,-1.6875687989e-10,1.1236475299e-14,1.7042518057e-18,7.9300735257e-15,5.2881862699e-19,-3.9990736798e-19"
+ " +inv_u=8.7760527928e+05,1.0000024735e+00,-2.8817540032e-10,-5.5627059451e-15,1.5543637570e-18,4.1357152105e-05,-4.2114813612e-11,-2.8523713454e-14,1.9109017837e-18,-3.3616407783e-10,-2.4382678126e-14,2.0245020199e-18,-1.2441377565e-15,-5.3885232238e-19,1.0167203661e-18"
+};
+
+
+int pj_horner_selftest (void) {
+ PJ *P;
+ PJ_OBS a, b;
+ double dist;
+
+ /* The TC32 for "System 45 Bornholm" */
+ /* TC32 -> UTM32" */
+ P = pj_create (tc32_utm32);
+ if (0==P)
+ return 10;
+
+ a = b = pj_obs_null;
+ a.coo.uv.v = 6125305.4245;
+ a.coo.uv.u = 878354.8539;
+
+ /* Forward projection */
+ b = pj_trans (P, PJ_FWD, a);
+ b = pj_trans (P, PJ_INV, b);
+
+ /* Check roundtrip precision for 1 iteration each way */
+ dist = pj_roundtrip (P, PJ_FWD, 1, a);
+ if (dist > 0.01)
+ return 1;
+ return 0;
+}
+#endif