@@ -7279,57 +7279,167 @@ TEST(operation, vertCRS_to_vertCRS_height_depth_pivot_context) {
72797279 // Test that PROJ can chain a registered vertical CT with a
72807280 // height-to-depth axis conversion when the target CRS differs from
72817281 // the CT's registered target only by axis direction.
7282+ auto authFactory =
7283+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
7284+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
7285+
7286+ auto checkPipeline = [&](const std::string &srcCode,
7287+ const std::string &tgtCode,
7288+ const std::string &expectedProj) {
7289+ auto list = CoordinateOperationFactory::create()->createOperations(
7290+ authFactory->createCoordinateReferenceSystem(srcCode),
7291+ authFactory->createCoordinateReferenceSystem(tgtCode), ctxt);
7292+ ASSERT_GE(list.size(), 1U);
7293+ EXPECT_FALSE(list[0]->hasBallparkTransformation());
7294+ EXPECT_EQ(
7295+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
7296+ expectedProj);
7297+ };
7298+
7299+ // Caspian: EPSG:5705 (Baltic 1977 height) -> EPSG:5706 (Caspian depth)
7300+ // via EPSG:5438 (dh=28) + height-to-depth axisswap
7301+ checkPipeline("5705", "5706",
7302+ "+proj=pipeline +step +proj=geogoffset +dh=28 "
7303+ "+step +proj=axisswap +order=1,2,-3");
7304+
7305+ // AIOC95: EPSG:5705 -> EPSG:5734 (AIOC95 depth)
7306+ // via EPSG:5443 (dh=26.3) + height-to-depth axisswap
7307+ checkPipeline("5705", "5734",
7308+ "+proj=pipeline +step +proj=geogoffset +dh=26.3 "
7309+ "+step +proj=axisswap +order=1,2,-3");
7310+
7311+ // KOC: EPSG:5790 (KOC CD height) -> EPSG:5789 (KOC WD depth)
7312+ // via EPSG:7986 (dh=-4.74) + height-to-depth axisswap
7313+ checkPipeline("5790", "5789",
7314+ "+proj=pipeline +step +proj=geogoffset +dh=-4.74 "
7315+ "+step +proj=axisswap +order=1,2,-3");
7316+
7317+ // Kuwait PWD: EPSG:5788 -> EPSG:5789 (KOC WD depth)
7318+ // via EPSG:7981 (dh=-4.25) + height-to-depth axisswap
7319+ checkPipeline("5788", "5789",
7320+ "+proj=pipeline +step +proj=geogoffset +dh=-4.25 "
7321+ "+step +proj=axisswap +order=1,2,-3");
7322+
7323+ // KOC ft: EPSG:5790 -> EPSG:5614 (KOC WD depth ft)
7324+ // via EPSG:7987 (dh=-4.74) + axisswap + unit conversion m->ft
7325+ checkPipeline("5790", "5614",
7326+ "+proj=pipeline "
7327+ "+step +proj=geogoffset +dh=-4.74 "
7328+ "+step +proj=axisswap +order=1,2,-3 "
7329+ "+step +proj=unitconvert +z_in=m +z_out=ft");
7330+ }
7331+
7332+ // ---------------------------------------------------------------------------
7333+
7334+ TEST(operation, vertCRS_to_vertCRS_depth_height_pivot_context) {
7335+ // Test the reverse direction: depth to height.
7336+ // When the source is depth (axis DOWN) and the target is height (axis UP),
7337+ // the code prefers pivoting through the SOURCE's datum, composing:
7338+ // depth-to-height axisswap + inverse of the registered forward op.
7339+ auto authFactory =
7340+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
7341+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
7342+
7343+ auto checkPipeline = [&](const std::string &srcCode,
7344+ const std::string &tgtCode,
7345+ const std::string &expectedProj) {
7346+ auto list = CoordinateOperationFactory::create()->createOperations(
7347+ authFactory->createCoordinateReferenceSystem(srcCode),
7348+ authFactory->createCoordinateReferenceSystem(tgtCode), ctxt);
7349+ ASSERT_GE(list.size(), 1U);
7350+ EXPECT_FALSE(list[0]->hasBallparkTransformation());
7351+ EXPECT_EQ(
7352+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
7353+ expectedProj);
7354+ };
7355+
7356+ // Caspian: EPSG:5706 (Caspian depth) -> EPSG:5705 (Baltic 1977 height)
7357+ // axisswap + inverse of EPSG:5438 (dh=-28)
7358+ checkPipeline("5706", "5705",
7359+ "+proj=pipeline +step +proj=axisswap +order=1,2,-3 "
7360+ "+step +proj=geogoffset +dh=-28");
7361+
7362+ // AIOC95: EPSG:5734 (AIOC95 depth) -> EPSG:5705 (Baltic 1977 height)
7363+ // axisswap + inverse of EPSG:5443 (dh=-26.3)
7364+ checkPipeline("5734", "5705",
7365+ "+proj=pipeline +step +proj=axisswap +order=1,2,-3 "
7366+ "+step +proj=geogoffset +dh=-26.3");
7367+
7368+ // KOC: EPSG:5789 (KOC WD depth) -> EPSG:5790 (KOC CD height)
7369+ // axisswap + inverse of EPSG:7986 (dh=4.74)
7370+ checkPipeline("5789", "5790",
7371+ "+proj=pipeline +step +proj=axisswap +order=1,2,-3 "
7372+ "+step +proj=geogoffset +dh=4.74");
7373+
7374+ // Kuwait PWD: EPSG:5789 (KOC WD depth) -> EPSG:5788 (Kuwait PWD height)
7375+ // axisswap + inverse of EPSG:7981 (dh=4.25)
7376+ checkPipeline("5789", "5788",
7377+ "+proj=pipeline +step +proj=axisswap +order=1,2,-3 "
7378+ "+step +proj=geogoffset +dh=4.25");
7379+
7380+ // KOC ft: EPSG:5614 (KOC WD depth ft) -> EPSG:5790 (KOC CD height)
7381+ // unit conversion ft->m + axisswap + inverse of EPSG:7987 (dh=4.74)
7382+ checkPipeline("5614", "5790",
7383+ "+proj=pipeline "
7384+ "+step +proj=unitconvert +z_in=ft +z_out=m "
7385+ "+step +proj=axisswap +order=1,2,-3 "
7386+ "+step +proj=geogoffset +dh=4.74");
7387+ }
7388+
7389+ // ---------------------------------------------------------------------------
7390+
7391+ TEST(operation, vertCRS_to_vertCRS_height_depth_pivot_blacksea_context) {
7392+ // Test that the intermediate-vert pivot finds a registered cross-datum
7393+ // height-to-height transformation and composes it with an axis reversal.
72827394 //
7283- // EPSG:5705 (Baltic 1977 height) to EPSG:5706 (Caspian depth)
7284- // should compose: EPSG:5438 (5705 to 5611, geogoffset +dh=28)
7285- // + height-to-depth (axisswap order=1,2,-3)
7395+ // EPSG:5705 (Baltic 1977 height) to EPSG:5336 (Black Sea depth)
7396+ // Strategy 1 composes: EPSG:5447 (5705 to 5735, geogoffset +dh=0.4)
7397+ // + height-to-depth (axisswap order=1,2,-3)
7398+ // PARTIAL_INTERSECTION is needed because EPSG:5447's area
7399+ // is slightly smaller than EPSG:5336's extent
72867400 auto authFactory =
72877401 AuthorityFactory::create(DatabaseContext::create(), "EPSG");
72887402 auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
7403+ ctxt->setSpatialCriterion(
7404+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
72897405 auto list = CoordinateOperationFactory::create()->createOperations(
72907406 // Baltic 1977 height
72917407 authFactory->createCoordinateReferenceSystem("5705"),
7292- // Caspian depth (same datum as Caspian height 5611, but axis: down)
7293- authFactory->createCoordinateReferenceSystem("5706 "), ctxt);
7408+ // Black Sea depth
7409+ authFactory->createCoordinateReferenceSystem("5336 "), ctxt);
72947410 ASSERT_GE(list.size(), 1U);
7295- // Must NOT be a ballpark transformation
72967411 EXPECT_FALSE(list[0]->hasBallparkTransformation());
7297- // The pipeline should match the registered operation followed by the
7298- // height-to-depth axis conversion.
72997412 auto projStr =
73007413 list[0]->exportToPROJString(PROJStringFormatter::create().get());
7301- EXPECT_EQ(projStr, "+proj=pipeline +step +proj=geogoffset +dh=28 +step "
7414+ EXPECT_EQ(projStr, "+proj=pipeline +step +proj=geogoffset +dh=0.4 +step "
73027415 "+proj=axisswap +order=1,2,-3");
73037416}
73047417
73057418// ---------------------------------------------------------------------------
73067419
7307- TEST(operation, vertCRS_to_vertCRS_depth_height_pivot_strategy2_context ) {
7308- // Test Strategy 2: pivot through a candidate sharing the SOURCE's datum .
7420+ TEST(operation, vertCRS_to_vertCRS_depth_height_pivot_blacksea_context ) {
7421+ // Test the reverse direction for the Black Sea case .
73097422 //
7310- // EPSG:5706 (Caspian depth) to EPSG:5705 (Baltic 1977 height)
7311- // The registered operation EPSG:5438 goes 5705 to 5611 (Caspian height).
7312- // Strategy 2 finds 5611 (shares Caspian datum with source 5706), discovers
7313- // the reverse of EPSG:5438 from 5611 to 5705, and composes:
7314- // 5706 -(depth-to-height conversion)-> 5611 -(inverse of EPSG:5438)->
7315- // 5705
7423+ // EPSG:5336 (Black Sea depth) to EPSG:5705 (Baltic 1977 height)
7424+ // Strategy 2 composes: depth-to-height (5336 to 5735, axisswap)
7425+ // + inverse of EPSG:5447 (5735 to 5705, dh=-0.4)
7426+
73167427 auto authFactory =
73177428 AuthorityFactory::create(DatabaseContext::create(), "EPSG");
73187429 auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
7430+ ctxt->setSpatialCriterion(
7431+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
73197432 auto list = CoordinateOperationFactory::create()->createOperations(
7320- // Caspian depth
7321- authFactory->createCoordinateReferenceSystem("5706 "),
7433+ // Black Sea depth
7434+ authFactory->createCoordinateReferenceSystem("5336 "),
73227435 // Baltic 1977 height
73237436 authFactory->createCoordinateReferenceSystem("5705"), ctxt);
73247437 ASSERT_GE(list.size(), 1U);
7325- // Must NOT be a ballpark transformation
73267438 EXPECT_FALSE(list[0]->hasBallparkTransformation());
7327- // The exported pipeline is normalized to the same PROJ steps as the
7328- // forward case.
73297439 auto projStr =
73307440 list[0]->exportToPROJString(PROJStringFormatter::create().get());
7331- EXPECT_EQ(projStr, "+proj=pipeline +step +proj=geogoffset +dh=28 +step "
7332- "+proj=axisswap +order=1,2,-3 ");
7441+ EXPECT_EQ(projStr, "+proj=pipeline +step +proj=axisswap +order=1,2,-3 "
7442+ "+step + proj=geogoffset +dh=-0.4 ");
73337443}
73347444
73357445// ---------------------------------------------------------------------------
0 commit comments