Skip to content

Commit 177f6f3

Browse files
authored
Merge pull request #4364 from rouault/etrf_to_etrf
Improve ETRFxxx to ETRFyyy, and WGS 84 (xxx) to WGS 84 (yyy)
2 parents 2743e42 + 52da535 commit 177f6f3

File tree

6 files changed

+411
-31
lines changed

6 files changed

+411
-31
lines changed

data/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ set(ALL_SQL_IN "${CMAKE_CURRENT_BINARY_DIR}/all.sql.in")
3131
set(PROJ_DB "${CMAKE_CURRENT_BINARY_DIR}/proj.db")
3232
include(sql_filelist.cmake)
3333

34-
set(PROJ_DB_SQL_EXPECTED_MD5 "5e23b08c318cecbd9cace26233f8f5d4")
34+
set(PROJ_DB_SQL_EXPECTED_MD5 "8145b5734f4007f81b188ac3b690e8b3")
3535

3636
add_custom_command(
3737
OUTPUT ${PROJ_DB}

data/sql/wgs84_realizations_concatenated_operations.sql

Lines changed: 188 additions & 0 deletions
Large diffs are not rendered by default.

data/sql_filelist.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ list(APPEND SQL_FILES
5858
"${SQL_DIR}/grid_alternatives.sql"
5959
"${SQL_DIR}/grid_alternatives_generated_noaa.sql"
6060
"${SQL_DIR}/nadcon5_concatenated_operations.sql"
61+
"${SQL_DIR}/wgs84_realizations_concatenated_operations.sql"
6162
"${SQL_DIR}/customizations.sql"
6263
"${SQL_DIR}/nkg_post_customizations.sql"
6364
"${SQL_DIR}/commit.sql"
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env python
2+
###############################################################################
3+
# $Id$
4+
#
5+
# Project: PROJ
6+
# Purpose: Populate data/sql/wgs84_realizations_concatenated_operations.sql
7+
# Author: Even Rouault <even.rouault at spatialys.com>
8+
#
9+
###############################################################################
10+
# Copyright (c) 2025, Even Rouault <even.rouault at spatialys.com>
11+
#
12+
# SPDX-License-Identifier: MIT
13+
###############################################################################
14+
15+
import os
16+
17+
script_dir_name = os.path.dirname(os.path.realpath(__file__))
18+
sql_dir_name = os.path.join(os.path.dirname(script_dir_name), 'data', 'sql')
19+
20+
out_filename = os.path.join(sql_dir_name, 'wgs84_realizations_concatenated_operations') + '.sql'
21+
#print(out_filename)
22+
23+
def sanitize_crs_name_for_code(name):
24+
return name.replace('(', '_').replace(')', '').replace(' ','_').replace('__','_').upper()
25+
26+
def gen_transformations(sql, transformations, crs_dict):
27+
for i in range(len(transformations)):
28+
if transformations[i][0] == "WGS 84 (G1150)" and transformations[i][1] == "WGS 84 (G1762)":
29+
continue
30+
for j in range(i+1,len(transformations)):
31+
if transformations[j][0] == "WGS 84 (G1150)" and transformations[j][1] == "WGS 84 (G1762)":
32+
continue
33+
source_crs = transformations[i][0]
34+
target_crs = transformations[j][1]
35+
36+
source_crs_week = 0 if source_crs == "WGS 84 (Transit)" else int(source_crs[len("WGS 84 (G"):-1])
37+
target_crs_week = int(target_crs[len("WGS 84 (G"):-1])
38+
39+
transfm_code = sanitize_crs_name_for_code(source_crs) + "_TO_" + sanitize_crs_name_for_code(target_crs)
40+
transfm_name = f"{source_crs} to {target_crs}"
41+
42+
source_crs_code = crs_dict[source_crs]
43+
target_crs_code = crs_dict[target_crs]
44+
45+
step = 0
46+
steps_sql = []
47+
total_acc = 0
48+
for k in range(i,j+1):
49+
source_crs_name = transformations[k][0]
50+
target_crs_name = transformations[k][1]
51+
if source_crs_week <= 1150 and target_crs_week >= 1762 and \
52+
((source_crs_name == "WGS 84 (G1150)" and target_crs_name == "WGS 84 (G1674)") or \
53+
(source_crs_name == "WGS 84 (G1674)" and target_crs_name == "WGS 84 (G1762)")):
54+
continue
55+
step += 1
56+
step_code = transformations[k][2]
57+
acc = transformations[k][3]
58+
total_acc += acc
59+
steps_sql.append(f"INSERT INTO concatenated_operation_step VALUES('PROJ','{transfm_code}',{step},'EPSG','{step_code}','forward'); -- {source_crs_name} to {target_crs_name} (EPSG:{step_code}), {acc} m\n")
60+
61+
if len(steps_sql) <= 1:
62+
continue
63+
64+
total_acc = round(total_acc * 100) / 100.0
65+
66+
sql += f"INSERT INTO concatenated_operation VALUES('PROJ','{transfm_code}','{transfm_name}','Transformation based on concatenation of transformations between WGS 84 realizations','EPSG','{source_crs_code}','EPSG','{target_crs_code}',{total_acc},NULL,0);\n"
67+
68+
for step_sql in steps_sql:
69+
sql += step_sql
70+
71+
sql += f"INSERT INTO usage VALUES('PROJ','{transfm_code}_USAGE','concatenated_operation','PROJ','{transfm_code}',\n"
72+
sql += " 'EPSG','1262', -- extent: World\n"
73+
sql += " 'EPSG','1027' -- scope: Geodesy\n"
74+
sql += ");\n"
75+
76+
sql += "\n"
77+
78+
return sql
79+
80+
crs_dict = {
81+
"WGS 84 (Transit)": 7815,
82+
"WGS 84 (G730)": 7656,
83+
"WGS 84 (G873)": 7658,
84+
"WGS 84 (G1150)": 7660,
85+
"WGS 84 (G1674)": 7662,
86+
"WGS 84 (G1762)": 7664,
87+
"WGS 84 (G2139)": 9753,
88+
"WGS 84 (G2296)": 10604,
89+
}
90+
91+
f = open(out_filename, 'wb')
92+
f.write("--- This file has been generated by scripts/build_wgs84_realizations_concatenated_operations.py. DO NOT EDIT !\n\n".encode('UTF-8'))
93+
94+
f.write("-- Concatenated accuracy is sum of accuracies\n\n".encode('UTF-8'))
95+
96+
sql = ""
97+
98+
transformations = [
99+
("WGS 84 (Transit)", "WGS 84 (G730)", 9960, 0.7), # regular Helmert
100+
("WGS 84 (G730)", "WGS 84 (G873)", 9961, 0.04), # regular Helmert
101+
("WGS 84 (G873)", "WGS 84 (G1150)", 9962, 0.03), # time-dependent Helmert
102+
("WGS 84 (G1150)", "WGS 84 (G1674)", 9963, 0.02), # time-dependent Helmert
103+
("WGS 84 (G1150)", "WGS 84 (G1762)", 7668, 0.02), # regular Helmert. Note that it doesn't go through G1674
104+
("WGS 84 (G1674)", "WGS 84 (G1762)", 7667, 0.01), # regular Helmert
105+
("WGS 84 (G1762)", "WGS 84 (G2139)", 9756, 0.01), # regular Helmert
106+
("WGS 84 (G2139)", "WGS 84 (G2296)", 10607, 0.01), # regular Helmert
107+
]
108+
109+
sql = gen_transformations(sql, transformations, crs_dict)
110+
111+
f.write(sql.encode('UTF-8'))
112+
113+
f.close()

src/iso19111/factory.cpp

Lines changed: 85 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7308,7 +7308,8 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
73087308
const std::string &code) {
73097309
return !(d->run("SELECT 1 FROM coordinate_operation_view WHERE "
73107310
"(source_crs_auth_name = ? AND source_crs_code = ?) OR "
7311-
"(target_crs_auth_name = ? AND target_crs_code = ?)",
7311+
"(target_crs_auth_name = ? AND target_crs_code = ?) "
7312+
"LIMIT 1",
73127313
{auth_name, code, auth_name, code})
73137314
.empty());
73147315
};
@@ -7407,36 +7408,50 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
74077408
"AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ");
74087409
std::string minDate;
74097410
std::string criterionOnIntermediateCRS;
7411+
7412+
const auto sourceCRS = d->createFactory(sourceCRSAuthName)
7413+
->createCoordinateReferenceSystem(sourceCRSCode);
7414+
const auto targetCRS = d->createFactory(targetCRSAuthName)
7415+
->createCoordinateReferenceSystem(targetCRSCode);
7416+
7417+
const bool ETRFtoETRF = starts_with(sourceCRS->nameStr(), "ETRF") &&
7418+
starts_with(targetCRS->nameStr(), "ETRF");
7419+
74107420
if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7411-
auto sourceCRS = d->createFactory(sourceCRSAuthName)
7412-
->createGeodeticCRS(sourceCRSCode);
7413-
auto targetCRS = d->createFactory(targetCRSAuthName)
7414-
->createGeodeticCRS(targetCRSCode);
7415-
const auto &sourceDatum = sourceCRS->datum();
7416-
const auto &targetDatum = targetCRS->datum();
7417-
if (sourceDatum && sourceDatum->publicationDate().has_value() &&
7418-
targetDatum && targetDatum->publicationDate().has_value()) {
7419-
const auto sourceDate(sourceDatum->publicationDate()->toString());
7420-
const auto targetDate(targetDatum->publicationDate()->toString());
7421-
minDate = std::min(sourceDate, targetDate);
7422-
// Check that the datum of the intermediateCRS has a publication
7423-
// date most recent that the one of the source and the target CRS
7424-
// Except when using the usual WGS84 pivot which happens to have a
7425-
// NULL publication date.
7426-
criterionOnIntermediateCRS =
7427-
"AND EXISTS(SELECT 1 FROM geodetic_crs x "
7428-
"JOIN geodetic_datum y "
7429-
"ON "
7430-
"y.auth_name = x.datum_auth_name AND "
7431-
"y.code = x.datum_code "
7432-
"WHERE "
7433-
"x.auth_name = v1.target_crs_auth_name AND "
7434-
"x.code = v1.target_crs_code AND "
7435-
"x.type IN ('geographic 2D', 'geographic 3D') AND "
7436-
"(y.publication_date IS NULL OR "
7437-
"(y.publication_date >= '" +
7438-
minDate + "'))) ";
7439-
} else {
7421+
const auto &sourceGeogCRS =
7422+
dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
7423+
const auto &targetGeogCRS =
7424+
dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
7425+
if (sourceGeogCRS && targetGeogCRS) {
7426+
const auto &sourceDatum = sourceGeogCRS->datum();
7427+
const auto &targetDatum = targetGeogCRS->datum();
7428+
if (sourceDatum && sourceDatum->publicationDate().has_value() &&
7429+
targetDatum && targetDatum->publicationDate().has_value()) {
7430+
const auto sourceDate(
7431+
sourceDatum->publicationDate()->toString());
7432+
const auto targetDate(
7433+
targetDatum->publicationDate()->toString());
7434+
minDate = std::min(sourceDate, targetDate);
7435+
// Check that the datum of the intermediateCRS has a publication
7436+
// date most recent that the one of the source and the target
7437+
// CRS Except when using the usual WGS84 pivot which happens to
7438+
// have a NULL publication date.
7439+
criterionOnIntermediateCRS =
7440+
"AND EXISTS(SELECT 1 FROM geodetic_crs x "
7441+
"JOIN geodetic_datum y "
7442+
"ON "
7443+
"y.auth_name = x.datum_auth_name AND "
7444+
"y.code = x.datum_code "
7445+
"WHERE "
7446+
"x.auth_name = v1.target_crs_auth_name AND "
7447+
"x.code = v1.target_crs_code AND "
7448+
"x.type IN ('geographic 2D', 'geographic 3D') AND "
7449+
"(y.publication_date IS NULL OR "
7450+
"(y.publication_date >= '" +
7451+
minDate + "'))) ";
7452+
}
7453+
}
7454+
if (criterionOnIntermediateCRS.empty()) {
74407455
criterionOnIntermediateCRS =
74417456
"AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
74427457
"x.auth_name = v1.target_crs_auth_name AND "
@@ -7586,6 +7601,33 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
75867601
if (discardSuperseded) {
75877602
res = filterOutSuperseded(std::move(res));
75887603
}
7604+
7605+
const auto checkPivotForETRFToETRF =
7606+
[ETRFtoETRF, &sourceCRS,
7607+
&targetCRS](const crs::CRSPtr &intermediateCRS) {
7608+
// Make sure that ETRF2000 to ETRF2014 doesn't go throught ITRF9x or
7609+
// ITRF>2014
7610+
if (ETRFtoETRF && intermediateCRS &&
7611+
starts_with(intermediateCRS->nameStr(), "ITRF")) {
7612+
const auto normalizeDate = [](int v) {
7613+
return (v >= 80 && v <= 99) ? v + 1900 : v;
7614+
};
7615+
const int srcDate = normalizeDate(
7616+
atoi(sourceCRS->nameStr().c_str() + strlen("ETRF")));
7617+
const int tgtDate = normalizeDate(
7618+
atoi(targetCRS->nameStr().c_str() + strlen("ETRF")));
7619+
const int intermDate = normalizeDate(
7620+
atoi(intermediateCRS->nameStr().c_str() + strlen("ITRF")));
7621+
if (srcDate > 0 && tgtDate > 0 && intermDate > 0) {
7622+
if (intermDate < std::min(srcDate, tgtDate) ||
7623+
intermDate > std::max(srcDate, tgtDate)) {
7624+
return false;
7625+
}
7626+
}
7627+
}
7628+
return true;
7629+
};
7630+
75897631
for (const auto &row : res) {
75907632
const auto &table1 = row[0];
75917633
const auto &auth_name1 = row[1];
@@ -7604,6 +7646,9 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
76047646
targetCRSAuthName, targetCRSCode)) {
76057647
continue;
76067648
}
7649+
if (!checkPivotForETRFToETRF(op1->targetCRS())) {
7650+
continue;
7651+
}
76077652
auto op2 =
76087653
d->createFactory(auth_name2)
76097654
->createCoordinateOperation(
@@ -7642,6 +7687,7 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
76427687
if (discardSuperseded) {
76437688
res = filterOutSuperseded(std::move(res));
76447689
}
7690+
76457691
for (const auto &row : res) {
76467692
const auto &table1 = row[0];
76477693
const auto &auth_name1 = row[1];
@@ -7660,6 +7706,9 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
76607706
targetCRSAuthName, targetCRSCode)) {
76617707
continue;
76627708
}
7709+
if (!checkPivotForETRFToETRF(op1->targetCRS())) {
7710+
continue;
7711+
}
76637712
auto op2 =
76647713
d->createFactory(auth_name2)
76657714
->createCoordinateOperation(
@@ -7737,6 +7786,9 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
77377786
targetCRSAuthName, targetCRSCode)) {
77387787
continue;
77397788
}
7789+
if (!checkPivotForETRFToETRF(op1->sourceCRS())) {
7790+
continue;
7791+
}
77407792
auto op2 =
77417793
d->createFactory(auth_name2)
77427794
->createCoordinateOperation(
@@ -7793,6 +7845,9 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
77937845
targetCRSAuthName, targetCRSCode)) {
77947846
continue;
77957847
}
7848+
if (!checkPivotForETRFToETRF(op1->sourceCRS())) {
7849+
continue;
7850+
}
77967851
auto op2 =
77977852
d->createFactory(auth_name2)
77987853
->createCoordinateOperation(

test/cli/test_projinfo.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,3 +1867,26 @@ tests:
18671867
+step +inv +proj=cart +ellps=WGS84
18681868
+step +proj=unitconvert +xy_in=rad +xy_out=deg
18691869
+step +proj=axisswap +order=2,1
1870+
- comment: >
1871+
Test that ETRF2000 to ETRF2014 only goes through ITRF2000, ITRF2008 or ITRF2014, and not ITRF9x or ITRF>2020
1872+
args: -s ETRF2000 -t ETRF2014 --3d --summary --authority EPSG
1873+
out: |
1874+
Candidate operations found: 6
1875+
unknown id, Conversion from ETRF2000 (geog3D) to ETRF2000 (geocentric) + Inverse of ITRF2014 to ETRF2000 (1) + ITRF2014 to ETRF2014 (2) + Conversion from ETRF2014 (geocentric) to ETRF2014 (geog3D), 0 m, Europe - onshore and offshore - ETRF extent - approximately 16°W to 33°E and 33°N to 84°N., time-dependent operation
1876+
unknown id, Conversion from ETRF2000 (geog3D) to ETRF2000 (geocentric) + Inverse of ITRF2014 to ETRF2000 (1) + ITRF2014 to ETRF2014 (1) + Conversion from ETRF2014 (geocentric) to ETRF2014 (geog3D), 0 m, Europe - onshore and offshore - ETRF extent - approximately 16°W to 33°E and 33°N to 84°N., time-dependent operation
1877+
unknown id, Conversion from ETRF2000 (geog3D) to ETRF2000 (geocentric) + Inverse of ITRF2008 to ETRF2000 (1) + ITRF2008 to ETRF2014 (1) + Conversion from ETRF2014 (geocentric) to ETRF2014 (geog3D), 0 m, Europe - onshore and offshore - ETRF extent - approximately 16°W to 33°E and 33°N to 84°N., time-dependent operation
1878+
unknown id, Conversion from ETRF2000 (geog3D) to ETRF2000 (geocentric) + Inverse of ITRF2005 to ETRF2000 (1) + ITRF2005 to ETRF2014 (1) + Conversion from ETRF2014 (geocentric) to ETRF2014 (geog3D), 0 m, Europe - onshore and offshore - ETRF extent - approximately 16°W to 33°E and 33°N to 84°N., time-dependent operation
1879+
unknown id, Conversion from ETRF2000 (geog3D) to ETRF2000 (geocentric) + Inverse of ITRF2000 to ETRF2000 (2) + ITRF2000 to ETRF2014 (1) + Conversion from ETRF2014 (geocentric) to ETRF2014 (geog3D), 0 m, Europe - onshore and offshore - ETRF extent - approximately 16°W to 33°E and 33°N to 84°N., time-dependent operation
1880+
unknown id, Conversion from ETRF2000 (geog3D) to ETRF2000 (geocentric) + Inverse of ITRF2000 to ETRF2000 (1) + ITRF2000 to ETRF2014 (1) + Conversion from ETRF2014 (geocentric) to ETRF2014 (geog3D), 0 m, Europe - onshore and offshore - ETRF extent - approximately 16°W to 33°E and 33°N to 84°N., time-dependent operation
1881+
- comment: >
1882+
Test that ETRF2000-PL to ETRF2014 is not negatively affected by https://github.com/OSGeo/PROJ/pull/4364 changes (the current actual result is not golden in any way, using ETRF2000 to ETRF2014 could probably make sense)
1883+
args: -s ETRF2000-PL -t ETRF2014 --summary
1884+
out: |
1885+
Candidate operations found: 1
1886+
unknown id, ETRF2000-PL to ETRS89 (1) + ETRS89 to ETRF2014, 0.1 m, Poland - onshore and offshore.
1887+
- comment: >
1888+
Test WGS 84 (G1150) to WGS 84 (G2296)
1889+
args: -s "WGS 84 (G1150)" -t "WGS 84 (G2296)" --summary
1890+
out: |
1891+
Candidate operations found: 1
1892+
unknown id, Conversion from WGS 84 (G1150) (geog2D) to WGS 84 (G1150) (geocentric) + WGS 84 (G1150) to WGS 84 (G1762) (1) + WGS 84 (G1762) to WGS 84 (G2139) (1) + WGS 84 (G2139) to WGS 84 (G2296) (1) + Conversion from WGS 84 (G2296) (geocentric) to WGS 84 (G2296) (geog2D), 0.04 m, World

0 commit comments

Comments
 (0)