diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 2 | ||||
| -rw-r--r-- | src/PJ_cart.c | 65 | ||||
| -rw-r--r-- | src/PJ_pipeline.c | 603 | ||||
| -rw-r--r-- | src/lib_proj.cmake | 1 | ||||
| -rw-r--r-- | src/makefile.vc | 2 | ||||
| -rw-r--r-- | src/pj_ell_set.c | 188 | ||||
| -rw-r--r-- | src/pj_init.c | 41 | ||||
| -rw-r--r-- | src/pj_list.h | 1 | ||||
| -rw-r--r-- | src/pj_log.c | 65 | ||||
| -rw-r--r-- | src/pj_obs_api.c | 46 | ||||
| -rw-r--r-- | src/pj_strerrno.c | 126 | ||||
| -rw-r--r-- | src/proj.def | 10 | ||||
| -rw-r--r-- | src/proj.h | 95 | ||||
| -rw-r--r-- | src/proj_api.h | 16 | ||||
| -rw-r--r-- | src/projects.h | 3 |
15 files changed, 1014 insertions, 250 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 1d53375a..69e0e44e 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_obs_api.c PJ_cart.c PJ_pipeline.c install-exec-local: rm -f $(DESTDIR)$(bindir)/invproj$(EXEEXT) diff --git a/src/PJ_cart.c b/src/PJ_cart.c index 5df95c31..4ec20ced 100644 --- a/src/PJ_cart.c +++ b/src/PJ_cart.c @@ -197,8 +197,9 @@ 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 */ + /* 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); } else @@ -247,21 +248,31 @@ int pj_cart_selftest (void) {return 0;} #else /* Testing quite a bit of the pj_obs_api as a side effect (inspired by pj_obs_api_test.c) */ int pj_cart_selftest (void) { - PJ *p; + PJ *P; PJ_OBS a, b; int err; double dist; + char *args[3] = {"proj=utm", "zone=32", "ellps=GRS80"}; + /* Log everything libproj offers to log for you */ - pj_debug_set (0, PJ_LOG_DEBUG_MINOR); + 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) + P = pj_create ("+proj=utm +zone=32 +ellps=GRS80"); + if (0==P) return 1; + /* Clean up */ + pj_free (P); + + /* Same projection, now using argc/argv style initialization */ + P = pj_create_argv (3, args); + if (0==P) + return puts ("Oops"), 0; + /* Turn off logging */ - pj_debug_set (0, PJ_LOG_NONE); + pj_log_level (0, PJ_LOG_NONE); /* zero initialize everything, then set (longitude, latitude) to (12, 55) */ a = pj_obs_null; @@ -270,38 +281,38 @@ int pj_cart_selftest (void) { a.coo.lp.phi = TORAD(55); /* Forward projection */ - b = pj_apply (p, PJ_FWD, a); + b = pj_trans (P, PJ_FWD, a); /* Inverse projection */ - a = pj_apply (p, PJ_INV, b); + a = pj_trans (P, PJ_INV, b); /* Null projection */ - a = pj_apply (p, PJ_IDENT, a); + a = pj_trans (P, PJ_IDENT, a); /* Forward again, to get two linear items for comparison */ - a = pj_apply (p, PJ_FWD, a); + a = pj_trans (P, PJ_FWD, a); - dist = pj_obs_dist_2d (a, b); + dist = pj_xy_dist (a.coo.xy, b.coo.xy); if (dist > 2e-9) return 2; /* Invalid projection */ - a = pj_apply (p, 42, a); + a = pj_trans (P, 42, a); if (a.coo.lpz.lam!=DBL_MAX) return 3; - err = pj_error (p); + err = pj_error (P); if (0==err) return 4; /* Clear error */ - pj_error_set (p, 0); + pj_error_set (P, 0); /* Clean up */ - pj_free (p); + pj_free (P); /* Now do some 3D transformations */ - p = pj_create ("+proj=cart +ellps=GRS80"); - if (0==p) + P = pj_create ("+proj=cart +ellps=GRS80"); + if (0==P) return 5; /* zero initialize everything, then set (longitude, latitude, height) to (12, 55, 100) */ @@ -311,26 +322,26 @@ int pj_cart_selftest (void) { a.coo.lpz.z = 100; /* Forward projection: 3D-Cartesian-to-Ellipsoidal */ - b = pj_apply (p, PJ_FWD, a); + b = pj_trans (P, PJ_FWD, a); /* Check roundtrip precision for 10000 iterations each way */ - dist = pj_roundtrip (p, PJ_FWD, 10000, a); - dist = pj_roundtrip (p, PJ_INV, 10000, b); + dist = pj_roundtrip (P, PJ_FWD, 10000, a); + dist = pj_roundtrip (P, PJ_INV, 10000, b); if (dist > 2e-9) return 6; /* Inverse projection: Ellipsoidal-to-3D-Cartesian */ - b = pj_apply (p, PJ_INV, b); + b = pj_trans (P, PJ_INV, b); /* Move p to another context */ - pj_context_renew (p); - b = pj_apply (p, PJ_FWD, b); + pj_context_renew (P); + b = pj_trans (P, PJ_FWD, b); /* Move it back to the default context */ - pj_context_free (p); - b = pj_apply (p, PJ_INV, b); + pj_context_free (P); + b = pj_trans (P, PJ_INV, b); - pj_free (p); + pj_free (P); return 0; } #endif diff --git a/src/PJ_pipeline.c b/src/PJ_pipeline.c new file mode 100644 index 00000000..ef86ccd3 --- /dev/null +++ b/src/PJ_pipeline.c @@ -0,0 +1,603 @@ +/******************************************************************************* + + Transformation pipeline manager + + Thomas Knudsen, 2016-05-20/2016-11-20 + +******************************************************************************** + + Geodetic transformations are typically organized in a number of + steps. For example, a datum shift could be carried out through + these steps: + + 1. Convert (latitude, longitude, ellipsoidal height) to + 3D geocentric cartesian coordinates (X, Y, Z) + 2. Transform the (X, Y, Z) coordinates to the new datum, using a + 7 parameter Helmert transformation. + 3. Convert (X, Y, Z) back to (latitude, longitude, ellipsoidal height) + + If the height system used is orthometric, rather than ellipsoidal, + another step is needed at each end of the process: + + 1. Add the local geoid undulation (N) to the orthometric height + to obtain the ellipsoidal (i.e. geometric) height. + 2. Convert (latitude, longitude, ellipsoidal height) to + 3D geocentric cartesian coordinates (X, Y, Z) + 3. Transform the (X, Y, Z) coordinates to the new datum, using a + 7 parameter Helmert transformation. + 4. Convert (X, Y, Z) back to (latitude, longitude, ellipsoidal height) + 5. Subtract the local geoid undulation (N) from the ellipsoidal height + to obtain the orthometric height. + + Additional steps can be added for e.g. change of vertical datum, so the + list can grow fairly long. None of the steps are, however, particularly + complex, and data flow is strictly from top to bottom. + + Hence, in principle, the first example above could be implemented using + Unix pipelines: + + cat my_coordinates | geographic_to_xyz | helmert | xyz_to_geographic > my_transformed_coordinates + + in the grand tradition of Software Tools [1]. + + The proj pipeline driver implements a similar concept: Stringing together + a number of steps, feeding the output of one step to the input of the next. + + It is a very powerful concept, that increases the range of relevance of the + proj.4 system substantially. It is, however, not a particularly intrusive + addition to the code base: The implementation is by and large completed by + adding an extra projection called "pipeline" (i.e. this file), which + handles all business. + + Syntactically, the pipeline system introduces the "+step" keyword (which + indicates the start of each transformation step), the "+omit_fwd" and + "+omit_inv" keywords (which indicate that a given transformation step + should be omitted when the pipeline is executed in forward, resp. inverse + direction), and reintroduces the +inv keyword (indicating that a given + transformation step should run in reverse, i.e. forward, when the pipeline + is executed in inverse direction, and vice versa). + + Hence, the first transformation example above, can be implemented as: + + +proj=pipeline +step proj=cart +step proj=helmert <ARGS> +step proj=cart +inv + + Where <ARGS> indicate the Helmert arguments: 3 translations (+x=..., +y=..., + +z=...), 3 rotations (+rx=..., +ry=..., +rz=...) and a scale factor (+s=...). + Following geodetic conventions, the rotations are given in Milliarcseconds, + and the scale factor is given as parts-per-million. + + [1] B. W. Kernighan & P. J. Plauger: Software tools. Addison-Wesley, 1976, 338 pp. + +******************************************************************************** + +Thomas Knudsen, thokn@sdfe.dk, 2016-05-20 + +******************************************************************************** +* 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 <assert.h> +#include <stddef.h> +#include <errno.h> +PROJ_HEAD(pipeline, "Transformation pipeline manager"); + +/* Projection specific elements for the PJ object */ +#define PIPELINE_STACK_SIZE 100 +struct pj_opaque { + int reversible; + int steps; + int depth; + int verbose; + int *reverse_step; + int *omit_forward; + int *omit_inverse; + PJ_OBS stack[PIPELINE_STACK_SIZE]; + PJ **pipeline; +}; + + +static PJ_OBS pipeline_forward_obs (PJ_OBS, PJ *P); +static PJ_OBS pipeline_reverse_obs (PJ_OBS, PJ *P); +static XYZ pipeline_forward_3d (LPZ lpz, PJ *P); +static LPZ pipeline_reverse_3d (XYZ xyz, PJ *P); +static XY pipeline_forward (LP lpz, PJ *P); +static LP pipeline_reverse (XY xyz, PJ *P); + + + +/******************************************************************** + + ISOMORPHIC TRANSFORMATIONS + +********************************************************************* + + In this context, an isomorphic transformation is a proj PJ + object returning the same kind of coordinates that it + receives, i.e. a transformation from angular to angular or + linear to linear coordinates. + + The degrees-to-radians operation is an example of the former, + while the latter is typical for most of the datum shift + operations used in geodesy, e.g. the Helmert 7-parameter shift. + + Isomorphic transformations trips up the pj_inv/pj_fwd + functions, which try to check input sanity and scale output to + internal proj units, under the assumption that if input is of + angular type, then output must be of linear (or vice versa). + + Hence, to avoid having pj_inv/pj_fwd stomping on output (or + choking on input), we need a way to tell them that they should + accept whatever is being handed to them. + + The P->left and P->right elements indicate isomorphism. + + For classic proj style projections, they both have the + value PJ_IO_UNITS_CLASSIC (default initialization), indicating + that the forward driver expects angular input coordinates, and + provides linear output coordinates. + + Newer projections may set P->left and P->right to either + PJ_IO_UNITS_METERS, PJ_IO_UNITS_RADIANS or PJ_IO_UNITS_ANY, + to indicate their I/O style. + + For the forward driver, left indicates input coordinate + type, while right indicates output coordinate type. + + For the inverse driver, left indicates output coordinate + type, while right indicates input coordinate type. + +*********************************************************************/ + + + + +static PJ_OBS pipeline_forward_obs (PJ_OBS point, PJ *P) { + int i, first_step, last_step, incr; + + first_step = 1; + last_step = P->opaque->steps + 1; + incr = 1; + + for (i = first_step; i != last_step; i += incr) { + if (P->opaque->omit_forward[i]) + continue; + if (P->opaque->reverse_step[i]) + point = pj_trans (P->opaque->pipeline[i], PJ_INV, point); + else + point = pj_trans (P->opaque->pipeline[i], PJ_FWD, point); + if (P->opaque->depth < PIPELINE_STACK_SIZE) + P->opaque->stack[P->opaque->depth++] = point; + } + + 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; + + first_step = P->opaque->steps; + last_step = 0; + incr = -1; + + for (i = first_step; i != last_step; i += incr) { + if (P->opaque->omit_inverse[i]) + continue; + if (P->opaque->reverse_step[i]) + point = pj_trans (P->opaque->pipeline[i], PJ_FWD, point); + else + point = pj_trans (P->opaque->pipeline[i], PJ_INV, point); + if (P->opaque->depth < PIPELINE_STACK_SIZE) + P->opaque->stack[P->opaque->depth++] = point; + } + + P->opaque->depth = 0; /* Clear the stack */ + return point; +} + + +/* Delegate the work to pipeline_forward_obs() */ +static XYZ pipeline_forward_3d (LPZ lpz, PJ *P) { + PJ_OBS point = pj_obs_null; + point.coo.lpz = lpz; + point = pipeline_forward_obs (point, P); + return point.coo.xyz; +} + +/* Delegate the work to pipeline_reverse_obs() */ +static LPZ pipeline_reverse_3d (XYZ xyz, PJ *P) { + PJ_OBS point = pj_obs_null; + point.coo.xyz = xyz; + point = pipeline_reverse_obs (point, P); + return point.coo.lpz; +} + +static XY pipeline_forward (LP lp, PJ *P) { + PJ_OBS point = pj_obs_null; + point.coo.lp = lp; + point = pipeline_forward_obs (point, P); + return point.coo.xy; +} + +static LP pipeline_reverse (XY xy, PJ *P) { + PJ_OBS point = pj_obs_null; + point.coo.xy = xy; + point = pipeline_reverse_obs (point, P); + return point.coo.lp; +} + +static void freeup(PJ *P) { /* Destructor */ + if (P==0) + return; + /* Projection specific deallocation goes here */ + pj_dealloc (P->opaque); + pj_dealloc (P); + return; +} + + +static void *pipeline_freeup (PJ *P, int errlev) { /* Destructor */ + int i; + if (0==P) + return 0; + + pj_error_set (P, errlev); + + if (0==P->opaque) + return pj_dealloc (P); + + for (i = 0; i < P->opaque->steps; i++) + pj_free (P->opaque->pipeline[i+1]); + + pj_dealloc (P->opaque->reverse_step); + pj_dealloc (P->opaque->omit_forward); + pj_dealloc (P->opaque->omit_inverse); + pj_dealloc (P->opaque->pipeline); + + pj_dealloc (P->opaque); + return pj_dealloc(P); +} + + +/* Adapts pipeline_freeup to the format defined for the PJ object */ +static void pipeline_freeup_wrapper (PJ *P) { + pipeline_freeup (P, 0); + return; +} + + +static PJ *pj_create_pipeline (PJ *P, size_t steps) { + + /* Room for the pipeline: An array of PJ * with room for sentinels at both ends */ + P->opaque->pipeline = pj_calloc (steps + 2, sizeof(PJ *)); + if (0==P->opaque->pipeline) + return 0; + + P->opaque->steps = steps; + + P->opaque->reverse_step = pj_calloc (steps + 2, sizeof(int)); + if (0==P->opaque->reverse_step) + return 0; + + P->opaque->omit_forward = pj_calloc (steps + 2, sizeof(int)); + if (0==P->opaque->omit_forward) + return 0; + + P->opaque->omit_inverse = pj_calloc (steps + 2, sizeof(int)); + if (0==P->opaque->omit_inverse) + return 0; + + return P; +} + +/* count the number of args in pipeline definition */ +size_t argc_params (paralist *params) { + size_t argc = 0; + for (; params != 0; params = params->next) + argc++; + return ++argc; /* one extra for the sentinel */ +} + +/* Sentinel for argument list */ +static char argv_sentinel[5] = "step"; + +/* turn paralist int argc/argv style argument list */ +char **argv_params (paralist *params) { + char **argv; + size_t argc = 0; + argv = pj_calloc (argc_params (params), sizeof (char *)); + if (0==argv) + return 0; + for (; params != 0; params = params->next) + argv[argc++] = params->param; + argv[argc++] = argv_sentinel; + return argv; +} + + +PJ *PROJECTION(pipeline) { + int i, nsteps = 0, argc; + int i_pipeline = -1, i_first_step = -1, i_current_step; + char **argv, **current_argv; + + P->fwdobs = pipeline_forward_obs; + P->invobs = pipeline_reverse_obs; + P->fwd3d = pipeline_forward_3d; + P->inv3d = pipeline_reverse_3d; + P->fwd = pipeline_forward; + P->inv = pipeline_reverse; + P->pfree = pipeline_freeup_wrapper; + + P->opaque = pj_calloc (1, sizeof(struct pj_opaque)); + if (0==P->opaque) + return 0; + + argc = argc_params (P->params); + argv = argv_params (P->params); + 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); + if (0==current_argv) + return pipeline_freeup (P, ENOMEM); + + /* Do some syntactic sanity checking */ + for (i = 0; i < argc; i++) { + if (0==strcmp ("step", argv[i])) { + if (-1==i_pipeline) { + pj_log_error (P, "Pipeline: +step before +proj=pipeline"); + return pipeline_freeup (P, -50); + } + if (0==nsteps) + i_first_step = i; + nsteps++; + continue; + } + + if (0==strcmp ("proj=pipeline", argv[i])) { + if (-1 != i_pipeline) { + pj_log_error (P, "Pipeline: Nesting invalid"); + return pipeline_freeup (P, -50); /* ERROR: nested pipelines */ + } + i_pipeline = i; + } + } + nsteps--; /* Last instance of +step is just a sentinel */ + P->opaque->steps = nsteps; + + if (-1==i_pipeline) + return pipeline_freeup (P, -50); /* ERROR: no pipeline def */ + + if (0==nsteps) + return pipeline_freeup (P, -50); /* ERROR: no pipeline def */ + + /* Make room for the pipeline and execution indicators */ + if (0==pj_create_pipeline (P, nsteps)) + return pipeline_freeup (P, ENOMEM); + + /* Now loop over all steps, building a new set of arguments for each init */ + for (i_current_step = i_first_step, i = 0; i < nsteps; i++) { + int j; + int current_argc = 0; + PJ *next_step = 0; + + /* Build a set of setup args for the current step */ + pj_log_trace (P, "Pipeline: Building arg list for step no. %d", i); + + /* First add the step specific args */ + for (j = i_current_step + 1; 0 != strcmp ("step", argv[j]); j++) + current_argv[current_argc++] = argv[j]; + + i_current_step = j; + + /* Then add the global args */ + for (j = i_pipeline + 1; 0 != strcmp ("step", argv[j]); j++) + current_argv[current_argc++] = argv[j]; + + /* Finally handle non-symmetric steps and inverted steps */ + for (j = 0; j < current_argc; j++) { + if (0==strcmp("omit_inv", current_argv[j])) { + P->opaque->omit_inverse[i+1] = 1; + P->opaque->omit_forward[i+1] = 0; + } + if (0==strcmp("omit_fwd", current_argv[j])) { + P->opaque->omit_inverse[i+1] = 0; + P->opaque->omit_forward[i+1] = 1; + } + if (0==strcmp("inv", current_argv[j])) + P->opaque->reverse_step[i+1] = 1; + } + + pj_log_trace (P, "Pipeline: init - %s, %d", current_argv[0], current_argc); + for (j = 1; j < current_argc; j++) + pj_log_trace (P, " %s", current_argv[j]); + + next_step = pj_init (current_argc, current_argv); + pj_log_trace (P, "Pipeline: Step %d at %p", i, next_step); + if (0==next_step) { + pj_log_error (P, "Pipeline: Bad step definition: %s", current_argv[0]); + return pipeline_freeup (P, -50); /* ERROR: bad pipeline def */ + } + P->opaque->pipeline[i+1] = next_step; + pj_log_trace (P, "Pipeline: step done"); + } + + pj_log_trace (P, "Pipeline: %d steps built. Determining i/o characteristics", nsteps); + + /* Determine forward input (= reverse output) data type */ + + /* First locate the first forward-active pipeline step */ + for (i = 0; i < nsteps; i++) + if (0==P->opaque->omit_forward[i+1]) + break; + if (i==nsteps) { + pj_log_error (P, "Pipeline: No forward steps"); + return pipeline_freeup (P, -50); + } + + if (P->opaque->reverse_step[i + 1]) + P->left = P->opaque->pipeline[i + 1]->right; + else + P->left = P->opaque->pipeline[i + 1]->left; + + if (P->left==PJ_IO_UNITS_CLASSIC) { + if (P->opaque->reverse_step[i + 1]) + P->left = PJ_IO_UNITS_METERS; + else + P->left = PJ_IO_UNITS_RADIANS; + } + + /* Now, correspondingly determine forward output (= reverse input) data type */ + + /* First locate the last reverse-active pipeline step */ + for (i = nsteps - 1; i >= 0; i--) + if (0==P->opaque->omit_inverse[i+1]) + break; + if (i==-1) { + pj_log (pj_get_ctx (P), PJ_LOG_ERROR, "Pipeline: No reverse steps"); + return pipeline_freeup (P, -50); + } + + if (P->opaque->reverse_step[i + 1]) + P->right = P->opaque->pipeline[i + 1]->left; + else + P->right = P->opaque->pipeline[i + 1]->right; + + if (P->right==PJ_IO_UNITS_CLASSIC) { + if (P->opaque->reverse_step[i + 1]) + P->right = PJ_IO_UNITS_RADIANS; + else + P->right = PJ_IO_UNITS_METERS; + } + pj_log_trace (P, "Pipeline: Units - left: [%s], right: [%s]\n", + P->left ==PJ_IO_UNITS_RADIANS? "angular": "linear", + P->right==PJ_IO_UNITS_RADIANS? "angular": "linear"); + + return P; +} + +#ifndef PJ_SELFTEST +/* selftest stub */ +int pj_pipeline_selftest (void) {return 0;} +#else + +int pj_pipeline_selftest (void) { + PJ *P; + PJ_OBS a, b; + XY cph_utm32 = {691875.63214, 6098907.82501}; + double dist; + + /* forward-reverse geo->utm->geo */ + P = pj_create ("+proj=pipeline +ellps=GRS80 +zone=32 +step +proj=utm +step +proj=utm +inv"); + if (0==P) + return 1000; + /* zero initialize everything, then set (longitude, latitude, height) to (12, 55, 0) */ + a = b = pj_obs_null; + a.coo.lpz.lam = TORAD(12); + a.coo.lpz.phi = TORAD(55); + a.coo.lpz.z = 0; + + /* Forward projection */ + b = pj_trans (P, PJ_FWD, a); + if (pj_lp_dist (P, a.coo.lp, b.coo.lp) > 1e-4) + return 1001; + + /* Inverse projection (still same result: pipeline is symmetrical) */ + a = pj_trans (P, PJ_INV, b); + if (pj_lp_dist (P, a.coo.lp, b.coo.lp) > 1e-4) + return 1002; + + pj_free (P); + + /* 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"); + if (0==P) + return 2000; + + /* zero initialize everything, then set (easting, northing) to utm(12, 55) */ + a = b = pj_obs_null; + a.coo.xy = cph_utm32; + + /* Forward projection */ + b = pj_trans (P, PJ_FWD, a); + if (pj_xy_dist (a.coo.xy, b.coo.xy) > 1e-4) + return 2001; + + /* Inverse projection */ + a = pj_trans (P, PJ_INV, b); + if (pj_xy_dist (a.coo.xy, b.coo.xy) > 1e-4) + return 2001; + if (pj_xyz_dist (a.coo.xyz, b.coo.xyz) > 1e-4) + return 2002; + + pj_free (P); + + + /* Finally testing a corner case: A rather pointless one-step pipeline geo->utm */ + P = pj_create ("+proj=pipeline +ellps=GRS80 +zone=32 +step +proj=utm"); + if (0==P) + return 3000; + + a = b = pj_obs_null; + a.coo.lpz.lam = TORAD(12); + a.coo.lpz.phi = TORAD(55); + + /* Forward projection */ + b = pj_trans (P, PJ_FWD, a); + if (pj_xy_dist (cph_utm32, b.coo.xy) > 1e-4) + return 3001; + + /* Inverse projection */ + b = pj_trans (P, PJ_INV, b); + if (pj_lp_dist (P, a.coo.lp, b.coo.lp) > 1e-4) + return 3002; + + + /* Since we use pj_lp_dist to determine success above, we should also test that it works */ + + /* Geodesic distance between two points with angular 2D coordinates */ + a.coo.lp.lam = TORAD(12); + a.coo.lp.phi = TORAD(60); + b.coo.lp.lam = TORAD(12); + b.coo.lp.phi = TORAD(61); + dist = pj_lp_dist (P, a.coo.lp, b.coo.lp); + if (fabs (111420.727870234 - dist) > 1e-4) + return 4001; + + a.coo.lp.lam = TORAD(12); + a.coo.lp.phi = TORAD(0.); + b.coo.lp.lam = TORAD(12); + b.coo.lp.phi = TORAD(1.); + dist = pj_lp_dist (P, a.coo.lp, b.coo.lp); + if (fabs (110574.388554153 - dist) > 1e-4) + return 4002; + + pj_free (P); + return 0; +} +#endif diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake index 2386a102..9e1ac4f7 100644 --- a/src/lib_proj.cmake +++ b/src/lib_proj.cmake @@ -111,6 +111,7 @@ SET(SRC_LIBPROJ_PJ PJ_omerc.c PJ_ortho.c PJ_patterson.c + PJ_pipeline.c PJ_poly.c PJ_putp2.c PJ_putp3.c diff --git a/src/makefile.vc b/src/makefile.vc index 51b4320b..a2869217 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_obs_api.obj PJ_cart.obj PJ_pipeline.obj geodesic = geodesic.obj diff --git a/src/pj_ell_set.c b/src/pj_ell_set.c index 7ca88ed1..87fb6e41 100644 --- a/src/pj_ell_set.c +++ b/src/pj_ell_set.c @@ -1,104 +1,108 @@ /* set ellipsoid parameters a and es */ #include <projects.h> #include <string.h> -#define SIXTH .1666666666666666667 /* 1/6 */ -#define RA4 .04722222222222222222 /* 17/360 */ -#define RA6 .02215608465608465608 /* 67/3024 */ -#define RV4 .06944444444444444444 /* 5/72 */ -#define RV6 .04243827160493827160 /* 55/1296 */ - int /* initialize geographic shape parameters */ -pj_ell_set(projCtx ctx, paralist *pl, double *a, double *es) { - int i; - double b=0.0, e; - char *name; - paralist *start = 0; +#define SIXTH .1666666666666666667 /* 1/6 */ +#define RA4 .04722222222222222222 /* 17/360 */ +#define RA6 .02215608465608465608 /* 67/3024 */ +#define RV4 .06944444444444444444 /* 5/72 */ +#define RV6 .04243827160493827160 /* 55/1296 */ - /* clear any previous error */ - pj_ctx_set_errno(ctx,0); +/* initialize geographic shape parameters */ +int pj_ell_set(projCtx ctx, paralist *pl, double *a, double *es) { + int i; + double b=0.0, e; + char *name; + paralist *start = 0; - /* check for varying forms of ellipsoid input */ - *a = *es = 0.; - /* R takes precedence */ - if (pj_param(ctx, pl, "tR").i) - *a = pj_param(ctx,pl, "dR").f; - else { /* probable elliptical figure */ + /* clear any previous error */ + pj_ctx_set_errno(ctx,0); - /* check if ellps present and temporarily append its values to pl */ + /* check for varying forms of ellipsoid input */ + *a = *es = 0.; + + /* R takes precedence */ + if (pj_param(ctx, pl, "tR").i) + *a = pj_param(ctx,pl, "dR").f; + + /* probable elliptical figure */ + else { + /* check if ellps present and temporarily append its values to pl */ if ((name = pj_param(ctx,pl, "sellps").s) != NULL) { - char *s; + char *s; + + for (start = pl; start && start->next ; start = start->next) ; + for (i = 0; (s = pj_ellps[i].id) && strcmp(name, s) ; ++i) ; + if (!s) { pj_ctx_set_errno( ctx, -9); return 1; } + start->next = pj_mkparam(pj_ellps[i].major); + start->next->next = pj_mkparam(pj_ellps[i].ell); + } - for (start = pl; start && start->next ; start = start->next) ; - for (i = 0; (s = pj_ellps[i].id) && strcmp(name, s) ; ++i) ; - if (!s) { pj_ctx_set_errno( ctx, -9); return 1; } - start->next = pj_mkparam(pj_ellps[i].major); - start->next->next = pj_mkparam(pj_ellps[i].ell); - } - *a = pj_param(ctx,pl, "da").f; - if (pj_param(ctx,pl, "tes").i) /* eccentricity squared */ - *es = pj_param(ctx,pl, "des").f; - else if (pj_param(ctx,pl, "te").i) { /* eccentricity */ - e = pj_param(ctx,pl, "de").f; - *es = e * e; - } else if (pj_param(ctx,pl, "trf").i) { /* recip flattening */ - *es = pj_param(ctx,pl, "drf").f; - if (!*es) { - pj_ctx_set_errno( ctx, -10); - goto bomb; - } - *es = 1./ *es; - *es = *es * (2. - *es); - } else if (pj_param(ctx,pl, "tf").i) { /* flattening */ - *es = pj_param(ctx,pl, "df").f; - *es = *es * (2. - *es); - } else if (pj_param(ctx,pl, "tb").i) { /* minor axis */ - b = pj_param(ctx,pl, "db").f; - *es = 1. - (b * b) / (*a * *a); - } /* else *es == 0. and sphere of radius *a */ - if (!b) - b = *a * sqrt(1. - *es); - /* following options turn ellipsoid into equivalent sphere */ - if (pj_param(ctx,pl, "bR_A").i) { /* sphere--area of ellipsoid */ - *a *= 1. - *es * (SIXTH + *es * (RA4 + *es * RA6)); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_V").i) { /* sphere--vol. of ellipsoid */ - *a *= 1. - *es * (SIXTH + *es * (RV4 + *es * RV6)); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_a").i) { /* sphere--arithmetic mean */ - *a = .5 * (*a + b); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_g").i) { /* sphere--geometric mean */ - *a = sqrt(*a * b); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_h").i) { /* sphere--harmonic mean */ - *a = 2. * *a * b / (*a + b); - *es = 0.; - } else if ((i = pj_param(ctx,pl, "tR_lat_a").i) || /* sphere--arith. */ - pj_param(ctx,pl, "tR_lat_g").i) { /* or geom. mean at latitude */ - double tmp; + *a = pj_param(ctx,pl, "da").f; + if (pj_param(ctx,pl, "tes").i) /* eccentricity squared */ + *es = pj_param(ctx,pl, "des").f; + else if (pj_param(ctx,pl, "te").i) { /* eccentricity */ + e = pj_param(ctx,pl, "de").f; + *es = e * e; + } else if (pj_param(ctx,pl, "trf").i) { /* recip flattening */ + *es = pj_param(ctx,pl, "drf").f; + if (!*es) { + pj_ctx_set_errno( ctx, -10); + goto bomb; + } + *es = 1./ *es; + *es = *es * (2. - *es); + } else if (pj_param(ctx,pl, "tf").i) { /* flattening */ + *es = pj_param(ctx,pl, "df").f; + *es = *es * (2. - *es); + } else if (pj_param(ctx,pl, "tb").i) { /* minor axis */ + b = pj_param(ctx,pl, "db").f; + *es = 1. - (b * b) / (*a * *a); + } /* else *es == 0. and sphere of radius *a */ + if (!b) + b = *a * sqrt(1. - *es); + /* following options turn ellipsoid into equivalent sphere */ + if (pj_param(ctx,pl, "bR_A").i) { /* sphere--area of ellipsoid */ + *a *= 1. - *es * (SIXTH + *es * (RA4 + *es * RA6)); + *es = 0.; + } else if (pj_param(ctx,pl, "bR_V").i) { /* sphere--vol. of ellipsoid */ + *a *= 1. - *es * (SIXTH + *es * (RV4 + *es * RV6)); + *es = 0.; + } else if (pj_param(ctx,pl, "bR_a").i) { /* sphere--arithmetic mean */ + *a = .5 * (*a + b); + *es = 0.; + } else if (pj_param(ctx,pl, "bR_g").i) { /* sphere--geometric mean */ + *a = sqrt(*a * b); + *es = 0.; + } else if (pj_param(ctx,pl, "bR_h").i) { /* sphere--harmonic mean */ + *a = 2. * *a * b / (*a + b); + *es = 0.; + } else if ((i = pj_param(ctx,pl, "tR_lat_a").i) || /* sphere--arith. */ + pj_param(ctx,pl, "tR_lat_g").i) { /* or geom. mean at latitude */ + double tmp; - tmp = sin(pj_param(ctx,pl, i ? "rR_lat_a" : "rR_lat_g").f); - if (fabs(tmp) > M_HALFPI) { + tmp = sin(pj_param(ctx,pl, i ? "rR_lat_a" : "rR_lat_g").f); + if (fabs(tmp) > M_HALFPI) { pj_ctx_set_errno(ctx,-11); - goto bomb; - } - tmp = 1. - *es * tmp * tmp; - *a *= i ? .5 * (1. - *es + tmp) / ( tmp * sqrt(tmp)) : - sqrt(1. - *es) / tmp; - *es = 0.; - } + goto bomb; + } + tmp = 1. - *es * tmp * tmp; + *a *= i ? .5 * (1. - *es + tmp) / ( tmp * sqrt(tmp)) : + sqrt(1. - *es) / tmp; + *es = 0.; + } bomb: - if (start) { /* clean up temporary extension of list */ - pj_dalloc(start->next->next); - pj_dalloc(start->next); - start->next = 0; - } - if (ctx->last_errno) - return 1; - } - /* some remaining checks */ - if (*es < 0.) - { pj_ctx_set_errno( ctx, -12); return 1; } - if (*a <= 0.) - { pj_ctx_set_errno( ctx, -13); return 1; } - return 0; + if (start) { /* clean up temporary extension of list */ + pj_dalloc(start->next->next); + pj_dalloc(start->next); + start->next = 0; + } + if (ctx->last_errno) + return 1; + } + /* some remaining checks */ + if (*es < 0.) + { pj_ctx_set_errno( ctx, -12); return 1; } + if (*a <= 0.) + { pj_ctx_set_errno( ctx, -13); return 1; } + return 0; } diff --git a/src/pj_init.c b/src/pj_init.c index 79b64bb3..d5fe2433 100644 --- a/src/pj_init.c +++ b/src/pj_init.c @@ -29,6 +29,7 @@ #define PJ_LIB__ #include <projects.h> +#include <geodesic.h> #include <stdio.h> #include <string.h> #include <errno.h> @@ -448,12 +449,42 @@ pj_init_ctx(projCtx ctx, int argc, char **argv) { PIN->a_orig = PIN->a; PIN->es_orig = PIN->es; - PIN->e = sqrt(PIN->es); + /* Compute some ancillary ellipsoidal parameters */ + + PIN->e = sqrt(PIN->es); /* eccentricity */ + PIN->alpha = asin (PIN->e); /* angular eccentricity */ + + /* second eccentricity */ + PIN->e2 = tan (PIN->alpha); + PIN->e2s = PIN->e2 * PIN->e2; + + /* third eccentricity */ + PIN->e3 = sin (PIN->alpha) / sqrt(2 - sin (PIN->alpha)*sin (PIN->alpha)); + PIN->e3s = PIN->e3 * PIN->e3; + + /* flattening */ + PIN->f = 1 - cos (PIN->alpha); /* = 1 - sqrt (1 - PIN->es); */ + PIN->rf = PIN->f? 1/PIN->f: HUGE_VAL; + + /* second flattening */ + PIN->f2 = 1/cos (PIN->alpha) - 1; + PIN->rf = PIN->f2? 1/PIN->f2: HUGE_VAL; + + /* third flattening */ + PIN->n = pow (tan (PIN->alpha/2), 2); + PIN->rn = PIN->n? 1/PIN->n: HUGE_VAL; + + /* ...and a few more */ + PIN->b = (1 - PIN->f)*PIN->a; + PIN->rb = 1. / PIN->b; PIN->ra = 1. / PIN->a; + PIN->one_es = 1. - PIN->es; if (PIN->one_es == 0.) { pj_ctx_set_errno( ctx, -6 ); goto bum_call; } PIN->rone_es = 1./PIN->one_es; + + /* Now that we have ellipse information check for WGS84 datum */ if( PIN->datum_type == PJD_3PARAM && PIN->datum_params[0] == 0.0 @@ -588,6 +619,11 @@ pj_init_ctx(projCtx ctx, int argc, char **argv) { else PIN->from_greenwich = 0.0; + /* Private object for the geodesic functions */ + PIN->geod = pj_calloc (1, sizeof (struct geod_geodesic)); + if (0!=PIN->geod) + geod_init(PIN->geod, PIN->a, (1 - sqrt (1 - PIN->es))); + /* projection specific initialization */ if (!(PIN = (*proj)(PIN)) || ctx->last_errno) { bum_call: /* cleanup error return */ @@ -635,6 +671,9 @@ pj_free(PJ *P) { if( P->catalog != NULL ) pj_dalloc( P->catalog ); + if( P->geod != NULL ) + pj_dalloc( P->geod ); + /* free projection parameters */ P->pfree(P); } diff --git a/src/pj_list.h b/src/pj_list.h index 3569e335..f49fd574 100644 --- a/src/pj_list.h +++ b/src/pj_list.h @@ -101,6 +101,7 @@ PROJ_HEAD(ortel, "Ortelius Oval") PROJ_HEAD(ortho, "Orthographic") PROJ_HEAD(pconic, "Perspective Conic") PROJ_HEAD(patterson, "Patterson Cylindrical") +PROJ_HEAD(pipeline, "Transformation pipeline manager") PROJ_HEAD(poly, "Polyconic (American)") PROJ_HEAD(putp1, "Putnins P1") PROJ_HEAD(putp2, "Putnins P2") diff --git a/src/pj_log.c b/src/pj_log.c index 54b83a05..0c711e52 100644 --- a/src/pj_log.c +++ b/src/pj_log.c @@ -42,13 +42,13 @@ void pj_stderr_logger( void *app_data, int level, const char *msg ) } /************************************************************************/ -/* pj_log() */ +/* pj_vlog() */ /************************************************************************/ -void pj_log( projCtx ctx, int level, const char *fmt, ... ) +/* Workhorse for the log functions - relates to pj_log as vsprintf relates to sprintf */ +static void pj_vlog( projCtx ctx, int level, const char *fmt, va_list args ) { - va_list args; char *msg_buf; if( level > ctx->debug_level ) @@ -58,14 +58,63 @@ void pj_log( projCtx ctx, int level, const char *fmt, ... ) if( msg_buf == NULL ) return; - va_start( args, fmt ); - /* we should use vsnprintf where available once we add configure detect.*/ vsprintf( msg_buf, fmt, args ); - va_end( args ); - ctx->logger( ctx->app_data, level, msg_buf ); - + free( msg_buf ); } + + +/************************************************************************/ +/* pj_log() */ +/************************************************************************/ + +void pj_log( projCtx ctx, int level, const char *fmt, ... ) + +{ + va_list args; + + if( level > ctx->debug_level ) + return; + + va_start( args, fmt ); + pj_vlog( ctx, level, fmt, args ); + va_end( args ); +} + + + +/************************************************************************/ +/* logging functions for the proj.h/obs_api */ +/************************************************************************/ + +void pj_log_error (PJ *P, const char *fmt, ...) + +{ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx (P), PJ_LOG_ERROR , fmt, args); + va_end( args ); +} + + +void pj_log_debug (PJ *P, const char *fmt, ...) + +{ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MAJOR , fmt, args); + va_end( args ); +} + + +void pj_log_trace (PJ *P, const char *fmt, ...) + +{ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MINOR , fmt, args); + va_end( args ); +} diff --git a/src/pj_obs_api.c b/src/pj_obs_api.c index ccec8a64..716c6095 100644 --- a/src/pj_obs_api.c +++ b/src/pj_obs_api.c @@ -35,6 +35,7 @@ #define PJ_OBS_C #include <proj.h> #include <projects.h> +#include <geodesic.h> #include <float.h> #include <math.h> @@ -57,20 +58,30 @@ const PJ_OBS pj_obs_null = { /* Magic object signaling proj system shutdown mode to routines taking a PJ * arg */ const PJ *pj_shutdown = (PJ *) &pj_shutdown; -/* Euclidean distance between two 2D coordinates stored in PJ_OBSs */ -double pj_obs_dist_2d (PJ_OBS a, PJ_OBS b) { - double *A = a.coo.v, *B = b.coo.v; - return hypot (A[0] - B[0], A[1] - B[1]); +/* Geodesic distance between two points with angular 2D coordinates */ +double pj_lp_dist (PJ *P, LP a, LP b) { + double s12, azi1, azi2; + /* Note: the geodesic code takes arguments in degrees */ + geod_inverse (P->geod, TODEG(a.phi), TODEG(a.lam), TODEG(b.phi), TODEG(b.lam), &s12, &azi1, &azi2); + return s12; } -/* Euclidean distance between two 3D coordinates stored in PJ_OBSs */ -double pj_obs_dist_3d (PJ_OBS a, PJ_OBS b) { - double *A = a.coo.v, *B = b.coo.v; - return hypot (hypot (A[0] - B[0], A[1] - B[1]), A[2] - B[2]); +/* Euclidean distance between two points with linear 2D coordinates */ +double pj_xy_dist (XY a, XY b) { + return hypot (a.x - b.x, a.y - b.y); +} + +/* Euclidean distance between two points with linear 3D coordinates */ +double pj_xyz_dist (XYZ a, XYZ b) { + return hypot (hypot (a.x - b.x, a.y - b.y), a.z - b.z); } PJ_OBS pj_fwdobs (PJ_OBS obs, PJ *P) { + if (0!=P->fwdobs) { + obs = P->fwdobs (obs, P); + return obs; + } if (0!=P->fwd3d) { obs.coo.xyz = pj_fwd3d (obs.coo.lpz, P); return obs; @@ -85,6 +96,10 @@ PJ_OBS pj_fwdobs (PJ_OBS obs, PJ *P) { PJ_OBS pj_invobs (PJ_OBS obs, PJ *P) { + if (0!=P->invobs) { + obs = P->invobs (obs, P); + return obs; + } if (0!=P->inv3d) { obs.coo.lpz = pj_inv3d (obs.coo.xyz, P); return obs; @@ -99,7 +114,7 @@ PJ_OBS pj_invobs (PJ_OBS obs, PJ *P) { /* Apply the transformation P to the observation obs */ -PJ_OBS pj_apply (PJ *P, enum pj_direction direction, PJ_OBS obs) { +PJ_OBS pj_trans (PJ *P, enum pj_direction direction, PJ_OBS obs) { if (0==P) return obs; @@ -150,7 +165,7 @@ double pj_roundtrip (PJ *P, enum pj_direction direction, int n, PJ_OBS obs) { } } - return pj_obs_dist_3d (o, obs); + return pj_xyz_dist (o.coo.xyz, obs.coo.xyz); } @@ -179,14 +194,19 @@ void pj_error_set (PJ *P, int err) { } -/* Set debug level 0-3. Higher number means more debug info. 0 turns it off */ -void pj_debug_set (PJ *P, enum pj_debug_level debuglevel) { +/* 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) { + enum pj_log_level previous; PJ_CONTEXT *ctx; if (0==P) ctx = pj_get_default_ctx(); else ctx = pj_get_ctx (P); - ctx->debug_level = debuglevel; + previous = ctx->debug_level; + if (PJ_LOG_TELL==log_level) + return previous; + ctx->debug_level = log_level; + return previous; } diff --git a/src/pj_strerrno.c b/src/pj_strerrno.c index b46f143c..9dc180b5 100644 --- a/src/pj_strerrno.c +++ b/src/pj_strerrno.c @@ -4,82 +4,82 @@ #include <errno.h> #include <string.h> - static char * + static char * pj_err_list[] = { - "no arguments in initialization list", /* -1 */ - "no options found in 'init' file", /* -2 */ - "no colon in init= string", /* -3 */ - "projection not named", /* -4 */ - "unknown projection id", /* -5 */ - "effective eccentricity = 1.", /* -6 */ - "unknown unit conversion id", /* -7 */ - "invalid boolean param argument", /* -8 */ - "unknown elliptical parameter name", /* -9 */ - "reciprocal flattening (1/f) = 0", /* -10 */ - "|radius reference latitude| > 90", /* -11 */ - "squared eccentricity < 0", /* -12 */ - "major axis or radius = 0 or not given", /* -13 */ - "latitude or longitude exceeded limits", /* -14 */ - "invalid x or y", /* -15 */ - "improperly formed DMS value", /* -16 */ - "non-convergent inverse meridional dist", /* -17 */ - "non-convergent inverse phi2", /* -18 */ - "acos/asin: |arg| >1.+1e-14", /* -19 */ - "tolerance condition error", /* -20 */ - "conic lat_1 = -lat_2", /* -21 */ - "lat_1 >= 90", /* -22 */ - "lat_1 = 0", /* -23 */ - "lat_ts >= 90", /* -24 */ - "no distance between control points", /* -25 */ - "projection not selected to be rotated", /* -26 */ - "W <= 0 or M <= 0", /* -27 */ - "lsat not in 1-5 range", /* -28 */ - "path not in range", /* -29 */ - "h <= 0", /* -30 */ - "k <= 0", /* -31 */ - "lat_0 = 0 or 90 or alpha = 90", /* -32 */ - "lat_1=lat_2 or lat_1=0 or lat_2=90", /* -33 */ - "elliptical usage required", /* -34 */ - "invalid UTM zone number", /* -35 */ - "arg(s) out of range for Tcheby eval", /* -36 */ - "failed to find projection to be rotated", /* -37 */ - "failed to load datum shift file", /* -38 */ - "both n & m must be spec'd and > 0", /* -39 */ - "n <= 0, n > 1 or not specified", /* -40 */ - "lat_1 or lat_2 not specified", /* -41 */ - "|lat_1| == |lat_2|", /* -42 */ - "lat_0 is pi/2 from mean lat", /* -43 */ - "unparseable coordinate system definition", /* -44 */ - "geocentric transformation missing z or ellps", /* -45 */ - "unknown prime meridian conversion id", /* -46 */ - "illegal axis orientation combination", /* -47 */ - "point not within available datum shift grids", /* -48 */ - "invalid sweep axis, choose x or y", /* -49 */ + "no arguments in initialization list", /* -1 */ + "no options found in 'init' file", /* -2 */ + "no colon in init= string", /* -3 */ + "projection not named", /* -4 */ + "unknown projection id", /* -5 */ + "effective eccentricity = 1.", /* -6 */ + "unknown unit conversion id", /* -7 */ + "invalid boolean param argument", /* -8 */ + "unknown elliptical parameter name", /* -9 */ + "reciprocal flattening (1/f) = 0", /* -10 */ + "|radius reference latitude| > 90", /* -11 */ + "squared eccentricity < 0", /* -12 */ + "major axis or radius = 0 or not given", /* -13 */ + "latitude or longitude exceeded limits", /* -14 */ + "invalid x or y", /* -15 */ + "improperly formed DMS value", /* -16 */ + "non-convergent inverse meridional dist", /* -17 */ + "non-convergent inverse phi2", /* -18 */ + "acos/asin: |arg| >1.+1e-14", /* -19 */ + "tolerance condition error", /* -20 */ + "conic lat_1 = -lat_2", /* -21 */ + "lat_1 >= 90", /* -22 */ + "lat_1 = 0", /* -23 */ + "lat_ts >= 90", /* -24 */ + "no distance between control points", /* -25 */ + "projection not selected to be rotated", /* -26 */ + "W <= 0 or M <= 0", /* -27 */ + "lsat not in 1-5 range", /* -28 */ + "path not in range", /* -29 */ + "h <= 0", /* -30 */ + "k <= 0", /* -31 */ + "lat_0 = 0 or 90 or alpha = 90", /* -32 */ + "lat_1=lat_2 or lat_1=0 or lat_2=90", /* -33 */ + "elliptical usage required", /* -34 */ + "invalid UTM zone number", /* -35 */ + "arg(s) out of range for Tcheby eval", /* -36 */ + "failed to find projection to be rotated", /* -37 */ + "failed to load datum shift file", /* -38 */ + "both n & m must be spec'd and > 0", /* -39 */ + "n <= 0, n > 1 or not specified", /* -40 */ + "lat_1 or lat_2 not specified", /* -41 */ + "|lat_1| == |lat_2|", /* -42 */ + "lat_0 is pi/2 from mean lat", /* -43 */ + "unparseable coordinate system definition", /* -44 */ + "geocentric transformation missing z or ellps", /* -45 */ + "unknown prime meridian conversion id", /* -46 */ + "illegal axis orientation combination", /* -47 */ + "point not within available datum shift grids", /* -48 */ + "invalid sweep axis, choose x or y", /* -49 */ + "malformed pipeline", /* -50 */ }; - char * -pj_strerrno(int err) -{ + +char *pj_strerrno(int err) { static char note[50]; - if (err > 0) + if (0==err) + return 0; + + if (err > 0) { #ifdef HAVE_STRERROR return strerror(err); #else - { sprintf(note,"no system list, errno: %d\n", err); return note; - } #endif - else if (err < 0) { + } + + if (err < 0) { size_t adjusted_err = - err - 1; if (adjusted_err < (sizeof(pj_err_list) / sizeof(char *))) return(pj_err_list[adjusted_err]); - else - { - sprintf( note, "invalid projection system error (%d)", - err ); + else { + sprintf( note, "invalid projection system error (%d)", err ); return note; } - } else - return NULL; + } } diff --git a/src/proj.def b/src/proj.def index 09b32c32..3a5e3de9 100644 --- a/src/proj.def +++ b/src/proj.def @@ -92,14 +92,20 @@ EXPORTS pj_error @90 pj_create @91 pj_create_argv @92 - pj_apply @93 + pj_trans @93 pj_fwdobs @94 pj_invobs @95 pj_roundtrip @96 - pj_debug_set @97 + 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 @@ -138,6 +138,8 @@ extern "C" { #include <proj_api.h> #undef PROJ_API_INCLUDED_FOR_PJ_VERSION_ONLY +extern char const pj_release[]; /* global release id string */ +extern int pj_errno; /* global error return code */ /* first forward declare everything needed */ @@ -189,13 +191,14 @@ typedef struct { double e, z, N; } PJ_EZN; /* Ellipsoidal parameters */ typedef struct { double a, f; } PJ_AF; +/* Avoid preprocessor renaming and implicit type-punning: Use unions to make it explicit */ union PJ_COORD { PJ_XYZT xyzt; PJ_UVWT uvwt; PJ_ENHT enht; PJ_LPZT lpzt; PJ_ENH enh; - double v[4]; /* Who cares - it's just a vector! */ + double v[4]; /* It's just a vector */ XYZ xyz; UVW uvw; LPZ lpz; @@ -204,28 +207,34 @@ union PJ_COORD { LP lp; }; - - -/* Avoid preprocessor renaming and implicit type-punning: Use a union to make it explicit */ union PJ_TRIPLET { PJ_OPK opk; PJ_ENH enh; PJ_EZN ezn; - PJ_AF af; - double v[3]; /* Who cares - it's just a vector! */ + PJ_DMS dms; + double v[3]; /* It's just a vector */ XYZ xyz; LPZ lpz; UVW uvw; XY xy; LP lp; UV uv; + PJ_AF af; +}; + +union PJ_PAIR { + XY xy; + LP lp; + UV uv; + PJ_AF af; + double v[2]; /* Yes - It's really just a vector! */ }; struct PJ_OBS { - PJ_COORD coo; /* coordinate data */ - PJ_TRIPLET anc; /* ancillary data */ - int id; /* integer ancillary data - e.g. observation number, EPSG code... */ - unsigned int flags; /* additional data, intended for flags */ + PJ_COORD coo; /* coordinate data */ + PJ_TRIPLET anc; /* ancillary data */ + int id; /* integer ancillary data - e.g. observation number, EPSG code... */ + unsigned int flags; /* additional data, intended for flags */ }; /* The context type - properly namespaced synonym for projCtx */ @@ -240,24 +249,6 @@ PJ *pj_create_argv (int argc, char **argv); void pj_free (PJ *P); int pj_error (PJ *P); -/* High level functionality for handling thread contexts */ -enum pj_debug_level { - PJ_LOG_NONE = 0, - PJ_LOG_ERROR = 1, - PJ_LOG_DEBUG_MAJOR = 2, - PJ_LOG_DEBUG_MINOR = 3 -}; -void pj_debug_set (PJ *P, enum pj_debug_level debuglevel); -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); -void pj_context_free (const PJ *P); - -/* Lowest level: Minimum support for fileapi */ -void pj_fileapi_set (PJ *P, void *fileapi); /* Apply transformation to observation - in forward or inverse direction */ enum pj_direction { @@ -265,17 +256,19 @@ enum pj_direction { PJ_IDENT = 0, /* Do nothing */ PJ_INV = -1 /* Inverse */ }; -PJ_OBS pj_apply (PJ *P, enum pj_direction direction, PJ_OBS obs); +PJ_OBS pj_trans (PJ *P, enum pj_direction direction, PJ_OBS obs); /* Measure internal consistency - in forward or inverse direction */ double pj_roundtrip (PJ *P, enum pj_direction direction, int n, PJ_OBS obs); -/* Euclidean distance between two 2D coordinates stored in PJ_OBSs */ -double pj_obs_dist_2d (PJ_OBS a, PJ_OBS b); +/* Geodesic distance between two points with angular 2D coordinates */ +double pj_lp_dist (PJ *P, LP a, LP b); -/* Euclidean distance between two 3D coordinates stored in PJ_OBSs */ -double pj_obs_dist_3d (PJ_OBS a, PJ_OBS b); +/* Euclidean distance between two points with linear 2D coordinates */ +double pj_xy_dist (XY a, XY b); +/* Euclidean distance between two points with linear 3D coordinates */ +double pj_xyz_dist (XYZ a, XYZ b); #ifndef PJ_OBS_C @@ -292,6 +285,42 @@ extern const PJ *pj_shutdown; #endif + + + + + +/* High level functionality for handling thread contexts */ +enum pj_log_level { + PJ_LOG_NONE = 0, + PJ_LOG_ERROR = 1, + PJ_LOG_DEBUG = 2, + PJ_LOG_TRACE = 3, + PJ_LOG_TELL = 4, + PJ_LOG_DEBUG_MAJOR = 2, /* for proj_api.h compatibility */ + PJ_LOG_DEBUG_MINOR = 3 /* for proj_api.h compatibility */ +}; + +/* 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_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); +void pj_context_free (const PJ *P); + +/* Lowest level: Minimum support for fileapi */ +void pj_fileapi_set (PJ *P, void *fileapi); + + #ifdef __cplusplus } #endif diff --git a/src/proj_api.h b/src/proj_api.h index 706e35ab..863795f0 100644 --- a/src/proj_api.h +++ b/src/proj_api.h @@ -64,11 +64,8 @@ extern "C" { /* Can be detected too at runtime if the symbol pj_atof exists */ #define PJ_LOCALE_SAFE 1 -extern char const pj_release[]; /* global release id string */ -extern int pj_errno; /* global error return code */ - -#define RAD_TO_DEG 57.295779513082321 -#define DEG_TO_RAD .017453292519943296 +#define RAD_TO_DEG 57.295779513082321 +#define DEG_TO_RAD .017453292519943296 #if defined(PROJECTS_H) || defined(PROJ_H) @@ -78,7 +75,10 @@ extern int pj_errno; /* global error return code */ #ifndef PROJ_H -/* In proj.h these macros are replaced by the enumeration pj_debug_level */ +extern char const pj_release[]; /* global release id string */ +extern int pj_errno; /* global error return code */ + +/* In proj.h these macros are replaced by the enumeration pj_log_level */ #define PJ_LOG_NONE 0 #define PJ_LOG_ERROR 1 #define PJ_LOG_DEBUG_MAJOR 2 @@ -90,7 +90,7 @@ extern int pj_errno; /* global error return code */ /* These make the function declarations below conform with classic proj */ typedef PJ *projPJ; /* projPJ is a pointer to PJ */ typedef projCtx_t *projCtx; /* projCtx is a pointer to projCtx_t */ -# define projXY XY +# define projXY XY # define projLP LP # define projXYZ XYZ # define projLPZ LPZ @@ -107,8 +107,6 @@ extern int pj_errno; /* global error return code */ #endif - - /* If included *after* proj.h finishes, we have alternative names */ /* file reading api, like stdio */ typedef int *PAFile; diff --git a/src/projects.h b/src/projects.h index d07c43c7..47a3364a 100644 --- a/src/projects.h +++ b/src/projects.h @@ -169,6 +169,7 @@ typedef struct { double u, v, w; } UVW; /* Forward declarations and typedefs for stuff needed inside the PJ object */ struct PJconsts; struct PJ_OBS; +struct geod_geodesic; struct pj_opaque; struct ARG_list; struct FACTORS; @@ -212,6 +213,7 @@ struct PJconsts { projCtx_t *ctx; const char *descr; /* From pj_list.h or individual PJ_*.c file */ paralist *params; /* Parameter list */ + struct geod_geodesic *geod; /* For geodesic computations */ struct pj_opaque *opaque; /* Projection specific parameters, Defined in PJ_*.c */ @@ -272,6 +274,7 @@ struct PJconsts { /* The eccentricities */ + double alpha; /* angular eccentricity */ double e; /* first eccentricity */ double es; /* first eccentricity squared */ double e2; /* second eccentricity */ |
