diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2020-05-16 16:20:10 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-05-16 16:20:10 +0200 |
| commit | 2d6b5c0a9a1e0f0f92e69c641a16377ef36f8270 (patch) | |
| tree | 55fff0406885c1694150e7889bf03415e8c4d35c | |
| parent | 330b2066244f77f89995a1aa195ef4323948a9b9 (diff) | |
| parent | 960285f9742cd407caef9956b6a6f9d947f03dde (diff) | |
| download | PROJ-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.cpp | 106 | ||||
| -rw-r--r-- | test/unit/test_operation.cpp | 70 |
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]; |
