Skip to content

Commit ddd9887

Browse files
committed
Yet another MEGA (Make ETRS89 Great Again) fix!
I've been made aware that ETRS89 to MGI was semi broken since the EPSG update that added a ETRS89-AUT [2002] datum and migrated the 'MGI to ETRS89 (8)' transformation using the last released grid AT_GIS_GRID_2021_09_28 to be 'MGI to ETRS89-AUT [2002]' and thus leaving user with the older transformation 'MGI to ETRS89 (5)'. This is just one manifestation of a problem that can be seen with ETRS89 in the Netherlands, Romania, etc. Until EPSG perhaps figures out a plan to restore cleanly backward compatibility (they have been made aware of those issues), I've devised the following s/hack/strategy that seems to work (TM): When looking for ETRS89 (EPSG:4258 the datum ensemble) to XXXX transformations, also query the ``alias_name`` table for old names of transformations that start with 'ETRS89 to XXXX' or 'XXXX to ETRS89', and are aliases to 'ETRS89-YYY to XXXX[something]' or 'XXXX to ETRS89-YYY[something]' newly named transformation. And then consider those 'migrated' transformations to be also used for plain ETRS89, because they used to be used for it! No new test cases, but actually reverting to old reference values before commit 047e5db (update to EPSG 12.033) that changed them.
1 parent dd5967f commit ddd9887

6 files changed

Lines changed: 180 additions & 32 deletions

File tree

include/proj/io.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,14 @@ class PROJ_GCC_DLL AuthorityFactory {
13281328
const metadata::ExtentPtr &intersectingExtent1,
13291329
const metadata::ExtentPtr &intersectingExtent2) const;
13301330

1331+
PROJ_INTERNAL std::vector<operation::CoordinateOperationNNPtr>
1332+
getOperationsFromAlias(const std::string &crs1Name,
1333+
const std::string &crs2Name,
1334+
bool usePROJAlternativeGridNames,
1335+
bool discardIfMissingGrid,
1336+
bool considerKnownGridsAsAvailable,
1337+
bool discardSuperseded) const;
1338+
13311339
typedef std::pair<common::IdentifiedObjectNNPtr, std::string>
13321340
PairObjectName;
13331341
PROJ_INTERNAL std::list<PairObjectName>

src/iso19111/factory.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10431,6 +10431,62 @@ AuthorityFactory::getPointMotionOperationsFor(
1043110431
return res;
1043210432
}
1043310433

10434+
// ---------------------------------------------------------------------------
10435+
10436+
std::vector<operation::CoordinateOperationNNPtr>
10437+
AuthorityFactory::getOperationsFromAlias(const std::string &crs1Name,
10438+
const std::string &crs2Name,
10439+
bool usePROJAlternativeGridNames,
10440+
bool discardIfMissingGrid,
10441+
bool considerKnownGridsAsAvailable,
10442+
bool discardSuperseded) const {
10443+
std::string sql("SELECT alias.auth_name, alias.code FROM alias_name alias "
10444+
"JOIN coordinate_operation_view cov "
10445+
"ON alias.table_name = cov.table_name "
10446+
"AND alias.auth_name = cov.auth_name "
10447+
"AND alias.code = cov.code "
10448+
"WHERE alias.table_name IN ('grid_transformation', "
10449+
"'helmert_transformation', 'other_transformation', "
10450+
"'concatenated_operation') "
10451+
"AND (alt_name LIKE ? OR alt_name LIKE ?) "
10452+
"AND NOT (alt_name LIKE ? OR alt_name LIKE ? OR alt_name "
10453+
"LIKE ? OR alt_name LIKE ?) "
10454+
"AND cov.deprecated = 0");
10455+
if (discardSuperseded) {
10456+
sql += " AND NOT EXISTS (SELECT 1 FROM supersession ss WHERE "
10457+
"ss.superseded_table_name = cov.table_name AND "
10458+
"ss.superseded_auth_name = cov.auth_name AND "
10459+
"ss.superseded_code = cov.code AND "
10460+
"ss.superseded_table_name = ss.replacement_table_name AND "
10461+
"ss.same_source_target_crs = 1)";
10462+
}
10463+
ListOfParams params{
10464+
std::string(crs1Name).append(" to ").append(crs2Name).append("%"),
10465+
std::string(crs2Name).append(" to ").append(crs1Name).append("%"),
10466+
// If looking for "MGI to ETRS89", don't match "ETRS89 to ETRS89-XXX"
10467+
std::string(crs1Name).append(" to ").append(crs1Name).append("-%"),
10468+
std::string(crs2Name).append(" to ").append(crs2Name).append("-%"),
10469+
// If looking for "MGI to ETRS89", don't match "MGI to ETRS89-XXX" (not
10470+
// an actual example, but just in case)
10471+
std::string(crs1Name).append(" to ").append(crs2Name).append("-%"),
10472+
std::string(crs2Name).append(" to ").append(crs1Name).append("-%"),
10473+
};
10474+
10475+
std::vector<operation::CoordinateOperationNNPtr> res;
10476+
auto sqlRes = d->run(sql, params);
10477+
for (const auto &row : sqlRes) {
10478+
const auto &auth_name = row[0];
10479+
const auto &code = row[1];
10480+
auto op = d->createFactory(auth_name)->createCoordinateOperation(
10481+
code, usePROJAlternativeGridNames);
10482+
if (!discardIfMissingGrid ||
10483+
!d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
10484+
res.emplace_back(op);
10485+
}
10486+
}
10487+
return res;
10488+
}
10489+
1043410490
//! @endcond
1043510491

