diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 8641d896f6..5ad36107ff 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -45,7 +45,7 @@ set(ALL_SQL_IN "${CMAKE_CURRENT_BINARY_DIR}/all.sql.in") set(PROJ_DB "${CMAKE_CURRENT_BINARY_DIR}/proj.db") include(sql_filelist.cmake) -set(PROJ_DB_SQL_EXPECTED_MD5 "3d51445a31ba6980332fa8e2348f7b16") +set(PROJ_DB_SQL_EXPECTED_MD5 "3e0b3f2f65e9bb051e2f5fb6b40e1a42") add_custom_command( OUTPUT ${PROJ_DB} diff --git a/data/sql/helmert_transformation.sql b/data/sql/helmert_transformation.sql index 1ef76b464b..5f6f806460 100644 --- a/data/sql/helmert_transformation.sql +++ b/data/sql/helmert_transformation.sql @@ -2690,10 +2690,14 @@ INSERT INTO "helmert_transformation" VALUES('EPSG','10607','WGS 84 (G2139) to WG INSERT INTO "usage" VALUES('EPSG','21197','helmert_transformation','EPSG','10607','EPSG','1262','EPSG','1027'); INSERT INTO "helmert_transformation" VALUES('EPSG','10608','WGS 84 (G2296) to ITRF2020 (1)','Scale difference in ppb where 1/billion = 1E-9 or nm/m. Accuracy of 1cm applies at epoch 2024.0. Due to subsequent drift between the reference frames it may reach 2cm at other epochs.','EPSG','1032','Coordinate Frame rotation (geocentric domain)','EPSG','10604','EPSG','9988',0.01,0.0,0.0,0.0,'EPSG','1025',0.0,0.0,0.0,'EPSG','1031',0.0,'EPSG','1028',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'NGA-Wld 2024.0',0); INSERT INTO "usage" VALUES('EPSG','21198','helmert_transformation','EPSG','10608','EPSG','1262','EPSG','1026'); +INSERT INTO "helmert_transformation" VALUES('EPSG','10646','Saba to BES2020 Saba (1)','For the reverse transformation from BES2020 Saba to Saba, method 1133 [used here] is reversible with the parameter values given here (see GN7-2); the BESTRANS software uses different parameter values with the forward formulas: see BESTRANS documentation.','EPSG','1133','Coordinate Frame rotation full matrix (geog2D)','EPSG','10636','EPSG','10639',0.05,1138.7432,-2064.4761,110.7016,'EPSG','9001',-214.615206,479.360036,-164.703951,'EPSG','9104',-402.32073,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'NSGI-Bes Saba',0); +INSERT INTO "usage" VALUES('EPSG','21861','helmert_transformation','EPSG','10646','EPSG','4757','EPSG','1144'); INSERT INTO "helmert_transformation" VALUES('EPSG','10647','BES2020 Saba to ITRF2014 (1)','Time-dependent component of official transformation BESTRANS2020.','EPSG','1056','Time-dependent Coordinate Frame rotation (geocen)','EPSG','10637','EPSG','7789',0.05,0.0,0.0,0.0,'EPSG','9001',0.0,0.0,0.0,'EPSG','9104',0.0,'EPSG','9202',0.00726,0.00848,0.01353,'EPSG','1042',0.0,0.0,0.0,'EPSG','1043',0.0,'EPSG','1041',2020.0,'EPSG','1029',NULL,NULL,NULL,NULL,NULL,'NSGI-Bes Saba',0); INSERT INTO "usage" VALUES('EPSG','21765','helmert_transformation','EPSG','10647','EPSG','4757','EPSG','1079'); INSERT INTO "helmert_transformation" VALUES('EPSG','10648','BES2020 Saba to WGS 84 (1)','','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','10639','EPSG','4326',0.5,0.0,0.0,0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'NSGI-Bes Saba',0); INSERT INTO "usage" VALUES('EPSG','21743','helmert_transformation','EPSG','10648','EPSG','4757','EPSG','1252'); +INSERT INTO "helmert_transformation" VALUES('EPSG','10676','Saba to WGS 84 (1)','Parameter values taken from Saba to BES2020 Saba (1) (transformation code 10646) assuming that BES2020 Saba is coincident with WGS 84 within the accuracy of the transformation.','EPSG','1133','Coordinate Frame rotation full matrix (geog2D)','EPSG','10636','EPSG','4326',1.0,1138.7432,-2064.4761,110.7016,'EPSG','9001',-214.615206,479.360036,-164.703951,'EPSG','9104',-402.32073,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'IOGP-Bes Saba',0); +INSERT INTO "usage" VALUES('EPSG','21783','helmert_transformation','EPSG','10676','EPSG','4757','EPSG','1252'); INSERT INTO "helmert_transformation" VALUES('EPSG','10682','RGM04 to RGM23 (1)','Approximation at the +/- 0.1m level. For more accurate official transformation see RGM04 to RGM23 (2) (code 10683).','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4470','EPSG','10671',0.1,-0.5377,0.3946,0.3608,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'IGN-Myt 10cm',0); INSERT INTO "usage" VALUES('EPSG','21834','helmert_transformation','EPSG','10682','EPSG','1159','EPSG','1189'); INSERT INTO "helmert_transformation" VALUES('EPSG','10684','RGM23 to WGS 84 (1)','Approximation at the +/- 1m level assuming that RGM23 is equivalent to WGS 84.','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','10671','EPSG','4326',1.0,0.0,0.0,0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'IOGP-Myt',0); diff --git a/scripts/build_db.py b/scripts/build_db.py index 3e7b1d880c..dd798fbce9 100755 --- a/scripts/build_db.py +++ b/scripts/build_db.py @@ -585,7 +585,7 @@ def fill_compound_crs(proj_db_cursor): raise def fill_helmert_transformation(proj_db_cursor): - proj_db_cursor.execute("SELECT coord_op_code, coord_op_name, coord_op_method_code, coord_op_method_name, source_crs_code, target_crs_code, coord_op_accuracy, coord_tfm_version, epsg_coordoperation.deprecated, epsg_coordoperation.remarks FROM epsg.epsg_coordoperation LEFT JOIN epsg.epsg_coordoperationmethod USING (coord_op_method_code) WHERE coord_op_type = 'transformation' AND coord_op_method_code IN (1031, 1032, 1033, 1034, 1035, 1037, 1038, 1039, 1053, 1054, 1055, 1056, 1057, 1058, 1061, 1062, 1063, 1065, 1066, 9603, 9606, 9607, 9636) ") + proj_db_cursor.execute("SELECT coord_op_code, coord_op_name, coord_op_method_code, coord_op_method_name, source_crs_code, target_crs_code, coord_op_accuracy, coord_tfm_version, epsg_coordoperation.deprecated, epsg_coordoperation.remarks FROM epsg.epsg_coordoperation LEFT JOIN epsg.epsg_coordoperationmethod USING (coord_op_method_code) WHERE coord_op_type = 'transformation' AND coord_op_method_code IN (1031, 1032, 1033, 1034, 1035, 1037, 1038, 1039, 1053, 1054, 1055, 1056, 1057, 1058, 1061, 1062, 1063, 1065, 1066, 1132, 1133, 9603, 9606, 9607, 9636) ") for (code, name, method_code, method_name, source_crs_code, target_crs_code, coord_op_accuracy, coord_tfm_version, deprecated, remarks) in proj_db_cursor.fetchall(): expected_order = 1 max_n_params = 15 @@ -928,6 +928,13 @@ def fill_concatenated_operation(proj_db_cursor): expected_order = 1 steps_code = [] + # FIXME: https://epsg.org/concatenated-operation_10675/BES2020-to-Saba-height-1.html ill defined in EPSG 11.023 + # due to first step referencing BES2020 Saba geographic 2D (EPSG:10639), but source CRS of concatenated + # operation referencing BES2020 Saba geographic 3D (EPSG:10638) + if code == 10675: + print("FIXME! Skipping EPSG:10675 'BES2020 to Saba height (1)' for now") + continue + iterator = proj_db_cursor.execute("SELECT op_path_step, single_operation_code FROM epsg_coordoperationpath WHERE concat_operation_code = ? ORDER BY op_path_step", (code,)) for (order, single_operation_code) in iterator: assert order == expected_order diff --git a/src/iso19111/operation/coordinateoperationfactory.cpp b/src/iso19111/operation/coordinateoperationfactory.cpp index 078a0196d3..fe17a6ebc1 100644 --- a/src/iso19111/operation/coordinateoperationfactory.cpp +++ b/src/iso19111/operation/coordinateoperationfactory.cpp @@ -2381,8 +2381,12 @@ struct MyPROJStringExportableHorizVerticalHorizPROJBased final methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D || methodEPSGCode == diff --git a/src/iso19111/operation/parammappings.cpp b/src/iso19111/operation/parammappings.cpp index 2d4fc2c549..0abaa8b61a 100644 --- a/src/iso19111/operation/parammappings.cpp +++ b/src/iso19111/operation/parammappings.cpp @@ -1003,7 +1003,9 @@ const struct MethodNameCode methodNameCodesList[] = { METHOD_NAME_CODE(AFFINE_PARAMETRIC_TRANSFORMATION), METHOD_NAME_CODE(SIMILARITY_TRANSFORMATION), METHOD_NAME_CODE(COORDINATE_FRAME_GEOCENTRIC), + METHOD_NAME_CODE(COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC), METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_2D), + METHOD_NAME_CODE(COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D), METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_3D), METHOD_NAME_CODE(POSITION_VECTOR_GEOCENTRIC), METHOD_NAME_CODE(POSITION_VECTOR_GEOGRAPHIC_2D), @@ -1534,9 +1536,15 @@ static const MethodMapping gOtherMethodMappings[] = { {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOCENTRIC, EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC, nullptr, nullptr, nullptr, paramsHelmert7}, + {EPSG_NAME_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC, + EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC, nullptr, nullptr, + nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D, EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, paramsHelmert7}, + {EPSG_NAME_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D, nullptr, + nullptr, nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D, EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, paramsHelmert7}, diff --git a/src/iso19111/operation/singleoperation.cpp b/src/iso19111/operation/singleoperation.cpp index 779d922042..42ca9ffc63 100644 --- a/src/iso19111/operation/singleoperation.cpp +++ b/src/iso19111/operation/singleoperation.cpp @@ -3473,6 +3473,7 @@ bool SingleOperation::exportToPROJStringGeneric( bool sevenParamsTransform = false; bool threeParamsTransform = false; bool fifteenParamsTransform = false; + bool fullMatrix = false; const auto &l_method = method(); const auto &methodName = l_method->nameStr(); const bool isMethodInverseOf = starts_with(methodName, INVERSE_OF); @@ -3484,10 +3485,19 @@ bool SingleOperation::exportToPROJStringGeneric( const bool isCoordinateFrame = ci_find(methodName, "Coordinate Frame") != std::string::npos || ci_find(methodName, "CF") != std::string::npos; - if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { + if (methodEPSGCode == + EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D) { + positionVectorConvention = false; + sevenParamsTransform = true; + fullMatrix = true; + } else if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { positionVectorConvention = false; sevenParamsTransform = true; } else if ( @@ -3558,6 +3568,8 @@ bool SingleOperation::exportToPROJStringGeneric( } formatter->addStep("helmert"); + if (fullMatrix) + formatter->addParam("exact"); formatter->addParam("x", x); formatter->addParam("y", y); formatter->addParam("z", z); diff --git a/src/iso19111/operation/transformation.cpp b/src/iso19111/operation/transformation.cpp index ce53cf88bf..ab3af1f99b 100644 --- a/src/iso19111/operation/transformation.cpp +++ b/src/iso19111/operation/transformation.cpp @@ -206,7 +206,11 @@ std::vector Transformation::getTOWGS84Parameters( if ((paramCount == 7 && ci_find(methodName, "Coordinate Frame") != std::string::npos) || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { sevenParamsTransform = true; invertRotSigns = true; @@ -1407,7 +1411,11 @@ createApproximateInverseIfPossible(const Transformation *op) { if ((paramCount == 7 && isCoordinateFrame && !isTimeDependent(methodName)) || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { sevenParamsTransform = true; } else if ( diff --git a/src/proj_constants.h b/src/proj_constants.h index dba70c8375..e64ec76540 100644 --- a/src/proj_constants.h +++ b/src/proj_constants.h @@ -410,10 +410,18 @@ "Coordinate Frame rotation (geocentric domain)" #define EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC 1032 +#define EPSG_NAME_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC \ + "Coordinate Frame rotation full matrix (geocen)" +#define EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC 1132 + #define EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D \ "Coordinate Frame rotation (geog2D domain)" #define EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D 9607 +#define EPSG_NAME_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D \ + "Coordinate Frame rotation full matrix (geog2D)" +#define EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D 1133 + #define EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D \ "Coordinate Frame rotation (geog3D domain)" #define EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D 1038 diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp index 4d73c66165..f2326760ba 100644 --- a/test/unit/test_operation.cpp +++ b/test/unit/test_operation.cpp @@ -6059,3 +6059,108 @@ TEST(operation, inverse_of_Geographic3DToGravityRelatedHeight) { PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=affine +zoff=10"); } + +// --------------------------------------------------------------------------- + +TEST(operation, CoordinateFrameRotationFullMatrixGeog2D) { + + auto wkt = + "COORDINATEOPERATION[\"Saba to WGS 84 (1)\",\n" + " VERSION[\"IOGP-Bes Saba\"],\n" + " SOURCECRS[\n" + " GEOGCRS[\"Saba\",\n" + " DATUM[\"Saba\",\n" + " ELLIPSOID[\"International 1924\",6378388,297,\n" + " LENGTHUNIT[\"metre\",1]]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + " CS[ellipsoidal,2],\n" + " AXIS[\"geodetic latitude (Lat)\",north,\n" + " ORDER[1],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + " AXIS[\"geodetic longitude (Lon)\",east,\n" + " ORDER[2],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + " ID[\"EPSG\",10636]]],\n" + " TARGETCRS[\n" + " GEOGCRS[\"WGS 84\",\n" + " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" + " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G2296)\"],\n" + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + " LENGTHUNIT[\"metre\",1]],\n" + " ENSEMBLEACCURACY[2.0]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + " CS[ellipsoidal,2],\n" + " AXIS[\"geodetic latitude (Lat)\",north,\n" + " ORDER[1],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + " AXIS[\"geodetic longitude (Lon)\",east,\n" + " ORDER[2],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + " ID[\"EPSG\",4326]]],\n" + " METHOD[\"Coordinate Frame rotation full matrix (geog2D)\",\n" + " ID[\"EPSG\",1133]],\n" + " PARAMETER[\"X-axis translation\",1138.7432,\n" + " LENGTHUNIT[\"metre\",1],\n" + " ID[\"EPSG\",8605]],\n" + " PARAMETER[\"Y-axis translation\",-2064.4761,\n" + " LENGTHUNIT[\"metre\",1],\n" + " ID[\"EPSG\",8606]],\n" + " PARAMETER[\"Z-axis translation\",110.7016,\n" + " LENGTHUNIT[\"metre\",1],\n" + " ID[\"EPSG\",8607]],\n" + " PARAMETER[\"X-axis rotation\",-214.615206,\n" + " ANGLEUNIT[\"arc-second\",4.84813681109536E-06],\n" + " ID[\"EPSG\",8608]],\n" + " PARAMETER[\"Y-axis rotation\",479.360036,\n" + " ANGLEUNIT[\"arc-second\",4.84813681109536E-06],\n" + " ID[\"EPSG\",8609]],\n" + " PARAMETER[\"Z-axis rotation\",-164.703951,\n" + " ANGLEUNIT[\"arc-second\",4.84813681109536E-06],\n" + " ID[\"EPSG\",8610]],\n" + " PARAMETER[\"Scale difference\",-402.32073,\n" + " SCALEUNIT[\"parts per million\",1E-06],\n" + " ID[\"EPSG\",8611]],\n" + " OPERATIONACCURACY[1.0]]"; + + auto obj = WKTParser().createFromWKT(wkt); + auto transf = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(transf != nullptr); + EXPECT_EQ(transf->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=intl " + "+step +proj=helmert +exact " + "+x=1138.7432 +y=-2064.4761 +z=110.7016 " + "+rx=-214.615206 +ry=479.360036 +rz=-164.703951 +s=-402.32073 " + "+convention=coordinate_frame " + "+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"); + EXPECT_EQ(transf->inverse()->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 +inv +proj=helmert +exact " + "+x=1138.7432 +y=-2064.4761 +z=110.7016 " + "+rx=-214.615206 +ry=479.360036 +rz=-164.703951 +s=-402.32073 " + "+convention=coordinate_frame " + "+step +inv +proj=cart +ellps=intl " + "+step +proj=pop +v_3 " + "+step +proj=unitconvert +xy_in=rad +xy_out=deg " + "+step +proj=axisswap +order=2,1"); +}