aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/source/operations/operations_computation.rst93
-rw-r--r--src/iso19111/coordinateoperation.cpp196
-rw-r--r--src/malloc.cpp2
-rw-r--r--src/proj_internal.h3
-rw-r--r--src/transformations/hgridshift.cpp51
-rw-r--r--src/transformations/vgridshift.cpp53
-rw-r--r--test/unit/test_network.cpp7
-rw-r--r--test/unit/test_operation.cpp79
8 files changed, 368 insertions, 116 deletions
diff --git a/docs/source/operations/operations_computation.rst b/docs/source/operations/operations_computation.rst
index 2f532127..2836b75c 100644
--- a/docs/source/operations/operations_computation.rst
+++ b/docs/source/operations/operations_computation.rst
@@ -657,46 +657,73 @@ CompoundCRS to CompoundCRS
---------------------------------------------------------------------------------
There is some similarity with the previous paragraph. We first research the
-vertical transformations between the vertical CRS. If such tranformation has
-a registered interpolation geographic CRS, then it is used. Otherwise we fallback
-to the geographic CRS of the source CRS.
+vertical transformations between the two vertical CRS.
-Finally, a 3-level loop to create the final set of operations chaining together:
+1. If there is such a tranformation, be it direct, or if both vertical CRS
+ relate to a common intermediate CRS.
+ If it has a registered interpolation geographic CRS, then it is used.
+ Otherwise we fallback to the geographic CRS of the source CRS.
-- the horizontal transformation from the source CRS to the interpolation CRS
-- the vertical transformation
-- the horizontal transformation from the interpolation CRS to the target CRS.
+ Finally, a 3-level loop to create the final set of operations chaining together:
-This is implemented by the ``createOperationsCompoundToGeog`` method
+ - the horizontal transformation from the source CRS to the interpolation CRS
+ - the vertical transformation
+ - the horizontal transformation from the interpolation CRS to the target CRS.
-Example:
+ Example:
-.. code-block:: shell
+ .. code-block:: shell
+
+ $ projinfo -s "NAD27 + NGVD29 height (ftUS)" -t "NAD83 + NAVD88 height" --spatial-test intersects --summary
+
+ Candidate operations found: 20
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS east of 89°W - onshore
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS 89°W-107°W - onshore
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS west of 107°W - onshore
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (5), 1.02 m, unknown domain of validity, at least one grid missing
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (6), 1.52 m, unknown domain of validity, at least one grid missing
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (9), 1.52 m, unknown domain of validity, at least one grid missing
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (9), 1.52 m, unknown domain of validity, at least one grid missing
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS east of 89°W - onshore, has ballpark transformation
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS 89°W-107°W - onshore, has ballpark transformation
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS west of 107°W - onshore, has ballpark transformation
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (1), unknown accuracy, USA - CONUS including EEZ, has ballpark transformation
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (3), unknown accuracy, Canada, has ballpark transformation
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (4), unknown accuracy, Canada - NAD27, has ballpark transformation
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (5), unknown accuracy, Canada - Quebec, has ballpark transformation, at least one grid missing
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (6), unknown accuracy, Canada - Quebec, has ballpark transformation, at least one grid missing
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (9), unknown accuracy, Canada - Saskatchewan, has ballpark transformation, at least one grid missing
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, World, has ballpark transformation
+
+
+2. Otherwise, when there is no such transformation, we decompose into 3 steps:
+
+ - transform from the source CRS to the geographic 3D CRS corresponding to it
+ - transform from the geographic 3D CRS corresponding to the source CRS to the
+ geographic 3D CRS corresponding to the target CRS
+ - transform from the geographic 3D CRS corresponding to the target CRS to the
+ target CRS.
+
+ Example:
+
+ .. code-block:: shell
+
+ $ projinfo -s "WGS 84 + EGM96 height" -t "ETRS89 + Belfast height" --spatial-test intersects --summary
+
+ Candidate operations found: 7
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Inverse of ETRS89 to WGS 84 (1) + ETRS89 to Belfast height (2), 2.014 m, UK - Northern Ireland - onshore
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Inverse of ETRS89 to WGS 84 (1) + ETRS89 to Belfast height (1), 2.03 m, UK - Northern Ireland - onshore, at least one grid missing
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Null geographic offset from WGS 84 (geog3D) to WGS 84 (geog2D) + Inverse of OSGB 1936 to WGS 84 (4) + OSGB 1936 to ETRS89 (2) + Null geographic offset from ETRS89 (geog2D) to ETRS89 (geog3D) + ETRS89 to Belfast height (2), 19.044 m, unknown domain of validity
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Null geographic offset from WGS 84 (geog3D) to WGS 84 (geog2D) + Inverse of OSGB 1936 to WGS 84 (2) + OSGB 1936 to ETRS89 (2) + Null geographic offset from ETRS89 (geog2D) to ETRS89 (geog3D) + ETRS89 to Belfast height (2), 11.044 m, unknown domain of validity
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Null geographic offset from WGS 84 (geog3D) to WGS 84 (geog2D) + Inverse of TM75 to WGS 84 (2) + TM75 to ETRS89 (3) + Null geographic offset from ETRS89 (geog2D) to ETRS89 (geog3D) + ETRS89 to Belfast height (2), 2.424 m, UK - Northern Ireland - onshore, at least one grid missing
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Null geographic offset from WGS 84 (geog3D) to WGS 84 (geog2D) + Inverse of TM75 to WGS 84 (2) + TM75 to ETRS89 (3) + Null geographic offset from ETRS89 (geog2D) to ETRS89 (geog3D) + ETRS89 to Belfast height (1), 2.44 m, UK - Northern Ireland - onshore, at least one grid missing
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Null geographic offset from WGS 84 (geog3D) to WGS 84 (geog2D) + Inverse of OSGB 1936 to WGS 84 (4) + OSGB 1936 to ETRS89 (2) + Null geographic offset from ETRS89 (geog2D) to ETRS89 (geog3D) + ETRS89 to Belfast height (1), 19.06 m, unknown domain of validity, at least one grid missing
- $ projinfo -s "NAD27 + NGVD29 height (ftUS)" -t "NAD83 + NAVD88 height" --spatial-test intersects --summary
-
- Candidate operations found: 20
- unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS east of 89°W - onshore
- unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS 89°W-107°W - onshore
- unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS west of 107°W - onshore
- unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
- unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
- unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
- unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (5), 1.02 m, unknown domain of validity, at least one grid missing
- unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (6), 1.52 m, unknown domain of validity, at least one grid missing
- unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (9), 1.52 m, unknown domain of validity, at least one grid missing
- unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (9), 1.52 m, unknown domain of validity, at least one grid missing
- unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS east of 89°W - onshore, has ballpark transformation
- unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS 89°W-107°W - onshore, has ballpark transformation
- unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS west of 107°W - onshore, has ballpark transformation
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (1), unknown accuracy, USA - CONUS including EEZ, has ballpark transformation
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (3), unknown accuracy, Canada, has ballpark transformation
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (4), unknown accuracy, Canada - NAD27, has ballpark transformation
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (5), unknown accuracy, Canada - Quebec, has ballpark transformation, at least one grid missing
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (6), unknown accuracy, Canada - Quebec, has ballpark transformation, at least one grid missing
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (9), unknown accuracy, Canada - Saskatchewan, has ballpark transformation, at least one grid missing
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, World, has ballpark transformation
+This is implemented by the ``createOperationsCompoundToCompound`` method
When the source or target CRS is a BoundCRS
---------------------------------------------------------------------------------
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index 267e9450..822d09ac 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -14754,84 +14754,144 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToCompound(
const auto &componentsSrc = compoundSrc->componentReferenceSystems();
const auto &componentsDst = compoundDst->componentReferenceSystems();
- if (!componentsSrc.empty() &&
- componentsSrc.size() == componentsDst.size()) {
- if (componentsSrc[0]->extractGeographicCRS() &&
- componentsDst[0]->extractGeographicCRS()) {
-
- std::vector<CoordinateOperationNNPtr> verticalTransforms;
- if (componentsSrc.size() >= 2 &&
- componentsSrc[1]->extractVerticalCRS() &&
- componentsDst[1]->extractVerticalCRS()) {
- if (!componentsSrc[1]->_isEquivalentTo(
- componentsDst[1].get())) {
- verticalTransforms = createOperations(
- componentsSrc[1], componentsDst[1], context);
- }
- }
+ if (componentsSrc.empty() || componentsSrc.size() != componentsDst.size()) {
+ return;
+ }
+ const auto srcGeog = componentsSrc[0]->extractGeographicCRS();
+ const auto dstGeog = componentsDst[0]->extractGeographicCRS();
+ if (srcGeog == nullptr || dstGeog == nullptr) {
+ return;
+ }
- for (const auto &verticalTransform : verticalTransforms) {
- auto interpolationGeogCRS =
- NN_NO_CHECK(componentsSrc[0]->extractGeographicCRS());
- auto transformationVerticalTransform =
- dynamic_cast<const Transformation *>(
- verticalTransform.get());
- if (transformationVerticalTransform) {
- auto interpTransformCRS =
- transformationVerticalTransform->interpolationCRS();
- if (interpTransformCRS) {
- auto nn_interpTransformCRS =
- NN_NO_CHECK(interpTransformCRS);
- if (dynamic_cast<const crs::GeographicCRS *>(
- nn_interpTransformCRS.get())) {
- interpolationGeogCRS = NN_NO_CHECK(
- util::nn_dynamic_pointer_cast<
- crs::GeographicCRS>(nn_interpTransformCRS));
- }
- }
- } else {
- auto compSrc0BoundCrs =
- dynamic_cast<crs::BoundCRS *>(componentsSrc[0].get());
- auto compDst0BoundCrs =
- dynamic_cast<crs::BoundCRS *>(componentsDst[0].get());
- if (compSrc0BoundCrs && compDst0BoundCrs &&
- dynamic_cast<crs::GeographicCRS *>(
- compSrc0BoundCrs->hubCRS().get()) &&
- compSrc0BoundCrs->hubCRS()->_isEquivalentTo(
- compDst0BoundCrs->hubCRS().get())) {
- interpolationGeogCRS = NN_NO_CHECK(
- util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
- compSrc0BoundCrs->hubCRS()));
- }
+ std::vector<CoordinateOperationNNPtr> verticalTransforms;
+ if (componentsSrc.size() >= 2 && componentsSrc[1]->extractVerticalCRS() &&
+ componentsDst[1]->extractVerticalCRS()) {
+ if (!componentsSrc[1]->_isEquivalentTo(componentsDst[1].get())) {
+ verticalTransforms =
+ createOperations(componentsSrc[1], componentsDst[1], context);
+ }
+ }
+
+ // If we didn't find a non-ballbark transformation between
+ // the 2 vertical CRS, then try through intermediate geographic CRS
+ // For example
+ // WGS 84 + EGM96 --> ETRS89 + Belfast height where
+ // there is a geoid model for EGM96 referenced to WGS 84
+ // and a geoid model for Belfast height referenced to ETRS89
+ if (verticalTransforms.size() == 1 &&
+ verticalTransforms.front()->hasBallparkTransformation()) {
+ auto dbContext =
+ context.context->getAuthorityFactory()->databaseContext();
+ const auto intermGeogSrc =
+ srcGeog->promoteTo3D(std::string(), dbContext);
+ const bool intermGeogSrcIsSameAsIntermGeogDst =
+ srcGeog->_isEquivalentTo(dstGeog.get());
+ const auto intermGeogDst =
+ intermGeogSrcIsSameAsIntermGeogDst
+ ? intermGeogSrc
+ : dstGeog->promoteTo3D(std::string(), dbContext);
+ const auto opsSrcToGeog =
+ createOperations(sourceCRS, intermGeogSrc, context);
+ const auto opsGeogToTarget =
+ createOperations(intermGeogDst, targetCRS, context);
+ const bool hasNonTrivalSrcTransf =
+ !opsSrcToGeog.empty() &&
+ !opsSrcToGeog.front()->hasBallparkTransformation();
+ const bool hasNonTrivialTargetTransf =
+ !opsGeogToTarget.empty() &&
+ !opsGeogToTarget.front()->hasBallparkTransformation();
+ if (hasNonTrivalSrcTransf && hasNonTrivialTargetTransf) {
+ const auto opsGeogSrcToGeogDst =
+ createOperations(intermGeogSrc, intermGeogDst, context);
+ for (const auto &op1 : opsSrcToGeog) {
+ if (op1->hasBallparkTransformation()) {
+ continue;
}
- auto opSrcCRSToGeogCRS = createOperations(
- componentsSrc[0], interpolationGeogCRS, context);
- auto opGeogCRStoDstCRS = createOperations(
- interpolationGeogCRS, componentsDst[0], context);
- for (const auto &opSrc : opSrcCRSToGeogCRS) {
- for (const auto &opDst : opGeogCRStoDstCRS) {
-
+ for (const auto &op2 : opsGeogSrcToGeogDst) {
+ for (const auto &op3 : opsGeogToTarget) {
+ if (op3->hasBallparkTransformation()) {
+ continue;
+ }
try {
- auto op = createHorizVerticalHorizPROJBased(
- sourceCRS, targetCRS, opSrc, verticalTransform,
- opDst, interpolationGeogCRS, true);
- res.emplace_back(op);
- } catch (const InvalidOperationEmptyIntersection &) {
- } catch (const io::FormattingException &) {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ intermGeogSrcIsSameAsIntermGeogDst
+ ? std::vector<
+ CoordinateOperationNNPtr>{op1,
+ op3}
+ : std::vector<
+ CoordinateOperationNNPtr>{op1,
+ op2,
+ op3},
+ !allowEmptyIntersection));
+ } catch (const std::exception &) {
}
}
}
}
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
- if (verticalTransforms.empty()) {
- auto resTmp = createOperations(componentsSrc[0],
- componentsDst[0], context);
- for (const auto &op : resTmp) {
- auto opClone = op->shallowClone();
- setCRSs(opClone.get(), sourceCRS, targetCRS);
- res.emplace_back(opClone);
+ for (const auto &verticalTransform : verticalTransforms) {
+ auto interpolationGeogCRS = NN_NO_CHECK(srcGeog);
+ auto transformationVerticalTransform =
+ dynamic_cast<const Transformation *>(verticalTransform.get());
+ if (transformationVerticalTransform) {
+ auto interpTransformCRS =
+ transformationVerticalTransform->interpolationCRS();
+ if (interpTransformCRS) {
+ auto nn_interpTransformCRS = NN_NO_CHECK(interpTransformCRS);
+ if (dynamic_cast<const crs::GeographicCRS *>(
+ nn_interpTransformCRS.get())) {
+ interpolationGeogCRS = NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ nn_interpTransformCRS));
}
}
+ } else {
+ auto compSrc0BoundCrs =
+ dynamic_cast<crs::BoundCRS *>(componentsSrc[0].get());
+ auto compDst0BoundCrs =
+ dynamic_cast<crs::BoundCRS *>(componentsDst[0].get());
+ if (compSrc0BoundCrs && compDst0BoundCrs &&
+ dynamic_cast<crs::GeographicCRS *>(
+ compSrc0BoundCrs->hubCRS().get()) &&
+ compSrc0BoundCrs->hubCRS()->_isEquivalentTo(
+ compDst0BoundCrs->hubCRS().get())) {
+ interpolationGeogCRS = NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ compSrc0BoundCrs->hubCRS()));
+ }
+ }
+ auto opSrcCRSToGeogCRS =
+ createOperations(componentsSrc[0], interpolationGeogCRS, context);
+ auto opGeogCRStoDstCRS =
+ createOperations(interpolationGeogCRS, componentsDst[0], context);
+ for (const auto &opSrc : opSrcCRSToGeogCRS) {
+ for (const auto &opDst : opGeogCRStoDstCRS) {
+
+ try {
+ auto op = createHorizVerticalHorizPROJBased(
+ sourceCRS, targetCRS, opSrc, verticalTransform, opDst,
+ interpolationGeogCRS, true);
+ res.emplace_back(op);
+ } catch (const InvalidOperationEmptyIntersection &) {
+ } catch (const io::FormattingException &) {
+ }
+ }
+ }
+ }
+
+ if (verticalTransforms.empty()) {
+ auto resTmp =
+ createOperations(componentsSrc[0], componentsDst[0], context);
+ for (const auto &op : resTmp) {
+ auto opClone = op->shallowClone();
+ setCRSs(opClone.get(), sourceCRS, targetCRS);
+ res.emplace_back(opClone);
}
}
}
diff --git a/src/malloc.cpp b/src/malloc.cpp
index 9ac28546..10ab2fad 100644
--- a/src/malloc.cpp
+++ b/src/malloc.cpp
@@ -264,4 +264,6 @@ void proj_cleanup() {
pj_clear_initcache();
pj_deallocate_grids();
FileManager::clearMemoryCache();
+ pj_clear_hgridshift_knowngrids_cache();
+ pj_clear_vgridshift_knowngrids_cache();
}
diff --git a/src/proj_internal.h b/src/proj_internal.h
index 4dc63e1d..a7c1ddc8 100644
--- a/src/proj_internal.h
+++ b/src/proj_internal.h
@@ -890,6 +890,9 @@ int pj_get_suggested_operation(PJ_CONTEXT *ctx,
const PJ_UNITS *pj_list_linear_units();
const PJ_UNITS *pj_list_angular_units();
+void pj_clear_hgridshift_knowngrids_cache();
+void pj_clear_vgridshift_knowngrids_cache();
+
/* classic public API */
#include "proj_api.h"
diff --git a/src/transformations/hgridshift.cpp b/src/transformations/hgridshift.cpp
index 122a7ab2..b28eaf48 100644
--- a/src/transformations/hgridshift.cpp
+++ b/src/transformations/hgridshift.cpp
@@ -12,6 +12,25 @@ PROJ_HEAD(hgridshift, "Horizontal grid shift");
using namespace NS_PROJ;
+#ifdef __MINGW32__
+// mingw32-win32 doesn't implement std::mutex
+namespace {
+class MyMutex {
+ public:
+ // cppcheck-suppress functionStatic
+ void lock() { pj_acquire_lock(); }
+ // cppcheck-suppress functionStatic
+ void unlock() { pj_release_lock(); }
+};
+}
+#else
+#include <mutex>
+#define MyMutex std::mutex
+#endif
+
+static MyMutex gMutex{};
+static std::set<std::string> gKnownGrids{};
+
namespace { // anonymous namespace
struct hgridshiftData {
double t_final = 0;
@@ -160,18 +179,36 @@ PJ *TRANSFORMATION(hgridshift,0) {
if (pj_param(P->ctx, P->params, "tt_epoch").i)
Q->t_epoch = pj_param (P->ctx, P->params, "dt_epoch").f;
-
if( P->ctx->defer_grid_opening ) {
Q->defer_grid_opening = true;
}
else {
- Q->grids = pj_hgrid_init(P, "grids");
- /* Was gridlist compiled properly? */
- if ( proj_errno(P) ) {
- proj_log_error(P, "hgridshift: could not find required grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ const char *gridnames = pj_param(P->ctx, P->params, "sgrids").s;
+ gMutex.lock();
+ const bool isKnownGrid = gKnownGrids.find(gridnames) != gKnownGrids.end();
+ gMutex.unlock();
+ if( isKnownGrid ) {
+ Q->defer_grid_opening = true;
}
- }
+ else {
+ Q->grids = pj_hgrid_init(P, "grids");
+ /* Was gridlist compiled properly? */
+ if ( proj_errno(P) ) {
+ proj_log_error(P, "hgridshift: could not find required grid(s).");
+ return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
+
+ gMutex.lock();
+ gKnownGrids.insert(gridnames);
+ gMutex.unlock();
+ }
+ }
return P;
}
+
+void pj_clear_hgridshift_knowngrids_cache() {
+ gMutex.lock();
+ gKnownGrids.clear();
+ gMutex.unlock();
+}
diff --git a/src/transformations/vgridshift.cpp b/src/transformations/vgridshift.cpp
index 121b795a..6ee8a6b0 100644
--- a/src/transformations/vgridshift.cpp
+++ b/src/transformations/vgridshift.cpp
@@ -12,6 +12,27 @@ PROJ_HEAD(vgridshift, "Vertical grid shift");
using namespace NS_PROJ;
+
+#ifdef __MINGW32__
+// mingw32-win32 doesn't implement std::mutex
+namespace {
+class MyMutex {
+ public:
+ // cppcheck-suppress functionStatic
+ void lock() { pj_acquire_lock(); }
+ // cppcheck-suppress functionStatic
+ void unlock() { pj_release_lock(); }
+};
+}
+#else
+#include <mutex>
+#define MyMutex std::mutex
+#endif
+
+static MyMutex gMutex{};
+static std::set<std::string> gKnownGrids{};
+
+
namespace { // anonymous namespace
struct vgridshiftData {
double t_final = 0;
@@ -192,13 +213,27 @@ PJ *TRANSFORMATION(vgridshift,0) {
Q->defer_grid_opening = true;
}
else {
- /* Build gridlist. P->vgridlist_geoid can be empty if +grids only ask for optional grids. */
- Q->grids = pj_vgrid_init(P, "grids");
+ const char *gridnames = pj_param(P->ctx, P->params, "sgrids").s;
+ gMutex.lock();
+ const bool isKnownGrid = gKnownGrids.find(gridnames) != gKnownGrids.end();
+ gMutex.unlock();
- /* Was gridlist compiled properly? */
- if ( proj_errno(P) ) {
- proj_log_error(P, "vgridshift: could not find required grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ if( isKnownGrid ) {
+ Q->defer_grid_opening = true;
+ }
+ else {
+ /* Build gridlist. P->vgridlist_geoid can be empty if +grids only ask for optional grids. */
+ Q->grids = pj_vgrid_init(P, "grids");
+
+ /* Was gridlist compiled properly? */
+ if ( proj_errno(P) ) {
+ proj_log_error(P, "vgridshift: could not find required grid(s).");
+ return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
+
+ gMutex.lock();
+ gKnownGrids.insert(gridnames);
+ gMutex.unlock();
}
}
@@ -214,3 +249,9 @@ PJ *TRANSFORMATION(vgridshift,0) {
return P;
}
+
+void pj_clear_vgridshift_knowngrids_cache() {
+ gMutex.lock();
+ gKnownGrids.clear();
+ gMutex.unlock();
+}
diff --git a/test/unit/test_network.cpp b/test/unit/test_network.cpp
index 429a7303..db3e987f 100644
--- a/test/unit/test_network.cpp
+++ b/test/unit/test_network.cpp
@@ -127,6 +127,8 @@ TEST(networking, basic) {
ASSERT_EQ(P, nullptr);
proj_context_destroy(ctx);
+ proj_cleanup();
+
#ifdef CURL_ENABLED
// enable through env variable
ctx = proj_context_create();
@@ -141,6 +143,8 @@ TEST(networking, basic) {
putenv(const_cast<char *>("PROJ_NETWORK="));
#endif
+ proj_cleanup();
+
// still disabled
ctx = proj_context_create();
proj_grid_cache_set_enable(ctx, false);
@@ -149,6 +153,8 @@ TEST(networking, basic) {
ASSERT_EQ(P, nullptr);
proj_context_destroy(ctx);
+ proj_cleanup();
+
// enable through API
ctx = proj_context_create();
proj_grid_cache_set_enable(ctx, false);
@@ -174,6 +180,7 @@ TEST(networking, basic) {
ASSERT_EQ(P, nullptr);
#endif
proj_context_destroy(ctx);
+ proj_cleanup();
}
// ---------------------------------------------------------------------------
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
index ad218899..99e05dd1 100644
--- a/test/unit/test_operation.cpp
+++ b/test/unit/test_operation.cpp
@@ -7357,11 +7357,11 @@ TEST(operation, compoundCRS_to_compoundCRS_context_helmert_noop) {
ctxt->setSpatialCriterion(
CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
// WGS84 + EGM96
- auto objSrc = createFromUserInput("EPSG:4326+3855", dbContext);
+ auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
ASSERT_TRUE(srcCrs != nullptr);
// ETRS89 + EGM96
- auto objDest = createFromUserInput("EPSG:4258+3855", dbContext);
+ auto objDest = createFromUserInput("EPSG:4258+5773", dbContext);
auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
ASSERT_TRUE(destCrs != nullptr);
auto list = CoordinateOperationFactory::create()->createOperations(
@@ -7373,6 +7373,81 @@ TEST(operation, compoundCRS_to_compoundCRS_context_helmert_noop) {
// ---------------------------------------------------------------------------
+// EGM96 has a geoid model referenced to WGS84, and Belfast height has a
+// geoid model referenced to ETRS89
+TEST(operation, compoundCRS_to_compoundCRS_WGS84_EGM96_to_ETRS89_Belfast) {
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ // WGS84 + EGM96
+ auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
+ auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
+ ASSERT_TRUE(srcCrs != nullptr);
+ // ETRS89 + Belfast height
+ auto objDest = createFromUserInput("EPSG:4258+5732", dbContext);
+ auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
+ ASSERT_TRUE(destCrs != nullptr);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
+ "Inverse of ETRS89 to WGS 84 (1) + "
+ "ETRS89 to Belfast height (2)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 "
+ "+step +inv +proj=vgridshift +grids=uk_os_OSGM15_Belfast.tif "
+ "+multiplier=1 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+// Variant of above where source intermediate geog3D CRS == target intermediate
+// geog3D CRS
+TEST(operation, compoundCRS_to_compoundCRS_WGS84_EGM96_to_WGS84_Belfast) {
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ // WGS84 + EGM96
+ auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
+ auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
+ ASSERT_TRUE(srcCrs != nullptr);
+ // WGS84 + Belfast height
+ auto objDest = createFromUserInput("EPSG:4326+5732", dbContext);
+ auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
+ ASSERT_TRUE(destCrs != nullptr);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
+ "Inverse of ETRS89 to WGS 84 (1) + "
+ "ETRS89 to Belfast height (2) + "
+ "ETRS89 to WGS 84 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 "
+ "+step +inv +proj=vgridshift +grids=uk_os_OSGM15_Belfast.tif "
+ "+multiplier=1 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, vertCRS_to_vertCRS) {
auto vertcrs_m_obj = PROJStringParser().createFromPROJString("+vunits=m");