diff options
| -rw-r--r-- | examples/pj_obs_api_test.c | 33 | ||||
| -rw-r--r-- | examples/s45b.pol | 402 | ||||
| -rwxr-xr-x | nad/testvarious | 4 | ||||
| -rw-r--r-- | src/Makefile.am | 2 | ||||
| -rw-r--r-- | src/PJ_calcofi.c | 4 | ||||
| -rw-r--r-- | src/PJ_cart.c | 110 | ||||
| -rw-r--r-- | src/PJ_helmert.c | 492 | ||||
| -rw-r--r-- | src/PJ_horner.c | 953 | ||||
| -rw-r--r-- | src/PJ_krovak.c | 2 | ||||
| -rw-r--r-- | src/PJ_pipeline.c | 54 | ||||
| -rw-r--r-- | src/lib_proj.cmake | 2 | ||||
| -rw-r--r-- | src/makefile.vc | 3 | ||||
| -rw-r--r-- | src/pj_fwd.c | 85 | ||||
| -rw-r--r-- | src/pj_fwd3d.c | 96 | ||||
| -rw-r--r-- | src/pj_init.c | 76 | ||||
| -rw-r--r-- | src/pj_inv.c | 60 | ||||
| -rw-r--r-- | src/pj_inv3d.c | 92 | ||||
| -rw-r--r-- | src/pj_list.h | 2 | ||||
| -rw-r--r-- | src/pj_obs_api.c | 81 | ||||
| -rw-r--r-- | src/pj_param.c | 12 | ||||
| -rw-r--r-- | src/pj_strerrno.c | 1 | ||||
| -rw-r--r-- | src/proj.def | 41 | ||||
| -rw-r--r-- | src/proj.h | 19 |
23 files changed, 2307 insertions, 319 deletions
diff --git a/examples/pj_obs_api_test.c b/examples/pj_obs_api_test.c index c75b4a21..bb792c62 100644 --- a/examples/pj_obs_api_test.c +++ b/examples/pj_obs_api_test.c @@ -44,8 +44,6 @@ int pj_pipeline_selftest (void); - - int main (void) { PJ *P; PJ_OBS a, b; @@ -71,7 +69,7 @@ int main (void) { return puts ("Oops"), 0; /* zero initialize everything, then set (longitude, latitude) to (12, 55) */ - a = pj_obs_null; + a = pj_obs_null (); /* a.coo.lp: The coordinate part of a, interpreted as a classic LP pair */ a.coo.lp.lam = TORAD(12); a.coo.lp.phi = TORAD(55); @@ -100,13 +98,12 @@ int main (void) { dist = pj_xyz_dist (a.coo.xyz, b.coo.xyz); printf ("Roundtrip deviation, (nm): %15.9f\n", dist*1e9); - /* Invalid projection */ a = pj_trans (P, 42, a); if (a.coo.lpz.lam!=DBL_MAX) printf ("%15.9f %15.9f\n", a.coo.lpz.lam, a.coo.lpz.phi); - err = pj_error (P); - printf ("pj_error: %d\n", err); + err = pj_err_level (P, PJ_ERR_TELL); + printf ("pj_err_level: %d\n", err); /* Clean up */ pj_free (P); @@ -117,7 +114,7 @@ int main (void) { return puts ("Oops"), 0; /* zero initialize everything, then set (longitude, latitude, height) to (12, 55, 100) */ - a = b = pj_obs_null; + a = b = pj_obs_null (); a.coo.lpz.lam = TORAD(12); a.coo.lpz.phi = TORAD(55); a.coo.lpz.z = 100; @@ -152,17 +149,18 @@ int main (void) { P I P E L I N E T E S T S - **************************************************************************** - - ***************************************************************************/ /* forward-reverse geo->utm->geo */ - P = pj_create ("+proj=pipeline +ellps=GRS80 +zone=32 +step +proj=utm +step +proj=utm +inv"); + P = pj_create ( + "+proj=pipeline +ellps=GRS80 +zone=32 +step " + "+proj=utm +step " + "+proj=utm +inv" + ); if (0==P) return puts ("Oops"), 0; /* zero initialize everything, then set (longitude, latitude, height) to (12, 55, 100) */ - a = b = pj_obs_null; + a = b = pj_obs_null (); a.coo.lpz.lam = TORAD(12); a.coo.lpz.phi = TORAD(55); a.coo.lpz.z = 100; @@ -180,12 +178,15 @@ int main (void) { /* And now the back-to-back situation utm->geo->utm */ - P = pj_create ("+proj=pipeline +ellps=GRS80 +zone=32 +step +proj=utm +inv +step +proj=utm"); + P = pj_create ( + "+proj=pipeline +ellps=GRS80 +zone=32 +step " + "+proj=utm +inv +step " + "+proj=utm"); if (0==P) return puts ("Oops"), 0; /* zero initialize everything, then set (easting, northing) to utm(12, 55) */ - a = b = pj_obs_null; + a = b = pj_obs_null (); a.coo.xy = cph_utm32; printf ("PRE: %15.9f %15.9f\n", a.coo.xy.x, a.coo.xy.y); @@ -207,7 +208,7 @@ int main (void) { return puts ("Oops"), 0; /* zero initialize everything, then set (easting, northing) to utm(12, 55) */ - a = b = pj_obs_null; + a = b = pj_obs_null (); a.coo.lpz.lam = TORAD(12); a.coo.lpz.phi = TORAD(55); printf ("PRE: %15.9f %15.9f\n", TODEG(a.coo.lp.lam), TODEG(a.coo.lp.phi)); @@ -221,7 +222,6 @@ int main (void) { a = pj_trans (P, PJ_INV, b); printf ("INV: %15.9f %15.9f\n", TODEG(a.coo.lp.lam), TODEG(a.coo.lp.phi)); - /* Geodesic distance between two points with angular 2D coordinates */ a.coo.lp.lam = TORAD(12); a.coo.lp.phi = TORAD(60); @@ -237,7 +237,6 @@ int main (void) { dist = pj_lp_dist (P, a.coo.lp, b.coo.lp); printf ("1 deg at Equator: %15.9f\n", dist); - pj_free (P); pj_pipeline_selftest (); return 0; diff --git a/examples/s45b.pol b/examples/s45b.pol new file mode 100644 index 00000000..9b70fbf7 --- /dev/null +++ b/examples/s45b.pol @@ -0,0 +1,402 @@ +################################################################################ +# +# Horner polynomial evaluation demo data +# +################################################################################ +# +# These are the polynomial coefficients used for transforming to and from +# the Danish legacy system s45b "System 45 Bornholm" +# +################################################################################ + +<s45b> +proj=pipeline + +step init=./s45b.pol:s45b_tc32 +# step init=./s45b.pol:tc32_utm32 + +step proj=utm inv ellps=intl zone=32 +step proj=cart ellps=intl + +step proj=helmert ellps=GRS80 + x=-81.0703 y=-89.3603 z=-115.7526 + rx=-484.88 ry=-24.36 rz=-413.21 s=-0.540645 + +step proj=cart inv ellps=GRS80 +step proj=utm ellps=GRS80 zone=32 + + + + +################################################################################ +# +# S 4 5 B -> T C 3 2 +# +################################################################################ +<s45b_tc32> + +proj=horner +ellps=intl +range=500000 + +fwd_origin=47022.563745,51779.260103 +inv_origin=878354.943082,6125305.175366 +deg=6 + + +# static double C_ttb[] +# tc32_ed50 -> s45b +# m_lim_gen: 0.153 red = 0 OBS = 1074 +# m = 1.51 cm my_loss = +3 y_enp = +8.4 +# m = 1.53 cm mx_loss = +4 x_enp = +8.4 + +# mht C_ttb er +# fwd-inv ombyttet ifht original Poder/Engsager-kode +# For at opnå at to fwd transform fører fra s45b->tc32->utm32 (->ETRS89) + +inv_v=\ +# Poly NORTH :: e-degree = 0 : n-degree = 6 + 5.1779004699e+04,\ + 9.9508320295e-01,\ + -2.9453823207e-10,\ + 1.9995084102e-14,\ + -1.4895751366e-18,\ + -9.9734812211e-23,\ + 1.1194218845e-26,\ + +# Poly NORTH :: e-degree = 1 : n-degree = 5 + -8.4285679515e-02,\ + -7.9623049286e-09,\ + -3.7190046062e-14,\ + -2.3324127411e-18,\ + -1.1150449763e-22,\ + 2.8703154270e-27,\ + +# Poly NORTH :: e-degree = 2 : n-degree = 4 + 8.7160434140e-10,\ + -3.3634602927e-14,\ + -5.5718245313e-18,\ + 6.2611750909e-23,\ + -2.1011243838e-26,\ + +# Poly NORTH :: e-degree = 3 : n-degree = 3 + 1.0905463989e-14,\ + -4.3960034360e-18,\ + 3.6121595001e-22,\ + -1.3493066011e-27,\ + +# Poly NORTH :: e-degree = 4 : n-degree = 2 + -1.3360171462e-18,\ + 1.0780850646e-22,\ + 4.5118286607e-26,\ + +# Poly NORTH :: e-degree = 5 : n-degree = 1 + -1.3718883973e-22,\ + 1.6263920750e-26,\ + +# Poly NORTH :: e-degree = 6 : n-degree = 0 + -5.1004217526e-27 + +# tcy 6125305.175366 + +inv_u=\ +# Poly EAST :: n-degree = 0 : e-degree = 6 + 4.7022495967e+04,\ + -9.9508282498e-01,\ + 3.2436283039e-09,\ + -2.6276394334e-15,\ + 8.6318533291e-18,\ + -3.8327518550e-23,\ + -2.5704924282e-26,\ + +# Poly EAST :: n-degree = 1 : e-degree = 5 + -8.4285975934e-02,\ + 5.7098765263e-10,\ + -6.0863955939e-14,\ + 2.3608788740e-18,\ + 6.8899581969e-24,\ + -1.1429511179e-26,\ + +# Poly EAST :: n-degree = 2 : e-degree = 4 + -4.6079778412e-09,\ + 1.5072604543e-14,\ + 5.4063862378e-18,\ + 1.2591327827e-22,\ + 7.9336388691e-27,\ + +# Poly EAST :: n-degree = 3 : e-degree = 3 + -2.9479268638e-14,\ + 1.7090049434e-18,\ + 2.8413337985e-22,\ + -3.3577391552e-27,\ + +# Poly EAST :: n-degree = 4 : e-degree = 2 + 3.0434879273e-18,\ + -1.8081673510e-22,\ + -2.3651419850e-26,\ + +# Poly EAST :: n-degree = 5 : e-degree = 1 + 9.2060044804e-23,\ + 3.7807953325e-27,\ + +# Poly EAST :: n-degree = 6 : e-degree = 0 + -4.9415665221e-27 + +# tcx 878354.943082 + + +# static double C_btt[] +# s45b -> tc32_ed50 +# m_lim_gen: 0.154 red = 0 OBS = 1074 +# m = 1.50 cm my_loss = +3 y_enp = +8.5 +# m = 1.54 cm mx_loss = +4 x_enp = +8.3 + +fwd_v=\ +# Poly NORTH :: e-degree = 0 : n-degree = 6 + 6.1253054245e+06,\ + 9.9778251908e-01,\ + -7.7346152025e-10,\ + -2.5359789369e-14,\ + 1.5614918228e-18,\ + 9.8091134295e-23,\ + -1.1092581145e-26,\ + +# Poly NORTH :: e-degree = 1 : n-degree = 5 + -8.4514352088e-02,\ + -7.9847579284e-09,\ + -2.6865560962e-14,\ + -2.0731372756e-18,\ + -1.3660341123e-22,\ + 1.1244836340e-26,\ + +# Poly NORTH :: e-degree = 2 : n-degree = 4 + 8.0551988135e-11,\ + 3.6661500679e-14,\ + 5.4247705403e-18,\ + 8.4494604807e-23,\ + 1.3334858516e-26,\ + +# Poly NORTH :: e-degree = 3 : n-degree = 3 + 8.3889821184e-15,\ + -4.8124202237e-18,\ + 2.9088188830e-22,\ + -2.0129874264e-26,\ + +# Poly NORTH :: e-degree = 4 : n-degree = 2 + 2.4716463766e-18,\ + -2.1717177513e-22,\ + -3.2828537638e-26,\ + +# Poly NORTH :: e-degree = 5 : n-degree = 1 + -1.2080655753e-22,\ + 2.5050435391e-26,\ + +# Poly NORTH :: e-degree = 6 : n-degree = 0 + 1.1383483826e-27 + +# tcy 51779.260103, + +fwd_u=\ +# Poly EAST :: n-degree = 0 : e-degree = 6 + 8.7835485387e+05,\ + -9.9778289691e-01,\ + 3.2537215213e-09,\ + 6.9217640616e-15,\ + 8.6268883840e-18,\ + 4.6748156909e-23,\ + -2.6492402009e-26,\ + +# Poly EAST :: n-degree = 1 : e-degree = 5 + -8.4514648771e-02,\ + 1.4399520180e-09,\ + -6.0423329711e-14,\ + 6.9816167332e-20,\ + 6.7729233542e-23,\ + -5.3308251880e-27,\ + +# Poly EAST :: n-degree = 2 : e-degree = 4 + -4.5697800099e-09,\ + -1.5194038814e-14,\ + 5.1112653016e-18,\ + -2.0307532869e-22,\ + 1.0374125432e-26,\ + +# Poly EAST :: n-degree = 3 : e-degree = 3 + -2.8983003841e-14,\ + -1.6414425785e-18,\ + 1.7874983379e-22,\ + 1.5492164174e-26,\ + +# Poly EAST :: n-degree = 4 : e-degree = 2 + 2.7919197366e-18,\ + 1.9218613279e-22,\ + -2.1007264634e-26,\ + +# Poly EAST :: n-degree = 5 : e-degree = 1 + 1.0032412389e-22,\ + -5.9007997846e-27,\ + +# Poly EAST :: n-degree = 6 : e-degree = 0 + -4.4410970979e-27 + +# tcx 47022.563745 + + + + + +################################################################################ +# +# T C 3 2 -> U T M 3 2 +# +################################################################################ + +<tc32_utm32> + +proj=horner +ellps=intl +range=500000 + + +fwd_origin=877605.269066,6125810.306769 +inv_origin=877605.760036,6125811.281773 + + +# tc32_ed50 -> utm32_ed50 : Bornholm + +deg=4 + +# ttu_n and ttu_e are based on static double C_ttu_b[] +# 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 +# static double ttu_n[] + +fwd_v=\ +# Poly NORTH :: e-degree = 0 : n-degree = 0..4 + 6.1258112678e+06,\ + 9.9999971567e-01,\ + 1.5372750011e-10,\ + 5.9300860915e-15,\ + 2.2609497633e-19,\ + +# Poly NORTH :: e-degree = 1 : n-degree = 0..3 + 4.3188227445e-05,\ + 2.8225130416e-10,\ + 7.8740007114e-16,\ + -1.7453997279e-19,\ + +# Poly NORTH :: e-degree = 2 : n-degree = 0..2 + 1.6877465415e-10,\ + -1.1234649773e-14,\ + -1.7042333358e-18,\ + +# Poly NORTH :: e-degree = 3 : n-degree = 0..1 + -7.9303467953e-15,\ + -5.2906832535e-19,\ + +# Poly NORTH :: e-degree = 4 : n-degree = 0 + 3.9984284847e-19 + +# tcy 6125810.306769 + + + +# static double ttu_e[] +fwd_u=\ +# Poly EAST :: n-degree = 0 : e-degree = 0..4 + 8.7760574982e+05,\ + 9.9999752475e-01,\ + 2.8817299305e-10,\ + 5.5641310680e-15,\ + -1.5544700949e-18,\ + +# Poly EAST :: n-degree = 1 : e-degree = 3 + -4.1357045890e-05,\ + 4.2106213519e-11,\ + 2.8525551629e-14,\ + -1.9107771273e-18,\ + +# Poly EAST :: n-degree = 2 : e-degree = 2 + 3.3615590093e-10,\ + 2.4380247154e-14,\ + -2.0241230315e-18,\ + +# Poly EAST :: n-degree = 3 : e-degree = 1 + 1.2429019719e-15,\ + 5.3886155968e-19,\ + +# Poly EAST :: n-degree = 4 : e-degree = 0 + -1.0167505000e-18 + +# tcx 877605.760036 + + +# utt_n and utt_e are based on 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 +# static double utt_n[] + +inv_v=\ +# Poly NORTH :: e-degree = 0 : n-degree = 4 + 6.1258103208e+06,\ + 1.0000002826e+00,\ + -1.5372762184e-10,\ + -5.9304261011e-15,\ + -2.2612705361e-19,\ + +# Poly NORTH :: e-degree = 1 : n-degree = 3 + -4.3188331419e-05,\ + -2.8225549995e-10,\ + -7.8529116371e-16,\ + 1.7476576773e-19,\ + +# Poly NORTH :: e-degree = 2 : n-degree = 2 + -1.6875687989e-10,\ + 1.1236475299e-14,\ + 1.7042518057e-18,\ + +# Poly NORTH :: e-degree = 3 : n-degree = 1 + 7.9300735257e-15,\ + 5.2881862699e-19,\ + +# Poly NORTH :: e-degree = 4 : n-degree = 0 + -3.9990736798e-19 + +# tcy 6125811.281773 + + + + +# static double utt_e[] +inv_u=\ +# Poly EAST :: n-degree = 0 : e-degree = 0..4 + 8.7760527928e+05,\ + 1.0000024735e+00,\ + -2.8817540032e-10,\ + -5.5627059451e-15,\ + 1.5543637570e-18,\ + +# Poly EAST :: n-degree = 1 : e-degree = 0..3 + 4.1357152105e-05,\ + -4.2114813612e-11,\ + -2.8523713454e-14,\ + 1.9109017837e-18,\ + +# Poly EAST :: n-degree = 2 : e-degree = 0..2 + -3.3616407783e-10,\ + -2.4382678126e-14,\ + 2.0245020199e-18,\ + +# Poly EAST :: n-degree = 3 : e-degree = 0..1 + -1.2441377565e-15,\ + -5.3885232238e-19,\ + +# Poly EAST :: n-degree = 4 : e-degree = 0 + 1.0167203661e-18 + +# tcx 877605.760036 + +<> diff --git a/nad/testvarious b/nad/testvarious index 58770347..f11bc806 100755 --- a/nad/testvarious +++ b/nad/testvarious @@ -283,8 +283,8 @@ $EXE +proj=hammer +datum=WGS84 \ EOF echo "##############################################################" >> ${OUT} echo "Test healpix forward projection on sphere" >> ${OUT} -$EXE +proj=latlong +a=1 +lon_0=0 \ - +to +proj=healpix +a=1 +lon_0=0 -f '%.'5'f' \ +$EXE +proj=latlong +R=1 +lon_0=0 \ + +to +proj=healpix +R=1 +lon_0=0 -f '%.'5'f' \ -E >>${OUT} <<EOF 0 41.81031 -90 0 diff --git a/src/Makefile.am b/src/Makefile.am index 69e0e44e..a2508fe8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -74,7 +74,7 @@ libproj_la_SOURCES = \ jniproj.c pj_mutex.c pj_initcache.c pj_apply_vgridshift.c geodesic.c \ pj_strtod.c \ \ - pj_obs_api.c PJ_cart.c PJ_pipeline.c + pj_obs_api.c PJ_cart.c PJ_pipeline.c PJ_horner.c PJ_helmert.c install-exec-local: rm -f $(DESTDIR)$(bindir)/invproj$(EXEEXT) diff --git a/src/PJ_calcofi.c b/src/PJ_calcofi.c index b9a8ca22..0f206155 100644 --- a/src/PJ_calcofi.c +++ b/src/PJ_calcofi.c @@ -174,8 +174,8 @@ int pj_calcofi_selftest (void) { double tolerance_lp = 1e-10; double tolerance_xy = 1e-7; - char e_args[] = {"+proj=calcofi +ellps=GRS80 +lat_1=0.5 +lat_2=2"}; - char s_args[] = {"+proj=calcofi +a=6400000 +lat_1=0.5 +lat_2=2"}; + char e_args[] = {"+proj=calcofi +ellps=GRS80 +lat_1=0.5 +lat_2=2 +no_defs"}; + char s_args[] = {"+proj=calcofi +R=6400000 +lat_1=0.5 +lat_2=2 +no_defs"}; LP fwd_in[] = { { 2, 1}, diff --git a/src/PJ_cart.c b/src/PJ_cart.c index 4ec20ced..c585fbeb 100644 --- a/src/PJ_cart.c +++ b/src/PJ_cart.c @@ -88,6 +88,16 @@ PROJ_HEAD(cart, "Geodetic/cartesian conversions"); (TF, below). + Close to the poles, we avoid singularities by switching to an + approximation requiring knowledge of the geocentric radius + at the given latitude. For this, we use an adaptation of the + formula given in: + + Wikipedia: Earth Radius + https://en.wikipedia.org/wiki/Earth_radius#Radius_at_a_given_geodetic_latitude + (Derivation and commentary at http://gis.stackexchange.com/questions/20200/how-do-you-compute-the-earths-radius-at-a-given-geodetic-latitude) + + (WP2, below) These routines are probably not as robust at those in geocent.c, at least thay haven't been through as heavy @@ -103,13 +113,6 @@ static void freeup (PJ *P) { return; } - -/*********************************************************************/ -static double semiminor_axis (double a, double es) { -/*********************************************************************/ - return a * sqrt (1 - es); -} - /*********************************************************************/ static double normal_radius_of_curvature (double a, double es, double phi) { /*********************************************************************/ @@ -120,15 +123,11 @@ static double normal_radius_of_curvature (double a, double es, double phi) { return a / sqrt (1 - es*s*s); } - /*********************************************************************/ -static double second_eccentricity_squared (double a, double es) { -/********************************************************************** - Follows the definition, e'2 = (a2-b2)/b2, but uses the identity - (a2-b2) = (a-b)(a+b), for improved numerical precision. -***********************************************************************/ - double b = semiminor_axis (a, es); - return (a - b) * (a + b) / (b*b); +static double geocentric_radius (double a, double b, double phi) { +/*********************************************************************/ + /* This is from WP2, but uses hypot() for potentially better numerical robustness */ + return hypot (a*a*cos (phi), b*b*sin(phi)) / hypot (a*cos(phi), b*sin(phi)); } @@ -148,21 +147,6 @@ static XYZ cartesian (LPZ geodetic, PJ *P) { xyz.y = (N + h) * cosphi * sin(lam); xyz.z = (N * (1 - P->es) + h) * sin(phi); - /*********************************************************************/ - /* */ - /* For historical reasons, in proj, plane coordinates are measured */ - /* in units of the semimajor axis. Since 3D handling is grafted on */ - /* later, this is not the case for heights. And even though this */ - /* coordinate really is 3D cartesian, the z-part looks like a height */ - /* to proj. Hence, we have the somewhat unusual situation of having */ - /* a point coordinate with differing units between dimensions. */ - /* */ - /* The scaling and descaling is handled by the pj_fwd/inv functions. */ - /* */ - /*********************************************************************/ - xyz.x /= P->a; - xyz.y /= P->a; - return xyz; } @@ -173,15 +157,12 @@ static LPZ geodetic (XYZ cartesian, PJ *P) { double N, b, p, theta, c, s, e2s; LPZ lpz; - cartesian.x *= P->a; - cartesian.y *= P->a; - /* Perpendicular distance from point to Z-axis (HM eq. 5-28) */ p = hypot (cartesian.x, cartesian.y); /* Ancillary ellipsoidal parameters */ - b = semiminor_axis (P->a, P->es); - e2s = second_eccentricity_squared (P->a, P->es); + b = P->b; + e2s = P->e2s; /* HM eq. (5-37) */ theta = atan2 (cartesian.z * P->a, p * b); @@ -196,11 +177,12 @@ static LPZ geodetic (XYZ cartesian, PJ *P) { c = cos(lpz.phi); if (fabs(c) < 1e-6) { - /* poleward of 89.99994 deg, we avoid division by zero */ - /* by computing the height as the cartesian z value */ - /* minus the semiminor axis length */ - /* (potential improvement: approx. by 2nd order poly) */ - lpz.z = cartesian.z - (cartesian.z > 0? b: -b); + /* poleward of 89.99994 deg, we avoid division by zero */ + /* by computing the height as the cartesian z value */ + /* minus the geocentric radius of the Earth at the given */ + /* latitude */ + double r = geocentric_radius (P->a, b, lpz.phi); + lpz.z = fabs (cartesian.z) - r; } else lpz.z = p / c - N; @@ -239,6 +221,8 @@ PJ *PROJECTION(cart) { P->inv3d = geodetic; P->fwd = cart_forward; P->inv = cart_reverse; + P->left = PJ_IO_UNITS_RADIANS; + P->right = PJ_IO_UNITS_METERS; return P; } @@ -255,9 +239,6 @@ int pj_cart_selftest (void) { char *args[3] = {"proj=utm", "zone=32", "ellps=GRS80"}; - /* Log everything libproj offers to log for you */ - pj_log_level (0, PJ_LOG_TRACE); - /* An utm projection on the GRS80 ellipsoid */ P = pj_create ("+proj=utm +zone=32 +ellps=GRS80"); if (0==P) @@ -269,7 +250,7 @@ int pj_cart_selftest (void) { /* Same projection, now using argc/argv style initialization */ P = pj_create_argv (3, args); if (0==P) - return puts ("Oops"), 0; + return 2; /* Turn off logging */ pj_log_level (0, PJ_LOG_NONE); @@ -294,18 +275,18 @@ int pj_cart_selftest (void) { dist = pj_xy_dist (a.coo.xy, b.coo.xy); if (dist > 2e-9) - return 2; + return 3; /* Invalid projection */ a = pj_trans (P, 42, a); - if (a.coo.lpz.lam!=DBL_MAX) - return 3; - err = pj_error (P); - if (0==err) + if (a.coo.lpz.lam!=HUGE_VAL) return 4; + err = pj_err_level (P, PJ_ERR_TELL); + if (0==err) + return 5; /* Clear error */ - pj_error_set (P, 0); + pj_err_level (P, 0); /* Clean up */ pj_free (P); @@ -313,7 +294,7 @@ int pj_cart_selftest (void) { /* Now do some 3D transformations */ P = pj_create ("+proj=cart +ellps=GRS80"); if (0==P) - return 5; + return 6; /* zero initialize everything, then set (longitude, latitude, height) to (12, 55, 100) */ a = b = pj_obs_null; @@ -328,9 +309,32 @@ int pj_cart_selftest (void) { dist = pj_roundtrip (P, PJ_FWD, 10000, a); dist = pj_roundtrip (P, PJ_INV, 10000, b); if (dist > 2e-9) - return 6; + return 7; + + + /* Test at the North Pole */ + a = b = pj_obs_null; + a.coo.lpz.lam = TORAD(0); + a.coo.lpz.phi = TORAD(90); + a.coo.lpz.z = 100; + + /* Forward projection: Ellipsoidal-to-3D-Cartesian */ + dist = pj_roundtrip (P, PJ_FWD, 1, a); + if (dist > 1e-12) + return 8; + + /* Test at the South Pole */ + a = b = pj_obs_null; + a.coo.lpz.lam = TORAD(0); + a.coo.lpz.phi = TORAD(-90); + a.coo.lpz.z = 100; + + /* Forward projection: Ellipsoidal-to-3D-Cartesian */ + dist = pj_roundtrip (P, PJ_FWD, 1, a); + if (dist > 1e-12) + return 9; - /* Inverse projection: Ellipsoidal-to-3D-Cartesian */ + /* Inverse projection: 3D-Cartesian-to-Ellipsoidal */ b = pj_trans (P, PJ_INV, b); /* Move p to another context */ diff --git a/src/PJ_helmert.c b/src/PJ_helmert.c new file mode 100644 index 00000000..72cefe90 --- /dev/null +++ b/src/PJ_helmert.c @@ -0,0 +1,492 @@ +/*********************************************************************** + + Helmert transformations, 3- and 7-parameter + + Thomas Knudsen, 2016-05-24 + +************************************************************************ + + Implements 3- and 7-parameter Helmert transformations for 3D data. + + Primarily useful for implementation of datum shifts in transformation + pipelines. + +************************************************************************ + +Thomas Knudsen, thokn@sdfe.dk, 2016-05-24/06-05 +Last update: 2016-11-24 + +************************************************************************ +* Copyright (c) 2016, Thomas Knudsen / SDFE +* +* 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. +* +***********************************************************************/ + +#define PJ_LIB__ +#include <proj.h> +#include <projects.h> +#include <geocent.h> +#include <assert.h> +#include <stddef.h> +#include <errno.h> +PROJ_HEAD(helmert, "3- and 7-parameter Helmert shift"); + +static XYZ helmert_forward_3d (LPZ lpz, PJ *P); +static LPZ helmert_reverse_3d (XYZ xyz, PJ *P); + + + +static void *freeup_msg (PJ *P, int errlev) { /* Destructor */ + if (0==P) + return 0; + + if (0!=P->ctx) + pj_ctx_set_errno (P->ctx, errlev); + + if (0==P->opaque) + return pj_dealloc (P); + + pj_dealloc (P->opaque); + return pj_dealloc(P); +} + + +/* Adapts pipeline_freeup to the format defined for the PJ object */ +static void freeup (PJ *P) { + freeup_msg (P, 0); + return; +} + + +/***********************************************************************/ +struct pj_opaque_helmert { +/************************************************************************ + Projection specific elements for the "helmert" PJ object +************************************************************************/ + XYZ xyz; + PJ_OPK opk; + double scale; + double R[3][3]; + int no_rotation; +}; + + +/* Make the maths of the rotation operations somewhat more readable and textbook like */ +#define R00 (Q->R[0][0]) +#define R01 (Q->R[0][1]) +#define R02 (Q->R[0][2]) + +#define R10 (Q->R[1][0]) +#define R11 (Q->R[1][1]) +#define R12 (Q->R[1][2]) + +#define R20 (Q->R[2][0]) +#define R21 (Q->R[2][1]) +#define R22 (Q->R[2][2]) + + + + + + +/***********************************************************************/ +static XY helmert_forward (LP lp, PJ *P) { +/************************************************************************ + + The 2D implementation is a slightly silly stop-gap, returning a + 2D sub-space of the 3D transformation. + + In order to follow the "principle of least astonishment", the 2D + interface should provide an implementation of the 4 parameter + 2D Helmert shift. + +************************************************************************/ + PJ_TRIPLET point; + point.lp = lp; + point.lpz.z = 0; + point.xyz = helmert_forward_3d (point.lpz, P); + return point.xy; +} + + +/***********************************************************************/ +static LP helmert_reverse (XY xy, PJ *P) { +/***********************************************************************/ + PJ_TRIPLET point; + point.xy = xy; + point.xyz.z = 0; + point.lpz = helmert_reverse_3d (point.xyz, P); + return point.lp; +} + + +/***********************************************************************/ +static XYZ helmert_forward_3d (LPZ lpz, PJ *P) { +/***********************************************************************/ + struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; + PJ_TRIPLET point; + double X, Y, Z, scale; + if (Q->no_rotation) { + point.xyz.x = lpz.lam + Q->xyz.x; + point.xyz.y = lpz.phi + Q->xyz.y; + point.xyz.z = lpz.z + Q->xyz.z; + return point.xyz; + } + + scale = 1 + Q->scale * 1e-6; + + X = lpz.lam; + Y = lpz.phi; + Z = lpz.z; + + point.lpz = lpz; + + point.xyz.x = scale * ( R00 * X + R01 * Y + R02 * Z); + point.xyz.y = scale * ( R10 * X + R11 * Y + R12 * Z); + point.xyz.z = scale * ( R20 * X + R21 * Y + R22 * Z); + + point.xyz.x += Q->xyz.x; + point.xyz.y += Q->xyz.y; + point.xyz.z += Q->xyz.z; + + return point.xyz; +} + + +/***********************************************************************/ +static LPZ helmert_reverse_3d (XYZ xyz, PJ *P) { +/***********************************************************************/ + struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; + PJ_TRIPLET point; + double X, Y, Z, scale; + + point.xyz = xyz; + + if (Q->no_rotation) { + point.xyz.x = xyz.x - Q->xyz.x; + point.xyz.y = xyz.y - Q->xyz.y; + point.xyz.z = xyz.z - Q->xyz.z; + return point.lpz; + } + + scale = 1 + Q->scale * 1e-6; + + /* Unscale and deoffset */ + X = (xyz.x - Q->xyz.x) / scale; + Y = (xyz.y - Q->xyz.y) / scale; + Z = (xyz.z - Q->xyz.z) / scale; + + /* Inverse rotation through transpose multiplication */ + point.xyz.x = ( R00 * X + R10 * Y + R20 * Z); + point.xyz.y = ( R01 * X + R11 * Y + R21 * Z); + point.xyz.z = ( R02 * X + R12 * Y + R22 * Z); + + return point.lpz; +} + + +static PJ_OBS helmert_forward_obs (PJ_OBS point, PJ *P) { + point.coo.xyz = helmert_forward_3d (point.coo.lpz, P); + return point; +} + + +static PJ_OBS helmert_reverse_obs (PJ_OBS point, PJ *P) { + point.coo.lpz = helmert_reverse_3d (point.coo.xyz, P); + return point; +} + + +/* Milliarcsecond to radians */ +#define MAS_TO_RAD (DEG_TO_RAD / 3600000.0) + + +/***********************************************************************/ +PJ *PROJECTION(helmert) { +/***********************************************************************/ + XYZ xyz = {0, 0, 0}; + PJ_OPK opk = {0, 0, 0}; + double scale = 0; + int approximate = 0, transpose = 0; + + struct pj_opaque_helmert *Q = pj_calloc (1, sizeof (struct pj_opaque_helmert)); + if (0==Q) + return freeup_msg (P, ENOMEM); + P->opaque = (void *) Q; + + P->fwdobs = helmert_forward_obs; + P->invobs = helmert_reverse_obs; + P->fwdobs = 0; + P->invobs = 0; + P->fwd3d = helmert_forward_3d; + P->inv3d = helmert_reverse_3d; + P->fwd = helmert_forward; + P->inv = helmert_reverse; + + P->left = PJ_IO_UNITS_METERS; + P->right = PJ_IO_UNITS_METERS; + + /* Translations */ + if (pj_param (P->ctx, P->params, "tx").i) + xyz.x = pj_param (P->ctx, P->params, "dx").f; + + if (pj_param (P->ctx, P->params, "ty").i) + xyz.y = pj_param (P->ctx, P->params, "dy").f; + + if (pj_param (P->ctx, P->params, "tz").i) + xyz.z = pj_param (P->ctx, P->params, "dz").f; + + /* Rotations */ + if (pj_param (P->ctx, P->params, "trx").i) + opk.o = pj_param (P->ctx, P->params, "drx").f * MAS_TO_RAD; + + if (pj_param (P->ctx, P->params, "try").i) + opk.p = pj_param (P->ctx, P->params, "dry").f * MAS_TO_RAD; + + if (pj_param (P->ctx, P->params, "trz").i) + opk.k = pj_param (P->ctx, P->params, "drz").f * MAS_TO_RAD; + + + /* Scale */ + if (pj_param (P->ctx, P->params, "ts").i) + scale = pj_param (P->ctx, P->params, "ds").f; + + /* Use small angle approximations? */ + if (pj_param (P->ctx, P->params, "bapprox").i) + approximate = 1; + + /* Use "other" rotation sign convention? */ + if (pj_param (P->ctx, P->params, "ttranspose").i) + transpose = 1; + + Q->xyz = xyz; + Q->opk = opk; + Q->scale = scale; + + if ((opk.o==0) && (opk.p==0) && (opk.k==0) && (scale==0)) { + Q->no_rotation = 1; + return P; + } + + + /************************************************************************** + + Build rotation matrix. + ---------------------- + + Here we rename rotation indices from omega, phi, kappa (opk), to + fi (i.e. phi), theta, psi (ftp), in order to reduce the mental agility + needed to implement the expression for the rotation matrix derived over + at https://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions + + If the option "approximate" is set, small angle approximations are used: + The matrix elements are approximated by expanding the trigonometric + functions to linear order (i.e. cos(x) = 1, sin(x) = x), and discarding + products of second order. + + This was a useful hack when calculating by hand was the only option, + but in general, today, should be avoided because: + + 1. It does not save much computation time, as the rotation matrix + is built only once, and probably used many times. + + 2. The error induced may be too large for ultra high accuracy + applications: the Earth is huge and the linear error is + approximately the angular error multiplied by the Earth radius. + + However, in many cases the approximation is necessary, since it has + been used historically: Rotation angles from older published datum + shifts may actually be a least squares fit to the linearized rotation + approximation, hence not being strictly valid for deriving the full + rotation matrix. + + So in order to fit historically derived coordinates, the access to + the approximate rotation matrix is necessary - at least in principle. + + Also, when using any published datum transformation information, one + should always check which convention (exact or approximate rotation + matrix) is expected, and whether the induced error for selecting + the opposite convention is acceptable (which it often is). + + + Sign conventions + ---------------- + + Take care: Two different sign conventions exist. + + Conceptually they relate to whether we rotate the coordinate system + or the "position vector" (the vector going from the coordinate system + origin to the point being transformed, i.e. the point coordinates + interpreted as vector coordinates). + + Switching between the "position vector" and "coordinate system" + conventions is simply a matter of switching the sign of the rotation + angles, which algebraically also translates into a transposition of + the rotation matrix. + + Hence, as geodetic constants should preferably be referred to exactly + as published, the "transpose" option provides the ability to switch + between the conventions. + + ***************************************************************************/ + do { + double f, t, p; /* phi/fi , theta, psi */ + double cf, ct, cp; /* cos (fi, theta, psi) */ + double sf, st, sp; /* sin (fi, theta, psi) */ + + /* rename (omega, phi, kappa) to (fi, theta, psi) */ + f = opk.o; + t = opk.p; + p = opk.k; + + if (approximate) { + R00 = 1; + R01 = p; + R02 = -t; + + R10 = -p; + R11 = 1; + R12 = f; + + R20 = t; + R21 = -f; + R22 = 1; + break; + } + + cf = cos(f); + sf = sin(f); + ct = cos(t); + st = sin(t); + cp = cos(p); + sp = sin(p); + + + R00 = ct*cp; + R01 = cf*sp + sf*st*cp; + R02 = sf*sp - cf*st*cp; + + R10 = -ct*sp; + R11 = cf*cp - sf*st*sp; + R12 = sf*cp + cf*st*sp; + + R20 = st; + R21 = -sf*ct; + R22 = cf*ct; + + /* + For comparison: Description from Engsager/Poder implementation + in set_dtm_1.c (trlib) + + DATUM SHIFT: + TO = scale * ROTZ * ROTY * ROTX * FROM + TRANSLA + + ( cz sz 0) (cy 0 -sy) (1 0 0) + ROTZ=(-sz cz 0), ROTY=(0 1 0), ROTX=(0 cx sx) + ( 0 0 1) (sy 0 cy) (0 -sx cx) + + trp->r11 = cos_ry*cos_rz; + trp->r12 = cos_rx*sin_rz + sin_rx*sin_ry*cos_rz; + trp->r13 = sin_rx*sin_rz - cos_rx*sin_ry*cos_rz; + + trp->r21 = -cos_ry*sin_rz; + trp->r22 = cos_rx*cos_rz - sin_rx*sin_ry*sin_rz; + trp->r23 = sin_rx*cos_rz + cos_rx*sin_ry*sin_rz; + + trp->r31 = sin_ry; + trp->r32 = -sin_rx*cos_ry; + trp->r33 = cos_rx*cos_ry; + + trp->scale = 1.0 + scale; + */ + + } while (0); + + + if (transpose) { + double r; + r = R01; R01 = R10; R10 = r; + r = R02; R02 = R20; R20 = r; + r = R12; R12 = R21; R21 = r; + } + + return P; +} + + + + +#ifndef PJ_SELFTEST + +int pj_helmert_selftest (void) {return 0;} /* self test stub */ + +#else + + +static int test (char *args, PJ_TRIPLET in, PJ_TRIPLET expect) { + PJ_TRIPLET out; + PJ *P = pj_init_plus (args); + + if (0==P) + return 5; + + out.xyz = pj_fwd3d (in.lpz, P); + if (pj_xyz_dist (out.xyz, expect.xyz) > 1e-4) + return 1; + + out.lpz = pj_inv3d (out.xyz, P); + if (pj_xyz_dist (out.xyz, in.xyz) > 1e-4) + return 2; + + return 0; +} + + + +int pj_helmert_selftest (void) { + + /* This example is from + Lotti Jivall: + Simplified transformations from ITRF2008/IGS08 to ETRS89 for maritime applications */ + PJ_TRIPLET in1 = {{3565285.0000, 855949.0000, 5201383.0000}}; + PJ_TRIPLET expect1 = {{3565285.41342351, 855948.67986759, 5201382.72939791}}; + char args1[] = { + " +proj=helmert +ellps=GRS80" + " +x=0.67678 +y=0.65495 +z=-0.52827" + " +rx=-22.742 +ry=12.667 +rz=22.704 +s=-0.01070" + }; + + + /* This example is a random point, transformed from ED50 to ETRS89 using KMStrans2 */ + PJ_TRIPLET in2 = {{3494994.3012, 1056601.9725, 5212382.1666}}; + PJ_TRIPLET expect2 = {{3494909.84026368, 1056506.78938633, 5212265.66699761}}; + char args2[] = { + " +proj=helmert +ellps=GRS80" + " +x=-81.0703 +y=-89.3603 +z=-115.7526" + " +rx=-484.88 +ry=-24.36 +rz=-413.21 +s=-0.540645" + }; + int ret; + + ret = test (args1, in1, expect1); if (ret) return ret; + ret = test (args2, in2, expect2); if (ret) return ret + 10; + return 0; +} + +#endif 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 diff --git a/src/PJ_krovak.c b/src/PJ_krovak.c index 136978a0..c469346f 100644 --- a/src/PJ_krovak.c +++ b/src/PJ_krovak.c @@ -239,7 +239,7 @@ int pj_krovak_selftest (void) { double tolerance_lp = 1e-10; double tolerance_xy = 1e-7; - char e_args[] = {"+proj=krovak +ellps=GRS80"}; + char e_args[] = {"+proj=krovak +ellps=GRS80 +no_defs"}; LP fwd_in[] = { { 2, 1}, diff --git a/src/PJ_pipeline.c b/src/PJ_pipeline.c index ef86ccd3..27fe6ec9 100644 --- a/src/PJ_pipeline.c +++ b/src/PJ_pipeline.c @@ -114,6 +114,8 @@ struct pj_opaque { int *reverse_step; int *omit_forward; int *omit_inverse; + char **argv; + char **current_argv; PJ_OBS stack[PIPELINE_STACK_SIZE]; PJ **pipeline; }; @@ -175,13 +177,17 @@ static LP pipeline_reverse (XY xyz, PJ *P); static PJ_OBS pipeline_forward_obs (PJ_OBS point, PJ *P) { - int i, first_step, last_step, incr; + int i, first_step, last_step; first_step = 1; last_step = P->opaque->steps + 1; - incr = 1; - for (i = first_step; i != last_step; i += incr) { + for (i = first_step; i != last_step; i++) { + pj_log_trace (P, "In[%2.2d]: %20.15g %20.15g %20.15g - %20.20s", + i-first_step, point.coo.xyz.x, point.coo.xyz.y, point.coo.xyz.z, + P->opaque->pipeline[i]->descr + ); + if (P->opaque->omit_forward[i]) continue; if (P->opaque->reverse_step[i]) @@ -191,19 +197,23 @@ static PJ_OBS pipeline_forward_obs (PJ_OBS point, PJ *P) { if (P->opaque->depth < PIPELINE_STACK_SIZE) P->opaque->stack[P->opaque->depth++] = point; } + pj_log_trace (P, "Out[ ]: %20.15g %20.15g %20.15g", point.coo.xyz.x, point.coo.xyz.y, point.coo.xyz.z); P->opaque->depth = 0; /* Clear the stack */ return point; } + static PJ_OBS pipeline_reverse_obs (PJ_OBS point, PJ *P) { - int i, first_step, last_step, incr; + int i, first_step, last_step; first_step = P->opaque->steps; last_step = 0; - incr = -1; - - for (i = first_step; i != last_step; i += incr) { + for (i = first_step; i != last_step; i--) { + pj_log_trace (P, "In[%2.2d]: %20.15g %20.15g %20.15g - %.4f %.4f", + i - 1, point.coo.xyz.x, point.coo.xyz.y, point.coo.xyz.z, + P->opaque->pipeline[i]->a, P->opaque->pipeline[i]->rf + ); if (P->opaque->omit_inverse[i]) continue; if (P->opaque->reverse_step[i]) @@ -213,6 +223,7 @@ static PJ_OBS pipeline_reverse_obs (PJ_OBS point, PJ *P) { if (P->opaque->depth < PIPELINE_STACK_SIZE) P->opaque->stack[P->opaque->depth++] = point; } + pj_log_trace (P, "Out[ ]: %20.15g %20.15g %20.15g", point.coo.xyz.x, point.coo.xyz.y, point.coo.xyz.z); P->opaque->depth = 0; /* Clear the stack */ return point; @@ -264,7 +275,7 @@ static void *pipeline_freeup (PJ *P, int errlev) { /* Destructor */ if (0==P) return 0; - pj_error_set (P, errlev); + pj_err_level (P, errlev); if (0==P->opaque) return pj_dealloc (P); @@ -275,6 +286,8 @@ static void *pipeline_freeup (PJ *P, int errlev) { /* Destructor */ pj_dealloc (P->opaque->reverse_step); pj_dealloc (P->opaque->omit_forward); pj_dealloc (P->opaque->omit_inverse); + pj_dealloc (P->opaque->argv); + pj_dealloc (P->opaque->current_argv); pj_dealloc (P->opaque->pipeline); pj_dealloc (P->opaque); @@ -313,8 +326,9 @@ static PJ *pj_create_pipeline (PJ *P, size_t steps) { return P; } + /* count the number of args in pipeline definition */ -size_t argc_params (paralist *params) { +static size_t argc_params (paralist *params) { size_t argc = 0; for (; params != 0; params = params->next) argc++; @@ -322,18 +336,18 @@ size_t argc_params (paralist *params) { } /* Sentinel for argument list */ -static char argv_sentinel[5] = "step"; +static char argv_sentinel[] = "step"; -/* turn paralist int argc/argv style argument list */ -char **argv_params (paralist *params) { +/* turn paralist into argc/argv style argument list */ +static char **argv_params (paralist *params, size_t argc) { char **argv; - size_t argc = 0; - argv = pj_calloc (argc_params (params), sizeof (char *)); + size_t i = 0; + argv = pj_calloc (argc, sizeof (char *)); if (0==argv) return 0; for (; params != 0; params = params->next) - argv[argc++] = params->param; - argv[argc++] = argv_sentinel; + argv[i++] = params->param; + argv[i++] = argv_sentinel; return argv; } @@ -356,17 +370,15 @@ PJ *PROJECTION(pipeline) { return 0; argc = argc_params (P->params); - argv = argv_params (P->params); + P->opaque->argv = argv = argv_params (P->params, argc); if (0==argv) return pipeline_freeup (P, ENOMEM); - /* The elements of current_argv are not used - we just use argv_params */ - /* as allocator for a "large enough" container needed later */ - current_argv = argv_params (P->params); + P->opaque->current_argv = current_argv = pj_calloc (argc, sizeof (char *)); if (0==current_argv) return pipeline_freeup (P, ENOMEM); - /* Do some syntactic sanity checking */ + /* Do some syntactical sanity checking */ for (i = 0; i < argc; i++) { if (0==strcmp ("step", argv[i])) { if (-1==i_pipeline) { diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake index 9e1ac4f7..2fcc4e31 100644 --- a/src/lib_proj.cmake +++ b/src/lib_proj.cmake @@ -77,6 +77,8 @@ SET(SRC_LIBPROJ_PJ PJ_gstmerc.c PJ_hammer.c PJ_hatano.c + PJ_helmert.c + PJ_horner.c PJ_igh.c PJ_isea.c PJ_imw_p.c diff --git a/src/makefile.vc b/src/makefile.vc index a2869217..e1eacf04 100644 --- a/src/makefile.vc +++ b/src/makefile.vc @@ -59,7 +59,7 @@ support = \ pj_strtod.obj pj_run_selftests.obj pj_generic_selftest.obj pipeline = \ - pj_obs_api.obj PJ_cart.obj PJ_pipeline.obj + pj_obs_api.obj PJ_cart.obj PJ_pipeline.obj PJ_horner.obj PJ_helmert.obj geodesic = geodesic.obj @@ -147,6 +147,7 @@ install: all copy *.exe $(INSTDIR)\bin copy *.dll $(INSTDIR)\bin copy *.lib $(INSTDIR)\lib + copy proj.h $(INSTDIR)\include copy proj_api.h $(INSTDIR)\include copy projects.h $(INSTDIR)\include copy geodesic.h $(INSTDIR)\include diff --git a/src/pj_fwd.c b/src/pj_fwd.c index 469e9a47..598c6dd2 100644 --- a/src/pj_fwd.c +++ b/src/pj_fwd.c @@ -6,41 +6,52 @@ XY /* forward projection entry */ pj_fwd(LP lp, PJ *P) { XY xy; - double t; - - /* check for forward and latitude or longitude overange */ - if ((t = fabs(lp.phi)-M_HALFPI) > EPS || fabs(lp.lam) > 10.) { - xy.x = xy.y = HUGE_VAL; - pj_ctx_set_errno( P->ctx, -14); - } else { /* proceed with projection */ - P->ctx->last_errno = 0; - pj_errno = 0; - errno = 0; - - if (fabs(t) <= EPS) - lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; - else if (P->geoc) - lp.phi = atan(P->rone_es * tan(lp.phi)); - lp.lam -= P->lam0; /* compute del lp.lam */ - if (!P->over) - lp.lam = adjlon(lp.lam); /* adjust del longitude */ - - /* Check for NULL pointer */ - if (P->fwd != NULL) - { - xy = (*P->fwd)(lp, P); /* project */ - if ( P->ctx->last_errno ) - xy.x = xy.y = HUGE_VAL; - /* adjust for major axis and easting/northings */ - else { - xy.x = P->fr_meter * (P->a * xy.x + P->x0); - xy.y = P->fr_meter * (P->a * xy.y + P->y0); - } - } - else - { - xy.x = xy.y = HUGE_VAL; - } - } - return xy; + XY err; + double t; + + /* cannot const-initialize this due to MSVC's broken (non const) HUGE_VAL */ + err.x = err.y = HUGE_VAL; + + if (0==P->fwd) + return err; + + P->ctx->last_errno = pj_errno = errno = 0; + + /* Check input coordinates if angular */ + if ((P->left==PJ_IO_UNITS_CLASSIC)||(P->left==PJ_IO_UNITS_RADIANS)) { + + /* check for forward and latitude or longitude overange */ + t = fabs(lp.phi)-M_HALFPI; + if (t > EPS || fabs(lp.lam) > 10.) { + pj_ctx_set_errno( P->ctx, -14); + return err; + } + + /* Clamp latitude to -90..90 degree range */ + if (fabs(t) <= EPS) + lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; + else if (P->geoc) /* Maybe redundant and never used. */ + lp.phi = atan(P->rone_es * tan(lp.phi)); + lp.lam -= P->lam0; /* compute del lp.lam */ + if (!P->over) + lp.lam = adjlon(lp.lam); /* adjust del longitude */ + } + + /* Do the transformation */ + xy = (*P->fwd)(lp, P); + if ( P->ctx->last_errno ) + return err; + + /* Classic proj.4 functions return plane coordinates in units of the semimajor axis */ + if (P->right==PJ_IO_UNITS_CLASSIC) { + xy.x *= P->a; + xy.y *= P->a; + } + + /* Handle false eastings/northings and non-metric linear units */ + xy.x = P->fr_meter * (xy.x + P->x0); + xy.y = P->fr_meter * (xy.y + P->y0); + /* z is not scaled since this is handled by vto_meter outside */ + + return xy; } diff --git a/src/pj_fwd3d.c b/src/pj_fwd3d.c index 63fc529e..0fb2f633 100644 --- a/src/pj_fwd3d.c +++ b/src/pj_fwd3d.c @@ -1,47 +1,59 @@ -/* general forward projection */ #define PJ_LIB__ #include <projects.h> #include <errno.h> # define EPS 1.0e-12 - XYZ /* forward projection entry */ -pj_fwd3d(LPZ lpz, PJ *P) { - XYZ xyz; - double t; - - /* check for forward and latitude or longitude overange */ - if ((t = fabs(lpz.phi)-M_HALFPI) > EPS || fabs(lpz.lam) > 10.) { - xyz.x = xyz.y = xyz.z = HUGE_VAL; - pj_ctx_set_errno( P->ctx, -14); - } else { /* proceed with projection */ - P->ctx->last_errno = 0; - pj_errno = 0; - errno = 0; - - if (fabs(t) <= EPS) - lpz.phi = lpz.phi < 0. ? -M_HALFPI : M_HALFPI; - else if (P->geoc) /* Maybe redundant and never used. */ - lpz.phi = atan(P->rone_es * tan(lpz.phi)); - lpz.lam -= P->lam0; /* compute del lp.lam */ - if (!P->over) - lpz.lam = adjlon(lpz.lam); /* adjust del longitude */ - - /* Check for NULL pointer */ - if (P->fwd3d != NULL) - { - xyz = (*P->fwd3d)(lpz, P); /* project */ - if ( P->ctx->last_errno ) - xyz.x = xyz.y = xyz.z = HUGE_VAL; - /* adjust for major axis and easting/northings */ - else { - xyz.x = P->fr_meter * (P->a * xyz.x + P->x0); - xyz.y = P->fr_meter * (P->a * xyz.y + P->y0); - /* z is not scaled since this handled by vto_meter outside */ - } - } - else - { - xyz.x = xyz.y = xyz.z = HUGE_VAL; - } - } - return xyz; + +/* 3D Forward transformation */ + +XYZ pj_fwd3d(LPZ lpz, PJ *P) { + XYZ xyz; + XYZ err; + double t; + + /* cannot const-initialize this due to MSVC's broken (non const) HUGE_VAL */ + err.x = err.y = err.z = HUGE_VAL; + + if (0==P->fwd3d) + return err; + + + P->ctx->last_errno = pj_errno = errno = 0; + + /* Check input coordinates if angular */ + if ((P->left==PJ_IO_UNITS_CLASSIC)||(P->left==PJ_IO_UNITS_RADIANS)) { + + /* check for forward and latitude or longitude overange */ + t = fabs(lpz.phi)-M_HALFPI; + if (t > EPS || fabs(lpz.lam) > 10.) { + pj_ctx_set_errno( P->ctx, -14); + return err; + } + + /* Clamp latitude to -90..90 degree range */ + if (fabs(t) <= EPS) + lpz.phi = lpz.phi < 0. ? -M_HALFPI : M_HALFPI; + else if (P->geoc) /* Maybe redundant and never used. */ + lpz.phi = atan(P->rone_es * tan(lpz.phi)); + lpz.lam -= P->lam0; /* compute del lp.lam */ + if (!P->over) + lpz.lam = adjlon(lpz.lam); /* adjust del longitude */ + } + + /* Do the transformation */ + xyz = (*P->fwd3d)(lpz, P); + if ( P->ctx->last_errno ) + return err; + + /* Classic proj.4 functions return plane coordinates in units of the semimajor axis */ + if (P->right==PJ_IO_UNITS_CLASSIC) { + xyz.x *= P->a; + xyz.y *= P->a; + } + + /* Handle false eastings/northings and non-metric linear units */ + xyz.x = P->fr_meter * (xyz.x + P->x0); + xyz.y = P->fr_meter * (xyz.y + P->y0); + /* z is not scaled since this is handled by vto_meter outside */ + + return xyz; } diff --git a/src/pj_init.c b/src/pj_init.c index c468f1d7..b6be52d9 100644 --- a/src/pj_init.c +++ b/src/pj_init.c @@ -35,10 +35,12 @@ #include <errno.h> #include <ctype.h> +/* Maximum size of files using the "escape carriage return" feature */ +#define MAX_CR_ESCAPE 65537 typedef struct { projCtx ctx; PAFile fid; - char buffer[8193]; + char buffer[MAX_CR_ESCAPE]; int buffer_filled; int at_eof; } pj_read_state; @@ -51,6 +53,7 @@ static const char *fill_buffer(pj_read_state *state, const char *last_char) { size_t bytes_read; size_t char_remaining, char_requested; + char *r, *w; /* -------------------------------------------------------------------- */ /* Don't bother trying to read more if we are at eof, or if the */ @@ -85,7 +88,36 @@ static const char *fill_buffer(pj_read_state *state, const char *last_char) state->buffer[state->buffer_filled + bytes_read] = '\0'; } - state->buffer_filled += bytes_read; +/* -------------------------------------------------------------------- */ +/* Line continuations: skip whitespace after escaped newlines */ +/* -------------------------------------------------------------------- */ + r = state->buffer; + w = state->buffer; + while (*r) { + /* Escaped newline? */ + while ((r[0]=='\\') && ((r[1]=='\n') || (r[1]=='\r'))) { + r += 2; + while (isspace (*r)) + r++; + /* we also skip comments immediately after an escaped newline */ + while (*r=='#') { + while( *r && (*r != '\n') ) + r++; + while (isspace (*r)) + r++; + /* Reaching end of buffer while skipping continuation comment is currently an error */ + if (0==*r) { + pj_ctx_set_errno (state->ctx, -2); + pj_log (state->ctx, PJ_LOG_ERROR, "init file too big"); + return 0; + } + } + } + *w++ = *r++; + } + *w = 0; + state->buffer_filled += (bytes_read - (r-w)); + return last_char; } @@ -96,11 +128,11 @@ static paralist * get_opt(projCtx ctx, paralist **start, PAFile fid, char *name, paralist *next, int *found_def) { pj_read_state *state = (pj_read_state*) calloc(1,sizeof(pj_read_state)); - char sword[302]; + char sword[MAX_CR_ESCAPE]; + char *pipeline; int len; int in_target = 0; const char *next_char = NULL; - state->fid = fid; state->ctx = ctx; next_char = fill_buffer(state, NULL); @@ -110,6 +142,9 @@ get_opt(projCtx ctx, paralist **start, PAFile fid, char *name, paralist *next, len = strlen(name); *sword = 't'; + if (0==next_char) + return 0; + /* loop till we find our target keyword */ while (*next_char) { @@ -120,6 +155,8 @@ get_opt(projCtx ctx, paralist **start, PAFile fid, char *name, paralist *next, next_char++; next_char = fill_buffer(state, next_char); + if (0==next_char) + return 0; /* for comments, skip past end of line. */ if( *next_char == '#' ) @@ -128,6 +165,8 @@ get_opt(projCtx ctx, paralist **start, PAFile fid, char *name, paralist *next, next_char++; next_char = fill_buffer(state, next_char); + if (0==next_char) + return 0; if (*next_char == '\n') next_char++; if (*next_char == '\r') @@ -171,7 +210,7 @@ get_opt(projCtx ctx, paralist **start, PAFile fid, char *name, paralist *next, } /* capture parameter */ - while( *next_char && !isspace(*next_char) ) + while ( *next_char && !isspace(*next_char) ) { next_char++; word_len++; @@ -180,8 +219,11 @@ get_opt(projCtx ctx, paralist **start, PAFile fid, char *name, paralist *next, strncpy(sword+1, start_of_word, word_len); sword[word_len+1] = '\0'; - /* do not override existing parameter value of same name */ - if (!pj_param(ctx, *start, sword).i) { + /* do not override existing parameter value of same name - unless in pipeline definition */ + pipeline = pj_param(ctx, *start, "sproj").s; + if (pipeline && strcmp (pipeline, "pipeline")) + pipeline = 0; + if (pipeline || !pj_param(ctx, *start, sword).i) { /* don't default ellipse if datum, ellps or any earth model information is set. */ if( strncmp(sword+1,"ellps=",6) != 0 @@ -194,8 +236,9 @@ get_opt(projCtx ctx, paralist **start, PAFile fid, char *name, paralist *next, { next = next->next = pj_mkparam(sword+1); } + else + next = next->next = pj_mkparam(sword+1); } - } else { @@ -388,6 +431,7 @@ pj_init_ctx(projCtx ctx, int argc, char **argv) { PJ *(*proj)(PJ *); paralist *curr; int i; + int defer_init_expansion = 0; PJ *PIN = 0; ctx->last_errno = 0; @@ -403,9 +447,10 @@ pj_init_ctx(projCtx ctx, int argc, char **argv) { if (ctx->last_errno) goto bum_call; /* check if +init present */ - if (pj_param(ctx, start, "tinit").i) { + if (pj_param(ctx, start, "tinit").i && ! defer_init_expansion) { int found_def = 0; - + /* avoid expanding additional inits (as could happen in a pipeline) */ + defer_init_expansion = 1; if (!(curr = get_init(ctx,&start, curr, pj_param(ctx, start, "sinit").s, &found_def))) @@ -444,7 +489,10 @@ pj_init_ctx(projCtx ctx, int argc, char **argv) { if (pj_datum_set(ctx, start, PIN)) goto bum_call; /* set ellipsoid/sphere parameters */ - if (pj_ell_set(ctx, start, &PIN->a, &PIN->es)) goto bum_call; + if (pj_ell_set(ctx, start, &PIN->a, &PIN->es)) { + pj_log (ctx, PJ_LOG_DEBUG_MINOR, "pj_init_ctx: Must specify ellipsoid or sphere"); + goto bum_call; + } PIN->a_orig = PIN->a; PIN->es_orig = PIN->es; @@ -464,11 +512,11 @@ pj_init_ctx(projCtx ctx, int argc, char **argv) { /* flattening */ PIN->f = 1 - cos (PIN->alpha); /* = 1 - sqrt (1 - PIN->es); */ - PIN->rf = PIN->f? 1/PIN->f: HUGE_VAL; + PIN->rf = PIN->f? 1.0/PIN->f: HUGE_VAL; /* second flattening */ - PIN->f2 = 1/cos (PIN->alpha) - 1; - PIN->rf = PIN->f2? 1/PIN->f2: HUGE_VAL; + PIN->f2 = 1/cos (PIN->alpha) - 1; + PIN->rf2 = PIN->f2? 1/PIN->f2: HUGE_VAL; /* third flattening */ PIN->n = pow (tan (PIN->alpha/2), 2); diff --git a/src/pj_inv.c b/src/pj_inv.c index ed05618a..d5393261 100644 --- a/src/pj_inv.c +++ b/src/pj_inv.c @@ -7,35 +7,49 @@ /* inverse projection entry */ LP pj_inv(XY xy, PJ *P) { LP lp; + LP err; + + /* cannot const-initialize this due to MSVC's broken (non const) HUGE_VAL */ + err.lam = err.phi = HUGE_VAL; + + if (0==P->inv) + return err; /* can't do as much preliminary checking as with forward */ if (xy.x == HUGE_VAL || xy.y == HUGE_VAL) { - lp.lam = lp.phi = HUGE_VAL; pj_ctx_set_errno( P->ctx, -15); - return lp; + return err; + } + P->ctx->last_errno = errno = pj_errno = 0; + + /* de-scale and de-offset */ + xy.x = (xy.x * P->to_meter - P->x0); + xy.y = (xy.y * P->to_meter - P->y0); + + /* Classic proj.4 functions expect plane coordinates in units of the semimajor axis */ + /* Multiplying by ra, rather than dividing by a because the CALCOFI projection */ + /* stomps on a and hence depends on this */ + if (P->right==PJ_IO_UNITS_CLASSIC) { + xy.x *= P->ra; + xy.y *= P->ra; } - errno = pj_errno = 0; - P->ctx->last_errno = 0; - - /* descale and de-offset */ - xy.x = (xy.x * P->to_meter - P->x0) * P->ra; - xy.y = (xy.y * P->to_meter - P->y0) * P->ra; - - /* Check for NULL pointer */ - if (P->inv != NULL) { - lp = (*P->inv)(xy, P); /* inverse project */ - if (P->ctx->last_errno ) { - lp.lam = lp.phi = HUGE_VAL; - } else { - lp.lam += P->lam0; /* reduce from del lp.lam */ - if (!P->over) - lp.lam = adjlon(lp.lam); /* adjust longitude to CM */ - if (P->geoc && fabs(fabs(lp.phi)-M_HALFPI) > EPS) - lp.phi = atan(P->one_es * tan(lp.phi)); - } - } else { - lp.lam = lp.phi = HUGE_VAL; + /* Do inverse transformation */ + lp = (*P->inv) (xy, P); + if (P->ctx->last_errno) + return err; + + if ((P->left==PJ_IO_UNITS_CLASSIC)||(P->left==PJ_IO_UNITS_RADIANS)) { + /* reduce from del lp.lam */ + lp.lam += P->lam0; + + /* adjust longitude to central meridian */ + if (!P->over) + lp.lam = adjlon(lp.lam); + + /* This may be redundant and never used */ + if (P->geoc && fabs(fabs(lp.phi)-M_HALFPI) > EPS) + lp.phi = atan(P->one_es * tan(lp.phi)); } return lp; diff --git a/src/pj_inv3d.c b/src/pj_inv3d.c index bf6a9324..c6052ad4 100644 --- a/src/pj_inv3d.c +++ b/src/pj_inv3d.c @@ -1,45 +1,57 @@ -/* general inverse projection */ #define PJ_LIB__ #include <projects.h> #include <errno.h> # define EPS 1.0e-12 - LPZ /* inverse projection entry */ -pj_inv3d(XYZ xyz, PJ *P) { - LPZ lpz; - - /* can't do as much preliminary checking as with forward */ - if (xyz.x == HUGE_VAL || xyz.y == HUGE_VAL || xyz.z == HUGE_VAL ) { - lpz.lam = lpz.phi = lpz.z = HUGE_VAL; - pj_ctx_set_errno( P->ctx, -15); - return lpz; - } - - errno = pj_errno = 0; - P->ctx->last_errno = 0; - - xyz.x = (xyz.x * P->to_meter - P->x0) * P->ra; /* descale and de-offset */ - xyz.y = (xyz.y * P->to_meter - P->y0) * P->ra; - /* z is not scaled since that is handled by vto_meter before we get here */ - - /* Check for NULL pointer */ - if (P->inv3d != NULL) - { - lpz = (*P->inv3d)(xyz, P); /* inverse project */ - if (P->ctx->last_errno ) - lpz.lam = lpz.phi = lpz.z = HUGE_VAL; - else { - lpz.lam += P->lam0; /* reduce from del lp.lam */ - if (!P->over) - lpz.lam = adjlon(lpz.lam); /* adjust longitude to CM */ - - /* This may be redundant and never used */ - if (P->geoc && fabs(fabs(lpz.phi)-M_HALFPI) > EPS) - lpz.phi = atan(P->one_es * tan(lpz.phi)); - } - } - else - { - lpz.lam = lpz.phi = lpz.z = HUGE_VAL; - } - return lpz; + +/* 3D inverse transformation */ + +LPZ pj_inv3d (XYZ xyz, PJ *P) { + LPZ lpz; + LPZ err; + + /* cannot const-initialize this due to MSVC's broken (non const) HUGE_VAL */ + err.lam = err.phi = err.z = HUGE_VAL; + + if (0==P->inv3d) + return err; + + /* can't do as much preliminary checking as with forward */ + if (xyz.x == HUGE_VAL || xyz.y == HUGE_VAL || xyz.z == HUGE_VAL ) { + pj_ctx_set_errno( P->ctx, -15); + return err; + } + + P->ctx->last_errno = errno = pj_errno = 0; + + /* de-scale and de-offset */ + /* z is not de-scaled since that is handled by vto_meter before we get here */ + xyz.x = (xyz.x * P->to_meter - P->x0); + xyz.y = (xyz.y * P->to_meter - P->y0); + /* Classic proj.4 functions expect plane coordinates in units of the semimajor axis */ + /* Multiplying by ra, rather than dividing by a because the CALCOFI projection */ + /* stomps on a and hence depends on this */ + if (P->right==PJ_IO_UNITS_CLASSIC) { + xyz.x *= P->ra; + xyz.y *= P->ra; + } + + /* Do inverse transformation */ + lpz = (*P->inv3d) (xyz, P); + if (P->ctx->last_errno) + return err; + + if ((P->left==PJ_IO_UNITS_CLASSIC)||(P->left==PJ_IO_UNITS_RADIANS)) { + /* reduce from del lp.lam */ + lpz.lam += P->lam0; + + /* adjust longitude to central meridian */ + if (!P->over) + lpz.lam = adjlon(lpz.lam); + + /* This may be redundant and never used */ + if (P->geoc && fabs(fabs(lpz.phi)-M_HALFPI) > EPS) + lpz.phi = atan(P->one_es * tan(lpz.phi)); + } + + return lpz; } diff --git a/src/pj_list.h b/src/pj_list.h index f49fd574..96db13d7 100644 --- a/src/pj_list.h +++ b/src/pj_list.h @@ -52,6 +52,8 @@ PROJ_HEAD(hammer, "Hammer & Eckert-Greifendorff") PROJ_HEAD(hatano, "Hatano Asymmetrical Equal Area") PROJ_HEAD(healpix, "HEALPix") PROJ_HEAD(rhealpix, "rHEALPix") +PROJ_HEAD(helmert, "3- and 7-parameter Helmert shift") +PROJ_HEAD(horner, "Horner polynomial evaluation") PROJ_HEAD(igh, "Interrupted Goode Homolosine") PROJ_HEAD(imw_p, "International Map of the World Polyconic") PROJ_HEAD(isea, "Icosahedral Snyder Equal Area") diff --git a/src/pj_obs_api.c b/src/pj_obs_api.c index 716c6095..4a6e98a5 100644 --- a/src/pj_obs_api.c +++ b/src/pj_obs_api.c @@ -32,7 +32,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ -#define PJ_OBS_C +#define PJ_OBS_API_C #include <proj.h> #include <projects.h> #include <geodesic.h> @@ -40,16 +40,9 @@ #include <math.h> -/* Used as return value in case of errors */ -const PJ_OBS pj_obs_error = { - /* Cannot use HUGE_VAL here: MSVC misimplements HUGE_VAL as something that is not compile time constant */ - {{DBL_MAX,DBL_MAX,DBL_MAX,DBL_MAX}}, - {{DBL_MAX,DBL_MAX,DBL_MAX}}, - 0, 0 -}; - /* Used for zero-initializing new objects */ -const PJ_OBS pj_obs_null = { +const PJ_COORD pj_coo_null = {{0, 0, 0, 0}}; +const PJ_OBS pj_obs_null = { {{0, 0, 0, 0}}, {{0, 0, 0}}, 0, 0 @@ -77,6 +70,23 @@ double pj_xyz_dist (XYZ a, XYZ b) { } +/* Work around non-constness of MSVC HUGE_VAL by providing functions rather than constants */ +PJ_COORD pj_coo_error (void) { + PJ_COORD c; + c.v[0] = c.v[1] = c.v[2] = c.v[3] = HUGE_VAL; + return c; +} + +PJ_OBS pj_obs_error (void) { + PJ_OBS obs; + obs.coo = pj_coo_error (); + obs.anc.v[0] = obs.anc.v[1] = obs.anc.v[2] = HUGE_VAL; + obs.id = obs.flags = 0; + return obs; +} + + + PJ_OBS pj_fwdobs (PJ_OBS obs, PJ *P) { if (0!=P->fwdobs) { obs = P->fwdobs (obs, P); @@ -90,8 +100,8 @@ PJ_OBS pj_fwdobs (PJ_OBS obs, PJ *P) { obs.coo.xy = pj_fwd (obs.coo.lp, P); return obs; } - pj_error_set (P, EINVAL); - return pj_obs_error; + pj_err_level (P, EINVAL); + return pj_obs_error (); } @@ -108,8 +118,8 @@ PJ_OBS pj_invobs (PJ_OBS obs, PJ *P) { obs.coo.lp = pj_inv (obs.coo.xy, P); return obs; } - pj_error_set (P, EINVAL); - return pj_obs_error; + pj_err_level (P, EINVAL); + return pj_obs_error (); } @@ -129,8 +139,8 @@ PJ_OBS pj_trans (PJ *P, enum pj_direction direction, PJ_OBS obs) { break; } - pj_error_set (P, EINVAL); - return pj_obs_error; + pj_err_level (P, EINVAL); + return pj_obs_error (); } @@ -143,7 +153,7 @@ double pj_roundtrip (PJ *P, enum pj_direction direction, int n, PJ_OBS obs) { return HUGE_VAL; if (n < 1) { - pj_error_set (P, EINVAL); + pj_err_level (P, EINVAL); return HUGE_VAL; } @@ -152,15 +162,15 @@ double pj_roundtrip (PJ *P, enum pj_direction direction, int n, PJ_OBS obs) { for (i = 0; i < n; i++) { switch (direction) { case PJ_FWD: - u.coo.xyz = pj_fwd3d (o.coo.lpz, P); - o.coo.lpz = pj_inv3d (u.coo.xyz, P); + u = pj_fwdobs (o, P); + o = pj_invobs (u, P); break; case PJ_INV: - u.coo.lpz = pj_inv3d (o.coo.xyz, P); - o.coo.xyz = pj_fwd3d (u.coo.lpz, P); + u = pj_invobs (o, P); + o = pj_fwdobs (u, P); break; default: - pj_error_set (P, EINVAL); + pj_err_level (P, EINVAL); return HUGE_VAL; } } @@ -179,18 +189,24 @@ PJ *pj_create_argv (int argc, char **argv) { } -/* From here: Minimum viable support for contexts. The first four functions */ +/* Below: Minimum viable support for contexts. The first four functions */ /* relate to error reporting, debugging, and logging, hence being generically */ /* useful. The remaining is a compact implementation of the more low level */ /* proj_api.h thread contexts, which may or may not be useful */ -int pj_error (PJ *P) { - return pj_ctx_get_errno (pj_get_ctx(P)); -} - - -void pj_error_set (PJ *P, int err) { - pj_ctx_set_errno (pj_get_ctx(P), err); +/* Set error level 0-3, or query current level */ +int pj_err_level (PJ *P, int err_level) { + int previous; + PJ_CONTEXT *ctx; + if (0==P) + ctx = pj_get_default_ctx(); + else + ctx = pj_get_ctx (P); + previous = pj_ctx_get_errno (ctx); + if (PJ_ERR_TELL==err_level) + return previous; + pj_ctx_set_errno (ctx, err_level); + return previous; } @@ -211,7 +227,7 @@ enum pj_log_level pj_log_level (PJ *P, enum pj_log_level log_level) { /* Put a new logging function into P's context. The opaque object app_data is passed as first arg at each call to the logger */ -void pj_log_set (PJ *P, void *app_data, void (*log)(void *, int, const char *)) { +void pj_log_func (PJ *P, void *app_data, void (*log)(void *, int, const char *)) { PJ_CONTEXT *ctx = pj_get_ctx (P); ctx->app_data = app_data; if (0!=log) @@ -223,7 +239,7 @@ void pj_log_set (PJ *P, void *app_data, void (*log)(void *, int, const char *)) int pj_context_renew (PJ *P) { PJ_CONTEXT *ctx = pj_ctx_alloc (); if (0==ctx) { - pj_error_set (P, ENOMEM); + pj_err_level (P, ENOMEM); return 1; } @@ -231,6 +247,7 @@ int pj_context_renew (PJ *P) { return 0; } + /* Move daughter to mother's context */ void pj_context_inherit (PJ *mother, PJ *daughter) { pj_set_ctx (daughter, pj_get_ctx (mother)); diff --git a/src/pj_param.c b/src/pj_param.c index 434d4535..24c2354e 100644 --- a/src/pj_param.c +++ b/src/pj_param.c @@ -23,13 +23,13 @@ pj_mkparam(char *str) { /* character in `opt' is a parameter type which can take the */ /* values: */ /* */ -/* `t' - test for presence, return TRUE/FALSE in PROJVALUE.i */ -/* `i' - integer value returned in PROJVALUE.i */ -/* `d' - simple valued real input returned in PROJVALUE.f */ +/* `t' - test for presence, return TRUE/FALSE in PROJVALUE.i */ +/* `i' - integer value returned in PROJVALUE.i */ +/* `d' - simple valued real input returned in PROJVALUE.f */ /* `r' - degrees (DMS translation applied), returned as */ -/* radians in PROJVALUE.f */ -/* `s' - string returned in PROJVALUE.s */ -/* `b' - test for t/T/f/F, return in PROJVALUE.i */ +/* radians in PROJVALUE.f */ +/* `s' - string returned in PROJVALUE.s */ +/* `b' - test for t/T/f/F, return in PROJVALUE.i */ /* */ /************************************************************************/ diff --git a/src/pj_strerrno.c b/src/pj_strerrno.c index 8a2a9c4b..bef42882 100644 --- a/src/pj_strerrno.c +++ b/src/pj_strerrno.c @@ -82,4 +82,5 @@ char *pj_strerrno(int err) { return note; } } + return 0; } diff --git a/src/proj.def b/src/proj.def index 3a5e3de9..b01b846c 100644 --- a/src/proj.def +++ b/src/proj.def @@ -89,23 +89,24 @@ EXPORTS geod_polygon_testpoint @87 geod_polygon_clear @88 pj_run_selftests @89 - pj_error @90 - pj_create @91 - pj_create_argv @92 - pj_trans @93 - pj_fwdobs @94 - pj_invobs @95 - pj_roundtrip @96 - pj_log_level @97 - pj_error_set @98 - pj_log_set @99 - pj_context_renew @100 - pj_context_inherit @101 - pj_context_free @102 - pj_fileapi_set @103 - pj_log_error @104 - pj_log_debug @105 - pj_log_trace @106 - pj_lp_dist @107 - pj_xy_dist @108 - pj_xyz_dist @109 + pj_create @90 + pj_create_argv @91 + pj_trans @92 + pj_fwdobs @93 + pj_invobs @94 + pj_roundtrip @95 + pj_log_level @96 + pj_err_level @97 + pj_log_func @98 + pj_context_renew @99 + pj_context_inherit @100 + pj_context_free @101 + pj_fileapi_set @102 + pj_log_error @103 + pj_log_debug @104 + pj_log_trace @105 + pj_lp_dist @106 + pj_xy_dist @107 + pj_xyz_dist @108 + pj_coo_error @109 + pj_obs_error @110 @@ -271,10 +271,13 @@ double pj_xy_dist (XY a, XY b); double pj_xyz_dist (XYZ a, XYZ b); -#ifndef PJ_OBS_C -extern const PJ_OBS pj_obs_error; -extern const PJ_OBS pj_obs_null; -extern const PJ *pj_shutdown; +#ifndef PJ_OBS_API_C +/* Part of MSVC workaround: Make pj_*_null look function-like for symmetry with pj_*_error */ +#define pj_coo_null(x) pj_coo_null +#define pj_obs_null(x) pj_obs_null +extern const PJ_COORD pj_coo_null; +extern const PJ_OBS pj_obs_null; +extern const PJ *pj_shutdown; #endif #ifndef TODEG @@ -301,17 +304,19 @@ enum pj_log_level { PJ_LOG_DEBUG_MINOR = 3 /* for proj_api.h compatibility */ }; +/* Set or read error level */ +#define PJ_ERR_TELL -56789 +int pj_err_level (PJ *P, int err_level); + /* Set logging level 0-3. Higher number means more debug info. 0 turns it off */ enum pj_log_level pj_log_level (PJ *P, enum pj_log_level log_level); void pj_log_error (PJ *P, const char *fmt, ...); void pj_log_debug (PJ *P, const char *fmt, ...); void pj_log_trace (PJ *P, const char *fmt, ...); +void pj_log_func (PJ *P, void *app_data, void (*log)(void *, int, const char *)); -void pj_error_set (PJ *P, int err); -void pj_log_set (PJ *P, void *app_data, void (*log)(void *, int, const char *)); - /* Lower level functionality for handling thread contexts */ int pj_context_renew (PJ *P); void pj_context_inherit (PJ *mother, PJ *daughter); |
