aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2019-10-31 16:16:01 +0100
committerGitHub <noreply@github.com>2019-10-31 16:16:01 +0100
commit8a31e8778b95eb8e857c30f276fbf1e5047f78fe (patch)
tree4e8de19f2973912748dbc21eb0bfa93d7aec73c1
parentbb3da7f1e4a52f12b6df48f4a427a8772e63ab18 (diff)
parent81e06f42c7552494bcb3f466b0b1317341187679 (diff)
downloadPROJ-8a31e8778b95eb8e857c30f276fbf1e5047f78fe.tar.gz
PROJ-8a31e8778b95eb8e857c30f276fbf1e5047f78fe.zip
Merge pull request #1703 from rouault/improve_transformation_with_alternative_vertical_unit_and_direction
Improve transformations with alternative vertical unit and direction
-rw-r--r--data/sql/conversion.sql21
-rw-r--r--data/sql/other_transformation.sql21
-rw-r--r--include/proj/coordinateoperation.hpp3
-rw-r--r--include/proj/internal/coordinateoperation_constants.hpp4
-rw-r--r--include/proj/io.hpp4
-rwxr-xr-xscripts/build_db.py33
-rw-r--r--scripts/reference_exported_symbols.txt1
-rw-r--r--src/iso19111/coordinateoperation.cpp565
-rw-r--r--src/iso19111/factory.cpp55
-rw-r--r--src/proj_constants.h5
-rw-r--r--test/unit/test_factory.cpp13
-rw-r--r--test/unit/test_operation.cpp339
12 files changed, 861 insertions, 203 deletions
diff --git a/data/sql/conversion.sql b/data/sql/conversion.sql
index 02e96c7d..f42f0e79 100644
--- a/data/sql/conversion.sql
+++ b/data/sql/conversion.sql
@@ -739,15 +739,6 @@ INSERT INTO "conversion" VALUES('EPSG','7823','CS63 zone X6',NULL,NULL,'EPSG','4
INSERT INTO "conversion" VALUES('EPSG','7824','CS63 zone X7',NULL,NULL,'EPSG','4434','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',0.05,'EPSG','9110','EPSG','8802','Longitude of natural origin',41.3,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.0,'EPSG','9201','EPSG','8806','False easting',7300000.0,'EPSG','9001','EPSG','8807','False northing',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','7875','St. Helena Local Grid 1971',NULL,NULL,'EPSG','3183','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',-15.58,'EPSG','9110','EPSG','8802','Longitude of natural origin',-5.43,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.0,'EPSG','9201','EPSG','8806','False easting',300000.0,'EPSG','9001','EPSG','8807','False northing',2000000.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','7876','St. Helena Local Grid (Tritan)',NULL,NULL,'EPSG','3183','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',-15.58,'EPSG','9110','EPSG','8802','Longitude of natural origin',-5.43,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.0,'EPSG','9201','EPSG','8806','False easting',299483.737,'EPSG','9001','EPSG','8807','False northing',2000527.879,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','7963','Poolbeg height (ft(Br36)) to Poolbeg height (m)',NULL,NULL,'EPSG','1305','EPSG','1069','Change of Vertical Unit','EPSG','1051','Unit conversion scalar',0.3048007491,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','7972','NGVD29 height (ftUS) to NGVD29 height (m)',NULL,NULL,'EPSG','1323','EPSG','1069','Change of Vertical Unit','EPSG','1051','Unit conversion scalar',0.304800609601219,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','7978','NGVD29 height (ftUS) to NGVD29 depth (ftUS)',NULL,NULL,'EPSG','1323','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','7982','HKPD height to HKPD depth',NULL,NULL,'EPSG','3334','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','7984','KOC WD height to KOC WD depth',NULL,NULL,'EPSG','3267','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','7985','KOC WD depth to KOC WD depth (ft)',NULL,NULL,'EPSG','3267','EPSG','1069','Change of Vertical Unit','EPSG','1051','Unit conversion scalar',3.28083989501312,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','7988','NAVD88 height to NAVD88 height (ftUS)',NULL,NULL,'EPSG','3664','EPSG','1069','Change of Vertical Unit','EPSG','1051','Unit conversion scalar',3.28083333333333,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','7989','NAVD88 height to NAVD88 depth',NULL,NULL,'EPSG','4161','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','7990','NAVD88 height (ftUS) to NAVD88 depth (ftUS)',NULL,NULL,'EPSG','3664','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','7993','Albany Grid 2020',NULL,NULL,'EPSG','4439','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',0.0,'EPSG','9110','EPSG','8802','Longitude of natural origin',117.53,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.0000044,'EPSG','9201','EPSG','8806','False easting',50000.0,'EPSG','9001','EPSG','8807','False northing',4100000.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','7994','Barrow Island Grid 2020',NULL,NULL,'EPSG','4438','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',0.0,'EPSG','9110','EPSG','8802','Longitude of natural origin',115.15,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.0000022,'EPSG','9201','EPSG','8806','False easting',60000.0,'EPSG','9001','EPSG','8807','False northing',2700000.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','7995','Broome Grid 2020',NULL,NULL,'EPSG','4441','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',0.0,'EPSG','9110','EPSG','8802','Longitude of natural origin',122.2,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.00000298,'EPSG','9201','EPSG','8806','False easting',50000.0,'EPSG','9001','EPSG','8807','False northing',2300000.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
@@ -770,15 +761,8 @@ INSERT INTO "conversion" VALUES('EPSG','8011','Perth Coastal Grid',NULL,NULL,'EP
INSERT INTO "conversion" VALUES('EPSG','8012','Port Hedland Grid 2020',NULL,NULL,'EPSG','4466','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',0.0,'EPSG','9110','EPSG','8802','Longitude of natural origin',118.36,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.00000135,'EPSG','9201','EPSG','8806','False easting',50000.0,'EPSG','9001','EPSG','8807','False northing',2500000.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8033','TM Zone 20N (US survey feet)',NULL,NULL,'EPSG','4467','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',0.0,'EPSG','9102','EPSG','8802','Longitude of natural origin',-63.0,'EPSG','9102','EPSG','8805','Scale factor at natural origin',0.9996,'EPSG','9201','EPSG','8806','False easting',1640416.667,'EPSG','9003','EPSG','8807','False northing',0.0,'EPSG','9003',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8034','TM Zone 21N (US survey feet)',NULL,NULL,'EPSG','4468','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',0.0,'EPSG','9102','EPSG','8802','Longitude of natural origin',-57.0,'EPSG','9102','EPSG','8805','Scale factor at natural origin',0.9996,'EPSG','9201','EPSG','8806','False easting',1640416.667,'EPSG','9003','EPSG','8807','False northing',0.0,'EPSG','9003',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8038','Instantaneous Water Level height to Instantaneous Water Level depth',NULL,NULL,'EPSG','1262','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8039','MSL height to MSL depth',NULL,NULL,'EPSG','1262','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8040','Gusterberg Grid',NULL,NULL,'EPSG','4455','EPSG','9806','Cassini-Soldner','EPSG','8801','Latitude of natural origin',48.021847,'EPSG','9110','EPSG','8802','Longitude of natural origin',31.481505,'EPSG','9110','EPSG','8806','False easting',0.0,'EPSG','9001','EPSG','8807','False northing',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8041','St. Stephen Grid',NULL,NULL,'EPSG','4456','EPSG','9806','Cassini-Soldner','EPSG','8801','Latitude of natural origin',48.123154,'EPSG','9110','EPSG','8802','Longitude of natural origin',34.022732,'EPSG','9110','EPSG','8806','False easting',0.0,'EPSG','9001','EPSG','8807','False northing',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8054','MSL height to MSL height (ft)',NULL,NULL,'EPSG','1262','EPSG','1069','Change of Vertical Unit','EPSG','1051','Unit conversion scalar',3.28083989501312,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8055','MSL height to MSL height (ftUS)',NULL,NULL,'EPSG','1245','EPSG','1069','Change of Vertical Unit','EPSG','1051','Unit conversion scalar',3.28083333333333,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8056','MSL depth to MSL depth (ft)',NULL,NULL,'EPSG','1262','EPSG','1069','Change of Vertical Unit','EPSG','1051','Unit conversion scalar',3.28083989501312,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8057','MSL depth to MSL depth (ftUS)',NULL,NULL,'EPSG','1245','EPSG','1069','Change of Vertical Unit','EPSG','1051','Unit conversion scalar',3.28083333333333,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8060','Baltic 1977 height to Baltic 1977 depth',NULL,NULL,'EPSG','2423','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8061','Pima County zone 1 East (ft)',NULL,NULL,'EPSG','4472','EPSG','9815','Hotine Oblique Mercator (variant B)','EPSG','8811','Latitude of projection centre',32.15,'EPSG','9110','EPSG','8812','Longitude of projection centre',-111.24,'EPSG','9110','EPSG','8813','Azimuth of initial line',45.0,'EPSG','9102','EPSG','8814','Angle from Rectified to Skew Grid',45.0,'EPSG','9102','EPSG','8815','Scale factor on initial line',1.00011,'EPSG','9201','EPSG','8816','Easting at projection centre',160000.0,'EPSG','9002','EPSG','8817','Northing at projection centre',800000.0,'EPSG','9002',0);
INSERT INTO "conversion" VALUES('EPSG','8062','Pima County zone 2 Central (ft)',NULL,NULL,'EPSG','4460','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',31.15,'EPSG','9110','EPSG','8802','Longitude of natural origin',-112.1,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.00009,'EPSG','9201','EPSG','8806','False easting',1800000.0,'EPSG','9002','EPSG','8807','False northing',1000000.0,'EPSG','9002',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8063','Pima County zone 3 West (ft)',NULL,NULL,'EPSG','4450','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',31.3,'EPSG','9110','EPSG','8802','Longitude of natural origin',-113.1,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.000055,'EPSG','9201','EPSG','8806','False easting',600000.0,'EPSG','9002','EPSG','8807','False northing',0.0,'EPSG','9002',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
@@ -786,7 +770,6 @@ INSERT INTO "conversion" VALUES('EPSG','8064','Pima County zone 4 Mt. Lemmon (ft
INSERT INTO "conversion" VALUES('EPSG','8080','MTM Nova Scotia zone 4 v2',NULL,NULL,'EPSG','1534','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',0.0,'EPSG','9110','EPSG','8802','Longitude of natural origin',-61.3,'EPSG','9110','EPSG','8805','Scale factor at natural origin',0.9999,'EPSG','9201','EPSG','8806','False easting',24500000.0,'EPSG','9001','EPSG','8807','False northing',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8081','MTM Nova Scotia zone 5 v2',NULL,NULL,'EPSG','1535','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',0.0,'EPSG','9110','EPSG','8802','Longitude of natural origin',-64.3,'EPSG','9110','EPSG','8805','Scale factor at natural origin',0.9999,'EPSG','9201','EPSG','8806','False easting',25500000.0,'EPSG','9001','EPSG','8807','False northing',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8087','Iceland Lambert 2016',NULL,NULL,'EPSG','1120','EPSG','9802','Lambert Conic Conformal (2SP)','EPSG','8821','Latitude of false origin',65.0,'EPSG','9110','EPSG','8822','Longitude of false origin',-19.0,'EPSG','9110','EPSG','8823','Latitude of 1st standard parallel',64.15,'EPSG','9110','EPSG','8824','Latitude of 2nd standard parallel',65.45,'EPSG','9110','EPSG','8826','Easting at false origin',2700000.0,'EPSG','9001','EPSG','8827','Northing at false origin',300000.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8229','NAVD88 height to NAVD88 height (ft)',NULL,NULL,'EPSG','4464','EPSG','1069','Change of Vertical Unit','EPSG','1051','Unit conversion scalar',3.28083989501312,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8273','Oregon Burns-Harper zone (meters)',NULL,NULL,'EPSG','4459','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',43.3,'EPSG','9110','EPSG','8802','Longitude of natural origin',-117.4,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.00014,'EPSG','9201','EPSG','8806','False easting',90000.0,'EPSG','9001','EPSG','8807','False northing',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8274','Oregon Burns-Harper zone (International feet)',NULL,NULL,'EPSG','4459','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',43.3,'EPSG','9110','EPSG','8802','Longitude of natural origin',-117.4,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.00014,'EPSG','9201','EPSG','8806','False easting',295275.5906,'EPSG','9002','EPSG','8807','False northing',0.0,'EPSG','9002',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8275','Oregon Canyon City-Burns zone (meters)',NULL,NULL,'EPSG','4465','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',43.3,'EPSG','9110','EPSG','8802','Longitude of natural origin',-119.0,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.00022,'EPSG','9201','EPSG','8806','False easting',20000.0,'EPSG','9001','EPSG','8807','False northing',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
@@ -825,10 +808,6 @@ INSERT INTO "conversion" VALUES('EPSG','8307','Oregon Warner Highway zone (meter
INSERT INTO "conversion" VALUES('EPSG','8308','Oregon Warner Highway zone (International feet)',NULL,NULL,'EPSG','4486','EPSG','9801','Lambert Conic Conformal (1SP)','EPSG','8801','Latitude of natural origin',42.3,'EPSG','9110','EPSG','8802','Longitude of natural origin',-120.0,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.000245,'EPSG','9201','EPSG','8806','False easting',131233.5958,'EPSG','9002','EPSG','8807','False northing',196850.3937,'EPSG','9002',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8309','Oregon Willamette Pass zone (meters)',NULL,NULL,'EPSG','4488','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',43.0,'EPSG','9110','EPSG','8802','Longitude of natural origin',-122.0,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.000223,'EPSG','9201','EPSG','8806','False easting',20000.0,'EPSG','9001','EPSG','8807','False northing',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8310','Oregon Willamette Pass zone (International feet)',NULL,NULL,'EPSG','4488','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',43.0,'EPSG','9110','EPSG','8802','Longitude of natural origin',-122.0,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.000223,'EPSG','9201','EPSG','8806','False easting',65616.7979,'EPSG','9002','EPSG','8807','False northing',0.0,'EPSG','9002',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8354','Black Sea height to Black Sea depth',NULL,NULL,'EPSG','3251','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8355','AIOC95 height to AIOC95 depth',NULL,NULL,'EPSG','2592','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8356','Caspian height to Caspian depth',NULL,NULL,'EPSG','1291','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
-INSERT INTO "conversion" VALUES('EPSG','8359','Baltic 1957 height to Baltic 1957 depth',NULL,NULL,'EPSG','1306','EPSG','1068','Height Depth Reversal',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8373','NCRS Las Vegas zone (m)',NULL,NULL,'EPSG','4485','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',36.15,'EPSG','9110','EPSG','8802','Longitude of natural origin',-114.58,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.0001,'EPSG','9201','EPSG','8806','False easting',100000.0,'EPSG','9001','EPSG','8807','False northing',200000.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8374','NCRS Las Vegas zone (ftUS)',NULL,NULL,'EPSG','4485','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',36.15,'EPSG','9110','EPSG','8802','Longitude of natural origin',-114.58,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.0001,'EPSG','9201','EPSG','8806','False easting',328083.3333,'EPSG','9003','EPSG','8807','False northing',656166.6667,'EPSG','9003',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "conversion" VALUES('EPSG','8375','NCRS Las Vegas high elevation zone (m)',NULL,NULL,'EPSG','4487','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',36.15,'EPSG','9110','EPSG','8802','Longitude of natural origin',-114.58,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.000135,'EPSG','9201','EPSG','8806','False easting',300000.0,'EPSG','9001','EPSG','8807','False northing',400000.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
diff --git a/data/sql/other_transformation.sql b/data/sql/other_transformation.sql
index 152e5f82..b4ad598c 100644
--- a/data/sql/other_transformation.sql
+++ b/data/sql/other_transformation.sql
@@ -192,11 +192,32 @@ INSERT INTO "other_transformation" VALUES('EPSG','7653','EGM96 height to Kumul 3
INSERT INTO "other_transformation" VALUES('EPSG','7654','EGM2008 height to Kiunga height (1)','','Derivation of heights referenced to Kiunga vertical CRS (CRS code 7652).','EPSG','9616','Vertical Offset','EPSG','3855','EPSG','7652','EPSG','4383',0.0,'EPSG','8603','Vertical Offset',-3.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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'QC-Png Kiunga',0);
INSERT INTO "other_transformation" VALUES('EPSG','7873','EGM96 height to POM96 height (1)','','Derivation of POM96 heights.','EPSG','9616','Vertical Offset','EPSG','5773','EPSG','7832','EPSG','4425',0.0,'EPSG','8603','Vertical Offset',-1.58,'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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'QC-Png Gulf-Cen',0);
INSERT INTO "other_transformation" VALUES('EPSG','7874','EGM2008 height to POM08 height (1)','','Derivation of POM08 heights.','EPSG','9616','Vertical Offset','EPSG','3855','EPSG','7841','EPSG','4425',0.0,'EPSG','8603','Vertical Offset',-0.93,'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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'QC-Png Gulf-Cen',0);
+INSERT INTO "other_transformation" VALUES('EPSG','7963','Poolbeg height (ft(Br36)) to Poolbeg height (m)','Change of unit from British foot (1936) [ft(BR36)] to metre.','Change of unit to facilitate transformation of heights or depths through concatenated operations.','EPSG','1069','Change of Vertical Unit','EPSG','5754','EPSG','7962','EPSG','1305',NULL,'EPSG','1051','Unit conversion scalar',0.3048007491,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
INSERT INTO "other_transformation" VALUES('EPSG','7964','Poolbeg height (m) to Malin Head height (1)','Poolbeg vertical reference surface is 2.7m below Malin Head surface. Transformations are subject to localised anomalies.','Change of height to a different vertical reference surface for topographic mapping and engineering survey. Accuracy 0.1m.','EPSG','9616','Vertical Offset','EPSG','7962','EPSG','5731','EPSG','1305',0.1,'EPSG','8603','Vertical Offset',-2.7,'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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'OSI-Ire',0);
INSERT INTO "other_transformation" VALUES('EPSG','7966','Poolbeg height (m) to Belfast height (1)','Poolbeg vertical reference surface is 2.7m below Belfast surface. Transformations are subject to localised anomalies.','Change of height to a different vertical reference surface for topographic mapping and engineering survey. Accuracy 0.1m.','EPSG','9616','Vertical Offset','EPSG','7962','EPSG','5732','EPSG','1305',0.1,'EPSG','8603','Vertical Offset',-2.7,'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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'OSI-Ire',0);
+INSERT INTO "other_transformation" VALUES('EPSG','7972','NGVD29 height (ftUS) to NGVD29 height (m)','Change of unit from US survey foot (ftUS) to metre. 1 ftUS = (12/39.37)m ≈ 0.304800609601219m.','Change of unit to facilitate transformation of heights or depths through concatenated operations.','EPSG','1069','Change of Vertical Unit','EPSG','5702','EPSG','7968','EPSG','1323',NULL,'EPSG','1051','Unit conversion scalar',0.304800609601219,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
INSERT INTO "other_transformation" VALUES('EPSG','7977','HKPD depth to HKCD depth (1)','The HKPD vertical reference surface is 0.146m above the HKCD surface.','Change of depth to a different vertical reference surface.','EPSG','9616','Vertical Offset','EPSG','7976','EPSG','5739','EPSG','3335',0.0,'EPSG','8603','Vertical Offset',-0.146,'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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'SMO-HK',0);
+INSERT INTO "other_transformation" VALUES('EPSG','7978','NGVD29 height (ftUS) to NGVD29 depth (ftUS)','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','5702','EPSG','6359','EPSG','1323',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
INSERT INTO "other_transformation" VALUES('EPSG','7980','KOC CD height to KOC WD height (1)','The KOC CD vertical reference surface is 4.74m (15.55ft) below KOC WD surface.','Change of height to a different vertical reference surface.','EPSG','9616','Vertical Offset','EPSG','5790','EPSG','7979','EPSG','3267',0.1,'EPSG','8603','Vertical Offset',-4.74,'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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'KOC-Kwt',0);
INSERT INTO "other_transformation" VALUES('EPSG','7981','Kuwait PWD height to KOC WD height (1)','The KOC WD vertical reference surface is 4.25m above the Kuwait PWD surface.','Change of height to a different vertical reference surface.','EPSG','9616','Vertical Offset','EPSG','5788','EPSG','7979','EPSG','3267',0.1,'EPSG','8603','Vertical Offset',-4.25,'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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'KOC-Kwt',0);
+INSERT INTO "other_transformation" VALUES('EPSG','7982','HKPD height to HKPD depth','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','5738','EPSG','7976','EPSG','3334',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','7984','KOC WD height to KOC WD depth','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','7979','EPSG','5789','EPSG','3267',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','7985','KOC WD depth to KOC WD depth (ft)','Change of unit from metre to International foot (ft). 1ft = 0.3048m.','Change of unit to facilitate transformation of heights or depths through concatenated operations.','EPSG','1069','Change of Vertical Unit','EPSG','5789','EPSG','5614','EPSG','3267',NULL,'EPSG','1051','Unit conversion scalar',3.28083989501312,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','7988','NAVD88 height to NAVD88 height (ftUS)','Change of unit from metre to US survey foot. 1 ftUS = (12/39.37)m ≈ 0.304800609601219m.','Change of unit to facilitate transformation of heights or depths through concatenated operations.','EPSG','1069','Change of Vertical Unit','EPSG','5703','EPSG','6360','EPSG','3664',NULL,'EPSG','1051','Unit conversion scalar',3.28083333333333,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','7989','NAVD88 height to NAVD88 depth','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','5703','EPSG','6357','EPSG','4161',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','7990','NAVD88 height (ftUS) to NAVD88 depth (ftUS)','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','6360','EPSG','6358','EPSG','3664',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8038','Instantaneous Water Level height to Instantaneous Water Level depth','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','5829','EPSG','5831','EPSG','1262',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8039','MSL height to MSL depth','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','5714','EPSG','5715','EPSG','1262',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8054','MSL height to MSL height (ft)','Change of unit from metre to International foot (ft). 1ft = 0.3048m.','Change of unit to facilitate transformation of heights or depths through concatenated operations.','EPSG','1069','Change of Vertical Unit','EPSG','5714','EPSG','8050','EPSG','1262',NULL,'EPSG','1051','Unit conversion scalar',3.28083989501312,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8055','MSL height to MSL height (ftUS)','Change of unit from metre to US survey foot (ftUS). 1 ftUS = (12/39.37)m ≈ 0.304800609601219m.','Change of unit to facilitate transformation of heights or depths through concatenated operations.','EPSG','1069','Change of Vertical Unit','EPSG','5714','EPSG','8052','EPSG','1245',NULL,'EPSG','1051','Unit conversion scalar',3.28083333333333,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8056','MSL depth to MSL depth (ft)','Change of unit from metre to International foot (ft). 1ft = 0.3048m.','Change of unit to facilitate transformation of heights or depths through concatenated operations.','EPSG','1069','Change of Vertical Unit','EPSG','5715','EPSG','8051','EPSG','1262',NULL,'EPSG','1051','Unit conversion scalar',3.28083989501312,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8057','MSL depth to MSL depth (ftUS)','Change of unit from metre to US survey foot (ftUS). 1 ftUS = (12/39.37)m ≈ 0.304800609601219m.','Change of unit to facilitate transformation of heights or depths through concatenated operations.','EPSG','1069','Change of Vertical Unit','EPSG','5715','EPSG','8053','EPSG','1245',NULL,'EPSG','1051','Unit conversion scalar',3.28083333333333,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8060','Baltic 1977 height to Baltic 1977 depth','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','5705','EPSG','5612','EPSG','2423',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8229','NAVD88 height to NAVD88 height (ft)','Change of unit from metre to International foot (ft). 1ft = 0.3048m.','Change of unit to facilitate transformation of heights or depths through concatenated operations for States of the US which have adopted International feet for their State Plane coordinate systems.','EPSG','1069','Change of Vertical Unit','EPSG','5703','EPSG','8228','EPSG','4464',NULL,'EPSG','1051','Unit conversion scalar',3.28083989501312,'EPSG','9201',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8354','Black Sea height to Black Sea depth','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','5735','EPSG','5336','EPSG','3251',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8355','AIOC95 height to AIOC95 depth','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','5797','EPSG','5734','EPSG','2592',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8356','Caspian height to Caspian depth','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','5611','EPSG','5706','EPSG','1291',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
+INSERT INTO "other_transformation" VALUES('EPSG','8359','Baltic 1957 height to Baltic 1957 depth','Change of axis positive direction from up to down.','Change of axis positive direction to facilitate transformation of heights or depths through concatenated operations.','EPSG','1068','Height Depth Reversal','EPSG','8357','EPSG','8358','EPSG','1306',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,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
INSERT INTO "other_transformation" VALUES('EPSG','10087','Jamaica 1875 / Jamaica (Old Grid) to JAD69 / Jamaica National Grid (1)','Derived by least squares fit at primary triangulation stations. Accuracy will be less outside of this network due to extrapolation.','Topographic mapping.','EPSG','9624','Affine parametric transformation','EPSG','24100','EPSG','24200','EPSG','3342',1.5,'EPSG','8623','A0',82357.457,'EPSG','9001','EPSG','8624','A1',0.304794369,'EPSG','9203','EPSG','8625','A2',1.5417425e-05,'EPSG','9203','EPSG','8639','B0',28091.324,'EPSG','9001','EPSG','8640','B1',-1.5417425e-05,'EPSG','9203','EPSG','8641','B2',0.304794369,'EPSG','9203',NULL,NULL,NULL,NULL,NULL,NULL,'SD-Jam',0);
INSERT INTO "other_transformation" VALUES('EPSG','10088','JAD69 / Jamaica National Grid to Jamaica 1875 / Jamaica (Old Grid) (1)','Derived by least squares fit at primary triangulation stations. Accuracy will be less outside of this network due to extrapolation.','Topographic mapping.','EPSG','9624','Affine parametric transformation','EPSG','24200','EPSG','24100','EPSG','3342',1.5,'EPSG','8623','A0',-270201.96,'EPSG','9005','EPSG','8624','A1',0.00016595792,'EPSG','9203','EPSG','8625','A2',3.2809005,'EPSG','9203','EPSG','8639','B0',-92178.51,'EPSG','9005','EPSG','8640','B1',3.2809005,'EPSG','9203','EPSG','8641','B2',-0.00016595792,'EPSG','9203',NULL,NULL,NULL,NULL,NULL,NULL,'SD-Jam',1);
INSERT INTO "other_transformation" VALUES('EPSG','10095','Mauritania 1999 / UTM zone 28N to WGS 84 / UTM zone 28N (1)','Parameter values consistent with the OGP Affine parametric transformation method derived by OGP from the published Helmert 2D parameter values.','Minerals management.','EPSG','9624','Affine parametric transformation','EPSG','3103','EPSG','32628','EPSG','2971',40.0,'EPSG','8623','A0',NULL,'EPSG',NULL,'EPSG','8624','A1',NULL,'EPSG',NULL,'EPSG','8625','A2',NULL,'EPSG',NULL,'EPSG','8639','B0',NULL,'EPSG',NULL,'EPSG','8640','B1',NULL,'EPSG',NULL,'EPSG','8641','B2',NULL,'EPSG',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'MMI-Mau W',1);
diff --git a/include/proj/coordinateoperation.hpp b/include/proj/coordinateoperation.hpp
index 6c4c25c2..1ced5333 100644
--- a/include/proj/coordinateoperation.hpp
+++ b/include/proj/coordinateoperation.hpp
@@ -1323,6 +1323,9 @@ class PROJ_GCC_DLL Conversion : public SingleOperation {
createChangeVerticalUnit(const util::PropertyMap &properties,
const common::Scale &factor);
+ PROJ_DLL static ConversionNNPtr
+ createHeightDepthReversal(const util::PropertyMap &properties);
+
PROJ_DLL static ConversionNNPtr createAxisOrderReversal(bool is3D);
PROJ_DLL static ConversionNNPtr
diff --git a/include/proj/internal/coordinateoperation_constants.hpp b/include/proj/internal/coordinateoperation_constants.hpp
index f1925c9b..eb0bb8c5 100644
--- a/include/proj/internal/coordinateoperation_constants.hpp
+++ b/include/proj/internal/coordinateoperation_constants.hpp
@@ -826,6 +826,7 @@ static const struct MethodNameCode {
METHOD_NAME_CODE(VERTICAL_PERSPECTIVE),
// Other conversions
METHOD_NAME_CODE(CHANGE_VERTICAL_UNIT),
+ METHOD_NAME_CODE(HEIGHT_DEPTH_REVERSAL),
METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_2D),
METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_3D),
METHOD_NAME_CODE(GEOGRAPHIC_GEOCENTRIC),
@@ -1153,6 +1154,9 @@ static const MethodMapping otherMethodMappings[] = {
{EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT,
EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT, nullptr, nullptr, nullptr,
paramsChangeVerticalUnit},
+ {EPSG_NAME_METHOD_HEIGHT_DEPTH_REVERSAL,
+ EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL, nullptr, nullptr, nullptr,
+ paramsChangeVerticalUnit},
{EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_2D,
EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D, nullptr, nullptr, nullptr,
nullptr},
diff --git a/include/proj/io.hpp b/include/proj/io.hpp
index 12b3b111..37b94c1e 100644
--- a/include/proj/io.hpp
+++ b/include/proj/io.hpp
@@ -1089,6 +1089,10 @@ class PROJ_GCC_DLL AuthorityFactory {
const std::string &datum_code,
const std::string &geodetic_crs_type) const;
+ PROJ_INTERNAL std::list<crs::VerticalCRSNNPtr>
+ createVerticalCRSFromDatum(const std::string &datum_auth_name,
+ const std::string &datum_code) const;
+
PROJ_INTERNAL std::list<crs::GeodeticCRSNNPtr>
createGeodeticCRSFromEllipsoid(const std::string &ellipsoid_auth_name,
const std::string &ellipsoid_code,
diff --git a/scripts/build_db.py b/scripts/build_db.py
index f52883cf..ba1502ae 100755
--- a/scripts/build_db.py
+++ b/scripts/build_db.py
@@ -137,7 +137,12 @@ BEFORE INSERT ON conversion
BEGIN
"""
- proj_db_cursor.execute("SELECT coord_op_code, coord_op_name, area_of_use_code, coord_op_method_code, coord_op_method_name, epsg_coordoperation.deprecated FROM epsg.epsg_coordoperation LEFT JOIN epsg.epsg_coordoperationmethod USING (coord_op_method_code) WHERE coord_op_type = 'conversion' AND coord_op_name NOT LIKE '%to DMSH'")
+ # 1068 and 1069 are Height Depth Reversal and Change of Vertical Unit
+ # In EPSG, there is one generic instance of those as 7812 and 7813 that
+ # don't refer to particular CRS, and instances pointing to CRS names
+ # The later are imported in the other_transformation table since we recover
+ # the source/target CRS names from the transformation name.
+ proj_db_cursor.execute("SELECT coord_op_code, coord_op_name, area_of_use_code, coord_op_method_code, coord_op_method_name, epsg_coordoperation.deprecated FROM epsg.epsg_coordoperation LEFT JOIN epsg.epsg_coordoperationmethod USING (coord_op_method_code) WHERE coord_op_type = 'conversion' AND coord_op_name NOT LIKE '%to DMSH' AND (coord_op_method_code NOT IN (1068, 1069) OR coord_op_code IN (7812,7813))")
for (code, name, area_of_use_code, method_code, method_name, deprecated) in proj_db_cursor.fetchall():
expected_order = 1
max_n_params = 7
@@ -423,8 +428,31 @@ def fill_other_transformation(proj_db_cursor):
# 9619: Geographic2D offsets
# 9624: Affine Parametric Transformation
# 9660: Geographic3D offsets
- 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, area_of_use_code, coord_op_accuracy, coord_tfm_version, epsg_coordoperation.deprecated, coord_op_scope, 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 (9601, 9616, 9618, 9619, 9624, 9660)")
+ # 1068: Height Depth Reversal
+ # 1069: Change of Vertical Unit
+ 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, area_of_use_code, coord_op_accuracy, coord_tfm_version, epsg_coordoperation.deprecated, coord_op_scope, epsg_coordoperation.remarks FROM epsg.epsg_coordoperation LEFT JOIN epsg.epsg_coordoperationmethod USING (coord_op_method_code) WHERE coord_op_method_code IN (9601, 9616, 9618, 9619, 9624, 9660, 1068, 1069)")
for (code, name, method_code, method_name, source_crs_code, target_crs_code, area_of_use_code, coord_op_accuracy, coord_tfm_version, deprecated, scope, remarks) in proj_db_cursor.fetchall():
+
+ # 1068 and 1069 are Height Depth Reversal and Change of Vertical Unit
+ # In EPSG, there is one generic instance of those as 7812 and 7813 that
+ # don't refer to particular CRS, and instances pointing to CRS names
+ # The later are imported in the other_transformation table since we recover
+ # the source/target CRS names from the transformation name.
+ if method_code in (1068, 1069) and source_crs_code is None and target_crs_code is None:
+ parts = name.split(" to ")
+ if len(parts) != 2:
+ continue
+
+ proj_db_cursor.execute("SELECT coord_ref_sys_code FROM epsg_coordinatereferencesystem WHERE coord_ref_sys_name = ?", (parts[0],))
+ source_codes = proj_db_cursor.fetchall()
+ proj_db_cursor.execute("SELECT coord_ref_sys_code FROM epsg_coordinatereferencesystem WHERE coord_ref_sys_name = ?", (parts[1],))
+ target_codes = proj_db_cursor.fetchall()
+ if len(source_codes) != 1 and len(target_codes) != 1:
+ continue
+
+ source_crs_code = source_codes[0][0]
+ target_crs_code = target_codes[0][0]
+
expected_order = 1
max_n_params = 7
param_auth_name = [None for i in range(max_n_params)]
@@ -471,6 +499,7 @@ def fill_other_transformation(proj_db_cursor):
deprecated)
#proj_db_cursor.execute("INSERT INTO coordinate_operation VALUES (?,?,'other_transformation')", (EPSG_AUTHORITY, code))
+ #print(arg)
proj_db_cursor.execute('INSERT INTO other_transformation VALUES (' +
'?,?,?, ?,?, ?,?,?, ?,?, ?,?, ?,?, ?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ' +
'?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?)', arg)
diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt
index 73a36a70..34e23b22 100644
--- a/scripts/reference_exported_symbols.txt
+++ b/scripts/reference_exported_symbols.txt
@@ -495,6 +495,7 @@ osgeo::proj::operation::Conversion::createGeostationarySatelliteSweepY(osgeo::pr
osgeo::proj::operation::Conversion::createGnomonic(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&)
osgeo::proj::operation::Conversion::createGoodeHomolosine(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&)
osgeo::proj::operation::Conversion::createGuamProjection(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&)
+osgeo::proj::operation::Conversion::createHeightDepthReversal(osgeo::proj::util::PropertyMap const&)
osgeo::proj::operation::Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&)
osgeo::proj::operation::Conversion::createHotineObliqueMercatorVariantA(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&)
osgeo::proj::operation::Conversion::createHotineObliqueMercatorVariantB(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&)
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index 5ac81aa1..9e3306ba 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -4661,6 +4661,26 @@ Conversion::createChangeVerticalUnit(const util::PropertyMap &properties,
// ---------------------------------------------------------------------------
+/** \brief Instantiate a conversion based on the Height Depth Reversal
+ * method.
+ *
+ * This method is defined as [EPSG:1068]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1068)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @return a new Conversion.
+ * @since 7.0
+ */
+ConversionNNPtr
+Conversion::createHeightDepthReversal(const util::PropertyMap &properties) {
+ return create(properties, createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL),
+ {}, {});
+}
+
+// ---------------------------------------------------------------------------
+
/** \brief Instantiate a conversion based on the Axis order reversal method
*
* This swaps the longitude, latitude axis.
@@ -4927,6 +4947,14 @@ CoordinateOperationNNPtr Conversion::inverse() const {
return conv;
}
+ if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
+
+ auto conv = createHeightDepthReversal(
+ createPropertiesForInverse(this, false, false));
+ conv->setCRSs(this, true);
+ return conv;
+ }
+
return InverseConversion::create(NN_NO_CHECK(
util::nn_dynamic_pointer_cast<Conversion>(shared_from_this())));
}
@@ -5808,9 +5836,12 @@ void Conversion::_exportToPROJString(
methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION;
const bool isGeographicGeocentric =
methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC;
+ const bool isHeightDepthReversal =
+ methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL;
const bool applySourceCRSModifiers =
!isZUnitConversion && !isAffineParametric &&
- !isAxisOrderReversal(methodEPSGCode) && !isGeographicGeocentric;
+ !isAxisOrderReversal(methodEPSGCode) && !isGeographicGeocentric &&
+ !isHeightDepthReversal;
bool applyTargetCRSModifiers = applySourceCRSModifiers;
auto l_sourceCRS = sourceCRS();
@@ -7883,6 +7914,17 @@ TransformationNNPtr Transformation::inverseAsTransformation() const {
coordinateOperationAccuracies()));
}
+#ifdef notdef
+ // We dont need that currently, but we might...
+ if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
+ return d->registerInv(
+ shared_from_this(),
+ createHeightDepthReversal(
+ createPropertiesForInverse(this, false, false), l_targetCRS,
+ l_sourceCRS, coordinateOperationAccuracies()));
+ }
+#endif
+
return InverseTransformation::create(NN_NO_CHECK(
util::nn_dynamic_pointer_cast<Transformation>(shared_from_this())));
}
@@ -9396,6 +9438,12 @@ bool SingleOperation::exportToPROJStringGeneric(
return true;
}
+ if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
+ formatter->addStep("axisswap");
+ formatter->addParam("order", "1,2,-3");
+ return true;
+ }
+
return false;
}
@@ -10305,6 +10353,7 @@ struct CoordinateOperationFactory::Private {
bool inCreateOperationsWithDatumPivotAntiRecursion = false;
bool inCreateOperationsThroughPreferredHub = false;
bool inCreateOperationsGeogToVertWithAlternativeGeog = false;
+ bool inCreateOperationsGeogToVertWithIntermediateVert = false;
bool skipHorizontalTransformation = false;
Context(const crs::CRSNNPtr &sourceCRSIn,
@@ -10334,6 +10383,23 @@ struct CoordinateOperationFactory::Private {
const crs::VerticalCRS *vertDst,
std::vector<CoordinateOperationNNPtr> &res);
+ static std::vector<CoordinateOperationNNPtr>
+ createOperationsGeogToVertWithIntermediateVert(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::VerticalCRS *vertDst, Context &context);
+
+ static std::vector<CoordinateOperationNNPtr>
+ createOperationsGeogToVertWithAlternativeGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Context &context);
+
+ static void createOperationsFromDatabaseWithVertCRS(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeographicCRS *geogSrc,
+ const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
static void createOperationsGeodToGeod(
const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
Private::Context &context, const crs::GeodeticCRS *geodSrc,
@@ -10416,11 +10482,6 @@ struct CoordinateOperationFactory::Private {
const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst,
Context &context);
- static std::vector<CoordinateOperationNNPtr>
- createOperationsGeogToVertWithAlternativeGeog(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Context &context);
-
static bool
hasPerfectAccuracyResult(const std::vector<CoordinateOperationNNPtr> &res,
const Context &context);
@@ -12409,51 +12470,6 @@ void CoordinateOperationFactory::Private::createOperationsThroughPreferredHub(
// ---------------------------------------------------------------------------
-std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private::
- createOperationsGeogToVertWithAlternativeGeog(
- const crs::CRSNNPtr & /*sourceCRS*/, // geographic CRS
- const crs::CRSNNPtr &targetCRS, // vertical CRS
- Private::Context &context) {
-
- std::vector<CoordinateOperationNNPtr> res;
-
- struct AntiRecursionGuard {
- Context &context;
-
- explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) {
- assert(!context.inCreateOperationsGeogToVertWithAlternativeGeog);
- context.inCreateOperationsGeogToVertWithAlternativeGeog = true;
- }
-
- ~AntiRecursionGuard() {
- context.inCreateOperationsGeogToVertWithAlternativeGeog = false;
- }
- };
- AntiRecursionGuard guard(context);
-
- for (int i = 0; i < 2; i++) {
-
- // Generally EPSG has operations from GeogCrs to VertCRS
- auto ops =
- i == 0 ? findOpsInRegistryDirectTo(targetCRS, context.context)
- : findOpsInRegistryDirectFrom(targetCRS, context.context);
-
- for (const auto &op : ops) {
- const auto tmpCRS = i == 0 ? op->sourceCRS() : op->targetCRS();
- if (tmpCRS &&
- dynamic_cast<const crs::GeographicCRS *>(tmpCRS.get())) {
- res.emplace_back(i == 0 ? op : op->inverse());
- }
- }
- if (!res.empty())
- break;
- }
-
- return res;
-}
-
-// ---------------------------------------------------------------------------
-
static CoordinateOperationNNPtr
createBallparkGeocentricTranslation(const crs::CRSNNPtr &sourceCRS,
const crs::CRSNNPtr &targetCRS) {
@@ -12730,88 +12746,32 @@ bool CoordinateOperationFactory::Private::createOperationsFromDatabase(
doFilterAndCheckPerfectOp = false;
bool sameGeodeticDatum = false;
- if (geodSrc && geodDst) {
+
+ if (vertSrc || vertDst) {
+ createOperationsFromDatabaseWithVertCRS(sourceCRS, targetCRS,
+ context, geogSrc, geogDst,
+ vertSrc, vertDst, res);
+ } else if (geodSrc && geodDst) {
+
const auto &srcDatum = geodSrc->datum();
const auto &dstDatum = geodDst->datum();
sameGeodeticDatum =
srcDatum != nullptr && dstDatum != nullptr &&
srcDatum->_isEquivalentTo(
dstDatum.get(), util::IComparable::Criterion::EQUIVALENT);
- }
- // NAD83 only exists in 2D version in EPSG, so if it has been
- // promotted to 3D, when researching a vertical to geog
- // transformation, try to down cast to 2D.
- const auto geog3DToVertTryThroughGeog2D = [&res, &context](
- const crs::GeographicCRS *geogSrcIn,
- const crs::VerticalCRS *vertDstIn,
- const crs::CRSNNPtr &targetCRSIn) {
- if (res.empty() && geogSrcIn && vertDstIn &&
- geogSrcIn->coordinateSystem()->axisList().size() == 3 &&
- geogSrcIn->datum()) {
- const auto &authFactory =
- context.context->getAuthorityFactory();
- const auto candidatesSrcGeod(findCandidateGeodCRSForDatum(
- authFactory, geogSrcIn->datum().get()));
- for (const auto &candidate : candidatesSrcGeod) {
- auto geogCandidate =
- util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
- candidate);
- if (geogCandidate &&
- geogCandidate->coordinateSystem()->axisList().size() ==
- 2) {
- res = findOpsInRegistryDirect(
- NN_NO_CHECK(geogCandidate), targetCRSIn,
- context.context);
- if (res.empty()) {
- res = applyInverse(findOpsInRegistryDirect(
- targetCRSIn, NN_NO_CHECK(geogCandidate),
- context.context));
- }
- break;
- }
- }
- return true;
- }
- return false;
- };
-
- if (geog3DToVertTryThroughGeog2D(geogSrc, vertDst, targetCRS)) {
- // do nothing
- } else if (geog3DToVertTryThroughGeog2D(geogDst, vertSrc, sourceCRS)) {
- res = applyInverse(res);
- }
-
- // There's no direct transformation from NAVD88 height to WGS84,
- // so try to research all transformations from NAVD88 to another
- // intermediate GeographicCRS.
- if (res.empty() &&
- !context.inCreateOperationsGeogToVertWithAlternativeGeog &&
- geogSrc && vertDst) {
- res = createOperationsGeogToVertWithAlternativeGeog(
- sourceCRS, targetCRS, context);
- } else if (res.empty() &&
- !context.inCreateOperationsGeogToVertWithAlternativeGeog &&
- geogDst && vertSrc) {
- res = applyInverse(createOperationsGeogToVertWithAlternativeGeog(
- targetCRS, sourceCRS, context));
- }
-
- if (res.empty() && !sameGeodeticDatum &&
- !context.inCreateOperationsWithDatumPivotAntiRecursion && geodSrc &&
- geodDst) {
- // If we still didn't find a transformation, and that the source
- // and target are GeodeticCRS, then go through their underlying
- // datum to find potential transformations between other
- // GeodeticRSs
- // that are made of those datum
- // The typical example is if transforming between two
- // GeographicCRS,
- // but transformations are only available between their
- // corresponding geocentric CRS.
- const auto &srcDatum = geodSrc->datum();
- const auto &dstDatum = geodDst->datum();
- if (srcDatum != nullptr && dstDatum != nullptr) {
+ if (res.empty() && !sameGeodeticDatum &&
+ !context.inCreateOperationsWithDatumPivotAntiRecursion &&
+ srcDatum != nullptr && dstDatum != nullptr) {
+ // If we still didn't find a transformation, and that the source
+ // and target are GeodeticCRS, then go through their underlying
+ // datum to find potential transformations between other
+ // GeodeticRSs
+ // that are made of those datum
+ // The typical example is if transforming between two
+ // GeographicCRS,
+ // but transformations are only available between their
+ // corresponding geocentric CRS.
createOperationsWithDatumPivot(res, sourceCRS, targetCRS,
geodSrc, geodDst, context);
doFilterAndCheckPerfectOp = !res.empty();
@@ -12858,6 +12818,222 @@ bool CoordinateOperationFactory::Private::createOperationsFromDatabase(
// ---------------------------------------------------------------------------
+static std::vector<crs::CRSNNPtr>
+findCandidateVertCRSForDatum(const io::AuthorityFactoryPtr &authFactory,
+ const datum::VerticalReferenceFrame *datum) {
+ std::vector<crs::CRSNNPtr> candidates;
+ assert(datum);
+ const auto &ids = datum->identifiers();
+ const auto &datumName = datum->nameStr();
+ if (!ids.empty()) {
+ for (const auto &id : ids) {
+ const auto &authName = *(id->codeSpace());
+ const auto &code = id->code();
+ if (!authName.empty()) {
+ auto l_candidates =
+ authFactory->createVerticalCRSFromDatum(authName, code);
+ for (const auto &candidate : l_candidates) {
+ candidates.emplace_back(candidate);
+ }
+ }
+ }
+ } else if (datumName != "unknown" && datumName != "unnamed") {
+ auto matches = authFactory->createObjectsFromName(
+ datumName,
+ {io::AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME}, false,
+ 2);
+ if (matches.size() == 1) {
+ const auto &match = matches.front();
+ if (datum->_isEquivalentTo(
+ match.get(), util::IComparable::Criterion::EQUIVALENT) &&
+ !match->identifiers().empty()) {
+ return findCandidateVertCRSForDatum(
+ authFactory,
+ dynamic_cast<const datum::VerticalReferenceFrame *>(
+ match.get()));
+ }
+ }
+ }
+ return candidates;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private::
+ createOperationsGeogToVertWithIntermediateVert(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::VerticalCRS *vertDst, Private::Context &context) {
+
+ std::vector<CoordinateOperationNNPtr> res;
+
+ struct AntiRecursionGuard {
+ Context &context;
+
+ explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) {
+ assert(!context.inCreateOperationsGeogToVertWithIntermediateVert);
+ context.inCreateOperationsGeogToVertWithIntermediateVert = true;
+ }
+
+ ~AntiRecursionGuard() {
+ context.inCreateOperationsGeogToVertWithIntermediateVert = false;
+ }
+ };
+ AntiRecursionGuard guard(context);
+ const auto &authFactory = context.context->getAuthorityFactory();
+ auto candidatesVert =
+ findCandidateVertCRSForDatum(authFactory, vertDst->datum().get());
+ for (const auto &candidateVert : candidatesVert) {
+ auto resTmp = createOperations(sourceCRS, candidateVert, context);
+ if (!resTmp.empty()) {
+ const auto opsSecond =
+ createOperations(candidateVert, targetCRS, context);
+ if (!opsSecond.empty()) {
+ // The transformation from candidateVert to targetCRS should
+ // be just a unit change typically, so take only the first one,
+ // which is likely/hopefully the only one.
+ for (const auto &opFirst : resTmp) {
+ if (hasIdentifiers(opFirst)) {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opsSecond.front()},
+ !allowEmptyIntersection));
+ }
+ }
+ if (!res.empty())
+ break;
+ }
+ }
+ }
+
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private::
+ createOperationsGeogToVertWithAlternativeGeog(
+ const crs::CRSNNPtr & /*sourceCRS*/, // geographic CRS
+ const crs::CRSNNPtr &targetCRS, // vertical CRS
+ Private::Context &context) {
+
+ std::vector<CoordinateOperationNNPtr> res;
+
+ struct AntiRecursionGuard {
+ Context &context;
+
+ explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) {
+ assert(!context.inCreateOperationsGeogToVertWithAlternativeGeog);
+ context.inCreateOperationsGeogToVertWithAlternativeGeog = true;
+ }
+
+ ~AntiRecursionGuard() {
+ context.inCreateOperationsGeogToVertWithAlternativeGeog = false;
+ }
+ };
+ AntiRecursionGuard guard(context);
+
+ for (int i = 0; i < 2; i++) {
+
+ // Generally EPSG has operations from GeogCrs to VertCRS
+ auto ops =
+ i == 0 ? findOpsInRegistryDirectTo(targetCRS, context.context)
+ : findOpsInRegistryDirectFrom(targetCRS, context.context);
+
+ for (const auto &op : ops) {
+ const auto tmpCRS = i == 0 ? op->sourceCRS() : op->targetCRS();
+ if (tmpCRS &&
+ dynamic_cast<const crs::GeographicCRS *>(tmpCRS.get())) {
+ res.emplace_back(i == 0 ? op : op->inverse());
+ }
+ }
+ if (!res.empty())
+ break;
+ }
+
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::
+ createOperationsFromDatabaseWithVertCRS(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeographicCRS *geogSrc,
+ const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ // Typically to transform from "NAVD88 height (ftUS)" to a geog CRS
+ // by using transformations of "NAVD88 height" (metre) to that geog CRS
+ if (res.empty() &&
+ !context.inCreateOperationsGeogToVertWithIntermediateVert && geogSrc &&
+ vertDst) {
+ res = createOperationsGeogToVertWithIntermediateVert(
+ sourceCRS, targetCRS, vertDst, context);
+ } else if (res.empty() &&
+ !context.inCreateOperationsGeogToVertWithIntermediateVert &&
+ geogDst && vertSrc) {
+ res = applyInverse(createOperationsGeogToVertWithIntermediateVert(
+ targetCRS, sourceCRS, vertSrc, context));
+ }
+
+ // NAD83 only exists in 2D version in EPSG, so if it has been
+ // promotted to 3D, when researching a vertical to geog
+ // transformation, try to down cast to 2D.
+ const auto geog3DToVertTryThroughGeog2D = [&res, &context](
+ const crs::GeographicCRS *geogSrcIn, const crs::VerticalCRS *vertDstIn,
+ const crs::CRSNNPtr &targetCRSIn) {
+ if (res.empty() && geogSrcIn && vertDstIn &&
+ geogSrcIn->coordinateSystem()->axisList().size() == 3 &&
+ geogSrcIn->datum()) {
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto candidatesSrcGeod(findCandidateGeodCRSForDatum(
+ authFactory, geogSrcIn->datum().get()));
+ for (const auto &candidate : candidatesSrcGeod) {
+ auto geogCandidate =
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ candidate);
+ if (geogCandidate &&
+ geogCandidate->coordinateSystem()->axisList().size() == 2) {
+ res = findOpsInRegistryDirect(NN_NO_CHECK(geogCandidate),
+ targetCRSIn, context.context);
+ if (res.empty()) {
+ res = applyInverse(findOpsInRegistryDirect(
+ targetCRSIn, NN_NO_CHECK(geogCandidate),
+ context.context));
+ }
+ break;
+ }
+ }
+ return true;
+ }
+ return false;
+ };
+
+ if (geog3DToVertTryThroughGeog2D(geogSrc, vertDst, targetCRS)) {
+ // do nothing
+ } else if (geog3DToVertTryThroughGeog2D(geogDst, vertSrc, sourceCRS)) {
+ res = applyInverse(res);
+ }
+
+ // There's no direct transformation from NAVD88 height to WGS84,
+ // so try to research all transformations from NAVD88 to another
+ // intermediate GeographicCRS.
+ if (res.empty() &&
+ !context.inCreateOperationsGeogToVertWithAlternativeGeog && geogSrc &&
+ vertDst) {
+ res = createOperationsGeogToVertWithAlternativeGeog(sourceCRS,
+ targetCRS, context);
+ } else if (res.empty() &&
+ !context.inCreateOperationsGeogToVertWithAlternativeGeog &&
+ geogDst && vertSrc) {
+ res = applyInverse(createOperationsGeogToVertWithAlternativeGeog(
+ targetCRS, sourceCRS, context));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
void CoordinateOperationFactory::Private::createOperationsGeodToGeod(
const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
Private::Context &context, const crs::GeodeticCRS *geodSrc,
@@ -13185,10 +13361,16 @@ void CoordinateOperationFactory::Private::createOperationsVertToVert(
srcDatum->_isEquivalentTo(dstDatum.get(),
util::IComparable::Criterion::EQUIVALENT));
- const double convSrc =
- vertSrc->coordinateSystem()->axisList()[0]->unit().conversionToSI();
- const double convDst =
- vertDst->coordinateSystem()->axisList()[0]->unit().conversionToSI();
+ const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0];
+ const double convSrc = srcAxis->unit().conversionToSI();
+ const auto &dstAxis = vertDst->coordinateSystem()->axisList()[0];
+ const double convDst = dstAxis->unit().conversionToSI();
+ const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP;
+ const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN;
+ const bool dstIsUp = dstAxis->direction() == cs::AxisDirection::UP;
+ const bool dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN;
+ const bool heightDepthReversal =
+ ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp));
const double factor = convSrc / convDst;
auto name = buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr());
@@ -13196,13 +13378,23 @@ void CoordinateOperationFactory::Private::createOperationsVertToVert(
name += BALLPARK_VERTICAL_TRANSFORMATION;
auto conv = Transformation::createChangeVerticalUnit(
util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name),
- sourceCRS, targetCRS, common::Scale(factor), {});
+ sourceCRS, targetCRS,
+ // In case of a height depth reversal, we should probably have
+ // 2 steps instead of putting a negative factor...
+ common::Scale(heightDepthReversal ? -factor : factor), {});
conv->setHasBallparkTransformation(true);
res.push_back(conv);
} else if (convSrc != convDst) {
auto conv = Conversion::createChangeVerticalUnit(
util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name),
- common::Scale(factor));
+ // In case of a height depth reversal, we should probably have
+ // 2 steps instead of putting a negative factor...
+ common::Scale(heightDepthReversal ? -factor : factor));
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ res.push_back(conv);
+ } else if (heightDepthReversal) {
+ auto conv = Conversion::createHeightDepthReversal(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name));
conv->setCRSs(sourceCRS, targetCRS, nullptr);
res.push_back(conv);
}
@@ -13353,6 +13545,8 @@ getOps(const CoordinateOperationNNPtr &op) {
return {op};
}
+// ---------------------------------------------------------------------------
+
static bool useDifferentTransformationsForSameSourceTarget(
const CoordinateOperationNNPtr &opA, const CoordinateOperationNNPtr &opB) {
auto subOpsA = getOps(opA);
@@ -13400,6 +13594,71 @@ static bool useDifferentTransformationsForSameSourceTarget(
return false;
}
+// ---------------------------------------------------------------------------
+
+static crs::GeographicCRSPtr
+getInterpolationGeogCRS(const CoordinateOperationNNPtr &verticalTransform,
+ const io::DatabaseContextPtr &dbContext) {
+ crs::GeographicCRSPtr interpolationGeogCRS;
+ auto transformationVerticalTransform =
+ dynamic_cast<const Transformation *>(verticalTransform.get());
+ if (transformationVerticalTransform == nullptr) {
+ const auto concat = dynamic_cast<const ConcatenatedOperation *>(
+ verticalTransform.get());
+ if (concat) {
+ const auto &steps = concat->operations();
+ // Is this change of unit and/or height depth reversal +
+ // transformation ?
+ for (const auto &step : steps) {
+ const auto transf =
+ dynamic_cast<const Transformation *>(step.get());
+ if (transf) {
+ // Only support a single Transformation in the steps
+ if (transformationVerticalTransform != nullptr) {
+ transformationVerticalTransform = nullptr;
+ break;
+ }
+ transformationVerticalTransform = transf;
+ }
+ }
+ }
+ }
+ if (transformationVerticalTransform &&
+ !transformationVerticalTransform->hasBallparkTransformation()) {
+ auto interpTransformCRS =
+ transformationVerticalTransform->interpolationCRS();
+ if (interpTransformCRS) {
+ interpolationGeogCRS =
+ std::dynamic_pointer_cast<crs::GeographicCRS>(
+ interpTransformCRS);
+ } else {
+ // If no explicit interpolation CRS, then
+ // this will be the geographic CRS of the
+ // vertical to geog transformation
+ interpolationGeogCRS =
+ std::dynamic_pointer_cast<crs::GeographicCRS>(
+ transformationVerticalTransform->targetCRS().as_nullable());
+ }
+ }
+
+ if (interpolationGeogCRS) {
+ if (interpolationGeogCRS->coordinateSystem()->axisList().size() == 3) {
+ // We need to force the interpolation CRS, which
+ // will
+ // frequently be 3D, to 2D to avoid transformations
+ // between source CRS and interpolation CRS to have
+ // 3D terms.
+ interpolationGeogCRS =
+ interpolationGeogCRS->demoteTo2D(std::string(), dbContext)
+ .as_nullable();
+ }
+ }
+
+ return interpolationGeogCRS;
+}
+
+// ---------------------------------------------------------------------------
+
void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
Private::Context &context, const crs::CompoundCRS *compoundSrc,
@@ -13447,7 +13706,7 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
CoordinateOperationContext::GridAvailabilityUse::
IGNORE_GRID_AVAILABILITY;
for (const auto &op : verticalTransforms) {
- if (!op->identifiers().empty() && dbContext) {
+ if (hasIdentifiers(op) && dbContext) {
bool missingGrid = false;
if (!ignoreMissingGrids) {
const auto gridsNeeded = op->gridsNeeded(dbContext);
@@ -13476,7 +13735,7 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
bool foundRegisteredTransform = false;
foundRegisteredTransformWithAllGridsAvailable = false;
for (const auto &op : verticalTransformsTmp) {
- if (!op->identifiers().empty() && dbContext) {
+ if (hasIdentifiers(op) && dbContext) {
bool missingGrid = false;
if (!ignoreMissingGrids) {
const auto gridsNeeded = op->gridsNeeded(dbContext);
@@ -13517,43 +13776,9 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
cacheHorizToInterpAndInterpToTarget;
for (const auto &verticalTransform : verticalTransforms) {
- crs::GeographicCRSPtr interpolationGeogCRS;
- auto transformationVerticalTransform =
- dynamic_cast<const Transformation *>(verticalTransform.get());
- if (transformationVerticalTransform &&
- !transformationVerticalTransform->hasBallparkTransformation()) {
- auto interpTransformCRS =
- transformationVerticalTransform->interpolationCRS();
- if (interpTransformCRS) {
- interpolationGeogCRS =
- std::dynamic_pointer_cast<crs::GeographicCRS>(
- interpTransformCRS);
- } else {
- // If no explicit interpolation CRS, then
- // this will be the geographic CRS of the
- // vertical to geog transformation
- interpolationGeogCRS =
- std::dynamic_pointer_cast<crs::GeographicCRS>(
- transformationVerticalTransform->targetCRS()
- .as_nullable());
- }
- }
-
+ crs::GeographicCRSPtr interpolationGeogCRS =
+ getInterpolationGeogCRS(verticalTransform, dbContext);
if (interpolationGeogCRS) {
- if (interpolationGeogCRS->coordinateSystem()
- ->axisList()
- .size() == 3) {
- // We need to force the interpolation CRS, which
- // will
- // frequently be 3D, to 2D to avoid transformations
- // between source CRS and interpolation CRS to have
- // 3D terms.
- interpolationGeogCRS =
- interpolationGeogCRS
- ->demoteTo2D(std::string(), dbContext)
- .as_nullable();
- }
-
std::vector<CoordinateOperationNNPtr> srcToInterpOps;
std::vector<CoordinateOperationNNPtr> interpToTargetOps;
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index 993b4eeb..44e44dbe 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -2295,6 +2295,20 @@ AuthorityFactory::createConversion(const std::string &code) const {
auto res = d->runWithCodeParam(sql, code);
if (res.empty()) {
+ try {
+ // Conversions using methods Change of Vertical Unit or
+ // Height Depth Reveral are stored in other_transformation
+ auto op = createCoordinateOperation(
+ code, false /* allowConcatenated */,
+ false /* usePROJAlternativeGridNames */,
+ "other_transformation");
+ auto conv =
+ util::nn_dynamic_pointer_cast<operation::Conversion>(op);
+ if (conv) {
+ return NN_NO_CHECK(conv);
+ }
+ } catch (const std::exception &) {
+ }
throw NoSuchAuthorityCodeException("conversion not found",
d->authority(), code);
}
@@ -3118,13 +3132,16 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
.set(metadata::Identifier::CODE_KEY, method_code)
.set(common::IdentifiedObject::NAME_KEY, method_name);
- if (method_auth_name == metadata::Identifier::EPSG &&
- operation::isAxisOrderReversal(
- std::atoi(method_code.c_str()))) {
- auto op = operation::Conversion::create(props, propsMethod,
- parameters, values);
- op->setCRSs(sourceCRS, targetCRS, nullptr);
- return op;
+ if (method_auth_name == metadata::Identifier::EPSG) {
+ int method_code_int = std::atoi(method_code.c_str());
+ if (operation::isAxisOrderReversal(method_code_int) ||
+ method_code_int == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT ||
+ method_code_int == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
+ auto op = operation::Conversion::create(props, propsMethod,
+ parameters, values);
+ op->setCRSs(sourceCRS, targetCRS, nullptr);
+ return op;
+ }
}
return operation::Transformation::create(
props, sourceCRS, targetCRS, nullptr, propsMethod, parameters,
@@ -4603,6 +4620,30 @@ std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+std::list<crs::VerticalCRSNNPtr> AuthorityFactory::createVerticalCRSFromDatum(
+ const std::string &datum_auth_name, const std::string &datum_code) const {
+ std::string sql(
+ "SELECT auth_name, code FROM vertical_crs WHERE "
+ "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
+ ListOfParams params{datum_auth_name, datum_code};
+ if (d->hasAuthorityRestriction()) {
+ sql += " AND auth_name = ?";
+ params.emplace_back(d->authority());
+ }
+ auto sqlRes = d->run(sql, params);
+ std::list<crs::VerticalCRSNNPtr> res;
+ for (const auto &row : sqlRes) {
+ const auto &auth_name = row[0];
+ const auto &code = row[1];
+ res.emplace_back(d->createFactory(auth_name)->createVerticalCRS(code));
+ }
+ return res;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
std::list<crs::GeodeticCRSNNPtr>
AuthorityFactory::createGeodeticCRSFromEllipsoid(
const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code,
diff --git a/src/proj_constants.h b/src/proj_constants.h
index 619d9d91..62cf94be 100644
--- a/src/proj_constants.h
+++ b/src/proj_constants.h
@@ -592,4 +592,9 @@
#define EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_3D \
"Axis Order Reversal (Geographic3D horizontal)"
+/* ------------------------------------------------------------------------ */
+
+#define EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL 1068
+#define EPSG_NAME_METHOD_HEIGHT_DEPTH_REVERSAL "Height Depth Reversal"
+
#endif /* PROJ_CONSTANTS_INCLUDED */
diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp
index 47cee060..94010135 100644
--- a/test/unit/test_factory.cpp
+++ b/test/unit/test_factory.cpp
@@ -542,6 +542,15 @@ TEST(factory, AuthorityFactory_createConversion) {
// ---------------------------------------------------------------------------
+TEST(factory, AuthorityFactory_createConversion_from_other_transformation) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("7984", false);
+ auto conversion = nn_dynamic_pointer_cast<Conversion>(op);
+ ASSERT_TRUE(conversion != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(factory, AuthorityFactory_createProjectedCRS) {
auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
EXPECT_THROW(factory->createProjectedCRS("-1"),
@@ -1157,9 +1166,7 @@ TEST(factory, AuthorityFactory_build_all_concatenated) {
AuthorityFactory::ObjectType::CONCATENATED_OPERATION, false);
EXPECT_LT(setConcatenatedNoDeprecated.size(), setConcatenated.size());
for (const auto &code : setConcatenated) {
- if (in(code, {"8422", "8481", "8482", "8565", "8566", "8572",
- // the issue with 7987 is the chaining of two conversions
- "7987"})) {
+ if (in(code, {"8422", "8481", "8482", "8565", "8566", "8572"})) {
EXPECT_THROW(factory->createCoordinateOperation(code, false),
FactoryException)
<< code;
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
index 79541d88..d61c74c0 100644
--- a/test/unit/test_operation.cpp
+++ b/test/unit/test_operation.cpp
@@ -6872,6 +6872,43 @@ TEST(operation, vertCRS_to_vertCRS) {
EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
"+proj=affine +s33=0.999998");
}
+
+ auto vertCRSMetreUp =
+ nn_dynamic_pointer_cast<VerticalCRS>(WKTParser().createFromWKT(
+ "VERTCRS[\"my height\",VDATUM[\"my datum\"],CS[vertical,1],"
+ "AXIS[\"gravity-related height (H)\",up,"
+ "LENGTHUNIT[\"metre\",1]]]"));
+ ASSERT_TRUE(vertCRSMetreUp != nullptr);
+
+ auto vertCRSMetreDown =
+ nn_dynamic_pointer_cast<VerticalCRS>(WKTParser().createFromWKT(
+ "VERTCRS[\"my depth\",VDATUM[\"my datum\"],CS[vertical,1],"
+ "AXIS[\"depth (D)\",down,LENGTHUNIT[\"metre\",1]]]"));
+ ASSERT_TRUE(vertCRSMetreDown != nullptr);
+
+ auto vertCRSMetreDownFtUS =
+ nn_dynamic_pointer_cast<VerticalCRS>(WKTParser().createFromWKT(
+ "VERTCRS[\"my depth (ftUS)\",VDATUM[\"my datum\"],CS[vertical,1],"
+ "AXIS[\"depth (D)\",down,LENGTHUNIT[\"US survey "
+ "foot\",0.304800609601219]]]"));
+ ASSERT_TRUE(vertCRSMetreDownFtUS != nullptr);
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(vertCRSMetreUp), NN_CHECK_ASSERT(vertCRSMetreDown));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=axisswap +order=1,2,-3");
+ }
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(vertCRSMetreUp),
+ NN_CHECK_ASSERT(vertCRSMetreDownFtUS));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=affine +s33=-3.28083333333333");
+ }
}
// ---------------------------------------------------------------------------
@@ -7165,6 +7202,308 @@ TEST(operation, compoundCRS_to_geogCRS_2D_promote_to_3D_context) {
// ---------------------------------------------------------------------------
+TEST(operation, compoundCRS_from_wkt_without_id_to_geogCRS) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ auto wkt =
+ "COMPOUNDCRS[\"NAD83(2011) + NAVD88 height\",\n"
+ " GEOGCRS[\"NAD83(2011)\",\n"
+ " DATUM[\"NAD83 (National Spatial Reference System 2011)\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\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"
+ " VERTCRS[\"NAVD88 height\",\n"
+ " VDATUM[\"North American Vertical Datum 1988\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]]]]";
+ auto srcObj =
+ createFromUserInput(wkt, authFactory->databaseContext(), false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto dst =
+ authFactory->createCoordinateReferenceSystem("6319"); // NAD83(2011)
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src), dst, ctxt);
+ // NAD83(2011) + NAVD88 height
+ auto srcRefObj = createFromUserInput("EPSG:6318+5703",
+ authFactory->databaseContext(), false);
+ auto srcRef = nn_dynamic_pointer_cast<CRS>(srcRefObj);
+ ASSERT_TRUE(srcRef != nullptr);
+ ASSERT_TRUE(
+ src->isEquivalentTo(srcRef.get(), IComparable::Criterion::EQUIVALENT));
+ auto listRef = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcRef), dst, ctxt);
+
+ EXPECT_EQ(list.size(), listRef.size());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_geogCRS_with_vertical_unit_change) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ // NAD83(2011) + NAVD88 height (ftUS)
+ auto srcObj = createFromUserInput("EPSG:6318+6360",
+ authFactory->databaseContext(), false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto nnSrc = NN_NO_CHECK(src);
+ auto dst =
+ authFactory->createCoordinateReferenceSystem("6319"); // NAD83(2011) 3D
+
+ auto listCompoundToGeog =
+ CoordinateOperationFactory::create()->createOperations(nnSrc, dst,
+ ctxt);
+ ASSERT_TRUE(!listCompoundToGeog.empty());
+
+ // NAD83(2011) + NAVD88 height
+ auto srcObjCompoundVMetre = createFromUserInput(
+ "EPSG:6318+5703", authFactory->databaseContext(), false);
+ auto srcCompoundVMetre = nn_dynamic_pointer_cast<CRS>(srcObjCompoundVMetre);
+ ASSERT_TRUE(srcCompoundVMetre != nullptr);
+ auto listCompoundMetreToGeog =
+ CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCompoundVMetre), dst, ctxt);
+
+ // Check that we get the same and similar results whether we start from
+ // regular NAVD88 height or its ftUs variant
+ ASSERT_EQ(listCompoundToGeog.size(), listCompoundMetreToGeog.size());
+
+ EXPECT_EQ(listCompoundToGeog[0]->nameStr(),
+ "Inverse of NAVD88 height to NAVD88 height (ftUS) + " +
+ listCompoundMetreToGeog[0]->nameStr());
+ EXPECT_EQ(
+ listCompoundToGeog[0]->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ replaceAll(listCompoundMetreToGeog[0]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad",
+ "+step +proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad "
+ "+z_out=m"));
+
+ // Check reverse path
+ auto listGeogToCompound =
+ CoordinateOperationFactory::create()->createOperations(dst, nnSrc,
+ ctxt);
+ EXPECT_EQ(listGeogToCompound.size(), listCompoundToGeog.size());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_geogCRS_with_vertical_unit_change_and_complex_horizontal_change) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ // NAD83(2011) + NAVD88 height (ftUS)
+ auto srcObj = createFromUserInput("EPSG:6318+6360",
+ authFactory->databaseContext(), false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto nnSrc = NN_NO_CHECK(src);
+ auto dst =
+ authFactory->createCoordinateReferenceSystem("7665"); // WGS84(G1762) 3D
+
+ auto listCompoundToGeog =
+ CoordinateOperationFactory::create()->createOperations(nnSrc, dst,
+ ctxt);
+
+ // NAD83(2011) + NAVD88 height
+ auto srcObjCompoundVMetre = createFromUserInput(
+ "EPSG:6318+5703", authFactory->databaseContext(), false);
+ auto srcCompoundVMetre = nn_dynamic_pointer_cast<CRS>(srcObjCompoundVMetre);
+ ASSERT_TRUE(srcCompoundVMetre != nullptr);
+ auto listCompoundMetreToGeog =
+ CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCompoundVMetre), dst, ctxt);
+
+ // Check that we get the same and similar results whether we start from
+ // regular NAVD88 height or its ftUs variant
+ ASSERT_EQ(listCompoundToGeog.size(), listCompoundMetreToGeog.size());
+
+ ASSERT_GE(listCompoundToGeog.size(), 1U);
+
+ EXPECT_EQ(listCompoundToGeog[0]->nameStr(),
+ "Inverse of NAVD88 height to NAVD88 height (ftUS) + " +
+ listCompoundMetreToGeog[0]->nameStr());
+ EXPECT_EQ(
+ listCompoundToGeog[0]->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ replaceAll(listCompoundMetreToGeog[0]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad",
+ "+step +proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad "
+ "+z_out=m"));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_geogCRS_with_height_depth_reversal) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ // NAD83(2011) + NAVD88 depth
+ auto srcObj = createFromUserInput("EPSG:6318+6357",
+ authFactory->databaseContext(), false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto nnSrc = NN_NO_CHECK(src);
+ auto dst =
+ authFactory->createCoordinateReferenceSystem("6319"); // NAD83(2011) 3D
+
+ auto listCompoundToGeog =
+ CoordinateOperationFactory::create()->createOperations(nnSrc, dst,
+ ctxt);
+ ASSERT_TRUE(!listCompoundToGeog.empty());
+
+ // NAD83(2011) + NAVD88 height
+ auto srcObjCompoundVMetre = createFromUserInput(
+ "EPSG:6318+5703", authFactory->databaseContext(), false);
+ auto srcCompoundVMetre = nn_dynamic_pointer_cast<CRS>(srcObjCompoundVMetre);
+ ASSERT_TRUE(srcCompoundVMetre != nullptr);
+ auto listCompoundMetreToGeog =
+ CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCompoundVMetre), dst, ctxt);
+
+ // Check that we get the same and similar results whether we start from
+ // regular NAVD88 height or its depth variant
+ ASSERT_EQ(listCompoundToGeog.size(), listCompoundMetreToGeog.size());
+
+ EXPECT_EQ(listCompoundToGeog[0]->nameStr(),
+ "Inverse of NAVD88 height to NAVD88 depth + " +
+ listCompoundMetreToGeog[0]->nameStr());
+ EXPECT_EQ(
+ listCompoundToGeog[0]->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ replaceAll(listCompoundMetreToGeog[0]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad",
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=axisswap +order=1,2,-3"));
+
+ // Check reverse path
+ auto listGeogToCompound =
+ CoordinateOperationFactory::create()->createOperations(dst, nnSrc,
+ ctxt);
+ EXPECT_EQ(listGeogToCompound.size(), listCompoundToGeog.size());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_geogCRS_with_vertical_unit_change_and_height_depth_reversal) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ // NAD83(2011) + NAVD88 depth (ftUS)
+ auto srcObj = createFromUserInput("EPSG:6318+6358",
+ authFactory->databaseContext(), false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto nnSrc = NN_NO_CHECK(src);
+ auto dst =
+ authFactory->createCoordinateReferenceSystem("6319"); // NAD83(2011) 3D
+
+ auto listCompoundToGeog =
+ CoordinateOperationFactory::create()->createOperations(nnSrc, dst,
+ ctxt);
+ ASSERT_TRUE(!listCompoundToGeog.empty());
+
+ // NAD83(2011) + NAVD88 height
+ auto srcObjCompoundVMetre = createFromUserInput(
+ "EPSG:6318+5703", authFactory->databaseContext(), false);
+ auto srcCompoundVMetre = nn_dynamic_pointer_cast<CRS>(srcObjCompoundVMetre);
+ ASSERT_TRUE(srcCompoundVMetre != nullptr);
+ auto listCompoundMetreToGeog =
+ CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCompoundVMetre), dst, ctxt);
+
+ // Check that we get the same and similar results whether we start from
+ // regular NAVD88 height or its depth (ftUS) variant
+ ASSERT_EQ(listCompoundToGeog.size(), listCompoundMetreToGeog.size());
+
+ EXPECT_EQ(listCompoundToGeog[0]->nameStr(),
+ "Inverse of NAVD88 height (ftUS) to NAVD88 depth (ftUS) + "
+ "Inverse of NAVD88 height to NAVD88 height (ftUS) + " +
+ listCompoundMetreToGeog[0]->nameStr());
+ EXPECT_EQ(
+ listCompoundToGeog[0]->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ replaceAll(listCompoundMetreToGeog[0]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad",
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=axisswap +order=1,2,-3 "
+ "+step +proj=unitconvert +z_in=us-ft +z_out=m"));
+
+ // Check reverse path
+ auto listGeogToCompound =
+ CoordinateOperationFactory::create()->createOperations(dst, nnSrc,
+ ctxt);
+ EXPECT_EQ(listGeogToCompound.size(), listCompoundToGeog.size());
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, compoundCRS_from_WKT2_to_geogCRS_3D_context) {
auto authFactory =
AuthorityFactory::create(DatabaseContext::create(), "EPSG");