aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/source/operations/conversions/index.rst2
-rw-r--r--docs/source/operations/conversions/pop.rst94
-rw-r--r--docs/source/operations/conversions/push.rst93
-rw-r--r--src/pipeline.cpp111
-rw-r--r--src/pj_list.h2
-rw-r--r--src/proj_internal.h1
-rw-r--r--test/gie/4D-API_cs2cs-style.gie89
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