diff options
| -rw-r--r-- | docs/source/operations/conversions/index.rst | 2 | ||||
| -rw-r--r-- | docs/source/operations/conversions/pop.rst | 94 | ||||
| -rw-r--r-- | docs/source/operations/conversions/push.rst | 93 | ||||
| -rw-r--r-- | src/pipeline.cpp | 111 | ||||
| -rw-r--r-- | src/pj_list.h | 2 | ||||
| -rw-r--r-- | src/proj_internal.h | 1 | ||||
| -rw-r--r-- | test/gie/4D-API_cs2cs-style.gie | 89 |
7 files changed, 390 insertions, 2 deletions
diff --git a/docs/source/operations/conversions/index.rst b/docs/source/operations/conversions/index.rst index 6dfc0be3..a9d137ab 100644 --- a/docs/source/operations/conversions/index.rst +++ b/docs/source/operations/conversions/index.rst @@ -15,4 +15,6 @@ conversions. cart geoc latlon + pop + push unitconvert diff --git a/docs/source/operations/conversions/pop.rst b/docs/source/operations/conversions/pop.rst new file mode 100644 index 00000000..2c6d264a --- /dev/null +++ b/docs/source/operations/conversions/pop.rst @@ -0,0 +1,94 @@ +.. _pop: + +================================================================================ +Pop coordinate value to pipeline stack +================================================================================ + +.. versionadded:: 6.0.0 + +Retrieve components of a coordinate that was saved in a previous pipeline step. + ++---------------------+--------------------------------------------------------+ +| **Alias** | pop | ++---------------------+--------------------------------------------------------+ +| **Domain** | 4D | ++---------------------+--------------------------------------------------------+ +| **Input type** | Any | ++---------------------+--------------------------------------------------------+ +| **Output type** | Any | ++---------------------+--------------------------------------------------------+ + +This operations makes it possible to retrieve coordinate components that was +saved in previous pipeline steps. A retrieved coordinate component is loaded, +or *popped*, from a memory stack that is part of a :ref:`pipeline<pipeline>`. +The pipeline coordinate stack is inspired by the stack data structure that is +commonly used in computer science. There's four stacks available: One four each +coordinate dimension. The dimensions, or coordinate components, are numbered +1--4. It is only possible to move data to and from the stack within the same +coordinate component number. Values can be saved to the stack by using the +:ref:`push operation<push>`. + +If the pop operation is used by itself, e.g. not in a pipeline, it will +function as a no-operation that passes the coordinate through unchanged. +Similarly, if no coordinate component is available on the stack to be popped +the operation does nothing. + +Examples +################################################################################ + +A common use of the :ref:`push<push>` and pop operations is in 3D +:ref:`Helmert<helmert>` transformations where only the horizontal components +are needed. This is often the case when combining heights from a legacy +vertical reference with a modern geocentric reference. Below is a an example of +such a transformation, where the horizontal part is transformed with a Helmert +operation but the vertical part is kept exactly as the input was. + +:: + + $ echo 12 56 12.3 2020 | cct +proj=pipeline \ + +step +proj=push +v_3 \ + +step +proj=cart +ellps=GRS80 \ + +step +proj=helmert +x=3000 +y=1000 +z=2000 \ + +step +proj=cart +ellps=GRS80 +inv \ + +step +proj=pop +v_3 \ + + 12.0056753463 55.9866540552 12.3000 2000.0000 + +Note that the third coordinate component in the output is the same as the input. + +The same transformation without the push and pop operations would look like this:: + + $ echo 12 56 12.3 2020 | cct +proj=pipeline \ + +step +proj=cart +ellps=GRS80 \ + +step +proj=helmert +x=3000 +y=1000 +z=2000 \ + +step +proj=cart +ellps=GRS80 +inv \ + + 12.0057 55.9867 3427.7404 2000.0000 + +Here the vertical component is adjusted significantly. + +Parameters +################################################################################ + +.. option:: +v_1 + + Retrieves the first coordinate component from the pipeline stack + +.. option:: +v_2 + + Retrieves the second coordinate component from the pipeline stack + +.. option:: +v_3 + + Retrieves the third coordinate component from the pipeline stack + +.. option:: +v_4 + + Retrieves the fourth coordinate component from the pipeline stack + + +Further reading +################################################################################ + +#. `Stack data structure on Wikipedia <https://en.wikipedia.org/wiki/Stack_(abstract_data_type)>`_ + diff --git a/docs/source/operations/conversions/push.rst b/docs/source/operations/conversions/push.rst new file mode 100644 index 00000000..a1f4423a --- /dev/null +++ b/docs/source/operations/conversions/push.rst @@ -0,0 +1,93 @@ +.. _push: + +================================================================================ +Push coordinate value to pipeline stack +================================================================================ + +.. versionadded:: 6.0.0 + +Save components of a coordinate from one step of a pipeline and make it +available for retrieving in another pipeline step. + ++---------------------+--------------------------------------------------------+ +| **Alias** | push | ++---------------------+--------------------------------------------------------+ +| **Domain** | 4D | ++---------------------+--------------------------------------------------------+ +| **Input type** | Any | ++---------------------+--------------------------------------------------------+ +| **Output type** | Any | ++---------------------+--------------------------------------------------------+ + +This operations allows for components of coordinates to be saved for +application in a later step. A saved coordinate component is moved, or +*pushed*, to a memory stack that is part of a :ref:`pipeline<pipeline>`. The +pipeline coordinate stack is inspired by the stack data structure that is +commonly used in computer science. There's four stacks available: One four each +coordinate dimension. The dimensions, or coordinate components, are numbered +1--4. It is only possible to move data to and from the stack within the same +coordinate component number. Values can be moved off the stack again by using +the :ref:`pop operation<pop>`. + +If the push operation is used by itself, e.g. not in a pipeline, it will +function as a no-operation that passes the coordinate through unchanged. + +Examples +################################################################################ + +A common use of the push and :ref:`pop<pop>` operations is in 3D +:ref:`Helmert<helmert>` transformations where only the horizontal components +are needed. This is often the case when combining heights from a legacy +vertical reference with a modern geocentric reference. Below is a an example of +such a transformation, where the horizontal part is transformed with a Helmert +operation but the vertical part is kept exactly as the input was. + +:: + + $ echo 12 56 12.3 2020 | cct +proj=pipeline \ + +step +proj=push +v_3 \ + +step +proj=cart +ellps=GRS80 \ + +step +proj=helmert +x=3000 +y=1000 +z=2000 \ + +step +proj=cart +ellps=GRS80 +inv \ + +step +proj=pop +v_3 \ + + 12.0056753463 55.9866540552 12.3000 2000.0000 + +Note that the third coordinate component in the output is the same as the input. + +The same transformation without the push and pop operations would look like this:: + + $ echo 12 56 12.3 2020 | cct +proj=pipeline \ + +step +proj=cart +ellps=GRS80 \ + +step +proj=helmert +x=3000 +y=1000 +z=2000 \ + +step +proj=cart +ellps=GRS80 +inv \ + + 12.0057 55.9867 3427.7404 2000.0000 + +Here the vertical component is adjusted significantly. + +Parameters +################################################################################ + +.. option:: +v_1 + + Stores the first coordinate component on the pipeline stack + +.. option:: +v_2 + + Stores the second coordinate component on the pipeline stack + +.. option:: +v_3 + + Stores the third coordinate component on the pipeline stack + +.. option:: +v_4 + + Stores the fourth coordinate component on the pipeline stack + + +Further reading +################################################################################ + +#. `Stack data structure on Wikipedia <https://en.wikipedia.org/wiki/Stack_(abstract_data_type)>`_ + diff --git a/src/pipeline.cpp b/src/pipeline.cpp index c4a9d2c0..39563c65 100644 --- a/src/pipeline.cpp +++ b/src/pipeline.cpp @@ -100,6 +100,7 @@ Thomas Knudsen, thokn@sdfe.dk, 2016-05-20 #include <math.h> #include <stddef.h> #include <string.h> +#include <stack> #include "geodesic.h" #include "proj.h" @@ -107,6 +108,8 @@ Thomas Knudsen, thokn@sdfe.dk, 2016-05-20 #include "proj_internal.h" PROJ_HEAD(pipeline, "Transformation pipeline manager"); +PROJ_HEAD(pop, "Retrieve coordinate value from pipeline stack"); +PROJ_HEAD(push, "Save coordinate value on pipeline stack"); /* Projection specific elements for the PJ object */ namespace { // anonymous namespace @@ -115,9 +118,16 @@ struct pj_opaque { char **argv; char **current_argv; PJ **pipeline; + std::stack<double> *stack[4]; }; -} // anonymous namespace +struct pj_opaque_pushpop { + bool v1; + bool v2; + bool v3; + bool v4; +}; +} // anonymous namespace static PJ_COORD pipeline_forward_4d (PJ_COORD point, PJ *P); @@ -217,7 +227,7 @@ static PJ *destructor (PJ *P, int errlev) { if (nullptr==P->opaque) return pj_default_destructor (P, errlev); - /* Deallocate each pipeine step, then pipeline array */ + /* Deallocate each pipeline step, then pipeline array */ if (nullptr!=static_cast<struct pj_opaque*>(P->opaque)->pipeline) for (i = 0; i < static_cast<struct pj_opaque*>(P->opaque)->steps; i++) proj_destroy (static_cast<struct pj_opaque*>(P->opaque)->pipeline[i+1]); @@ -226,6 +236,9 @@ static PJ *destructor (PJ *P, int errlev) { pj_dealloc (static_cast<struct pj_opaque*>(P->opaque)->argv); pj_dealloc (static_cast<struct pj_opaque*>(P->opaque)->current_argv); + for (i=0; i<4; i++) + delete static_cast<struct pj_opaque*>(P->opaque)->stack[i]; + return pj_default_destructor(P, errlev); } @@ -384,6 +397,10 @@ PJ *OPERATION(pipeline,0) { if (nullptr==P->opaque) return destructor(P, ENOMEM); + /* initialize stack */ + for (i=0; i<4; i++) + static_cast<struct pj_opaque*>(P->opaque)->stack[i] = new std::stack<double>; + argc = (int)argc_params (P->params); static_cast<struct pj_opaque*>(P->opaque)->argv = argv = argv_params (P->params, argc); if (nullptr==argv) @@ -467,6 +484,7 @@ PJ *OPERATION(pipeline,0) { proj_log_error (P, "Pipeline: Bad step definition: %s (%s)", current_argv[0], pj_strerrno (err_to_report)); return destructor (P, err_to_report); /* ERROR: bad pipeline def */ } + next_step->parent = P; proj_errno_restore (P, err); @@ -551,3 +569,92 @@ PJ *OPERATION(pipeline,0) { P->right = pj_right (static_cast<struct pj_opaque*>(P->opaque)->pipeline[nsteps]); return P; } + +static PJ_COORD push(PJ_COORD point, PJ *P) { + if (P->parent == nullptr) + return point; + + struct pj_opaque *pipeline = static_cast<struct pj_opaque*>(P->parent->opaque); + struct pj_opaque_pushpop *opaque = static_cast<struct pj_opaque_pushpop*>(P->opaque); + + if (opaque->v1) + pipeline->stack[0]->push(point.v[0]); + if (opaque->v2) + pipeline->stack[1]->push(point.v[1]); + if (opaque->v3) + pipeline->stack[2]->push(point.v[2]); + if (opaque->v4) + pipeline->stack[3]->push(point.v[3]); + + return point; +} + +static PJ_COORD pop(PJ_COORD point, PJ *P) { + if (P->parent == nullptr) + return point; + + struct pj_opaque *pipeline = static_cast<struct pj_opaque*>(P->parent->opaque); + struct pj_opaque_pushpop *opaque = static_cast<struct pj_opaque_pushpop*>(P->opaque); + + if (opaque->v1 && !pipeline->stack[0]->empty()) { + point.v[0] = pipeline->stack[0]->top(); + pipeline->stack[0]->pop(); + } + + if (opaque->v2 && !pipeline->stack[1]->empty()) { + point.v[1] = pipeline->stack[1]->top(); + pipeline->stack[1]->pop(); + } + + if (opaque->v3 && !pipeline->stack[2]->empty()) { + point.v[2] = pipeline->stack[2]->top(); + pipeline->stack[2]->pop(); + } + + if (opaque->v4 && !pipeline->stack[3]->empty()) { + point.v[3] = pipeline->stack[3]->top(); + pipeline->stack[3]->pop(); + } + + return point; +} + + + +static PJ *setup_pushpop(PJ *P) { + P->opaque = static_cast<struct pj_opaque_pushpop*>(pj_calloc (1, sizeof(struct pj_opaque_pushpop))); + if (nullptr==P->opaque) + return destructor(P, ENOMEM); + + if (pj_param_exists(P->params, "v_1")) + static_cast<struct pj_opaque_pushpop*>(P->opaque)->v1 = true; + + if (pj_param_exists(P->params, "v_2")) + static_cast<struct pj_opaque_pushpop*>(P->opaque)->v2 = true; + + if (pj_param_exists(P->params, "v_3")) + static_cast<struct pj_opaque_pushpop*>(P->opaque)->v3 = true; + + if (pj_param_exists(P->params, "v_4")) + static_cast<struct pj_opaque_pushpop*>(P->opaque)->v4 = true; + + P->left = PJ_IO_UNITS_WHATEVER; + P->right = PJ_IO_UNITS_WHATEVER; + + return P; +} + + +PJ *OPERATION(push, 0) { + P->fwd4d = push; + P->inv4d = pop; + + return setup_pushpop(P); +} + +PJ *OPERATION(pop, 0) { + P->inv4d = push; + P->fwd4d = pop; + + return setup_pushpop(P); +} diff --git a/src/pj_list.h b/src/pj_list.h index 3592dcc4..8ab4cdc0 100644 --- a/src/pj_list.h +++ b/src/pj_list.h @@ -116,6 +116,8 @@ PROJ_HEAD(pconic, "Perspective Conic") PROJ_HEAD(patterson, "Patterson Cylindrical") PROJ_HEAD(pipeline, "Transformation pipeline manager") PROJ_HEAD(poly, "Polyconic (American)") +PROJ_HEAD(pop, "Retrieve coordinate value from pipeline stack") +PROJ_HEAD(push, "Save coordinate value on pipeline stack") PROJ_HEAD(putp1, "Putnins P1") PROJ_HEAD(putp2, "Putnins P2") PROJ_HEAD(putp3, "Putnins P3") diff --git a/src/proj_internal.h b/src/proj_internal.h index 9ffcc2b3..1f86f93e 100644 --- a/src/proj_internal.h +++ b/src/proj_internal.h @@ -321,6 +321,7 @@ struct PJconsts { const char *descr = nullptr; /* From pj_list.h or individual PJ_*.c file */ paralist *params = nullptr; /* Parameter list */ char *def_full = nullptr; /* Full textual definition (usually 0 - set by proj_pj_info) */ + PJconsts *parent = nullptr; /* Parent PJ of pipeline steps - nullptr if not a pipeline step */ /* For debugging / logging purposes */ char *def_size = nullptr; /* Shape and size parameters extracted from params */ diff --git a/test/gie/4D-API_cs2cs-style.gie b/test/gie/4D-API_cs2cs-style.gie index 215971a0..bcdc256f 100644 --- a/test/gie/4D-API_cs2cs-style.gie +++ b/test/gie/4D-API_cs2cs-style.gie @@ -294,6 +294,95 @@ accept 12 56 expect 1335.8339 7522.963 ------------------------------------------------------------------------------- +------------------------------------------------------------------------------- +Test Pipeline Coordinate Stack +------------------------------------------------------------------------------- +operation +proj=pipeline + +step +proj=push +v_1 + +step +proj=utm +zone=32 + +step +proj=utm +zone=33 +inv + +step +proj=pop +v_1 + +accept 12 56 0 2020 +expect 12 56 0 2020 +roundtrip 10 + +#operation +proj=pipeline + +step +proj=latlon # dummy step + +step +proj=push +v_1 + +step +proj=utm +zone=32 + +step +proj=utm +zone=33 +inv + +step +proj=pop +v_1 + +step +proj=affine # dummy step + +accept 12 56 0 2020 +expect 12 56 0 2020 +roundtrip 10 + +# push value to stack without popping it again +operation +proj=pipeline + +step +proj=push +v_1 + +step +proj=utm +zone=32 + +step +proj=utm +zone=33 +inv + +accept 12 56 0 2020 +expect 18 56 0 2020 + +# test that multiple pushes and pops works +operation +proj=pipeline + +step +proj=push +v_1 + +step +proj=utm +zone=32 + +step +proj=push +v_1 + +step +proj=utm +zone=33 +inv + +step +proj=utm +zone=34 + +step +proj=pop +v_1 + +step +proj=utm +zone=32 +inv + +step +proj=pop +v_1 + +accept 12 56 0 2020 +expect 12 56 0 2020 + +# pop from empty stack +operation +proj=pipeline + +step +proj=utm +zone=32 + +step +proj=utm +zone=33 +inv + +step +proj=pop +v_1 + +accept 12 56 0 2020 +expect 18 56 0 2020 + +operation +proj=pipeline + +step +proj=push +v_2 + +step +inv +proj=eqearth + +step +proj=laea + +step +proj=pop +v_2 + +accept 900000 6000000 0 2020 +expect 896633.0226 6000000 0 2020 + +# Datum shift in cartesian space but keeping the height +# (simulates a datum-shift with affin since ISO19111 code +# currrently obfuscates proj-strings using cart/helmert/invcart) +operation +proj=pipeline +ellps=GRS80 + +step +proj=push +v_3 + +step +proj=cart + +step +proj=affine +xoff=1000 +yoff=2000 +xoff=3000 + +step +proj=cart +inv + +step +proj=pop +v_3 +tolerance 50 cm + +accept 12 56 0 +expect 12.0280112877 55.9896187413 0 +roundtrip 1 + +operation +proj=push +v_3 +accept 12 56 0 0 +expect 12 56 0 0 + +operation +proj=pop +v_3 +accept 12 56 0 0 +expect 12 56 0 0 + ------------------------------------------------------------------------------- Test bugfix of https://github.com/OSGeo/proj.4/issues/1002 |
