aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2020-05-16 16:20:10 +0200
committerGitHub <noreply@github.com>2020-05-16 16:20:10 +0200
commit2d6b5c0a9a1e0f0f92e69c641a16377ef36f8270 (patch)
tree55fff0406885c1694150e7889bf03415e8c4d35c
parent330b2066244f77f89995a1aa195ef4323948a9b9 (diff)
parent960285f9742cd407caef9956b6a6f9d947f03dde (diff)
downloadPROJ-2d6b5c0a9a1e0f0f92e69c641a16377ef36f8270.tar.gz
PROJ-2d6b5c0a9a1e0f0f92e69c641a16377ef36f8270.zip
Merge pull request #2227 from rouault/fix_2225
createOperations(): when converting CompoundCRS<-->Geographic3DCrs, do not use discard change of ellipsoidal height if a Helmert transformation is involved
-rw-r--r--src/iso19111/coordinateoperation.cpp106
-rw-r--r--test/unit/test_operation.cpp70
2 files changed, 122 insertions, 54 deletions
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index ad456860..35be415f 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -119,8 +119,6 @@ static const char *BALLPARK_GEOCENTRIC_TRANSLATION =
static const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset";
static const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation";
static const char *BALLPARK_GEOGRAPHIC_OFFSET = "Ballpark geographic offset";
-static const char *BALLPARK_VERTICAL_TRANSFORMATION_PREFIX =
- " (ballpark vertical transformation";
static const char *BALLPARK_VERTICAL_TRANSFORMATION =
" (ballpark vertical transformation)";
static const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT =
@@ -128,6 +126,8 @@ static const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT =
"height correction)";
static const std::string AXIS_ORDER_CHANGE_2D_NAME = "axis order change (2D)";
+static const std::string AXIS_ORDER_CHANGE_3D_NAME =
+ "axis order change (geographic3D horizontal)";
//! @endcond
//! @cond Doxygen_Suppress
@@ -4842,8 +4842,7 @@ Conversion::createHeightDepthReversal(const util::PropertyMap &properties) {
*/
ConversionNNPtr Conversion::createAxisOrderReversal(bool is3D) {
if (is3D) {
- return create(createMapNameEPSGCode(
- "axis order change (geographic3D horizontal)", 15499),
+ return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_3D_NAME, 15499),
createMethodMapNameEPSGCode(
EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D),
{}, {});
@@ -7829,7 +7828,8 @@ createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom,
}
if (starts_with(tokens[i], INVERSE_OF)) {
name += tokens[i].substr(INVERSE_OF.size());
- } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME) {
+ } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME ||
+ tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) {
name += tokens[i];
} else {
name += INVERSE_OF + tokens[i];
@@ -11123,8 +11123,8 @@ struct SortFunction {
// Sorting function
// Return true if a < b
- bool operator()(const CoordinateOperationNNPtr &a,
- const CoordinateOperationNNPtr &b) const {
+ bool compare(const CoordinateOperationNNPtr &a,
+ const CoordinateOperationNNPtr &b) const {
auto iterA = map.find(a.get());
assert(iterA != map.end());
auto iterB = map.find(b.get());
@@ -11249,6 +11249,15 @@ struct SortFunction {
// Arbitrary final criterion
return a_name < b_name;
}
+
+ bool operator()(const CoordinateOperationNNPtr &a,
+ const CoordinateOperationNNPtr &b) const {
+ const bool ret = compare(a, b);
+#if 0
+ std::cerr << a->nameStr() << " < " << b->nameStr() << " : " << ret << std::endl;
+#endif
+ return ret;
+ }
};
// ---------------------------------------------------------------------------
@@ -11264,6 +11273,17 @@ static size_t getStepCount(const CoordinateOperationNNPtr &op) {
// ---------------------------------------------------------------------------
+static bool isNullTransformation(const std::string &name) {
+ if (name.find(" + ") != std::string::npos)
+ return false;
+ return starts_with(name, BALLPARK_GEOCENTRIC_TRANSLATION) ||
+ starts_with(name, BALLPARK_GEOGRAPHIC_OFFSET) ||
+ starts_with(name, NULL_GEOGRAPHIC_OFFSET) ||
+ starts_with(name, NULL_GEOCENTRIC_TRANSLATION);
+}
+
+// ---------------------------------------------------------------------------
+
struct FilterResults {
FilterResults(const std::vector<CoordinateOperationNNPtr> &sourceListIn,
@@ -11514,19 +11534,6 @@ struct FilterResults {
const auto stepCount = getStepCount(op);
- const bool isApprox =
- op->nameStr().find(BALLPARK_VERTICAL_TRANSFORMATION_PREFIX) !=
- std::string::npos;
- const bool isNullTransformation =
- op->nameStr().find(BALLPARK_GEOGRAPHIC_OFFSET) !=
- std::string::npos ||
- op->nameStr().find(NULL_GEOGRAPHIC_OFFSET) !=
- std::string::npos ||
- op->nameStr().find(NULL_GEOCENTRIC_TRANSLATION) !=
- std::string::npos ||
- op->nameStr().find(BALLPARK_GEOCENTRIC_TRANSLATION) !=
- std::string::npos;
-
bool isPROJExportable = false;
auto formatter = io::PROJStringFormatter::create();
try {
@@ -11537,10 +11544,24 @@ struct FilterResults {
} catch (const std::exception &) {
}
+#if 0
+ std::cerr << op->nameStr() << " ";
+ std::cerr << area << " ";
+ std::cerr << getAccuracy(op) << " ";
+ std::cerr << isPROJExportable << " ";
+ std::cerr << hasGrids << " ";
+ std::cerr << gridsAvailable << " ";
+ std::cerr << gridsKnown << " ";
+ std::cerr << stepCount << " ";
+ std::cerr << op->hasBallparkTransformation() << " ";
+ std::cerr << isNullTransformation(op->nameStr()) << " ";
+ std::cerr << std::endl;
+#endif
map[op.get()] = PrecomputedOpCharacteristics(
area, getAccuracy(op), isPROJExportable, hasGrids,
- gridsAvailable, gridsKnown, stepCount, isApprox,
- isNullTransformation);
+ gridsAvailable, gridsKnown, stepCount,
+ op->hasBallparkTransformation(),
+ isNullTransformation(op->nameStr()));
}
// Sort !
@@ -11587,12 +11608,8 @@ struct FilterResults {
// better
if (hasOpThatContainsAreaOfInterestAndNoGrid && res.size() > 1) {
const auto &opLast = res.back();
- const std::string &name = opLast->nameStr();
- if (name.find(BALLPARK_GEOGRAPHIC_OFFSET) != std::string::npos ||
- name.find(NULL_GEOGRAPHIC_OFFSET) != std::string::npos ||
- name.find(NULL_GEOCENTRIC_TRANSLATION) != std::string::npos ||
- name.find(BALLPARK_GEOCENTRIC_TRANSLATION) !=
- std::string::npos) {
+ if (opLast->hasBallparkTransformation() ||
+ isNullTransformation(opLast->nameStr())) {
std::vector<CoordinateOperationNNPtr> resTemp;
for (size_t i = 0; i < res.size() - 1; i++) {
resTemp.emplace_back(res[i]);
@@ -12438,7 +12455,8 @@ static CoordinateOperationNNPtr createHorizVerticalPROJBased(
horizTransform, verticalTransform, geogDst);
const bool horizTransformIsNoOp =
- starts_with(horizTransform->nameStr(), NULL_GEOGRAPHIC_OFFSET);
+ starts_with(horizTransform->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ horizTransform->nameStr().find(" + ") == std::string::npos;
if (horizTransformIsNoOp) {
auto properties = util::PropertyMap();
properties.set(common::IdentifiedObject::NAME_KEY,
@@ -12496,11 +12514,13 @@ static CoordinateOperationNNPtr createHorizVerticalHorizPROJBased(
interpolationGeogCRS);
std::vector<CoordinateOperationNNPtr> ops;
- if (!starts_with(opSrcCRSToGeogCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET)) {
+ if (!(starts_with(opSrcCRSToGeogCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ opSrcCRSToGeogCRS->nameStr().find(" + ") == std::string::npos)) {
ops.emplace_back(opSrcCRSToGeogCRS);
}
ops.emplace_back(verticalTransform);
- if (!starts_with(opGeogCRStoDstCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET)) {
+ if (!(starts_with(opGeogCRStoDstCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ opGeogCRStoDstCRS->nameStr().find(" + ") == std::string::npos)) {
ops.emplace_back(opGeogCRStoDstCRS);
}
@@ -12792,16 +12812,6 @@ findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory,
// ---------------------------------------------------------------------------
-static bool isNullTransformation(const std::string &name) {
-
- return starts_with(name, BALLPARK_GEOCENTRIC_TRANSLATION) ||
- starts_with(name, BALLPARK_GEOGRAPHIC_OFFSET) ||
- starts_with(name, NULL_GEOGRAPHIC_OFFSET) ||
- starts_with(name, NULL_GEOCENTRIC_TRANSLATION);
-}
-
-// ---------------------------------------------------------------------------
-
void CoordinateOperationFactory::Private::setCRSs(
CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS,
const crs::CRSNNPtr &targetCRS) {
@@ -14789,10 +14799,16 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
if (!componentsSrc[0]->isEquivalentTo(
target2D.get(),
util::IComparable::Criterion::EQUIVALENT)) {
- interpToTargetOps = createOperations(
- NN_NO_CHECK(interpolationGeogCRS),
- targetCRS->demoteTo2D(std::string(), dbContext),
- context);
+ // We do the transformation from the
+ // interpolationCRS
+ // to the target one in 3D (see #2225)
+ // But we don't do that between sourceCRS and
+ // interpolationCRS, as this would mess with an
+ // orthometric elevation.
+ auto interp3D = interpolationGeogCRS->promoteTo3D(
+ std::string(), dbContext);
+ interpToTargetOps =
+ createOperations(interp3D, targetCRS, context);
}
};
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
index 3d002a17..92b5f923 100644
--- a/test/unit/test_operation.cpp
+++ b/test/unit/test_operation.cpp
@@ -7003,13 +7003,17 @@ TEST(operation, compoundCRS_with_boundGeogCRS_to_geogCRS) {
compound, GeographicCRS::EPSG_4979);
ASSERT_TRUE(op != nullptr);
EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
- "+step +proj=cart +ellps=WGS84 +step +proj=helmert +x=1 +y=2 "
- "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step "
- "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector "
+ "+step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
}
// ---------------------------------------------------------------------------
@@ -8066,6 +8070,49 @@ TEST(operation, compoundCRS_to_geogCRS_3D_context) {
// ---------------------------------------------------------------------------
+TEST(operation, compoundCRS_to_geogCRS_3D_with_3D_helmert_context) {
+ // Use case of https://github.com/OSGeo/PROJ/issues/2225
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ // WGS84 + EGM96 height
+ auto srcObj = createFromUserInput("EPSG:4326+5773", dbContext, false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src),
+ // CH1903+
+ authFactory->createCoordinateReferenceSystem("4150")->promoteTo3D(
+ std::string(), dbContext),
+ ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
+ "Inverse of CH1903+ to WGS 84 (1)");
+ // Check that there is no push v_3 / pop v_3
+ const char *expected_proj =
+ "+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 +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=-674.374 +y=-15.056 +z=-405.346 "
+ "+step +inv +proj=cart +ellps=bessel "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1";
+ EXPECT_EQ(list[0]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5, dbContext)
+ .get()),
+ expected_proj);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, compoundCRS_to_geogCRS_2D_promote_to_3D_context) {
auto authFactory =
AuthorityFactory::create(DatabaseContext::create(), "EPSG");
@@ -8691,13 +8738,18 @@ TEST(operation, compoundCRS_from_WKT2_no_id_to_geogCRS_3D_context) {
" VDATUM[\"Normaal Amsterdams Peil\"],\n"
" CS[vertical,1],\n"
" AXIS[\"gravity-related height (H)\",up,\n"
- " LENGTHUNIT[\"metre\",1]]]]";
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " USAGE[\n"
+ " SCOPE[\"unknown\"],\n"
+ " AREA[\"Netherlands - onshore\"],\n"
+ " BBOX[50.75,3.2,53.7,7.22]]]";
+
auto obj = WKTParser().createFromWKT(wkt2);
auto src_from_wkt2 = nn_dynamic_pointer_cast<CRS>(obj);
ASSERT_TRUE(src_from_wkt2 != nullptr);
auto list2 = CoordinateOperationFactory::create()->createOperations(
NN_NO_CHECK(src_from_wkt2), dst, ctxt);
- ASSERT_GE(list.size(), list2.size() - 1);
+ ASSERT_EQ(list.size(), list2.size());
for (size_t i = 0; i < list.size(); i++) {
const auto &op = list[i];
const auto &op2 = list2[i];