1043610492
// ---------------------------------------------------------------------------

src/iso19111/operation/coordinateoperationfactory.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4270,6 +4270,80 @@ bool CoordinateOperationFactory::Private::createOperationsFromDatabase(
42704270
}
42714271
}
42724272

4273+
// ETRS89 specific hack to deal with the fact that a lot of ETRS89-XXX
4274+
// datum/CRS have been created, with older transformations going from/to
4275+
// generic ETRS89 being re-attributed to that specific ETRS89-XXX datum/CRS
4276+
// which breaks backwards compatibility
4277+
// e.g we want ETRS89 to MGI to be able to use the previously named
4278+
// "MGI to ETRS89 (8)" operation, that is now "MGI to ETRS89-AUT [2002] (8)"
4279+
const bool srcIsETRS89 = sourceCRS->nameStr() == "ETRS89";
4280+
const bool dstIsETRS89 = targetCRS->nameStr() == "ETRS89";
4281+
if (geodSrc && geodDst && (srcIsETRS89 || dstIsETRS89) &&
4282+
!(srcIsETRS89 && dstIsETRS89)) {
4283+
const auto &authFactory = context.context->getAuthorityFactory();
4284+
const auto gridAvailabilityUse =
4285+
context.context->getGridAvailabilityUse();
4286+
const auto opFromAlias = authFactory->getOperationsFromAlias(
4287+
sourceCRS->nameStr(), targetCRS->nameStr(),
4288+
context.context->getUsePROJAlternativeGridNames(),
4289+
/* discardIfMissingGrid = */
4290+
gridAvailabilityUse ==
4291+
CoordinateOperationContext::GridAvailabilityUse::
4292+
DISCARD_OPERATION_IF_MISSING_GRID ||
4293+
gridAvailabilityUse == CoordinateOperationContext::
4294+
GridAvailabilityUse::KNOWN_AVAILABLE,
4295+
/* considerKnownGridsAsAvailable = */
4296+
gridAvailabilityUse == CoordinateOperationContext::
4297+
GridAvailabilityUse::KNOWN_AVAILABLE,
4298+
context.context->getDiscardSuperseded());
4299+
for (const auto &aliasOp : opFromAlias) {
4300+
const auto aliasOpSourceCRS = aliasOp->sourceCRS();
4301+
const auto aliasOpTargetCRS = aliasOp->targetCRS();
4302+
if (aliasOpSourceCRS && aliasOpTargetCRS &&
4303+
(aliasOpSourceCRS->nameStr().find("ETRS89-") !=
4304+
std::string::npos ||
4305+
aliasOpTargetCRS->nameStr().find("ETRS89-") !=
4306+
std::string::npos)) {
4307+
const bool bSourceAliasIsEquivalentToSourceCRS = starts_with(
4308+
aliasOpSourceCRS->nameStr(), sourceCRS->nameStr());
4309+
auto newOp = aliasOp->shallowClone();
4310+
// Patch the transformation to modify its source/target CRS
4311+
// to the expected one
4312+
if (bSourceAliasIsEquivalentToSourceCRS) {
4313+
setCRSs(newOp.get(), sourceCRS, targetCRS);
4314+
} else {
4315+
setCRSs(newOp.get(), targetCRS, sourceCRS);
4316+
newOp = newOp->inverse();
4317+
}
4318+
4319+
const auto getNatureOf =
4320+
[](const crs::GeodeticCRS &crs) -> std::string {
4321+
if (dynamic_cast<const crs::GeographicCRS *>(&crs) !=
4322+
nullptr) {
4323+
if (crs.coordinateSystem()->axisList().size() == 2)
4324+
return "geographic2D";
4325+
else
4326+
return "geographic3D";
4327+
} else {
4328+
return "geodetic";
4329+
}
4330+
};
4331+
// TODO? Ideally we'd create 2D<-->3D<-->geocentric conversions
4332+
// when needed
4333+
const auto newOpSrc = dynamic_cast<const crs::GeodeticCRS *>(
4334+
newOp->sourceCRS().get());
4335+
const auto newOpDst = dynamic_cast<const crs::GeodeticCRS *>(
4336+
newOp->targetCRS().get());
4337+
if (newOpSrc && newOpDst &&
4338+
getNatureOf(*newOpSrc) == getNatureOf(*geodSrc) &&
4339+
getNatureOf(*newOpDst) == getNatureOf(*geodDst)) {
4340+
res.emplace_back(newOp);
4341+
doFilterAndCheckPerfectOp = true;
4342+
}
4343+
}
4344+
}
4345+
}
4346+
42734347
if (doFilterAndCheckPerfectOp) {
42744348
// If we get at least a result with perfect accuracy, do not bother
42754349
// generating synthetic transforms.

test/unit/gie_self_tests.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,15 +1297,15 @@ TEST(gie, proj_create_crs_to_crs_PULKOVO42_ETRS89) {
12971297
c.xyzt.z = 0;
12981298
c.xyzt.t = HUGE_VAL;
12991299
c = proj_trans(P, PJ_FWD, c);
1300-
EXPECT_NEAR(c.xy.x, 45.000346961, 1e-9);
1301-
EXPECT_NEAR(c.xy.y, 25.001524075, 1e-9);
1300+
EXPECT_NEAR(c.xy.x, 44.999701238, 1e-9);
1301+
EXPECT_NEAR(c.xy.y, 24.998474948, 1e-9);
13021302
EXPECT_EQ(std::string(proj_pj_info(P).definition),
13031303
"proj=pipeline step proj=axisswap order=2,1 "
13041304
"step proj=unitconvert xy_in=deg xy_out=rad "
13051305
"step proj=push v_3 "
13061306
"step proj=cart "
1307-
"ellps=krass step proj=helmert x=68.1564 y=32.7756 z=80.2249 "
1308-
"rx=2.20333014 ry=2.19256447 rz=-2.54166911 s=-0.14155333 "
1307+
"ellps=krass step proj=helmert x=2.3287 y=-147.0425 z=-92.0802 "
1308+
"rx=0.3092483 ry=-0.32482185 rz=-0.49729934 s=5.68906266 "
13091309
"convention=coordinate_frame step inv proj=cart ellps=GRS80 "
13101310
"step proj=pop v_3 "
13111311
"step proj=unitconvert xy_in=rad xy_out=deg step proj=axisswap "
@@ -1322,8 +1322,8 @@ TEST(gie, proj_create_crs_to_crs_PULKOVO42_ETRS89) {
13221322
proj_trans_generic(P, PJ_FWD, &(c.xyz.x), sizeof(double), 1, &(c.xyz.y),
13231323
sizeof(double), 1, &(c.xyz.z), sizeof(double), 1,
13241324
nullptr, 0, 0);
1325-
EXPECT_NEAR(c.xy.x, 45.000346961, 1e-9);
1326-
EXPECT_NEAR(c.xy.y, 25.001524075, 1e-9);
1325+
EXPECT_NEAR(c.xy.x, 44.999701238, 1e-9);
1326+
EXPECT_NEAR(c.xy.y, 24.998474948, 1e-9);
13271327

13281328
// Poland
13291329
c.xyz.x = 52; // Lat

test/unit/test_network.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,7 +1386,8 @@ TEST(networking, network_endpoint_api_and_not_reachable_hgridshift) {
13861386
EXPECT_EQ(c2.xyz.x, HUGE_VAL);
13871387
EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR);
13881388
PJ *last_op = proj_trans_get_last_used_operation(P);
1389-
EXPECT_STREQ(proj_pj_info(last_op).description, "MGI to ETRS89 (5)");
1389+
EXPECT_STREQ(proj_pj_info(last_op).description,
1390+
"MGI to ETRS89-AUT [2002] (8)");
13901391
proj_destroy(last_op);
13911392
}
13921393

@@ -1398,7 +1399,8 @@ TEST(networking, network_endpoint_api_and_not_reachable_hgridshift) {
13981399
EXPECT_EQ(c2.xyz.x, HUGE_VAL);
13991400
EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR);
14001401
PJ *last_op = proj_trans_get_last_used_operation(P);
1401-
EXPECT_STREQ(proj_pj_info(last_op).description, "MGI to ETRS89 (5)");
1402+
EXPECT_STREQ(proj_pj_info(last_op).description,
1403+
"MGI to ETRS89-AUT [2002] (8)");
14021404
proj_destroy(last_op);
14031405
}
14041406

@@ -1410,7 +1412,8 @@ TEST(networking, network_endpoint_api_and_not_reachable_hgridshift) {
14101412
EXPECT_EQ(c2.xyz.x, HUGE_VAL);
14111413
EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR);
14121414
PJ *last_op = proj_trans_get_last_used_operation(P);
1413-
EXPECT_STREQ(proj_pj_info(last_op).description, "MGI to ETRS89 (5)");
1415+
EXPECT_STREQ(proj_pj_info(last_op).description,
1416+
"MGI to ETRS89-AUT [2002] (8)");
14141417
proj_destroy(last_op);
14151418
}
14161419

test/unit/test_operationfactory.cpp

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -95,20 +95,24 @@ TEST(operation, geogCRS_to_geogCRS_context_default) {
9595
authFactory->createCoordinateReferenceSystem("4179"), // Pulkovo 42
9696
authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
9797
ctxt);
98-
ASSERT_EQ(list.size(), 3U);
98+
ASSERT_EQ(list.size(), 4U);
9999
// Romania has a larger area than Poland (given our approx formula)
100-
EXPECT_EQ(list[0]->getEPSGCode(), 15993); // Romania - 10m
101-
EXPECT_EQ(list[1]->getEPSGCode(), 1644); // Poland - 1m
102-
EXPECT_EQ(list[2]->nameStr(),
100+
EXPECT_EQ(
101+
list[0]->nameStr(),
102+
"Pulkovo 1942(58) to ETRS89-ROU [ETRF2000] (4)"); // Romania - 10m
103+
EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
104+
EXPECT_EQ(list[1]->getEPSGCode(), 15993); // Romania - 10m
105+
EXPECT_EQ(list[2]->getEPSGCode(), 1644); // Poland - 1m
106+
EXPECT_EQ(list[3]->nameStr(),
103107
"Ballpark geographic offset from Pulkovo 1942(58) to ETRS89");
104108

105109
EXPECT_EQ(
106110
list[0]->exportToPROJString(PROJStringFormatter::create().get()),
107111
"+proj=pipeline +step +proj=axisswap +order=2,1 +step "
108112
"+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
109-
"+step +proj=cart +ellps=krass +step +proj=helmert +x=68.1564 "
110-
"+y=32.7756 +z=80.2249 +rx=2.20333014 +ry=2.19256447 "
111-
"+rz=-2.54166911 +s=-0.14155333 +convention=coordinate_frame +step "
113+
"+step +proj=cart +ellps=krass +step +proj=helmert +x=2.3287 "
114+
"+y=-147.0425 +z=-92.0802 +rx=0.3092483 +ry=-0.32482185 "
115+
"+rz=-0.49729934 +s=5.68906266 +convention=coordinate_frame +step "
112116
"+inv +proj=cart +ellps=GRS80 +step +proj=pop +v_3 +step "
113117
"+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
114118
"+order=2,1");
@@ -119,18 +123,21 @@ TEST(operation, geogCRS_to_geogCRS_context_default) {
119123
auto list = CoordinateOperationFactory::create()->createOperations(
120124
authFactory->createCoordinateReferenceSystem("4258"),
121125
authFactory->createCoordinateReferenceSystem("4179"), ctxt);
122-
ASSERT_EQ(list.size(), 3U);
126+
ASSERT_EQ(list.size(), 4U);
123127
// Romania has a larger area than Poland (given our approx formula)
124-
EXPECT_EQ(list[0]->nameStr(),
125-
"Inverse of Pulkovo 1942(58) to ETRS89 (3)"); // Romania - 10m
128+
EXPECT_EQ(
129+
list[0]->nameStr(),
130+
"Inverse of Pulkovo 1942(58) to ETRS89-ROU [ETRF2000] (4)"); // Romania
131+
// -
132+
// 10m
126133

127134
EXPECT_EQ(
128135
list[0]->exportToPROJString(PROJStringFormatter::create().get()),
129136
"+proj=pipeline +step +proj=axisswap +order=2,1 +step "
130137
"+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
131-
"+step +proj=cart +ellps=GRS80 +step +inv +proj=helmert +x=68.1564 "
132-
"+y=32.7756 +z=80.2249 +rx=2.20333014 +ry=2.19256447 "
133-
"+rz=-2.54166911 +s=-0.14155333 +convention=coordinate_frame +step "
138+
"+step +proj=cart +ellps=GRS80 +step +inv +proj=helmert +x=2.3287 "
139+
"+y=-147.0425 +z=-92.0802 +rx=0.3092483 +ry=-0.32482185 "
140+
"+rz=-0.49729934 +s=5.68906266 +convention=coordinate_frame +step "
134141
"+inv +proj=cart +ellps=krass +step +proj=pop +v_3 +step "
135142
"+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
136143
"+order=2,1");
@@ -208,8 +215,9 @@ TEST(operation, geogCRS_to_geogCRS_context_filter_bbox) {
208215
auto list = CoordinateOperationFactory::create()->createOperations(
209216
authFactory->createCoordinateReferenceSystem("4179"),
210217
authFactory->createCoordinateReferenceSystem("4258"), ctxt);
211-
ASSERT_EQ(list.size(), 1U);
212-
EXPECT_EQ(list[0]->getEPSGCode(), 15993); // Romania - 10m
218+
ASSERT_EQ(list.size(), 2U);
219+
EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
220+
EXPECT_EQ(list[1]->getEPSGCode(), 15993); // Romania - 10m
213221
}
214222
{
215223
auto ctxt = CoordinateOperationContext::create(
@@ -220,8 +228,9 @@ TEST(operation, geogCRS_to_geogCRS_context_filter_bbox) {
220228
auto list = CoordinateOperationFactory::create()->createOperations(
221229
authFactory->createCoordinateReferenceSystem("4179"),
222230
authFactory->createCoordinateReferenceSystem("4258"), ctxt);
223-
ASSERT_EQ(list.size(), 1U);
224-
EXPECT_EQ(list[0]->getEPSGCode(), 15993); // Romania - 10m
231+
ASSERT_EQ(list.size(), 2U);
232+
EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
233+
EXPECT_EQ(list[1]->getEPSGCode(), 15993); // Romania - 10m
225234
}
226235
{
227236
auto ctxt = CoordinateOperationContext::create(
@@ -1510,7 +1519,7 @@ TEST(operation, geogCRS_without_id_to_geogCRS_3D_context) {
15101519
auto list =
15111520
CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
15121521
ASSERT_GE(list.size(), 1U);
1513-
auto wkt2 = "GEOGCRS[\"unnamed\",\n"
1522+
auto wkt2 = "GEOGCRS[\"Amersfoort\",\n"
15141523
" DATUM[\"Amersfoort\",\n"
15151524
" ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
15161525
" LENGTHUNIT[\"metre\",1]]],\n"
@@ -12294,10 +12303,9 @@ TEST(operation, createOperation_ETRS89_to_Amersfoort) {
1229412303
// Amersfoort
1229512304
authFactoryEPSG->createCoordinateReferenceSystem("4289"), ctxt);
1229612305
ASSERT_GE(list.size(), 1U);
12297-
// We check that we go through ETRS89-NLD [AGRS2010] to use the most
12306+
// We check that we use the most
1229812307
// precise "Amersfoort to ETRS89-NLD [AGRS2010] (9)" operation.
1229912308
EXPECT_EQ(list[0]->nameStr(),
12300-
"ETRS89 to ETRS89-NLD [AGRS2010] + "
1230112309
"Inverse of Amersfoort to ETRS89-NLD [AGRS2010] (9)");
1230212310
}
1230312311
{
@@ -12307,10 +12315,9 @@ TEST(operation, createOperation_ETRS89_to_Amersfoort) {
1230712315
// ETRS89
1230812316
authFactoryEPSG->createCoordinateReferenceSystem("4258"), ctxt);
1230912317
ASSERT_GE(list.size(), 1U);
12310-
// We check that we go through ETRS89-NLD [AGRS2010] to use the most
12318+
// We check that we use the most
1231112319
// precise "Amersfoort to ETRS89-NLD [AGRS2010] (9)" operation.
1231212320
EXPECT_EQ(list[0]->nameStr(),
12313-
"Amersfoort to ETRS89-NLD [AGRS2010] (9) + "
12314-
"Inverse of ETRS89 to ETRS89-NLD [AGRS2010]");
12321+
"Amersfoort to ETRS89-NLD [AGRS2010] (9)");
1231512322
}
1231612323
}

0 commit comments

Comments
 (0)