aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKristian Evers <kristianevers@gmail.com>2020-12-21 17:28:48 +0100
committerKristian Evers <kristianevers@gmail.com>2020-12-21 17:28:48 +0100
commit5aad0d25f8423b8b88a716d0333c7bd19f6184c7 (patch)
treef0a248ef08fb51ef0ec29178ef41fd4168d4c85d
parentc3efbd23a5bf26f1dfd5bc55ae3488d5665ace98 (diff)
parent1cafe3e602d3f697c8d2daaa9b634f3ad23b0d53 (diff)
downloadPROJ-5aad0d25f8423b8b88a716d0333c7bd19f6184c7.tar.gz
PROJ-5aad0d25f8423b8b88a716d0333c7bd19f6184c7.zip
Merge remote-tracking branch 'osgeo/master'
-rw-r--r--Doxyfile2
-rw-r--r--data/CMakeLists.txt3
-rw-r--r--data/Makefile.am1
-rw-r--r--data/sql/alias_name.sql40
-rw-r--r--data/sql/conversion.sql4
-rw-r--r--data/sql/extent.sql13
-rw-r--r--data/sql/geodetic_crs.sql12
-rw-r--r--data/sql/geodetic_datum.sql4
-rw-r--r--data/sql/grid_alternatives.sql1
-rw-r--r--data/sql/grid_transformation.sql110
-rw-r--r--data/sql/helmert_transformation.sql4
-rw-r--r--data/sql/metadata.sql4
-rw-r--r--data/sql/nkg.sql2174
-rw-r--r--data/sql/other_transformation.sql12
-rw-r--r--data/sql/projected_crs.sql24
-rw-r--r--data/sql/scope.sql1
-rw-r--r--data/sql/vertical_crs.sql6
-rw-r--r--data/sql/vertical_datum.sql12
-rw-r--r--data/sql_filelist.cmake1
-rw-r--r--docs/source/apps/cs2cs.rst15
-rw-r--r--docs/source/apps/projinfo.rst10
-rw-r--r--docs/source/development/reference/datatypes.rst96
-rw-r--r--docs/source/development/reference/functions.rst36
-rw-r--r--docs/source/resource_files.rst14
-rw-r--r--include/proj/internal/Makefile.am3
-rw-r--r--scripts/build_esri_projection_mapping.py60
-rwxr-xr-xscripts/doxygen.sh2
-rw-r--r--scripts/reference_exported_symbols.txt3
-rwxr-xr-xscripts/reformat_cpp.sh5
-rw-r--r--src/4D_api.cpp98
-rw-r--r--src/Makefile.am18
-rw-r--r--src/aasincos.cpp4
-rw-r--r--src/apps/cs2cs.cpp33
-rw-r--r--src/apps/gie.cpp111
-rw-r--r--src/apps/projinfo.cpp27
-rw-r--r--src/conversions/axisswap.cpp27
-rw-r--r--src/conversions/set.cpp2
-rw-r--r--src/conversions/topocentric.cpp16
-rw-r--r--src/conversions/unitconvert.cpp42
-rw-r--r--src/ctx.cpp4
-rw-r--r--src/datum_set.cpp8
-rw-r--r--src/dmstor.cpp4
-rw-r--r--src/ell_set.cpp248
-rw-r--r--src/factors.cpp14
-rw-r--r--src/filemanager.cpp9
-rw-r--r--src/fwd.cpp19
-rw-r--r--src/gauss.cpp2
-rw-r--r--src/generic_inverse.cpp3
-rw-r--r--src/grids.cpp287
-rw-r--r--src/init.cpp106
-rw-r--r--src/internal.cpp93
-rw-r--r--src/inv.cpp10
-rw-r--r--src/iso19111/c_api.cpp74
-rw-r--r--src/iso19111/coordinateoperation.cpp16224
-rw-r--r--src/iso19111/crs.cpp3
-rw-r--r--src/iso19111/factory.cpp4
-rw-r--r--src/iso19111/io.cpp9
-rw-r--r--src/iso19111/operation/concatenatedoperation.cpp710
-rw-r--r--src/iso19111/operation/conversion.cpp3955
-rw-r--r--src/iso19111/operation/coordinateoperation_internal.hpp (renamed from include/proj/internal/coordinateoperation_internal.hpp)56
-rw-r--r--src/iso19111/operation/coordinateoperation_private.hpp88
-rw-r--r--src/iso19111/operation/coordinateoperationfactory.cpp5236
-rw-r--r--src/iso19111/operation/esriparammappings.cpp (renamed from include/proj/internal/esri_projection_mappings.hpp)127
-rw-r--r--src/iso19111/operation/esriparammappings.hpp86
-rw-r--r--src/iso19111/operation/operationmethod_private.hpp56
-rw-r--r--src/iso19111/operation/oputils.cpp643
-rw-r--r--src/iso19111/operation/oputils.hpp121
-rw-r--r--src/iso19111/operation/parammappings.cpp (renamed from include/proj/internal/coordinateoperation_constants.hpp)211
-rw-r--r--src/iso19111/operation/parammappings.hpp114
-rw-r--r--src/iso19111/operation/projbasedoperation.cpp316
-rw-r--r--src/iso19111/operation/singleoperation.cpp2218
-rw-r--r--src/iso19111/operation/transformation.cpp3274
-rw-r--r--src/iso19111/operation/vectorofvaluesparams.cpp116
-rw-r--r--src/iso19111/operation/vectorofvaluesparams.hpp101
-rw-r--r--src/iso19111/static.cpp12
-rw-r--r--src/lib_proj.cmake11
-rw-r--r--src/log.cpp90
-rw-r--r--src/mlfn.hpp2
-rw-r--r--src/networkfilemanager.cpp6
-rw-r--r--src/param.cpp2
-rw-r--r--src/phi2.cpp2
-rw-r--r--src/pipeline.cpp37
-rw-r--r--src/proj.h30
-rw-r--r--src/proj_internal.h83
-rw-r--r--src/proj_mdist.cpp2
-rw-r--r--src/projections/adams.cpp8
-rw-r--r--src/projections/aea.cpp30
-rw-r--r--src/projections/aeqd.cpp8
-rw-r--r--src/projections/airy.cpp8
-rw-r--r--src/projections/aitoff.cpp13
-rw-r--r--src/projections/bacon.cpp6
-rw-r--r--src/projections/bertin1953.cpp2
-rw-r--r--src/projections/bipc.cpp14
-rw-r--r--src/projections/bonne.cpp13
-rw-r--r--src/projections/calcofi.cpp4
-rw-r--r--src/projections/cass.cpp4
-rw-r--r--src/projections/cc.cpp2
-rw-r--r--src/projections/ccon.cpp10
-rw-r--r--src/projections/cea.cpp14
-rw-r--r--src/projections/chamb.cpp7
-rw-r--r--src/projections/col_urban.cpp2
-rw-r--r--src/projections/collg.cpp2
-rw-r--r--src/projections/comill.cpp2
-rw-r--r--src/projections/eck2.cpp2
-rw-r--r--src/projections/eck3.cpp8
-rw-r--r--src/projections/eqc.cpp7
-rw-r--r--src/projections/eqdc.cpp30
-rw-r--r--src/projections/eqearth.cpp6
-rw-r--r--src/projections/fouc_s.cpp7
-rw-r--r--src/projections/geos.cpp18
-rw-r--r--src/projections/gn_sinu.cpp44
-rw-r--r--src/projections/gnom.cpp4
-rw-r--r--src/projections/goode.cpp6
-rw-r--r--src/projections/gstmerc.cpp2
-rw-r--r--src/projections/hammer.cpp16
-rw-r--r--src/projections/hatano.cpp4
-rw-r--r--src/projections/healpix.cpp26
-rw-r--r--src/projections/igh.cpp6
-rw-r--r--src/projections/igh_o.cpp8
-rw-r--r--src/projections/imw_p.cpp30
-rw-r--r--src/projections/isea.cpp11
-rw-r--r--src/projections/krovak.cpp7
-rw-r--r--src/projections/labrd.cpp5
-rw-r--r--src/projections/laea.cpp15
-rw-r--r--src/projections/lagrng.cpp16
-rw-r--r--src/projections/lcc.cpp42
-rw-r--r--src/projections/lcca.cpp9
-rw-r--r--src/projections/loxim.cpp8
-rw-r--r--src/projections/lsat.cpp17
-rw-r--r--src/projections/mbtfpp.cpp4
-rw-r--r--src/projections/mbtfpq.cpp4
-rw-r--r--src/projections/merc.cpp5
-rw-r--r--src/projections/misrsom.cpp9
-rw-r--r--src/projections/mod_ster.cpp12
-rw-r--r--src/projections/moll.cpp6
-rw-r--r--src/projections/natearth.cpp2
-rw-r--r--src/projections/natearth2.cpp2
-rw-r--r--src/projections/nsper.cpp13
-rw-r--r--src/projections/ob_tran.cpp44
-rw-r--r--src/projections/ocea.cpp2
-rw-r--r--src/projections/oea.cpp41
-rw-r--r--src/projections/omerc.cpp60
-rw-r--r--src/projections/ortho.cpp14
-rw-r--r--src/projections/patterson.cpp2
-rw-r--r--src/projections/poly.cpp10
-rw-r--r--src/projections/putp3.cpp4
-rw-r--r--src/projections/putp4p.cpp4
-rw-r--r--src/projections/putp5.cpp4
-rw-r--r--src/projections/putp6.cpp4
-rw-r--r--src/projections/qsc.cpp2
-rw-r--r--src/projections/robin.cpp10
-rw-r--r--src/projections/rouss.cpp6
-rw-r--r--src/projections/rpoly.cpp2
-rw-r--r--src/projections/sch.cpp15
-rw-r--r--src/projections/sconics.cpp33
-rw-r--r--src/projections/somerc.cpp4
-rw-r--r--src/projections/stere.cpp15
-rw-r--r--src/projections/sterea.cpp6
-rw-r--r--src/projections/sts.cpp8
-rw-r--r--src/projections/tcc.cpp2
-rw-r--r--src/projections/tmerc.cpp42
-rw-r--r--src/projections/tobmerc.cpp2
-rw-r--r--src/projections/tpeqd.cpp10
-rw-r--r--src/projections/urm5.cpp23
-rw-r--r--src/projections/urmfps.cpp21
-rw-r--r--src/projections/vandg.cpp8
-rw-r--r--src/projections/vandg2.cpp4
-rw-r--r--src/projections/wag3.cpp2
-rw-r--r--src/projections/wink1.cpp2
-rw-r--r--src/projections/wink2.cpp2
-rw-r--r--src/strerrno.cpp143
-rw-r--r--src/transformations/affine.cpp6
-rw-r--r--src/transformations/defmodel.cpp48
-rw-r--r--src/transformations/deformation.cpp42
-rw-r--r--src/transformations/helmert.cpp32
-rw-r--r--src/transformations/hgridshift.cpp8
-rw-r--r--src/transformations/horner.cpp74
-rw-r--r--src/transformations/molodensky.cpp54
-rw-r--r--src/transformations/tinshift.cpp20
-rw-r--r--src/transformations/vgridshift.cpp8
-rw-r--r--src/transformations/xyzgridshift.cpp14
-rwxr-xr-xtest/cli/testprojinfo4
-rw-r--r--test/cli/testprojinfo_out.dist3
-rwxr-xr-xtest/cli/testvarious12
-rw-r--r--test/cli/tv_out.dist4
-rw-r--r--test/gie/4D-API_cs2cs-style.gie4
-rw-r--r--test/gie/adams_hemi.gie776
-rw-r--r--test/gie/adams_ws1.gie114
-rw-r--r--test/gie/adams_ws2.gie114
-rw-r--r--test/gie/builtins.gie194
-rw-r--r--test/gie/defmodel.gie6
-rw-r--r--test/gie/deformation.gie20
-rw-r--r--test/gie/ellipsoid.gie28
-rw-r--r--test/gie/geotiff_grids.gie12
-rw-r--r--test/gie/guyou.gie772
-rw-r--r--test/gie/more_builtins.gie32
-rw-r--r--test/gie/nkg.gie240
-rw-r--r--test/gie/peirce_q.gie106
-rw-r--r--test/gie/tinshift.gie6
-rw-r--r--test/unit/CMakeLists.txt1
-rw-r--r--test/unit/Makefile.am13
-rw-r--r--test/unit/gie_self_tests.cpp44
-rw-r--r--test/unit/proj_errno_string_test.cpp43
-rw-r--r--test/unit/test_c_api.cpp66
-rw-r--r--test/unit/test_factory.cpp6
-rw-r--r--test/unit/test_operation.cpp5815
-rw-r--r--test/unit/test_operationfactory.cpp5954
207 files changed, 28947 insertions, 24735 deletions
diff --git a/Doxyfile b/Doxyfile
index 6da7f7b9..f76b6b1d 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -766,7 +766,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
-INPUT = src/iso19111 include/proj src/proj.h src/proj_experimental.h src/general_doc.dox src/filemanager.cpp src/networkfilemanager.cpp
+INPUT = src/iso19111 src/iso19111/operation include/proj src/proj.h src/proj_experimental.h src/general_doc.dox src/filemanager.cpp src/networkfilemanager.cpp
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt
index fa2e35d5..6b8a97ed 100644
--- a/data/CMakeLists.txt
+++ b/data/CMakeLists.txt
@@ -27,6 +27,8 @@ file(GLOB GSB_FILES *.gsb)
file(GLOB GTX_FILES *.gtx)
set(GRIDSHIFT_FILES ${GSB_FILES} ${GTX_FILES})
+file(GLOB SCHEMA_FILES *.json)
+
set(ALL_SQL_IN "${CMAKE_CURRENT_BINARY_DIR}/all.sql.in")
set(PROJ_DB "${CMAKE_CURRENT_BINARY_DIR}/proj.db")
include(sql_filelist.cmake)
@@ -106,6 +108,7 @@ set(ALL_DATA_FILE
${PROJ_DICTIONARY}
${GRIDSHIFT_FILES}
${PROJ_DB}
+ ${SCHEMA_FILES}
)
install(
FILES ${ALL_DATA_FILE}
diff --git a/data/Makefile.am b/data/Makefile.am
index eda69a9a..8f65ed5c 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -42,6 +42,7 @@ SQL_ORDERED_LIST = sql/begin.sql \
sql/grid_alternatives.sql \
sql/grid_alternatives_generated_noaa.sql \
sql/customizations.sql \
+ sql/nkg.sql \
sql/commit.sql
EXTRA_DIST = proj.ini GL27 nad.lst nad27 nad83 \
diff --git a/data/sql/alias_name.sql b/data/sql/alias_name.sql
index 6dcb36c1..9173abd8 100644
--- a/data/sql/alias_name.sql
+++ b/data/sql/alias_name.sql
@@ -192,7 +192,6 @@ INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','1192','NAD83(CSRS96)','
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','1195','NAD83(CSRS)v4','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6265','Rome 1940','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6806','Rome 1940 (Rome)','EPSG');
-INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6670','IGM95','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6673','CI1979','EPSG');
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','5172','NG-L','EPSG');
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','5174','NN1954','EPSG');
@@ -333,7 +332,6 @@ INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','5202','Bora Bora 2001',
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','5124','Fahud HD','EPSG');
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','5132','DNN','EPSG');
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','5138','ODN Orkney','EPSG');
-INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','5215','EVRF2007','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6743','Karbala 1979 (Polservice)','EPSG');
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','5149','British Vertical Datum','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','1029','IGRS','EPSG');
@@ -361,7 +359,6 @@ INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6903','Madrid','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','1156','WGS 84 (G1762)','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','1157','PZ-90.02','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','1158','PZ-90.11','EPSG');
-INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1274','EVRF2019','EPSG');
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1275','REDNAP','EPSG');
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1276','REDNAP','EPSG');
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1277','REDNAP','EPSG');
@@ -506,7 +503,6 @@ INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1283','REDNAP','EPSG');
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1284','REDNAP','EPSG');
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1285','REDNAP','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','1243','SIRGAS-Chile 2010','EPSG');
-INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1287','EVRF2019mean','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','1289','GBK19-IRF','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','1291','ATRF2014','EPSG');
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1270','MSL NL','EPSG');
@@ -611,6 +607,11 @@ INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','1132','RDN2008','EPSG')
INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1288','BI','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6258','European Terrestrial Reference System 1989','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6326','World Geodetic System 1984','EPSG');
+INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6670','IGM95','EPSG');
+INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','5215','EVRF2007','EPSG');
+INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1274','EVRF2019','EPSG');
+INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1287','EVRF2019mean','EPSG');
+INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1302','Pago Pago 2020','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','21100','Genuk / NEIEZ','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','2140','NAD83(CSRS98) / SCoPQ zone 3','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','2141','NAD83(CSRS98) / SCoPQ zone 4','EPSG');
@@ -1028,7 +1029,6 @@ INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29849','Timbalai 1948 /
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29850','Timbalai 1948 / UTM 50N','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29871','Timbalai / Borneo (ch)','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29872','Timbalai / Borneo (ftSe)','EPSG');
-INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29873','Timbalai / Borneo (m)','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29900','TM65 / Irish Nat Grid','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','30161','Tokyo / Japan zone I','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','30162','Tokyo / Japan zone II','EPSG');
@@ -1714,8 +1714,6 @@ INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6861','CORS96 / OCRS_SAN
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','3069','NAD27 / WTM 27','EPSG');
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5723','Japanese Standard Levelling Datum height','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','7005','Nahrwan 1934 / UTM 37N','EPSG');
-INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6875','RDN2008 / Fuso Italia (N-E)','EPSG');
-INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6876','RDN2008 / Fuso 12 (N-E)','EPSG');
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5787','Baltic 1980 height','EPSG');
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5782','REDNAP height','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','3072','Maine Coordinate System of 2000 East Zone','EPSG');
@@ -2205,10 +2203,8 @@ INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','4298','Timbalai 1968','EP
INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','4298','BT68','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29849','BT68 / UTM zone 49N','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29850','BT68 / UTM zone 50N','EPSG');
-INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29873','BT68 / RSO Borneo (m)','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29849','Timbalai 1968 / UTM zone 49N','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29850','Timbalai 1968 / UTM zone 50N','EPSG');
-INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29873','Timbalai 1968 / RSO Borneo (m)','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','4245','MRT68','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','24500','MRT68 / Singapore Grid','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','24547','MRT68 / UTM zone 47N','EPSG');
@@ -4762,7 +4758,6 @@ INSERT INTO "alias_name" VALUES('projected_crs','EPSG','3857','Web Mercator','EP
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','9391','BGS2005 / UTM zone 35 (E-N)','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','5634','REGCAN95 - LCC','EPSG');
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','9400','REDNAP height','EPSG');
-INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','9389','EVRF2019_AMST / NH','EPSG');
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','9392','REDNAP height','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','7692','Kyrg06-68','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','7693','Kyrg06-71','EPSG');
@@ -6078,7 +6073,6 @@ INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','8690','SI_KOP / NH','EPSG
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5195','SI_TRIE / NOH','EPSG');
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5195','MK_TRIE / NOH','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','4826','WGS 84 / Cape Verde New','EPSG');
-INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','9390','EVRF2019mean_AMST / NH','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','4479','732','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','4480','733','EPSG');
INSERT INTO "alias_name" VALUES('compound_crs','EPSG','9450','ETRS89 + Belfast Lough height','EPSG');
@@ -7184,9 +7178,6 @@ INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6677','JGD2011 / Japan z
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','6693','Japan Levelling Datum height','EPSG');
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','6693','Japanese Standard Levelling Datum height','EPSG');
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','6693','JSLD height','EPSG');
-INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6707','RDN2008 / TM32','EPSG');
-INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6708','RDN2008 / TM33','EPSG');
-INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6709','RDN2008 / TM34','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','2062','Madrid (Madrid) / Spain LCC','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','2062','Madrid - LCC','EPSG');
INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','9380','725','EPSG');
@@ -7196,4 +7187,25 @@ INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5711','AHD71 height','EPS
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5711','AHD-TAS83 height','EPSG');
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5711','339','EPSG');
INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5711','AHD - NOHt','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29873','Timbalai / Borneo (m)','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29873','BT68 / RSO Borneo (m)','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29873','Timbalai 1968 / RSO Borneo (m)','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','29874','BT68 / RSO Sarawak LSD (m)','EPSG');
+INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','6705','ETRF2000 epoca 2008.0','EPSG');
+INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','6704','ETRF2000 epoca 2008.0','EPSG');
+INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','6706','ETRF2000 epoca 2008.0','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6708','RDN2008 / TM33','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6708','ETRF2000 epoca 2008.0 fuso 33','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6707','RDN2008 / TM32','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6707','ETRF2000 epoca 2008.0 fuso 32','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6709','RDN2008 / TM34','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6709','ETRF2000 epoca 2008.0 fuso 34','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6875','RDN2008 / Fuso Italia (N-E)','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6875','ETRF2000 epoca 2008.0 fuso Italia','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6876','RDN2008 / Fuso 12 (N-E)','EPSG');
+INSERT INTO "alias_name" VALUES('projected_crs','EPSG','6876','ETRF2000 epoca 2008.0 fuso 12','EPSG');
INSERT INTO "alias_name" VALUES('projected_crs','EPSG','9498','CABA-P07','EPSG');
+INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','9389','EVRF2019_AMST / NH','EPSG');
+INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','9390','EVRF2019mean_AMST / NH','EPSG');
+INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','9675','741','EPSG');
+INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','9675','LT at Pago Pago - NOHt','EPSG');
diff --git a/data/sql/conversion.sql b/data/sql/conversion.sql
index 76a4bfc1..f5666316 100644
--- a/data/sql/conversion.sql
+++ b/data/sql/conversion.sql
@@ -1736,6 +1736,8 @@ INSERT INTO "conversion" VALUES('EPSG','9455','GBK19-TM','In conjunction with tr
INSERT INTO "usage" VALUES('EPSG','14130','conversion','EPSG','9455','EPSG','4607','EPSG','1141');
INSERT INTO "conversion" VALUES('EPSG','9497','Gauss-Kruger CABA 2019','Projection created in 2017 for the purpose of modernizing the city''s cadastre and linking it to modern reference frames. Origin approximates the 1919 origin of the cross of the main tower of the San José de Flores church ("0 de Flores" plane grid).','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',-34.374536,'EPSG','9110','EPSG','8802','Longitude of natural origin',-58.274791,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.0,'EPSG','9201','EPSG','8806','False easting',20000.0,'EPSG','9001','EPSG','8807','False northing',70000.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
INSERT INTO "usage" VALUES('EPSG','14242','conversion','EPSG','9497','EPSG','4610','EPSG','1056');
+INSERT INTO "conversion" VALUES('EPSG','9673','US Forest Service region 6 Albers','','EPSG','9822','Albers Equal Area','EPSG','8821','Latitude of false origin',34.0,'EPSG','9102','EPSG','8822','Longitude of false origin',-120.0,'EPSG','9102','EPSG','8823','Latitude of 1st standard parallel',43.0,'EPSG','9102','EPSG','8824','Latitude of 2nd standard parallel',48.0,'EPSG','9102','EPSG','8826','Easting at false origin',600000.0,'EPSG','9001','EPSG','8827','Northing at false origin',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,0);
+INSERT INTO "usage" VALUES('EPSG','14786','conversion','EPSG','9673','EPSG','2381','EPSG','1165');
INSERT INTO "conversion" VALUES('EPSG','10101','Alabama CS27 East zone','','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',30.3,'EPSG','9110','EPSG','8802','Longitude of natural origin',-85.5,'EPSG','9110','EPSG','8805','Scale factor at natural origin',0.99996,'EPSG','9201','EPSG','8806','False easting',500000.0,'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 "usage" VALUES('EPSG','11101','conversion','EPSG','10101','EPSG','2154','EPSG','1142');
INSERT INTO "conversion" VALUES('EPSG','10102','Alabama CS27 West zone','','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',30.0,'EPSG','9110','EPSG','8802','Longitude of natural origin',-87.3,'EPSG','9110','EPSG','8805','Scale factor at natural origin',0.999933333,'EPSG','9201','EPSG','8806','False easting',500000.0,'EPSG','9003','EPSG','8807','False northing',0.0,'EPSG','9003',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
@@ -4547,6 +4549,8 @@ INSERT INTO "conversion" VALUES('EPSG','18451','CS63 zone C1','','EPSG','9807','
INSERT INTO "usage" VALUES('EPSG','12917','conversion','EPSG','18451','EPSG','3174','EPSG','1207');
INSERT INTO "conversion" VALUES('EPSG','18452','CS63 zone C2','','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',0.06,'EPSG','9110','EPSG','8802','Longitude of natural origin',27.57,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.0,'EPSG','9201','EPSG','8806','False easting',2250000.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 "usage" VALUES('EPSG','12918','conversion','EPSG','18452','EPSG','3175','EPSG','1207');
+INSERT INTO "conversion" VALUES('EPSG','19838','Rectified Skew Orthomorphic Sarawak LSD (metres)','Used by the Sarawak Land and Survey Department. Conversion 19958 but using Hotine Oblique Mercator (variant A) and with FE increased by 2,000,000 m and FN by 5,000,000 m. If using variant B method (code 9815) FE = 2590476.871 m and FN = 5442857.653 m.','EPSG','9812','Hotine Oblique Mercator (variant A)','EPSG','8811','Latitude of projection centre',4.0,'EPSG','9110','EPSG','8812','Longitude of projection centre',115.0,'EPSG','9110','EPSG','8813','Azimuth of initial line',53.18569537,'EPSG','9110','EPSG','8814','Angle from Rectified to Skew Grid',53.07483685,'EPSG','9110','EPSG','8815','Scale factor on initial line',0.99984,'EPSG','9201','EPSG','8806','False easting',2000000.0,'EPSG','9001','EPSG','8807','False northing',5000000.0,'EPSG','9001',0);
+INSERT INTO "usage" VALUES('EPSG','14399','conversion','EPSG','19838','EPSG','4611','EPSG','1142');
INSERT INTO "conversion" VALUES('EPSG','19839','Dubai Local Transverse Mercator','','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',0.0,'EPSG','9110','EPSG','8802','Longitude of natural origin',55.2,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.0,'EPSG','9201','EPSG','8806','False easting',500000.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 "usage" VALUES('EPSG','12919','conversion','EPSG','19839','EPSG','3531','EPSG','1055');
INSERT INTO "conversion" VALUES('EPSG','19840','IBCAO Polar Stereographic','Used for the International Bathymetric Chart of Arctic Ocean.','EPSG','9829','Polar Stereographic (variant B)','EPSG','8832','Latitude of standard parallel',75.0,'EPSG','9102','EPSG','8833','Longitude of origin',0.0,'EPSG','9102','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);
diff --git a/data/sql/extent.sql b/data/sql/extent.sql
index 1c94827d..8e4239c7 100644
--- a/data/sql/extent.sql
+++ b/data/sql/extent.sql
@@ -1202,8 +1202,8 @@ INSERT INTO "extent" VALUES('EPSG','2222','USA - Nebraska - SPCS27 - S','United
INSERT INTO "extent" VALUES('EPSG','2223','USA - Nevada - SPCS - C','United States (USA) - Nevada - counties of Lander; Nye.',36.0,41.0,-118.19,-114.99,0);
INSERT INTO "extent" VALUES('EPSG','2224','USA - Nevada - SPCS - E','United States (USA) - Nevada - counties of Clark; Elko; Eureka; Lincoln; White Pine.',34.99,42.0,-117.01,-114.03,0);
INSERT INTO "extent" VALUES('EPSG','2225','USA - Nevada - SPCS - W','United States (USA) - Nevada - counties of Churchill; Douglas; Esmeralda; Humboldt; Lyon; Mineral; Pershing; Storey; Washoe.',36.95,42.0,-120.0,-116.99,0);
-INSERT INTO "extent" VALUES('EPSG','2226','Canada - Newfoundland - east of 54.5°W','Canada - Newfoundland - onshore east of 54°30''W.',46.56,49.71,-54.5,-52.54,0);
-INSERT INTO "extent" VALUES('EPSG','2227','Canada - Newfoundland and Labrador - 57.5°W to 54.5°W','Canada - Newfoundland and Labrador between 57°30''W and 54°30''W.',46.81,54.71,-57.5,-54.49,0);
+INSERT INTO "extent" VALUES('EPSG','2226','Canada - Newfoundland - east of 54.5°W','Canada - Newfoundland - onshore east of 54°30''W.',46.567800287196,49.880440168616,-54.5,-52.543563434093,0);
+INSERT INTO "extent" VALUES('EPSG','2227','Canada - Newfoundland and Labrador - 57.5°W to 54.5°W','Canada - Newfoundland and Labrador between 57°30''W and 54°30''W.',46.810451303426,54.704265687623,-57.5,-54.5,0);
INSERT INTO "extent" VALUES('EPSG','2228','USA - New Mexico - SPCS - E','United States (USA) - New Mexico - counties of Chaves; Colfax; Curry; De Baca; Eddy; Guadalupe; Harding; Lea; Mora; Quay; Roosevelt; San Miguel; Union.',32.0,37.0,-105.72,-102.99,0);
INSERT INTO "extent" VALUES('EPSG','2229','USA - New Mexico - SPCS27 - C','United States (USA) - New Mexico - counties of Bernalillo; Dona Ana; Lincoln; Los Alamos; Otero; Rio Arriba; Sandoval; Santa Fe; Socorro; Taos; Torrance.',31.78,37.0,-107.73,-104.83,0);
INSERT INTO "extent" VALUES('EPSG','2230','USA - New Mexico - SPCS27 - W','United States (USA) - New Mexico - counties of Catron; Cibola; Grant; Hidalgo; Luna; McKinley; San Juan; Sierra; Valencia.',31.33,37.0,-109.06,-106.32,0);
@@ -1346,7 +1346,7 @@ INSERT INTO "extent" VALUES('EPSG','2366','Spain - mainland onshore','Spain - ma
INSERT INTO "extent" VALUES('EPSG','2367','Spain - mainland northeast','Spain - onshore mainland north of the parallel of approximately 41°58''N from approximately 6°35''W to the meridian of 4°W of Greenwich and then a line from 41°58''N, 4°W through 40°N, 0°E of Greenwich.',39.96,43.82,-9.37,3.39,0);
INSERT INTO "extent" VALUES('EPSG','2368','Spain - mainland southwest','Spain - onshore mainland south of the parallel of approximately 41°58''N from approximately 6°35''W to the meridian of 4°W of Greenwich and then a line from 41°58''N, 4°W through 40°N, 0°E of Greenwich.',35.95,41.98,-7.54,0.28,0);
INSERT INTO "extent" VALUES('EPSG','2369','Seychelles - Mahe Island','Seychelles - Mahe Island.',-4.86,-4.5,55.3,55.59,0);
-INSERT INTO "extent" VALUES('EPSG','2370','Europe - former Yugoslavia onshore','Boznia and Herzegovina; Croatia - onshore; Kosovo; Montenegro - onshore; North Macedonia; Serbia; Slovenia - onshore.',40.85,46.88,13.38,23.04,0);
+INSERT INTO "extent" VALUES('EPSG','2370','Europe - former Yugoslavia onshore','Bosnia and Herzegovina; Croatia - onshore; Kosovo; Montenegro - onshore; North Macedonia; Serbia; Slovenia - onshore.',40.855888366699,46.876247406006,13.383471488953,23.030969619751,0);
INSERT INTO "extent" VALUES('EPSG','2371','Nigeria - south','Nigeria - onshore south.',4.22,6.95,4.35,9.45,0);
INSERT INTO "extent" VALUES('EPSG','2372','Italy - mainland','Italy - mainland including San Marino and Vatican City State.',37.86,47.1,6.62,18.58,0);
INSERT INTO "extent" VALUES('EPSG','2373','USA - Alaska including EEZ','United States (USA) - Alaska including EEZ.',47.88,74.71,167.65,-129.99,0);
@@ -1392,7 +1392,7 @@ INSERT INTO "extent" VALUES('EPSG','2412','USA - Alaska mainland','United States
INSERT INTO "extent" VALUES('EPSG','2413','Bahamas - main islands onshore','Bahamas - onshore southwest of a line from 27°30''N, 77°30''W through 23°15''N, 74°30''W to 22°30''N, 72°30''W.',20.86,27.29,-79.04,-72.68,0);
INSERT INTO "extent" VALUES('EPSG','2414','Bahamas (San Salvador Island) - onshore','Bahamas (San Salvador Island) - onshore.',23.9,24.19,-74.6,-74.37,0);
INSERT INTO "extent" VALUES('EPSG','2415','Canada - Manitoba and Ontario','Canada - Manitoba; Ontario.',41.67,60.01,-102.0,-74.35,0);
-INSERT INTO "extent" VALUES('EPSG','2416','Canada - eastern provinces','Canada - onshore - New Brunswick; Newfoundland and Labrador; Nova Scotia; Prince Edward Island; Quebec.',43.41,62.62,-79.85,-52.54,0);
+INSERT INTO "extent" VALUES('EPSG','2416','Canada - eastern provinces','Canada - onshore - New Brunswick; Newfoundland and Labrador; Nova Scotia; Prince Edward Island; Quebec.',43.415647861102,62.61106035396,-79.847158104293,-52.543563434093,0);
INSERT INTO "extent" VALUES('EPSG','2417','Canada - Yukon','Canada - Yukon.',59.99,69.7,-141.01,-123.91,0);
INSERT INTO "extent" VALUES('EPSG','2418','Caribbean - central (DMA tfm)','Antigua; Barbados; Barbuda; Cuba; Dominican Republic; Grand Cayman; Jamaica; Turks and Caicos Islands. Note: does not include other islands within this geographic area.',13.0,23.25,-85.01,-59.37,0);
INSERT INTO "extent" VALUES('EPSG','2419','Central America - Belize to Costa Rica','Onshore Belize, Costa Rica, El Salvador, Guatemala, Honduras and Nicaragua.',7.98,18.49,-92.29,-82.53,0);
@@ -1807,7 +1807,7 @@ INSERT INTO "extent" VALUES('EPSG','2827','Africa - South Sudan and Sudan - 24°
INSERT INTO "extent" VALUES('EPSG','2828','Guadeloupe - St Martin and St Barthelemy - onshore','Guadeloupe - onshore - St Martin and St Barthélemy islands.',17.82,18.17,-63.21,-62.73,0);
INSERT INTO "extent" VALUES('EPSG','2829','Guadeloupe - Grande-Terre and surrounding islands - onshore','Guadeloupe - onshore - Basse-Terre, Grande-Terre, La Desirade, Marie-Galante, Les Saintes.',15.8,16.55,-61.85,-60.97,0);
INSERT INTO "extent" VALUES('EPSG','2830','World (by country)','World: Afghanistan, Albania, Algeria, American Samoa, Andorra, Angola, Anguilla, Antarctica, Antigua and Barbuda, Argentina, Armenia, Aruba, Australia, Austria, Azerbaijan, Bahamas, Bahrain, Bangladesh, Barbados, Belgium, Belgium, Belize, Benin, Bermuda, Bhutan, Bolivia, Bonaire, Saint Eustasius and Saba, Bosnia and Herzegovina, Botswana, Bouvet Island, Brazil, British Indian Ocean Territory, British Virgin Islands, Brunei Darussalam, Bulgaria, Burkina Faso, Burundi, Cambodia, Cameroon, Canada, Cape Verde, Cayman Islands, Central African Republic, Chad, Chile, China, Christmas Island, Cocos (Keeling) Islands, Comoros, Congo, Cook Islands, Costa Rica, Côte d''Ivoire (Ivory Coast), Croatia, Cuba, Curacao, Cyprus, Czechia, Denmark, Djibouti, Dominica, Dominican Republic, East Timor, Ecuador, Egypt, El Salvador, Equatorial Guinea, Eritrea, Estonia, Eswatini (Swaziland), Ethiopia, Falkland Islands (Malvinas), Faroe Islands, Fiji, Finland, France, French Guiana, French Polynesia, French Southern Territories, Gabon, Gambia, Georgia, Germany, Ghana, Gibraltar, Greece, Greenland, Grenada, Guadeloupe, Guam, Guatemala, Guinea, Guinea-Bissau, Guyana, Haiti, Heard Island and McDonald Islands, Holy See (Vatican City State), Honduras, China - Hong Kong, Hungary, Iceland, India, Indonesia, Islamic Republic of Iran, Iraq, Ireland, Israel, Italy, Jamaica, Japan, Jordan, Kazakhstan, Kenya, Kiribati, Democratic People''s Republic of Korea (North Korea), Republic of Korea (South Korea), Kosovo, Kuwait, Kyrgyzstan, Lao People''s Democratic Republic (Laos), Latvia, Lebanon, Lesotho, Liberia, Libyan Arab Jamahiriya, Liechtenstein, Lithuania, Luxembourg, China - Macao, Madagascar, Malawi, Malaysia, Maldives, Mali, Malta, Marshall Islands, Martinique, Mauritania, Mauritius, Mayotte, Mexico, Federated States of Micronesia, Monaco, Mongolia, Montenegro, Montserrat, Morocco, Mozambique, Myanmar (Burma), Namibia, Nauru, Nepal, Netherlands, New Caledonia, New Zealand, Nicaragua, Niger, Nigeria, Niue, Norfolk Island, North Macedonia, Northern Mariana Islands, Norway, Oman, Pakistan, Palau, Panama, Papua New Guinea (PNG), Paraguay, Peru, Philippines, Pitcairn, Poland, Portugal, Puerto Rico, Qatar, Reunion, Romania, Russian Federation, Rwanda, Saint Kitts and Nevis, Saint Helena, Ascension and Tristan da Cunha, Saint Lucia, Saint Pierre and Miquelon, Saint Vincent and the Grenadines, Samoa, San Marino, Sao Tome and Principe, Saudi Arabia, Senegal, Serbia, Seychelles, Sierra Leone, Singapore, Slovakia (Slovak Republic), Slovenia, Sint Maarten, Solomon Islands, Somalia, South Africa, South Georgia and the South Sandwich Islands, South Sudan, Spain, Sri Lanka, Sudan, Suriname, Svalbard and Jan Mayen, Sweden, Switzerland, Syrian Arab Republic, Taiwan, Tajikistan, United Republic of Tanzania, Thailand, The Democratic Republic of the Congo (Zaire), Togo, Tokelau, Tonga, Trinidad and Tobago, Tunisia, Turkey, Turkmenistan, Turks and Caicos Islands, Tuvalu, Uganda, Ukraine, United Arab Emirates (UAE), United Kingdom (UK), United States (USA), United States Minor Outlying Islands, Uruguay, Uzbekistan, Vanuatu, Venezuela, Vietnam, US Virgin Islands, Wallis and Futuna, Western Sahara, Yemen, Zambia, Zimbabwe.',-90.0,90.0,-180.0,180.0,0);
-INSERT INTO "extent" VALUES('EPSG','2831','Canada - Atlantic offshore','Canada - offshore Newfoundland and Labrador, New Brunswick and Nova Scotia.',40.04,64.21,-67.75,-47.74,0);
+INSERT INTO "extent" VALUES('EPSG','2831','Canada - Atlantic offshore','Canada - offshore Newfoundland and Labrador, New Brunswick and Nova Scotia.',40.040199904302,64.205208824162,-67.743055559783,-47.743430543984,0);
INSERT INTO "extent" VALUES('EPSG','2832','Canada - British Columbia','Canada - British Columbia.',48.25,60.01,-139.04,-114.08,0);
INSERT INTO "extent" VALUES('EPSG','2833','Sweden - 12 00','Sweden - communes west of approximately 12°45''E and south of approximately 60°N. See information source for map.',56.74,60.13,10.93,13.11,0);
INSERT INTO "extent" VALUES('EPSG','2834','Sweden - 13 30','Sweden - communes between approximately 12°45''E and 14°15''E and south of approximately 62°10''N. See information source for map.',55.28,62.28,12.12,14.79,0);
@@ -3586,3 +3586,6 @@ INSERT INTO "extent" VALUES('EPSG','4607','UK - Glasgow to Kilmarnock','UK - on
INSERT INTO "extent" VALUES('EPSG','4608','Europe - EVRF2019','Europe - onshore - Andorra; Austria; Belarus; Belgium; Bosnia and Herzegovina; Bulgaria; Croatia; Czechia; Denmark; Estonia; Finland; France - mainland; Germany; Gibraltar, Hungary; Italy - mainland and Sicily; Latvia; Liechtenstein; Lithuania; Luxembourg; Netherlands; North Macedonia; Norway; Poland; Portugal - mainland; Romania; Russia – west of approximately 60°E; San Marino; Slovakia; Slovenia; Spain - mainland; Sweden; Switzerland; United Kingdom (UK) - Great Britain mainland; Ukraine; Vatican City State.',35.95,77.07,-9.56,69.15,0);
INSERT INTO "extent" VALUES('EPSG','4609','Europe - ETRF EVRF2019','Europe - onshore - Andorra; Austria; Belgium; Bosnia and Herzegovina; Bulgaria; Croatia; Czechia; Denmark; Estonia; Finland; France - mainland; Germany; Gibraltar, Hungary; Italy - mainland and Sicily; Latvia; Liechtenstein; Lithuania; Luxembourg; Netherlands; North Macedonia; Norway; Poland; Portugal - mainland; Romania; San Marino; Slovakia; Slovenia; Spain - mainland; Sweden; Switzerland; United Kingdom (UK) - Great Britain mainland; Vatican City State.',35.95,71.21,-9.56,31.59,0);
INSERT INTO "extent" VALUES('EPSG','4610','Argentina - Buenos Aires city','Argentina - autonomous city of Buenos Aires.',-34.705314975913,-34.506992229796,-58.531465195974,-58.29240989685,0);
+INSERT INTO "extent" VALUES('EPSG','4611','Malaysia - East Malaysia - Sarawak onshore','Malaysia - East Malaysia - Sarawak onshore.',0.85,5.03,109.54,115.69,0);
+INSERT INTO "extent" VALUES('EPSG','4612','Canada - Newfoundland','Canada - Newfoundland - onshore.',46.567800287196,51.674372389257,-59.477936442937,-52.543563434093,0);
+INSERT INTO "extent" VALUES('EPSG','4614','Argentina - Comodoro Rivadavia - west of 67.5°W','Argentina - Comodoro Rivadavia area west of 67°30''W.',-46.699998855591,-45.199998855591,-69.5,-67.5,0);
diff --git a/data/sql/geodetic_crs.sql b/data/sql/geodetic_crs.sql
index 545e8c91..e62b4990 100644
--- a/data/sql/geodetic_crs.sql
+++ b/data/sql/geodetic_crs.sql
@@ -807,7 +807,7 @@ INSERT INTO "usage" VALUES('EPSG','3498','geodetic_crs','EPSG','4668','EPSG','12
INSERT INTO "geodetic_crs" VALUES('EPSG','4669','LKS94',NULL,'geographic 2D','EPSG','6422','EPSG','6126',NULL,0);
INSERT INTO "usage" VALUES('EPSG','3499','geodetic_crs','EPSG','4669','EPSG','1145','EPSG','1183');
INSERT INTO "geodetic_crs" VALUES('EPSG','4670','IGM95',NULL,'geographic 2D','EPSG','6422','EPSG','6670',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','3500','geodetic_crs','EPSG','4670','EPSG','3343','EPSG','1183');
+INSERT INTO "usage" VALUES('EPSG','14404','geodetic_crs','EPSG','4670','EPSG','3343','EPSG','1183');
INSERT INTO "geodetic_crs" VALUES('EPSG','4671','Voirol 1879',NULL,'geographic 2D','EPSG','6422','EPSG','6671',NULL,0);
INSERT INTO "usage" VALUES('EPSG','3501','geodetic_crs','EPSG','4671','EPSG','1365','EPSG','1027');
INSERT INTO "geodetic_crs" VALUES('EPSG','4672','Chatham Islands 1971',NULL,'geographic 2D','EPSG','6422','EPSG','6672',NULL,0);
@@ -1241,9 +1241,9 @@ INSERT INTO "usage" VALUES('EPSG','3780','geodetic_crs','EPSG','4980','EPSG','12
INSERT INTO "geodetic_crs" VALUES('EPSG','4981','Yemen NGN96',NULL,'geographic 3D','EPSG','6423','EPSG','6163',NULL,0);
INSERT INTO "usage" VALUES('EPSG','3781','geodetic_crs','EPSG','4981','EPSG','1257','EPSG','1027');
INSERT INTO "geodetic_crs" VALUES('EPSG','4982','IGM95',NULL,'geocentric','EPSG','6500','EPSG','6670',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','3782','geodetic_crs','EPSG','4982','EPSG','3343','EPSG','1027');
+INSERT INTO "usage" VALUES('EPSG','14402','geodetic_crs','EPSG','4982','EPSG','3343','EPSG','1027');
INSERT INTO "geodetic_crs" VALUES('EPSG','4983','IGM95',NULL,'geographic 3D','EPSG','6423','EPSG','6670',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','3783','geodetic_crs','EPSG','4983','EPSG','3343','EPSG','1027');
+INSERT INTO "usage" VALUES('EPSG','14403','geodetic_crs','EPSG','4983','EPSG','3343','EPSG','1027');
INSERT INTO "geodetic_crs" VALUES('EPSG','4984','WGS 72',NULL,'geocentric','EPSG','6500','EPSG','6322',NULL,0);
INSERT INTO "usage" VALUES('EPSG','3784','geodetic_crs','EPSG','4984','EPSG','1262','EPSG','1027');
INSERT INTO "geodetic_crs" VALUES('EPSG','4985','WGS 72',NULL,'geographic 3D','EPSG','6423','EPSG','6322',NULL,0);
@@ -1455,11 +1455,11 @@ INSERT INTO "usage" VALUES('EPSG','4886','geodetic_crs','EPSG','6667','EPSG','11
INSERT INTO "geodetic_crs" VALUES('EPSG','6668','JGD2011',NULL,'geographic 2D','EPSG','6422','EPSG','1128',NULL,0);
INSERT INTO "usage" VALUES('EPSG','4887','geodetic_crs','EPSG','6668','EPSG','1129','EPSG','1183');
INSERT INTO "geodetic_crs" VALUES('EPSG','6704','RDN2008',NULL,'geocentric','EPSG','6500','EPSG','1132',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','4919','geodetic_crs','EPSG','6704','EPSG','3343','EPSG','1027');
+INSERT INTO "usage" VALUES('EPSG','14411','geodetic_crs','EPSG','6704','EPSG','3343','EPSG','1027');
INSERT INTO "geodetic_crs" VALUES('EPSG','6705','RDN2008',NULL,'geographic 3D','EPSG','6423','EPSG','1132',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','4920','geodetic_crs','EPSG','6705','EPSG','3343','EPSG','1027');
+INSERT INTO "usage" VALUES('EPSG','14410','geodetic_crs','EPSG','6705','EPSG','3343','EPSG','1027');
INSERT INTO "geodetic_crs" VALUES('EPSG','6706','RDN2008',NULL,'geographic 2D','EPSG','6422','EPSG','1132',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','4921','geodetic_crs','EPSG','6706','EPSG','3343','EPSG','1183');
+INSERT INTO "usage" VALUES('EPSG','14412','geodetic_crs','EPSG','6706','EPSG','3343','EPSG','1183');
INSERT INTO "geodetic_crs" VALUES('EPSG','6781','NAD83(CORS96)',NULL,'geocentric','EPSG','6500','EPSG','1133',NULL,0);
INSERT INTO "usage" VALUES('EPSG','4937','geodetic_crs','EPSG','6781','EPSG','1511','EPSG','1027');
INSERT INTO "geodetic_crs" VALUES('EPSG','6782','NAD83(CORS96)',NULL,'geographic 3D','EPSG','6423','EPSG','1133',NULL,0);
diff --git a/data/sql/geodetic_datum.sql b/data/sql/geodetic_datum.sql
index 47b4c498..b249221e 100644
--- a/data/sql/geodetic_datum.sql
+++ b/data/sql/geodetic_datum.sql
@@ -934,8 +934,8 @@ INSERT INTO "geodetic_datum" VALUES('EPSG','6667','Iraq-Kuwait Boundary Datum 19
INSERT INTO "usage" VALUES('EPSG','13727','geodetic_datum','EPSG','6667','EPSG','2876','EPSG','1053');
INSERT INTO "geodetic_datum" VALUES('EPSG','6668','European Datum 1979',NULL,'EPSG','7022','EPSG','8901','1979-01-01',NULL,NULL,0);
INSERT INTO "usage" VALUES('EPSG','13728','geodetic_datum','EPSG','6668','EPSG','1297','EPSG','1027');
-INSERT INTO "geodetic_datum" VALUES('EPSG','6670','Istituto Geografico Militaire 1995',NULL,'EPSG','7030','EPSG','8901','1995-01-01',NULL,NULL,0);
-INSERT INTO "usage" VALUES('EPSG','13729','geodetic_datum','EPSG','6670','EPSG','3343','EPSG','1027');
+INSERT INTO "geodetic_datum" VALUES('EPSG','6670','Istituto Geografico Militaire 1995',NULL,'EPSG','7019','EPSG','8901','1995-01-01',NULL,NULL,0);
+INSERT INTO "usage" VALUES('EPSG','14401','geodetic_datum','EPSG','6670','EPSG','3343','EPSG','1027');
INSERT INTO "geodetic_datum" VALUES('EPSG','6671','Voirol 1879',NULL,'EPSG','7011','EPSG','8901','1879-01-01',NULL,NULL,0);
INSERT INTO "usage" VALUES('EPSG','13730','geodetic_datum','EPSG','6671','EPSG','1365','EPSG','1153');
INSERT INTO "geodetic_datum" VALUES('EPSG','6672','Chatham Islands Datum 1971',NULL,'EPSG','7022','EPSG','8901','1971-01-01',NULL,NULL,0);
diff --git a/data/sql/grid_alternatives.sql b/data/sql/grid_alternatives.sql
index 7e8e8032..a7198378 100644
--- a/data/sql/grid_alternatives.sql
+++ b/data/sql/grid_alternatives.sql
@@ -21,6 +21,7 @@ VALUES
('AUSGeoid09_GDA94_V1.01_DOV_windows.gsb','au_ga_AUSGeoid09_V1.01.tif','AUSGeoid09_V1.01.gtx','GTiff','geoid_like',0,NULL,'https://cdn.proj.org/au_ga_AUSGeoid09_V1.01.tif',1,1,NULL),
-- source file contains undulation in first band, and deflection in 2nd and 3d band
('AUSGeoid2020_20180201.gsb','au_ga_AUSGeoid2020_20180201.tif','AUSGeoid2020_20180201.gtx','GTiff','geoid_like',0,NULL,'https://cdn.proj.org/au_ga_AUSGeoid2020_20180201.tif',1,1,NULL),
+('AGQG_20191107.gsb','au_ga_AGQG_20191107.tif',NULL,'GTiff','geoid_like',0,NULL,'https://cdn.proj.org/au_ga_AGQG_20191107.tif',1,1,NULL),
-- au_icsm - Australian Intergovernmental Committee on Surveying and Mapping
('A66 National (13.09.01).gsb','au_icsm_A66_National_13_09_01.tif','A66_National_13_09_01.gsb','GTiff','hgridshift',0,NULL,'https://cdn.proj.org/au_icsm_A66_National_13_09_01.tif',1,1,NULL),
diff --git a/data/sql/grid_transformation.sql b/data/sql/grid_transformation.sql
index 5bd59179..4c176837 100644
--- a/data/sql/grid_transformation.sql
+++ b/data/sql/grid_transformation.sql
@@ -323,7 +323,7 @@ INSERT INTO "usage" VALUES('EPSG','9347','grid_transformation','EPSG','5334','EP
INSERT INTO "grid_transformation" VALUES('EPSG','5335','ETRS89 to Malin Head height (1)','May be used for transformations from WGS 84 to Malin Head. Replaced by ETRS89 to Malin Head height (2) (code 7959).','EPSG','1045','Geographic3D to GravityRelatedHeight (OSGM02-Ire)','EPSG','4937','EPSG','5731',0.04,'EPSG','8666','Geoid (height correction) model file','OSGM02_RoI.txt',NULL,NULL,NULL,NULL,NULL,NULL,'OS-Ire',0);
INSERT INTO "usage" VALUES('EPSG','9348','grid_transformation','EPSG','5335','EPSG','1305','EPSG','1133');
INSERT INTO "grid_transformation" VALUES('EPSG','5338','OSGB 1936 to ETRS89 (1)','Approximate alternative to official OSTN02 method (tfm code 7952). Accuracy at 2000 test points compared to OSTN02 (tfm code 1039): latitude 0.5mm av, 17mm max; longitude 0.8mm av, 23mm max. May be taken as approximate CT to WGS 84 - see code 5339.','EPSG','9615','NTv2','EPSG','4277','EPSG','4258',0.03,'EPSG','8656','Latitude and longitude difference file','OSTN02_NTv2.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'OSGB-UK Gbr02 NT',0);
-INSERT INTO "usage" VALUES('EPSG','9349','grid_transformation','EPSG','5338','EPSG','1264','EPSG','1033');
+INSERT INTO "usage" VALUES('EPSG','9349','grid_transformation','EPSG','5338','EPSG','1264','EPSG','1273');
INSERT INTO "grid_transformation" VALUES('EPSG','5339','OSGB 1936 to WGS 84 (7)','Parameter values taken from OSGB 1936 to ETRS89 (1) (tfm code 5338) assuming that ETRS89 is coincident with WGS 84 within the accuracy of the tfm. Within accuracy of the tfm equivalent to OSGB 1936 / British National Grid to WGS 84 (2) (tfm code 15956).','EPSG','9615','NTv2','EPSG','4277','EPSG','4326',1.0,'EPSG','8656','Latitude and longitude difference file','OSTN02_NTv2.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'OGP-UK Gbr02 NT',0);
INSERT INTO "usage" VALUES('EPSG','9350','grid_transformation','EPSG','5339','EPSG','1264','EPSG','1041');
INSERT INTO "grid_transformation" VALUES('EPSG','5409','NGVD29 height to NAVD88 height (1)','Interpolation within the gridded data file may be made using any of the horizontal CRSs NAD27, NAD83 or NAD83(HARN).','EPSG','9658','Vertical Offset by Grid Interpolation (VERTCON)','EPSG','5702','EPSG','5703',0.02,'EPSG','8732','Vertical offset file','vertconw.94',NULL,NULL,NULL,NULL,NULL,NULL,'NGS-US Conus W',1);
@@ -425,7 +425,7 @@ INSERT INTO "usage" VALUES('EPSG','10205','grid_transformation','EPSG','7673','E
INSERT INTO "grid_transformation" VALUES('EPSG','7674','CH1903 to ETRS89 (2)','Replaces tfm code 1646. Equivalent to concatenation of transformations 15486 and 1647 to within 2cm. Also used as transformation between CH1903 and CHTRF95 (see code 7673). May be used as approximate tfm CH1903 to WGS 84 - see code 7788.','EPSG','9615','NTv2','EPSG','4149','EPSG','4258',0.25,'EPSG','8656','Latitude and longitude difference file','CHENyx06_ETRS.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'BfL-Che NTv2',0);
INSERT INTO "usage" VALUES('EPSG','10206','grid_transformation','EPSG','7674','EPSG','1286','EPSG','1083');
INSERT INTO "grid_transformation" VALUES('EPSG','7709','OSGB 1936 to ETRS89 (2)','Approximate alternative to official OSTN15 method (tfm code 7953). May be taken as approximate transformation OSGB 1936 to WGS 84 - see code 7710. Replaces OSGB 1936 to ETRS89 (1) (tfm code 5338).','EPSG','9615','NTv2','EPSG','4277','EPSG','4258',0.03,'EPSG','8656','Latitude and longitude difference file','OSTN15_NTv2_OSGBtoETRS.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'OSGB-UK Gbr15 NT',0);
-INSERT INTO "usage" VALUES('EPSG','10222','grid_transformation','EPSG','7709','EPSG','4390','EPSG','1133');
+INSERT INTO "usage" VALUES('EPSG','10222','grid_transformation','EPSG','7709','EPSG','4390','EPSG','1273');
INSERT INTO "grid_transformation" VALUES('EPSG','7710','OSGB 1936 to WGS 84 (9)','Parameter values taken from OSGB 1936 to ETRS89 (3) (tfm code 7709) assuming that ETRS89 is coincident with WGS 84 within the accuracy of the tfm. Replaces OSGB 1936 to WGS 84 (7) (tfm code 5339).','EPSG','9615','NTv2','EPSG','4277','EPSG','4326',1.0,'EPSG','8656','Latitude and longitude difference file','OSTN15_NTv2_OSGBtoETRS.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'OGP-UK Gbr15 NT',0);
INSERT INTO "usage" VALUES('EPSG','10223','grid_transformation','EPSG','7710','EPSG','4390','EPSG','1252');
INSERT INTO "grid_transformation" VALUES('EPSG','7711','ETRS89 to ODN height (2)','OSGM15 supersedes OSGM02 geoid model. Replaces ETRS89 to Newlyn height (1) (tfm code 10021).','EPSG','9663','Geographic3D to GravityRelatedHeight (OSGM-GB)','EPSG','4937','EPSG','5701',0.008,'EPSG','8666','Geoid (height correction) model file','OSTN15_OSGM15_GB.txt',NULL,NULL,NULL,NULL,NULL,NULL,'OS-UK Gbr 2015',0);
@@ -670,8 +670,8 @@ INSERT INTO "grid_transformation" VALUES('EPSG','9239','NAD27 to NAD83(CSRS)v2 (
INSERT INTO "usage" VALUES('EPSG','13884','grid_transformation','EPSG','9239','EPSG','1368','EPSG','1151');
INSERT INTO "grid_transformation" VALUES('EPSG','9240','NAD27(CGQ77) to NAD83(CSRS)v2 (1)','Uses NT method which expects longitudes positive west; EPSG GeogCRSs NAD27(CGQ77) (code 4609) and NAD83(CSRS) (code 4617) have longitudes positive east. Can be taken as an approximate transformation NAD27(CGQ77) to WGS 84 - see code 1691.','EPSG','9615','NTv2','EPSG','4609','EPSG','8237',1.5,'EPSG','8656','Latitude and longitude difference file','CGQ77-98.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'SGQ-Can QC',0);
INSERT INTO "usage" VALUES('EPSG','13885','grid_transformation','EPSG','9240','EPSG','1368','EPSG','1151');
-INSERT INTO "grid_transformation" VALUES('EPSG','9241','NAD83 to NAD83(CSRS)v2 (1)','Uses NT method which expects longitudes positive west; EPSG GeogCRSs NAD83 (code 4269) and NAD83(CSRS) (code 4617) have longitudes positive east. Can be taken as an approximate transformation NAD83 to WGS 84 - see code 1696.','EPSG','9615','NTv2','EPSG','4269','EPSG','8237',1.5,'EPSG','8656','Latitude and longitude difference file','NAD83-98.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'SGQ-Can QC',0);
-INSERT INTO "usage" VALUES('EPSG','13886','grid_transformation','EPSG','9241','EPSG','1368','EPSG','1151');
+INSERT INTO "grid_transformation" VALUES('EPSG','9241','NAD83 to NAD83(CSRS)v2 (1)','Uses NT method which expects longitudes positive west; EPSG GeogCRSs NAD83 (code 4269) and NAD83(CSRS)v2 (code 8237) have longitudes positive east. Can be taken as an approximate transformation NAD83 to WGS 84 - see code 1696.','EPSG','9615','NTv2','EPSG','4269','EPSG','8237',1.5,'EPSG','8656','Latitude and longitude difference file','NAD83-98.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'SGQ-Can QC',0);
+INSERT INTO "usage" VALUES('EPSG','14556','grid_transformation','EPSG','9241','EPSG','1368','EPSG','1151');
INSERT INTO "grid_transformation" VALUES('EPSG','9242','NAD27 to NAD83(CSRS)v3 (2)','Can be taken as an approximate transformation NAD27 to WGS 84 - see code 1703.','EPSG','9615','NTv2','EPSG','4267','EPSG','8240',1.5,'EPSG','8656','Latitude and longitude difference file','SK27-98.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'SK PMC-Can SK',0);
INSERT INTO "usage" VALUES('EPSG','13887','grid_transformation','EPSG','9242','EPSG','2375','EPSG','1151');
INSERT INTO "grid_transformation" VALUES('EPSG','9243','NAD83 to NAD83(CSRS)v3 (2)','Can be taken as an approximate transformation NAD83 to WGS 84 - see code 1697.','EPSG','9615','NTv2','EPSG','4269','EPSG','8240',1.5,'EPSG','8656','Latitude and longitude difference file','SK83-98.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'SK PMC-Can SK',0);
@@ -786,35 +786,35 @@ INSERT INTO "grid_transformation" VALUES('EPSG','9420','REGCAN95 to La Palma hei
INSERT INTO "usage" VALUES('EPSG','14076','grid_transformation','EPSG','9420','EPSG','4596','EPSG','1133');
INSERT INTO "grid_transformation" VALUES('EPSG','9421','REGCAN95 to El Hierro height (1)','','EPSG','1025','Geographic3D to GravityRelatedHeight (EGM2008)','EPSG','4080','EPSG','9401',0.05,'EPSG','8666','Geoid (height correction) model file','EGM08_REDNAP_Canarias.txt',NULL,NULL,NULL,NULL,NULL,NULL,'IGN-Esp 2008',0);
INSERT INTO "usage" VALUES('EPSG','14077','grid_transformation','EPSG','9421','EPSG','4597','EPSG','1133');
-INSERT INTO "grid_transformation" VALUES('EPSG','9431','GHA height to EVRF2019 height (1)','Determined at 147 points, SD 0.060m. Offset: mean -0.306m, minimum -0.442m, maximum -0.219m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5778','EPSG','9389',0.12,'EPSG','8732','Vertical offset file','at_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Aut 2019z',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9431','GHA height to EVRF2019 height (1)','Determined at 147 points, SD 0.060m. Offset: mean -0.306m, minimum -0.442m, maximum -0.219m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5778','EPSG','9389',0.12,'EPSG','8732','Vertical offset file','at_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Aut 2019z',1);
INSERT INTO "usage" VALUES('EPSG','14089','grid_transformation','EPSG','9431','EPSG','1037','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9432','GHA height to EVRF2019 mean-tide height (1)','Determined at 147 points, SD 0.058m. Offset: mean -0.330m, minimum -0.463m, maximum -0.245m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5778','EPSG','9390',0.116,'EPSG','8732','Vertical offset file','at_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Aut 2019m',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9432','GHA height to EVRF2019 mean-tide height (1)','Determined at 147 points, SD 0.058m. Offset: mean -0.330m, minimum -0.463m, maximum -0.245m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5778','EPSG','9390',0.116,'EPSG','8732','Vertical offset file','at_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Aut 2019m',1);
INSERT INTO "usage" VALUES('EPSG','14090','grid_transformation','EPSG','9432','EPSG','1037','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9433','Ostend height to EVRF2019 mean-tide height (1)','Determined at 39 points, SD 0.016m. Offset: mean -2.323m, minimum -2.372m, maximum -2.290m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5710','EPSG','9390',0.032,'EPSG','8732','Vertical offset file','be_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Bel 2019m',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9433','Ostend height to EVRF2019 mean-tide height (1)','Determined at 39 points, SD 0.016m. Offset: mean -2.323m, minimum -2.372m, maximum -2.290m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5710','EPSG','9390',0.032,'EPSG','8732','Vertical offset file','be_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Bel 2019m',1);
INSERT INTO "usage" VALUES('EPSG','14091','grid_transformation','EPSG','9433','EPSG','1347','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9434','Ostend height to EVRF2019 height (1)','Determined at 39 points, SD 0.017m. Offset: mean -2.315m, minimum -2.364m, maximum -2.279m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5710','EPSG','9389',0.034,'EPSG','8732','Vertical offset file','be_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Bel 2019z',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9434','Ostend height to EVRF2019 height (1)','Determined at 39 points, SD 0.017m. Offset: mean -2.315m, minimum -2.364m, maximum -2.279m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5710','EPSG','9389',0.034,'EPSG','8732','Vertical offset file','be_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Bel 2019z',1);
INSERT INTO "usage" VALUES('EPSG','14092','grid_transformation','EPSG','9434','EPSG','1347','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9435','DHHN2016 height to EVRF2019 height (1)','Determined at 802 points, SD 0.010m. Offset: mean 0.013m, minimum -0.008m, maximum 0.039m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','7837','EPSG','9389',0.02,'EPSG','8732','Vertical offset file','de_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Deu 2019z',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9435','DHHN2016 height to EVRF2019 height (1)','Determined at 802 points, SD 0.010m. Offset: mean 0.013m, minimum -0.008m, maximum 0.039m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','7837','EPSG','9389',0.02,'EPSG','8732','Vertical offset file','de_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Deu 2019z',1);
INSERT INTO "usage" VALUES('EPSG','14093','grid_transformation','EPSG','9435','EPSG','3339','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9436','DHHN2016 height to EVRF2019 mean-tide height (1)','Determined at 802 points, SD 0.003m. Offset: mean 0.007m, minimum -0.004m, maximum 0.018m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','7837','EPSG','9390',0.006,'EPSG','8732','Vertical offset file','de_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Deu 2019m',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9436','DHHN2016 height to EVRF2019 mean-tide height (1)','Determined at 802 points, SD 0.003m. Offset: mean 0.007m, minimum -0.004m, maximum 0.018m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','7837','EPSG','9390',0.006,'EPSG','8732','Vertical offset file','de_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Deu 2019m',1);
INSERT INTO "usage" VALUES('EPSG','14094','grid_transformation','EPSG','9436','EPSG','3339','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9437','Trieste height to EVRF2019 height (1)','Determined at 46 points, SD 0.016m. Offset: mean -0.548m, minimum -0.595m, maximum -0.500m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5195','EPSG','9389',0.032,'EPSG','8732','Vertical offset file','mk_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Mkd 2019z',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9437','Trieste height to EVRF2019 height (1)','Determined at 46 points, SD 0.016m. Offset: mean -0.548m, minimum -0.595m, maximum -0.500m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5195','EPSG','9389',0.032,'EPSG','8732','Vertical offset file','mk_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Mkd 2019z',1);
INSERT INTO "usage" VALUES('EPSG','14095','grid_transformation','EPSG','9437','EPSG','1148','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9438','Trieste height to EVRF2019 mean-tide height (1)','Determined at 46 points, SD 0.015m. Offset: mean -0.604m, minimum -0.650m, maximum -0.558m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5195','EPSG','9390',0.03,'EPSG','8732','Vertical offset file','mk_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Mkd 2019m',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9438','Trieste height to EVRF2019 mean-tide height (1)','Determined at 46 points, SD 0.015m. Offset: mean -0.604m, minimum -0.650m, maximum -0.558m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5195','EPSG','9390',0.03,'EPSG','8732','Vertical offset file','mk_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Mkd 2019m',1);
INSERT INTO "usage" VALUES('EPSG','14096','grid_transformation','EPSG','9438','EPSG','1148','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9439','Cascais height to EVRF2019 height (1)','Determined at 18 points, SD 0.010m. Offset: mean -0.277m, minimum -0.323m, maximum -0.264m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5780','EPSG','9389',0.02,'EPSG','8732','Vertical offset file','pt_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Prt 2019z',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9439','Cascais height to EVRF2019 height (1)','Determined at 18 points, SD 0.010m. Offset: mean -0.277m, minimum -0.323m, maximum -0.264m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5780','EPSG','9389',0.02,'EPSG','8732','Vertical offset file','pt_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Prt 2019z',1);
INSERT INTO "usage" VALUES('EPSG','14097','grid_transformation','EPSG','9439','EPSG','1294','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9440','Cascais height to EVRF2019 mean-tide height (1)','Determined at 18 points, SD 0.007m. Offset: mean -0.343m, minimum -0.383m, maximum -0.332m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5780','EPSG','9390',0.014,'EPSG','8732','Vertical offset file','pt_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Prt 2019m',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9440','Cascais height to EVRF2019 mean-tide height (1)','Determined at 18 points, SD 0.007m. Offset: mean -0.343m, minimum -0.383m, maximum -0.332m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5780','EPSG','9390',0.014,'EPSG','8732','Vertical offset file','pt_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Prt 2019m',1);
INSERT INTO "usage" VALUES('EPSG','14098','grid_transformation','EPSG','9440','EPSG','1294','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9441','SVS2010 height to EVRF2019 height (1)','Determined at 66 points, SD 0.003m. Offset: mean -0.258m, minimum -0.264m, maximum -0.250m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','8690','EPSG','9389',0.006,'EPSG','8732','Vertical offset file','si_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Svn 2019z',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9441','SVS2010 height to EVRF2019 height (1)','Determined at 66 points, SD 0.003m. Offset: mean -0.258m, minimum -0.264m, maximum -0.250m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','8690','EPSG','9389',0.006,'EPSG','8732','Vertical offset file','si_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Svn 2019z',1);
INSERT INTO "usage" VALUES('EPSG','14099','grid_transformation','EPSG','9441','EPSG','3307','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9442','SVS2010 height to EVRF2019 mean-tide height (1)','Determined at 66 points, SD 0.004m. Offset: mean -0.290m, minimum -0.295m, maximum -0.280m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','8690','EPSG','9390',0.008,'EPSG','8732','Vertical offset file','si_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Svn 2019m',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9442','SVS2010 height to EVRF2019 mean-tide height (1)','Determined at 66 points, SD 0.004m. Offset: mean -0.290m, minimum -0.295m, maximum -0.280m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','8690','EPSG','9390',0.008,'EPSG','8732','Vertical offset file','si_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Svn 2019m',1);
INSERT INTO "usage" VALUES('EPSG','14100','grid_transformation','EPSG','9442','EPSG','3307','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9443','LHN95 height to EVRF2019 height (1)','Determined at 553 points, SD 0.075m. Offset: mean -0.204m, minimum -0.330m, maximum -0.130m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5729','EPSG','9389',0.15,'EPSG','8732','Vertical offset file','ch_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Che 2019z',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9443','LHN95 height to EVRF2019 height (1)','Determined at 553 points, SD 0.075m. Offset: mean -0.204m, minimum -0.330m, maximum -0.130m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5729','EPSG','9389',0.15,'EPSG','8732','Vertical offset file','ch_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Che 2019z',1);
INSERT INTO "usage" VALUES('EPSG','14101','grid_transformation','EPSG','9443','EPSG','1286','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9444','LHN95 height to EVRF2019 mean-tide height (1)','Determined at 553 points, SD 0.073m. Offset: mean -0.233m, minimum -0.353m, maximum -0.044m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5729','EPSG','9390',0.146,'EPSG','8732','Vertical offset file','ch_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Che 2019m',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9444','LHN95 height to EVRF2019 mean-tide height (1)','Determined at 553 points, SD 0.073m. Offset: mean -0.233m, minimum -0.353m, maximum -0.044m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5729','EPSG','9390',0.146,'EPSG','8732','Vertical offset file','ch_2019_m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Che 2019m',1);
INSERT INTO "usage" VALUES('EPSG','14102','grid_transformation','EPSG','9444','EPSG','1286','EPSG','1059');
-INSERT INTO "grid_transformation" VALUES('EPSG','9445','ODN height to EVRF2019 height (1)','Determined at 35 points, SD 0.011m. Offset: mean -0.181m, minimum -0.202m, maximum -0.161m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5701','EPSG','9389',0.022,'EPSG','8732','Vertical offset file','gb_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Gbr 2019z',0);
+INSERT INTO "grid_transformation" VALUES('EPSG','9445','ODN height to EVRF2019 height (1)','Determined at 35 points, SD 0.011m. Offset: mean -0.181m, minimum -0.202m, maximum -0.161m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5701','EPSG','9389',0.022,'EPSG','8732','Vertical offset file','gb_2019_z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Gbr 2019z',1);
INSERT INTO "usage" VALUES('EPSG','14103','grid_transformation','EPSG','9445','EPSG','2792','EPSG','1059');
INSERT INTO "grid_transformation" VALUES('EPSG','9454','ETRS89 to GBK19-IRF (1)','In conjunction with the GBK19-TM map projection (code 9455) applied to GBK19-IRF (code 9453), emulates the GBK19 Snake projection. Applied to ETRS89 (as realized through the OSNet v2009 CORS) defines GBK19-IRF hence is errorless.','EPSG','9615','NTv2','EPSG','4258','EPSG','9453',0.0,'EPSG','8656','Latitude and longitude difference file','TN15-ETRS89-to-GBK19-IRF.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'NR-Gbr GBK19 OSNet2009',0);
INSERT INTO "usage" VALUES('EPSG','14129','grid_transformation','EPSG','9454','EPSG','4607','EPSG','1141');
@@ -834,6 +834,78 @@ INSERT INTO "grid_transformation" VALUES('EPSG','9485','ETRS89 to NN2000 height
INSERT INTO "usage" VALUES('EPSG','14216','grid_transformation','EPSG','9485','EPSG','1352','EPSG','1133');
INSERT INTO "grid_transformation" VALUES('EPSG','9496','MGI 1901 to SRB-ETRS89 (9)','','EPSG','9615','NTv2','EPSG','3906','EPSG','8685',0.03,'EPSG','8656','Latitude and longitude difference file','MGI1901_TO_SRBETRS89_NTv2.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'RGZ-Srb 0.1m 2020',0);
INSERT INTO "usage" VALUES('EPSG','14226','grid_transformation','EPSG','9496','EPSG','4543','EPSG','1185');
+INSERT INTO "grid_transformation" VALUES('EPSG','9550','NAD83 to NAD83(CSRS)v6 (10)','File NLCSRSV4A.GSB corrects error in file header record previously released as NLCSRSV4.GSB. No change to gridded data.','EPSG','9615','NTv2','EPSG','4269','EPSG','8252',0.1,'EPSG','8656','Latitude and longitude difference file','NLCSRSV4A.GSB ',NULL,NULL,NULL,NULL,NULL,NULL,'CGS-Can Nfl island',0);
+INSERT INTO "usage" VALUES('EPSG','14831','grid_transformation','EPSG','9550','EPSG','4612','EPSG','1026');
+INSERT INTO "grid_transformation" VALUES('EPSG','9553','Cascais height to EVRF2019 height (2)','Determined at 18 points, SD 0.014m. Offset: mean -0.275m, minimum -0.322m, maximum -0.262m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5780','EPSG','9389',0.028,'EPSG','8732','Vertical offset file','pt_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Prt 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14842','grid_transformation','EPSG','9553','EPSG','1294','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9554','Cascais height to EVRF2019 mean-tide height (2)','Determined at 18 points, SD 0.012m. Offset: mean -0.340m, minimum -0.383m, maximum -0.324m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5780','EPSG','9390',0.024,'EPSG','8732','Vertical offset file','pt_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Prt 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14671','grid_transformation','EPSG','9554','EPSG','1294','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9555','DHHN2016 height to EVRF2019 height (2)','Determined at 802 points, SD 0.010m. Offset: mean 0.016m, minimum -0.004m, maximum 0.052m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','7837','EPSG','9389',0.02,'EPSG','8732','Vertical offset file','de_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Deu 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14672','grid_transformation','EPSG','9555','EPSG','3339','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9556','DHHN2016 height to EVRF2019 mean-tide height (2)','Determined at 802 points, SD 0.004m. Offset: mean 0.009m, minimum -0.011m, maximum 0.028m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','7837','EPSG','9390',0.008,'EPSG','8732','Vertical offset file','de_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Deu 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14843','grid_transformation','EPSG','9556','EPSG','3339','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9557','GHA height to EVRF2019 height (2)','Determined at 150 points, SD 0.068m. Offset: mean -0.309m, minimum -0.450m, maximum -0.210m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5778','EPSG','9389',0.136,'EPSG','8732','Vertical offset file','at_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Aut 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14673','grid_transformation','EPSG','9557','EPSG','1037','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9558','GHA height to EVRF2019 mean-tide height (2)','Determined at 150 points, SD 0.065m. Offset: mean -0.333m, minimum -0.471m, maximum -0.236m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5778','EPSG','9390',0.13,'EPSG','8732','Vertical offset file','at_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Aut 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14674','grid_transformation','EPSG','9558','EPSG','1037','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9559','LHN95 height to EVRF2019 height (2)','Determined at 553 points, SD 0.073m. Offset: mean -0.216m, minimum -0.478m, maximum -0.021m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5729','EPSG','9389',0.146,'EPSG','8732','Vertical offset file','ch_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Che 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14675','grid_transformation','EPSG','9559','EPSG','1286','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9560','LHN95 height to EVRF2019 mean-tide height (2)','Determined at 553 points, SD 0.071m. Offset: mean -0.244m, minimum -0.506m, maximum -0.012m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5729','EPSG','9390',0.142,'EPSG','8732','Vertical offset file','ch_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Che 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14676','grid_transformation','EPSG','9560','EPSG','1286','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9561','ODN height to EVRF2019 height (2)','Determined at 35 points, SD 0.012m. Offset: mean -0.178m, minimum -0.199m, maximum -0.159m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5701','EPSG','9389',0.024,'EPSG','8732','Vertical offset file','gb_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Gbr 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14677','grid_transformation','EPSG','9561','EPSG','2792','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9563','Ostend height to EVRF2019 height (2)','Determined at 39 points, SD 0.021m. Offset: mean -2.312m, minimum -2.362m, maximum -2.275m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5710','EPSG','9389',0.042,'EPSG','8732','Vertical offset file','be_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Bel 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14678','grid_transformation','EPSG','9563','EPSG','1347','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9564','Ostend height to EVRF2019 mean-tide height (2)','Determined at 39 points, SD 0.020m. Offset: mean -2.320m, minimum -2.370m, maximum -2.285m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5710','EPSG','9390',0.04,'EPSG','8732','Vertical offset file','be_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Bel 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14679','grid_transformation','EPSG','9564','EPSG','1347','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9565','SVS2010 height to EVRF2019 height (2)','Determined at 65 points, SD 0.003m. Offset: mean -0.259m, minimum -0.265m, maximum -0.251m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','8690','EPSG','9389',0.006,'EPSG','8732','Vertical offset file','si_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Svn 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14680','grid_transformation','EPSG','9565','EPSG','3307','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9566','SVS2010 height to EVRF2019 mean-tide height (2)','Determined at 65 points, SD 0.004m. Offset: mean -0.290m, minimum -0.295m, maximum -0.280m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','8690','EPSG','9390',0.008,'EPSG','8732','Vertical offset file','si_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Svn 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14681','grid_transformation','EPSG','9566','EPSG','3307','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9567','Trieste height to EVRF2019 height (2)','Determined at 46 points, SD 0.021m. Offset: mean -0.551m, minimum -0.606m, maximum -0.490m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5195','EPSG','9389',0.042,'EPSG','8732','Vertical offset file','mk_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Mkd 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14668','grid_transformation','EPSG','9567','EPSG','1148','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9568','Trieste height to EVRF2019 mean-tide height (2)','Determined at 46 points, SD 0.022m. Offset: mean -0.606m, minimum -0.662m, maximum -0.548m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5195','EPSG','9390',0.044,'EPSG','8732','Vertical offset file','mk_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Mkd 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14667','grid_transformation','EPSG','9568','EPSG','1148','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9569','Trieste height to EVRF2019 height (3)','Determined at 10 points, SD 0.006m. Offset: mean -0.336m, minimum -0.346m, maximum -0.326m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5195','EPSG','9389',0.012,'EPSG','8732','Vertical offset file','ba_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Bih 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14666','grid_transformation','EPSG','9569','EPSG','1050','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9570','Trieste height to EVRF2019 mean-tide height (3)','Determined at 10 points, SD 0.005m. Offset: mean -0.377m, minimum -0.386m, maximum -0.368m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5195','EPSG','9390',0.01,'EPSG','8732','Vertical offset file','ba_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Bih 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14665','grid_transformation','EPSG','9570','EPSG','1050','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9571','Baltic 1982 height to EVRF2019 height (1)','Determined at 58 points, SD 0.024m. Offset: mean 0.228m, minimum 0.167m, maximum 0.277m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5786','EPSG','9389',0.048,'EPSG','8732','Vertical offset file','bgalt_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Bgr 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14628','grid_transformation','EPSG','9571','EPSG','3224','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9572','Baltic 1982 height to EVRF2019 mean-tide height (1)','Determined at 58 points, SD 0.021m. Offset: mean 0.180m, minimum 0.123m, maximum 0.228m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5786','EPSG','9390',0.042,'EPSG','8732','Vertical offset file','bgalt_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Bgr 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14781','grid_transformation','EPSG','9572','EPSG','3224','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9573','N2000 height to EVRF2019 height (1)','Determined at 191 points, SD 0.002m. Offset: mean 0.002m, minimum -0.005m, maximum 0.007m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','3900','EPSG','9389',0.004,'EPSG','8732','Vertical offset file','fi_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Fin 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14664','grid_transformation','EPSG','9573','EPSG','3333','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9574','N2000 height to EVRF2019 mean-tide height (1)','Determined at 191 points, SD 0.012m. Offset: mean 0.054m, minimum 0.034m, maximum 0.079m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','3900','EPSG','9390',0.024,'EPSG','8732','Vertical offset file','fi_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Fin 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14663','grid_transformation','EPSG','9574','EPSG','3333','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9575','NGF-IGN69 height to EVRF2019 height (1)','Determined at 1228 points, SD 0.054m. Offset: mean -0.539m, minimum -0.651m, maximum -0.380m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5720','EPSG','9389',0.108,'EPSG','8732','Vertical offset file','fr_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Fra 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14662','grid_transformation','EPSG','9575','EPSG','1326','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9576','NGF-IGN69 height to EVRF2019 mean-tide height (1)','Determined at 1228 points, SD 0.043m. Offset: mean -0.561m, minimum -0.658m, maximum -0.430m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5720','EPSG','9390',0.086,'EPSG','8732','Vertical offset file','fr_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Fra 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14682','grid_transformation','EPSG','9576','EPSG','1326','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9577','EOMA height 1980 to EVRF2019 height (1)','Determined at 35 points, SD 0.003m. Offset: mean 0.163m, minimum 0.156m, maximum 0.171m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5787','EPSG','9389',0.006,'EPSG','8732','Vertical offset file','hu_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Hun 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14844','grid_transformation','EPSG','9577','EPSG','1119','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9578','EOMA 1980 height to EVRF2019 mean-tide height (1)','Determined at 35 points, SD 0.005m. Offset: mean 0.138m, minimum 0.127m, maximum 0.149m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5787','EPSG','9390',0.01,'EPSG','8732','Vertical offset file','hu_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Hun 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14686','grid_transformation','EPSG','9578','EPSG','1119','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9579','Latvia 2000 height to EVRF2019 height (1)','Determined at 134 points, SD 0.003m. Offset: mean 0.009m, minimum 0.000m, maximum 0.019m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','7700','EPSG','9389',0.006,'EPSG','8732','Vertical offset file','lv_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Lva 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14687','grid_transformation','EPSG','9579','EPSG','3268','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9580','Latvia 2000 height to EVRF2019 mean-tide height (1)','Determined at 134 points, SD 0.004m. Offset: mean 0.031m, minimum 0.025m, maximum 0.045m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','7700','EPSG','9390',0.008,'EPSG','8732','Vertical offset file','lv_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Lva 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14780','grid_transformation','EPSG','9580','EPSG','3268','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9581','NAP height to EVRF2019 height (1)','Determined at 1095 points, SD 0.008m. Offset: mean 0.021m, minimum 0.009m, maximum 0.055m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5709','EPSG','9389',0.016,'EPSG','8732','Vertical offset file','nl_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Nld 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14689','grid_transformation','EPSG','9581','EPSG','1275','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9582','NAP height to EVRF2019 mean-tide height (1)','Determined at 1095 points, SD 0.006m. Offset: mean 0.021m, minimum 0.008m, maximum 0.047m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5709','EPSG','9390',0.012,'EPSG','8732','Vertical offset file','nl_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Nld 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14779','grid_transformation','EPSG','9582','EPSG','1275','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9583','Constanta height to EVRF2019 height (1)','Determined at 96 points, SD 0.006m. Offset: mean 0.050m, minimum 0.029m, maximum 0.063m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5781','EPSG','9389',0.12,'EPSG','8732','Vertical offset file','ro_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Rou 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14693','grid_transformation','EPSG','9583','EPSG','3295','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9645','Constanta height to EVRF2019 mean-tide height (1)','Determined at 96 points, SD 0.010m. Offset: mean 0.015m, minimum -0.006m, maximum 0.040m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5781','EPSG','9390',0.02,'EPSG','8732','Vertical offset file','ro_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Rou 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14692','grid_transformation','EPSG','9645','EPSG','3295','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9646','Alicante height to EVRF2019 height (1)','Determined at 155 points, SD 0.041m. Offset: mean -0.427m, minimum -0.555m, maximum -0.355m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5782','EPSG','9389',0.082,'EPSG','8732','Vertical offset file','es_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Esp 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14696','grid_transformation','EPSG','9646','EPSG','2366','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9647','Alicante height to EVRF2019 mean-tide height (1)','Determined at 155 points, SD 0.039m. Offset: mean -0.488m, minimum -0.603m, maximum -0.426m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5782','EPSG','9390',0.078,'EPSG','8732','Vertical offset file','es_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Esp 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14854','grid_transformation','EPSG','9647','EPSG','2366','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9648','RH2000 height to EVRF2019 height (1)','Determined at 3356 points, SD 0.003m. Offset: mean -0.003m, minimum -0.014m, maximum 0.003m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5613','EPSG','9389',0.006,'EPSG','8732','Vertical offset file','se_2019z.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Swe 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14697','grid_transformation','EPSG','9648','EPSG','3313','EPSG','1059');
+INSERT INTO "grid_transformation" VALUES('EPSG','9649','RH2000 height to EVRF2019 mean-tide height (1)','Determined at 3356 points, SD 0.016m. Offset: mean 0.036m, minimum 0.003m, maximum 0.071m.','EPSG','1085','Vertical Offset by Grid Interpolation (asc)','EPSG','5613','EPSG','9390',0.032,'EPSG','8732','Vertical offset file','se_2019m.asc',NULL,NULL,NULL,NULL,'EPSG','4258','EuG-Swe 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14778','grid_transformation','EPSG','9649','EPSG','3313','EPSG','1059');
INSERT INTO "grid_transformation" VALUES('EPSG','10000','RGF93 to NGF-IGN69 height (1)','May be used for transformations from WGS 84 to NGF-IGN69 height. Accuracy at each 0.1 deg x 0.1 degree grid node is given within the geoid model file.','EPSG','9664','Geographic3D to GravityRelatedHeight (IGN1997)','EPSG','4965','EPSG','5720',0.5,'EPSG','8666','Geoid (height correction) model file','ggf97a.txt',NULL,NULL,NULL,NULL,NULL,NULL,'IGN Fra',0);
INSERT INTO "usage" VALUES('EPSG','11001','grid_transformation','EPSG','10000','EPSG','1326','EPSG','1133');
INSERT INTO "grid_transformation" VALUES('EPSG','10001','ETRS89 to NGF-IGN69 height (1)','Parameter values taken from RGF93 to NGF-IGN69 height (1) (code 10000) assuming that RGF93 is equivalent to ETRS89 within the accuracy of the transformation. Accuracy at each 0.1 deg x 0.1 degree grid node is given within the geoid model file.','EPSG','9664','Geographic3D to GravityRelatedHeight (IGN1997)','EPSG','4937','EPSG','5720',0.5,'EPSG','8666','Geoid (height correction) model file','ggf97a.txt',NULL,NULL,NULL,NULL,NULL,NULL,'IGN Fra',0);
diff --git a/data/sql/helmert_transformation.sql b/data/sql/helmert_transformation.sql
index ff7e9a42..efddf45f 100644
--- a/data/sql/helmert_transformation.sql
+++ b/data/sql/helmert_transformation.sql
@@ -79,9 +79,9 @@ INSERT INTO "usage" VALUES('EPSG','8017','helmert_transformation','EPSG','1096',
INSERT INTO "helmert_transformation" VALUES('EPSG','1097','Dealul Piscului 1970 to WGS 84 (2)','Parameter values taken from Pulkovo 1942 to WGS 84 (9) (code 1293) assuming that Pulkovo 1942 in Romania is equivalent to Dealul Piscului 1970.','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4317','EPSG','4326',7.0,28.0,-121.0,-77.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,'EPSG-Rom',1);
INSERT INTO "usage" VALUES('EPSG','8018','helmert_transformation','EPSG','1097','EPSG','1197','EPSG','1160');
INSERT INTO "helmert_transformation" VALUES('EPSG','1098','IGM95 to ETRS89 (1)','IGM95 is a realization of ETRS89. May be taken as approximate transformation IGM95 to WGS 84 - see code 1099.','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4670','EPSG','4258',0.0,0.0,0.0,0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'IGM-Ita',0);
-INSERT INTO "usage" VALUES('EPSG','8019','helmert_transformation','EPSG','1098','EPSG','3343','EPSG','1161');
+INSERT INTO "usage" VALUES('EPSG','14407','helmert_transformation','EPSG','1098','EPSG','3343','EPSG','1161');
INSERT INTO "helmert_transformation" VALUES('EPSG','1099','IGM95 to WGS 84 (1)','Parameter values taken from IGM95 to ETRS89 (1) (code 1098) assuming that ETRS89 is coincident with WGS 84 within the accuracy of the transformation.','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4670','EPSG','4326',1.0,0.0,0.0,0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'IGM-Ita',0);
-INSERT INTO "usage" VALUES('EPSG','8020','helmert_transformation','EPSG','1099','EPSG','3343','EPSG','1252');
+INSERT INTO "usage" VALUES('EPSG','14408','helmert_transformation','EPSG','1099','EPSG','3343','EPSG','1252');
INSERT INTO "helmert_transformation" VALUES('EPSG','1100','Adindan to WGS 84 (1)','Derived at 22 stations. Accuracy 5m in each axis.','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4201','EPSG','4326',9.0,-166.0,-15.0,204.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,'DMA-Eth Sud',0);
INSERT INTO "usage" VALUES('EPSG','8021','helmert_transformation','EPSG','1100','EPSG','1271','EPSG','1160');
INSERT INTO "helmert_transformation" VALUES('EPSG','1101','Adindan to WGS 84 (2)','Derived at 1 station connected to the Adindan (Blue Nile 1958) network through the 1968-69 12th parallel traverse. Accuracy 25m in each axis. Note: the Adindan (Blue Nile 1958) CRS is used in Ethiopia and Sudan, not Burkino Faso.','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4201','EPSG','4326',44.0,-118.0,-14.0,218.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,'DMA-Bfa',0);
diff --git a/data/sql/metadata.sql b/data/sql/metadata.sql
index adcee507..189c401d 100644
--- a/data/sql/metadata.sql
+++ b/data/sql/metadata.sql
@@ -9,5 +9,5 @@
INSERT INTO "metadata" VALUES('DATABASE.LAYOUT.VERSION.MAJOR', 1);
INSERT INTO "metadata" VALUES('DATABASE.LAYOUT.VERSION.MINOR', 0);
-INSERT INTO "metadata" VALUES('EPSG.VERSION', 'v10.007');
-INSERT INTO "metadata" VALUES('EPSG.DATE', '2020-11-18');
+INSERT INTO "metadata" VALUES('EPSG.VERSION', 'v10.008');
+INSERT INTO "metadata" VALUES('EPSG.DATE', '2020-12-16');
diff --git a/data/sql/nkg.sql b/data/sql/nkg.sql
new file mode 100644
index 00000000..93d82f97
--- /dev/null
+++ b/data/sql/nkg.sql
@@ -0,0 +1,2174 @@
+INSERT INTO "metadata" VALUES('NKG.SOURCE', 'https://github.com/NordicGeodesy/NordicTransformations');
+INSERT INTO "metadata" VALUES('NKG.VERSION', '1.0.0');
+INSERT INTO "metadata" VALUES('NKG.DATE', '2020-12-21');
+
+-- Append NKG to authority references
+UPDATE
+ authority_to_authority_preference
+SET
+ allowed_authorities = allowed_authorities || ',NKG'
+WHERE
+ source_auth_name = 'EPSG' AND target_auth_name = 'EPSG';
+
+INSERT INTO "authority_to_authority_preference"
+ (source_auth_name,target_auth_name, allowed_authorities)
+VALUES
+ ('NKG', 'EPSG', 'NKG,PROJ,EPSG');
+
+-- extent for NKG2008 transformations
+INSERT INTO "extent" VALUES(
+ 'NKG','EXTENT_2008', -- extend auth+code
+ 'Nordic and Baltic countries', -- name
+ 'Denmark; Estonia; Finland; Latvia; Lithuania; Norway; Sweden', -- description
+ 53.0, -- south latitude
+ 73.0, -- north latitude
+ 3.0, -- west longitude
+ 40.0, -- east longitude
+ 0
+);
+
+-- extent for NKG2020 transformations
+INSERT INTO "extent" VALUES(
+ 'NKG','EXTENT_2020', -- extend auth+code
+ 'Nordic and Baltic countries', -- name
+ 'Denmark; Estonia; Finland; Latvia; Lithuania; Norway; Sweden', -- description
+ 50.0, -- south latitude
+ 75.0, -- north latitude
+ 0.0, -- west longitude
+ 49.0, -- east longitude
+ 0
+);
+
+-- Scope for both NKG2008 and NKG2020 transformations
+INSERT INTO "scope" VALUES (
+ 'NKG', 'SCOPE_GENERIC', -- scope auth+code
+ 'Geodesy. High accuracy ETRS89 transformations', -- scope
+ 0 --deprecated
+);
+
+
+-------------------------------------------------------
+-- DATUM+CRS: NKG_ETRF00
+-------------------------------------------------------
+
+INSERT INTO "geodetic_datum" VALUES (
+ 'NKG','DATUM_NKG_ETRF00', -- auth+code
+ 'NKG_ETRF00', -- name
+ NULL, -- description
+ 'EPSG','7019', -- ellipsoid auth+code
+ 'EPSG','8901', -- prime meridian auth+code
+ '2016-03-16', -- publication date
+ 2000.0, -- frame reference epoch
+ NULL, -- ensemble accuracy
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG','5007',
+ 'geodetic_datum',
+ 'NKG','DATUM_NKG_ETRF00',
+ 'NKG','EXTENT_2008', -- extend auth+code
+ 'NKG','SCOPE_GENERIC' -- scope auth+code
+);
+
+-- Add CRS entry for NKG common frame ETRF_NKG00
+INSERT INTO "geodetic_crs" VALUES(
+ 'NKG','ETRF00', -- CRS auth+code
+ 'NKG_ETRF00', -- name
+ 'NKG Common reference frame 2000', -- description
+ 'geocentric', -- type
+ 'EPSG','6500', -- CRS type auth+code: ECEF
+ 'NKG','DATUM_NKG_ETRF00', -- datum auth+code
+ NULL, -- text definition
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5101', -- usage auth+code
+ 'geodetic_crs', -- object_table_name
+ 'NKG', 'ETRF00', -- object auth+code
+ 'NKG', 'EXTENT_2008', -- extent auth+code
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+-------------------------------------------------------
+-- DATUM+CRS: NKG_ETRF14
+-------------------------------------------------------
+
+INSERT INTO "geodetic_datum" VALUES (
+ 'NKG','DATUM_NKG_ETRF14', -- auth+code
+ 'NKG_ETRF14', -- name
+ NULL, -- description
+ 'EPSG','7019', -- ellipsoid auth+code
+ 'EPSG','8901', -- prime meridian auth+code
+ '2021-03-01', -- publication date
+ 2000.0, -- frame reference epoch
+ NULL, -- ensemble accuracy
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG','5033',
+ 'geodetic_datum',
+ 'NKG','DATUM_NKG_ETRF14',
+ 'NKG','EXTENT_2020', -- extend auth+code
+ 'NKG','SCOPE_GENERIC' -- scope auth+code
+);
+
+-- Add CRS entry for NKG common frame ETRF_NKG00
+INSERT INTO "geodetic_crs" VALUES(
+ 'NKG','ETRF14', -- CRS auth+code
+ 'NKG_ETRF14', -- name
+ 'NKG Common reference frame 2014', -- description
+ 'geocentric', -- type
+ 'EPSG','6500', -- CRS type auth+code: ECEF
+ 'NKG','DATUM_NKG_ETRF14', -- datum auth+code
+ NULL, -- text definition
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5102', -- usage auth+code
+ 'geodetic_crs', -- object_table_name
+ 'NKG', 'ETRF14', -- object auth+code
+ 'NKG', 'EXTENT_2020', -- extent auth+code
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+-------------------------------------------------------
+-- Transformation: ITRF2000 -> NKG_ETRF00
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2000_TO_NKG_ETRF00', -- operation auth+code
+ 'ITRF2000 to NKG_ETRF00', -- name
+ 'Time-dependent transformation from ITRF2000 to NKG_ETRF00', -- description
+ 'EPSG', '4919', -- source_crs: ITRF2000
+ 'NKG', 'ETRF00',-- target_crs: NKG_ETRF00
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG','NKG_ETRF00_TO_ETRF2000', -- operation auth+code
+ 'NKG_ETRF00 to ETRF2000', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+code
+ '+proj=deformation +t_epoch=2000.0 +grids=eur_nkg_nkgrf03vel_realigned.tif',
+ 'NKG', 'ETRF00',-- source_crs: NKG_ETRF00
+ 'EPSG','7930', -- target_crs: ETRF2000
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5003', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG','NKG_ETRF00_TO_ETRF2000', -- object auth+code
+ 'NKG','EXTENT_2008', -- extent auth+code
+ 'NKG','SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2000_TO_NKG_ETRF00', 2, 'EPSG', '7941'), -- ITRF2000 -> ETRF2000
+ ('NKG', 'ITRF2000_TO_NKG_ETRF00', 3, 'NKG', 'NKG_ETRF00_TO_ETRF2000')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5001', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2000_TO_NKG_ETRF00', -- object auth+code
+ 'NKG', 'EXTENT_2008', -- extent auth+code
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2014 -> NKG_ETRF14
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2014_TO_NKG_ETRF14', -- operation auth+code
+ 'ITRF2014 to NKG_ETRF14', -- name
+ 'Time-dependent transformation from ITRF2014 to NKG_ETRF14', -- description
+ 'EPSG', '7789', -- source_crs: ITRF2014
+ 'NKG', 'ETRF14',-- target_crs: NKG_ETRF14
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG','NKG_ETRF14_TO_ETRF2014', -- operation auth+code
+ 'NKG_ETRF14 to ETRF2014', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+code
+ '+proj=deformation +t_epoch=2000.0 +grids=eur_nkg_nkgrf17vel.tif',
+ 'NKG', 'ETRF14',-- source_crs: NKG_ETRF14
+ 'EPSG','8401', -- target_crs: ETRF2014
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5034', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG','NKG_ETRF14_TO_ETRF2014', -- object auth+code
+ 'NKG','EXTENT_2020', -- extent auth+code
+ 'NKG','SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2014_TO_NKG_ETRF14', 2, 'EPSG', '8366'), -- ITRF2014 -> ETRF2014
+ ('NKG', 'ITRF2014_TO_NKG_ETRF14', 3, 'NKG', 'NKG_ETRF14_TO_ETRF2014')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5035', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2014_TO_NKG_ETRF14', -- object auth+code
+ 'NKG', 'EXTENT_2020', -- extent auth+code
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+
+-------------------------------------------------------------
+-- Intermediate transformations: NKG_ETRF00 -> ETRFyy@2000.00
+-------------------------------------------------------------
+
+-- DK
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','P1_2008_DK', -- operation auth+code
+ 'NKG_ETRF00 to ETRF92@2000.0', -- name
+ 'Transformation from NKG_ETRF00 to ETRF92, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF00', -- source auth+code
+ 'EPSG','7920', -- target auth+code
+ 0.005, -- accuracy
+ 0.03863, -- x
+ 0.147, -- y
+ 0.02776, -- z
+ 'EPSG','9001',
+ 0.00617753, -- rx
+ 5.064e-05, -- ry
+ 4.729e-05, -- rz
+ 'EPSG','9104',
+ -0.009420, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2008', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5004', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','P1_2008_DK', -- object auth+code
+ 'EPSG', '1080', -- extent: Denmark - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+
+-- EE
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','P1_2008_EE', -- operation auth+code
+ 'NKG_ETRF00 to ETRF96@2000.0', -- name
+ 'Transformation from NKG_ETRF00 to ETRF96, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF00', -- source auth+code
+ 'EPSG','7926', -- target auth+code
+ 0.005, -- accuracy
+ 0.12194, -- x
+ 0.02225, -- y
+ -0.03541, -- z
+ 'EPSG','9001',
+ 0.00227196, -- rx
+ -0.00323934, -- ry
+ 0.00247008, -- rz
+ 'EPSG','9104',
+ -0.005626, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2008', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5008', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','P1_2008_EE', -- object auth+code
+ 'EPSG', '1090', -- extent: Estonia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+
+-- FI
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','P1_2008_FI', -- operation auth+code
+ 'NKG_ETRF00 to ETRF96@2000.0', -- name
+ 'Transformation from NKG_ETRF00 to ETRF96, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF00', -- source auth+code
+ 'EPSG','7926', -- target auth+code
+ 0.005, -- accuracy
+ 0.07251, -- x
+ -0.13019, -- y
+ -0.11323, -- z
+ 'EPSG','9001',
+ -0.00157399, -- rx
+ -0.00308833, -- ry
+ 0.00410332, -- rz
+ 'EPSG','9104',
+ 0.013012, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2008', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5009', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','P1_2008_FI', -- object auth+code
+ 'EPSG', '1095', -- extent: Finland - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+
+-- LV
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','P1_2008_LV', -- operation auth+code
+ 'NKG_ETRF00 to ETRF89@2000.0', -- name
+ 'Transformation from NKG_ETRF00 to ETRF89, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF00', -- source auth+code
+ 'EPSG','7914', -- target auth+code
+ 0.02, -- accuracy
+ 0.41812, -- x
+ -0.78105, -- y
+ -0.01335, -- z
+ 'EPSG','9001',
+ -0.0216436, -- rx
+ -0.0115184, -- ry
+ 0.01719911, -- rz
+ 'EPSG','9104',
+ 0.000757, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2008', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5010', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','P1_2008_LV', -- object auth+code
+ 'EPSG', '1139', -- extent: Latvia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+-- LT
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','P1_2008_LT', -- operation auth+code
+ 'NKG_ETRF00 to ETRF2000@2000.0', -- name
+ 'Transformation from NKG_ETRF00 to ETRF2000, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF00', -- source auth+code
+ 'EPSG','7930', -- target auth+code
+ 0.01, -- accuracy
+ 0.05692, -- x
+ 0.115495, -- y
+ -0.00078, -- z
+ 'EPSG','9001',
+ 0.00314291, -- rx
+ -0.00147975, -- ry
+ -0.00134758, -- rz
+ 'EPSG','9104',
+ -0.006182, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2008', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5011', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','P1_2008_LT', -- object auth+code
+ 'EPSG', '1145', -- extent: Lithuania - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+
+-- NO
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','P1_2008_NO', -- operation auth+code
+ 'NKG_ETRF00 to ETRF93@2000.0', -- name
+ 'Transformation from NKG_ETRF00 to ETRF93, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF00', -- source auth+code
+ 'EPSG','7922', -- target auth+code
+ 0.005, -- accuracy
+ -0.13116, -- x
+ -0.02817, -- y
+ 0.02036, -- z
+ 'EPSG','9001',
+ -0.00038674, -- rx
+ 0.00408947, -- ry
+ 0.00103588, -- rz
+ 'EPSG','9104',
+ 0.006569, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2008', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5012', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','P1_2008_NO', -- object auth+code
+ 'EPSG', '1352', -- extent: Norway - onshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+
+-- SE
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','P1_2008_SE', -- operation auth+code
+ 'NKG_ETRF00 to ETRF97@2000.0', -- name
+ 'Transformation from NKG_ETRF00 to ETRF97, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF00', -- source auth+code
+ 'EPSG','7928', -- target auth+code
+ 0.005, -- accuracy
+ -0.01642, -- x
+ -0.00064, -- y
+ -0.0305, -- z
+ 'EPSG','9001',
+ 0.00187431, -- rx
+ 0.00046382, -- ry
+ 0.00228487, -- rz
+ 'EPSG','9104',
+ 0.001861, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2008', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5014', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','P1_2008_SE', -- object auth+code
+ 'EPSG', '1225', -- extent: Sweden - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+
+-------------------------------------------------------------
+-- Intermediate transformations: NKG_ETRF14 -> ETRFyy@2000.00
+-------------------------------------------------------------
+
+-- DK
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','PAR_2020_DK', -- operation auth+code
+ 'NKG_ETRF14 to ETRF92@2000.0', -- name
+ 'Transformation from NKG_ETRF14 to ETRF92, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF14', -- source auth+code
+ 'EPSG','7920', -- target auth+code
+ 0.005, -- accuracy
+ 0.66818, -- x
+ 0.04453, -- y
+ -0.45049, -- z
+ 'EPSG','9001',
+ 0.00312883, -- rx
+ -0.02373423, -- ry
+ 0.00442969, -- rz
+ 'EPSG','9104',
+ -0.003136, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2020', -- operation version
+ 0
+);
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5036', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','PAR_2020_DK', -- object auth+code
+ 'EPSG', '1080', -- extent: Denmark - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+
+-- EE
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','PAR_2020_EE', -- operation auth+code
+ 'NKG_ETRF14 to ETRF96@2000.0', -- name
+ 'Transformation from NKG_ETRF14 to ETRF96, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF14', -- source auth+code
+ 'EPSG','7926', -- target auth+code
+ 0.005, -- accuracy
+ -0.05027, -- x
+ -0.11595, -- y
+ 0.03012, -- z
+ 'EPSG','9001',
+ -0.00310814, -- rx
+ 0.00457237, -- ry
+ 0.00472406, -- rz
+ 'EPSG','9104',
+ 0.003191, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2020', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5037', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','PAR_2020_EE', -- object auth+code
+ 'EPSG', '1090', -- extent: Estonia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+
+-- FI
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','PAR_2020_FI', -- operation auth+code
+ 'NKG_ETRF14 to ETRF96@2000.0', -- name
+ 'Transformation from NKG_ETRF14 to ETRF96, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF14', -- source auth+code
+ 'EPSG','7926', -- target auth+code
+ 0.005, -- accuracy
+ 0.15651, -- x
+ -0.10993, -- y
+ -0.10935, -- z
+ 'EPSG','9001',
+ -0.00312861, -- rx
+ -0.00378935, -- ry
+ 0.00403512, -- rz
+ 'EPSG','9104',
+ 0.00529, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2020', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5038', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','PAR_2020_FI', -- object auth+code
+ 'EPSG', '1095', -- extent: Finland - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+
+-- LV
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','PAR_2020_LV', -- operation auth+code
+ 'NKG_ETRF14 to ETRF89@2000.0', -- name
+ 'Transformation from NKG_ETRF14 to ETRF89, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF14', -- source auth+code
+ 'EPSG','7914', -- target auth+code
+ 0.01, -- accuracy
+ 0.09745, -- x
+ -0.69388, -- y
+ 0.52901, -- z
+ 'EPSG','9001',
+ -0.0192069, -- rx
+ 0.01043272, -- ry
+ 0.02327169, -- rz
+ 'EPSG','9104',
+ -0.049663, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2020', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5039', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','PAR_2020_LV', -- object auth+code
+ 'EPSG', '1139', -- extent: Latvia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+-- LT
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','PAR_2020_LT', -- operation auth+code
+ 'NKG_ETRF14 to ETRF2000@2000.0', -- name
+ 'Transformation from NKG_ETRF14 to ETRF2000, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF14', -- source auth+code
+ 'EPSG','7930', -- target auth+code
+ 0.015, -- accuracy
+ 0.36749, -- x
+ 0.14351, -- y
+ -0.18472, -- z
+ 'EPSG','9001',
+ 0.0047914, -- rx
+ -0.01027566, -- ry
+ 0.00276102, -- rz
+ 'EPSG','9104',
+ -0.003684, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2020', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5040', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','PAR_2020_LT', -- object auth+code
+ 'EPSG', '1145', -- extent: Lithuania - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+
+
+-- SE
+INSERT INTO "helmert_transformation" VALUES (
+ 'NKG','PAR_2020_SE', -- operation auth+code
+ 'NKG_ETRF14 to ETRF97@2000.0', -- name
+ 'Transformation from NKG_ETRF14 to ETRF97, at transformation reference epoch 2000.0', -- description / remark
+ 'EPSG','1033', -- method auth+code
+ 'Position Vector transformation (geocentric domain)',
+ 'NKG','ETRF14', -- source auth+code
+ 'EPSG','7928', -- target auth+code
+ 0.005, -- accuracy
+ 0.03054, -- x
+ 0.04606, -- y
+ -0.07944, -- z
+ 'EPSG','9001',
+ 0.00141958, -- rx
+ 0.00015132, -- ry
+ 0.00150337, -- rz
+ 'EPSG','9104',
+ 0.003002, -- s
+ 'EPSG','9202',
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
+ 'NKG 2020', -- operation version
+ 0
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5042', -- usage auth+code
+ 'helmert_transformation', -- object_table_name
+ 'NKG','PAR_2020_SE', -- object auth+code
+ 'EPSG', '1225', -- extent: Sweden - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+
+
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF00 -> ETRF92@1994.704 (DK)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'ETRF92_2000_TO_ETRF92_1994',-- object auth+code
+ 'ETRF92@2000.0 to ETRF92@1994.704', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=-5.296 +grids=eur_nkg_nkgrf03vel_realigned.tif',
+ 'EPSG','7920', -- source_crs: ETRF92@2000.0
+ 'EPSG','4936', -- target_crs: ETRS89 (DK)
+ 0.005, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5005', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'ETRF92_2000_TO_ETRF92_1994', -- object auth+code
+ 'EPSG', '1080', -- extent: Denmark - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF00_TO_DK', -- operation auth+code
+ 'NKG_ETRF00 to ETRS89(DK)', -- name
+ 'Transformation from NKG_ETRF00@2000.0 to ETRF92@1994.704', -- description
+ 'NKG', 'ETRF00',-- source_crs: NKG_ETRF00
+ 'EPSG','4936', -- target_crs: ETRS89 (DK)
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF00_TO_DK', 1, 'NKG', 'P1_2008_DK'),
+ ('NKG', 'ETRF00_TO_DK', 2, 'NKG', 'ETRF92_2000_TO_ETRF92_1994')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5006', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF00_TO_DK', -- object auth+code
+ 'EPSG', '1080', -- extent: Denmark - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2000 -> ETRF92@1994.704 (DK)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2000_TO_DK', -- operation auth+code
+ 'ITRF2000 to ETRS89(DK)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89(DK)', -- description
+ 'EPSG', '4919', -- source_crs: ITRF2000
+ 'EPSG', '4936', -- target_crs: ETRS89(DK)
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2000_TO_DK', 1, 'EPSG', '7941'), -- ITRF2000 -> ETRF2000
+ ('NKG', 'ITRF2000_TO_DK', 2, 'NKG', 'NKG_ETRF00_TO_ETRF2000'),
+ ('NKG', 'ITRF2000_TO_DK', 3, 'NKG', 'P1_2008_DK'),
+ ('NKG', 'ITRF2000_TO_DK', 4, 'NKG', 'ETRF92_2000_TO_ETRF92_1994')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5013', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2000_TO_DK', -- object auth+code
+ 'EPSG', '1080', -- extent: Denmark - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF00 -> ETRF96@1997.56 (EE)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'ETRF96_2000_TO_ETRF96_1997_56',-- object auth+code
+ 'ETRF96@2000.0 to ETRF96@1997.56', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=-2.44 +grids=eur_nkg_nkgrf03vel_realigned.tif',
+ 'EPSG','7926', -- source_crs: ETRF96@2000.0
+ 'EPSG','4936', -- target_crs: ETRS89 (EE)
+ 0.005, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5015', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'ETRF96_2000_TO_ETRF96_1997_56', -- object auth+code
+ 'EPSG', '1090', -- extent: Estonia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF00_TO_EE', -- operation auth+code
+ 'NKG_ETRF00 to ETRS89 (EUREF-EST97)', -- name
+ 'Transformation from NKG_ETRF00@2000.0 to ETRF96@1997.56', -- description
+ 'NKG', 'ETRF00',-- source_crs: NKG_ETRF00
+ 'EPSG','4936', -- target_crs: ETRS89 (EE)
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF00_TO_EE', 1, 'NKG', 'P1_2008_EE'),
+ ('NKG', 'ETRF00_TO_EE', 2, 'NKG', 'ETRF96_2000_TO_ETRF96_1997_56')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5016', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF00_TO_EE', -- object auth+code
+ 'EPSG', '1090', -- extent: Estonia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2000 -> ETRF96@1997.56 (EE)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2000_TO_EE', -- operation auth+code
+ 'ITRF2000 to ETRS89(EE)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89 (EUREF-EST97)', -- description
+ 'EPSG', '4919', -- source_crs: ITRF2000
+ 'EPSG', '4936', -- target_crs: ETRS89(EE)
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2000_TO_EE', 1, 'EPSG', '7941'), -- ITRF2000 -> ETRF2000
+ ('NKG', 'ITRF2000_TO_EE', 2, 'NKG', 'NKG_ETRF00_TO_ETRF2000'),
+ ('NKG', 'ITRF2000_TO_EE', 3, 'NKG', 'P1_2008_EE'),
+ ('NKG', 'ITRF2000_TO_EE', 4, 'NKG', 'ETRF96_2000_TO_ETRF96_1997_56')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5017', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2000_TO_EE', -- object auth+code
+ 'EPSG', '1090', -- extent: Estonia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF00 -> ETRF96@1997.0 (FI)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'ETRF96_2000_TO_ETRF96_1997',-- object auth+code
+ 'ETRF96@2000.0 to ETRF96@1997.0', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=-3.0 +grids=eur_nkg_nkgrf03vel_realigned.tif',
+ 'EPSG','7926', -- source_crs: ETRF96@2000.0
+ 'EPSG','4936', -- target_crs: ETRS89 (FI)
+ 0.005, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5018', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'ETRF96_2000_TO_ETRF96_1997', -- object auth+code
+ 'EPSG', '1095', -- extent: Finland - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF00_TO_FI', -- operation auth+code
+ 'NKG_ETRF00 to ETRS89 (EUREF-FIN)', -- name
+ 'Transformation from NKG_ETRF00@2000.0 to ETRF96@1997.0', -- description
+ 'NKG', 'ETRF00',-- source_crs: NKG_ETRF00
+ 'EPSG','4936', -- target_crs: ETRS89 (FI)
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF00_TO_FI', 1, 'NKG', 'P1_2008_FI'),
+ ('NKG', 'ETRF00_TO_FI', 2, 'NKG', 'ETRF96_2000_TO_ETRF96_1997')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5019', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF00_TO_FI', -- object auth+code
+ 'EPSG', '1095', -- extent: Finland - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2000 -> ETRF96@1997.0 (FI)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2000_TO_FI', -- operation auth+code
+ 'ITRF2000 to ETRS89 (EUREF-FIN)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89 (EUREF-FIN)', -- description
+ 'EPSG', '4919', -- source_crs: ITRF2000
+ 'EPSG', '4936', -- target_crs: ETRS89(FI)
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2000_TO_FI', 1, 'EPSG', '7941'), -- ITRF2000 -> ETRF2000
+ ('NKG', 'ITRF2000_TO_FI', 2, 'NKG', 'NKG_ETRF00_TO_ETRF2000'),
+ ('NKG', 'ITRF2000_TO_FI', 3, 'NKG', 'P1_2008_FI'),
+ ('NKG', 'ITRF2000_TO_FI', 4, 'NKG', 'ETRF96_2000_TO_ETRF96_1997')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5020', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2000_TO_FI', -- object auth+code
+ 'EPSG', '1095', -- extent: Finland - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF00 -> ETRF89@1992.75 (LV)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'ETRF89_2000_TO_ETRF89_1992',-- object auth+code
+ 'ETRF89@2000.0 to ETRF89@1992.75', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=-7.25 +grids=eur_nkg_nkgrf03vel_realigned.tif',
+ 'EPSG','7914', -- source_crs: ETRF89@2000.0
+ 'EPSG','4948', -- target_crs: LKS-92
+ 0.005, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5021', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'ETRF89_2000_TO_ETRF89_1992', -- object auth+code
+ 'EPSG', '1139', -- extent: Latvia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF00_TO_LV', -- operation auth+code
+ 'NKG_ETRF00 to ETRS89 (LKS-92)', -- name
+ 'Transformation from NKG_ETRF00@2000.0 to ETRF89@1992.75', -- description
+ 'NKG', 'ETRF00',-- source_crs: NKG_ETRF00
+ 'EPSG','4948', -- target_crs: LKS-92
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF00_TO_LV', 1, 'NKG', 'P1_2008_LV'),
+ ('NKG', 'ETRF00_TO_LV', 2, 'NKG', 'ETRF89_2000_TO_ETRF89_1992')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5022', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF00_TO_LV', -- object auth+code
+ 'EPSG', '1139', -- extent: Latvia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2000 -> ETRF89@1992.75 (LV)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2000_TO_LV', -- operation auth+code
+ 'ITRF2000 to ETRS89 (LKS-92)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89 (LKS-92)', -- description
+ 'EPSG', '4919', -- source_crs: ITRF2000
+ 'EPSG', '4948', -- target_crs: LKS-92
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2000_TO_LV', 1, 'EPSG', '7941'), -- ITRF2000 -> ETRF2000
+ ('NKG', 'ITRF2000_TO_LV', 2, 'NKG', 'NKG_ETRF00_TO_ETRF2000'),
+ ('NKG', 'ITRF2000_TO_LV', 3, 'NKG', 'P1_2008_LV'),
+ ('NKG', 'ITRF2000_TO_LV', 4, 'NKG', 'ETRF89_2000_TO_ETRF89_1992')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5023', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2000_TO_LV', -- object auth+code
+ 'EPSG', '1139', -- extent: Latvia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF00 -> ETRF2000@2003.75 (LT)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'ETRF2000_2000_TO_ETRF_2000_2003',-- object auth+code
+ 'ETRF2000@2000.0 to ETRF2000@2003.75', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=3.75 +grids=eur_nkg_nkgrf03vel_realigned.tif',
+ 'EPSG','7930', -- source_crs: ETRF2000@2000.0
+ 'EPSG','4950', -- target_crs: LKS94
+ 0.005, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5024', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'ETRF2000_2000_TO_ETRF_2000_2003', -- object auth+code
+ 'EPSG', '1145', -- extent: Lithuania - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF00_TO_LT', -- operation auth+code
+ 'NKG_ETRF00 to LKS94', -- name
+ 'Transformation from NKG_ETRF00@2000.0 to ETRF2000@2003.75', -- description
+ 'NKG', 'ETRF00',-- source_crs: NKG_ETRF00
+ 'EPSG','4950', -- target_crs: LKS94
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF00_TO_LT', 1, 'NKG', 'P1_2008_LT'),
+ ('NKG', 'ETRF00_TO_LT', 2, 'NKG', 'ETRF2000_2000_TO_ETRF_2000_2003')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5025', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF00_TO_LT', -- object auth+code
+ 'EPSG', '1145', -- extent: Lithuania - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2000 -> ETRF2000@2003.75 (LT)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2000_TO_LT', -- operation auth+code
+ 'ITRF2000 to ETRS89(LT)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89(LT)', -- description
+ 'EPSG', '4919', -- source_crs: ITRF2000
+ 'EPSG', '4950', -- target_crs: LKS94
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2000_TO_LT', 1, 'EPSG', '7941'), -- ITRF2000 -> ETRF2000
+ ('NKG', 'ITRF2000_TO_LT', 2, 'NKG', 'NKG_ETRF00_TO_ETRF2000'),
+ ('NKG', 'ITRF2000_TO_LT', 3, 'NKG', 'P1_2008_LT'),
+ ('NKG', 'ITRF2000_TO_LT', 4, 'NKG', 'ETRF2000_2000_TO_ETRF_2000_2003')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5026', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2000_TO_LT', -- object auth+code
+ 'EPSG', '1145', -- extent: Lithuania - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF00 -> ETRF93@1995.0 (NO)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'ETRF93_2000_TO_ETRF93_1995',-- object auth+code
+ 'ETRF93@2000.0 to ETRF93@1995.0', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=-5 +grids=eur_nkg_nkgrf03vel_realigned.tif',
+ 'EPSG','7922', -- source_crs: ETRF93@2000.0
+ 'EPSG','4936', -- target_crs: ETRS89 (NO)
+ 0.005, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5027', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'ETRF93_2000_TO_ETRF93_1995', -- object auth+code
+ 'EPSG', '1352', -- extent: Norway - onshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF00_TO_NO', -- operation auth+code
+ 'NKG_ETRF00 to ETRS89(NO)', -- name
+ 'Transformation from NKG_ETRF00@2000.0 to ETRF93@1995.0', -- description
+ 'NKG', 'ETRF00',-- source_crs: NKG_ETRF00
+ 'EPSG','4936', -- target_crs: ETRS89 (NO)
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF00_TO_NO', 1, 'NKG', 'P1_2008_NO'),
+ ('NKG', 'ETRF00_TO_NO', 2, 'NKG', 'ETRF93_2000_TO_ETRF93_1995')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5028', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF00_TO_NO', -- object auth+code
+ 'EPSG', '1352', -- extent: Norway - onshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2000 -> ETRF93@1995.0 (NO)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2000_TO_NO', -- operation auth+code
+ 'ITRF2000 to ETRS89(NO)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89(NO)', -- description
+ 'EPSG', '4919', -- source_crs: ITRF2000
+ 'EPSG', '4936', -- target_crs: ETRS89(NO)
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2000_TO_NO', 1, 'EPSG', '7941'), -- ITRF2000 -> ETRF2000
+ ('NKG', 'ITRF2000_TO_NO', 2, 'NKG', 'NKG_ETRF00_TO_ETRF2000'),
+ ('NKG', 'ITRF2000_TO_NO', 3, 'NKG', 'P1_2008_NO'),
+ ('NKG', 'ITRF2000_TO_NO', 4, 'NKG', 'ETRF93_2000_TO_ETRF93_1995')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5029', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2000_TO_NO', -- object auth+code
+ 'EPSG', '1352', -- extent: Norway - onshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF00 -> ETRF97@1999.5 (SE)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'ETRF97_2000_TO_ETRF97_1999',-- object auth+code
+ 'ETRF97@2000.0 to ETRF97@1999.5', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=-0.5 +grids=eur_nkg_nkgrf03vel_realigned.tif',
+ 'EPSG','7928', -- source_crs: ETRF97@2000.0
+ 'EPSG','4976', -- target_crs: SWEREF99
+ 0.005, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5030', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'ETRF97_2000_TO_ETRF97_1999', -- object auth+code
+ 'EPSG', '1225', -- extent: Sweden - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF00_TO_SE', -- operation auth+code
+ 'NKG_ETRF00 to SWEREF99', -- name
+ 'Transformation from NKG_ETRF00@2000.0 to ETRF97@1999.5', -- description
+ 'NKG', 'ETRF00',-- source_crs: NKG_ETRF00
+ 'EPSG','4976', -- target_crs: SWEREF99
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF00_TO_SE', 1, 'NKG', 'P1_2008_SE'),
+ ('NKG', 'ETRF00_TO_SE', 2, 'NKG', 'ETRF97_2000_TO_ETRF97_1999')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5031', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF00_TO_SE', -- object auth+code
+ 'EPSG', '1225', -- extent: Sweden - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2000 -> ETRF97@1999.5 (SE)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2000_TO_SE', -- operation auth+code
+ 'ITRF2000 to ETRS89(SE)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89(SE)', -- description
+ 'EPSG', '4919', -- source_crs: ITRF2000
+ 'EPSG', '4976', -- target_crs: SWEREF99
+ 0.01, -- accuracy
+ 'NKG 2008', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2000_TO_SE', 1, 'EPSG', '7941'), -- ITRF2000 -> ETRF2000
+ ('NKG', 'ITRF2000_TO_SE', 2, 'NKG', 'NKG_ETRF00_TO_ETRF2000'),
+ ('NKG', 'ITRF2000_TO_SE', 3, 'NKG', 'P1_2008_SE'),
+ ('NKG', 'ITRF2000_TO_SE', 4, 'NKG', 'ETRF97_2000_TO_ETRF97_1999')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5032', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2000_TO_SE', -- object auth+code
+ 'EPSG', '1225', -- extent: Sweden - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF14 -> ETRF92@1994.704 (DK)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'DK_2020_INTRAPLATE', -- object auth+code
+ 'ETRF92@2000.0 to ETRF92@1994.704', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=15.829 +grids=eur_nkg_nkgrf17vel.tif',
+ 'EPSG','7920', -- source_crs: ETRF92@2000.0
+ 'EPSG','4936', -- target_crs: ETRS89 (DK)
+ 0.005, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5043', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'DK_2020_INTRAPLATE', -- object auth+code
+ 'EPSG', '1080', -- extent: Denmark - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF14_TO_DK', -- operation auth+code
+ 'NKG_ETRF14 to ETRS89(DK)', -- name
+ 'Transformation from NKG_ETRF14@2000.0 to ETRF92@1994.704', -- description
+ 'NKG', 'ETRF14',-- source_crs: NKG_ETRF00
+ 'EPSG','4936', -- target_crs: ETRS89 (DK)
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF14_TO_DK', 1, 'NKG', 'PAR_2020_DK'),
+ ('NKG', 'ETRF14_TO_DK', 2, 'NKG', 'DK_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5044', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF14_TO_DK', -- object auth+code
+ 'EPSG', '1080', -- extent: Denmark - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2014 -> ETRF92@1994.704 (DK)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2014_TO_DK', -- operation auth+code
+ 'ITRF2014 to ETRS89(DK)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89(DK)', -- description
+ 'EPSG', '7789', -- source_crs: ITRF2014
+ 'EPSG', '4936', -- target_crs: ETRS89(DK)
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2014_TO_DK', 1, 'EPSG', '8366'), -- ITRF2014 -> ETRF2014
+ ('NKG', 'ITRF2014_TO_DK', 2, 'NKG', 'NKG_ETRF14_TO_ETRF2014'),
+ ('NKG', 'ITRF2014_TO_DK', 3, 'NKG', 'PAR_2020_DK'),
+ ('NKG', 'ITRF2014_TO_DK', 4, 'NKG', 'DK_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5045', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2014_TO_DK', -- object auth+code
+ 'EPSG', '1080', -- extent: Denmark - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+INSERT INTO "supersession" VALUES (
+ 'concatenated_operation',
+ 'NKG', 'ITRF2000_TO_DK',
+ 'concatenated_operation',
+ 'NKG', 'ITRF2014_TO_DK',
+ 'NKG',
+ 0
+);
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF14 -> ETRF96@1997.56 (EE)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'EE_2020_INTRAPLATE',-- object auth+code
+ 'ETRF96@2000.0 to ETRF96@1997.56', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=-2.44 +grids=eur_nkg_nkgrf17vel.tif',
+ 'EPSG','7926', -- source_crs: ETRF96@2000.0
+ 'EPSG','4936', -- target_crs: ETRS89 (EE)
+ 0.005, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5046', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'EE_2020_INTRAPLATE', -- object auth+code
+ 'EPSG', '1090', -- extent: Estonia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF14_TO_EE', -- operation auth+code
+ 'NKG_ETRF14 to ETRS89 (EUREF-EST97)', -- name
+ 'Transformation from NKG_ETRF14@2000.0 to ETRF96@1997.56', -- description
+ 'NKG', 'ETRF14',-- source_crs: NKG_ETRF00
+ 'EPSG','4936', -- target_crs: ETRS89 (EE)
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF14_TO_EE', 1, 'NKG', 'PAR_2020_EE'),
+ ('NKG', 'ETRF14_TO_EE', 2, 'NKG', 'EE_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5047', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF14_TO_EE', -- object auth+code
+ 'EPSG', '1090', -- extent: Estonia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2014 -> ETRF96@1997.56 (EE)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2014_TO_EE', -- operation auth+code
+ 'ITRF2014 to ETRS89 (EUREF-EST97)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89 (EUREF-EST97)', -- description
+ 'EPSG', '7789', -- source_crs: ITRF2014
+ 'EPSG', '4936', -- target_crs: ETRS89(EE)
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2014_TO_EE', 1, 'EPSG', '8366'), -- ITRF2014 -> ETRF2014
+ ('NKG', 'ITRF2014_TO_EE', 2, 'NKG', 'NKG_ETRF14_TO_ETRF2014'),
+ ('NKG', 'ITRF2014_TO_EE', 3, 'NKG', 'PAR_2020_EE'),
+ ('NKG', 'ITRF2014_TO_EE', 4, 'NKG', 'EE_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5048', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2014_TO_EE', -- object auth+code
+ 'EPSG', '1090', -- extent: Estonia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+INSERT INTO "supersession" VALUES (
+ 'concatenated_operation',
+ 'NKG', 'ITRF2000_TO_EE',
+ 'concatenated_operation',
+ 'NKG', 'ITRF2014_TO_EE',
+ 'NKG',
+ 0
+);
+
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF14 -> ETRF96@1997.0 (FI)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'FI_2020_INTRAPLATE',-- object auth+code
+ 'ETRF96@2000.0 to ETRF96@1997.0', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=-3 +grids=eur_nkg_nkgrf17vel.tif',
+ 'EPSG','7926', -- source_crs: ETRF96@2000.0
+ 'EPSG','4936', -- target_crs: ETRS89 (FI)
+ 0.005, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5049', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'FI_2020_INTRAPLATE', -- object auth+code
+ 'EPSG', '1095', -- extent: Finland - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF14_TO_FI', -- operation auth+code
+ 'NKG_ETRF14 to ETRS89 (EUREF-FIN)', -- name
+ 'Transformation from NKG_ETRF14@2000.0 to ETRF96@1997.0', -- description
+ 'NKG', 'ETRF14',-- source_crs: NKG_ETRF00
+ 'EPSG','4936', -- target_crs: ETRS89 (FI)
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF14_TO_FI', 1, 'NKG', 'PAR_2020_FI'),
+ ('NKG', 'ETRF14_TO_FI', 2, 'NKG', 'FI_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5050', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF14_TO_FI', -- object auth+code
+ 'EPSG', '1095', -- extent: Finland - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2014 -> ETRF96@1997.0 (FI)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2014_TO_FI', -- operation auth+code
+ 'ITRF2014 to ETRS89 (EUREF-FIN)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89 (EUREF-FIN)', -- description
+ 'EPSG', '7789', -- source_crs: ITRF2014
+ 'EPSG', '4936', -- target_crs: ETRS89(FI)
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2014_TO_FI', 1, 'EPSG', '8366'), -- ITRF2014 -> ETRF2014
+ ('NKG', 'ITRF2014_TO_FI', 2, 'NKG', 'NKG_ETRF14_TO_ETRF2014'),
+ ('NKG', 'ITRF2014_TO_FI', 3, 'NKG', 'PAR_2020_FI'),
+ ('NKG', 'ITRF2014_TO_FI', 4, 'NKG', 'FI_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5051', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2014_TO_FI', -- object auth+code
+ 'EPSG', '1095', -- extent: Finland - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+INSERT INTO "supersession" VALUES (
+ 'concatenated_operation',
+ 'NKG', 'ITRF2000_TO_FI',
+ 'concatenated_operation',
+ 'NKG', 'ITRF2014_TO_FI',
+ 'NKG',
+ 0
+);
+
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF14 -> ETRF89@1992.75 (LV)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'LV_2020_INTRAPLATE', -- object auth+code
+ 'ETRF89@2000.0 to ETRF89@1992.75 (LKS-92)', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=-7.25 +grids=eur_nkg_nkgrf17vel.tif',
+ 'EPSG','7914', -- source_crs: ETRF89@2000.0
+ 'EPSG','4948', -- target_crs: LKS-92
+ 0.005, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5052', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'LV_2020_INTRAPLATE', -- object auth+code
+ 'EPSG', '1139', -- extent: Latvia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF14_TO_LV', -- operation auth+code
+ 'NKG_ETRF14 to ETRS89 (LKS-92)', -- name
+ 'Transformation from NKG_ETRF14@2000.0 to ETRF89@1992.75', -- description
+ 'NKG', 'ETRF14',-- source_crs: NKG_ETRF00
+ 'EPSG','4948', -- target_crs: LKS-92
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF14_TO_LV', 1, 'NKG', 'PAR_2020_LV'),
+ ('NKG', 'ETRF14_TO_LV', 2, 'NKG', 'LV_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5053', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF14_TO_LV', -- object auth+code
+ 'EPSG', '1139', -- extent: Latvia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2014 -> ETRF89@1992.75 (LV)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2014_TO_LV', -- operation auth+code
+ 'ITRF2014 to ETRS89 (LKS-92)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89 (LKS-92)', -- description
+ 'EPSG', '7789', -- source_crs: ITRF2014
+ 'EPSG', '4948', -- target_crs: LKS-92
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2014_TO_LV', 1, 'EPSG', '8366'), -- ITRF2014 -> ETRF2014
+ ('NKG', 'ITRF2014_TO_LV', 2, 'NKG', 'NKG_ETRF14_TO_ETRF2014'),
+ ('NKG', 'ITRF2014_TO_LV', 3, 'NKG', 'PAR_2020_LV'),
+ ('NKG', 'ITRF2014_TO_LV', 4, 'NKG', 'LV_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5054', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2014_TO_LV', -- object auth+code
+ 'EPSG', '1139', -- extent: Latvia - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+INSERT INTO "supersession" VALUES (
+ 'concatenated_operation',
+ 'NKG', 'ITRF2000_TO_LV',
+ 'concatenated_operation',
+ 'NKG', 'ITRF2014_TO_LV',
+ 'NKG',
+ 0
+);
+
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF14 -> ETRF2000@2003.75 (LT)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'LT_2020_INTRAPLATE', -- object auth+code
+ 'ETRF2000@2000.0 to ETRF2000@2003.75 (LKS94)', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=3.75 +grids=eur_nkg_nkgrf17vel.tif',
+ 'EPSG','7930', -- source_crs: ETRF2000@2000.0
+ 'EPSG','4950', -- target_crs: LKS94
+ 0.005, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5055', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'LT_2020_INTRAPLATE', -- object auth+code
+ 'EPSG', '1145', -- extent: Lithuania - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF14_TO_LT', -- operation auth+code
+ 'NKG_ETRF14 to LKS94', -- name
+ 'Transformation from NKG_ETRF14@2000.0 to ETRF2000@2003.75 (LKS94)', -- description
+ 'NKG', 'ETRF14',-- source_crs: NKG_ETRF00
+ 'EPSG','4950', -- target_crs: LKS94
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF14_TO_LT', 1, 'NKG', 'PAR_2020_LT'),
+ ('NKG', 'ETRF14_TO_LT', 2, 'NKG', 'LT_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5056', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF14_TO_LT', -- object auth+code
+ 'EPSG', '1145', -- extent: Lithuania - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2014 -> ETRF2000@2003.75 (LT)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2014_TO_LT', -- operation auth+code
+ 'ITRF2014 to ETRS89(LT)', -- name
+ 'Time-dependent transformation from ITRF2014 to ETRS89(LT)', -- description
+ 'EPSG', '7789', -- source_crs: ITRF2014
+ 'EPSG', '4950', -- target_crs: LKS94
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2014_TO_LT', 1, 'EPSG', '8366'), -- ITRF2014 -> ETRF2014
+ ('NKG', 'ITRF2014_TO_LT', 2, 'NKG', 'NKG_ETRF14_TO_ETRF2014'),
+ ('NKG', 'ITRF2014_TO_LT', 3, 'NKG', 'PAR_2020_LT'),
+ ('NKG', 'ITRF2014_TO_LT', 4, 'NKG', 'LT_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5057', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2014_TO_LT', -- object auth+code
+ 'EPSG', '1145', -- extent: Lithuania - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+INSERT INTO "supersession" VALUES (
+ 'concatenated_operation',
+ 'NKG', 'ITRF2000_TO_LT',
+ 'concatenated_operation',
+ 'NKG', 'ITRF2014_TO_LT',
+ 'NKG',
+ 0
+);
+
+
+-------------------------------------------------------
+-- Transformation: NKG_ETRF14 -> ETRF97@1999.5 (SE)
+-------------------------------------------------------
+
+INSERT INTO "other_transformation" (
+ auth_name,
+ code,
+ name,
+ description,
+ method_auth_name,
+ method_code,
+ method_name,
+ source_crs_auth_name,
+ source_crs_code,
+ target_crs_auth_name,
+ target_crs_code,
+ accuracy,
+ operation_version,
+ deprecated
+)
+VALUES(
+ 'NKG', 'SE_2020_INTRAPLATE',-- object auth+code
+ 'ETRF97@2000.0 to ETRF97@1999.5', -- name
+ NULL, -- description
+ 'PROJ', 'PROJString', -- method auth+cod
+ '+proj=deformation +dt=-0.5 +grids=eur_nkg_nkgrf17vel.tif',
+ 'EPSG','7928', -- source_crs: ETRF97@2000.0
+ 'EPSG','4976', -- target_crs: SWEREF99
+ 0.005, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5061', -- usage auth+code
+ 'other_transformation', -- object_table_name
+ 'NKG', 'SE_2020_INTRAPLATE', -- object auth+code
+ 'EPSG', '1225', -- extent: Sweden - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope
+);
+
+INSERT INTO "concatenated_operation" VALUES(
+ 'NKG', 'ETRF14_TO_SE', -- operation auth+code
+ 'NKG_ETRF14 to SWEREF99', -- name
+ 'Transformation from NKG_ETRF14@2000.0 to SWEREF99 (ETRF97@1999.5)', -- description
+ 'NKG', 'ETRF14',-- source_crs: NKG_ETRF00
+ 'EPSG','4976', -- target_crs: SWEREF99
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+);
+
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ETRF14_TO_SE', 1, 'NKG', 'PAR_2020_SE'),
+ ('NKG', 'ETRF14_TO_SE', 2, 'NKG', 'SE_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5062', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ETRF14_TO_SE', -- object auth+code
+ 'EPSG', '1225', -- extent: Sweden - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+
+-------------------------------------------------------
+-- Transformation: ITRF2014 -> ETRF97@1999.5 (SE)
+-------------------------------------------------------
+
+INSERT INTO "concatenated_operation" VALUES (
+ 'NKG', 'ITRF2014_TO_SE', -- operation auth+code
+ 'ITRF2014 to ETRS89(SE)', -- name
+ 'Time-dependent transformation from ITRF2014 to SWEREF99', -- description
+ 'EPSG', '7789', -- source_crs: ITRF2014
+ 'EPSG', '4976', -- target_crs: SWEREF99
+ 0.01, -- accuracy
+ 'NKG 2020', -- operation_version
+ 0 -- deprecated
+
+);
+
+INSERT INTO "concatenated_operation_step" (
+ operation_auth_name, operation_code, step_number, step_auth_name, step_code
+) VALUES
+ ('NKG', 'ITRF2014_TO_SE', 1, 'EPSG', '8366'), -- ITRF2014 -> ETRF2014
+ ('NKG', 'ITRF2014_TO_SE', 2, 'NKG', 'NKG_ETRF14_TO_ETRF2014'),
+ ('NKG', 'ITRF2014_TO_SE', 3, 'NKG', 'PAR_2020_SE'),
+ ('NKG', 'ITRF2014_TO_SE', 4, 'NKG', 'SE_2020_INTRAPLATE')
+;
+
+
+INSERT INTO "usage" VALUES (
+ 'NKG', '5063', -- usage auth+code
+ 'concatenated_operation', -- object_table_name
+ 'NKG', 'ITRF2014_TO_SE', -- object auth+code
+ 'EPSG', '1352', -- extent: Sweden - onshore and offshore
+ 'NKG', 'SCOPE_GENERIC' -- scope auth+code
+);
+
+
+INSERT INTO "supersession" VALUES (
+ 'concatenated_operation',
+ 'NKG', 'ITRF2000_TO_SE',
+ 'concatenated_operation',
+ 'NKG', 'ITRF2014_TO_SE',
+ 'NKG',
+ 0
+);
+
+
diff --git a/data/sql/other_transformation.sql b/data/sql/other_transformation.sql
index 9714c79b..9b3bb471 100644
--- a/data/sql/other_transformation.sql
+++ b/data/sql/other_transformation.sql
@@ -438,12 +438,18 @@ INSERT INTO "other_transformation" VALUES('EPSG','8359','Baltic 1957 height to B
INSERT INTO "usage" VALUES('EPSG','10507','other_transformation','EPSG','8359','EPSG','1306','EPSG','1111');
INSERT INTO "other_transformation" VALUES('EPSG','9371','Vienna height to GHA height (1)','Defines Wiener Null surface. GHA vertical reference surface is 156.68m below Wiener Null surface.','EPSG','9616','Vertical Offset','EPSG','8881','EPSG','5778',0.0,'EPSG','8603','Vertical Offset',156.68,'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,NULL,NULL,'BEV-Aut Wien',0);
INSERT INTO "usage" VALUES('EPSG','13986','other_transformation','EPSG','9371','EPSG','4585','EPSG','1059');
-INSERT INTO "other_transformation" VALUES('EPSG','9446','ODN height to EVRF2019 mean-tide height (1)','Determined at Channel Tunnel portal.','EPSG','9616','Vertical Offset','EPSG','5701','EPSG','9390',0.02,'EPSG','8603','Vertical Offset',-0.173,'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,NULL,NULL,'EuG-Gbr 2019m',0);
+INSERT INTO "other_transformation" VALUES('EPSG','9446','ODN height to EVRF2019 mean-tide height (1)','Determined at Channel Tunnel portal.','EPSG','9616','Vertical Offset','EPSG','5701','EPSG','9390',0.02,'EPSG','8603','Vertical Offset',-0.173,'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,NULL,NULL,'EuG-Gbr 2019m',1);
INSERT INTO "usage" VALUES('EPSG','14104','other_transformation','EPSG','9446','EPSG','2792','EPSG','1059');
-INSERT INTO "other_transformation" VALUES('EPSG','9447','Antalya height to EVRF2019 mean-tide height (1)','Determined at two points. No accuracy figure available. Applicable for points up to a maximum height of about 1000 m.','EPSG','9616','Vertical Offset','EPSG','5775','EPSG','9390',0.02,'EPSG','8603','Vertical Offset',-0.446,'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,NULL,NULL,'EuG-Tur 2019m',0);
+INSERT INTO "other_transformation" VALUES('EPSG','9447','Antalya height to EVRF2019 mean-tide height (1)','Determined at two points. No accuracy figure available. Applicable for points up to a maximum height of about 1000 m.','EPSG','9616','Vertical Offset','EPSG','5775','EPSG','9390',0.02,'EPSG','8603','Vertical Offset',-0.446,'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,NULL,NULL,'EuG-Tur 2019m',1);
INSERT INTO "usage" VALUES('EPSG','14105','other_transformation','EPSG','9447','EPSG','3322','EPSG','1059');
-INSERT INTO "other_transformation" VALUES('EPSG','9448','Antalya height to EVRF2019 height (1)','Determined at two points. No accuracy figure available. Applicable for points up to a maximum height of about 1000 m.','EPSG','9616','Vertical Offset','EPSG','5775','EPSG','9389',0.02,'EPSG','8603','Vertical Offset',-0.392,'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,NULL,NULL,'EuG-Tur 2019z',0);
+INSERT INTO "other_transformation" VALUES('EPSG','9448','Antalya height to EVRF2019 height (1)','Determined at two points. No accuracy figure available. Applicable for points up to a maximum height of about 1000 m.','EPSG','9616','Vertical Offset','EPSG','5775','EPSG','9389',0.02,'EPSG','8603','Vertical Offset',-0.392,'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,NULL,NULL,'EuG-Tur 2019z',1);
INSERT INTO "usage" VALUES('EPSG','14106','other_transformation','EPSG','9448','EPSG','3322','EPSG','1059');
+INSERT INTO "other_transformation" VALUES('EPSG','9551','Antalya height to EVRF2019 height (2)','Determined at two points. No accuracy figure available. Applicable for points up to a maximum height of about 1000 m.','EPSG','9616','Vertical Offset','EPSG','5775','EPSG','9389',0.02,'EPSG','8603','Vertical Offset',-0.394,'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,NULL,NULL,'EuG-Tur 2019z 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14651','other_transformation','EPSG','9551','EPSG','3322','EPSG','1059');
+INSERT INTO "other_transformation" VALUES('EPSG','9552','Antalya height to EVRF2019 mean-tide height (2)','Determined at two points. No accuracy figure available. Applicable for points up to a maximum height of about 1000 m.','EPSG','9616','Vertical Offset','EPSG','5775','EPSG','9390',0.02,'EPSG','8603','Vertical Offset',-0.448,'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,NULL,NULL,'EuG-Tur 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14650','other_transformation','EPSG','9552','EPSG','3322','EPSG','1059');
+INSERT INTO "other_transformation" VALUES('EPSG','9562','ODN height to EVRF2019 mean-tide height (2)','Determined at Channel Tunnel portal.','EPSG','9616','Vertical Offset','EPSG','5701','EPSG','9390',0.02,'EPSG','8603','Vertical Offset',-0.17,'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,NULL,NULL,'EuG-Gbr 2019m 2020-09',0);
+INSERT INTO "usage" VALUES('EPSG','14640','other_transformation','EPSG','9562','EPSG','2792','EPSG','1059');
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.','EPSG','9624','Affine parametric transformation','EPSG','24100','EPSG','24200',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,NULL,NULL,'SD-Jam',0);
INSERT INTO "usage" VALUES('EPSG','11088','other_transformation','EPSG','10087','EPSG','3342','EPSG','1153');
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.','EPSG','9624','Affine parametric transformation','EPSG','24200','EPSG','24100',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,NULL,NULL,'SD-Jam',1);
diff --git a/data/sql/projected_crs.sql b/data/sql/projected_crs.sql
index a2adb443..64b361a1 100644
--- a/data/sql/projected_crs.sql
+++ b/data/sql/projected_crs.sql
@@ -2114,9 +2114,9 @@ INSERT INTO "usage" VALUES('EPSG','2080','projected_crs','EPSG','3062','EPSG','1
INSERT INTO "projected_crs" VALUES('EPSG','3063','Azores Central 1995 / UTM zone 26N',NULL,'EPSG','4400','EPSG','4665','EPSG','16026',NULL,0);
INSERT INTO "usage" VALUES('EPSG','2081','projected_crs','EPSG','3063','EPSG','1301','EPSG','1153');
INSERT INTO "projected_crs" VALUES('EPSG','3064','IGM95 / UTM zone 32N',NULL,'EPSG','4400','EPSG','4670','EPSG','16032',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','2082','projected_crs','EPSG','3064','EPSG','1718','EPSG','1027');
+INSERT INTO "usage" VALUES('EPSG','14405','projected_crs','EPSG','3064','EPSG','1718','EPSG','1027');
INSERT INTO "projected_crs" VALUES('EPSG','3065','IGM95 / UTM zone 33N',NULL,'EPSG','4400','EPSG','4670','EPSG','16033',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','2083','projected_crs','EPSG','3065','EPSG','1719','EPSG','1027');
+INSERT INTO "usage" VALUES('EPSG','14406','projected_crs','EPSG','3065','EPSG','1719','EPSG','1027');
INSERT INTO "projected_crs" VALUES('EPSG','3066','ED50 / Jordan TM',NULL,'EPSG','4400','EPSG','4230','EPSG','19995',NULL,0);
INSERT INTO "usage" VALUES('EPSG','2084','projected_crs','EPSG','3066','EPSG','1130','EPSG','1142');
INSERT INTO "projected_crs" VALUES('EPSG','3067','ETRS89 / TM35FIN(E,N)',NULL,'EPSG','4400','EPSG','4258','EPSG','16065',NULL,0);
@@ -5685,11 +5685,11 @@ INSERT INTO "usage" VALUES('EPSG','4911','projected_crs','EPSG','6692','EPSG','3
INSERT INTO "projected_crs" VALUES('EPSG','6703','WGS 84 / TM 60 SW',NULL,'EPSG','4400','EPSG','4326','EPSG','6702',NULL,0);
INSERT INTO "usage" VALUES('EPSG','4918','projected_crs','EPSG','6703','EPSG','4172','EPSG','1136');
INSERT INTO "projected_crs" VALUES('EPSG','6707','RDN2008 / UTM zone 32N (N-E)',NULL,'EPSG','4500','EPSG','6706','EPSG','16032',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','4922','projected_crs','EPSG','6707','EPSG','1718','EPSG','1142');
+INSERT INTO "usage" VALUES('EPSG','14415','projected_crs','EPSG','6707','EPSG','1718','EPSG','1142');
INSERT INTO "projected_crs" VALUES('EPSG','6708','RDN2008 / UTM zone 33N (N-E)',NULL,'EPSG','4500','EPSG','6706','EPSG','16033',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','4923','projected_crs','EPSG','6708','EPSG','4186','EPSG','1142');
+INSERT INTO "usage" VALUES('EPSG','14414','projected_crs','EPSG','6708','EPSG','4186','EPSG','1142');
INSERT INTO "projected_crs" VALUES('EPSG','6709','RDN2008 / UTM zone 34N (N-E)',NULL,'EPSG','4500','EPSG','6706','EPSG','16034',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','4924','projected_crs','EPSG','6709','EPSG','4187','EPSG','1142');
+INSERT INTO "usage" VALUES('EPSG','14416','projected_crs','EPSG','6709','EPSG','4187','EPSG','1142');
INSERT INTO "projected_crs" VALUES('EPSG','6720','WGS 84 / CIG92',NULL,'EPSG','4400','EPSG','4326','EPSG','6716',NULL,0);
INSERT INTO "usage" VALUES('EPSG','4926','projected_crs','EPSG','6720','EPSG','4169','EPSG','1029');
INSERT INTO "projected_crs" VALUES('EPSG','6721','GDA94 / CIG94',NULL,'EPSG','4400','EPSG','4283','EPSG','6717',NULL,0);
@@ -5879,9 +5879,9 @@ INSERT INTO "usage" VALUES('EPSG','5021','projected_crs','EPSG','6868','EPSG','1
INSERT INTO "projected_crs" VALUES('EPSG','6870','ETRS89 / Albania TM 2010',NULL,'EPSG','4530','EPSG','4258','EPSG','6869',NULL,0);
INSERT INTO "usage" VALUES('EPSG','5022','projected_crs','EPSG','6870','EPSG','3212','EPSG','1092');
INSERT INTO "projected_crs" VALUES('EPSG','6875','RDN2008 / Italy zone (N-E)',NULL,'EPSG','4500','EPSG','6706','EPSG','6877',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','5024','projected_crs','EPSG','6875','EPSG','1127','EPSG','1241');
+INSERT INTO "usage" VALUES('EPSG','14417','projected_crs','EPSG','6875','EPSG','1127','EPSG','1241');
INSERT INTO "projected_crs" VALUES('EPSG','6876','RDN2008 / Zone 12 (N-E)',NULL,'EPSG','4500','EPSG','6706','EPSG','6878',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','5025','projected_crs','EPSG','6876','EPSG','1127','EPSG','1142');
+INSERT INTO "usage" VALUES('EPSG','14418','projected_crs','EPSG','6876','EPSG','1127','EPSG','1142');
INSERT INTO "projected_crs" VALUES('EPSG','6879','NAD83(2011) / Wisconsin Central',NULL,'EPSG','4499','EPSG','6318','EPSG','14832',NULL,0);
INSERT INTO "usage" VALUES('EPSG','5026','projected_crs','EPSG','6879','EPSG','2266','EPSG','1142');
INSERT INTO "projected_crs" VALUES('EPSG','6880','NAD83(2011) / Nebraska (ftUS)',NULL,'EPSG','4497','EPSG','6318','EPSG','15396',NULL,0);
@@ -7302,8 +7302,10 @@ INSERT INTO "projected_crs" VALUES('EPSG','9493','SRGI2013 / UTM zone 53S',NULL,
INSERT INTO "usage" VALUES('EPSG','14171','projected_crs','EPSG','9493','EPSG','1662','EPSG','1266');
INSERT INTO "projected_crs" VALUES('EPSG','9494','SRGI2013 / UTM zone 54S',NULL,'EPSG','4400','EPSG','9470','EPSG','16154',NULL,0);
INSERT INTO "usage" VALUES('EPSG','14172','projected_crs','EPSG','9494','EPSG','1663','EPSG','1266');
-INSERT INTO "projected_crs" VALUES('EPSG','9498','POSGAR 2007 / CABA 2019',NULL,'EPSG','4500','EPSG','5340','EPSG','9497',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','14243','projected_crs','EPSG','9498','EPSG','4610','EPSG','1056');
+INSERT INTO "projected_crs" VALUES('EPSG','9498','POSGAR 2007 / CABA 2019',NULL,'EPSG','4530','EPSG','5340','EPSG','9497',NULL,0);
+INSERT INTO "usage" VALUES('EPSG','14513','projected_crs','EPSG','9498','EPSG','4610','EPSG','1056');
+INSERT INTO "projected_crs" VALUES('EPSG','9674','NAD83 / USFS R6 Albers',NULL,'EPSG','4400','EPSG','4269','EPSG','9673',NULL,0);
+INSERT INTO "usage" VALUES('EPSG','14787','projected_crs','EPSG','9674','EPSG','2381','EPSG','1165');
INSERT INTO "projected_crs" VALUES('EPSG','20004','Pulkovo 1995 / Gauss-Kruger zone 4',NULL,'EPSG','4530','EPSG','4200','EPSG','16204',NULL,0);
INSERT INTO "usage" VALUES('EPSG','6177','projected_crs','EPSG','20004','EPSG','1763','EPSG','1211');
INSERT INTO "projected_crs" VALUES('EPSG','20005','Pulkovo 1995 / Gauss-Kruger zone 5',NULL,'EPSG','4530','EPSG','4200','EPSG','16205',NULL,0);
@@ -8925,7 +8927,9 @@ INSERT INTO "usage" VALUES('EPSG','6985','projected_crs','EPSG','29871','EPSG','
INSERT INTO "projected_crs" VALUES('EPSG','29872','Timbalai 1948 / RSO Borneo (ftSe)',NULL,'EPSG','4405','EPSG','4298','EPSG','19957',NULL,0);
INSERT INTO "usage" VALUES('EPSG','6986','projected_crs','EPSG','29872','EPSG','3977','EPSG','1136');
INSERT INTO "projected_crs" VALUES('EPSG','29873','Timbalai 1948 / RSO Borneo (m)',NULL,'EPSG','4400','EPSG','4298','EPSG','19958',NULL,0);
-INSERT INTO "usage" VALUES('EPSG','6987','projected_crs','EPSG','29873','EPSG','1362','EPSG','1149');
+INSERT INTO "usage" VALUES('EPSG','14397','projected_crs','EPSG','29873','EPSG','1362','EPSG','1149');
+INSERT INTO "projected_crs" VALUES('EPSG','29874','Timbalai 1948 / RSO Sarawak LSD (m)',NULL,'EPSG','4400','EPSG','4298','EPSG','19838',NULL,0);
+INSERT INTO "usage" VALUES('EPSG','14400','projected_crs','EPSG','29874','EPSG','4611','EPSG','1142');
INSERT INTO "projected_crs" VALUES('EPSG','29900','TM65 / Irish National Grid',NULL,'EPSG','4400','EPSG','4299','EPSG','19908',NULL,1);
INSERT INTO "usage" VALUES('EPSG','6988','projected_crs','EPSG','29900','EPSG','1305','EPSG','1142');
INSERT INTO "projected_crs" VALUES('EPSG','29901','OSNI 1952 / Irish National Grid',NULL,'EPSG','4400','EPSG','4188','EPSG','19973',NULL,0);
diff --git a/data/sql/scope.sql b/data/sql/scope.sql
index 9f314f37..58fc0779 100644
--- a/data/sql/scope.sql
+++ b/data/sql/scope.sql
@@ -246,3 +246,4 @@ INSERT INTO "scope" VALUES('EPSG','1266','Engineering survey, topographic mappin
INSERT INTO "scope" VALUES('EPSG','1267','Location-based services, Intelligent Transport Services, navigation, positioning.',0);
INSERT INTO "scope" VALUES('EPSG','1268','Geodesy, location based services, intelligent transport services.',0);
INSERT INTO "scope" VALUES('EPSG','1269','Intermediate CRS in transformation to and from projected CRS.',0);
+INSERT INTO "scope" VALUES('EPSG','1273','Transformation of coordinates at 0.03m level of accuracy.',0);
diff --git a/data/sql/vertical_crs.sql b/data/sql/vertical_crs.sql
index ab265193..291ee40f 100644
--- a/data/sql/vertical_crs.sql
+++ b/data/sql/vertical_crs.sql
@@ -453,9 +453,9 @@ INSERT INTO "usage" VALUES('EPSG','13922','vertical_crs','EPSG','9335','EPSG','3
INSERT INTO "vertical_crs" VALUES('EPSG','9351','NGNC08 height',NULL,'EPSG','6499','EPSG','1255',0);
INSERT INTO "usage" VALUES('EPSG','13977','vertical_crs','EPSG','9351','EPSG','3430','EPSG','1026');
INSERT INTO "vertical_crs" VALUES('EPSG','9389','EVRF2019 height',NULL,'EPSG','6499','EPSG','1274',0);
-INSERT INTO "usage" VALUES('EPSG','14080','vertical_crs','EPSG','9389','EPSG','4608','EPSG','1261');
+INSERT INTO "usage" VALUES('EPSG','14658','vertical_crs','EPSG','9389','EPSG','4608','EPSG','1261');
INSERT INTO "vertical_crs" VALUES('EPSG','9390','EVRF2019 mean-tide height',NULL,'EPSG','6499','EPSG','1287',0);
-INSERT INTO "usage" VALUES('EPSG','14081','vertical_crs','EPSG','9390','EPSG','4608','EPSG','1262');
+INSERT INTO "usage" VALUES('EPSG','14659','vertical_crs','EPSG','9390','EPSG','4608','EPSG','1262');
INSERT INTO "vertical_crs" VALUES('EPSG','9392','Mallorca height',NULL,'EPSG','6499','EPSG','1275',0);
INSERT INTO "usage" VALUES('EPSG','14031','vertical_crs','EPSG','9392','EPSG','4602','EPSG','1178');
INSERT INTO "vertical_crs" VALUES('EPSG','9393','Menorca height',NULL,'EPSG','6499','EPSG','1276',0);
@@ -484,3 +484,5 @@ INSERT INTO "vertical_crs" VALUES('EPSG','9458','AVWS height',NULL,'EPSG','6499'
INSERT INTO "usage" VALUES('EPSG','14231','vertical_crs','EPSG','9458','EPSG','4177','EPSG','1264');
INSERT INTO "vertical_crs" VALUES('EPSG','9471','INAGeoid2020 height',NULL,'EPSG','6499','EPSG','1294',0);
INSERT INTO "usage" VALUES('EPSG','14153','vertical_crs','EPSG','9471','EPSG','1122','EPSG','1178');
+INSERT INTO "vertical_crs" VALUES('EPSG','9675','Pago Pago 2020 height',NULL,'EPSG','6499','EPSG','1302',0);
+INSERT INTO "usage" VALUES('EPSG','14793','vertical_crs','EPSG','9675','EPSG','2288','EPSG','1026');
diff --git a/data/sql/vertical_datum.sql b/data/sql/vertical_datum.sql
index 9bb855cb..e1fff6e3 100644
--- a/data/sql/vertical_datum.sql
+++ b/data/sql/vertical_datum.sql
@@ -176,8 +176,8 @@ INSERT INTO "vertical_datum" VALUES('EPSG','1269','Kingdom of Saudi Arabia Verti
INSERT INTO "usage" VALUES('EPSG','13897','vertical_datum','EPSG','1269','EPSG','3303','EPSG','1181');
INSERT INTO "vertical_datum" VALUES('EPSG','1270','Mean Sea Level Netherlands',NULL,NULL,NULL,NULL,0);
INSERT INTO "usage" VALUES('EPSG','14119','vertical_datum','EPSG','1270','EPSG','1630','EPSG','1265');
-INSERT INTO "vertical_datum" VALUES('EPSG','1274','European Vertical Reference Frame 2019',NULL,'2019-05-22',NULL,NULL,0);
-INSERT INTO "usage" VALUES('EPSG','14078','vertical_datum','EPSG','1274','EPSG','4608','EPSG','1261');
+INSERT INTO "vertical_datum" VALUES('EPSG','1274','European Vertical Reference Frame 2019',NULL,'2020-09-01',NULL,NULL,0);
+INSERT INTO "usage" VALUES('EPSG','14738','vertical_datum','EPSG','1274','EPSG','4608','EPSG','1261');
INSERT INTO "vertical_datum" VALUES('EPSG','1275','Mallorca',NULL,NULL,NULL,NULL,0);
INSERT INTO "usage" VALUES('EPSG','14014','vertical_datum','EPSG','1275','EPSG','4602','EPSG','1178');
INSERT INTO "vertical_datum" VALUES('EPSG','1276','Menorca',NULL,NULL,NULL,NULL,0);
@@ -200,14 +200,16 @@ INSERT INTO "vertical_datum" VALUES('EPSG','1284','El Hierro',NULL,NULL,NULL,NUL
INSERT INTO "usage" VALUES('EPSG','14023','vertical_datum','EPSG','1284','EPSG','4597','EPSG','1178');
INSERT INTO "vertical_datum" VALUES('EPSG','1285','Ceuta 2',NULL,NULL,NULL,NULL,0);
INSERT INTO "usage" VALUES('EPSG','14024','vertical_datum','EPSG','1285','EPSG','4590','EPSG','1178');
-INSERT INTO "vertical_datum" VALUES('EPSG','1287','European Vertical Reference Frame 2019 mean tide',NULL,'2019-05-22',NULL,NULL,0);
-INSERT INTO "usage" VALUES('EPSG','14079','vertical_datum','EPSG','1287','EPSG','4608','EPSG','1262');
+INSERT INTO "vertical_datum" VALUES('EPSG','1287','European Vertical Reference Frame 2019 mean tide',NULL,'2020-09-01',NULL,NULL,0);
+INSERT INTO "usage" VALUES('EPSG','14739','vertical_datum','EPSG','1287','EPSG','4608','EPSG','1262');
INSERT INTO "vertical_datum" VALUES('EPSG','1290','Lowest Astronomical Tide Netherlands',NULL,NULL,NULL,NULL,0);
INSERT INTO "usage" VALUES('EPSG','14120','vertical_datum','EPSG','1290','EPSG','1630','EPSG','1198');
INSERT INTO "vertical_datum" VALUES('EPSG','1292','Australian Vertical Working Surface',NULL,'2020-07-14',NULL,NULL,0);
INSERT INTO "usage" VALUES('EPSG','14134','vertical_datum','EPSG','1292','EPSG','4177','EPSG','1264');
INSERT INTO "vertical_datum" VALUES('EPSG','1294','Indonesian Geoid 2020',NULL,'2020-01-01',NULL,NULL,0);
INSERT INTO "usage" VALUES('EPSG','14149','vertical_datum','EPSG','1294','EPSG','1122','EPSG','1178');
+INSERT INTO "vertical_datum" VALUES('EPSG','1302','Local Tidal Datum at Pago Pago 2020',NULL,NULL,NULL,NULL,0);
+INSERT INTO "usage" VALUES('EPSG','14795','vertical_datum','EPSG','1302','EPSG','2288','EPSG','1026');
INSERT INTO "vertical_datum" VALUES('EPSG','5100','Mean Sea Level',NULL,NULL,NULL,NULL,0);
INSERT INTO "usage" VALUES('EPSG','13307','vertical_datum','EPSG','5100','EPSG','1262','EPSG','1199');
INSERT INTO "vertical_datum" VALUES('EPSG','5101','Ordnance Datum Newlyn',NULL,'1956-01-01',NULL,NULL,0);
@@ -437,6 +439,6 @@ INSERT INTO "usage" VALUES('EPSG','13419','vertical_datum','EPSG','5213','EPSG',
INSERT INTO "vertical_datum" VALUES('EPSG','5214','IGN 1988 SM',NULL,'1988-01-01',NULL,NULL,0);
INSERT INTO "usage" VALUES('EPSG','13420','vertical_datum','EPSG','5214','EPSG','2890','EPSG','1178');
INSERT INTO "vertical_datum" VALUES('EPSG','5215','European Vertical Reference Frame 2007',NULL,'2008-01-01',NULL,NULL,0);
-INSERT INTO "usage" VALUES('EPSG','13421','vertical_datum','EPSG','5215','EPSG','3594','EPSG','1027');
+INSERT INTO "usage" VALUES('EPSG','14655','vertical_datum','EPSG','5215','EPSG','3594','EPSG','1027');
INSERT INTO "vertical_datum" VALUES('EPSG','1288','British Isles height ensemble',NULL,NULL,NULL,0.4,0);
INSERT INTO "usage" VALUES('EPSG','14086','vertical_datum','EPSG','1288','EPSG','4606','EPSG','1026');
diff --git a/data/sql_filelist.cmake b/data/sql_filelist.cmake
index a21e8630..e7bca4ff 100644
--- a/data/sql_filelist.cmake
+++ b/data/sql_filelist.cmake
@@ -35,5 +35,6 @@ set(SQL_FILES
"${SQL_DIR}/grid_alternatives.sql"
"${SQL_DIR}/grid_alternatives_generated_noaa.sql"
"${SQL_DIR}/customizations.sql"
+ "${SQL_DIR}/nkg.sql"
"${SQL_DIR}/commit.sql"
)
diff --git a/docs/source/apps/cs2cs.rst b/docs/source/apps/cs2cs.rst
index 7df8890f..4706459a 100644
--- a/docs/source/apps/cs2cs.rst
+++ b/docs/source/apps/cs2cs.rst
@@ -13,7 +13,7 @@ Synopsis
| **cs2cs** [**-eEfIlrstvwW** [args]]
| [[--area <name_or_code>] | [--bbox <west_long,south_lat,east_long,north_lat>]]
- | [--authority <name>]
+ | [--authority <name>] [--no-ballpark] [--accuracy <accuracy>]
| ([*+opt[=arg]* ...] [+to *+opt[=arg]* ...] | {source_crs} {target_crs})
| file ...
@@ -166,6 +166,19 @@ The following control parameters can appear in any order:
`south_lat` and `north_lat` in the [-90,90]. `west_long` is generally lower than
`east_long`, except in the case where the area of interest crosses the antimeridian.
+.. option:: --no-ballpark
+
+ .. versionadded:: 8.0.0
+
+ Disallow any coordinate operation that is, or contains, a
+ :term:`Ballpark transformation`
+
+.. option:: --accuracy <accuracy>
+
+ .. versionadded:: 8.0.0
+
+ Sets the minimum desired accuracy for candidate coordinate operations.
+
.. option:: --authority <name>
.. versionadded:: 8.0.0
diff --git a/docs/source/apps/projinfo.rst b/docs/source/apps/projinfo.rst
index 803c0a65..f0f66620 100644
--- a/docs/source/apps/projinfo.rst
+++ b/docs/source/apps/projinfo.rst
@@ -22,7 +22,7 @@ Synopsis
| [--crs-extent-use none|both|intersection|smallest]
| [--grid-check none|discard_missing|sort|known_available]
| [--pivot-crs always|if_no_direct_transformation|never|{auth:code[,auth:code]*}]
- | [--show-superseded] [--hide-ballpark]
+ | [--show-superseded] [--hide-ballpark] [--accuracy {accuracy}]
| [--allow-ellipsoidal-height-as-vertical-crs]
| [--boundcrs-to-wgs84]
| [--main-db-path path] [--aux-db-path path]*
@@ -213,6 +213,14 @@ The following control parameters can appear in any order:
.. note:: only used for coordinate operation computation
+.. option:: --accuracy {accuracy}
+
+ .. versionadded:: 8.0
+
+ Sets the minimum desired accuracy for returned coordinate operations.
+
+ .. note:: only used for coordinate operation computation
+
.. option:: --allow-ellipsoidal-height-as-vertical-crs
.. versionadded:: 8.0
diff --git a/docs/source/development/reference/datatypes.rst b/docs/source/development/reference/datatypes.rst
index 8e0141df..ed219daf 100644
--- a/docs/source/development/reference/datatypes.rst
+++ b/docs/source/development/reference/datatypes.rst
@@ -804,6 +804,102 @@ Info structures
Date of last update of the init-file.
+.. _error_codes:
+
+Error codes
+-----------
+
+.. versionadded:: 8.0.0
+
+Three classes of errors are defined below. The belonging of a given error
+code to a class can bit tested with a binary and test. The error class itself
+can be used as an error value in some rare cases where the error does not
+fit into a more precise error value.
+
+Those error codes are still quite generic for a number of them. Details on the
+actual errors will be typically logged with the PJ_LOG_ERROR level.
+
+Errors in class PROJ_ERR_INVALID_OP
++++++++++++++++++++++++++++++++++++
+
+.. c:macro:: PROJ_ERR_INVALID_OP
+
+ Class of error codes typically related to coordinate operation initalization,
+ typically when creating a PJ* object from a PROJ string.
+
+ .. note:: some of them can also be emitted during coordinate transformation,
+ like PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID in case the resource loading
+ is differed until it is really needed.
+
+.. c:macro:: PROJ_ERR_INVALID_OP_WRONG_SYNTAX
+
+ Invalid pipeline structure, missing +proj argument, etc.
+
+.. c:macro:: PROJ_ERR_INVALID_OP_MISSING_ARG
+
+ Missing required operation parameter
+
+.. c:macro:: PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE
+
+ One of the operation parameter has an illegal value.
+
+.. c:macro:: PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS
+
+ Mutually exclusive arguments
+
+.. c:macro:: PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID
+
+ File not found or with invalid content (particular case of PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE)
+
+Errors in class PROJ_ERR_COORD_TRANSFM
+++++++++++++++++++++++++++++++++++++++
+
+.. c:macro:: PROJ_ERR_COORD_TRANSFM
+
+ Class of error codes related to transformation on a specific coordinate.
+
+.. c:macro:: PROJ_ERR_COORD_TRANSFM_INVALID_COORD
+
+ Invalid input coordinate. e.g a latitude > 90°.
+
+.. c:macro:: PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN
+
+ Coordinate is outside of the projection domain. e.g approximate mercator with \|longitude - lon_0\| > 90°,
+ or iterative convergence method failed.
+
+.. c:macro:: PROJ_ERR_COORD_TRANSFM_NO_OPERATION
+
+ No operation found, e.g if no match the required accuracy, or if ballpark transformations
+ were asked to not be used and they would be only such candidate.
+
+.. c:macro:: PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID
+
+ Point to transform falls outside grid/subgrid/TIN.
+
+.. c:macro:: PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA
+
+ Point to transform falls in a grid cell that evaluates to nodata.
+
+Errors in class PROJ_ERR_OTHER
+++++++++++++++++++++++++++++++
+
+.. c:macro:: PROJ_ERR_OTHER
+
+ Class of error codes that do not fit into one of the above class.
+
+.. c:macro:: PROJ_ERR_OTHER_API_MISUSE
+
+ Error related to a misuse of PROJ API.
+
+.. c:macro:: PROJ_ERR_OTHER_NO_INVERSE_OP
+
+ No inverse method available
+
+.. c:macro:: PROJ_ERR_OTHER_NETWORK_ERROR
+
+ Failure when accessing a network resource.
+
+
Logging
-------------------------------------------------------------------------------
diff --git a/docs/source/development/reference/functions.rst b/docs/source/development/reference/functions.rst
index b37eacdd..34fe59ed 100644
--- a/docs/source/development/reference/functions.rst
+++ b/docs/source/development/reference/functions.rst
@@ -195,6 +195,11 @@ paragraph for more details.
If authority is a non-empty string different of ``any``, then coordinate operations
will be searched only in that authority namespace (e.g ``EPSG``).
+ - ACCURACY=value: to set the minimum desired accuracy (in metres) of the
+ candidate coordinate operations.
+
+ - ALLOW_BALLPARK=YES/NO: can be set to NO to disallow the use of
+ :term:`Ballpark transformation` in the candidate coordinate operations.
.. doxygenfunction:: proj_normalize_for_visualization
:project: doxygen_api
@@ -368,13 +373,22 @@ Coordinate transformation
Batch transform an array of :c:type:`PJ_COORD`.
+ Performs transformation on all points, even if errors occur on some points
+ (new to 8.0. Previous versions would exit early in case of failure on a given point)
+
+ Individual points that fail to transform will have their components set to
+ ``HUGE_VAL``
+
:param P: Transformation object
:type P: :c:type:`PJ` *
:param `direction`: Transformation direction.
:type `direction`: PJ_DIRECTION
:param n: Number of coordinates in :c:data:`coord`
:type n: `size_t`
- :returns: `int` 0 if all observations are transformed without error, otherwise returns error number
+ :returns: `int` 0 if all observations are transformed without error, otherwise returns error number.
+ This error number will be a precise error number if all coordinates that fail to transform
+ for the same reason, or a generic error code if they fail for different
+ reasons.
Error reporting
@@ -388,6 +402,8 @@ Error reporting
context is read. A text representation of the error number can be retrieved
with :c:func:`proj_errno_string`.
+ Consult :ref:`error_codes` for the list of error codes (PROJ >= 8.0)
+
:param P: Transformation object
:type P: :c:type:`PJ` *
@@ -400,6 +416,8 @@ Error reporting
transformation. A text representation of the error number can be retrieved
with :c:func:`proj_errno_string`.
+ Consult :ref:`error_codes` for the list of error codes (PROJ >= 8.0)
+
:param ctx: threading context.
:type ctx: :c:type:`PJ_CONTEXT` *
@@ -464,6 +482,22 @@ Error reporting
Get a text representation of an error number.
+ .. deprecated:: This function is potentially thread-unsafe, replaced by :c:func:`proj_context_errno_string`.
+
+ :param err: Error number.
+ :type err: `int`
+
+ :returns: `const char*` String with description of error.
+
+.. c:function:: const char* proj_context_errno_string(PJ_CONTEXT* ctx, int err)
+
+ .. versionadded:: 8.0.0
+
+ Get a text representation of an error number.
+
+ :param ctx: threading context.
+ :type ctx: :c:type:`PJ_CONTEXT` *
+
:param err: Error number.
:type err: `int`
diff --git a/docs/source/resource_files.rst b/docs/source/resource_files.rst
index ae3a808d..22edbc90 100644
--- a/docs/source/resource_files.rst
+++ b/docs/source/resource_files.rst
@@ -151,7 +151,7 @@ proj-datumgrid
series of packages are not maintained anymore and are only kept available for
legacy purposes.
-For a functioning builds of PROJ prior to version 7, installation of the
+For a functioning build of PROJ prior to version 7, installation of the
`proj-datumgrid <https://github.com/OSGeo/proj-datumgrid>`_ is needed. If you
have installed PROJ from a package system chances are that this will already be
done for you. The *proj-datumgrid* package provides transformation grids that
@@ -197,7 +197,7 @@ includes grids that have global extent, e.g. the global geoid model EGM08.
All packages above come in different versions, e.g proj-datumgrid-1.8 or
proj-datumgrid-europe-1.4. The `-latest` packages are symbolic links to the
-latest version of a given packages. That means that the link
+latest version of a given package. That means that the link
https://download.osgeo.org/proj/proj-datumgrid-north-america-latest.zip is
equivalent to https://download.osgeo.org/proj/proj-datumgrid-north-america-1.2.zip
(as of the time of writing this).
@@ -278,7 +278,7 @@ than one foot over the last two decades).
Getting and building HTDP
................................................................................
-The HTDP modelling program is in written FORTRAN. The source and documentation
+The HTDP modelling program is written in FORTRAN. The source and documentation
can be found on the HTDP page at http://www.ngs.noaa.gov/TOOLS/Htdp/Htdp.shtml
On linux systems it will be necessary to install `gfortran` or some FORTRAN
@@ -322,10 +322,10 @@ Usage
[-htdp <path_to_exe>] [-wrkdir <dirpath>] [-kwf]
-o <output_grid_name>
- -griddef: by default the following values for roughly the continental USA
- at a six minute step size are used:
- -127 50 -66 25 251 611
- -kwf: keep working files in the working directory for review.
+ -griddef: by default the following values for roughly the continental USA
+ at a six minute step size are used:
+ -127 50 -66 25 251 611
+ -kwf: keep working files in the working directory for review.
::
diff --git a/include/proj/internal/Makefile.am b/include/proj/internal/Makefile.am
index ba14c2a5..8e9edacf 100644
--- a/include/proj/internal/Makefile.am
+++ b/include/proj/internal/Makefile.am
@@ -1,9 +1,6 @@
SUBDIRS = nlohmann
noinst_HEADERS = \
- coordinateoperation_constants.hpp \
- coordinateoperation_internal.hpp \
- esri_projection_mappings.hpp \
coordinatesystem_internal.hpp \
internal.hpp \
io_internal.hpp \
diff --git a/scripts/build_esri_projection_mapping.py b/scripts/build_esri_projection_mapping.py
index 00a49c68..752f9850 100644
--- a/scripts/build_esri_projection_mapping.py
+++ b/scripts/build_esri_projection_mapping.py
@@ -751,7 +751,19 @@ def generate_mapping(WKT2_name, esri_proj_name, Params, suffix=''):
all_projs.append([esri_proj_name, WKT2_name_s, c_name])
else:
all_projs.append([esri_proj_name, WKT2_name, c_name])
- print('static const ESRIParamMapping %s[] = { ' % c_name)
+
+ qualifier = 'static '
+ if c_name in ('paramsESRI_Plate_Carree',
+ 'paramsESRI_Equidistant_Cylindrical',
+ 'paramsESRI_Gauss_Kruger',
+ 'paramsESRI_Transverse_Mercator',
+ 'paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin',
+ 'paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin',
+ 'paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center',
+ 'paramsESRI_Rectified_Skew_Orthomorphic_Center'):
+ qualifier = ''
+
+ print(qualifier + 'const ESRIParamMapping %s[] = { ' % c_name)
for param in Params:
for param_name in param:
param_value = param[param_name]
@@ -803,24 +815,22 @@ print("""
* DEALINGS IN THE SOFTWARE.
****************************************************************************/
-#ifndef FROM_COORDINATE_OPERATION_CPP
-#error This file should only be included from coordinateoperation.cpp
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
#endif
-#ifndef ESRI_PROJECTION_MAPPINGS_HH_INCLUDED
-#define ESRI_PROJECTION_MAPPINGS_HH_INCLUDED
+#include "esriparammappings.hpp"
+#include "proj_constants.h"
-#include "coordinateoperation_internal.hpp"
+#include "proj/internal/internal.hpp"
-//! @cond Doxygen_Suppress
+NS_PROJ_START
-// ---------------------------------------------------------------------------
+using namespace internal;
-// anonymous namespace
-namespace {
+namespace operation {
-using namespace ::NS_PROJ;
-using namespace ::NS_PROJ::operation;
+//! @cond Doxygen_Suppress
""")
@@ -841,6 +851,7 @@ for item in config:
count += 1
print('')
+
print('static const ESRIMethodMapping esriMappings[] = {')
for esri_proj_name, WKT2_name, c_name in all_projs:
if WKT2_name.startswith('EPSG_'):
@@ -852,11 +863,32 @@ for esri_proj_name, WKT2_name, c_name in all_projs:
print('};')
print("""
+
// ---------------------------------------------------------------------------
-} // namespace {
+const ESRIMethodMapping *getEsriMappings(size_t &nElts) {
+ nElts = sizeof(esriMappings) / sizeof(esriMappings[0]);
+ return esriMappings;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<const ESRIMethodMapping *>
+getMappingsFromESRI(const std::string &esri_name) {
+ std::vector<const ESRIMethodMapping *> res;
+ for (const auto &mapping : esriMappings) {
+ if (ci_equal(esri_name, mapping.esri_name)) {
+ res.push_back(&mapping);
+ }
+ }
+ return res;
+}
//! @endcond
-#endif // ESRI_PROJECTION_MAPPINGS_HH_INCLUDED
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
""")
diff --git a/scripts/doxygen.sh b/scripts/doxygen.sh
index fd3a0468..f169c78e 100755
--- a/scripts/doxygen.sh
+++ b/scripts/doxygen.sh
@@ -38,7 +38,7 @@ rm -rf docs/build/xml/
# Ugly hack to workaround a bug of Doxygen 1.8.17 that erroneously detect proj_network_get_header_value_cbk_type/ as a variable
sed "s/const char\* (\*proj_network_get_header_value_cbk_type/CONST_CHAR\* (\*proj_network_get_header_value_cbk_type/" < src/proj.h > docs/build/tmp_breathe/proj.h
-(cat Doxyfile; printf "GENERATE_HTML=NO\nGENERATE_XML=YES\nINPUT= src/iso19111 include/proj docs/build/tmp_breathe/proj.h src/filemanager.cpp src/networkfilemanager.cpp docs/build/tmp_breathe/general_doc.dox.reworked.h") | doxygen - > docs/build/tmp_breathe/docs_log.txt 2>&1
+(cat Doxyfile; printf "GENERATE_HTML=NO\nGENERATE_XML=YES\nINPUT= src/iso19111 src/iso19111/operation include/proj docs/build/tmp_breathe/proj.h src/filemanager.cpp src/networkfilemanager.cpp docs/build/tmp_breathe/general_doc.dox.reworked.h") | doxygen - > docs/build/tmp_breathe/docs_log.txt 2>&1
if grep -i warning docs/build/tmp_breathe/docs_log.txt; then
echo "Doxygen warnings found" && cat docs/build/tmp_breathe/docs_log.txt && /bin/false;
else
diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt
index 167b8a27..a5fd41ca 100644
--- a/scripts/reference_exported_symbols.txt
+++ b/scripts/reference_exported_symbols.txt
@@ -784,6 +784,7 @@ proj_context_create
proj_context_delete_cpp_context(projCppContext*)
proj_context_destroy
proj_context_errno
+proj_context_errno_string
proj_context_get_database_metadata
proj_context_get_database_path
proj_context_get_url_endpoint
@@ -992,7 +993,7 @@ proj_list_get_count
proj_list_operations
proj_list_prime_meridians
proj_list_units
-proj_log_error(PJconsts*, char const*, ...)
+proj_log_error(PJconsts const*, char const*, ...)
proj_log_func
proj_log_level
proj_lp_dist
diff --git a/scripts/reformat_cpp.sh b/scripts/reformat_cpp.sh
index 50a572a1..89237b65 100755
--- a/scripts/reformat_cpp.sh
+++ b/scripts/reformat_cpp.sh
@@ -16,7 +16,10 @@ esac
TOPDIR="$SCRIPT_DIR/.."
for i in "$TOPDIR"/include/proj/*.hpp "$TOPDIR"/include/proj/internal/*.hpp \
- "$TOPDIR"/src/iso19111/*.cpp "$TOPDIR"/test/unit/*.cpp \
+ "$TOPDIR"/src/iso19111/*.cpp \
+ "$TOPDIR"/src/iso19111/operation/*.cpp \
+ "$TOPDIR"/src/iso19111/operation/*.hpp \
+ "$TOPDIR"/test/unit/*.cpp \
"$TOPDIR"/src/apps/projinfo.cpp "$TOPDIR"/src/apps/projsync.cpp \
"$TOPDIR"/src/tracing.cpp "$TOPDIR"/src/grids.hpp "$TOPDIR"/src/grids.cpp \
"$TOPDIR"/src/filemanager.hpp "$TOPDIR"/src/filemanager.cpp \
diff --git a/src/4D_api.cpp b/src/4D_api.cpp
index d6eb901d..909c3c32 100644
--- a/src/4D_api.cpp
+++ b/src/4D_api.cpp
@@ -176,7 +176,8 @@ double proj_roundtrip (PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord) {
return HUGE_VAL;
if (n < 1) {
- proj_errno_set (P, EINVAL);
+ proj_log_error(P, _("n should be >= 1"));
+ proj_errno_set (P, PROJ_ERR_OTHER_API_MISUSE);
return HUGE_VAL;
}
@@ -294,7 +295,7 @@ similarly, but prefers the 2D resp. 3D interfaces if available.
if( iRetry > 0 ) {
const int oldErrno = proj_errno_reset(P);
if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) {
- pj_log(P->ctx, PJ_LOG_DEBUG, proj_errno_string(oldErrno));
+ pj_log(P->ctx, PJ_LOG_DEBUG, proj_context_errno_string(P->ctx, oldErrno));
}
pj_log(P->ctx, PJ_LOG_DEBUG,
"Did not result in valid result. "
@@ -312,7 +313,7 @@ similarly, but prefers the 2D resp. 3D interfaces if available.
}
PJ_COORD res = direction == PJ_FWD ?
pj_fwd4d( coord, alt.pj ) : pj_inv4d( coord, alt.pj );
- if( proj_errno(alt.pj) == PJD_ERR_NETWORK_ERROR ) {
+ if( proj_errno(alt.pj) == PROJ_ERR_OTHER_NETWORK_ERROR ) {
return proj_coord_error ();
}
if( res.xyzt.x != HUGE_VAL ) {
@@ -359,21 +360,14 @@ similarly, but prefers the 2D resp. 3D interfaces if available.
}
}
- proj_errno_set (P, EINVAL);
+ proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_NO_OPERATION);
return proj_coord_error ();
}
- switch (direction) {
- case PJ_FWD:
- return pj_fwd4d (coord, P);
- case PJ_INV:
- return pj_inv4d (coord, P);
- default:
- break;
- }
-
- proj_errno_set (P, EINVAL);
- return proj_coord_error ();
+ if (direction == PJ_FWD)
+ return pj_fwd4d (coord, P);
+ else
+ return pj_inv4d (coord, P);
}
@@ -383,18 +377,43 @@ int proj_trans_array (PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord)
/******************************************************************************
Batch transform an array of PJ_COORD.
+ Performs transformation on all points, even if errors occur on some points.
+
+ Individual points that fail to transform will have their components set to
+ HUGE_VAL
+
Returns 0 if all coordinates are transformed without error, otherwise
- returns error number.
+ returns a precise error number if all coordinates that fail to transform
+ for the same reason, or a generic error code if they fail for different
+ reasons.
******************************************************************************/
size_t i;
+ int retErrno = 0;
+ bool hasSetRetErrno = false;
+ bool sameRetErrno = true;
for (i = 0; i < n; i++) {
+ proj_context_errno_set(P->ctx, 0);
coord[i] = proj_trans (P, direction, coord[i]);
- if (proj_errno(P))
- return proj_errno (P);
- }
+ int thisErrno = proj_errno(P);
+ if( thisErrno != 0 )
+ {
+ if( !hasSetRetErrno )
+ {
+ retErrno = thisErrno;
+ hasSetRetErrno = true;
+ }
+ else if( sameRetErrno && retErrno != thisErrno )
+ {
+ sameRetErrno = false;
+ retErrno = PROJ_ERR_COORD_TRANSFM;
+ }
+ }
+ }
- return 0;
+ proj_context_errno_set(P->ctx, retErrno);
+
+ return retErrno;
}
@@ -500,9 +519,6 @@ size_t proj_trans_generic (
break;
case PJ_IDENT:
return nmin;
- default:
- proj_errno_set (P, EINVAL);
- return 0;
}
/* Arrays of length==0 are broadcast as the constant 0 */
@@ -771,7 +787,7 @@ PJ *pj_create_internal (PJ_CONTEXT *ctx, const char *definition) {
n = strlen (definition);
args = (char *) malloc (n + 1);
if (nullptr==args) {
- proj_context_errno_set(ctx, ENOMEM);
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/);
return nullptr;
}
strcpy (args, definition);
@@ -779,14 +795,14 @@ PJ *pj_create_internal (PJ_CONTEXT *ctx, const char *definition) {
argc = pj_trim_argc (args);
if (argc==0) {
free (args);
- proj_context_errno_set(ctx, PJD_ERR_NO_ARGS);
+ proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG);
return nullptr;
}
argv = pj_trim_argv (argc, args);
if (!argv) {
free(args);
- proj_context_errno_set(ctx, ENOMEM);
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/);
return nullptr;
}
@@ -821,14 +837,14 @@ indicator, as in {"+proj=utm", "+zone=32"}, or leave it out, as in {"proj=utm",
if (nullptr==ctx)
ctx = pj_get_default_ctx ();
if (nullptr==argv) {
- proj_context_errno_set(ctx, PJD_ERR_NO_ARGS);
+ proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG);
return nullptr;
}
/* We assume that free format is used, and build a full proj_create compatible string */
c = pj_make_args (argc, argv);
if (nullptr==c) {
- proj_context_errno_set(ctx, ENOMEM);
+ proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP /* ENOMEM */);
return nullptr;
}
@@ -849,14 +865,14 @@ Same as proj_create_argv() but calls pj_create_internal() instead of proj_create
if (nullptr==ctx)
ctx = pj_get_default_ctx ();
if (nullptr==argv) {
- proj_context_errno_set(ctx, PJD_ERR_NO_ARGS);
+ proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG);
return nullptr;
}
/* We assume that free format is used, and build a full proj_create compatible string */
c = pj_make_args (argc, argv);
if (nullptr==c) {
- proj_context_errno_set(ctx, ENOMEM);
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/);
return nullptr;
}
@@ -1286,10 +1302,24 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons
}
const char* authority = nullptr;
+ double accuracy = -1;
+ bool allowBallparkTransformations = true;
for (auto iter = options; iter && iter[0]; ++iter) {
const char *value;
if ((value = getOptionValue(*iter, "AUTHORITY="))) {
authority = value;
+ } else if ((value = getOptionValue(*iter, "ACCURACY="))) {
+ accuracy = pj_atof(value);
+ } else if ((value = getOptionValue(*iter, "ALLOW_BALLPARK="))) {
+ if( ci_equal(value, "yes") )
+ allowBallparkTransformations = true;
+ else if( ci_equal(value, "no") )
+ allowBallparkTransformations = false;
+ else {
+ ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR,
+ "Invalid value for ALLOW_BALLPARK option.");
+ return nullptr;
+ }
} else {
std::string msg("Unknown option :");
msg += *iter;
@@ -1303,6 +1333,14 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons
return nullptr;
}
+ proj_operation_factory_context_set_allow_ballpark_transformations(
+ ctx, operation_ctx, allowBallparkTransformations);
+
+ if( accuracy >= 0 ) {
+ proj_operation_factory_context_set_desired_accuracy(ctx, operation_ctx,
+ accuracy);
+ }
+
if( area && area->bbox_set ) {
proj_operation_factory_context_set_area_of_interest(
ctx,
diff --git a/src/Makefile.am b/src/Makefile.am
index 83ee4adc..16eca5de 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -63,7 +63,23 @@ libproj_la_SOURCES = \
iso19111/crs.cpp \
iso19111/datum.cpp \
iso19111/coordinatesystem.cpp \
- iso19111/coordinateoperation.cpp \
+ iso19111/operation/concatenatedoperation.cpp \
+ iso19111/operation/coordinateoperation_internal.hpp \
+ iso19111/operation/coordinateoperation_private.hpp \
+ iso19111/operation/coordinateoperationfactory.cpp \
+ iso19111/operation/conversion.cpp \
+ iso19111/operation/esriparammappings.hpp \
+ iso19111/operation/esriparammappings.cpp \
+ iso19111/operation/operationmethod_private.hpp \
+ iso19111/operation/oputils.hpp \
+ iso19111/operation/oputils.cpp \
+ iso19111/operation/parammappings.hpp \
+ iso19111/operation/parammappings.cpp \
+ iso19111/operation/projbasedoperation.cpp \
+ iso19111/operation/singleoperation.cpp \
+ iso19111/operation/transformation.cpp \
+ iso19111/operation/vectorofvaluesparams.hpp \
+ iso19111/operation/vectorofvaluesparams.cpp \
iso19111/io.cpp \
iso19111/internal.cpp \
iso19111/factory.cpp \
diff --git a/src/aasincos.cpp b/src/aasincos.cpp
index c4314c67..ca33663b 100644
--- a/src/aasincos.cpp
+++ b/src/aasincos.cpp
@@ -14,7 +14,7 @@ aasin(PJ_CONTEXT *ctx,double v) {
if ((av = fabs(v)) >= 1.) {
if (av > ONE_TOL)
- proj_context_errno_set( ctx, PJD_ERR_ACOS_ASIN_ARG_TOO_LARGE );
+ proj_context_errno_set( ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
return (v < 0. ? -M_HALFPI : M_HALFPI);
}
return asin(v);
@@ -26,7 +26,7 @@ aacos(PJ_CONTEXT *ctx, double v) {
if ((av = fabs(v)) >= 1.) {
if (av > ONE_TOL)
- proj_context_errno_set( ctx, PJD_ERR_ACOS_ASIN_ARG_TOO_LARGE );
+ proj_context_errno_set( ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
return (v < 0. ? M_PI : 0.);
}
return acos(v);
diff --git a/src/apps/cs2cs.cpp b/src/apps/cs2cs.cpp
index 409a5ef3..5542a282 100644
--- a/src/apps/cs2cs.cpp
+++ b/src/apps/cs2cs.cpp
@@ -38,6 +38,7 @@
#include <cassert>
#include <iostream>
#include <string>
+#include <vector>
#include <proj/io.hpp>
#include <proj/metadata.hpp>
@@ -77,7 +78,7 @@ static const char *oterr = "*\t*"; /* output line for unprojectable input */
static const char *usage =
"%s\nusage: %s [-dDeEfIlrstvwW [args]]\n"
" [[--area name_or_code] | [--bbox west_long,south_lat,east_long,north_lat]]\n"
- " [--authority {name}]\n"
+ " [--authority {name}] [--accuracy {accuracy}] [--no-ballpark]\n"
" [+opt[=arg] ...] [+to +opt[=arg] ...] [file ...]\n";
static double (*informat)(const char *,
@@ -374,6 +375,8 @@ int main(int argc, char **argv) {
ExtentPtr bboxFilter;
std::string area;
const char* authority = nullptr;
+ double accuracy = -1;
+ bool allowBallpark = true;
/* process run line arguments */
while (--argc > 0) { /* collect run line arguments */
@@ -412,6 +415,15 @@ int main(int argc, char **argv) {
std::exit(1);
}
}
+ else if (strcmp(*argv, "--accuracy") == 0 ) {
+ ++argv;
+ --argc;
+ if( argc == 0 ) {
+ emess(1, "missing argument for --accuracy");
+ std::exit(1);
+ }
+ accuracy = c_locale_stod(*argv);
+ }
else if (strcmp(*argv, "--authority") == 0 ) {
++argv;
--argc;
@@ -421,6 +433,9 @@ int main(int argc, char **argv) {
}
authority = *argv;
}
+ else if (strcmp(*argv, "--no-ballpark") == 0 ) {
+ allowBallpark = false;
+ }
else if (**argv == '-') {
for (arg = *argv;;) {
switch (*++arg) {
@@ -773,14 +788,24 @@ int main(int argc, char **argv) {
}
std::string authorityOption; /* keep this variable in this outer scope ! */
- const char* options[2] = { nullptr, nullptr };
+ std::string accuracyOption; /* keep this variable in this outer scope ! */
+ std::vector<const char*> options;
if( authority ) {
authorityOption = "AUTHORITY=";
authorityOption += authority;
- options[0] = authorityOption.data();
+ options.push_back(authorityOption.data());
+ }
+ if( accuracy >= 0 ) {
+ accuracyOption = "ACCURACY=";
+ accuracyOption += toString(accuracy);
+ options.push_back(accuracyOption.data());
+ }
+ if( !allowBallpark ) {
+ options.push_back("ALLOW_BALLPARK=NO");
}
+ options.push_back(nullptr);
transformation = proj_create_crs_to_crs_from_pj(nullptr, src, dst,
- pj_area, options);
+ pj_area, options.data());
proj_destroy(src);
proj_destroy(dst);
diff --git a/src/apps/gie.cpp b/src/apps/gie.cpp
index b504b922..95c7da34 100644
--- a/src/apps/gie.cpp
+++ b/src/apps/gie.cpp
@@ -951,7 +951,15 @@ Tell GIE what to expect, when transforming the ACCEPTed input
if (expect_failure_with_errno) {
if (proj_errno (T.P)==expect_failure_with_errno)
return another_succeeding_failure ();
- fprintf (T.fout, "errno=%d, expected=%d\n", proj_errno (T.P), expect_failure_with_errno);
+ //fprintf (T.fout, "errno=%d, expected=%d\n", proj_errno (T.P), expect_failure_with_errno);
+ banner (T.operation);
+ errmsg (3, "%serrno=%s (%d), expected=%d at line %d\n",
+ delim,
+ err_const_from_errno(proj_errno(T.P)),
+ proj_errno (T.P),
+ expect_failure_with_errno,
+ F->lineno
+ );
return another_failing_failure ();
}
@@ -1107,85 +1115,32 @@ static int dispatch (const char *cmnd, const char *args) {
namespace { // anonymous namespace
struct errno_vs_err_const {const char *the_err_const; int the_errno;};
static const struct errno_vs_err_const lookup[] = {
- {"pjd_err_no_args" , -1},
- {"pjd_err_no_option_in_init_file" , -2},
- {"pjd_err_no_colon_in_init_string" , -3},
- {"pjd_err_proj_not_named" , -4},
- {"pjd_err_unknown_projection_id" , -5},
- {"pjd_err_invalid_eccentricity" , -6},
- {"pjd_err_unknown_unit_id" , -7},
- {"pjd_err_invalid_boolean_param" , -8},
- {"pjd_err_unknown_ellp_param" , -9},
- {"pjd_err_rev_flattening_is_zero" , -10},
- {"pjd_err_ref_rad_larger_than_90" , -11},
- {"pjd_err_es_less_than_zero" , -12},
- {"pjd_err_major_axis_not_given" , -13},
- {"pjd_err_lat_or_lon_exceed_limit" , -14},
- {"pjd_err_invalid_x_or_y" , -15},
- {"pjd_err_wrong_format_dms_value" , -16},
- {"pjd_err_non_conv_inv_meri_dist" , -17},
- {"pjd_err_non_conv_sinhpsi2tanphi" , -18},
- {"pjd_err_acos_asin_arg_too_large" , -19},
- {"pjd_err_tolerance_condition" , -20},
- {"pjd_err_conic_lat_equal" , -21},
- {"pjd_err_lat_larger_than_90" , -22},
- {"pjd_err_lat1_is_zero" , -23},
- {"pjd_err_lat_ts_larger_than_90" , -24},
- {"pjd_err_control_point_no_dist" , -25},
- {"pjd_err_no_rotation_proj" , -26},
- {"pjd_err_w_or_m_zero_or_less" , -27},
- {"pjd_err_lsat_not_in_range" , -28},
- {"pjd_err_path_not_in_range" , -29},
- {"pjd_err_invalid_h" , -30},
- {"pjd_err_k_less_than_zero" , -31},
- {"pjd_err_lat_1_or_2_zero_or_90" , -32},
- {"pjd_err_lat_0_or_alpha_eq_90" , -33},
- {"pjd_err_ellipsoid_use_required" , -34},
- {"pjd_err_invalid_utm_zone" , -35},
- {"" , -36}, /* no longer used */
- {"pjd_err_failed_to_find_proj" , -37},
- {"pjd_err_failed_to_load_grid" , -38},
- {"pjd_err_invalid_m_or_n" , -39},
- {"pjd_err_n_out_of_range" , -40},
- {"pjd_err_lat_1_2_unspecified" , -41},
- {"pjd_err_abs_lat1_eq_abs_lat2" , -42},
- {"pjd_err_lat_0_half_pi_from_mean" , -43},
- {"pjd_err_unparseable_cs_def" , -44},
- {"pjd_err_geocentric" , -45},
- {"pjd_err_unknown_prime_meridian" , -46},
- {"pjd_err_axis" , -47},
- {"pjd_err_grid_area" , -48},
- {"pjd_err_invalid_sweep_axis" , -49},
- {"pjd_err_malformed_pipeline" , -50},
- {"pjd_err_unit_factor_less_than_0" , -51},
- {"pjd_err_invalid_scale" , -52},
- {"pjd_err_non_convergent" , -53},
- {"pjd_err_missing_args" , -54},
- {"pjd_err_lat_0_is_zero" , -55},
- {"pjd_err_ellipsoidal_unsupported" , -56},
- {"pjd_err_too_many_inits" , -57},
- {"pjd_err_invalid_arg" , -58},
- {"pjd_err_inconsistent_unit" , -59},
- {"pjd_err_mutually_exclusive_args" , -60},
- {"pjd_err_generic_error" , -61},
- {"pjd_err_network_error" , -62},
- {"pjd_err_dont_skip" , 5555},
- {"pjd_err_unknown" , 9999},
- {"pjd_err_enomem" , ENOMEM},
+
+ { "invalid_op", PROJ_ERR_INVALID_OP },
+ { "invalid_op_wrong_syntax", PROJ_ERR_INVALID_OP_WRONG_SYNTAX },
+ { "invalid_op_missing_arg", PROJ_ERR_INVALID_OP_MISSING_ARG },
+ { "invalid_op_illegal_arg_value", PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE },
+ { "invalid_op_mutually_exclusive_args", PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS },
+ { "invalid_op_file_not_found_or_invalid", PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID },
+ { "coord_transfm", PROJ_ERR_COORD_TRANSFM },
+ { "coord_transfm_invalid_coord", PROJ_ERR_COORD_TRANSFM_INVALID_COORD },
+ { "coord_transfm_outside_projection_domain", PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN },
+ { "coord_transfm_no_operation", PROJ_ERR_COORD_TRANSFM_NO_OPERATION },
+ { "coord_transfm_outside_grid", PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID },
+ { "coord_transfm_grid_at_nodata", PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA },
+ { "other", PROJ_ERR_OTHER },
+ { "api_misuse", PROJ_ERR_OTHER_API_MISUSE },
+ { "no_inverse_op", PROJ_ERR_OTHER_NO_INVERSE_OP },
+ { "network_error", PROJ_ERR_OTHER_NETWORK_ERROR },
};
} // anonymous namespace
-static const struct errno_vs_err_const unknown = {"PJD_ERR_UNKNOWN", 9999};
-
-
static int list_err_codes (void) {
int i;
const int n = sizeof lookup / sizeof lookup[0];
for (i = 0; i < n; i++) {
- if (9999==lookup[i].the_errno)
- break;
- fprintf (T.fout, "%25s (%2.2d): %s\n", lookup[i].the_err_const + 8,
+ fprintf (T.fout, "%25s (%2.2d): %s\n", lookup[i].the_err_const,
lookup[i].the_errno, proj_errno_string (lookup[i].the_errno));
}
return 0;
@@ -1198,9 +1153,9 @@ static const char *err_const_from_errno (int err) {
for (i = 0; i < n; i++) {
if (err==lookup[i].the_errno)
- return lookup[i].the_err_const + 8;
+ return lookup[i].the_err_const;
}
- return unknown.the_err_const;
+ return "unknown";
}
@@ -1226,14 +1181,6 @@ static int errno_from_err_const (const char *err_const) {
/* Else try to find a matching identifier */
len = strlen (tolower_err_const);
- /* First try to find a match excluding the PJD_ERR_ prefix */
- for (i = 0; i < n; i++) {
- if (strlen(lookup[i].the_err_const) > 8 &&
- 0==strncmp (lookup[i].the_err_const + 8, err_const, len))
- return lookup[i].the_errno;
- }
-
- /* If that did not work, try with the full name */
for (i = 0; i < n; i++) {
if (0==strncmp (lookup[i].the_err_const, err_const, len))
return lookup[i].the_errno;
diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp
index da885fbb..8d389019 100644
--- a/src/apps/projinfo.cpp
+++ b/src/apps/projinfo.cpp
@@ -95,7 +95,9 @@ static void usage() {
<< std::endl
<< " [--pivot-crs always|if_no_direct_transformation|"
<< "never|{auth:code[,auth:code]*}]" << std::endl
- << " [--show-superseded] [--hide-ballpark]" << std::endl
+ << " [--show-superseded] [--hide-ballpark] "
+ "[--accuracy {accuracy}]"
+ << std::endl
<< " [--allow-ellipsoidal-height-as-vertical-crs]"
<< std::endl
<< " [--boundcrs-to-wgs84]" << std::endl
@@ -672,8 +674,8 @@ static void outputOperations(
CoordinateOperationContext::IntermediateCRSUse allowUseIntermediateCRS,
const std::vector<std::pair<std::string, std::string>> &pivots,
const std::string &authority, bool usePROJGridAlternatives,
- bool showSuperseded, bool promoteTo3D, const OutputOptions &outputOpt,
- bool summary) {
+ bool showSuperseded, bool promoteTo3D, double minimumAccuracy,
+ const OutputOptions &outputOpt, bool summary) {
auto sourceObj =
buildObject(dbContext, sourceCRSStr, "crs", "source CRS", false,
CoordinateOperationContext::IntermediateCRSUse::NEVER,
@@ -715,6 +717,9 @@ static void outputOperations(
ctxt->setUsePROJAlternativeGridNames(usePROJGridAlternatives);
ctxt->setDiscardSuperseded(!showSuperseded);
ctxt->setAllowBallparkTransformations(outputOpt.ballparkAllowed);
+ if (minimumAccuracy >= 0) {
+ ctxt->setDesiredAccuracy(minimumAccuracy);
+ }
list = CoordinateOperationFactory::create()->createOperations(
nnSourceCRS, nnTargetCRS, ctxt);
if (!spatialCriterionExplicitlySpecified &&
@@ -819,6 +824,7 @@ int main(int argc, char **argv) {
bool identify = false;
bool showSuperseded = false;
bool promoteTo3D = false;
+ double minimumAccuracy = -1;
for (int i = 1; i < argc; i++) {
std::string arg(argv[i]);
@@ -934,6 +940,9 @@ int main(int argc, char **argv) {
<< ", " << e.what() << std::endl;
usage();
}
+ } else if (arg == "--accuracy" && i + 1 < argc) {
+ i++;
+ minimumAccuracy = c_locale_stod(argv[i]);
} else if (arg == "--area" && i + 1 < argc) {
i++;
area = argv[i];
@@ -1338,12 +1347,12 @@ int main(int argc, char **argv) {
}
try {
- outputOperations(dbContext, sourceCRSStr, targetCRSStr, bboxFilter,
- spatialCriterion,
- spatialCriterionExplicitlySpecified, crsExtentUse,
- gridAvailabilityUse, allowUseIntermediateCRS,
- pivots, authority, usePROJGridAlternatives,
- showSuperseded, promoteTo3D, outputOpt, summary);
+ outputOperations(
+ dbContext, sourceCRSStr, targetCRSStr, bboxFilter,
+ spatialCriterion, spatialCriterionExplicitlySpecified,
+ crsExtentUse, gridAvailabilityUse, allowUseIntermediateCRS,
+ pivots, authority, usePROJGridAlternatives, showSuperseded,
+ promoteTo3D, minimumAccuracy, outputOpt, summary);
} catch (const std::exception &e) {
std::cerr << "outputOperations() failed with: " << e.what()
<< std::endl;
diff --git a/src/conversions/axisswap.cpp b/src/conversions/axisswap.cpp
index 1aa339c3..682f74ef 100644
--- a/src/conversions/axisswap.cpp
+++ b/src/conversions/axisswap.cpp
@@ -174,13 +174,16 @@ PJ *CONVERSION(axisswap,0) {
unsigned int i, j, n = 0;
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = (void *) Q;
/* +order and +axis are mutually exclusive */
if ( !pj_param_exists(P->params, "order") == !pj_param_exists(P->params, "axis") )
- return pj_default_destructor(P, PJD_ERR_AXIS);
+ {
+ proj_log_error(P, _("order and axis parameters are mutually exclusive."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS);
+ }
/* fill axis list with indices from 4-7 to simplify duplicate search further down */
for (i=0; i<4; i++) {
@@ -196,8 +199,8 @@ PJ *CONVERSION(axisswap,0) {
/* check that all characters are valid */
for (i=0; i<strlen(order); i++)
if (strchr("1234-,", order[i]) == nullptr) {
- proj_log_error(P, "axisswap: unknown axis '%c'", order[i]);
- return pj_default_destructor(P, PJD_ERR_AXIS);
+ proj_log_error(P, _("unknown axis '%c'"), order[i]);
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
/* read axes numbers and signs */
@@ -206,8 +209,8 @@ PJ *CONVERSION(axisswap,0) {
while ( *s != '\0' && n < 4 ) {
Q->axis[n] = abs(atoi(s))-1;
if (Q->axis[n] > 3) {
- proj_log_error(P, "axisswap: invalid axis '%d'", Q->axis[n]);
- return pj_default_destructor(P, PJD_ERR_AXIS);
+ proj_log_error(P, _("invalid axis '%d'"), Q->axis[n]);
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
Q->sign[n++] = sign(atoi(s));
while ( *s != '\0' && *s != ',' )
@@ -247,8 +250,8 @@ PJ *CONVERSION(axisswap,0) {
Q->axis[i] = 2;
break;
default:
- proj_log_error(P, "axisswap: unknown axis '%c'", P->axis[i]);
- return pj_default_destructor(P, PJD_ERR_AXIS);
+ proj_log_error(P, _("unknown axis '%c'"), P->axis[i]);
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
}
n = 3;
@@ -260,8 +263,8 @@ PJ *CONVERSION(axisswap,0) {
if (i==j)
continue;
if (Q->axis[i] == Q->axis[j]) {
- proj_log_error(P, "swapaxis: duplicate axes specified");
- return pj_default_destructor(P, PJD_ERR_AXIS);
+ proj_log_error(P, _("swapaxis: duplicate axes specified"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
}
@@ -282,8 +285,8 @@ PJ *CONVERSION(axisswap,0) {
if (P->fwd4d == nullptr && P->fwd3d == nullptr && P->fwd == nullptr) {
- proj_log_error(P, "swapaxis: bad axis order");
- return pj_default_destructor(P, PJD_ERR_AXIS);
+ proj_log_error(P, _("swapaxis: bad axis order"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
if (pj_param(P->ctx, P->params, "tangularunits").i) {
diff --git a/src/conversions/set.cpp b/src/conversions/set.cpp
index 2f30bda8..fa8c3eb7 100644
--- a/src/conversions/set.cpp
+++ b/src/conversions/set.cpp
@@ -42,7 +42,7 @@ PJ *OPERATION(set, 0) {
auto set = static_cast<struct Set*>(calloc (1, sizeof(struct Set)));
P->opaque = set;
if (nullptr==P->opaque)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
if (pj_param_exists(P->params, "v_1"))
{
diff --git a/src/conversions/topocentric.cpp b/src/conversions/topocentric.cpp
index f6f328ad..bbe52400 100644
--- a/src/conversions/topocentric.cpp
+++ b/src/conversions/topocentric.cpp
@@ -78,7 +78,7 @@ PJ *CONVERSION(topocentric,1) {
/*********************************************************************/
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = static_cast<void *>(Q);
// The topocentric origin can be specified either in geocentric coordinates
@@ -97,26 +97,30 @@ PJ *CONVERSION(topocentric,1) {
const auto hash0 = pj_param_exists(P->params, "h_0");
if( !hasX0 && !hasLon0 )
{
- return pj_default_destructor(P, PJD_ERR_MISSING_ARGS);
+ proj_log_error(P, _("missing X_0 or lon_0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
if ( (hasX0 || hasY0 || hasZ0) &&
(hasLon0 || hasLat0 || hash0) )
{
- return pj_default_destructor(P, PJD_ERR_MUTUALLY_EXCLUSIVE_ARGS);
+ proj_log_error(P, _("(X_0,Y_0,Z_0) and (lon_0,lat_0,h_0) are mutually exclusive"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS);
}
if( hasX0 && (!hasY0 || !hasZ0) )
{
- return pj_default_destructor(P, PJD_ERR_MISSING_ARGS);
+ proj_log_error(P, _("missing Y_0 and/or Z_0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
if( hasLon0 && !hasLat0 ) // allow missing h_0
{
- return pj_default_destructor(P, PJD_ERR_MISSING_ARGS);
+ proj_log_error(P, _("missing lat_0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
// Pass a dummy ellipsoid definition that will be overridden just afterwards
PJ* cart = proj_create(P->ctx, "+proj=cart +a=1");
if (cart == nullptr)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
/* inherit ellipsoid definition from P to cart */
pj_inherit_ellipsoid_def (P, cart);
diff --git a/src/conversions/unitconvert.cpp b/src/conversions/unitconvert.cpp
index 61bccbf1..187acf17 100644
--- a/src/conversions/unitconvert.cpp
+++ b/src/conversions/unitconvert.cpp
@@ -443,7 +443,7 @@ PJ *CONVERSION(unitconvert,0) {
int z_out_is_linear = -1; /* unknown */
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = (void *) Q;
P->fwd4d = forward_4d;
@@ -473,7 +473,10 @@ PJ *CONVERSION(unitconvert,0) {
} else {
f = pj_param (P->ctx, P->params, "dxy_in").f;
if (f == 0.0 || 1.0 / f == 0.0)
- return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID);
+ {
+ proj_log_error(P, _("unknown xy_in unit"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
}
Q->xy_factor = f;
if (normalized_name != nullptr) {
@@ -492,7 +495,10 @@ PJ *CONVERSION(unitconvert,0) {
} else {
f = pj_param (P->ctx, P->params, "dxy_out").f;
if (f == 0.0 || 1.0 / f == 0.0)
- return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID);
+ {
+ proj_log_error(P, _("unknown xy_out unit"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
}
Q->xy_factor /= f;
if (normalized_name != nullptr) {
@@ -505,8 +511,8 @@ PJ *CONVERSION(unitconvert,0) {
if( xy_in_is_linear >= 0 && xy_out_is_linear >= 0 &&
xy_in_is_linear != xy_out_is_linear ) {
- proj_log_debug(P, "inconsistent unit type between xy_in and xy_out");
- return pj_default_destructor(P, PJD_ERR_INCONSISTENT_UNIT);
+ proj_log_error(P, _("inconsistent unit type between xy_in and xy_out"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
if ((name = pj_param (P->ctx, P->params, "sz_in").s) != nullptr) {
@@ -517,7 +523,10 @@ PJ *CONVERSION(unitconvert,0) {
} else {
f = pj_param (P->ctx, P->params, "dz_in").f;
if (f == 0.0 || 1.0 / f == 0.0)
- return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID);
+ {
+ proj_log_error(P, _("unknown z_in unit"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
}
Q->z_factor = f;
}
@@ -530,21 +539,28 @@ PJ *CONVERSION(unitconvert,0) {
} else {
f = pj_param (P->ctx, P->params, "dz_out").f;
if (f == 0.0 || 1.0 / f == 0.0)
- return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID);
+ {
+ proj_log_error(P, _("unknown z_out unit"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
}
Q->z_factor /= f;
}
if( z_in_is_linear >= 0 && z_out_is_linear >= 0 &&
z_in_is_linear != z_out_is_linear ) {
- proj_log_debug(P, "inconsistent unit type between z_in and z_out");
- return pj_default_destructor(P, PJD_ERR_INCONSISTENT_UNIT);
+ proj_log_error(P, _("inconsistent unit type between z_in and z_out"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
if ((name = pj_param (P->ctx, P->params, "st_in").s) != nullptr) {
for (i = 0; (s = time_units[i].id) && strcmp(name, s) ; ++i);
- if (!s) return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); /* unknown unit conversion id */
+ if (!s)
+ {
+ proj_log_error(P, _("unknown t_in unit"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->t_in_id = i;
proj_log_trace(P, "t_in unit: %s", time_units[i].name);
@@ -554,7 +570,11 @@ PJ *CONVERSION(unitconvert,0) {
if ((name = pj_param (P->ctx, P->params, "st_out").s) != nullptr) {
for (i = 0; (s = time_units[i].id) && strcmp(name, s) ; ++i);
- if (!s) return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); /* unknown unit conversion id */
+ if (!s)
+ {
+ proj_log_error(P, _("unknown t_out unit"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->t_out_id = i;
proj_log_trace(P, "t_out unit: %s", time_units[i].name);
diff --git a/src/ctx.cpp b/src/ctx.cpp
index 2093950b..b774a40a 100644
--- a/src/ctx.cpp
+++ b/src/ctx.cpp
@@ -93,10 +93,10 @@ pj_ctx pj_ctx::createDefault()
if( getenv("PROJ_DEBUG") != nullptr )
{
- if( atoi(getenv("PROJ_DEBUG")) >= -PJ_LOG_DEBUG_MINOR )
+ if( atoi(getenv("PROJ_DEBUG")) >= -PJ_LOG_TRACE )
ctx.debug_level = atoi(getenv("PROJ_DEBUG"));
else
- ctx.debug_level = PJ_LOG_DEBUG_MINOR;
+ ctx.debug_level = PJ_LOG_TRACE;
}
return ctx;
}
diff --git a/src/datum_set.cpp b/src/datum_set.cpp
index 3f612633..d55eb982 100644
--- a/src/datum_set.cpp
+++ b/src/datum_set.cpp
@@ -25,7 +25,6 @@
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
-#include <errno.h>
#include <string.h>
#include "proj.h"
@@ -71,7 +70,8 @@ int pj_datum_set(PJ_CONTEXT *ctx, paralist *pl, PJ *projdef)
for (i = 0; (s = pj_datums[i].id) && strcmp(name, s) ; ++i) {}
if (!s) {
- proj_context_errno_set(ctx, PJD_ERR_UNKNOWN_ELLP_PARAM);
+ pj_log (ctx, PJ_LOG_ERROR, _("Unknown value for datum"));
+ proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
return 1;
}
@@ -87,7 +87,7 @@ int pj_datum_set(PJ_CONTEXT *ctx, paralist *pl, PJ *projdef)
auto param = pj_mkparam(entry);
if (nullptr == param)
{
- proj_context_errno_set(ctx, ENOMEM);
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/);
return 1;
}
curr->next = param;
@@ -99,7 +99,7 @@ int pj_datum_set(PJ_CONTEXT *ctx, paralist *pl, PJ *projdef)
auto param = pj_mkparam(pj_datums[i].defn);
if (nullptr == param)
{
- proj_context_errno_set(ctx, ENOMEM);
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/);
return 1;
}
curr->next = param;
diff --git a/src/dmstor.cpp b/src/dmstor.cpp
index 24887a11..4c7408cf 100644
--- a/src/dmstor.cpp
+++ b/src/dmstor.cpp
@@ -61,7 +61,7 @@ dmstor_ctx(PJ_CONTEXT *ctx, const char *is, char **rs) {
n = 2; break;
case 'r': case 'R':
if (nl) {
- proj_context_errno_set( ctx, PJD_ERR_WRONG_FORMAT_DMS_VALUE );
+ proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE );
return HUGE_VAL;
}
++s;
@@ -73,7 +73,7 @@ dmstor_ctx(PJ_CONTEXT *ctx, const char *is, char **rs) {
continue;
}
if (n < nl) {
- proj_context_errno_set( ctx, PJD_ERR_WRONG_FORMAT_DMS_VALUE );
+ proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE );
return HUGE_VAL;
}
v += tv * vm[n];
diff --git a/src/ell_set.cpp b/src/ell_set.cpp
index 176fc553..438448d2 100644
--- a/src/ell_set.cpp
+++ b/src/ell_set.cpp
@@ -1,6 +1,5 @@
/* set ellipsoid parameters a and es */
-#include <errno.h>
#include <math.h>
#include <stddef.h>
#include <string.h>
@@ -147,23 +146,29 @@ static int ellps_ellps (PJ *P) {
/* Then look up the right size and shape parameters from the builtin list */
if (strlen (par->param) < 7)
- return proj_errno_set (P, PJD_ERR_INVALID_ARG);
+ {
+ proj_log_error(P, _("Invalid value for +ellps"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
name = par->param + 6;
ellps = pj_find_ellps (name);
if (nullptr==ellps)
- return proj_errno_set (P, PJD_ERR_UNKNOWN_ELLP_PARAM);
+ {
+ proj_log_error(P, _("Unrecognized value for +ellps"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
/* Now, get things ready for ellps_size/ellps_shape, make them do their thing */
err = proj_errno_reset (P);
paralist* new_params = pj_mkparam (ellps->major);
if (nullptr == new_params)
- return proj_errno_set (P, ENOMEM);
+ return proj_errno_set (P, PROJ_ERR_OTHER /*ENOMEM*/);
new_params->next = pj_mkparam (ellps->ell);
if (nullptr == new_params->next)
{
free(new_params);
- return proj_errno_set (P, ENOMEM);
+ return proj_errno_set (P, PROJ_ERR_OTHER /*ENOMEM*/);
}
paralist* old_params = P->params;
P->params = new_params;
@@ -207,15 +212,26 @@ static int ellps_size (PJ *P) {
if (nullptr==par)
par = pj_get_param (P->params, "a");
if (nullptr==par)
- return a_was_set? 0: proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN);
+ {
+ if( a_was_set )
+ return 0;
+ proj_log_error(P, _("Major axis not given"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
P->def_size = pj_strdup(par->param);
par->used = 1;
P->a = pj_atof (pj_param_value (par));
if (P->a <= 0)
- return proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN);
+ {
+ proj_log_error(P, _("Invalid value for major axis"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (HUGE_VAL==P->a)
- return proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN);
+ {
+ proj_log_error(P, _("Invalid value for major axis"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if ('R'==par->param[0]) {
P->es = P->f = P->e = P->rf = 0;
@@ -264,10 +280,11 @@ static int ellps_shape (PJ *P) {
/* reverse flattening, rf */
case 0:
P->rf = pj_atof (pj_param_value (par));
- if (HUGE_VAL==P->rf)
- return proj_errno_set (P, PJD_ERR_INVALID_ARG);
- if (0==P->rf)
- return proj_errno_set (P, PJD_ERR_REV_FLATTENING_IS_ZERO);
+ if (HUGE_VAL==P->rf || P->rf <= 0)
+ {
+ proj_log_error(P, _("Invalid value for rf. Should be > 0"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
P->f = 1 / P->rf;
P->es = 2*P->f - P->f*P->f;
break;
@@ -275,8 +292,11 @@ static int ellps_shape (PJ *P) {
/* flattening, f */
case 1:
P->f = pj_atof (pj_param_value (par));
- if (HUGE_VAL==P->f)
- return proj_errno_set (P, PJD_ERR_INVALID_ARG);
+ if (HUGE_VAL==P->f || P->f < 0)
+ {
+ proj_log_error(P, _("Invalid value for f. Should be >= 0"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
P->rf = P->f != 0.0 ? 1.0/P->f: HUGE_VAL;
P->es = 2*P->f - P->f*P->f;
@@ -285,42 +305,49 @@ static int ellps_shape (PJ *P) {
/* eccentricity squared, es */
case 2:
P->es = pj_atof (pj_param_value (par));
- if (HUGE_VAL==P->es)
- return proj_errno_set (P, PJD_ERR_INVALID_ARG);
- if (P->es >= 1)
- return proj_errno_set (P, PJD_ERR_INVALID_ECCENTRICITY);
+ if (HUGE_VAL==P->es || P->es < 0 || P->es >= 1)
+ {
+ proj_log_error(P, _("Invalid value for es. Should be in [0,1[ range"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
break;
/* eccentricity, e */
case 3:
P->e = pj_atof (pj_param_value (par));
- if (HUGE_VAL==P->e)
- return proj_errno_set (P, PJD_ERR_INVALID_ARG);
- if (P->e < 0 || P->e >= 1)
- return proj_errno_set (P, PJD_ERR_INVALID_ECCENTRICITY);
+ if (HUGE_VAL==P->e || P->e < 0 || P->e >= 1)
+ {
+ proj_log_error(P, _("Invalid value for e. Should be in [0,1[ range"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
P->es = P->e * P->e;
break;
/* semiminor axis, b */
case 4:
P->b = pj_atof (pj_param_value (par));
- if (HUGE_VAL==P->b)
- return proj_errno_set (P, PJD_ERR_INVALID_ARG);
- if (P->b <= 0)
- return proj_errno_set (P, PJD_ERR_INVALID_ECCENTRICITY);
+ if (HUGE_VAL==P->b || P->b <= 0)
+ {
+ proj_log_error(P, _("Invalid value for b. Should be > 0"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (P->b==P->a)
break;
P->f = (P->a - P->b) / P->a;
P->es = 2*P->f - P->f*P->f;
break;
default:
- return PJD_ERR_INVALID_ARG;
+ // shouldn't happen
+ return PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE;
}
// Written that way to catch NaN
if (!(P->es >= 0))
- return proj_errno_set (P, PJD_ERR_ES_LESS_THAN_ZERO);
+ {
+ proj_log_error(P, _("Invalid eccentricity"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
return 0;
}
@@ -384,7 +411,7 @@ static int ellps_spherification (PJ *P) {
/* R_h - a sphere with R = the harmonic mean of the ellipsoid */
case 4:
if (P->a + P->b == 0)
- return proj_errno_set (P, PJD_ERR_TOLERANCE_CONDITION);
+ return proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
P->a = (2*P->a * P->b) / (P->a + P->b);
break;
@@ -395,11 +422,15 @@ static int ellps_spherification (PJ *P) {
v = pj_param_value (par);
t = proj_dmstor (v, &endp);
if (fabs (t) > M_HALFPI)
- return proj_errno_set (P, PJD_ERR_REF_RAD_LARGER_THAN_90);
+ {
+ proj_log_error(P, _("Invalid value for lat_g. |lat_g| should be <= 90°"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
t = sin (t);
t = 1 - P->es * t * t;
if (t == 0.) {
- return proj_errno_set(P, PJD_ERR_INVALID_ECCENTRICITY);
+ proj_log_error(P, _("Invalid eccentricity"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
if (i==5) /* arithmetic */
P->a *= (1. - P->es + t) / (2 * t * sqrt(t));
@@ -409,7 +440,8 @@ static int ellps_spherification (PJ *P) {
}
if (P->a <= 0.) {
- return proj_errno_set(P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN);
+ proj_log_error(P, _("Invalid or missing major axis"));
+ return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
/* Clean up the ellipsoidal parameters to reflect the sphere */
@@ -552,8 +584,9 @@ int pj_calc_ellipsoid_params (PJ *P, double a, double es) {
if (0==P->f)
P->f = 1 - cos (P->alpha); /* = 1 - sqrt (1 - PIN->es); */
if (P->f == 1.0) {
- proj_context_errno_set( P->ctx, PJD_ERR_INVALID_ECCENTRICITY);
- return PJD_ERR_INVALID_ECCENTRICITY;
+ proj_log_error(P, _("Invalid eccentricity"));
+ proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ return PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE;
}
P->rf = P->f != 0.0 ? 1.0/P->f: HUGE_VAL;
@@ -573,8 +606,9 @@ int pj_calc_ellipsoid_params (PJ *P, double a, double es) {
P->one_es = 1. - P->es;
if (P->one_es == 0.) {
- proj_context_errno_set( P->ctx, PJD_ERR_INVALID_ECCENTRICITY);
- return PJD_ERR_INVALID_ECCENTRICITY;
+ proj_log_error(P, _("Invalid eccentricity"));
+ proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ return PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE;
}
P->rone_es = 1./P->one_es;
@@ -582,9 +616,6 @@ int pj_calc_ellipsoid_params (PJ *P, double a, double es) {
return 0;
}
-
-
-#ifndef KEEP_ORIGINAL_PJ_ELL_SET
/**************************************************************************************/
int pj_ell_set (PJ_CONTEXT *ctx, paralist *pl, double *a, double *es) {
/***************************************************************************************
@@ -605,142 +636,3 @@ int pj_ell_set (PJ_CONTEXT *ctx, paralist *pl, double *a, double *es) {
*es = B.es;
return 0;
}
-#else
-
-
-/**************************************************************************************/
-int pj_ell_set (PJ_CONTEXT *ctx, paralist *pl, double *a, double *es) {
-/***************************************************************************************
- Initialize ellipsoidal parameters: This is the original ellipsoid setup
- function by Gerald Evenden - significantly more compact than pj_ellipsoid and
- its many helper functions, and still quite readable.
-
- It is, however, also so tight that it is hard to modify and add functionality,
- and equally hard to find the right place to add further commentary for improved
- future maintainability.
-
- Hence, when the need to store in the PJ object, the parameters actually used to
- define the ellipsoid came up, rather than modifying this little gem of
- "economy of expression", a much more verbose reimplementation, pj_ellipsoid,
- was written.
-***************************************************************************************/
- int i;
- double b=0.0, e;
- char *name;
- paralist *start = 0;
-
- /* clear any previous error */
- proj_context_errno_set(ctx,0);
-
- /* check for varying forms of ellipsoid input */
- *a = *es = 0.;
-
- /* R takes precedence */
- if (pj_param(ctx, pl, "tR").i)
- *a = pj_param(ctx,pl, "dR").f;
-
- /* probable elliptical figure */
- else {
- /* check if ellps present and temporarily append its values to pl */
- if ((name = pj_param(ctx,pl, "sellps").s) != NULL) {
- char *s;
-
- for (start = pl; start && start->next ; start = start->next) ;
- for (i = 0; (s = pj_ellps[i].id) && strcmp(name, s) ; ++i) ;
- if (!s) {
- proj_context_errno_set( ctx, PJD_ERR_UNKNOWN_ELLP_PARAM);
- return 1;
- }
- start->next = pj_mkparam(pj_ellps[i].major);
- start->next->next = pj_mkparam(pj_ellps[i].ell);
- }
-
- *a = pj_param(ctx,pl, "da").f;
-
- if (pj_param(ctx,pl, "tes").i) /* eccentricity squared */
- *es = pj_param(ctx,pl, "des").f;
- else if (pj_param(ctx,pl, "te").i) { /* eccentricity */
- e = pj_param(ctx,pl, "de").f;
- if (e < 0) {
- proj_context_errno_set(ctx, PJD_ERR_INVALID_ECCENTRICITY);
- return 1;
- }
- *es = e * e;
- } else if (pj_param(ctx,pl, "trf").i) { /* recip flattening */
- *es = pj_param(ctx,pl, "drf").f;
- if (*es == 0.0) {
- proj_context_errno_set(ctx, PJD_ERR_REV_FLATTENING_IS_ZERO);
- goto bomb;
- }
- *es = 1./ *es;
- *es = *es * (2. - *es);
- } else if (pj_param(ctx,pl, "tf").i) { /* flattening */
- *es = pj_param(ctx,pl, "df").f;
- *es = *es * (2. - *es);
- } else if (pj_param(ctx,pl, "tb").i) { /* minor axis */
- b = pj_param(ctx,pl, "db").f;
- *es = 1. - (b * b) / (*a * *a);
- } /* else *es == 0. and sphere of radius *a */
- if (b == 0.0)
- b = *a * sqrt(1. - *es);
-
-
- /* following options turn ellipsoid into equivalent sphere */
- if (pj_param(ctx,pl, "bR_A").i) { /* sphere--area of ellipsoid */
- *a *= 1. - *es * (SIXTH + *es * (RA4 + *es * RA6));
- *es = 0.;
- } else if (pj_param(ctx,pl, "bR_V").i) { /* sphere--vol. of ellipsoid */
- *a *= 1. - *es * (SIXTH + *es * (RV4 + *es * RV6));
- *es = 0.;
- } else if (pj_param(ctx,pl, "bR_a").i) { /* sphere--arithmetic mean */
- *a = .5 * (*a + b);
- *es = 0.;
- } else if (pj_param(ctx,pl, "bR_g").i) { /* sphere--geometric mean */
- *a = sqrt(*a * b);
- *es = 0.;
- } else if (pj_param(ctx,pl, "bR_h").i) { /* sphere--harmonic mean */
- if ( (*a + b) == 0.0) {
- proj_context_errno_set(ctx, PJD_ERR_TOLERANCE_CONDITION);
- goto bomb;
- }
- *a = 2. * *a * b / (*a + b);
- *es = 0.;
- } else if ((i = pj_param(ctx,pl, "tR_lat_a").i) || /* sphere--arith. */
- pj_param(ctx,pl, "tR_lat_g").i) { /* or geom. mean at latitude */
- double tmp;
-
- tmp = sin(pj_param(ctx,pl, i ? "rR_lat_a" : "rR_lat_g").f);
- if (fabs(tmp) > M_HALFPI) {
- proj_context_errno_set(ctx, PJD_ERR_REF_RAD_LARGER_THAN_90);
- goto bomb;
- }
- tmp = 1. - *es * tmp * tmp;
- *a *= i ? .5 * (1. - *es + tmp) / ( tmp * sqrt(tmp)) :
- sqrt(1. - *es) / tmp;
- *es = 0.;
- }
-bomb:
- if (start) { /* clean up temporary extension of list */
- free(start->next->next);
- free(start->next);
- start->next = 0;
- }
- if (ctx->last_errno)
- return 1;
- }
- /* some remaining checks */
- if (*es < 0.) {
- proj_context_errno_set(ctx, PJD_ERR_ES_LESS_THAN_ZERO);
- return 1;
- }
- if (*es >= 1.) {
- proj_context_errno_set(ctx, PJD_ERR_INVALID_ECCENTRICITY);
- return 1;
- }
- if (*a <= 0.) {
- proj_context_errno_set(ctx, PJD_ERR_MAJOR_AXIS_NOT_GIVEN);
- return 1;
- }
- return 0;
-}
-#endif
diff --git a/src/factors.cpp b/src/factors.cpp
index ff733d07..d6c24f01 100644
--- a/src/factors.cpp
+++ b/src/factors.cpp
@@ -36,8 +36,15 @@ int pj_factors(PJ_LP lp, const PJ *P, double h, struct FACTORS *fac) {
fac->code = 0;
/* Check for latitude or longitude overange */
- if ((fabs (lp.phi)-M_HALFPI) > EPS || fabs (lp.lam) > 10.) {
- proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT);
+ if ((fabs (lp.phi)-M_HALFPI) > EPS )
+ {
+ proj_log_error(P, _("Invalid latitude"));
+ proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD);
+ return 1;
+ }
+ if( fabs (lp.lam) > 10.) {
+ proj_log_error(P, _("Invalid longitude"));
+ proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD);
return 1;
}
@@ -62,7 +69,8 @@ int pj_factors(PJ_LP lp, const PJ *P, double h, struct FACTORS *fac) {
/* Derivatives */
if (pj_deriv (lp, h, P, &(fac->der))) {
- proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT);
+ proj_log_error(P, _("Invalid latitude or longitude"));
+ proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD);
return 1;
}
diff --git a/src/filemanager.cpp b/src/filemanager.cpp
index b51205eb..0a0af8cf 100644
--- a/src/filemanager.cpp
+++ b/src/filemanager.cpp
@@ -1489,13 +1489,13 @@ static void *pj_open_lib_internal(
if (ctx->last_errno == 0 && errno != 0)
proj_context_errno_set(ctx, errno);
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "pj_open_lib(%s): call fopen(%s) - %s",
- name, sysname, fid == nullptr ? "failed" : "succeeded");
+ pj_log(ctx, PJ_LOG_DEBUG, "pj_open_lib(%s): call fopen(%s) - %s", name,
+ sysname, fid == nullptr ? "failed" : "succeeded");
return (fid);
} catch (const std::exception &) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "pj_open_lib(%s): out of memory", name);
+ pj_log(ctx, PJ_LOG_DEBUG, "pj_open_lib(%s): out of memory", name);
return nullptr;
}
@@ -1640,8 +1640,7 @@ NS_PROJ::FileManager::open_resource_file(PJ_CONTEXT *ctx, const char *name) {
file =
open(ctx, remote_file.c_str(), NS_PROJ::FileAccess::READ_ONLY);
if (file) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Using %s",
- remote_file.c_str());
+ pj_log(ctx, PJ_LOG_DEBUG, "Using %s", remote_file.c_str());
proj_context_errno_set(ctx, 0);
}
}
diff --git a/src/fwd.cpp b/src/fwd.cpp
index 962a5051..97ba5999 100644
--- a/src/fwd.cpp
+++ b/src/fwd.cpp
@@ -52,10 +52,19 @@ static PJ_COORD fwd_prepare (PJ *P, PJ_COORD coo) {
/* check for latitude or longitude over-range */
t = (coo.lp.phi < 0 ? -coo.lp.phi : coo.lp.phi) - M_HALFPI;
- if (t > PJ_EPS_LAT || coo.lp.lam > 10 || coo.lp.lam < -10) {
- proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT);
+ if (t > PJ_EPS_LAT)
+ {
+ proj_log_error(P, _("Invalid latitude"));
+ proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD);
return proj_coord_error ();
}
+ if (coo.lp.lam > 10 || coo.lp.lam < -10)
+ {
+ proj_log_error(P, _("Invalid longitude"));
+ proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD);
+ return proj_coord_error ();
+ }
+
/* Clamp latitude to -90..90 degree range */
if (coo.lp.phi > M_HALFPI)
@@ -186,7 +195,7 @@ PJ_XY pj_fwd(PJ_LP lp, PJ *P) {
else if (P->fwd4d)
coo = P->fwd4d (coo, P);
else {
- proj_errno_set (P, EINVAL);
+ proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP);
return proj_coord_error ().xy;
}
if (HUGE_VAL==coo.v[0])
@@ -220,7 +229,7 @@ PJ_XYZ pj_fwd3d(PJ_LPZ lpz, PJ *P) {
else if (P->fwd)
coo.xy = P->fwd (coo.lp, P);
else {
- proj_errno_set (P, EINVAL);
+ proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP);
return proj_coord_error ().xyz;
}
if (HUGE_VAL==coo.v[0])
@@ -250,7 +259,7 @@ PJ_COORD pj_fwd4d (PJ_COORD coo, PJ *P) {
else if (P->fwd)
coo.xy = P->fwd (coo.lp, P);
else {
- proj_errno_set (P, EINVAL);
+ proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP);
return proj_coord_error ();
}
if (HUGE_VAL==coo.v[0])
diff --git a/src/gauss.cpp b/src/gauss.cpp
index 96bd5166..07d0433a 100644
--- a/src/gauss.cpp
+++ b/src/gauss.cpp
@@ -109,6 +109,6 @@ PJ_LP pj_inv_gauss(PJ_CONTEXT *ctx, PJ_LP slp, const void *data) {
}
/* convergence failed */
if (!i)
- proj_context_errno_set(ctx, PJD_ERR_NON_CONV_INV_MERI_DIST);
+ proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return (elp);
}
diff --git a/src/generic_inverse.cpp b/src/generic_inverse.cpp
index ddd5060b..41cf6c50 100644
--- a/src/generic_inverse.cpp
+++ b/src/generic_inverse.cpp
@@ -109,6 +109,7 @@ PJ_LP pj_generic_inverse_2d(PJ_XY xy, PJ *P, PJ_LP lpInitial) {
lp.phi = M_HALFPI;
}
}
- proj_context_errno_set(P->ctx, PJD_ERR_NON_CONVERGENT);
+ proj_context_errno_set(P->ctx,
+ PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
diff --git a/src/grids.cpp b/src/grids.cpp
index 871e21ed..b7ab526a 100644
--- a/src/grids.cpp
+++ b/src/grids.cpp
@@ -197,7 +197,9 @@ GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx,
/* Read the header. */
/* -------------------------------------------------------------------- */
if (fp->read(header, sizeof(header)) != sizeof(header)) {
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ pj_log(ctx, PJ_LOG_ERROR, _("Cannot read grid header"));
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
@@ -222,8 +224,9 @@ GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx,
if (xorigin < -360 || xorigin > 360 || yorigin < -90 || yorigin > 90) {
pj_log(ctx, PJ_LOG_ERROR,
- "gtx file header has invalid extents, corrupt?");
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ _("gtx file header has invalid extents, corrupt?"));
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
@@ -234,7 +237,7 @@ GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx,
xorigin -= 360.0;
if (xorigin >= 0.0 && xorigin + xstep * columns > 180.0) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ pj_log(ctx, PJ_LOG_DEBUG,
"This GTX spans the dateline! This will cause problems.");
}
@@ -258,7 +261,8 @@ bool GTXVerticalShiftGrid::valueAt(int x, int y, float &out) const {
m_fp->seek(40 + sizeof(float) * (y * m_width + x));
if (m_fp->read(&out, sizeof(out)) != sizeof(out)) {
- proj_context_errno_set(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(m_ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return false;
}
if (IS_LSB) {
@@ -621,7 +625,7 @@ bool GTiffGrid::valueAt(uint16 sample, int x, int yFromBottom,
try {
m_buffer.resize(blockSize);
} catch (const std::exception &e) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Exception %s", e.what());
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
return false;
}
}
@@ -642,7 +646,7 @@ bool GTiffGrid::valueAt(uint16 sample, int x, int yFromBottom,
m_cache.insert(m_ifdIdx, blockId, m_buffer);
} catch (const std::exception &e) {
// Should normally not happen
- pj_log(m_ctx, PJ_LOG_ERROR, "Exception %s", e.what());
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
}
}
@@ -847,35 +851,35 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
TIFFGetField(m_hTIFF, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetField(m_hTIFF, TIFFTAG_IMAGELENGTH, &height);
if (width == 0 || height == 0 || width > INT_MAX || height > INT_MAX) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Invalid image size");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Invalid image size"));
return nullptr;
}
uint16 samplesPerPixel = 0;
if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel)) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Missing SamplesPerPixel tag");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Missing SamplesPerPixel tag"));
return nullptr;
}
if (samplesPerPixel == 0) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Invalid SamplesPerPixel value");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Invalid SamplesPerPixel value"));
return nullptr;
}
uint16 bitsPerSample = 0;
if (!TIFFGetField(m_hTIFF, TIFFTAG_BITSPERSAMPLE, &bitsPerSample)) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Missing BitsPerSample tag");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Missing BitsPerSample tag"));
return nullptr;
}
uint16 planarConfig = 0;
if (!TIFFGetField(m_hTIFF, TIFFTAG_PLANARCONFIG, &planarConfig)) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Missing PlanarConfig tag");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Missing PlanarConfig tag"));
return nullptr;
}
uint16 sampleFormat = 0;
if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLEFORMAT, &sampleFormat)) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Missing SampleFormat tag");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Missing SampleFormat tag"));
return nullptr;
}
@@ -893,9 +897,8 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 64)
dt = TIFFDataType::Float64;
else {
- pj_log(
- m_ctx, PJ_LOG_ERROR,
- "Unsupported combination of SampleFormat and BitsPerSample values");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported combination of SampleFormat "
+ "and BitsPerSample values"));
return nullptr;
}
@@ -903,7 +906,7 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
if (!TIFFGetField(m_hTIFF, TIFFTAG_PHOTOMETRIC, &photometric))
photometric = PHOTOMETRIC_MINISBLACK;
if (photometric != PHOTOMETRIC_MINISBLACK) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported Photometric value");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported Photometric value"));
return nullptr;
}
@@ -914,19 +917,19 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
if (compression != COMPRESSION_NONE &&
!TIFFIsCODECConfigured(compression)) {
pj_log(m_ctx, PJ_LOG_ERROR,
- "Cannot open TIFF file due to missing codec.");
+ _("Cannot open TIFF file due to missing codec."));
return nullptr;
}
// We really don't want to try dealing with old-JPEG images
if (compression == COMPRESSION_OJPEG) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported compression method.");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported compression method."));
return nullptr;
}
const auto blockSize = TIFFIsTiled(m_hTIFF) ? TIFFTileSize64(m_hTIFF)
: TIFFStripSize64(m_hTIFF);
if (blockSize == 0 || blockSize > 64 * 1024 * 2014) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported block size.");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported block size."));
return nullptr;
}
@@ -938,23 +941,22 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
extent.isGeographic = true;
if (!TIFFGetField(m_hTIFF, TIFFTAG_GEOKEYDIRECTORY, &count, &geokeys)) {
- pj_log(m_ctx, PJ_LOG_DEBUG_MINOR, "No GeoKeys tag");
+ pj_log(m_ctx, PJ_LOG_TRACE, "No GeoKeys tag");
} else {
if (count < 4 || (count % 4) != 0) {
pj_log(m_ctx, PJ_LOG_ERROR,
- "Wrong number of values in GeoKeys tag");
+ _("Wrong number of values in GeoKeys tag"));
return nullptr;
}
if (geokeys[0] != 1) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported GeoTIFF major version");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported GeoTIFF major version"));
return nullptr;
}
// We only know that we support GeoTIFF 1.0 and 1.1 at that time
if (geokeys[1] != 1 || geokeys[2] > 1) {
- pj_log(m_ctx, PJ_LOG_DEBUG_MINOR,
- "GeoTIFF %d.%d possibly not handled", geokeys[1],
- geokeys[2]);
+ pj_log(m_ctx, PJ_LOG_TRACE, "GeoTIFF %d.%d possibly not handled",
+ geokeys[1], geokeys[2]);
}
for (unsigned int i = 4; i + 3 < count; i += 4) {
@@ -971,9 +973,9 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
extent.isGeographic = false;
} else if (geokeys[i + 3] != ModelTypeGeographic) {
pj_log(m_ctx, PJ_LOG_ERROR,
- "Only GTModelTypeGeoKey = "
- "ModelTypeGeographic or ModelTypeProjected are "
- "supported");
+ _("Only GTModelTypeGeoKey = "
+ "ModelTypeGeographic or ModelTypeProjected are "
+ "supported"));
return nullptr;
}
} else if (geokeys[i] == GTRasterTypeGeoKey) {
@@ -996,8 +998,8 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
// a GeoTransformationMatrix, since negative values in GeoPixelScale
// have historically been implementation bugs.
if (matrix[1] != 0 || matrix[4] != 0) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Rotational terms not supported in "
- "GeoTransformationMatrix tag");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Rotational terms not supported in "
+ "GeoTransformationMatrix tag"));
return nullptr;
}
@@ -1009,12 +1011,12 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
double *geopixelscale = nullptr;
if (TIFFGetField(m_hTIFF, TIFFTAG_GEOPIXELSCALE, &count,
&geopixelscale) != 1) {
- pj_log(m_ctx, PJ_LOG_ERROR, "No GeoPixelScale tag");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("No GeoPixelScale tag"));
return nullptr;
}
if (count != 3) {
pj_log(m_ctx, PJ_LOG_ERROR,
- "Wrong number of values in GeoPixelScale tag");
+ _("Wrong number of values in GeoPixelScale tag"));
return nullptr;
}
hRes = geopixelscale[0];
@@ -1023,12 +1025,12 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
double *geotiepoints = nullptr;
if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTIEPOINTS, &count,
&geotiepoints) != 1) {
- pj_log(m_ctx, PJ_LOG_ERROR, "No GeoTiePoints tag");
+ pj_log(m_ctx, PJ_LOG_ERROR, _("No GeoTiePoints tag"));
return nullptr;
}
if (count != 6) {
pj_log(m_ctx, PJ_LOG_ERROR,
- "Wrong number of values in GeoTiePoints tag");
+ _("Wrong number of values in GeoTiePoints tag"));
return nullptr;
}
@@ -1059,7 +1061,7 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
fabs(extent.south) <= M_PI + 1e-5)) &&
extent.west < extent.east && extent.south < extent.north &&
extent.resX > 1e-10 && extent.resY > 1e-10)) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s",
+ pj_log(m_ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"),
m_filename.c_str());
return nullptr;
}
@@ -1097,7 +1099,7 @@ class GTiffVGridShiftSet : public VerticalShiftGridSet {
}
bool reopen(PJ_CONTEXT *ctx) override {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
m_name.c_str());
m_grids.clear();
m_GTiffDataset.reset();
@@ -1130,7 +1132,7 @@ insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid,
// the names to recreate the hierarchy
if (!gridName.empty()) {
if (mapGrids.find(gridName) != mapGrids.end()) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Several grids called %s found!",
+ pj_log(ctx, PJ_LOG_DEBUG, "Several grids called %s found!",
gridName.c_str());
}
mapGrids[gridName] = grid.get();
@@ -1139,7 +1141,7 @@ insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid,
if (!parentName.empty()) {
auto iter = mapGrids.find(parentName);
if (iter == mapGrids.end()) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ pj_log(ctx, PJ_LOG_DEBUG,
"Grid %s refers to non-existing parent %s. "
"Using bounding-box method.",
gridName.c_str(), parentName.c_str());
@@ -1148,7 +1150,7 @@ insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid,
iter->second->m_children.emplace_back(std::move(grid));
gridInserted = true;
} else {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ pj_log(ctx, PJ_LOG_DEBUG,
"Grid %s refers to parent %s, but its extent is "
"not included in it. Using bounding-box method.",
gridName.c_str(), parentName.c_str());
@@ -1169,7 +1171,7 @@ insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid,
gridInserted = true;
break;
} else if (candidateParentExtent.intersects(extent)) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ pj_log(ctx, PJ_LOG_DEBUG,
"Partially intersecting grids found!");
}
}
@@ -1243,8 +1245,7 @@ void GTiffVGrid::insertGrid(PJ_CONTEXT *ctx,
gridInserted = true;
break;
} else if (candidateParentExtent.intersects(extent)) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
- "Partially intersecting grids found!");
+ pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!");
}
}
if (!gridInserted) {
@@ -1279,10 +1280,10 @@ GTiffVGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
const auto subfileType = grid->subfileType();
if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
if (ifd == 0) {
- pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType");
+ pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType"));
return nullptr;
} else {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ pj_log(ctx, PJ_LOG_DEBUG,
"Ignoring IFD %d as it has a unsupported subfileType",
ifd);
continue;
@@ -1310,13 +1311,13 @@ GTiffVGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
// can be ignored
// One could imagine to put the accuracy values in separate
// IFD for example
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ pj_log(ctx, PJ_LOG_DEBUG,
"Ignoring IFD %d as it has no "
"geoid_undulation/vertical_offset channel",
ifd);
continue;
} else {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ pj_log(ctx, PJ_LOG_DEBUG,
"IFD 0 has channel descriptions, but no "
"geoid_undulation/vertical_offset channel");
return nullptr;
@@ -1325,7 +1326,7 @@ GTiffVGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
}
if (idxSample >= grid->samplesPerPixel()) {
- pj_log(ctx, PJ_LOG_ERROR, "Invalid sample index");
+ pj_log(ctx, PJ_LOG_ERROR, _("Invalid sample index"));
return nullptr;
}
@@ -1389,23 +1390,24 @@ VerticalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
auto set = std::unique_ptr<VerticalShiftGridSet>(
GTiffVGridShiftSet::open(ctx, std::move(fp), actualName));
if (!set)
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(
+ ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return set;
#else
pj_log(ctx, PJ_LOG_ERROR,
- "TIFF grid, but TIFF support disabled in this build");
+ _("TIFF grid, but TIFF support disabled in this build"));
return nullptr;
#endif
}
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized vertical grid format");
+ pj_log(ctx, PJ_LOG_ERROR, _("Unrecognized vertical grid format"));
return nullptr;
}
// ---------------------------------------------------------------------------
bool VerticalShiftGridSet::reopen(PJ_CONTEXT *ctx) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
m_name.c_str());
auto newGS = open(ctx, m_name);
m_grids.clear();
@@ -1570,7 +1572,8 @@ NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
/* Read the header. */
/* -------------------------------------------------------------------- */
if (fp->read(header, sizeof(header)) != sizeof(header)) {
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
@@ -1589,8 +1592,9 @@ NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
if (*((int *)(header + 8)) != 12) {
pj_log(ctx, PJ_LOG_ERROR,
- "NTv1 grid shift file has wrong record count, corrupt?");
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ _("NTv1 grid shift file has wrong record count, corrupt?"));
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
@@ -1607,9 +1611,10 @@ NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
fabs(extent.south) <= M_PI + 1e-5 && extent.west < extent.east &&
extent.south < extent.north && extent.resX > 1e-10 &&
extent.resY > 1e-10)) {
- pj_log(ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s",
+ pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"),
filename.c_str());
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
const int columns = static_cast<int>(
@@ -1631,7 +1636,8 @@ bool NTv1Grid::valueAt(int x, int y, bool compensateNTConvention,
m_fp->seek(192 + 2 * sizeof(double) * (y * m_width + m_width - 1 - x));
if (m_fp->read(&two_doubles[0], sizeof(two_doubles)) !=
sizeof(two_doubles)) {
- proj_context_errno_set(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(m_ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return false;
}
if (IS_LSB) {
@@ -1692,7 +1698,8 @@ CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
/* Read the header. */
/* -------------------------------------------------------------------- */
if (fp->read(header, sizeof(header)) != sizeof(header)) {
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
@@ -1716,9 +1723,10 @@ CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
memcpy(&extent.resY, header + 120, 8);
if (!(fabs(extent.west) <= 4 * M_PI && fabs(extent.south) <= M_PI + 1e-5 &&
extent.resX > 1e-10 && extent.resY > 1e-10)) {
- pj_log(ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s",
+ pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"),
filename.c_str());
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
int width;
@@ -1726,7 +1734,8 @@ CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
memcpy(&width, header + 128, 4);
memcpy(&height, header + 132, 4);
if (width <= 0 || height <= 0) {
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
extent.east = extent.west + (width - 1) * extent.resX;
@@ -1744,7 +1753,8 @@ bool CTable2Grid::valueAt(int x, int y, bool compensateNTConvention,
float two_floats[2];
m_fp->seek(160 + 2 * sizeof(float) * (y * m_width + x));
if (m_fp->read(&two_floats[0], sizeof(two_floats)) != sizeof(two_floats)) {
- proj_context_errno_set(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(m_ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return false;
}
if (!IS_LSB) {
@@ -1828,7 +1838,8 @@ bool NTv2Grid::valueAt(int x, int y, bool compensateNTConvention,
4 * sizeof(float) *
(static_cast<unsigned long long>(y) * m_width + m_width - 1 - x));
if (m_fp->read(&two_float[0], sizeof(two_float)) != sizeof(two_float)) {
- proj_context_errno_set(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(m_ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return false;
}
if (m_mustSwap) {
@@ -1862,14 +1873,16 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx,
/* Read the header. */
/* -------------------------------------------------------------------- */
if (fpRaw->read(header, sizeof(header)) != sizeof(header)) {
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
constexpr int OFFSET_GS_TYPE = 56;
if (memcmp(header + OFFSET_GS_TYPE, "SECONDS", 7) != 0) {
- pj_log(ctx, PJ_LOG_ERROR, "Only GS_TYPE=SECONDS is supported");
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ pj_log(ctx, PJ_LOG_ERROR, _("Only GS_TYPE=SECONDS is supported"));
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
@@ -1899,12 +1912,14 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx,
for (unsigned subfile = 0; subfile < num_subfiles; subfile++) {
// Read header
if (fpRaw->read(header, sizeof(header)) != sizeof(header)) {
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(
+ ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
if (strncmp(header, "SUB_NAME", 8) != 0) {
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(
+ ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
@@ -1944,9 +1959,10 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx,
fabs(extent.south) <= M_PI + 1e-5 && extent.west < extent.east &&
extent.south < extent.north && extent.resX > 1e-10 &&
extent.resY > 1e-10)) {
- pj_log(ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s",
+ pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"),
filename.c_str());
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(
+ ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
const int columns = static_cast<int>(
@@ -1954,7 +1970,7 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx,
const int rows = static_cast<int>(
fabs((extent.north - extent.south) / extent.resY + 0.5) + 1);
- pj_log(ctx, PJ_LOG_DEBUG_MINOR,
+ pj_log(ctx, PJ_LOG_TRACE,
"NTv2 %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", gridName.c_str(),
columns, rows, extent.west * RAD_TO_DEG,
extent.south * RAD_TO_DEG, extent.east * RAD_TO_DEG,
@@ -1964,9 +1980,10 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx,
memcpy(&gs_count, header + OFFSET_GS_COUNT, 4);
if (gs_count / columns != static_cast<unsigned>(rows)) {
pj_log(ctx, PJ_LOG_ERROR,
- "GS_COUNT(%u) does not match expected cells (%dx%d)",
+ _("GS_COUNT(%u) does not match expected cells (%dx%d)"),
gs_count, columns, rows);
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(
+ ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return nullptr;
}
@@ -2018,7 +2035,7 @@ class GTiffHGridShiftSet : public HorizontalShiftGridSet {
}
bool reopen(PJ_CONTEXT *ctx) override {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
m_name.c_str());
m_grids.clear();
m_GTiffDataset.reset();
@@ -2119,8 +2136,7 @@ void GTiffHGrid::insertGrid(PJ_CONTEXT *ctx,
gridInserted = true;
break;
} else if (candidateParentExtent.intersects(extent)) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
- "Partially intersecting grids found!");
+ pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!");
}
}
if (!gridInserted) {
@@ -2161,11 +2177,11 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
const auto subfileType = grid->subfileType();
if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
if (ifd == 0) {
- pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType");
+ pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType"));
return nullptr;
} else {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
- "Ignoring IFD %d as it has a unsupported subfileType",
+ pj_log(ctx, PJ_LOG_DEBUG,
+ _("Ignoring IFD %d as it has a unsupported subfileType"),
ifd);
continue;
}
@@ -2174,11 +2190,12 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
if (grid->samplesPerPixel() < 2) {
if (ifd == 0) {
pj_log(ctx, PJ_LOG_ERROR,
- "At least 2 samples per pixel needed");
+ _("At least 2 samples per pixel needed"));
return nullptr;
} else {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
- "Ignoring IFD %d as it has not at least 2 samples", ifd);
+ pj_log(ctx, PJ_LOG_DEBUG,
+ _("Ignoring IFD %d as it has not at least 2 samples"),
+ ifd);
continue;
}
}
@@ -2209,13 +2226,13 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
// longitude_offset/latitude_offset can be ignored
// One could imagine to put the accuracy values in separate
// IFD for example
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ pj_log(ctx, PJ_LOG_DEBUG,
"Ignoring IFD %d as it has no "
"longitude_offset/latitude_offset channel",
ifd);
continue;
} else {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ pj_log(ctx, PJ_LOG_DEBUG,
"IFD 0 has channel descriptions, but no "
"longitude_offset/latitude_offset channel");
return nullptr;
@@ -2223,19 +2240,21 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
}
}
if (foundDescriptionForLatOffset && !foundDescriptionForLonOffset) {
- pj_log(ctx, PJ_LOG_ERROR,
- "Found latitude_offset channel, but not longitude_offset");
+ pj_log(
+ ctx, PJ_LOG_ERROR,
+ _("Found latitude_offset channel, but not longitude_offset"));
return nullptr;
} else if (foundDescriptionForLonOffset &&
!foundDescriptionForLatOffset) {
- pj_log(ctx, PJ_LOG_ERROR,
- "Found longitude_offset channel, but not latitude_offset");
+ pj_log(
+ ctx, PJ_LOG_ERROR,
+ _("Found longitude_offset channel, but not latitude_offset"));
return nullptr;
}
if (idxLatShift >= grid->samplesPerPixel() ||
idxLonShift >= grid->samplesPerPixel()) {
- pj_log(ctx, PJ_LOG_ERROR, "Invalid sample index");
+ pj_log(ctx, PJ_LOG_ERROR, _("Invalid sample index"));
return nullptr;
}
@@ -2249,7 +2268,7 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
positiveEast = true;
} else {
pj_log(ctx, PJ_LOG_ERROR,
- "Unsupported value %s for 'positive_value'",
+ _("Unsupported value %s for 'positive_value'"),
positiveValue.c_str());
return nullptr;
}
@@ -2264,7 +2283,7 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
grid->metadataItem("UNITTYPE", idxLonShift);
if (unitLatShift != unitLonShift) {
pj_log(ctx, PJ_LOG_ERROR,
- "Different unit for longitude and latitude offset");
+ _("Different unit for longitude and latitude offset"));
return nullptr;
}
if (!unitLatShift.empty()) {
@@ -2275,7 +2294,7 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
} else if (unitLatShift == "degree") {
convFactorToRadian = M_PI / 180.0;
} else {
- pj_log(ctx, PJ_LOG_ERROR, "Unsupported unit %s",
+ pj_log(ctx, PJ_LOG_ERROR, _("Unsupported unit %s"),
unitLatShift.c_str());
return nullptr;
}
@@ -2324,7 +2343,7 @@ HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
if (header_size != sizeof(header)) {
/* some files may be smaller that sizeof(header), eg 160, so */
ctx->last_errno = 0; /* don't treat as a persistent error */
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ pj_log(ctx, PJ_LOG_DEBUG,
"pj_gridinfo_init: short header read of %d bytes",
(int)header_size);
}
@@ -2367,23 +2386,24 @@ HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
auto set = std::unique_ptr<HorizontalShiftGridSet>(
GTiffHGridShiftSet::open(ctx, std::move(fp), actualName));
if (!set)
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(
+ ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return set;
#else
pj_log(ctx, PJ_LOG_ERROR,
- "TIFF grid, but TIFF support disabled in this build");
+ _("TIFF grid, but TIFF support disabled in this build"));
return nullptr;
#endif
}
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized horizontal grid format");
+ pj_log(ctx, PJ_LOG_ERROR, _("Unrecognized horizontal grid format"));
return nullptr;
}
// ---------------------------------------------------------------------------
bool HorizontalShiftGridSet::reopen(PJ_CONTEXT *ctx) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
m_name.c_str());
auto newGS = open(ctx, m_name);
m_grids.clear();
@@ -2460,7 +2480,7 @@ class GTiffGenericGridShiftSet : public GenericShiftGridSet {
}
bool reopen(PJ_CONTEXT *ctx) override {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
m_name.c_str());
m_grids.clear();
m_GTiffDataset.reset();
@@ -2558,8 +2578,7 @@ void GTiffGenericGrid::insertGrid(PJ_CONTEXT *ctx,
gridInserted = true;
break;
} else if (candidateParentExtent.intersects(extent)) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
- "Partially intersecting grids found!");
+ pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!");
}
}
if (!gridInserted) {
@@ -2628,11 +2647,11 @@ GTiffGenericGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
const auto subfileType = grid->subfileType();
if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
if (ifd == 0) {
- pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType");
+ pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType"));
return nullptr;
} else {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
- "Ignoring IFD %d as it has a unsupported subfileType",
+ pj_log(ctx, PJ_LOG_DEBUG,
+ _("Ignoring IFD %d as it has a unsupported subfileType"),
ifd);
continue;
}
@@ -2703,23 +2722,24 @@ GenericShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
auto set = std::unique_ptr<GenericShiftGridSet>(
GTiffGenericGridShiftSet::open(ctx, std::move(fp), actualName));
if (!set)
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(
+ ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return set;
#else
pj_log(ctx, PJ_LOG_ERROR,
- "TIFF grid, but TIFF support disabled in this build");
+ _("TIFF grid, but TIFF support disabled in this build"));
return nullptr;
#endif
}
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized generic grid format");
+ pj_log(ctx, PJ_LOG_ERROR, _("Unrecognized generic grid format"));
return nullptr;
}
// ---------------------------------------------------------------------------
bool GenericShiftGridSet::reopen(PJ_CONTEXT *ctx) {
- pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
m_name.c_str());
auto newGS = open(ctx, m_name);
m_grids.clear();
@@ -2785,8 +2805,10 @@ ListOfGenericGrids pj_generic_grid_init(PJ *P, const char *gridkey) {
auto gridSet = GenericShiftGridSet::open(P->ctx, gridname);
if (!gridSet) {
if (!canFail) {
- if (proj_context_errno(P->ctx) != PJD_ERR_NETWORK_ERROR) {
- proj_context_errno_set(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ if (proj_context_errno(P->ctx) !=
+ PROJ_ERR_OTHER_NETWORK_ERROR) {
+ proj_context_errno_set(
+ P->ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
return {};
}
@@ -2830,8 +2852,9 @@ static ListOfHGrids getListOfGridSets(PJ_CONTEXT *ctx, const char *grids) {
auto gridSet = HorizontalShiftGridSet::open(ctx, gridname);
if (!gridSet) {
if (!canFail) {
- if (proj_context_errno(ctx) != PJD_ERR_NETWORK_ERROR) {
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ if (proj_context_errno(ctx) != PROJ_ERR_OTHER_NETWORK_ERROR) {
+ proj_context_errno_set(
+ ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
return {};
}
@@ -3008,7 +3031,7 @@ static PJ_LP pj_hgrid_apply_internal(PJ_CONTEXT *ctx, PJ_LP in,
auto newGrid = findGrid(grids, lp, gridset);
if (newGrid == nullptr || newGrid == grid || newGrid->isNullGrid())
break;
- pj_log(ctx, PJ_LOG_DEBUG_MINOR, "Switching from grid %s to grid %s",
+ pj_log(ctx, PJ_LOG_TRACE, "Switching from grid %s to grid %s",
grid->name().c_str(), newGrid->name().c_str());
grid = newGrid;
extent = &(grid->extentAndRes());
@@ -3067,7 +3090,7 @@ PJ_LP pj_hgrid_apply(PJ_CONTEXT *ctx, const ListOfHGrids &grids, PJ_LP lp,
HorizontalShiftGridSet *gridset = nullptr;
const auto grid = findGrid(grids, lp, gridset);
if (!grid) {
- proj_context_errno_set(ctx, PJD_ERR_GRID_AREA);
+ proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
return out;
}
if (grid->isNullGrid()) {
@@ -3083,7 +3106,7 @@ PJ_LP pj_hgrid_apply(PJ_CONTEXT *ctx, const ListOfHGrids &grids, PJ_LP lp,
}
if (out.lam == HUGE_VAL || out.phi == HUGE_VAL)
- proj_context_errno_set(ctx, PJD_ERR_GRID_AREA);
+ proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
return out;
}
@@ -3099,7 +3122,7 @@ PJ_LP pj_hgrid_value(PJ *P, const ListOfHGrids &grids, PJ_LP lp) {
HorizontalShiftGridSet *gridset = nullptr;
const auto grid = findGrid(grids, lp, gridset);
if (!grid) {
- proj_context_errno_set(P->ctx, PJD_ERR_GRID_AREA);
+ proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
return out;
}
@@ -3107,8 +3130,9 @@ PJ_LP pj_hgrid_value(PJ *P, const ListOfHGrids &grids, PJ_LP lp) {
const auto &extent = grid->extentAndRes();
if (!extent.isGeographic) {
pj_log(P->ctx, PJ_LOG_ERROR,
- "Can only handle grids referenced in a geographic CRS");
- proj_context_errno_set(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ _("Can only handle grids referenced in a geographic CRS"));
+ proj_context_errno_set(P->ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return out;
}
@@ -3131,7 +3155,7 @@ PJ_LP pj_hgrid_value(PJ *P, const ListOfHGrids &grids, PJ_LP lp) {
}
if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) {
- proj_context_errno_set(P->ctx, PJD_ERR_GRID_AREA);
+ proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
}
return out;
@@ -3158,7 +3182,7 @@ static double read_vgrid_value(PJ_CONTEXT *ctx, const ListOfVGrids &grids,
}
}
if (!grid) {
- proj_context_errno_set(ctx, PJD_ERR_GRID_AREA);
+ proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
return HUGE_VAL;
}
if (grid->isNullGrid()) {
@@ -3168,8 +3192,9 @@ static double read_vgrid_value(PJ_CONTEXT *ctx, const ListOfVGrids &grids,
const auto &extent = grid->extentAndRes();
if (!extent.isGeographic) {
pj_log(ctx, PJ_LOG_ERROR,
- "Can only handle grids referenced in a geographic CRS");
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ _("Can only handle grids referenced in a geographic CRS"));
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return HUGE_VAL;
}
@@ -3200,8 +3225,8 @@ static double read_vgrid_value(PJ_CONTEXT *ctx, const ListOfVGrids &grids,
int grid_ix = static_cast<int>(lround(floor(grid_x)));
if (!(grid_ix >= 0 && grid_ix < grid->width())) {
// in the unlikely case we end up here...
- pj_log(ctx, PJ_LOG_ERROR, "grid_ix not in grid");
- proj_context_errno_set(ctx, PJD_ERR_GRID_AREA);
+ pj_log(ctx, PJ_LOG_ERROR, _("grid_ix not in grid"));
+ proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
return HUGE_VAL;
}
int grid_iy = static_cast<int>(lround(floor(grid_y)));
@@ -3268,9 +3293,10 @@ static double read_vgrid_value(PJ_CONTEXT *ctx, const ListOfVGrids &grids,
total_weight += weight;
n_weights++;
}
- if (n_weights == 0)
+ if (n_weights == 0) {
+ proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA);
value = HUGE_VAL;
- else if (n_weights != 4)
+ } else if (n_weights != 4)
value /= total_weight;
return value * vmultiplier;
@@ -3309,8 +3335,10 @@ ListOfVGrids pj_vgrid_init(PJ *P, const char *gridkey) {
auto gridSet = VerticalShiftGridSet::open(P->ctx, gridname);
if (!gridSet) {
if (!canFail) {
- if (proj_context_errno(P->ctx) != PJD_ERR_NETWORK_ERROR) {
- proj_context_errno_set(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ if (proj_context_errno(P->ctx) !=
+ PROJ_ERR_OTHER_NETWORK_ERROR) {
+ proj_context_errno_set(
+ P->ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
return {};
}
@@ -3379,7 +3407,8 @@ bool pj_bilinear_interpolation_three_samples(
if (!extent.isGeographic) {
pj_log(ctx, PJ_LOG_ERROR,
"Can only handle grids referenced in a geographic CRS");
- proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_context_errno_set(ctx,
+ PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
return false;
}
diff --git a/src/init.cpp b/src/init.cpp
index 1e89402d..a0d727cb 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -30,7 +30,6 @@
#define PJ_LIB__
#include <ctype.h>
-#include <errno.h>
#include <math.h>
#include <stddef.h>
#include <stdio.h>
@@ -55,7 +54,7 @@ static paralist *string_to_paralist (PJ_CONTEXT *ctx, char *definition) {
/* Keep a handle to the start of the list, so we have something to return */
auto param = pj_mkparam_ws (c, &c);
if (nullptr==param) {
- free_params (ctx, first, ENOMEM);
+ free_params (ctx, first, PROJ_ERR_OTHER /*ENOMEM*/);
return nullptr;
}
if (nullptr==last) {
@@ -104,7 +103,8 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) {
/* Locate the name of the section we search for */
section = strrchr(fname, ':');
if (nullptr==section) {
- proj_context_errno_set (ctx, PJD_ERR_NO_COLON_IN_INIT_STRING);
+ pj_log(ctx, PJ_LOG_ERROR, _("Missing colon in +init"));
+ proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
free (fname);
return nullptr;
}
@@ -117,8 +117,9 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) {
auto file = NS_PROJ::FileManager::open_resource_file(ctx, fname);
if (nullptr==file) {
+ pj_log(ctx, PJ_LOG_ERROR, _("Cannot open %s"), fname);
+ proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
free (fname);
- proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE);
return nullptr;
}
@@ -131,8 +132,9 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) {
line = file->read_line(MAX_LINE_LENGTH, maxLenReached, eofReached);
/* End of file? */
if (maxLenReached || eofReached) {
+ pj_log(ctx, PJ_LOG_ERROR, _("Invalid content for %s"), fname);
+ proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
free (fname);
- proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE);
return nullptr;
}
@@ -441,7 +443,8 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
ctx->last_errno = 0;
if (argc <= 0) {
- proj_context_errno_set (ctx, PJD_ERR_NO_ARGS);
+ pj_log(ctx, PJ_LOG_ERROR, _("No arguments"));
+ proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_MISSING_ARG);
return nullptr;
}
@@ -455,13 +458,15 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
/* can't have nested pipelines directly */
if (n_pipelines > 1) {
- proj_context_errno_set (ctx, PJD_ERR_MALFORMED_PIPELINE);
+ pj_log(ctx, PJ_LOG_ERROR, _("Nested pipelines are not supported"));
+ proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_WRONG_SYNTAX);
return nullptr;
}
/* don't allow more than one +init in non-pipeline operations */
if (n_pipelines == 0 && n_inits > 1) {
- proj_context_errno_set (ctx, PJD_ERR_TOO_MANY_INITS);
+ pj_log(ctx, PJ_LOG_ERROR, _("Too many inits"));
+ proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_WRONG_SYNTAX);
return nullptr;
}
@@ -469,14 +474,14 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
/* put arguments into internal linked list */
start = curr = pj_mkparam(argv[0]);
if (!curr) {
- free_params (ctx, start, ENOMEM);
+ free_params (ctx, start, PROJ_ERR_OTHER /*ENOMEM*/);
return nullptr;
}
for (i = 1; i < argc; ++i) {
curr->next = pj_mkparam(argv[i]);
if (!curr->next) {
- free_params (ctx, start, ENOMEM);
+ free_params (ctx, start, PROJ_ERR_OTHER /*ENOMEM*/);
return nullptr;
}
curr = curr->next;
@@ -491,7 +496,7 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
if (init && n_pipelines == 0) {
init = pj_expand_init_internal (ctx, init, allow_init_epsg);
if (!init) {
- free_params (ctx, start, PJD_ERR_NO_ARGS);
+ free_params (ctx, start, PROJ_ERR_INVALID_OP_WRONG_SYNTAX);
return nullptr;
}
}
@@ -503,19 +508,22 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
/* Find projection selection */
curr = pj_param_exists (start, "proj");
if (nullptr==curr) {
- free_params (ctx, start, PJD_ERR_PROJ_NOT_NAMED);
+ pj_log(ctx, PJ_LOG_ERROR, _("Missing proj"));
+ free_params (ctx, start, PROJ_ERR_INVALID_OP_MISSING_ARG);
return nullptr;
}
name = curr->param;
if (strlen (name) < 6) {
- free_params (ctx, start, PJD_ERR_PROJ_NOT_NAMED);
+ pj_log(ctx, PJ_LOG_ERROR, _("Invalid value for proj"));
+ free_params (ctx, start, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
return nullptr;
}
name += 5;
proj = locate_constructor (name);
if (nullptr==proj) {
- free_params (ctx, start, PJD_ERR_UNKNOWN_PROJECTION_ID);
+ pj_log(ctx, PJ_LOG_ERROR, _("Unknown projection"));
+ free_params (ctx, start, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
return nullptr;
}
@@ -524,7 +532,7 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
/* Allocate projection structure */
PIN = proj(nullptr);
if (nullptr==PIN) {
- free_params (ctx, start, ENOMEM);
+ free_params (ctx, start, PROJ_ERR_OTHER /*ENOMEM*/);
return nullptr;
}
@@ -550,11 +558,11 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
if (err) {
/* Didn't get an ellps, but doesn't need one: Get a free WGS84 */
if (PIN->need_ellps) {
- pj_log (ctx, PJ_LOG_DEBUG_MINOR, "pj_init_ctx: Must specify ellipsoid or sphere");
+ pj_log (ctx, PJ_LOG_ERROR, _("pj_init_ctx: Must specify ellipsoid or sphere"));
return pj_default_destructor (PIN, proj_errno(PIN));
}
else {
- if (PJD_ERR_MAJOR_AXIS_NOT_GIVEN==proj_errno (PIN))
+ if (PIN->a == 0)
proj_errno_reset (PIN);
PIN->f = 1.0/298.257223563;
PIN->a = 6378137.0;
@@ -564,7 +572,7 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
PIN->a_orig = PIN->a;
PIN->es_orig = PIN->es;
if (pj_calc_ellipsoid_params (PIN, PIN->a, PIN->es))
- return pj_default_destructor (PIN, PJD_ERR_INVALID_ECCENTRICITY);
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
/* Now that we have ellipse information check for WGS84 datum */
if( PIN->datum_type == PJD_3PARAM
@@ -596,7 +604,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
/* when correcting longitudes around it */
/* The test is written this way to error on long_wrap_center "=" NaN */
if( !(fabs(PIN->long_wrap_center) < 10 * M_TWOPI) )
- return pj_default_destructor (PIN, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT);
+ {
+ proj_log_error(PIN, _("Invalid value for lon_wrap"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
}
/* Axis orientation */
@@ -605,12 +616,18 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
const char *axis_legal = "ewnsud";
const char *axis_arg = pj_param(ctx, start,"saxis").s;
if( strlen(axis_arg) != 3 )
- return pj_default_destructor (PIN, PJD_ERR_AXIS);
+ {
+ proj_log_error(PIN, _("Invalid value for axis"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if( strchr( axis_legal, axis_arg[0] ) == nullptr
|| strchr( axis_legal, axis_arg[1] ) == nullptr
|| strchr( axis_legal, axis_arg[2] ) == nullptr)
- return pj_default_destructor (PIN, PJD_ERR_AXIS);
+ {
+ proj_log_error(PIN, _("Invalid value for axis"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
/* TODO: it would be nice to validate we don't have on axis repeated */
strcpy( PIN->axis, axis_arg );
@@ -622,7 +639,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
/* Central latitude */
PIN->phi0 = pj_param(ctx, start, "rlat_0").f;
if( fabs(PIN->phi0) > M_HALFPI )
- return pj_default_destructor (PIN, PJD_ERR_LAT_LARGER_THAN_90);
+ {
+ proj_log_error(PIN, _("Invalid value for lat_0: |lat_0| should be <= 90°"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
/* False easting and northing */
PIN->x0 = pj_param(ctx, start, "dx_0").f;
@@ -638,7 +658,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
else
PIN->k0 = 1.;
if (PIN->k0 <= 0.)
- return pj_default_destructor (PIN, PJD_ERR_K_LESS_THAN_ZERO);
+ {
+ proj_log_error(PIN, _("Invalid value for k/k_0: it should be > 0"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
/* Set units */
units = pj_list_linear_units();
@@ -646,7 +669,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
if ((name = pj_param(ctx, start, "sunits").s) != nullptr) {
for (i = 0; (s = units[i].id) && strcmp(name, s) ; ++i) ;
if (!s)
- return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_UNIT_ID);
+ {
+ proj_log_error(PIN, _("Invalid value for units"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
s = units[i].to_meter;
}
if (s || (s = pj_param(ctx, start, "sto_meter").s)) {
@@ -657,11 +683,17 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
++s;
double denom = pj_strtod(s, nullptr);
if (denom == 0.0)
- return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0);
+ {
+ proj_log_error(PIN, _("Invalid value for to_meter donominator"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
PIN->to_meter /= denom;
}
if (PIN->to_meter <= 0.0)
- return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0);
+ {
+ proj_log_error(PIN, _("Invalid value for to_meter"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
PIN->fr_meter = 1 / PIN->to_meter;
} else
@@ -672,7 +704,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
if ((name = pj_param(ctx, start, "svunits").s) != nullptr) {
for (i = 0; (s = units[i].id) && strcmp(name, s) ; ++i) ;
if (!s)
- return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_UNIT_ID);
+ {
+ proj_log_error(PIN, _("Invalid value for vunits"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
s = units[i].to_meter;
}
if (s || (s = pj_param(ctx, start, "svto_meter").s)) {
@@ -683,11 +718,17 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
++s;
double denom = pj_strtod(s, nullptr);
if (denom == 0.0)
- return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0);
+ {
+ proj_log_error(PIN, _("Invalid value for vto_meter donominator"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
PIN->vto_meter /= denom;
}
if (PIN->vto_meter <= 0.0)
- return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0);
+ {
+ proj_log_error(PIN, _("Invalid value for vto_meter"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
PIN->vfr_meter = 1. / PIN->vto_meter;
} else {
PIN->vto_meter = PIN->to_meter;
@@ -716,7 +757,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
value = name;
if (!value)
- return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_PRIME_MERIDIAN);
+ {
+ proj_log_error(PIN, _("Invalid value for pm"));
+ return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
PIN->from_greenwich = dmstor_ctx(ctx,value,nullptr);
}
else
@@ -725,7 +769,7 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all
/* Private object for the geodesic functions */
PIN->geod = static_cast<struct geod_geodesic*>(calloc (1, sizeof (struct geod_geodesic)));
if (nullptr==PIN->geod)
- return pj_default_destructor (PIN, ENOMEM);
+ return pj_default_destructor (PIN, PROJ_ERR_OTHER /*ENOMEM*/);
geod_init(PIN->geod, PIN->a, (1 - sqrt (1 - PIN->es)));
/* Projection specific initialization */
diff --git a/src/internal.cpp b/src/internal.cpp
index 175ffa9b..b96e2160 100644
--- a/src/internal.cpp
+++ b/src/internal.cpp
@@ -89,12 +89,9 @@ chained calls starting out with a call to its 2D interface.
coo.lp = pj_inv (coo.xy, P);
return coo;
case PJ_IDENT:
- return coo;
- default:
break;
}
- proj_errno_set (P, EINVAL);
- return proj_coord_error ();
+ return coo;
}
@@ -119,12 +116,9 @@ chained calls starting out with a call to its 3D interface.
coo.lpz = pj_inv3d (coo.xyz, P);
return coo;
case PJ_IDENT:
- return coo;
- default:
break;
}
- proj_errno_set (P, EINVAL);
- return proj_coord_error ();
+ return coo;
}
/**************************************************************************************/
@@ -416,86 +410,3 @@ to that context.
return;
errno = err;
}
-
-/* logging */
-
-/* pj_vlog resides in pj_log.c and relates to pj_log as vsprintf relates to sprintf */
-void pj_vlog( PJ_CONTEXT *ctx, int level, const char *fmt, va_list args );
-
-
-/***************************************************************************************/
-PJ_LOG_LEVEL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level) {
-/****************************************************************************************
- Set logging level 0-3. Higher number means more debug info. 0 turns it off
-****************************************************************************************/
- PJ_LOG_LEVEL previous;
- if (nullptr==ctx)
- ctx = pj_get_default_ctx();
- if (nullptr==ctx)
- return PJ_LOG_TELL;
- previous = static_cast<PJ_LOG_LEVEL>(abs (ctx->debug_level));
- if (PJ_LOG_TELL==log_level)
- return previous;
- ctx->debug_level = log_level;
- return previous;
-}
-
-
-/*****************************************************************************/
-void proj_log_error (PJ *P, const char *fmt, ...) {
-/******************************************************************************
- For reporting the most severe events.
-******************************************************************************/
- va_list args;
- va_start( args, fmt );
- pj_vlog (pj_get_ctx (P), PJ_LOG_ERROR , fmt, args);
- va_end( args );
-}
-
-
-/*****************************************************************************/
-void proj_log_debug (PJ *P, const char *fmt, ...) {
-/******************************************************************************
- For reporting debugging information.
-******************************************************************************/
- va_list args;
- va_start( args, fmt );
- pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MAJOR , fmt, args);
- va_end( args );
-}
-
-/*****************************************************************************/
-void proj_context_log_debug (PJ_CONTEXT *ctx, const char *fmt, ...) {
-/******************************************************************************
- For reporting debugging information.
-******************************************************************************/
- va_list args;
- va_start( args, fmt );
- pj_vlog (ctx, PJ_LOG_DEBUG_MAJOR , fmt, args);
- va_end( args );
-}
-
-/*****************************************************************************/
-void proj_log_trace (PJ *P, const char *fmt, ...) {
-/******************************************************************************
- For reporting embarrassingly detailed debugging information.
-******************************************************************************/
- va_list args;
- va_start( args, fmt );
- pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MINOR , fmt, args);
- va_end( args );
-}
-
-
-/*****************************************************************************/
-void proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf) {
-/******************************************************************************
- Put a new logging function into P's context. The opaque object app_data is
- passed as first arg at each call to the logger
-******************************************************************************/
- if (nullptr==ctx)
- ctx = pj_get_default_ctx ();
- ctx->logger_app_data = app_data;
- if (nullptr!=logf)
- ctx->logger = logf;
-}
diff --git a/src/inv.cpp b/src/inv.cpp
index 626c9ee5..b4a42189 100644
--- a/src/inv.cpp
+++ b/src/inv.cpp
@@ -38,7 +38,7 @@
static PJ_COORD inv_prepare (PJ *P, PJ_COORD coo) {
if (coo.v[0] == HUGE_VAL || coo.v[1] == HUGE_VAL || coo.v[2] == HUGE_VAL) {
- proj_errno_set (P, PJD_ERR_INVALID_X_OR_Y);
+ proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error ();
}
@@ -97,7 +97,7 @@ static PJ_COORD inv_prepare (PJ *P, PJ_COORD coo) {
static PJ_COORD inv_finalize (PJ *P, PJ_COORD coo) {
if (coo.xyz.x == HUGE_VAL) {
- proj_errno_set (P, PJD_ERR_INVALID_X_OR_Y);
+ proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error ();
}
@@ -163,7 +163,7 @@ PJ_LP pj_inv(PJ_XY xy, PJ *P) {
else if (P->inv4d)
coo = P->inv4d (coo, P);
else {
- proj_errno_set (P, EINVAL);
+ proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP);
return proj_coord_error ().lp;
}
if (HUGE_VAL==coo.v[0])
@@ -197,7 +197,7 @@ PJ_LPZ pj_inv3d (PJ_XYZ xyz, PJ *P) {
else if (P->inv)
coo.lp = P->inv (coo.xy, P);
else {
- proj_errno_set (P, EINVAL);
+ proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP);
return proj_coord_error ().lpz;
}
if (HUGE_VAL==coo.v[0])
@@ -227,7 +227,7 @@ PJ_COORD pj_inv4d (PJ_COORD coo, PJ *P) {
else if (P->inv)
coo.lp = P->inv (coo.xy, P);
else {
- proj_errno_set (P, EINVAL);
+ proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP);
return proj_coord_error ();
}
if (HUGE_VAL==coo.v[0])
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index 6bc1f166..cb0c113a 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -82,7 +82,7 @@ static void PROJ_NO_INLINE proj_log_error(PJ_CONTEXT *ctx, const char *function,
auto previous_errno = proj_context_errno(ctx);
if (previous_errno == 0) {
// only set errno if it wasn't set deeper down the call stack
- proj_context_errno_set(ctx, PJD_ERR_GENERIC_ERROR);
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER);
}
}
@@ -344,6 +344,7 @@ const char *proj_context_get_database_metadata(PJ_CONTEXT *ctx,
const char *key) {
SANITIZE_CTX(ctx);
if (!key) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -375,6 +376,7 @@ PJ_GUESSED_WKT_DIALECT proj_context_guess_wkt_dialect(PJ_CONTEXT *ctx,
const char *wkt) {
(void)ctx;
if (!wkt) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return PJ_GUESSED_NOT_WKT;
}
@@ -423,6 +425,7 @@ static const char *getOptionValue(const char *option,
PJ *proj_clone(PJ_CONTEXT *ctx, const PJ *obj) {
SANITIZE_CTX(ctx);
if (!obj) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -458,6 +461,7 @@ PJ *proj_clone(PJ_CONTEXT *ctx, const PJ *obj) {
PJ *proj_create(PJ_CONTEXT *ctx, const char *text) {
SANITIZE_CTX(ctx);
if (!text) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -534,6 +538,7 @@ PJ *proj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt,
PROJ_STRING_LIST *out_grammar_errors) {
SANITIZE_CTX(ctx);
if (!wkt) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -647,6 +652,7 @@ PJ *proj_create_from_database(PJ_CONTEXT *ctx, const char *auth_name,
const char *const *options) {
SANITIZE_CTX(ctx);
if (!auth_name || !code) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -752,6 +758,7 @@ int proj_uom_get_info_from_database(PJ_CONTEXT *ctx, const char *auth_name,
SANITIZE_CTX(ctx);
if (!auth_name || !code) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -804,6 +811,7 @@ int PROJ_DLL proj_grid_get_info_from_database(
int *out_direct_download, int *out_open_license, int *out_available) {
SANITIZE_CTX(ctx);
if (!grid_name) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -863,6 +871,7 @@ PJ_OBJ_LIST *proj_query_geodetic_crs_from_datum(PJ_CONTEXT *ctx,
const char *crs_type) {
SANITIZE_CTX(ctx);
if (!datum_auth_name || !datum_code) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -1200,6 +1209,7 @@ int proj_is_deprecated(const PJ *obj) {
PJ_OBJ_LIST *proj_get_non_deprecated(PJ_CONTEXT *ctx, const PJ *obj) {
SANITIZE_CTX(ctx);
if (!obj) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -1230,6 +1240,7 @@ static int proj_is_equivalent_to_internal(PJ_CONTEXT *ctx, const PJ *obj,
if (!obj || !other) {
if (ctx) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
}
return false;
@@ -1435,6 +1446,7 @@ const char *proj_as_wkt(PJ_CONTEXT *ctx, const PJ *obj, PJ_WKT_TYPE type,
const char *const *options) {
SANITIZE_CTX(ctx);
if (!obj) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -1536,6 +1548,7 @@ const char *proj_as_proj_string(PJ_CONTEXT *ctx, const PJ *obj,
const char *const *options) {
SANITIZE_CTX(ctx);
if (!obj) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -1625,6 +1638,7 @@ const char *proj_as_projjson(PJ_CONTEXT *ctx, const PJ *obj,
const char *const *options) {
SANITIZE_CTX(ctx);
if (!obj) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -1784,6 +1798,7 @@ int proj_get_area_of_use(PJ_CONTEXT *ctx, const PJ *obj,
static const GeodeticCRS *extractGeodeticCRS(PJ_CONTEXT *ctx, const PJ *crs,
const char *fname) {
if (!crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, fname, "missing required input");
return nullptr;
}
@@ -1841,6 +1856,7 @@ PJ *proj_crs_get_geodetic_crs(PJ_CONTEXT *ctx, const PJ *crs) {
PJ *proj_crs_get_sub_crs(PJ_CONTEXT *ctx, const PJ *crs, int index) {
SANITIZE_CTX(ctx);
if (!crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -1875,6 +1891,7 @@ PJ *proj_crs_create_bound_crs(PJ_CONTEXT *ctx, const PJ *base_crs,
const PJ *hub_crs, const PJ *transformation) {
SANITIZE_CTX(ctx);
if (!base_crs || !hub_crs || !transformation) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -1934,6 +1951,7 @@ PJ *proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, const PJ *crs,
const char *const *options) {
SANITIZE_CTX(ctx);
if (!crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -1996,6 +2014,7 @@ PJ *proj_crs_create_bound_vertical_crs(PJ_CONTEXT *ctx, const PJ *vert_crs,
const char *grid_name) {
SANITIZE_CTX(ctx);
if (!vert_crs || !hub_geographic_3D_crs || !grid_name) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -2122,6 +2141,7 @@ int proj_ellipsoid_get_parameters(PJ_CONTEXT *ctx, const PJ *ellipsoid,
double *out_inv_flattening) {
SANITIZE_CTX(ctx);
if (!ellipsoid) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return FALSE;
}
@@ -2203,6 +2223,7 @@ int proj_prime_meridian_get_parameters(PJ_CONTEXT *ctx,
const char **out_unit_name) {
SANITIZE_CTX(ctx);
if (!prime_meridian) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -2288,6 +2309,7 @@ PJ *proj_get_source_crs(PJ_CONTEXT *ctx, const PJ *obj) {
PJ *proj_get_target_crs(PJ_CONTEXT *ctx, const PJ *obj) {
SANITIZE_CTX(ctx);
if (!obj) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -2372,6 +2394,7 @@ PJ_OBJ_LIST *proj_identify(PJ_CONTEXT *ctx, const PJ *obj,
int **out_confidence) {
SANITIZE_CTX(ctx);
if (!obj) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -2468,6 +2491,7 @@ PROJ_STRING_LIST proj_get_codes_from_database(PJ_CONTEXT *ctx,
int allow_deprecated) {
SANITIZE_CTX(ctx);
if (!auth_name) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -2824,6 +2848,7 @@ void proj_unit_list_destroy(PROJ_UNIT_INFO **list) {
PJ *proj_crs_get_coordoperation(PJ_CONTEXT *ctx, const PJ *crs) {
SANITIZE_CTX(ctx);
if (!crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -2868,6 +2893,7 @@ int proj_coordoperation_get_method_info(PJ_CONTEXT *ctx,
const char **out_method_code) {
SANITIZE_CTX(ctx);
if (!coordoperation) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -3416,6 +3442,7 @@ PJ *proj_create_compound_crs(PJ_CONTEXT *ctx, const char *crs_name,
SANITIZE_CTX(ctx);
if (!horiz_crs || !vert_crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -3458,6 +3485,7 @@ PJ *proj_create_compound_crs(PJ_CONTEXT *ctx, const char *crs_name,
PJ PROJ_DLL *proj_alter_name(PJ_CONTEXT *ctx, const PJ *obj, const char *name) {
SANITIZE_CTX(ctx);
if (!obj || !name) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -3495,6 +3523,7 @@ PJ PROJ_DLL *proj_alter_id(PJ_CONTEXT *ctx, const PJ *obj,
const char *auth_name, const char *code) {
SANITIZE_CTX(ctx);
if (!obj || !auth_name || !code) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -3535,6 +3564,7 @@ PJ *proj_crs_alter_geodetic_crs(PJ_CONTEXT *ctx, const PJ *obj,
const PJ *new_geod_crs) {
SANITIZE_CTX(ctx);
if (!obj || !new_geod_crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -3648,6 +3678,7 @@ PJ *proj_crs_alter_cs_linear_unit(PJ_CONTEXT *ctx, const PJ *obj,
const char *unit_code) {
SANITIZE_CTX(ctx);
if (!obj) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -3700,6 +3731,7 @@ PJ *proj_crs_alter_parameters_linear_unit(PJ_CONTEXT *ctx, const PJ *obj,
int convert_to_new_unit) {
SANITIZE_CTX(ctx);
if (!obj) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -3745,6 +3777,7 @@ PJ *proj_crs_promote_to_3D(PJ_CONTEXT *ctx, const char *crs_3D_name,
const PJ *crs_2D) {
SANITIZE_CTX(ctx);
if (!crs_2D) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -3804,6 +3837,7 @@ PJ *proj_crs_create_projected_3D_crs_from_2D(PJ_CONTEXT *ctx,
const PJ *geog_3D_crs) {
SANITIZE_CTX(ctx);
if (!projected_2D_crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -3889,6 +3923,7 @@ PJ *proj_crs_demote_to_2D(PJ_CONTEXT *ctx, const char *crs_2D_name,
const PJ *crs_3D) {
SANITIZE_CTX(ctx);
if (!crs_3D) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -4097,6 +4132,7 @@ PJ *proj_create_transformation(PJ_CONTEXT *ctx, const char *name,
double accuracy) {
SANITIZE_CTX(ctx);
if (!source_crs || !target_crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -4182,6 +4218,7 @@ PJ *proj_convert_conversion_to_other_method(PJ_CONTEXT *ctx,
const char *new_method_name) {
SANITIZE_CTX(ctx);
if (!conversion) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -4566,6 +4603,7 @@ PJ *proj_create_projected_crs(PJ_CONTEXT *ctx, const char *crs_name,
const PJ *coordinate_system) {
SANITIZE_CTX(ctx);
if (!geodetic_crs || !conversion || !coordinate_system) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -6897,6 +6935,7 @@ int proj_coordoperation_is_instantiable(PJ_CONTEXT *ctx,
const PJ *coordoperation) {
SANITIZE_CTX(ctx);
if (!coordoperation) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -6942,6 +6981,7 @@ int proj_coordoperation_has_ballpark_transformation(PJ_CONTEXT *ctx,
const PJ *coordoperation) {
SANITIZE_CTX(ctx);
if (!coordoperation) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -6968,6 +7008,7 @@ int proj_coordoperation_get_param_count(PJ_CONTEXT *ctx,
const PJ *coordoperation) {
SANITIZE_CTX(ctx);
if (!coordoperation) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -6996,6 +7037,7 @@ int proj_coordoperation_get_param_index(PJ_CONTEXT *ctx,
const char *name) {
SANITIZE_CTX(ctx);
if (!coordoperation || !name) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return -1;
}
@@ -7057,6 +7099,7 @@ int proj_coordoperation_get_param(
const char **out_unit_code, const char **out_unit_category) {
SANITIZE_CTX(ctx);
if (!coordoperation) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -7186,6 +7229,7 @@ int proj_coordoperation_get_towgs84_values(PJ_CONTEXT *ctx,
int emit_error_if_incompatible) {
SANITIZE_CTX(ctx);
if (!coordoperation) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -7225,6 +7269,7 @@ int proj_coordoperation_get_grid_used_count(PJ_CONTEXT *ctx,
const PJ *coordoperation) {
SANITIZE_CTX(ctx);
if (!coordoperation) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -7418,6 +7463,7 @@ void proj_operation_factory_context_set_desired_accuracy(
double accuracy) {
SANITIZE_CTX(ctx);
if (!factory_ctx) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return;
}
@@ -7449,6 +7495,7 @@ void proj_operation_factory_context_set_area_of_interest(
double north_lat_degree) {
SANITIZE_CTX(ctx);
if (!factory_ctx) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return;
}
@@ -7478,6 +7525,7 @@ void proj_operation_factory_context_set_crs_extent_use(
PROJ_CRS_EXTENT_USE use) {
SANITIZE_CTX(ctx);
if (!factory_ctx) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return;
}
@@ -7527,6 +7575,7 @@ void PROJ_DLL proj_operation_factory_context_set_spatial_criterion(
PROJ_SPATIAL_CRITERION criterion) {
SANITIZE_CTX(ctx);
if (!factory_ctx) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return;
}
@@ -7564,6 +7613,7 @@ void PROJ_DLL proj_operation_factory_context_set_grid_availability_use(
PROJ_GRID_AVAILABILITY_USE use) {
SANITIZE_CTX(ctx);
if (!factory_ctx) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return;
}
@@ -7614,6 +7664,7 @@ void proj_operation_factory_context_set_use_proj_alternative_grid_names(
int usePROJNames) {
SANITIZE_CTX(ctx);
if (!factory_ctx) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return;
}
@@ -7650,6 +7701,7 @@ void proj_operation_factory_context_set_allow_use_intermediate_crs(
PROJ_INTERMEDIATE_CRS_USE use) {
SANITIZE_CTX(ctx);
if (!factory_ctx) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return;
}
@@ -7691,6 +7743,7 @@ void proj_operation_factory_context_set_allowed_intermediate_crs(
const char *const *list_of_auth_name_codes) {
SANITIZE_CTX(ctx);
if (!factory_ctx) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return;
}
@@ -7720,6 +7773,7 @@ void PROJ_DLL proj_operation_factory_context_set_discard_superseded(
PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int discard) {
SANITIZE_CTX(ctx);
if (!factory_ctx) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return;
}
@@ -7743,6 +7797,7 @@ void PROJ_DLL proj_operation_factory_context_set_allow_ballpark_transformations(
PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int allow) {
SANITIZE_CTX(ctx);
if (!factory_ctx) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return;
}
@@ -7840,6 +7895,7 @@ proj_create_operations(PJ_CONTEXT *ctx, const PJ *source_crs,
const PJ_OPERATION_FACTORY_CONTEXT *operationContext) {
SANITIZE_CTX(ctx);
if (!source_crs || !target_crs || !operationContext) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -7951,6 +8007,7 @@ int proj_list_get_count(const PJ_OBJ_LIST *result) {
PJ *proj_list_get(PJ_CONTEXT *ctx, const PJ_OBJ_LIST *result, int index) {
SANITIZE_CTX(ctx);
if (!result) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -7984,6 +8041,7 @@ double proj_coordoperation_get_accuracy(PJ_CONTEXT *ctx,
const PJ *coordoperation) {
SANITIZE_CTX(ctx);
if (!coordoperation) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return -1;
}
@@ -8024,6 +8082,7 @@ double proj_coordoperation_get_accuracy(PJ_CONTEXT *ctx,
PJ *proj_crs_get_datum(PJ_CONTEXT *ctx, const PJ *crs) {
SANITIZE_CTX(ctx);
if (!crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -8060,6 +8119,7 @@ PJ *proj_crs_get_datum(PJ_CONTEXT *ctx, const PJ *crs) {
PJ *proj_crs_get_datum_ensemble(PJ_CONTEXT *ctx, const PJ *crs) {
SANITIZE_CTX(ctx);
if (!crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -8088,6 +8148,7 @@ int proj_datum_ensemble_get_member_count(PJ_CONTEXT *ctx,
const PJ *datum_ensemble) {
SANITIZE_CTX(ctx);
if (!datum_ensemble) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return 0;
}
@@ -8114,6 +8175,7 @@ double proj_datum_ensemble_get_accuracy(PJ_CONTEXT *ctx,
const PJ *datum_ensemble) {
SANITIZE_CTX(ctx);
if (!datum_ensemble) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return -1;
}
@@ -8152,6 +8214,7 @@ PJ *proj_datum_ensemble_get_member(PJ_CONTEXT *ctx, const PJ *datum_ensemble,
int member_index) {
SANITIZE_CTX(ctx);
if (!datum_ensemble) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -8191,6 +8254,7 @@ PJ *proj_datum_ensemble_get_member(PJ_CONTEXT *ctx, const PJ *datum_ensemble,
PJ *proj_crs_get_datum_forced(PJ_CONTEXT *ctx, const PJ *crs) {
SANITIZE_CTX(ctx);
if (!crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -8225,6 +8289,7 @@ double proj_dynamic_datum_get_frame_reference_epoch(PJ_CONTEXT *ctx,
const PJ *datum) {
SANITIZE_CTX(ctx);
if (!datum) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return -1;
}
@@ -8259,6 +8324,7 @@ double proj_dynamic_datum_get_frame_reference_epoch(PJ_CONTEXT *ctx,
PJ *proj_crs_get_coordinate_system(PJ_CONTEXT *ctx, const PJ *crs) {
SANITIZE_CTX(ctx);
if (!crs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -8281,6 +8347,7 @@ PJ *proj_crs_get_coordinate_system(PJ_CONTEXT *ctx, const PJ *crs) {
PJ_COORDINATE_SYSTEM_TYPE proj_cs_get_type(PJ_CONTEXT *ctx, const PJ *cs) {
SANITIZE_CTX(ctx);
if (!cs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return PJ_CS_TYPE_UNKNOWN;
}
@@ -8330,6 +8397,7 @@ PJ_COORDINATE_SYSTEM_TYPE proj_cs_get_type(PJ_CONTEXT *ctx, const PJ *cs) {
int proj_cs_get_axis_count(PJ_CONTEXT *ctx, const PJ *cs) {
SANITIZE_CTX(ctx);
if (!cs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return -1;
}
@@ -8373,6 +8441,7 @@ int proj_cs_get_axis_info(PJ_CONTEXT *ctx, const PJ *cs, int index,
const char **out_unit_code) {
SANITIZE_CTX(ctx);
if (!cs) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -8526,6 +8595,7 @@ PJ *proj_coordoperation_create_inverse(PJ_CONTEXT *ctx, const PJ *obj) {
SANITIZE_CTX(ctx);
if (!obj) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
@@ -8557,6 +8627,7 @@ int proj_concatoperation_get_step_count(PJ_CONTEXT *ctx,
const PJ *concatoperation) {
SANITIZE_CTX(ctx);
if (!concatoperation) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return false;
}
@@ -8590,6 +8661,7 @@ PJ *proj_concatoperation_get_step(PJ_CONTEXT *ctx, const PJ *concatoperation,
int i_step) {
SANITIZE_CTX(ctx);
if (!concatoperation) {
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE);
proj_log_error(ctx, __FUNCTION__, "missing required input");
return nullptr;
}
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
deleted file mode 100644
index 83b626b3..00000000
--- a/src/iso19111/coordinateoperation.cpp
+++ /dev/null
@@ -1,16224 +0,0 @@
-/******************************************************************************
- *
- * Project: PROJ
- * Purpose: ISO19111:2019 implementation
- * Author: Even Rouault <even dot rouault at spatialys dot com>
- *
- ******************************************************************************
- * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- ****************************************************************************/
-
-#ifndef FROM_PROJ_CPP
-#define FROM_PROJ_CPP
-#endif
-#define FROM_COORDINATE_OPERATION_CPP
-
-#include "proj/coordinateoperation.hpp"
-#include "proj/common.hpp"
-#include "proj/crs.hpp"
-#include "proj/io.hpp"
-#include "proj/metadata.hpp"
-#include "proj/util.hpp"
-
-#include "proj/internal/internal.hpp"
-#include "proj/internal/io_internal.hpp"
-#include "proj/internal/tracing.hpp"
-
-// PROJ include order is sensitive
-// clang-format off
-#include "proj.h"
-#include "proj_internal.h" // M_PI
-// clang-format on
-
-#include "proj_json_streaming_writer.hpp"
-
-#include <algorithm>
-#include <cassert>
-#include <cmath>
-#include <cstring>
-#include <memory>
-#include <set>
-#include <string>
-#include <vector>
-
-// #define TRACE_CREATE_OPERATIONS
-// #define DEBUG_SORT
-// #define DEBUG_CONCATENATED_OPERATION
-#if defined(DEBUG_SORT) || defined(DEBUG_CONCATENATED_OPERATION)
-#include <iostream>
-
-void dumpWKT(const NS_PROJ::crs::CRS *crs);
-void dumpWKT(const NS_PROJ::crs::CRS *crs) {
- auto f(NS_PROJ::io::WKTFormatter::create(
- NS_PROJ::io::WKTFormatter::Convention::WKT2_2019));
- std::cerr << crs->exportToWKT(f.get()) << std::endl;
-}
-
-void dumpWKT(const NS_PROJ::crs::CRSPtr &crs);
-void dumpWKT(const NS_PROJ::crs::CRSPtr &crs) { dumpWKT(crs.get()); }
-
-void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs);
-void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs) {
- dumpWKT(crs.as_nullable().get());
-}
-
-void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs);
-void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs) { dumpWKT(crs.get()); }
-
-void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs);
-void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs) {
- dumpWKT(crs.as_nullable().get());
-}
-
-#endif
-
-using namespace NS_PROJ::internal;
-
-#if 0
-namespace dropbox{ namespace oxygen {
-template<> nn<NS_PROJ::operation::CoordinateOperationPtr>::~nn() = default;
-template<> nn<NS_PROJ::operation::SingleOperationPtr>::~nn() = default;
-template<> nn<NS_PROJ::operation::ConversionPtr>::~nn() = default;
-template<> nn<NS_PROJ::operation::TransformationPtr>::~nn() = default;
-template<> nn<NS_PROJ::operation::ConcatenatedOperationPtr>::~nn() = default;
-template<> nn<NS_PROJ::operation::PointMotionOperationPtr>::~nn() = default;
-template<> nn<NS_PROJ::operation::GeneralOperationParameterPtr>::~nn() = default;
-template<> nn<NS_PROJ::operation::OperationParameterPtr>::~nn() = default;
-template<> nn<NS_PROJ::operation::GeneralParameterValuePtr>::~nn() = default;
-template<> nn<NS_PROJ::operation::ParameterValuePtr>::~nn() = default;
-template<> nn<NS_PROJ::operation::OperationMethodPtr>::~nn() = default;
-template<> nn<NS_PROJ::operation::OperationParameterValuePtr>::~nn() = default;
-template<> nn<std::unique_ptr<NS_PROJ::operation::CoordinateOperationFactory, std::default_delete<NS_PROJ::operation::CoordinateOperationFactory> > >::~nn() = default;
-template<> nn<std::unique_ptr<NS_PROJ::operation::CoordinateOperationContext, std::default_delete<NS_PROJ::operation::CoordinateOperationContext> > >::~nn() = default;
-}}
-#endif
-
-#include "proj/internal/coordinateoperation_constants.hpp"
-#include "proj/internal/coordinateoperation_internal.hpp"
-#include "proj/internal/esri_projection_mappings.hpp"
-
-#if 0
-namespace dropbox{ namespace oxygen {
-template<> nn<std::shared_ptr<NS_PROJ::operation::InverseCoordinateOperation>>::~nn() = default;
-template<> nn<std::shared_ptr<NS_PROJ::operation::InverseConversion>>::~nn() = default;
-template<> nn<std::shared_ptr<NS_PROJ::operation::InverseTransformation>>::~nn() = default;
-template<> nn<NS_PROJ::operation::PROJBasedOperationPtr>::~nn() = default;
-}}
-#endif
-
-// ---------------------------------------------------------------------------
-
-NS_PROJ_START
-namespace operation {
-
-//! @cond Doxygen_Suppress
-
-constexpr double UTM_LATITUDE_OF_NATURAL_ORIGIN = 0.0;
-constexpr double UTM_SCALE_FACTOR = 0.9996;
-constexpr double UTM_FALSE_EASTING = 500000.0;
-constexpr double UTM_NORTH_FALSE_NORTHING = 0.0;
-constexpr double UTM_SOUTH_FALSE_NORTHING = 10000000.0;
-
-static const std::string INVERSE_OF = "Inverse of ";
-static const char *BALLPARK_GEOCENTRIC_TRANSLATION =
- "Ballpark geocentric translation";
-static const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset";
-static const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation";
-static const char *BALLPARK_GEOGRAPHIC_OFFSET = "Ballpark geographic offset";
-static const char *BALLPARK_VERTICAL_TRANSFORMATION =
- " (ballpark vertical transformation)";
-static const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT =
- " (ballpark vertical transformation, without ellipsoid height to vertical "
- "height correction)";
-
-static const std::string AXIS_ORDER_CHANGE_2D_NAME = "axis order change (2D)";
-static const std::string AXIS_ORDER_CHANGE_3D_NAME =
- "axis order change (geographic3D horizontal)";
-//! @endcond
-
-//! @cond Doxygen_Suppress
-static util::PropertyMap
-createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom,
- bool approximateInversion);
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-#ifdef TRACE_CREATE_OPERATIONS
-
-//! @cond Doxygen_Suppress
-
-static std::string objectAsStr(const common::IdentifiedObject *obj) {
- std::string ret(obj->nameStr());
- const auto &ids = obj->identifiers();
- if (!ids.empty()) {
- ret += " (";
- ret += (*ids[0]->codeSpace()) + ":" + ids[0]->code();
- ret += ")";
- }
- return ret;
-}
-//! @endcond
-
-#endif
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-class InvalidOperationEmptyIntersection : public InvalidOperation {
- public:
- explicit InvalidOperationEmptyIntersection(const std::string &message);
- InvalidOperationEmptyIntersection(
- const InvalidOperationEmptyIntersection &other);
- ~InvalidOperationEmptyIntersection() override;
-};
-
-InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection(
- const std::string &message)
- : InvalidOperation(message) {}
-
-InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection(
- const InvalidOperationEmptyIntersection &) = default;
-
-InvalidOperationEmptyIntersection::~InvalidOperationEmptyIntersection() =
- default;
-
-// ---------------------------------------------------------------------------
-
-static std::string createEntryEqParam(const std::string &a,
- const std::string &b) {
- return a < b ? a + b : b + a;
-}
-
-static std::set<std::string> buildSetEquivalentParameters() {
-
- std::set<std::string> set;
-
- const char *const listOfEquivalentParameterNames[][7] = {
- {"latitude_of_point_1", "Latitude_Of_1st_Point", nullptr},
- {"longitude_of_point_1", "Longitude_Of_1st_Point", nullptr},
- {"latitude_of_point_2", "Latitude_Of_2nd_Point", nullptr},
- {"longitude_of_point_2", "Longitude_Of_2nd_Point", nullptr},
-
- {"satellite_height", "height", nullptr},
-
- {EPSG_NAME_PARAMETER_FALSE_EASTING,
- EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN,
- EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, nullptr},
-
- {EPSG_NAME_PARAMETER_FALSE_NORTHING,
- EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN,
- EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, nullptr},
-
- {EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR,
- EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
- EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, nullptr},
-
- {WKT1_LATITUDE_OF_ORIGIN, WKT1_LATITUDE_OF_CENTER,
- EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
- EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
- EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, "Central_Parallel",
- nullptr},
-
- {WKT1_CENTRAL_MERIDIAN, WKT1_LONGITUDE_OF_CENTER,
- EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
- EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
- EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
- EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, nullptr},
-
- {"pseudo_standard_parallel_1", WKT1_STANDARD_PARALLEL_1, nullptr},
- };
-
- for (const auto &paramList : listOfEquivalentParameterNames) {
- for (size_t i = 0; paramList[i]; i++) {
- auto a = metadata::Identifier::canonicalizeName(paramList[i]);
- for (size_t j = i + 1; paramList[j]; j++) {
- auto b = metadata::Identifier::canonicalizeName(paramList[j]);
- set.insert(createEntryEqParam(a, b));
- }
- }
- }
- return set;
-}
-
-bool areEquivalentParameters(const std::string &a, const std::string &b) {
-
- static const std::set<std::string> setEquivalentParameters =
- buildSetEquivalentParameters();
-
- auto a_can = metadata::Identifier::canonicalizeName(a);
- auto b_can = metadata::Identifier::canonicalizeName(b);
- return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) !=
- setEquivalentParameters.end();
-}
-
-// ---------------------------------------------------------------------------
-
-PROJ_NO_INLINE const MethodMapping *getMapping(int epsg_code) noexcept {
- for (const auto &mapping : projectionMethodMappings) {
- if (mapping.epsg_code == epsg_code) {
- return &mapping;
- }
- }
- return nullptr;
-}
-
-// ---------------------------------------------------------------------------
-
-const MethodMapping *getMapping(const OperationMethod *method) noexcept {
- const std::string &name(method->nameStr());
- const int epsg_code = method->getEPSGCode();
- for (const auto &mapping : projectionMethodMappings) {
- if ((epsg_code != 0 && mapping.epsg_code == epsg_code) ||
- metadata::Identifier::isEquivalentName(mapping.wkt2_name,
- name.c_str())) {
- return &mapping;
- }
- }
- return nullptr;
-}
-
-// ---------------------------------------------------------------------------
-
-const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept {
- // Unusual for a WKT1 projection name, but mentioned in OGC 12-063r5 C.4.2
- if (ci_starts_with(wkt1_name, "UTM zone")) {
- return getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
- }
-
- for (const auto &mapping : projectionMethodMappings) {
- if (mapping.wkt1_name && metadata::Identifier::isEquivalentName(
- mapping.wkt1_name, wkt1_name.c_str())) {
- return &mapping;
- }
- }
- return nullptr;
-}
-// ---------------------------------------------------------------------------
-
-const MethodMapping *getMapping(const char *wkt2_name) noexcept {
- for (const auto &mapping : projectionMethodMappings) {
- if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
- wkt2_name)) {
- return &mapping;
- }
- }
- for (const auto &mapping : otherMethodMappings) {
- if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
- wkt2_name)) {
- return &mapping;
- }
- }
- return nullptr;
-}
-
-// ---------------------------------------------------------------------------
-
-std::vector<const MethodMapping *>
-getMappingsFromPROJName(const std::string &projName) {
- std::vector<const MethodMapping *> res;
- for (const auto &mapping : projectionMethodMappings) {
- if (mapping.proj_name_main && projName == mapping.proj_name_main) {
- res.push_back(&mapping);
- }
- }
- return res;
-}
-
-// ---------------------------------------------------------------------------
-
-static const ParamMapping *getMapping(const MethodMapping *mapping,
- const OperationParameterNNPtr &param) {
- if (mapping->params == nullptr) {
- return nullptr;
- }
-
- // First try with id
- const int epsg_code = param->getEPSGCode();
- if (epsg_code) {
- for (int i = 0; mapping->params[i] != nullptr; ++i) {
- const auto *paramMapping = mapping->params[i];
- if (paramMapping->epsg_code == epsg_code) {
- return paramMapping;
- }
- }
- }
-
- // then equivalent name
- const std::string &name = param->nameStr();
- for (int i = 0; mapping->params[i] != nullptr; ++i) {
- const auto *paramMapping = mapping->params[i];
- if (metadata::Identifier::isEquivalentName(paramMapping->wkt2_name,
- name.c_str())) {
- return paramMapping;
- }
- }
-
- // and finally different name, but equivalent parameter
- for (int i = 0; mapping->params[i] != nullptr; ++i) {
- const auto *paramMapping = mapping->params[i];
- if (areEquivalentParameters(paramMapping->wkt2_name, name)) {
- return paramMapping;
- }
- }
-
- return nullptr;
-}
-
-// ---------------------------------------------------------------------------
-
-const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping,
- const std::string &wkt1_name) {
- for (int i = 0; mapping->params[i] != nullptr; ++i) {
- const auto *paramMapping = mapping->params[i];
- if (paramMapping->wkt1_name &&
- (metadata::Identifier::isEquivalentName(paramMapping->wkt1_name,
- wkt1_name.c_str()) ||
- areEquivalentParameters(paramMapping->wkt1_name, wkt1_name))) {
- return paramMapping;
- }
- }
- return nullptr;
-}
-
-// ---------------------------------------------------------------------------
-
-std::vector<const ESRIMethodMapping *>
-getMappingsFromESRI(const std::string &esri_name) {
- std::vector<const ESRIMethodMapping *> res;
- for (const auto &mapping : esriMappings) {
- if (ci_equal(esri_name, mapping.esri_name)) {
- res.push_back(&mapping);
- }
- }
- return res;
-}
-
-// ---------------------------------------------------------------------------
-
-static const ESRIMethodMapping *getESRIMapping(const std::string &wkt2_name,
- int epsg_code) {
- for (const auto &mapping : esriMappings) {
- if ((epsg_code != 0 && mapping.epsg_code == epsg_code) ||
- ci_equal(wkt2_name, mapping.wkt2_name)) {
- return &mapping;
- }
- }
- return nullptr;
-}
-
-// ---------------------------------------------------------------------------
-
-static double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops);
-
-// Returns the accuracy of an operation, or -1 if unknown
-static double getAccuracy(const CoordinateOperationNNPtr &op) {
-
- if (dynamic_cast<const Conversion *>(op.get())) {
- // A conversion is perfectly accurate.
- return 0.0;
- }
-
- double accuracy = -1.0;
- const auto &accuracies = op->coordinateOperationAccuracies();
- if (!accuracies.empty()) {
- try {
- accuracy = c_locale_stod(accuracies[0]->value());
- } catch (const std::exception &) {
- }
- } else {
- auto concatenated =
- dynamic_cast<const ConcatenatedOperation *>(op.get());
- if (concatenated) {
- accuracy = getAccuracy(concatenated->operations());
- }
- }
- return accuracy;
-}
-
-// ---------------------------------------------------------------------------
-
-// Returns the accuracy of a set of concatenated operations, or -1 if unknown
-static double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) {
- double accuracy = -1.0;
- for (const auto &subop : ops) {
- const double subops_accuracy = getAccuracy(subop);
- if (subops_accuracy < 0.0) {
- return -1.0;
- }
- if (accuracy < 0.0) {
- accuracy = 0.0;
- }
- accuracy += subops_accuracy;
- }
- return accuracy;
-}
-
-// ---------------------------------------------------------------------------
-
-static metadata::ExtentPtr
-getExtent(const std::vector<CoordinateOperationNNPtr> &ops,
- bool conversionExtentIsWorld, bool &emptyIntersection);
-
-static metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op,
- bool conversionExtentIsWorld,
- bool &emptyIntersection) {
- auto conv = dynamic_cast<const Conversion *>(op.get());
- if (conv) {
- emptyIntersection = false;
- return metadata::Extent::WORLD;
- }
- const auto &domains = op->domains();
- if (!domains.empty()) {
- emptyIntersection = false;
- return domains[0]->domainOfValidity();
- }
- auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
- if (!concatenated) {
- emptyIntersection = false;
- return nullptr;
- }
- return getExtent(concatenated->operations(), conversionExtentIsWorld,
- emptyIntersection);
-}
-
-// ---------------------------------------------------------------------------
-
-static const metadata::ExtentPtr nullExtent{};
-
-static const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) {
- const auto &domains = crs->domains();
- if (!domains.empty()) {
- return domains[0]->domainOfValidity();
- }
- const auto *boundCRS = dynamic_cast<const crs::BoundCRS *>(crs.get());
- if (boundCRS) {
- return getExtent(boundCRS->baseCRS());
- }
- return nullExtent;
-}
-
-static const metadata::ExtentPtr
-getExtentPossiblySynthetized(const crs::CRSNNPtr &crs, bool &approxOut) {
- const auto &rawExtent(getExtent(crs));
- approxOut = false;
- if (rawExtent)
- return rawExtent;
- const auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get());
- if (compoundCRS) {
- // For a compoundCRS, take the intersection of the extent of its
- // components.
- const auto &components = compoundCRS->componentReferenceSystems();
- metadata::ExtentPtr extent;
- approxOut = true;
- for (const auto &component : components) {
- const auto &componentExtent(getExtent(component));
- if (extent && componentExtent)
- extent = extent->intersection(NN_NO_CHECK(componentExtent));
- else if (componentExtent)
- extent = componentExtent;
- }
- return extent;
- }
- return rawExtent;
-}
-
-// ---------------------------------------------------------------------------
-
-static metadata::ExtentPtr
-getExtent(const std::vector<CoordinateOperationNNPtr> &ops,
- bool conversionExtentIsWorld, bool &emptyIntersection) {
- metadata::ExtentPtr res = nullptr;
- for (const auto &subop : ops) {
-
- const auto &subExtent =
- getExtent(subop, conversionExtentIsWorld, emptyIntersection);
- if (!subExtent) {
- if (emptyIntersection) {
- return nullptr;
- }
- continue;
- }
- if (res == nullptr) {
- res = subExtent;
- } else {
- res = res->intersection(NN_NO_CHECK(subExtent));
- if (!res) {
- emptyIntersection = true;
- return nullptr;
- }
- }
- }
- emptyIntersection = false;
- return res;
-}
-
-// ---------------------------------------------------------------------------
-
-static double getPseudoArea(const metadata::ExtentPtr &extent) {
- if (!extent)
- return 0.0;
- const auto &geographicElements = extent->geographicElements();
- if (geographicElements.empty())
- return 0.0;
- auto bbox = dynamic_cast<const metadata::GeographicBoundingBox *>(
- geographicElements[0].get());
- if (!bbox)
- return 0;
- double w = bbox->westBoundLongitude();
- double s = bbox->southBoundLatitude();
- double e = bbox->eastBoundLongitude();
- double n = bbox->northBoundLatitude();
- if (w > e) {
- e += 360.0;
- }
- // Integrate cos(lat) between south_lat and north_lat
- return (e - w) * (std::sin(common::Angle(n).getSIValue()) -
- std::sin(common::Angle(s).getSIValue()));
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct CoordinateOperation::Private {
- util::optional<std::string> operationVersion_{};
- std::vector<metadata::PositionalAccuracyNNPtr>
- coordinateOperationAccuracies_{};
- std::weak_ptr<crs::CRS> sourceCRSWeak_{};
- std::weak_ptr<crs::CRS> targetCRSWeak_{};
- crs::CRSPtr interpolationCRS_{};
- util::optional<common::DataEpoch> sourceCoordinateEpoch_{};
- util::optional<common::DataEpoch> targetCoordinateEpoch_{};
- bool hasBallparkTransformation_ = false;
- bool use3DHelmert_ = false;
-
- // do not set this for a ProjectedCRS.definingConversion
- struct CRSStrongRef {
- crs::CRSNNPtr sourceCRS_;
- crs::CRSNNPtr targetCRS_;
- CRSStrongRef(const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn)
- : sourceCRS_(sourceCRSIn), targetCRS_(targetCRSIn) {}
- };
- std::unique_ptr<CRSStrongRef> strongRef_{};
-
- Private() = default;
- Private(const Private &other)
- : operationVersion_(other.operationVersion_),
- coordinateOperationAccuracies_(other.coordinateOperationAccuracies_),
- sourceCRSWeak_(other.sourceCRSWeak_),
- targetCRSWeak_(other.targetCRSWeak_),
- interpolationCRS_(other.interpolationCRS_),
- sourceCoordinateEpoch_(other.sourceCoordinateEpoch_),
- targetCoordinateEpoch_(other.targetCoordinateEpoch_),
- hasBallparkTransformation_(other.hasBallparkTransformation_),
- strongRef_(other.strongRef_ ? internal::make_unique<CRSStrongRef>(
- *(other.strongRef_))
- : nullptr) {}
-
- Private &operator=(const Private &) = delete;
-};
-
-// ---------------------------------------------------------------------------
-
-GridDescription::GridDescription()
- : shortName{}, fullName{}, packageName{}, url{}, directDownload(false),
- openLicense(false), available(false) {}
-
-GridDescription::~GridDescription() = default;
-
-GridDescription::GridDescription(const GridDescription &) = default;
-
-GridDescription::GridDescription(GridDescription &&other) noexcept
- : shortName(std::move(other.shortName)),
- fullName(std::move(other.fullName)),
- packageName(std::move(other.packageName)),
- url(std::move(other.url)),
- directDownload(other.directDownload),
- openLicense(other.openLicense),
- available(other.available) {}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperation::CoordinateOperation()
- : d(internal::make_unique<Private>()) {}
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperation::CoordinateOperation(const CoordinateOperation &other)
- : ObjectUsage(other), d(internal::make_unique<Private>(*other.d)) {}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-CoordinateOperation::~CoordinateOperation() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the version of the coordinate transformation (i.e.
- * instantiation
- * due to the stochastic nature of the parameters).
- *
- * Mandatory when describing a coordinate transformation or point motion
- * operation, and should not be supplied for a coordinate conversion.
- *
- * @return version or empty.
- */
-const util::optional<std::string> &
-CoordinateOperation::operationVersion() const {
- return d->operationVersion_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return estimate(s) of the impact of this coordinate operation on
- * point accuracy.
- *
- * Gives position error estimates for target coordinates of this coordinate
- * operation, assuming no errors in source coordinates.
- *
- * @return estimate(s) or empty vector.
- */
-const std::vector<metadata::PositionalAccuracyNNPtr> &
-CoordinateOperation::coordinateOperationAccuracies() const {
- return d->coordinateOperationAccuracies_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the source CRS of this coordinate operation.
- *
- * This should not be null, expect for of a derivingConversion of a DerivedCRS
- * when the owning DerivedCRS has been destroyed.
- *
- * @return source CRS, or null.
- */
-const crs::CRSPtr CoordinateOperation::sourceCRS() const {
- return d->sourceCRSWeak_.lock();
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the target CRS of this coordinate operation.
- *
- * This should not be null, expect for of a derivingConversion of a DerivedCRS
- * when the owning DerivedCRS has been destroyed.
- *
- * @return target CRS, or null.
- */
-const crs::CRSPtr CoordinateOperation::targetCRS() const {
- return d->targetCRSWeak_.lock();
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the interpolation CRS of this coordinate operation.
- *
- * @return interpolation CRS, or null.
- */
-const crs::CRSPtr &CoordinateOperation::interpolationCRS() const {
- return d->interpolationCRS_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the source epoch of coordinates.
- *
- * @return source epoch of coordinates, or empty.
- */
-const util::optional<common::DataEpoch> &
-CoordinateOperation::sourceCoordinateEpoch() const {
- return d->sourceCoordinateEpoch_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the target epoch of coordinates.
- *
- * @return target epoch of coordinates, or empty.
- */
-const util::optional<common::DataEpoch> &
-CoordinateOperation::targetCoordinateEpoch() const {
- return d->targetCoordinateEpoch_;
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperation::setWeakSourceTargetCRS(
- std::weak_ptr<crs::CRS> sourceCRSIn, std::weak_ptr<crs::CRS> targetCRSIn) {
- d->sourceCRSWeak_ = sourceCRSIn;
- d->targetCRSWeak_ = targetCRSIn;
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperation::setCRSs(const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn,
- const crs::CRSPtr &interpolationCRSIn) {
- d->strongRef_ =
- internal::make_unique<Private::CRSStrongRef>(sourceCRSIn, targetCRSIn);
- d->sourceCRSWeak_ = sourceCRSIn.as_nullable();
- d->targetCRSWeak_ = targetCRSIn.as_nullable();
- d->interpolationCRS_ = interpolationCRSIn;
-}
-// ---------------------------------------------------------------------------
-
-void CoordinateOperation::setCRSs(const CoordinateOperation *in,
- bool inverseSourceTarget) {
- auto l_sourceCRS = in->sourceCRS();
- auto l_targetCRS = in->targetCRS();
- if (l_sourceCRS && l_targetCRS) {
- auto nn_sourceCRS = NN_NO_CHECK(l_sourceCRS);
- auto nn_targetCRS = NN_NO_CHECK(l_targetCRS);
- if (inverseSourceTarget) {
- setCRSs(nn_targetCRS, nn_sourceCRS, in->interpolationCRS());
- } else {
- setCRSs(nn_sourceCRS, nn_targetCRS, in->interpolationCRS());
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperation::setAccuracies(
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- d->coordinateOperationAccuracies_ = accuracies;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return whether a coordinate operation can be instantiated as
- * a PROJ pipeline, checking in particular that referenced grids are
- * available.
- */
-bool CoordinateOperation::isPROJInstantiable(
- const io::DatabaseContextPtr &databaseContext,
- bool considerKnownGridsAsAvailable) const {
- try {
- exportToPROJString(io::PROJStringFormatter::create().get());
- } catch (const std::exception &) {
- return false;
- }
- for (const auto &gridDesc :
- gridsNeeded(databaseContext, considerKnownGridsAsAvailable)) {
- if (!gridDesc.available) {
- return false;
- }
- }
- return true;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return whether a coordinate operation has a "ballpark"
- * transformation,
- * that is a very approximate one, due to lack of more accurate transformations.
- *
- * Typically a null geographic offset between two horizontal datum, or a
- * null vertical offset (or limited to unit changes) between two vertical
- * datum. Errors of several tens to one hundred meters might be expected,
- * compared to more accurate transformations.
- */
-bool CoordinateOperation::hasBallparkTransformation() const {
- return d->hasBallparkTransformation_;
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperation::setHasBallparkTransformation(bool b) {
- d->hasBallparkTransformation_ = b;
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperation::setProperties(
- const util::PropertyMap &properties) // throw(InvalidValueTypeException)
-{
- ObjectUsage::setProperties(properties);
- properties.getStringValue(OPERATION_VERSION_KEY, d->operationVersion_);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return a variation of the current coordinate operation whose axis
- * order is the one expected for visualization purposes.
- */
-CoordinateOperationNNPtr
-CoordinateOperation::normalizeForVisualization() const {
- auto l_sourceCRS = sourceCRS();
- auto l_targetCRS = targetCRS();
- if (!l_sourceCRS || !l_targetCRS) {
- throw util::UnsupportedOperationException(
- "Cannot retrieve source or target CRS");
- }
- const bool swapSource =
- l_sourceCRS->mustAxisOrderBeSwitchedForVisualization();
- const bool swapTarget =
- l_targetCRS->mustAxisOrderBeSwitchedForVisualization();
- auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast<CoordinateOperation>(
- shared_from_this().as_nullable()));
- if (!swapSource && !swapTarget) {
- return l_this;
- }
- std::vector<CoordinateOperationNNPtr> subOps;
- if (swapSource) {
- auto op = Conversion::createAxisOrderReversal(false);
- op->setCRSs(l_sourceCRS->normalizeForVisualization(),
- NN_NO_CHECK(l_sourceCRS), nullptr);
- subOps.emplace_back(op);
- }
- subOps.emplace_back(l_this);
- if (swapTarget) {
- auto op = Conversion::createAxisOrderReversal(false);
- op->setCRSs(NN_NO_CHECK(l_targetCRS),
- l_targetCRS->normalizeForVisualization(), nullptr);
- subOps.emplace_back(op);
- }
- return util::nn_static_pointer_cast<CoordinateOperation>(
- ConcatenatedOperation::createComputeMetadata(subOps, true));
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-CoordinateOperationNNPtr CoordinateOperation::shallowClone() const {
- return _shallowClone();
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct OperationMethod::Private {
- util::optional<std::string> formula_{};
- util::optional<metadata::Citation> formulaCitation_{};
- std::vector<GeneralOperationParameterNNPtr> parameters_{};
- std::string projMethodOverride_{};
-};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-OperationMethod::OperationMethod() : d(internal::make_unique<Private>()) {}
-
-// ---------------------------------------------------------------------------
-
-OperationMethod::OperationMethod(const OperationMethod &other)
- : IdentifiedObject(other), d(internal::make_unique<Private>(*other.d)) {}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-OperationMethod::~OperationMethod() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the formula(s) or procedure used by this coordinate operation
- * method.
- *
- * This may be a reference to a publication (in which case use
- * formulaCitation()).
- *
- * Note that the operation method may not be analytic, in which case this
- * attribute references or contains the procedure, not an analytic formula.
- *
- * @return the formula, or empty.
- */
-const util::optional<std::string> &OperationMethod::formula() PROJ_PURE_DEFN {
- return d->formula_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return a reference to a publication giving the formula(s) or
- * procedure
- * used by the coordinate operation method.
- *
- * @return the formula citation, or empty.
- */
-const util::optional<metadata::Citation> &
-OperationMethod::formulaCitation() PROJ_PURE_DEFN {
- return d->formulaCitation_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the parameters of this operation method.
- *
- * @return the parameters.
- */
-const std::vector<GeneralOperationParameterNNPtr> &
-OperationMethod::parameters() PROJ_PURE_DEFN {
- return d->parameters_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a operation method from a vector of
- * GeneralOperationParameter.
- *
- * @param properties See \ref general_properties. At minimum the name should be
- * defined.
- * @param parameters Vector of GeneralOperationParameterNNPtr.
- * @return a new OperationMethod.
- */
-OperationMethodNNPtr OperationMethod::create(
- const util::PropertyMap &properties,
- const std::vector<GeneralOperationParameterNNPtr> &parameters) {
- OperationMethodNNPtr method(
- OperationMethod::nn_make_shared<OperationMethod>());
- method->assignSelf(method);
- method->setProperties(properties);
- method->d->parameters_ = parameters;
- properties.getStringValue("proj_method", method->d->projMethodOverride_);
- return method;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a operation method from a vector of OperationParameter.
- *
- * @param properties See \ref general_properties. At minimum the name should be
- * defined.
- * @param parameters Vector of OperationParameterNNPtr.
- * @return a new OperationMethod.
- */
-OperationMethodNNPtr OperationMethod::create(
- const util::PropertyMap &properties,
- const std::vector<OperationParameterNNPtr> &parameters) {
- std::vector<GeneralOperationParameterNNPtr> parametersGeneral;
- parametersGeneral.reserve(parameters.size());
- for (const auto &p : parameters) {
- parametersGeneral.push_back(p);
- }
- return create(properties, parametersGeneral);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the EPSG code, either directly, or through the name
- * @return code, or 0 if not found
- */
-int OperationMethod::getEPSGCode() PROJ_PURE_DEFN {
- int epsg_code = IdentifiedObject::getEPSGCode();
- if (epsg_code == 0) {
- auto l_name = nameStr();
- if (ends_with(l_name, " (3D)")) {
- l_name.resize(l_name.size() - strlen(" (3D)"));
- }
- for (const auto &tuple : methodNameCodes) {
- if (metadata::Identifier::isEquivalentName(l_name.c_str(),
- tuple.name)) {
- return tuple.epsg_code;
- }
- }
- }
- return epsg_code;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const {
- const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
- formatter->startNode(isWKT2 ? io::WKTConstants::METHOD
- : io::WKTConstants::PROJECTION,
- !identifiers().empty());
- std::string l_name(nameStr());
- if (!isWKT2) {
- const MethodMapping *mapping = getMapping(this);
- if (mapping == nullptr) {
- l_name = replaceAll(l_name, " ", "_");
- } else {
- if (l_name ==
- PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) {
- l_name = "Geostationary_Satellite";
- } else {
- if (mapping->wkt1_name == nullptr) {
- throw io::FormattingException(
- std::string("Unsupported conversion method: ") +
- mapping->wkt2_name);
- }
- l_name = mapping->wkt1_name;
- }
- }
- }
- formatter->addQuotedString(l_name);
- if (formatter->outputId()) {
- formatID(formatter);
- }
- formatter->endNode();
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-void OperationMethod::_exportToJSON(
- io::JSONFormatter *formatter) const // throw(FormattingException)
-{
- auto writer = formatter->writer();
- auto objectContext(formatter->MakeObjectContext("OperationMethod",
- !identifiers().empty()));
-
- writer->AddObjKey("name");
- writer->Add(nameStr());
-
- if (formatter->outputId()) {
- formatID(formatter);
- }
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-bool OperationMethod::_isEquivalentTo(
- const util::IComparable *other, util::IComparable::Criterion criterion,
- const io::DatabaseContextPtr &dbContext) const {
- auto otherOM = dynamic_cast<const OperationMethod *>(other);
- if (otherOM == nullptr ||
- !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) {
- return false;
- }
- // TODO test formula and formulaCitation
- const auto &params = parameters();
- const auto &otherParams = otherOM->parameters();
- const auto paramsSize = params.size();
- if (paramsSize != otherParams.size()) {
- return false;
- }
- if (criterion == util::IComparable::Criterion::STRICT) {
- for (size_t i = 0; i < paramsSize; i++) {
- if (!params[i]->_isEquivalentTo(otherParams[i].get(), criterion,
- dbContext)) {
- return false;
- }
- }
- } else {
- std::vector<bool> candidateIndices(paramsSize, true);
- for (size_t i = 0; i < paramsSize; i++) {
- bool found = false;
- for (size_t j = 0; j < paramsSize; j++) {
- if (candidateIndices[j] &&
- params[i]->_isEquivalentTo(otherParams[j].get(), criterion,
- dbContext)) {
- candidateIndices[j] = false;
- found = true;
- break;
- }
- }
- if (!found) {
- return false;
- }
- }
- }
- return true;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct GeneralParameterValue::Private {};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-GeneralParameterValue::GeneralParameterValue() : d(nullptr) {}
-
-// ---------------------------------------------------------------------------
-
-GeneralParameterValue::GeneralParameterValue(const GeneralParameterValue &)
- : d(nullptr) {}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-GeneralParameterValue::~GeneralParameterValue() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct OperationParameterValue::Private {
- OperationParameterNNPtr parameter;
- ParameterValueNNPtr parameterValue;
-
- Private(const OperationParameterNNPtr &parameterIn,
- const ParameterValueNNPtr &valueIn)
- : parameter(parameterIn), parameterValue(valueIn) {}
-};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-OperationParameterValue::OperationParameterValue(
- const OperationParameterValue &other)
- : GeneralParameterValue(other),
- d(internal::make_unique<Private>(*other.d)) {}
-
-// ---------------------------------------------------------------------------
-
-OperationParameterValue::OperationParameterValue(
- const OperationParameterNNPtr &parameterIn,
- const ParameterValueNNPtr &valueIn)
- : GeneralParameterValue(),
- d(internal::make_unique<Private>(parameterIn, valueIn)) {}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a OperationParameterValue.
- *
- * @param parameterIn Parameter (definition).
- * @param valueIn Parameter value.
- * @return a new OperationParameterValue.
- */
-OperationParameterValueNNPtr
-OperationParameterValue::create(const OperationParameterNNPtr &parameterIn,
- const ParameterValueNNPtr &valueIn) {
- return OperationParameterValue::nn_make_shared<OperationParameterValue>(
- parameterIn, valueIn);
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-OperationParameterValue::~OperationParameterValue() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the parameter (definition)
- *
- * @return the parameter (definition).
- */
-const OperationParameterNNPtr &
-OperationParameterValue::parameter() PROJ_PURE_DEFN {
- return d->parameter;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the parameter value.
- *
- * @return the parameter value.
- */
-const ParameterValueNNPtr &
-OperationParameterValue::parameterValue() PROJ_PURE_DEFN {
- return d->parameterValue;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-void OperationParameterValue::_exportToWKT(
- // cppcheck-suppress passedByValue
- io::WKTFormatter *formatter) const {
- _exportToWKT(formatter, nullptr);
-}
-
-void OperationParameterValue::_exportToWKT(io::WKTFormatter *formatter,
- const MethodMapping *mapping) const {
- const ParamMapping *paramMapping =
- mapping ? getMapping(mapping, d->parameter) : nullptr;
- if (paramMapping && paramMapping->wkt1_name == nullptr) {
- return;
- }
- const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
- if (isWKT2 && parameterValue()->type() == ParameterValue::Type::FILENAME) {
- formatter->startNode(io::WKTConstants::PARAMETERFILE,
- !parameter()->identifiers().empty());
- } else {
- formatter->startNode(io::WKTConstants::PARAMETER,
- !parameter()->identifiers().empty());
- }
- if (paramMapping) {
- formatter->addQuotedString(paramMapping->wkt1_name);
- } else {
- formatter->addQuotedString(parameter()->nameStr());
- }
- parameterValue()->_exportToWKT(formatter);
- if (formatter->outputId()) {
- parameter()->formatID(formatter);
- }
- formatter->endNode();
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-void OperationParameterValue::_exportToJSON(
- io::JSONFormatter *formatter) const {
- auto writer = formatter->writer();
- auto objectContext(formatter->MakeObjectContext(
- "ParameterValue", !parameter()->identifiers().empty()));
-
- writer->AddObjKey("name");
- writer->Add(parameter()->nameStr());
-
- const auto &l_value(parameterValue());
- if (l_value->type() == ParameterValue::Type::MEASURE) {
- writer->AddObjKey("value");
- writer->Add(l_value->value().value(), 15);
- writer->AddObjKey("unit");
- const auto &l_unit(l_value->value().unit());
- if (l_unit == common::UnitOfMeasure::METRE ||
- l_unit == common::UnitOfMeasure::DEGREE ||
- l_unit == common::UnitOfMeasure::SCALE_UNITY) {
- writer->Add(l_unit.name());
- } else {
- l_unit._exportToJSON(formatter);
- }
- } else if (l_value->type() == ParameterValue::Type::FILENAME) {
- writer->AddObjKey("value");
- writer->Add(l_value->valueFile());
- }
-
- if (formatter->outputId()) {
- parameter()->formatID(formatter);
- }
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-/** Utility method used on WKT2 import to convert from abridged transformation
- * to "normal" transformation parameters.
- */
-bool OperationParameterValue::convertFromAbridged(
- const std::string &paramName, double &val,
- const common::UnitOfMeasure *&unit, int &paramEPSGCode) {
- if (metadata::Identifier::isEquivalentName(
- paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION) ||
- paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) {
- unit = &common::UnitOfMeasure::METRE;
- paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION;
- return true;
- } else if (metadata::Identifier::isEquivalentName(
- paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION) ||
- paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) {
- unit = &common::UnitOfMeasure::METRE;
- paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION;
- return true;
- } else if (metadata::Identifier::isEquivalentName(
- paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION) ||
- paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) {
- unit = &common::UnitOfMeasure::METRE;
- paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION;
- return true;
- } else if (metadata::Identifier::isEquivalentName(
- paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_ROTATION) ||
- paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_ROTATION) {
- unit = &common::UnitOfMeasure::ARC_SECOND;
- paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_ROTATION;
- return true;
- } else if (metadata::Identifier::isEquivalentName(
- paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_ROTATION) ||
- paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) {
- unit = &common::UnitOfMeasure::ARC_SECOND;
- paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_ROTATION;
- return true;
-
- } else if (metadata::Identifier::isEquivalentName(
- paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_ROTATION) ||
- paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) {
- unit = &common::UnitOfMeasure::ARC_SECOND;
- paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_ROTATION;
- return true;
-
- } else if (metadata::Identifier::isEquivalentName(
- paramName.c_str(), EPSG_NAME_PARAMETER_SCALE_DIFFERENCE) ||
- paramEPSGCode == EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) {
- val = (val - 1.0) * 1e6;
- unit = &common::UnitOfMeasure::PARTS_PER_MILLION;
- paramEPSGCode = EPSG_CODE_PARAMETER_SCALE_DIFFERENCE;
- return true;
- }
- return false;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-bool OperationParameterValue::_isEquivalentTo(
- const util::IComparable *other, util::IComparable::Criterion criterion,
- const io::DatabaseContextPtr &dbContext) const {
- auto otherOPV = dynamic_cast<const OperationParameterValue *>(other);
- if (otherOPV == nullptr) {
- return false;
- }
- if (!d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(), criterion,
- dbContext)) {
- return false;
- }
- if (criterion == util::IComparable::Criterion::STRICT) {
- return d->parameterValue->_isEquivalentTo(
- otherOPV->d->parameterValue.get(), criterion);
- }
- if (d->parameterValue->_isEquivalentTo(otherOPV->d->parameterValue.get(),
- criterion, dbContext)) {
- return true;
- }
- if (d->parameter->getEPSGCode() ==
- EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE ||
- d->parameter->getEPSGCode() ==
- EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) {
- if (parameterValue()->type() == ParameterValue::Type::MEASURE &&
- otherOPV->parameterValue()->type() ==
- ParameterValue::Type::MEASURE) {
- const double a = std::fmod(parameterValue()->value().convertToUnit(
- common::UnitOfMeasure::DEGREE) +
- 360.0,
- 360.0);
- const double b =
- std::fmod(otherOPV->parameterValue()->value().convertToUnit(
- common::UnitOfMeasure::DEGREE) +
- 360.0,
- 360.0);
- return std::fabs(a - b) <= 1e-10 * std::fabs(a);
- }
- }
- return false;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct GeneralOperationParameter::Private {};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-GeneralOperationParameter::GeneralOperationParameter() : d(nullptr) {}
-
-// ---------------------------------------------------------------------------
-
-GeneralOperationParameter::GeneralOperationParameter(
- const GeneralOperationParameter &other)
- : IdentifiedObject(other), d(nullptr) {}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-GeneralOperationParameter::~GeneralOperationParameter() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct OperationParameter::Private {};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-OperationParameter::OperationParameter() : d(nullptr) {}
-
-// ---------------------------------------------------------------------------
-
-OperationParameter::OperationParameter(const OperationParameter &other)
- : GeneralOperationParameter(other), d(nullptr) {}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-OperationParameter::~OperationParameter() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a OperationParameter.
- *
- * @param properties See \ref general_properties. At minimum the name should be
- * defined.
- * @return a new OperationParameter.
- */
-OperationParameterNNPtr
-OperationParameter::create(const util::PropertyMap &properties) {
- OperationParameterNNPtr op(
- OperationParameter::nn_make_shared<OperationParameter>());
- op->assignSelf(op);
- op->setProperties(properties);
- return op;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-bool OperationParameter::_isEquivalentTo(
- const util::IComparable *other, util::IComparable::Criterion criterion,
- const io::DatabaseContextPtr &dbContext) const {
- auto otherOP = dynamic_cast<const OperationParameter *>(other);
- if (otherOP == nullptr) {
- return false;
- }
- if (criterion == util::IComparable::Criterion::STRICT) {
- return IdentifiedObject::_isEquivalentTo(other, criterion, dbContext);
- }
- if (IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) {
- return true;
- }
- auto l_epsgCode = getEPSGCode();
- return l_epsgCode != 0 && l_epsgCode == otherOP->getEPSGCode();
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-void OperationParameter::_exportToWKT(io::WKTFormatter *) const {}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the name of a parameter designed by its EPSG code
- * @return name, or nullptr if not found
- */
-const char *OperationParameter::getNameForEPSGCode(int epsg_code) noexcept {
- for (const auto &tuple : paramNameCodes) {
- if (tuple.epsg_code == epsg_code) {
- return tuple.name;
- }
- }
- return nullptr;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the EPSG code, either directly, or through the name
- * @return code, or 0 if not found
- */
-int OperationParameter::getEPSGCode() PROJ_PURE_DEFN {
- int epsg_code = IdentifiedObject::getEPSGCode();
- if (epsg_code == 0) {
- const auto &l_name = nameStr();
- for (const auto &tuple : paramNameCodes) {
- if (metadata::Identifier::isEquivalentName(l_name.c_str(),
- tuple.name)) {
- return tuple.epsg_code;
- }
- }
- if (metadata::Identifier::isEquivalentName(l_name.c_str(),
- "Latitude of origin")) {
- return EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
- }
- if (metadata::Identifier::isEquivalentName(l_name.c_str(),
- "Scale factor")) {
- return EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN;
- }
- }
- return epsg_code;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct SingleOperation::Private {
- std::vector<GeneralParameterValueNNPtr> parameterValues_{};
- OperationMethodNNPtr method_;
-
- explicit Private(const OperationMethodNNPtr &methodIn)
- : method_(methodIn) {}
-};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn)
- : d(internal::make_unique<Private>(methodIn)) {}
-
-// ---------------------------------------------------------------------------
-
-SingleOperation::SingleOperation(const SingleOperation &other)
- :
-#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
- CoordinateOperation(other),
-#endif
- d(internal::make_unique<Private>(*other.d)) {
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-SingleOperation::~SingleOperation() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the parameter values.
- *
- * @return the parameter values.
- */
-const std::vector<GeneralParameterValueNNPtr> &
-SingleOperation::parameterValues() PROJ_PURE_DEFN {
- return d->parameterValues_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the operation method associated to the operation.
- *
- * @return the operation method.
- */
-const OperationMethodNNPtr &SingleOperation::method() PROJ_PURE_DEFN {
- return d->method_;
-}
-
-// ---------------------------------------------------------------------------
-
-void SingleOperation::setParameterValues(
- const std::vector<GeneralParameterValueNNPtr> &values) {
- d->parameterValues_ = values;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static const ParameterValuePtr nullParameterValue;
-//! @endcond
-
-/** \brief Return the parameter value corresponding to a parameter name or
- * EPSG code
- *
- * @param paramName the parameter name (or empty, in which case epsg_code
- * should be non zero)
- * @param epsg_code the parameter EPSG code (possibly zero)
- * @return the value, or nullptr if not found.
- */
-const ParameterValuePtr &
-SingleOperation::parameterValue(const std::string &paramName,
- int epsg_code) const noexcept {
- if (epsg_code) {
- for (const auto &genOpParamvalue : parameterValues()) {
- auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto &parameter = opParamvalue->parameter();
- if (parameter->getEPSGCode() == epsg_code) {
- return opParamvalue->parameterValue();
- }
- }
- }
- }
- for (const auto &genOpParamvalue : parameterValues()) {
- auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto &parameter = opParamvalue->parameter();
- if (metadata::Identifier::isEquivalentName(
- paramName.c_str(), parameter->nameStr().c_str())) {
- return opParamvalue->parameterValue();
- }
- }
- }
- for (const auto &genOpParamvalue : parameterValues()) {
- auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto &parameter = opParamvalue->parameter();
- if (areEquivalentParameters(paramName, parameter->nameStr())) {
- return opParamvalue->parameterValue();
- }
- }
- }
- return nullParameterValue;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the parameter value corresponding to a EPSG code
- *
- * @param epsg_code the parameter EPSG code
- * @return the value, or nullptr if not found.
- */
-const ParameterValuePtr &SingleOperation::parameterValue(int epsg_code) const
- noexcept {
- for (const auto &genOpParamvalue : parameterValues()) {
- auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto &parameter = opParamvalue->parameter();
- if (parameter->getEPSGCode() == epsg_code) {
- return opParamvalue->parameterValue();
- }
- }
- }
- return nullParameterValue;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static const common::Measure nullMeasure;
-//! @endcond
-
-/** \brief Return the parameter value, as a measure, corresponding to a
- * parameter name or EPSG code
- *
- * @param paramName the parameter name (or empty, in which case epsg_code
- * should be non zero)
- * @param epsg_code the parameter EPSG code (possibly zero)
- * @return the measure, or the empty Measure() object if not found.
- */
-const common::Measure &
-SingleOperation::parameterValueMeasure(const std::string &paramName,
- int epsg_code) const noexcept {
- const auto &val = parameterValue(paramName, epsg_code);
- if (val && val->type() == ParameterValue::Type::MEASURE) {
- return val->value();
- }
- return nullMeasure;
-}
-
-/** \brief Return the parameter value, as a measure, corresponding to a
- * EPSG code
- *
- * @param epsg_code the parameter EPSG code
- * @return the measure, or the empty Measure() object if not found.
- */
-const common::Measure &
-SingleOperation::parameterValueMeasure(int epsg_code) const noexcept {
- const auto &val = parameterValue(epsg_code);
- if (val && val->type() == ParameterValue::Type::MEASURE) {
- return val->value();
- }
- return nullMeasure;
-}
-
-//! @cond Doxygen_Suppress
-
-double SingleOperation::parameterValueNumericAsSI(int epsg_code) const
- noexcept {
- const auto &val = parameterValue(epsg_code);
- if (val && val->type() == ParameterValue::Type::MEASURE) {
- return val->value().getSIValue();
- }
- return 0.0;
-}
-
-double SingleOperation::parameterValueNumeric(
- int epsg_code, const common::UnitOfMeasure &targetUnit) const noexcept {
- const auto &val = parameterValue(epsg_code);
- if (val && val->type() == ParameterValue::Type::MEASURE) {
- return val->value().convertToUnit(targetUnit);
- }
- return 0.0;
-}
-
-double SingleOperation::parameterValueNumeric(
- const char *param_name, const common::UnitOfMeasure &targetUnit) const
- noexcept {
- const auto &val = parameterValue(param_name, 0);
- if (val && val->type() == ParameterValue::Type::MEASURE) {
- return val->value().convertToUnit(targetUnit);
- }
- return 0.0;
-}
-
-//! @endcond
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a PROJ-based single operation.
- *
- * \note The operation might internally be a pipeline chaining several
- * operations.
- * The use of the SingleOperation modeling here is mostly to be able to get
- * the PROJ string as a parameter.
- *
- * @param properties Properties
- * @param PROJString the PROJ string.
- * @param sourceCRS source CRS (might be null).
- * @param targetCRS target CRS (might be null).
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return the new instance
- */
-SingleOperationNNPtr SingleOperation::createPROJBased(
- const util::PropertyMap &properties, const std::string &PROJString,
- const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return util::nn_static_pointer_cast<SingleOperation>(
- PROJBasedOperation::create(properties, PROJString, sourceCRS, targetCRS,
- accuracies));
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static SingleOperationNNPtr createPROJBased(
- const util::PropertyMap &properties,
- const io::IPROJStringExportableNNPtr &projExportable,
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- const crs::CRSPtr &interpolationCRS,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies,
- bool hasBallparkTransformation) {
- return util::nn_static_pointer_cast<SingleOperation>(
- PROJBasedOperation::create(properties, projExportable, false, sourceCRS,
- targetCRS, interpolationCRS, accuracies,
- hasBallparkTransformation));
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-bool SingleOperation::_isEquivalentTo(
- const util::IComparable *other, util::IComparable::Criterion criterion,
- const io::DatabaseContextPtr &dbContext) const {
- return _isEquivalentTo(other, criterion, dbContext, false);
-}
-
-bool SingleOperation::_isEquivalentTo(const util::IComparable *other,
- util::IComparable::Criterion criterion,
- const io::DatabaseContextPtr &dbContext,
- bool inOtherDirection) const {
-
- auto otherSO = dynamic_cast<const SingleOperation *>(other);
- if (otherSO == nullptr ||
- (criterion == util::IComparable::Criterion::STRICT &&
- !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) {
- return false;
- }
-
- const int methodEPSGCode = d->method_->getEPSGCode();
- const int otherMethodEPSGCode = otherSO->d->method_->getEPSGCode();
-
- bool equivalentMethods =
- (criterion == util::IComparable::Criterion::EQUIVALENT &&
- methodEPSGCode != 0 && methodEPSGCode == otherMethodEPSGCode) ||
- d->method_->_isEquivalentTo(otherSO->d->method_.get(), criterion,
- dbContext);
- if (!equivalentMethods &&
- criterion == util::IComparable::Criterion::EQUIVALENT) {
- if ((methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA &&
- otherMethodEPSGCode ==
- EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) ||
- (otherMethodEPSGCode ==
- EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA &&
- methodEPSGCode ==
- EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) ||
- (methodEPSGCode ==
- EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA &&
- otherMethodEPSGCode ==
- EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) ||
- (otherMethodEPSGCode ==
- EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA &&
- methodEPSGCode ==
- EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) ||
- (methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
- otherMethodEPSGCode ==
- EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) ||
- (otherMethodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
- methodEPSGCode ==
- EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) {
- auto geodCRS =
- dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get());
- auto otherGeodCRS = dynamic_cast<const crs::GeodeticCRS *>(
- otherSO->sourceCRS().get());
- if (geodCRS && otherGeodCRS && geodCRS->ellipsoid()->isSphere() &&
- otherGeodCRS->ellipsoid()->isSphere()) {
- equivalentMethods = true;
- }
- }
- }
-
- if (!equivalentMethods) {
- if (criterion == util::IComparable::Criterion::EQUIVALENT) {
-
- const auto isTOWGS84Transf = [](int code) {
- return code ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
- code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
- code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
- code ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
- code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
- code ==
- EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
- code ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D ||
- code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D ||
- code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D;
- };
-
- // Translation vs (PV or CF)
- // or different PV vs CF convention
- if (isTOWGS84Transf(methodEPSGCode) &&
- isTOWGS84Transf(otherMethodEPSGCode)) {
- auto transf = static_cast<const Transformation *>(this);
- auto otherTransf = static_cast<const Transformation *>(otherSO);
- auto params = transf->getTOWGS84Parameters();
- auto otherParams = otherTransf->getTOWGS84Parameters();
- assert(params.size() == 7);
- assert(otherParams.size() == 7);
- for (size_t i = 0; i < 7; i++) {
- if (std::fabs(params[i] - otherParams[i]) >
- 1e-10 * std::fabs(params[i])) {
- return false;
- }
- }
- return true;
- }
-
- // _1SP methods can sometimes be equivalent to _2SP ones
- // Check it by using convertToOtherMethod()
- if (methodEPSGCode ==
- EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
- otherMethodEPSGCode ==
- EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
- // Convert from 2SP to 1SP as the other direction has more
- // degree of liberties.
- return otherSO->_isEquivalentTo(this, criterion, dbContext);
- } else if ((methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A &&
- otherMethodEPSGCode ==
- EPSG_CODE_METHOD_MERCATOR_VARIANT_B) ||
- (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
- otherMethodEPSGCode ==
- EPSG_CODE_METHOD_MERCATOR_VARIANT_A) ||
- (methodEPSGCode ==
- EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
- otherMethodEPSGCode ==
- EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) {
- auto conv = dynamic_cast<const Conversion *>(this);
- if (conv) {
- auto eqConv =
- conv->convertToOtherMethod(otherMethodEPSGCode);
- if (eqConv) {
- return eqConv->_isEquivalentTo(other, criterion,
- dbContext);
- }
- }
- }
- }
-
- return false;
- }
-
- const auto &values = d->parameterValues_;
- const auto &otherValues = otherSO->d->parameterValues_;
- const auto valuesSize = values.size();
- const auto otherValuesSize = otherValues.size();
- if (criterion == util::IComparable::Criterion::STRICT) {
- if (valuesSize != otherValuesSize) {
- return false;
- }
- for (size_t i = 0; i < valuesSize; i++) {
- if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion,
- dbContext)) {
- return false;
- }
- }
- return true;
- }
-
- std::vector<bool> candidateIndices(otherValuesSize, true);
- bool equivalent = true;
- bool foundMissingArgs = valuesSize != otherValuesSize;
-
- for (size_t i = 0; equivalent && i < valuesSize; i++) {
- auto opParamvalue =
- dynamic_cast<const OperationParameterValue *>(values[i].get());
- if (!opParamvalue)
- return false;
-
- equivalent = false;
- bool sameNameDifferentValue = false;
- for (size_t j = 0; j < otherValuesSize; j++) {
- if (candidateIndices[j] &&
- values[i]->_isEquivalentTo(otherValues[j].get(), criterion,
- dbContext)) {
- candidateIndices[j] = false;
- equivalent = true;
- break;
- } else if (candidateIndices[j]) {
- auto otherOpParamvalue =
- dynamic_cast<const OperationParameterValue *>(
- otherValues[j].get());
- if (!otherOpParamvalue)
- return false;
- sameNameDifferentValue =
- opParamvalue->parameter()->_isEquivalentTo(
- otherOpParamvalue->parameter().get(), criterion,
- dbContext);
- if (sameNameDifferentValue) {
- candidateIndices[j] = false;
- break;
- }
- }
- }
-
- if (!equivalent &&
- methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
- // For LCC_2SP, the standard parallels can be switched and
- // this will result in the same result.
- const int paramEPSGCode = opParamvalue->parameter()->getEPSGCode();
- if (paramEPSGCode ==
- EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL ||
- paramEPSGCode ==
- EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
- auto value_1st = parameterValue(
- EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL);
- auto value_2nd = parameterValue(
- EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL);
- if (value_1st && value_2nd) {
- equivalent =
- value_1st->_isEquivalentTo(
- otherSO
- ->parameterValue(
- EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)
- .get(),
- criterion, dbContext) &&
- value_2nd->_isEquivalentTo(
- otherSO
- ->parameterValue(
- EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)
- .get(),
- criterion, dbContext);
- }
- }
- }
-
- if (equivalent) {
- continue;
- }
-
- if (sameNameDifferentValue) {
- break;
- }
-
- // If there are parameters in this method not found in the other one,
- // check that they are set to a default neutral value, that is 1
- // for scale, and 0 otherwise.
- foundMissingArgs = true;
- const auto &value = opParamvalue->parameterValue();
- if (value->type() != ParameterValue::Type::MEASURE) {
- break;
- }
- if (value->value().unit().type() ==
- common::UnitOfMeasure::Type::SCALE) {
- equivalent = value->value().getSIValue() == 1.0;
- } else {
- equivalent = value->value().getSIValue() == 0.0;
- }
- }
-
- // In the case the arguments don't perfectly match, try the reverse
- // check.
- if (equivalent && foundMissingArgs && !inOtherDirection) {
- return otherSO->_isEquivalentTo(this, criterion, dbContext, true);
- }
-
- // Equivalent formulations of 2SP can have different parameters
- // Then convert to 1SP and compare.
- if (!equivalent &&
- methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
- auto conv = dynamic_cast<const Conversion *>(this);
- auto otherConv = dynamic_cast<const Conversion *>(other);
- if (conv && otherConv) {
- auto thisAs1SP = conv->convertToOtherMethod(
- EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
- auto otherAs1SP = otherConv->convertToOtherMethod(
- EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
- if (thisAs1SP && otherAs1SP) {
- equivalent = thisAs1SP->_isEquivalentTo(otherAs1SP.get(),
- criterion, dbContext);
- }
- }
- }
- return equivalent;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-std::set<GridDescription>
-SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
- bool considerKnownGridsAsAvailable) const {
- std::set<GridDescription> res;
- for (const auto &genOpParamvalue : parameterValues()) {
- auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto &value = opParamvalue->parameterValue();
- if (value->type() == ParameterValue::Type::FILENAME) {
- const auto gridNames = split(value->valueFile(), ",");
- for (const auto &gridName : gridNames) {
- GridDescription desc;
- desc.shortName = gridName;
- if (databaseContext) {
- databaseContext->lookForGridInfo(
- desc.shortName, considerKnownGridsAsAvailable,
- desc.fullName, desc.packageName, desc.url,
- desc.directDownload, desc.openLicense,
- desc.available);
- }
- res.insert(desc);
- }
- }
- }
- }
- return res;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Validate the parameters used by a coordinate operation.
- *
- * Return whether the method is known or not, or a list of missing or extra
- * parameters for the operations recognized by this implementation.
- */
-std::list<std::string> SingleOperation::validateParameters() const {
- std::list<std::string> res;
-
- const auto &l_method = method();
- const auto &methodName = l_method->nameStr();
- const MethodMapping *methodMapping = nullptr;
- const auto methodEPSGCode = l_method->getEPSGCode();
- for (const auto &mapping : projectionMethodMappings) {
- if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
- methodName.c_str()) ||
- (methodEPSGCode != 0 && methodEPSGCode == mapping.epsg_code)) {
- methodMapping = &mapping;
- }
- }
- if (methodMapping == nullptr) {
- for (const auto &mapping : otherMethodMappings) {
- if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
- methodName.c_str()) ||
- (methodEPSGCode != 0 && methodEPSGCode == mapping.epsg_code)) {
- methodMapping = &mapping;
- }
- }
- }
- if (!methodMapping) {
- res.emplace_back("Unknown method " + methodName);
- return res;
- }
- if (methodMapping->wkt2_name != methodName) {
- if (metadata::Identifier::isEquivalentName(methodMapping->wkt2_name,
- methodName.c_str())) {
- std::string msg("Method name ");
- msg += methodName;
- msg += " is equivalent to official ";
- msg += methodMapping->wkt2_name;
- msg += " but not strictly equal";
- res.emplace_back(msg);
- } else {
- std::string msg("Method name ");
- msg += methodName;
- msg += ", matched to ";
- msg += methodMapping->wkt2_name;
- msg += ", through its EPSG code has not an equivalent name";
- res.emplace_back(msg);
- }
- }
- if (methodEPSGCode != 0 && methodEPSGCode != methodMapping->epsg_code) {
- std::string msg("Method of EPSG code ");
- msg += toString(methodEPSGCode);
- msg += " does not match official code (";
- msg += toString(methodMapping->epsg_code);
- msg += ')';
- res.emplace_back(msg);
- }
-
- // Check if expected parameters are found
- for (int i = 0;
- methodMapping->params && methodMapping->params[i] != nullptr; ++i) {
- const auto *paramMapping = methodMapping->params[i];
-
- const OperationParameterValue *opv = nullptr;
- for (const auto &genOpParamvalue : parameterValues()) {
- auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto &parameter = opParamvalue->parameter();
- if ((paramMapping->epsg_code != 0 &&
- parameter->getEPSGCode() == paramMapping->epsg_code) ||
- ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) {
- opv = opParamvalue;
- break;
- }
- }
- }
-
- if (!opv) {
- if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) &&
- paramMapping == &paramLatitudeNatOrigin) {
- // extension of EPSG used by GDAL/PROJ, so we should not
- // warn on its absence.
- continue;
- }
- std::string msg("Cannot find expected parameter ");
- msg += paramMapping->wkt2_name;
- res.emplace_back(msg);
- continue;
- }
- const auto &parameter = opv->parameter();
- if (paramMapping->wkt2_name != parameter->nameStr()) {
- if (ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) {
- std::string msg("Parameter name ");
- msg += parameter->nameStr();
- msg += " is equivalent to official ";
- msg += paramMapping->wkt2_name;
- msg += " but not strictly equal";
- res.emplace_back(msg);
- } else {
- std::string msg("Parameter name ");
- msg += parameter->nameStr();
- msg += ", matched to ";
- msg += paramMapping->wkt2_name;
- msg += ", through its EPSG code has not an equivalent name";
- res.emplace_back(msg);
- }
- }
- const auto paramEPSGCode = parameter->getEPSGCode();
- if (paramEPSGCode != 0 && paramEPSGCode != paramMapping->epsg_code) {
- std::string msg("Parameter of EPSG code ");
- msg += toString(paramEPSGCode);
- msg += " does not match official code (";
- msg += toString(paramMapping->epsg_code);
- msg += ')';
- res.emplace_back(msg);
- }
- }
-
- // Check if there are extra parameters
- for (const auto &genOpParamvalue : parameterValues()) {
- auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto &parameter = opParamvalue->parameter();
- if (!getMapping(methodMapping, parameter)) {
- std::string msg("Parameter ");
- msg += parameter->nameStr();
- msg += " found but not expected for this method";
- res.emplace_back(msg);
- }
- }
- }
-
- return res;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct ParameterValue::Private {
- ParameterValue::Type type_{ParameterValue::Type::STRING};
- std::unique_ptr<common::Measure> measure_{};
- std::unique_ptr<std::string> stringValue_{};
- int integerValue_{};
- bool booleanValue_{};
-
- explicit Private(const common::Measure &valueIn)
- : type_(ParameterValue::Type::MEASURE),
- measure_(internal::make_unique<common::Measure>(valueIn)) {}
-
- Private(const std::string &stringValueIn, ParameterValue::Type typeIn)
- : type_(typeIn),
- stringValue_(internal::make_unique<std::string>(stringValueIn)) {}
-
- explicit Private(int integerValueIn)
- : type_(ParameterValue::Type::INTEGER), integerValue_(integerValueIn) {}
-
- explicit Private(bool booleanValueIn)
- : type_(ParameterValue::Type::BOOLEAN), booleanValue_(booleanValueIn) {}
-};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-ParameterValue::~ParameterValue() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-ParameterValue::ParameterValue(const common::Measure &measureIn)
- : d(internal::make_unique<Private>(measureIn)) {}
-
-// ---------------------------------------------------------------------------
-
-ParameterValue::ParameterValue(const std::string &stringValueIn,
- ParameterValue::Type typeIn)
- : d(internal::make_unique<Private>(stringValueIn, typeIn)) {}
-
-// ---------------------------------------------------------------------------
-
-ParameterValue::ParameterValue(int integerValueIn)
- : d(internal::make_unique<Private>(integerValueIn)) {}
-
-// ---------------------------------------------------------------------------
-
-ParameterValue::ParameterValue(bool booleanValueIn)
- : d(internal::make_unique<Private>(booleanValueIn)) {}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a ParameterValue from a Measure (i.e. a value associated
- * with a
- * unit)
- *
- * @return a new ParameterValue.
- */
-ParameterValueNNPtr ParameterValue::create(const common::Measure &measureIn) {
- return ParameterValue::nn_make_shared<ParameterValue>(measureIn);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a ParameterValue from a string value.
- *
- * @return a new ParameterValue.
- */
-ParameterValueNNPtr ParameterValue::create(const char *stringValueIn) {
- return ParameterValue::nn_make_shared<ParameterValue>(
- std::string(stringValueIn), ParameterValue::Type::STRING);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a ParameterValue from a string value.
- *
- * @return a new ParameterValue.
- */
-ParameterValueNNPtr ParameterValue::create(const std::string &stringValueIn) {
- return ParameterValue::nn_make_shared<ParameterValue>(
- stringValueIn, ParameterValue::Type::STRING);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a ParameterValue from a filename.
- *
- * @return a new ParameterValue.
- */
-ParameterValueNNPtr
-ParameterValue::createFilename(const std::string &stringValueIn) {
- return ParameterValue::nn_make_shared<ParameterValue>(
- stringValueIn, ParameterValue::Type::FILENAME);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a ParameterValue from a integer value.
- *
- * @return a new ParameterValue.
- */
-ParameterValueNNPtr ParameterValue::create(int integerValueIn) {
- return ParameterValue::nn_make_shared<ParameterValue>(integerValueIn);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a ParameterValue from a boolean value.
- *
- * @return a new ParameterValue.
- */
-ParameterValueNNPtr ParameterValue::create(bool booleanValueIn) {
- return ParameterValue::nn_make_shared<ParameterValue>(booleanValueIn);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Returns the type of a parameter value.
- *
- * @return the type.
- */
-const ParameterValue::Type &ParameterValue::type() PROJ_PURE_DEFN {
- return d->type_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Returns the value as a Measure (assumes type() == Type::MEASURE)
- * @return the value as a Measure.
- */
-const common::Measure &ParameterValue::value() PROJ_PURE_DEFN {
- return *d->measure_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Returns the value as a string (assumes type() == Type::STRING)
- * @return the value as a string.
- */
-const std::string &ParameterValue::stringValue() PROJ_PURE_DEFN {
- return *d->stringValue_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Returns the value as a filename (assumes type() == Type::FILENAME)
- * @return the value as a filename.
- */
-const std::string &ParameterValue::valueFile() PROJ_PURE_DEFN {
- return *d->stringValue_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Returns the value as a integer (assumes type() == Type::INTEGER)
- * @return the value as a integer.
- */
-int ParameterValue::integerValue() PROJ_PURE_DEFN { return d->integerValue_; }
-
-// ---------------------------------------------------------------------------
-
-/** \brief Returns the value as a boolean (assumes type() == Type::BOOLEAN)
- * @return the value as a boolean.
- */
-bool ParameterValue::booleanValue() PROJ_PURE_DEFN { return d->booleanValue_; }
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-void ParameterValue::_exportToWKT(io::WKTFormatter *formatter) const {
- const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
-
- const auto &l_type = type();
- if (l_type == Type::MEASURE) {
- const auto &l_value = value();
- if (formatter->abridgedTransformation()) {
- const auto &unit = l_value.unit();
- const auto &unitType = unit.type();
- if (unitType == common::UnitOfMeasure::Type::LINEAR) {
- formatter->add(l_value.getSIValue());
- } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
- formatter->add(
- l_value.convertToUnit(common::UnitOfMeasure::ARC_SECOND));
- } else if (unit == common::UnitOfMeasure::PARTS_PER_MILLION) {
- formatter->add(1.0 + l_value.value() * 1e-6);
- } else {
- formatter->add(l_value.value());
- }
- } else {
- const auto &unit = l_value.unit();
- if (isWKT2) {
- formatter->add(l_value.value());
- } else {
- // In WKT1, as we don't output the natural unit, output to the
- // registered linear / angular unit.
- const auto &unitType = unit.type();
- if (unitType == common::UnitOfMeasure::Type::LINEAR) {
- const auto &targetUnit = *(formatter->axisLinearUnit());
- if (targetUnit.conversionToSI() == 0.0) {
- throw io::FormattingException(
- "cannot convert value to target linear unit");
- }
- formatter->add(l_value.convertToUnit(targetUnit));
- } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
- const auto &targetUnit = *(formatter->axisAngularUnit());
- if (targetUnit.conversionToSI() == 0.0) {
- throw io::FormattingException(
- "cannot convert value to target angular unit");
- }
- formatter->add(l_value.convertToUnit(targetUnit));
- } else {
- formatter->add(l_value.getSIValue());
- }
- }
- if (isWKT2 && unit != common::UnitOfMeasure::NONE) {
- if (!formatter
- ->primeMeridianOrParameterUnitOmittedIfSameAsAxis() ||
- (unit != common::UnitOfMeasure::SCALE_UNITY &&
- unit != *(formatter->axisLinearUnit()) &&
- unit != *(formatter->axisAngularUnit()))) {
- unit._exportToWKT(formatter);
- }
- }
- }
- } else if (l_type == Type::STRING || l_type == Type::FILENAME) {
- formatter->addQuotedString(stringValue());
- } else if (l_type == Type::INTEGER) {
- formatter->add(integerValue());
- } else {
- throw io::FormattingException("boolean parameter value not handled");
- }
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-bool ParameterValue::_isEquivalentTo(const util::IComparable *other,
- util::IComparable::Criterion criterion,
- const io::DatabaseContextPtr &) const {
- auto otherPV = dynamic_cast<const ParameterValue *>(other);
- if (otherPV == nullptr) {
- return false;
- }
- if (type() != otherPV->type()) {
- return false;
- }
- switch (type()) {
- case Type::MEASURE: {
- return value()._isEquivalentTo(otherPV->value(), criterion, 2e-10);
- }
-
- case Type::STRING:
- case Type::FILENAME: {
- return stringValue() == otherPV->stringValue();
- }
-
- case Type::INTEGER: {
- return integerValue() == otherPV->integerValue();
- }
-
- case Type::BOOLEAN: {
- return booleanValue() == otherPV->booleanValue();
- }
-
- default: {
- assert(false);
- break;
- }
- }
- return true;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct Conversion::Private {};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-Conversion::Conversion(const OperationMethodNNPtr &methodIn,
- const std::vector<GeneralParameterValueNNPtr> &values)
- : SingleOperation(methodIn), d(nullptr) {
- setParameterValues(values);
-}
-
-// ---------------------------------------------------------------------------
-
-Conversion::Conversion(const Conversion &other)
- : CoordinateOperation(other), SingleOperation(other), d(nullptr) {}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-Conversion::~Conversion() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-ConversionNNPtr Conversion::shallowClone() const {
- auto conv = Conversion::nn_make_shared<Conversion>(*this);
- conv->assignSelf(conv);
- conv->setCRSs(this, false);
- return conv;
-}
-
-CoordinateOperationNNPtr Conversion::_shallowClone() const {
- return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-ConversionNNPtr
-Conversion::alterParametersLinearUnit(const common::UnitOfMeasure &unit,
- bool convertToNewUnit) const {
-
- std::vector<GeneralParameterValueNNPtr> newValues;
- bool changesDone = false;
- for (const auto &genOpParamvalue : parameterValues()) {
- bool updated = false;
- auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto &paramValue = opParamvalue->parameterValue();
- if (paramValue->type() == ParameterValue::Type::MEASURE) {
- const auto &measure = paramValue->value();
- if (measure.unit().type() ==
- common::UnitOfMeasure::Type::LINEAR) {
- if (!measure.unit()._isEquivalentTo(
- unit, util::IComparable::Criterion::EQUIVALENT)) {
- const double newValue =
- convertToNewUnit ? measure.convertToUnit(unit)
- : measure.value();
- newValues.emplace_back(OperationParameterValue::create(
- opParamvalue->parameter(),
- ParameterValue::create(
- common::Measure(newValue, unit))));
- updated = true;
- }
- }
- }
- }
- if (updated) {
- changesDone = true;
- } else {
- newValues.emplace_back(genOpParamvalue);
- }
- }
- if (changesDone) {
- auto conv = create(util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY, "unknown"),
- method(), newValues);
- conv->setCRSs(this, false);
- return conv;
- } else {
- return NN_NO_CHECK(
- util::nn_dynamic_pointer_cast<Conversion>(shared_from_this()));
- }
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a Conversion from a vector of GeneralParameterValue.
- *
- * @param properties See \ref general_properties. At minimum the name should be
- * defined.
- * @param methodIn the operation method.
- * @param values the values.
- * @return a new Conversion.
- * @throws InvalidOperation
- */
-ConversionNNPtr Conversion::create(const util::PropertyMap &properties,
- const OperationMethodNNPtr &methodIn,
- const std::vector<GeneralParameterValueNNPtr>
- &values) // throw InvalidOperation
-{
- if (methodIn->parameters().size() != values.size()) {
- throw InvalidOperation(
- "Inconsistent number of parameters and parameter values");
- }
- auto conv = Conversion::nn_make_shared<Conversion>(methodIn, values);
- conv->assignSelf(conv);
- conv->setProperties(properties);
- return conv;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a Conversion and its OperationMethod
- *
- * @param propertiesConversion See \ref general_properties of the conversion.
- * At minimum the name should be defined.
- * @param propertiesOperationMethod See \ref general_properties of the operation
- * method. At minimum the name should be defined.
- * @param parameters the operation parameters.
- * @param values the operation values. Constraint:
- * values.size() == parameters.size()
- * @return a new Conversion.
- * @throws InvalidOperation
- */
-ConversionNNPtr Conversion::create(
- const util::PropertyMap &propertiesConversion,
- const util::PropertyMap &propertiesOperationMethod,
- const std::vector<OperationParameterNNPtr> &parameters,
- const std::vector<ParameterValueNNPtr> &values) // throw InvalidOperation
-{
- OperationMethodNNPtr op(
- OperationMethod::create(propertiesOperationMethod, parameters));
-
- if (parameters.size() != values.size()) {
- throw InvalidOperation(
- "Inconsistent number of parameters and parameter values");
- }
- std::vector<GeneralParameterValueNNPtr> generalParameterValues;
- generalParameterValues.reserve(values.size());
- for (size_t i = 0; i < values.size(); i++) {
- generalParameterValues.push_back(
- OperationParameterValue::create(parameters[i], values[i]));
- }
- return create(propertiesConversion, op, generalParameterValues);
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-// ---------------------------------------------------------------------------
-
-static util::PropertyMap createMapNameEPSGCode(const std::string &name,
- int code) {
- return util::PropertyMap()
- .set(common::IdentifiedObject::NAME_KEY, name)
- .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
- .set(metadata::Identifier::CODE_KEY, code);
-}
-
-// ---------------------------------------------------------------------------
-
-static util::PropertyMap createMapNameEPSGCode(const char *name, int code) {
- return util::PropertyMap()
- .set(common::IdentifiedObject::NAME_KEY, name)
- .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
- .set(metadata::Identifier::CODE_KEY, code);
-}
-
-// ---------------------------------------------------------------------------
-
-static util::PropertyMap createMethodMapNameEPSGCode(int code) {
- const char *name = nullptr;
- for (const auto &tuple : methodNameCodes) {
- if (tuple.epsg_code == code) {
- name = tuple.name;
- break;
- }
- }
- assert(name);
- return createMapNameEPSGCode(name, code);
-}
-
-// ---------------------------------------------------------------------------
-
-static util::PropertyMap
-getUTMConversionProperty(const util::PropertyMap &properties, int zone,
- bool north) {
- if (!properties.get(common::IdentifiedObject::NAME_KEY)) {
- std::string conversionName("UTM zone ");
- conversionName += toString(zone);
- conversionName += (north ? 'N' : 'S');
-
- return createMapNameEPSGCode(conversionName,
- (north ? 16000 : 17000) + zone);
- } else {
- return properties;
- }
-}
-
-// ---------------------------------------------------------------------------
-
-static util::PropertyMap
-addDefaultNameIfNeeded(const util::PropertyMap &properties,
- const std::string &defaultName) {
- if (!properties.get(common::IdentifiedObject::NAME_KEY)) {
- return util::PropertyMap(properties)
- .set(common::IdentifiedObject::NAME_KEY, defaultName);
- } else {
- return properties;
- }
-}
-
-// ---------------------------------------------------------------------------
-
-static ConversionNNPtr
-createConversion(const util::PropertyMap &properties,
- const MethodMapping *mapping,
- const std::vector<ParameterValueNNPtr> &values) {
-
- std::vector<OperationParameterNNPtr> parameters;
- for (int i = 0; mapping->params[i] != nullptr; i++) {
- const auto *param = mapping->params[i];
- auto paramProperties = util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY, param->wkt2_name);
- if (param->epsg_code != 0) {
- paramProperties
- .set(metadata::Identifier::CODESPACE_KEY,
- metadata::Identifier::EPSG)
- .set(metadata::Identifier::CODE_KEY, param->epsg_code);
- }
- auto parameter = OperationParameter::create(paramProperties);
- parameters.push_back(parameter);
- }
-
- auto methodProperties = util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY, mapping->wkt2_name);
- if (mapping->epsg_code != 0) {
- methodProperties
- .set(metadata::Identifier::CODESPACE_KEY,
- metadata::Identifier::EPSG)
- .set(metadata::Identifier::CODE_KEY, mapping->epsg_code);
- }
- return Conversion::create(
- addDefaultNameIfNeeded(properties, mapping->wkt2_name),
- methodProperties, parameters, values);
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-ConversionNNPtr
-Conversion::create(const util::PropertyMap &properties, int method_epsg_code,
- const std::vector<ParameterValueNNPtr> &values) {
- const MethodMapping *mapping = getMapping(method_epsg_code);
- assert(mapping);
- return createConversion(properties, mapping, values);
-}
-
-// ---------------------------------------------------------------------------
-
-ConversionNNPtr
-Conversion::create(const util::PropertyMap &properties,
- const char *method_wkt2_name,
- const std::vector<ParameterValueNNPtr> &values) {
- const MethodMapping *mapping = getMapping(method_wkt2_name);
- assert(mapping);
- return createConversion(properties, mapping, values);
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-struct VectorOfParameters : public std::vector<OperationParameterNNPtr> {
- VectorOfParameters() : std::vector<OperationParameterNNPtr>() {}
- explicit VectorOfParameters(
- std::initializer_list<OperationParameterNNPtr> list)
- : std::vector<OperationParameterNNPtr>(list) {}
- VectorOfParameters(const VectorOfParameters &) = delete;
-
- ~VectorOfParameters();
-};
-
-// This way, we disable inlining of destruction, and save a lot of space
-VectorOfParameters::~VectorOfParameters() = default;
-
-struct VectorOfValues : public std::vector<ParameterValueNNPtr> {
- VectorOfValues() : std::vector<ParameterValueNNPtr>() {}
- explicit VectorOfValues(std::initializer_list<ParameterValueNNPtr> list)
- : std::vector<ParameterValueNNPtr>(list) {}
-
- explicit VectorOfValues(std::initializer_list<common::Measure> list);
- VectorOfValues(const VectorOfValues &) = delete;
- VectorOfValues(VectorOfValues &&) = default;
-
- ~VectorOfValues();
-};
-
-static std::vector<ParameterValueNNPtr> buildParameterValueFromMeasure(
- const std::initializer_list<common::Measure> &list) {
- std::vector<ParameterValueNNPtr> res;
- for (const auto &v : list) {
- res.emplace_back(ParameterValue::create(v));
- }
- return res;
-}
-
-VectorOfValues::VectorOfValues(std::initializer_list<common::Measure> list)
- : std::vector<ParameterValueNNPtr>(buildParameterValueFromMeasure(list)) {}
-
-// This way, we disable inlining of destruction, and save a lot of space
-VectorOfValues::~VectorOfValues() = default;
-
-PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1,
- const common::Measure &m2,
- const common::Measure &m3) {
- return VectorOfValues{ParameterValue::create(m1),
- ParameterValue::create(m2),
- ParameterValue::create(m3)};
-}
-
-PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1,
- const common::Measure &m2,
- const common::Measure &m3,
- const common::Measure &m4) {
- return VectorOfValues{
- ParameterValue::create(m1), ParameterValue::create(m2),
- ParameterValue::create(m3), ParameterValue::create(m4)};
-}
-
-PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1,
- const common::Measure &m2,
- const common::Measure &m3,
- const common::Measure &m4,
- const common::Measure &m5) {
- return VectorOfValues{
- ParameterValue::create(m1), ParameterValue::create(m2),
- ParameterValue::create(m3), ParameterValue::create(m4),
- ParameterValue::create(m5),
- };
-}
-
-PROJ_NO_INLINE static VectorOfValues
-createParams(const common::Measure &m1, const common::Measure &m2,
- const common::Measure &m3, const common::Measure &m4,
- const common::Measure &m5, const common::Measure &m6) {
- return VectorOfValues{
- ParameterValue::create(m1), ParameterValue::create(m2),
- ParameterValue::create(m3), ParameterValue::create(m4),
- ParameterValue::create(m5), ParameterValue::create(m6),
- };
-}
-
-PROJ_NO_INLINE static VectorOfValues
-createParams(const common::Measure &m1, const common::Measure &m2,
- const common::Measure &m3, const common::Measure &m4,
- const common::Measure &m5, const common::Measure &m6,
- const common::Measure &m7) {
- return VectorOfValues{
- ParameterValue::create(m1), ParameterValue::create(m2),
- ParameterValue::create(m3), ParameterValue::create(m4),
- ParameterValue::create(m5), ParameterValue::create(m6),
- ParameterValue::create(m7),
- };
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a [Universal Transverse Mercator]
- *(https://proj.org/operations/projections/utm.html) conversion.
- *
- * UTM is a family of conversions, of EPSG codes from 16001 to 16060 for the
- * northern hemisphere, and 17001 to 17060 for the southern hemisphere,
- * based on the Transverse Mercator projection method.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param zone UTM zone number between 1 and 60.
- * @param north true for UTM northern hemisphere, false for UTM southern
- * hemisphere.
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createUTM(const util::PropertyMap &properties,
- int zone, bool north) {
- return create(
- getUTMConversionProperty(properties, zone, north),
- EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
- createParams(common::Angle(UTM_LATITUDE_OF_NATURAL_ORIGIN),
- common::Angle(zone * 6.0 - 183.0),
- common::Scale(UTM_SCALE_FACTOR),
- common::Length(UTM_FALSE_EASTING),
- common::Length(north ? UTM_NORTH_FALSE_NORTHING
- : UTM_SOUTH_FALSE_NORTHING)));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Transverse Mercator]
- *(https://proj.org/operations/projections/tmerc.html) projection method.
- *
- * This method is defined as [EPSG:9807]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9807)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param scale See \ref scale
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createTransverseMercator(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Scale &scale,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
- createParams(centerLat, centerLong, scale, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Gauss Schreiber Transverse
- *Mercator]
- *(https://proj.org/operations/projections/gstmerc.html) projection method.
- *
- * This method is also known as Gauss-Laborde Reunion.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param scale See \ref scale
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createGaussSchreiberTransverseMercator(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Scale &scale,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties,
- PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR,
- createParams(centerLat, centerLong, scale, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Transverse Mercator South
- *Orientated]
- *(https://proj.org/operations/projections/tmerc.html) projection method.
- *
- * This method is defined as [EPSG:9808]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9808)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param scale See \ref scale
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createTransverseMercatorSouthOriented(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Scale &scale,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties,
- EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED,
- createParams(centerLat, centerLong, scale, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Two Point Equidistant]
- *(https://proj.org/operations/projections/tpeqd.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeFirstPoint Latitude of first point.
- * @param longitudeFirstPoint Longitude of first point.
- * @param latitudeSecondPoint Latitude of second point.
- * @param longitudeSeconPoint Longitude of second point.
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr
-Conversion::createTwoPointEquidistant(const util::PropertyMap &properties,
- const common::Angle &latitudeFirstPoint,
- const common::Angle &longitudeFirstPoint,
- const common::Angle &latitudeSecondPoint,
- const common::Angle &longitudeSeconPoint,
- const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT,
- createParams(latitudeFirstPoint, longitudeFirstPoint,
- latitudeSecondPoint, longitudeSeconPoint,
- falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the Tunisia Mapping Grid projection
- * method.
- *
- * This method is defined as [EPSG:9816]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9816)
- *
- * \note There is currently no implementation of the method formulas in PROJ.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createTunisiaMappingGrid(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(
- properties, EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID,
- createParams(centerLat, centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Albers Conic Equal Area]
- *(https://proj.org/operations/projections/aea.html) projection method.
- *
- * This method is defined as [EPSG:9822]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9822)
- *
- * @note the order of arguments is conformant with the corresponding EPSG
- * mode and different than OGRSpatialReference::setACEA() of GDAL &lt;= 2.3
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeFalseOrigin See \ref latitude_false_origin
- * @param longitudeFalseOrigin See \ref longitude_false_origin
- * @param latitudeFirstParallel See \ref latitude_first_std_parallel
- * @param latitudeSecondParallel See \ref latitude_second_std_parallel
- * @param eastingFalseOrigin See \ref easting_false_origin
- * @param northingFalseOrigin See \ref northing_false_origin
- * @return a new Conversion.
- */
-ConversionNNPtr
-Conversion::createAlbersEqualArea(const util::PropertyMap &properties,
- const common::Angle &latitudeFalseOrigin,
- const common::Angle &longitudeFalseOrigin,
- const common::Angle &latitudeFirstParallel,
- const common::Angle &latitudeSecondParallel,
- const common::Length &eastingFalseOrigin,
- const common::Length &northingFalseOrigin) {
- return create(properties, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA,
- createParams(latitudeFalseOrigin, longitudeFalseOrigin,
- latitudeFirstParallel, latitudeSecondParallel,
- eastingFalseOrigin, northingFalseOrigin));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Lambert Conic Conformal 1SP]
- *(https://proj.org/operations/projections/lcc.html) projection method.
- *
- * This method is defined as [EPSG:9801]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9801)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param scale See \ref scale
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createLambertConicConformal_1SP(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Scale &scale,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP,
- createParams(centerLat, centerLong, scale, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP)]
- *(https://proj.org/operations/projections/lcc.html) projection method.
- *
- * This method is defined as [EPSG:9802]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9802)
- *
- * @note the order of arguments is conformant with the corresponding EPSG
- * mode and different than OGRSpatialReference::setLCC() of GDAL &lt;= 2.3
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeFalseOrigin See \ref latitude_false_origin
- * @param longitudeFalseOrigin See \ref longitude_false_origin
- * @param latitudeFirstParallel See \ref latitude_first_std_parallel
- * @param latitudeSecondParallel See \ref latitude_second_std_parallel
- * @param eastingFalseOrigin See \ref easting_false_origin
- * @param northingFalseOrigin See \ref northing_false_origin
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createLambertConicConformal_2SP(
- const util::PropertyMap &properties,
- const common::Angle &latitudeFalseOrigin,
- const common::Angle &longitudeFalseOrigin,
- const common::Angle &latitudeFirstParallel,
- const common::Angle &latitudeSecondParallel,
- const common::Length &eastingFalseOrigin,
- const common::Length &northingFalseOrigin) {
- return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP,
- createParams(latitudeFalseOrigin, longitudeFalseOrigin,
- latitudeFirstParallel, latitudeSecondParallel,
- eastingFalseOrigin, northingFalseOrigin));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP
- *Michigan)]
- *(https://proj.org/operations/projections/lcc.html) projection method.
- *
- * This method is defined as [EPSG:1051]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1051)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeFalseOrigin See \ref latitude_false_origin
- * @param longitudeFalseOrigin See \ref longitude_false_origin
- * @param latitudeFirstParallel See \ref latitude_first_std_parallel
- * @param latitudeSecondParallel See \ref latitude_second_std_parallel
- * @param eastingFalseOrigin See \ref easting_false_origin
- * @param northingFalseOrigin See \ref northing_false_origin
- * @param ellipsoidScalingFactor Ellipsoid scaling factor.
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createLambertConicConformal_2SP_Michigan(
- const util::PropertyMap &properties,
- const common::Angle &latitudeFalseOrigin,
- const common::Angle &longitudeFalseOrigin,
- const common::Angle &latitudeFirstParallel,
- const common::Angle &latitudeSecondParallel,
- const common::Length &eastingFalseOrigin,
- const common::Length &northingFalseOrigin,
- const common::Scale &ellipsoidScalingFactor) {
- return create(properties,
- EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN,
- createParams(latitudeFalseOrigin, longitudeFalseOrigin,
- latitudeFirstParallel, latitudeSecondParallel,
- eastingFalseOrigin, northingFalseOrigin,
- ellipsoidScalingFactor));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP
- *Belgium)]
- *(https://proj.org/operations/projections/lcc.html) projection method.
- *
- * This method is defined as [EPSG:9803]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9803)
- *
- * \warning The formulas used currently in PROJ are, incorrectly, the ones of
- * the regular LCC_2SP method.
- *
- * @note the order of arguments is conformant with the corresponding EPSG
- * mode and different than OGRSpatialReference::setLCCB() of GDAL &lt;= 2.3
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeFalseOrigin See \ref latitude_false_origin
- * @param longitudeFalseOrigin See \ref longitude_false_origin
- * @param latitudeFirstParallel See \ref latitude_first_std_parallel
- * @param latitudeSecondParallel See \ref latitude_second_std_parallel
- * @param eastingFalseOrigin See \ref easting_false_origin
- * @param northingFalseOrigin See \ref northing_false_origin
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createLambertConicConformal_2SP_Belgium(
- const util::PropertyMap &properties,
- const common::Angle &latitudeFalseOrigin,
- const common::Angle &longitudeFalseOrigin,
- const common::Angle &latitudeFirstParallel,
- const common::Angle &latitudeSecondParallel,
- const common::Length &eastingFalseOrigin,
- const common::Length &northingFalseOrigin) {
-
- return create(properties,
- EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM,
- createParams(latitudeFalseOrigin, longitudeFalseOrigin,
- latitudeFirstParallel, latitudeSecondParallel,
- eastingFalseOrigin, northingFalseOrigin));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Modified Azimuthal
- *Equidistant]
- *(https://proj.org/operations/projections/aeqd.html) projection method.
- *
- * This method is defined as [EPSG:9832]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9832)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeNatOrigin See \ref center_latitude
- * @param longitudeNatOrigin See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createAzimuthalEquidistant(
- const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
- const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT,
- createParams(latitudeNatOrigin, longitudeNatOrigin,
- falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Guam Projection]
- *(https://proj.org/operations/projections/aeqd.html) projection method.
- *
- * This method is defined as [EPSG:9831]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9831)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- *is
- * not provided, it is automatically set.
- * @param latitudeNatOrigin See \ref center_latitude
- * @param longitudeNatOrigin See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createGuamProjection(
- const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
- const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_GUAM_PROJECTION,
- createParams(latitudeNatOrigin, longitudeNatOrigin,
- falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Bonne]
- *(https://proj.org/operations/projections/bonne.html) projection method.
- *
- * This method is defined as [EPSG:9827]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9827)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeNatOrigin See \ref center_latitude . PROJ calls its the
- * standard parallel 1.
- * @param longitudeNatOrigin See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createBonne(const util::PropertyMap &properties,
- const common::Angle &latitudeNatOrigin,
- const common::Angle &longitudeNatOrigin,
- const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_BONNE,
- createParams(latitudeNatOrigin, longitudeNatOrigin,
- falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Lambert Cylindrical Equal Area
- *(Spherical)]
- *(https://proj.org/operations/projections/cea.html) projection method.
- *
- * This method is defined as [EPSG:9834]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9834)
- *
- * \warning The PROJ cea computation code would select the ellipsoidal form if
- * a non-spherical ellipsoid is used for the base GeographicCRS.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
- * @param longitudeNatOrigin See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createLambertCylindricalEqualAreaSpherical(
- const util::PropertyMap &properties,
- const common::Angle &latitudeFirstParallel,
- const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties,
- EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL,
- createParams(latitudeFirstParallel, longitudeNatOrigin,
- falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Lambert Cylindrical Equal Area
- *(ellipsoidal form)]
- *(https://proj.org/operations/projections/cea.html) projection method.
- *
- * This method is defined as [EPSG:9835]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9835)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
- * @param longitudeNatOrigin See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createLambertCylindricalEqualArea(
- const util::PropertyMap &properties,
- const common::Angle &latitudeFirstParallel,
- const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA,
- createParams(latitudeFirstParallel, longitudeNatOrigin,
- falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Cassini-Soldner]
- * (https://proj.org/operations/projections/cass.html) projection method.
- *
- * This method is defined as [EPSG:9806]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9806)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createCassiniSoldner(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(
- properties, EPSG_CODE_METHOD_CASSINI_SOLDNER,
- createParams(centerLat, centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Equidistant Conic]
- *(https://proj.org/operations/projections/eqdc.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @note Although not found in EPSG, the order of arguments is conformant with
- * the "spirit" of EPSG and different than OGRSpatialReference::setEC() of GDAL
- *&lt;= 2.3 * @param properties See \ref general_properties of the conversion.
- *If the name
- * is not provided, it is automatically set.
- *
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param latitudeFirstParallel See \ref latitude_first_std_parallel
- * @param latitudeSecondParallel See \ref latitude_second_std_parallel
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createEquidistantConic(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Angle &latitudeFirstParallel,
- const common::Angle &latitudeSecondParallel,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC,
- createParams(centerLat, centerLong, latitudeFirstParallel,
- latitudeSecondParallel, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Eckert I]
- * (https://proj.org/operations/projections/eck1.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createEckertI(const util::PropertyMap &properties,
- const common::Angle &centerLong,
- const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_I,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Eckert II]
- * (https://proj.org/operations/projections/eck2.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createEckertII(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_II,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Eckert III]
- * (https://proj.org/operations/projections/eck3.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createEckertIII(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_III,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Eckert IV]
- * (https://proj.org/operations/projections/eck4.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createEckertIV(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_IV,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Eckert V]
- * (https://proj.org/operations/projections/eck5.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createEckertV(const util::PropertyMap &properties,
- const common::Angle &centerLong,
- const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_V,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Eckert VI]
- * (https://proj.org/operations/projections/eck6.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createEckertVI(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_VI,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Equidistant Cylindrical]
- *(https://proj.org/operations/projections/eqc.html) projection method.
- *
- * This is also known as the Equirectangular method, and in the particular case
- * where the latitude of first parallel is 0.
- *
- * This method is defined as [EPSG:1028]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1028)
- *
- * @note This is the equivalent OGRSpatialReference::SetEquirectangular2(
- * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL &lt;= 2.3,
- * where the lat_0 / center_latitude parameter is forced to 0.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
- * @param longitudeNatOrigin See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createEquidistantCylindrical(
- const util::PropertyMap &properties,
- const common::Angle &latitudeFirstParallel,
- const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL,
- createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin,
- falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Equidistant Cylindrical
- *(Spherical)]
- *(https://proj.org/operations/projections/eqc.html) projection method.
- *
- * This is also known as the Equirectangular method, and in the particular case
- * where the latitude of first parallel is 0.
- *
- * This method is defined as [EPSG:1029]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1029)
- *
- * @note This is the equivalent OGRSpatialReference::SetEquirectangular2(
- * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL &lt;= 2.3,
- * where the lat_0 / center_latitude parameter is forced to 0.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
- * @param longitudeNatOrigin See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createEquidistantCylindricalSpherical(
- const util::PropertyMap &properties,
- const common::Angle &latitudeFirstParallel,
- const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties,
- EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL,
- createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin,
- falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Gall (Stereographic)]
- * (https://proj.org/operations/projections/gall.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createGall(const util::PropertyMap &properties,
- const common::Angle &centerLong,
- const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Goode Homolosine]
- * (https://proj.org/operations/projections/goode.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createGoodeHomolosine(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Interrupted Goode Homolosine]
- * (https://proj.org/operations/projections/igh.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @note OGRSpatialReference::SetIGH() of GDAL &lt;= 2.3 assumes the 3
- * projection
- * parameters to be zero and this is the nominal case.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createInterruptedGoodeHomolosine(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties,
- PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Geostationary Satellite View]
- * (https://proj.org/operations/projections/geos.html) projection method,
- * with the sweep angle axis of the viewing instrument being x
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param height Height of the view point above the Earth.
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createGeostationarySatelliteSweepX(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &height, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(
- properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X,
- createParams(centerLong, height, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Geostationary Satellite View]
- * (https://proj.org/operations/projections/geos.html) projection method,
- * with the sweep angle axis of the viewing instrument being y.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param height Height of the view point above the Earth.
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createGeostationarySatelliteSweepY(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &height, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(
- properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y,
- createParams(centerLong, height, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Gnomonic]
- *(https://proj.org/operations/projections/gnom.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createGnomonic(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(
- properties, PROJ_WKT2_NAME_METHOD_GNOMONIC,
- createParams(centerLat, centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator
- *(Variant A)]
- *(https://proj.org/operations/projections/omerc.html) projection method
- *
- * This is the variant with the no_uoff parameter, which corresponds to
- * GDAL &gt;=2.3 Hotine_Oblique_Mercator projection.
- * In this variant, the false grid coordinates are
- * defined at the intersection of the initial line and the aposphere (the
- * equator on one of the intermediate surfaces inherent in the method), that is
- * at the natural origin of the coordinate system).
- *
- * This method is defined as [EPSG:9812]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9812)
- *
- * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid =
- *90deg,
- * this maps to the [Swiss Oblique Mercator]
- *(https://proj.org/operations/projections/somerc.html) formulas.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeProjectionCentre See \ref latitude_projection_centre
- * @param longitudeProjectionCentre See \ref longitude_projection_centre
- * @param azimuthInitialLine See \ref azimuth_initial_line
- * @param angleFromRectifiedToSkrewGrid See
- * \ref angle_from_recitfied_to_skrew_grid
- * @param scale See \ref scale_factor_initial_line
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createHotineObliqueMercatorVariantA(
- const util::PropertyMap &properties,
- const common::Angle &latitudeProjectionCentre,
- const common::Angle &longitudeProjectionCentre,
- const common::Angle &azimuthInitialLine,
- const common::Angle &angleFromRectifiedToSkrewGrid,
- const common::Scale &scale, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(
- properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A,
- createParams(latitudeProjectionCentre, longitudeProjectionCentre,
- azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale,
- falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator
- *(Variant B)]
- *(https://proj.org/operations/projections/omerc.html) projection method
- *
- * This is the variant without the no_uoff parameter, which corresponds to
- * GDAL &gt;=2.3 Hotine_Oblique_Mercator_Azimuth_Center projection.
- * In this variant, the false grid coordinates are defined at the projection
- *centre.
- *
- * This method is defined as [EPSG:9815]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9815)
- *
- * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid =
- *90deg,
- * this maps to the [Swiss Oblique Mercator]
- *(https://proj.org/operations/projections/somerc.html) formulas.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeProjectionCentre See \ref latitude_projection_centre
- * @param longitudeProjectionCentre See \ref longitude_projection_centre
- * @param azimuthInitialLine See \ref azimuth_initial_line
- * @param angleFromRectifiedToSkrewGrid See
- * \ref angle_from_recitfied_to_skrew_grid
- * @param scale See \ref scale_factor_initial_line
- * @param eastingProjectionCentre See \ref easting_projection_centre
- * @param northingProjectionCentre See \ref northing_projection_centre
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createHotineObliqueMercatorVariantB(
- const util::PropertyMap &properties,
- const common::Angle &latitudeProjectionCentre,
- const common::Angle &longitudeProjectionCentre,
- const common::Angle &azimuthInitialLine,
- const common::Angle &angleFromRectifiedToSkrewGrid,
- const common::Scale &scale, const common::Length &eastingProjectionCentre,
- const common::Length &northingProjectionCentre) {
- return create(
- properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B,
- createParams(latitudeProjectionCentre, longitudeProjectionCentre,
- azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale,
- eastingProjectionCentre, northingProjectionCentre));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator Two
- *Point Natural Origin]
- *(https://proj.org/operations/projections/omerc.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeProjectionCentre See \ref latitude_projection_centre
- * @param latitudePoint1 Latitude of point 1.
- * @param longitudePoint1 Latitude of point 1.
- * @param latitudePoint2 Latitude of point 2.
- * @param longitudePoint2 Longitude of point 2.
- * @param scale See \ref scale_factor_initial_line
- * @param eastingProjectionCentre See \ref easting_projection_centre
- * @param northingProjectionCentre See \ref northing_projection_centre
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(
- const util::PropertyMap &properties,
- const common::Angle &latitudeProjectionCentre,
- const common::Angle &latitudePoint1, const common::Angle &longitudePoint1,
- const common::Angle &latitudePoint2, const common::Angle &longitudePoint2,
- const common::Scale &scale, const common::Length &eastingProjectionCentre,
- const common::Length &northingProjectionCentre) {
- return create(
- properties,
- PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN,
- {
- ParameterValue::create(latitudeProjectionCentre),
- ParameterValue::create(latitudePoint1),
- ParameterValue::create(longitudePoint1),
- ParameterValue::create(latitudePoint2),
- ParameterValue::create(longitudePoint2),
- ParameterValue::create(scale),
- ParameterValue::create(eastingProjectionCentre),
- ParameterValue::create(northingProjectionCentre),
- });
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Laborde Oblique Mercator]
- *(https://proj.org/operations/projections/labrd.html) projection method.
- *
- * This method is defined as [EPSG:9813]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9813)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeProjectionCentre See \ref latitude_projection_centre
- * @param longitudeProjectionCentre See \ref longitude_projection_centre
- * @param azimuthInitialLine See \ref azimuth_initial_line
- * @param scale See \ref scale_factor_initial_line
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createLabordeObliqueMercator(
- const util::PropertyMap &properties,
- const common::Angle &latitudeProjectionCentre,
- const common::Angle &longitudeProjectionCentre,
- const common::Angle &azimuthInitialLine, const common::Scale &scale,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR,
- createParams(latitudeProjectionCentre,
- longitudeProjectionCentre, azimuthInitialLine,
- scale, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [International Map of the World
- *Polyconic]
- *(https://proj.org/operations/projections/imw_p.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @note the order of arguments is conformant with the corresponding EPSG
- * mode and different than OGRSpatialReference::SetIWMPolyconic() of GDAL &lt;=
- *2.3
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param latitudeFirstParallel See \ref latitude_first_std_parallel
- * @param latitudeSecondParallel See \ref latitude_second_std_parallel
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createInternationalMapWorldPolyconic(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Angle &latitudeFirstParallel,
- const common::Angle &latitudeSecondParallel,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC,
- createParams(centerLong, latitudeFirstParallel,
- latitudeSecondParallel, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Krovak (north oriented)]
- *(https://proj.org/operations/projections/krovak.html) projection method.
- *
- * This method is defined as [EPSG:1041]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1041)
- *
- * The coordinates are returned in the "GIS friendly" order: easting, northing.
- * This method is similar to createKrovak(), except that the later returns
- * projected values as southing, westing, where
- * southing(Krovak) = -northing(Krovak_North) and
- * westing(Krovak) = -easting(Krovak_North).
- *
- * @note The current PROJ implementation of Krovak hard-codes
- * colatitudeConeAxis = 30deg17'17.30311"
- * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for
- * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221).
- * It also hard-codes the parameters of the Bessel ellipsoid typically used for
- * Krovak.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeProjectionCentre See \ref latitude_projection_centre
- * @param longitudeOfOrigin See \ref longitude_of_origin
- * @param colatitudeConeAxis See \ref colatitude_cone_axis
- * @param latitudePseudoStandardParallel See \ref
- *latitude_pseudo_standard_parallel
- * @param scaleFactorPseudoStandardParallel See \ref
- *scale_factor_pseudo_standard_parallel
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createKrovakNorthOriented(
- const util::PropertyMap &properties,
- const common::Angle &latitudeProjectionCentre,
- const common::Angle &longitudeOfOrigin,
- const common::Angle &colatitudeConeAxis,
- const common::Angle &latitudePseudoStandardParallel,
- const common::Scale &scaleFactorPseudoStandardParallel,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED,
- createParams(latitudeProjectionCentre, longitudeOfOrigin,
- colatitudeConeAxis,
- latitudePseudoStandardParallel,
- scaleFactorPseudoStandardParallel, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Krovak]
- *(https://proj.org/operations/projections/krovak.html) projection method.
- *
- * This method is defined as [EPSG:9819]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9819)
- *
- * The coordinates are returned in the historical order: southing, westing
- * This method is similar to createKrovakNorthOriented(), except that the later
- *returns
- * projected values as easting, northing, where
- * easting(Krovak_North) = -westing(Krovak) and
- * northing(Krovak_North) = -southing(Krovak).
- *
- * @note The current PROJ implementation of Krovak hard-codes
- * colatitudeConeAxis = 30deg17'17.30311"
- * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for
- * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221).
- * It also hard-codes the parameters of the Bessel ellipsoid typically used for
- * Krovak.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeProjectionCentre See \ref latitude_projection_centre
- * @param longitudeOfOrigin See \ref longitude_of_origin
- * @param colatitudeConeAxis See \ref colatitude_cone_axis
- * @param latitudePseudoStandardParallel See \ref
- *latitude_pseudo_standard_parallel
- * @param scaleFactorPseudoStandardParallel See \ref
- *scale_factor_pseudo_standard_parallel
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr
-Conversion::createKrovak(const util::PropertyMap &properties,
- const common::Angle &latitudeProjectionCentre,
- const common::Angle &longitudeOfOrigin,
- const common::Angle &colatitudeConeAxis,
- const common::Angle &latitudePseudoStandardParallel,
- const common::Scale &scaleFactorPseudoStandardParallel,
- const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_KROVAK,
- createParams(latitudeProjectionCentre, longitudeOfOrigin,
- colatitudeConeAxis,
- latitudePseudoStandardParallel,
- scaleFactorPseudoStandardParallel, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Lambert Azimuthal Equal Area]
- *(https://proj.org/operations/projections/laea.html) projection method.
- *
- * This method is defined as [EPSG:9820]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9820)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeNatOrigin See \ref center_latitude
- * @param longitudeNatOrigin See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createLambertAzimuthalEqualArea(
- const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
- const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA,
- createParams(latitudeNatOrigin, longitudeNatOrigin,
- falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Miller Cylindrical]
- *(https://proj.org/operations/projections/mill.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createMillerCylindrical(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Mercator]
- *(https://proj.org/operations/projections/merc.html) projection method.
- *
- * This is the variant, also known as Mercator (1SP), defined with the scale
- * factor. Note that latitude of natural origin (centerLat) is a parameter,
- * but unused in the transformation formulas.
- *
- * This method is defined as [EPSG:9804]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9804)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude . Should be 0.
- * @param centerLong See \ref center_longitude
- * @param scale See \ref scale
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createMercatorVariantA(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Scale &scale,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_A,
- createParams(centerLat, centerLong, scale, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Mercator]
- *(https://proj.org/operations/projections/merc.html) projection method.
- *
- * This is the variant, also known as Mercator (2SP), defined with the latitude
- * of the first standard parallel (the second standard parallel is implicitly
- * the opposite value). The latitude of natural origin is fixed to zero.
- *
- * This method is defined as [EPSG:9805]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9805)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeFirstParallel See \ref latitude_first_std_parallel
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createMercatorVariantB(
- const util::PropertyMap &properties,
- const common::Angle &latitudeFirstParallel, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_B,
- createParams(latitudeFirstParallel, centerLong, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Popular Visualisation Pseudo
- *Mercator]
- *(https://proj.org/operations/projections/webmerc.html) projection method.
- *
- * Also known as WebMercator. Mostly/only used for Projected CRS EPSG:3857
- * (WGS 84 / Pseudo-Mercator)
- *
- * This method is defined as [EPSG:1024]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1024)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude . Usually 0
- * @param centerLong See \ref center_longitude . Usually 0
- * @param falseEasting See \ref false_easting . Usually 0
- * @param falseNorthing See \ref false_northing . Usually 0
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createPopularVisualisationPseudoMercator(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(
- properties, EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR,
- createParams(centerLat, centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Mollweide]
- * (https://proj.org/operations/projections/moll.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createMollweide(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_MOLLWEIDE,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [New Zealand Map Grid]
- * (https://proj.org/operations/projections/nzmg.html) projection method.
- *
- * This method is defined as [EPSG:9811]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9811)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createNewZealandMappingGrid(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(
- properties, EPSG_CODE_METHOD_NZMG,
- createParams(centerLat, centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Oblique Stereographic
- *(Alternative)]
- *(https://proj.org/operations/projections/sterea.html) projection method.
- *
- * This method is defined as [EPSG:9809]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9809)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param scale See \ref scale
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createObliqueStereographic(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Scale &scale,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC,
- createParams(centerLat, centerLong, scale, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Orthographic]
- *(https://proj.org/operations/projections/ortho.html) projection method.
- *
- * This method is defined as [EPSG:9840]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9840)
- *
- * \note Before PROJ 7.2, only the spherical formulation was implemented.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createOrthographic(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(
- properties, EPSG_CODE_METHOD_ORTHOGRAPHIC,
- createParams(centerLat, centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [American Polyconic]
- *(https://proj.org/operations/projections/poly.html) projection method.
- *
- * This method is defined as [EPSG:9818]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9818)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createAmericanPolyconic(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(
- properties, EPSG_CODE_METHOD_AMERICAN_POLYCONIC,
- createParams(centerLat, centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Polar Stereographic (Variant
- *A)]
- *(https://proj.org/operations/projections/stere.html) projection method.
- *
- * This method is defined as [EPSG:9810]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9810)
- *
- * This is the variant of polar stereographic defined with a scale factor.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude . Should be 90 deg ou -90 deg.
- * @param centerLong See \ref center_longitude
- * @param scale See \ref scale
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createPolarStereographicVariantA(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Scale &scale,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A,
- createParams(centerLat, centerLong, scale, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Polar Stereographic (Variant
- *B)]
- *(https://proj.org/operations/projections/stere.html) projection method.
- *
- * This method is defined as [EPSG:9829]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9829)
- *
- * This is the variant of polar stereographic defined with a latitude of
- * standard parallel.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeStandardParallel See \ref latitude_std_parallel
- * @param longitudeOfOrigin See \ref longitude_of_origin
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createPolarStereographicVariantB(
- const util::PropertyMap &properties,
- const common::Angle &latitudeStandardParallel,
- const common::Angle &longitudeOfOrigin, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B,
- createParams(latitudeStandardParallel, longitudeOfOrigin,
- falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Robinson]
- * (https://proj.org/operations/projections/robin.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createRobinson(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_ROBINSON,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Sinusoidal]
- * (https://proj.org/operations/projections/sinu.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createSinusoidal(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_SINUSOIDAL,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Stereographic]
- *(https://proj.org/operations/projections/stere.html) projection method.
- *
- * There is no equivalent in EPSG. This method implements the original "Oblique
- * Stereographic" method described in "Snyder's Map Projections - A Working
- *manual",
- * which is different from the "Oblique Stereographic (alternative") method
- * implemented in createObliqueStereographic().
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param scale See \ref scale
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createStereographic(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Scale &scale,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC,
- createParams(centerLat, centerLong, scale, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Van der Grinten]
- * (https://proj.org/operations/projections/vandg.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createVanDerGrinten(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Wagner I]
- * (https://proj.org/operations/projections/wag1.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createWagnerI(const util::PropertyMap &properties,
- const common::Angle &centerLong,
- const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_I,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Wagner II]
- * (https://proj.org/operations/projections/wag2.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createWagnerII(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_II,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Wagner III]
- * (https://proj.org/operations/projections/wag3.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param latitudeTrueScale Latitude of true scale.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createWagnerIII(
- const util::PropertyMap &properties, const common::Angle &latitudeTrueScale,
- const common::Angle &centerLong, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_III,
- createParams(latitudeTrueScale, centerLong, falseEasting,
- falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Wagner IV]
- * (https://proj.org/operations/projections/wag4.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createWagnerIV(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_IV,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Wagner V]
- * (https://proj.org/operations/projections/wag5.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createWagnerV(const util::PropertyMap &properties,
- const common::Angle &centerLong,
- const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_V,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Wagner VI]
- * (https://proj.org/operations/projections/wag6.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createWagnerVI(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VI,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Wagner VII]
- * (https://proj.org/operations/projections/wag7.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createWagnerVII(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VII,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Quadrilateralized Spherical
- *Cube]
- *(https://proj.org/operations/projections/qsc.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLat See \ref center_latitude
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createQuadrilateralizedSphericalCube(
- const util::PropertyMap &properties, const common::Angle &centerLat,
- const common::Angle &centerLong, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(
- properties, PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE,
- createParams(centerLat, centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Spherical Cross-Track Height]
- *(https://proj.org/operations/projections/sch.html) projection method.
- *
- * There is no equivalent in EPSG.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param pegPointLat Peg point latitude.
- * @param pegPointLong Peg point longitude.
- * @param pegPointHeading Peg point heading.
- * @param pegPointHeight Peg point height.
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createSphericalCrossTrackHeight(
- const util::PropertyMap &properties, const common::Angle &pegPointLat,
- const common::Angle &pegPointLong, const common::Angle &pegPointHeading,
- const common::Length &pegPointHeight) {
- return create(properties,
- PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT,
- createParams(pegPointLat, pegPointLong, pegPointHeading,
- pegPointHeight));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Equal Earth]
- * (https://proj.org/operations/projections/eqearth.html) projection method.
- *
- * This method is defined as [EPSG:1078]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1078)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param centerLong See \ref center_longitude
- * @param falseEasting See \ref false_easting
- * @param falseNorthing See \ref false_northing
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createEqualEarth(
- const util::PropertyMap &properties, const common::Angle &centerLong,
- const common::Length &falseEasting, const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_EQUAL_EARTH,
- createParams(centerLong, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the [Vertical Perspective]
- * (https://proj.org/operations/projections/nsper.html) projection method.
- *
- * This method is defined as [EPSG:9838]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9838)
- *
- * The PROJ implementation of the EPSG Vertical Perspective has the current
- * limitations with respect to the method described in EPSG:
- * <ul>
- * <li> it is a 2D-only method, ignoring the ellipsoidal height of the point to
- * project.</li>
- * <li> it has only a spherical development.</li>
- * <li> the height of the topocentric origin is ignored, and thus assumed to be
- * 0.</li>
- * </ul>
- *
- * For completeness, PROJ adds the falseEasting and falseNorthing parameter,
- * which are not described in EPSG. They should usually be set to 0.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param topoOriginLat Latitude of topocentric origin
- * @param topoOriginLong Longitude of topocentric origin
- * @param topoOriginHeight Ellipsoidal height of topocentric origin. Ignored by
- * PROJ (that is assumed to be 0)
- * @param viewPointHeight Viewpoint height with respect to the
- * topocentric/mapping plane. In the case where topoOriginHeight = 0, this is
- * the height above the ellipsoid surface at topoOriginLat, topoOriginLong.
- * @param falseEasting See \ref false_easting . (not in EPSG)
- * @param falseNorthing See \ref false_northing . (not in EPSG)
- * @return a new Conversion.
- *
- * @since 6.3
- */
-ConversionNNPtr Conversion::createVerticalPerspective(
- const util::PropertyMap &properties, const common::Angle &topoOriginLat,
- const common::Angle &topoOriginLong, const common::Length &topoOriginHeight,
- const common::Length &viewPointHeight, const common::Length &falseEasting,
- const common::Length &falseNorthing) {
- return create(properties, EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE,
- createParams(topoOriginLat, topoOriginLong, topoOriginHeight,
- viewPointHeight, falseEasting, falseNorthing));
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the Pole Rotation method, using
- * the conventions of the GRIB 1 and GRIB 2 data formats.
- *
- * Those are mentioned in the Note 2 of
- * https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_temp3-1.shtml
- *
- * Several conventions for the pole rotation method exists.
- * The parameters provided in this method are remapped to the PROJ ob_tran
- * operation with:
- * <pre>
- * +proj=ob_tran +o_proj=longlat +o_lon_p=-rotationAngle
- * +o_lat_p=-southPoleLatInUnrotatedCRS
- * +lon_0=southPoleLongInUnrotatedCRS
- * </pre>
- *
- * Another implementation of that convention is also in the netcdf-java library:
- * https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedLatLon.java
- *
- * The PROJ implementation of this method assumes a spherical ellipsoid.
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param southPoleLatInUnrotatedCRS Latitude of the point from the unrotated
- * CRS, expressed in the unrotated CRS, that will become the south pole of the
- * rotated CRS.
- * @param southPoleLongInUnrotatedCRS Longitude of the point from the unrotated
- * CRS, expressed in the unrotated CRS, that will become the south pole of the
- * rotated CRS.
- * @param axisRotation The angle of rotation about the new polar
- * axis (measured clockwise when looking from the southern to the northern pole)
- * of the coordinate system, assuming the new axis to have been obtained by
- * first rotating the sphere through southPoleLongInUnrotatedCRS degrees about
- * the geographic polar axis and then rotating through
- * (90 + southPoleLatInUnrotatedCRS) degrees so that the southern pole moved
- * along the (previously rotated) Greenwich meridian.
- * @return a new Conversion.
- *
- * @since 7.0
- */
-ConversionNNPtr Conversion::createPoleRotationGRIBConvention(
- const util::PropertyMap &properties,
- const common::Angle &southPoleLatInUnrotatedCRS,
- const common::Angle &southPoleLongInUnrotatedCRS,
- const common::Angle &axisRotation) {
- return create(properties,
- PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION,
- createParams(southPoleLatInUnrotatedCRS,
- southPoleLongInUnrotatedCRS, axisRotation));
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-static OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
- const char *name = OperationParameter::getNameForEPSGCode(code);
- assert(name);
- return OperationParameter::create(createMapNameEPSGCode(name, code));
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the Change of Vertical Unit
- * method.
- *
- * This method is defined as [EPSG:1069]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1069)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param factor Conversion factor
- * @return a new Conversion.
- */
-ConversionNNPtr
-Conversion::createChangeVerticalUnit(const util::PropertyMap &properties,
- const common::Scale &factor) {
- return create(properties, createMethodMapNameEPSGCode(
- EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT),
- VectorOfParameters{
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR),
- },
- VectorOfValues{
- factor,
- });
-}
-
-// ---------------------------------------------------------------------------
-
-/** \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 6.3
- */
-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.
- *
- * This method is defined as [EPSG:9843]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9843),
- * or for 3D as [EPSG:9844]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9844)
- *
- * @param is3D Whether this should apply on 3D geographicCRS
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::createAxisOrderReversal(bool is3D) {
- if (is3D) {
- return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_3D_NAME, 15499),
- createMethodMapNameEPSGCode(
- EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D),
- {}, {});
- } else {
- return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_2D_NAME, 15498),
- createMethodMapNameEPSGCode(
- EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D),
- {}, {});
- }
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a conversion based on the Geographic/Geocentric method.
- *
- * This method is defined as [EPSG:9602]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9602),
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @return a new Conversion.
- */
-ConversionNNPtr
-Conversion::createGeographicGeocentric(const util::PropertyMap &properties) {
- return create(properties, createMethodMapNameEPSGCode(
- EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC),
- {}, {});
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-static const char *getCRSQualifierStr(const crs::CRSPtr &crs) {
- auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get());
- if (geod) {
- if (geod->isGeocentric()) {
- return " (geocentric)";
- }
- auto geog = dynamic_cast<crs::GeographicCRS *>(geod);
- if (geog) {
- if (geog->coordinateSystem()->axisList().size() == 2) {
- return " (geog2D)";
- } else {
- return " (geog3D)";
- }
- }
- }
- return "";
-}
-
-// ---------------------------------------------------------------------------
-
-static std::string buildOpName(const char *opType, const crs::CRSPtr &source,
- const crs::CRSPtr &target) {
- std::string res(opType);
- const auto &srcName = source->nameStr();
- const auto &targetName = target->nameStr();
- const char *srcQualifier = "";
- const char *targetQualifier = "";
- if (srcName == targetName) {
- srcQualifier = getCRSQualifierStr(source);
- targetQualifier = getCRSQualifierStr(target);
- if (strcmp(srcQualifier, targetQualifier) == 0) {
- srcQualifier = "";
- targetQualifier = "";
- }
- }
- res += " from ";
- res += srcName;
- res += srcQualifier;
- res += " to ";
- res += targetName;
- res += targetQualifier;
- return res;
-}
-
-// ---------------------------------------------------------------------------
-
-ConversionNNPtr
-Conversion::createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS,
- const crs::CRSNNPtr &targetCRS) {
- auto properties = util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- buildOpName("Conversion", sourceCRS, targetCRS));
- auto conv = createGeographicGeocentric(properties);
- conv->setCRSs(sourceCRS, targetCRS, nullptr);
- return conv;
-}
-// ---------------------------------------------------------------------------
-
-static util::PropertyMap &addDomains(util::PropertyMap &map,
- const common::ObjectUsage *obj) {
-
- auto ar = util::ArrayOfBaseObject::create();
- for (const auto &domain : obj->domains()) {
- ar->add(domain);
- }
- if (!ar->empty()) {
- map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar);
- }
- return map;
-}
-
-// ---------------------------------------------------------------------------
-
-static void addModifiedIdentifier(util::PropertyMap &map,
- const common::IdentifiedObject *obj,
- bool inverse, bool derivedFrom) {
- // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE
- // as identifier.
-
- auto ar = util::ArrayOfBaseObject::create();
- for (const auto &idSrc : obj->identifiers()) {
- auto authName = *(idSrc->codeSpace());
- const auto &srcCode = idSrc->code();
- if (derivedFrom) {
- authName = concat("DERIVED_FROM(", authName, ")");
- }
- if (inverse) {
- if (starts_with(authName, "INVERSE(") && authName.back() == ')') {
- authName = authName.substr(strlen("INVERSE("));
- authName.resize(authName.size() - 1);
- } else {
- authName = concat("INVERSE(", authName, ")");
- }
- }
- auto idsProp = util::PropertyMap().set(
- metadata::Identifier::CODESPACE_KEY, authName);
- ar->add(metadata::Identifier::create(srcCode, idsProp));
- }
- if (!ar->empty()) {
- map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar);
- }
-}
-
-// ---------------------------------------------------------------------------
-
-static util::PropertyMap
-createPropertiesForInverse(const OperationMethodNNPtr &method) {
- util::PropertyMap map;
-
- const std::string &forwardName = method->nameStr();
- if (!forwardName.empty()) {
- if (starts_with(forwardName, INVERSE_OF)) {
- map.set(common::IdentifiedObject::NAME_KEY,
- forwardName.substr(INVERSE_OF.size()));
- } else {
- map.set(common::IdentifiedObject::NAME_KEY,
- INVERSE_OF + forwardName);
- }
- }
-
- addModifiedIdentifier(map, method.get(), true, false);
-
- return map;
-}
-
-// ---------------------------------------------------------------------------
-
-InverseConversion::InverseConversion(const ConversionNNPtr &forward)
- : Conversion(
- OperationMethod::create(createPropertiesForInverse(forward->method()),
- forward->method()->parameters()),
- forward->parameterValues()),
- InverseCoordinateOperation(forward, true) {
- setPropertiesFromForward();
-}
-
-// ---------------------------------------------------------------------------
-
-InverseConversion::~InverseConversion() = default;
-
-// ---------------------------------------------------------------------------
-
-ConversionNNPtr InverseConversion::inverseAsConversion() const {
- return NN_NO_CHECK(
- util::nn_dynamic_pointer_cast<Conversion>(forwardOperation_));
-}
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperationNNPtr
-InverseConversion::create(const ConversionNNPtr &forward) {
- auto conv = util::nn_make_shared<InverseConversion>(forward);
- conv->assignSelf(conv);
- return conv;
-}
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperationNNPtr InverseConversion::_shallowClone() const {
- auto op = InverseConversion::nn_make_shared<InverseConversion>(
- inverseAsConversion()->shallowClone());
- op->assignSelf(op);
- op->setCRSs(this, false);
- return util::nn_static_pointer_cast<CoordinateOperation>(op);
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-static bool isAxisOrderReversal2D(int methodEPSGCode) {
- return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D;
-}
-
-static bool isAxisOrderReversal3D(int methodEPSGCode) {
- return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D;
-}
-
-bool isAxisOrderReversal(int methodEPSGCode) {
- return isAxisOrderReversal2D(methodEPSGCode) ||
- isAxisOrderReversal3D(methodEPSGCode);
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperationNNPtr Conversion::inverse() const {
- const int methodEPSGCode = method()->getEPSGCode();
-
- if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
- const double convFactor = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
- auto conv = createChangeVerticalUnit(
- createPropertiesForInverse(this, false, false),
- common::Scale(1.0 / convFactor));
- conv->setCRSs(this, true);
- return conv;
- }
-
- const bool l_isAxisOrderReversal2D = isAxisOrderReversal2D(methodEPSGCode);
- const bool l_isAxisOrderReversal3D = isAxisOrderReversal3D(methodEPSGCode);
- if (l_isAxisOrderReversal2D || l_isAxisOrderReversal3D) {
- auto conv = createAxisOrderReversal(l_isAxisOrderReversal3D);
- conv->setCRSs(this, true);
- return conv;
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) {
-
- auto conv = createGeographicGeocentric(
- createPropertiesForInverse(this, false, false));
- conv->setCRSs(this, true);
- 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())));
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-static double msfn(double phi, double e2) {
- const double sinphi = std::sin(phi);
- const double cosphi = std::cos(phi);
- return pj_msfn(sinphi, cosphi, e2);
-}
-
-// ---------------------------------------------------------------------------
-
-static double tsfn(double phi, double ec) {
- const double sinphi = std::sin(phi);
- return pj_tsfn(phi, sinphi, ec);
-}
-
-// ---------------------------------------------------------------------------
-
-// Function whose zeroes are the sin of the standard parallels of LCC_2SP
-static double lcc_1sp_to_2sp_f(double sinphi, double K, double ec, double n) {
- const double x = sinphi;
- const double ecx = ec * x;
- return (1 - x * x) / (1 - ecx * ecx) -
- K * K * std::pow((1.0 - x) / (1.0 + x) *
- std::pow((1.0 + ecx) / (1.0 - ecx), ec),
- n);
-}
-
-// ---------------------------------------------------------------------------
-
-// Find the sin of the standard parallels of LCC_2SP
-static double find_zero_lcc_1sp_to_2sp_f(double sinphi0, bool bNorth, double K,
- double ec) {
- double a, b;
- double f_a;
- if (bNorth) {
- // Look for zero above phi0
- a = sinphi0;
- b = 1.0; // sin(North pole)
- f_a = 1.0; // some positive value, but we only care about the sign
- } else {
- // Look for zero below phi0
- a = -1.0; // sin(South pole)
- b = sinphi0;
- f_a = -1.0; // minus infinity in fact, but we only care about the sign
- }
- // We use dichotomy search. lcc_1sp_to_2sp_f() is positive at sinphi_init,
- // has a zero in ]-1,sinphi0[ and ]sinphi0,1[ ranges
- for (int N = 0; N < 100; N++) {
- double c = (a + b) / 2;
- double f_c = lcc_1sp_to_2sp_f(c, K, ec, sinphi0);
- if (f_c == 0.0 || (b - a) < 1e-18) {
- return c;
- }
- if ((f_c > 0 && f_a > 0) || (f_c < 0 && f_a < 0)) {
- a = c;
- f_a = f_c;
- } else {
- b = c;
- }
- }
- return (a + b) / 2;
-}
-
-static inline double DegToRad(double x) { return x / 180.0 * M_PI; }
-static inline double RadToDeg(double x) { return x / M_PI * 180.0; }
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/**
- * \brief Return an equivalent projection.
- *
- * Currently implemented:
- * <ul>
- * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to
- * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)</li>
- * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to
- * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)</li>
- * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to
- * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP</li>
- * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to
- * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP</li>
- * </ul>
- *
- * @param targetEPSGCode EPSG code of the target method.
- * @return new conversion, or nullptr
- */
-ConversionPtr Conversion::convertToOtherMethod(int targetEPSGCode) const {
- const int current_epsg_code = method()->getEPSGCode();
- if (current_epsg_code == targetEPSGCode) {
- return util::nn_dynamic_pointer_cast<Conversion>(shared_from_this());
- }
-
- auto geogCRS = dynamic_cast<crs::GeodeticCRS *>(sourceCRS().get());
- if (!geogCRS) {
- return nullptr;
- }
-
- const double e2 = geogCRS->ellipsoid()->squaredEccentricity();
- if (e2 < 0) {
- return nullptr;
- }
-
- if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_A &&
- targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
- parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) {
- const double k0 = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
- if (!(k0 > 0 && k0 <= 1.0 + 1e-10))
- return nullptr;
- const double dfStdP1Lat =
- (k0 >= 1.0)
- ? 0.0
- : std::acos(std::sqrt((1.0 - e2) / ((1.0 / (k0 * k0)) - e2)));
- auto latitudeFirstParallel = common::Angle(
- common::Angle(dfStdP1Lat, common::UnitOfMeasure::RADIAN)
- .convertToUnit(common::UnitOfMeasure::DEGREE),
- common::UnitOfMeasure::DEGREE);
- auto conv = createMercatorVariantB(
- util::PropertyMap(), latitudeFirstParallel,
- common::Angle(parameterValueMeasure(
- EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
- common::Length(
- parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
- common::Length(
- parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
- conv->setCRSs(this, false);
- return conv.as_nullable();
- }
-
- if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
- targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
- const double phi1 = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL);
- if (!(std::fabs(phi1) < M_PI / 2))
- return nullptr;
- const double k0 = msfn(phi1, e2);
- auto conv = createMercatorVariantA(
- util::PropertyMap(),
- common::Angle(0.0, common::UnitOfMeasure::DEGREE),
- common::Angle(parameterValueMeasure(
- EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
- common::Scale(k0, common::UnitOfMeasure::SCALE_UNITY),
- common::Length(
- parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
- common::Length(
- parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
- conv->setCRSs(this, false);
- return conv.as_nullable();
- }
-
- if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
- targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
- // Notations m0, t0, n, m1, t1, F are those of the EPSG guidance
- // "1.3.1.1 Lambert Conic Conformal (2SP)" and
- // "1.3.1.2 Lambert Conic Conformal (1SP)" and
- // or Snyder pages 106-109
- auto latitudeOfOrigin = common::Angle(parameterValueMeasure(
- EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN));
- const double phi0 = latitudeOfOrigin.getSIValue();
- const double k0 = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
- if (!(std::fabs(phi0) < M_PI / 2))
- return nullptr;
- if (!(k0 > 0 && k0 <= 1.0 + 1e-10))
- return nullptr;
- const double ec = std::sqrt(e2);
- const double m0 = msfn(phi0, e2);
- const double t0 = tsfn(phi0, ec);
- const double n = sin(phi0);
- if (std::fabs(n) < 1e-10)
- return nullptr;
- if (fabs(k0 - 1.0) <= 1e-10) {
- auto conv = createLambertConicConformal_2SP(
- util::PropertyMap(), latitudeOfOrigin,
- common::Angle(parameterValueMeasure(
- EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
- latitudeOfOrigin, latitudeOfOrigin,
- common::Length(
- parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
- common::Length(
- parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
- conv->setCRSs(this, false);
- return conv.as_nullable();
- } else {
- const double K = k0 * m0 / std::pow(t0, n);
- const double phi1 =
- std::asin(find_zero_lcc_1sp_to_2sp_f(n, true, K, ec));
- const double phi2 =
- std::asin(find_zero_lcc_1sp_to_2sp_f(n, false, K, ec));
- double phi1Deg = RadToDeg(phi1);
- double phi2Deg = RadToDeg(phi2);
-
- // Try to round to hundreth of degree if very close to it
- if (std::fabs(phi1Deg * 1000 - std::floor(phi1Deg * 1000 + 0.5)) <
- 1e-8)
- phi1Deg = floor(phi1Deg * 1000 + 0.5) / 1000;
- if (std::fabs(phi2Deg * 1000 - std::floor(phi2Deg * 1000 + 0.5)) <
- 1e-8)
- phi2Deg = std::floor(phi2Deg * 1000 + 0.5) / 1000;
-
- // The following improvement is too turn the LCC1SP equivalent of
- // EPSG:2154 to the real LCC2SP
- // If the computed latitude of origin is close to .0 or .5 degrees
- // then check if rounding it to it will get a false northing
- // close to an integer
- const double FN =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
- const double latitudeOfOriginDeg =
- latitudeOfOrigin.convertToUnit(common::UnitOfMeasure::DEGREE);
- if (std::fabs(latitudeOfOriginDeg * 2 -
- std::floor(latitudeOfOriginDeg * 2 + 0.5)) < 0.2) {
- const double dfRoundedLatOfOrig =
- std::floor(latitudeOfOriginDeg * 2 + 0.5) / 2;
- const double m1 = msfn(phi1, e2);
- const double t1 = tsfn(phi1, ec);
- const double F = m1 / (n * std::pow(t1, n));
- const double a =
- geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
- const double tRoundedLatOfOrig =
- tsfn(DegToRad(dfRoundedLatOfOrig), ec);
- const double FN_correction =
- a * F * (std::pow(tRoundedLatOfOrig, n) - std::pow(t0, n));
- const double FN_corrected = FN - FN_correction;
- const double FN_corrected_rounded =
- std::floor(FN_corrected + 0.5);
- if (std::fabs(FN_corrected - FN_corrected_rounded) < 1e-8) {
- auto conv = createLambertConicConformal_2SP(
- util::PropertyMap(),
- common::Angle(dfRoundedLatOfOrig,
- common::UnitOfMeasure::DEGREE),
- common::Angle(parameterValueMeasure(
- EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
- common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE),
- common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE),
- common::Length(parameterValueMeasure(
- EPSG_CODE_PARAMETER_FALSE_EASTING)),
- common::Length(FN_corrected_rounded));
- conv->setCRSs(this, false);
- return conv.as_nullable();
- }
- }
-
- auto conv = createLambertConicConformal_2SP(
- util::PropertyMap(), latitudeOfOrigin,
- common::Angle(parameterValueMeasure(
- EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
- common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE),
- common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE),
- common::Length(
- parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
- common::Length(FN));
- conv->setCRSs(this, false);
- return conv.as_nullable();
- }
- }
-
- if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
- targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP) {
- // Notations m0, t0, m1, t1, m2, t2 n, F are those of the EPSG guidance
- // "1.3.1.1 Lambert Conic Conformal (2SP)" and
- // "1.3.1.2 Lambert Conic Conformal (1SP)" and
- // or Snyder pages 106-109
- const double phiF =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN)
- .getSIValue();
- const double phi1 =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)
- .getSIValue();
- const double phi2 =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)
- .getSIValue();
- if (!(std::fabs(phiF) < M_PI / 2))
- return nullptr;
- if (!(std::fabs(phi1) < M_PI / 2))
- return nullptr;
- if (!(std::fabs(phi2) < M_PI / 2))
- return nullptr;
- const double ec = std::sqrt(e2);
- const double m1 = msfn(phi1, e2);
- const double m2 = msfn(phi2, e2);
- const double t1 = tsfn(phi1, ec);
- const double t2 = tsfn(phi2, ec);
- const double n_denom = std::log(t1) - std::log(t2);
- const double n = (std::fabs(n_denom) < 1e-10)
- ? std::sin(phi1)
- : (std::log(m1) - std::log(m2)) / n_denom;
- if (std::fabs(n) < 1e-10)
- return nullptr;
- const double F = m1 / (n * std::pow(t1, n));
- const double phi0 = std::asin(n);
- const double m0 = msfn(phi0, e2);
- const double t0 = tsfn(phi0, ec);
- const double F0 = m0 / (n * std::pow(t0, n));
- const double k0 = F / F0;
- const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
- const double tF = tsfn(phiF, ec);
- const double FN_correction =
- a * F * (std::pow(tF, n) - std::pow(t0, n));
-
- double phi0Deg = RadToDeg(phi0);
- // Try to round to thousandth of degree if very close to it
- if (std::fabs(phi0Deg * 1000 - std::floor(phi0Deg * 1000 + 0.5)) < 1e-8)
- phi0Deg = std::floor(phi0Deg * 1000 + 0.5) / 1000;
-
- auto conv = createLambertConicConformal_1SP(
- util::PropertyMap(),
- common::Angle(phi0Deg, common::UnitOfMeasure::DEGREE),
- common::Angle(parameterValueMeasure(
- EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN)),
- common::Scale(k0), common::Length(parameterValueMeasure(
- EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN)),
- common::Length(
- parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN) +
- (std::fabs(FN_correction) > 1e-8 ? FN_correction : 0)));
- conv->setCRSs(this, false);
- return conv.as_nullable();
- }
-
- return nullptr;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-static void getESRIMethodNameAndParams(const Conversion *conv,
- const std::string &methodName,
- int methodEPSGCode,
- const char *&esriMethodName,
- const ESRIParamMapping *&esriParams) {
- esriParams = nullptr;
- esriMethodName = nullptr;
- const auto *esriMapping = getESRIMapping(methodName, methodEPSGCode);
- const auto l_targetCRS = conv->targetCRS();
- if (esriMapping) {
- esriParams = esriMapping->params;
- esriMethodName = esriMapping->esri_name;
- if (esriMapping->epsg_code ==
- EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
- esriMapping->epsg_code ==
- EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) {
- if (l_targetCRS &&
- ci_find(l_targetCRS->nameStr(), "Plate Carree") !=
- std::string::npos &&
- conv->parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) {
- esriParams = paramsESRI_Plate_Carree;
- esriMethodName = "Plate_Carree";
- } else {
- esriParams = paramsESRI_Equidistant_Cylindrical;
- esriMethodName = "Equidistant_Cylindrical";
- }
- } else if (esriMapping->epsg_code ==
- EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
- if (ci_find(conv->nameStr(), "Gauss Kruger") != std::string::npos ||
- (l_targetCRS && (ci_find(l_targetCRS->nameStr(), "Gauss") !=
- std::string::npos ||
- ci_find(l_targetCRS->nameStr(), "GK_") !=
- std::string::npos))) {
- esriParams = paramsESRI_Gauss_Kruger;
- esriMethodName = "Gauss_Kruger";
- } else {
- esriParams = paramsESRI_Transverse_Mercator;
- esriMethodName = "Transverse_Mercator";
- }
- } else if (esriMapping->epsg_code ==
- EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) {
- if (std::abs(
- conv->parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) -
- conv->parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) <
- 1e-15) {
- esriParams =
- paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin;
- esriMethodName =
- "Hotine_Oblique_Mercator_Azimuth_Natural_Origin";
- } else {
- esriParams =
- paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin;
- esriMethodName = "Rectified_Skew_Orthomorphic_Natural_Origin";
- }
- } else if (esriMapping->epsg_code ==
- EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) {
- if (std::abs(
- conv->parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) -
- conv->parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) <
- 1e-15) {
- esriParams = paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center;
- esriMethodName = "Hotine_Oblique_Mercator_Azimuth_Center";
- } else {
- esriParams = paramsESRI_Rectified_Skew_Orthomorphic_Center;
- esriMethodName = "Rectified_Skew_Orthomorphic_Center";
- }
- } else if (esriMapping->epsg_code ==
- EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
- if (conv->parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL) > 0) {
- esriMethodName = "Stereographic_North_Pole";
- } else {
- esriMethodName = "Stereographic_South_Pole";
- }
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-const char *Conversion::getESRIMethodName() const {
- const auto &l_method = method();
- const auto &methodName = l_method->nameStr();
- const auto methodEPSGCode = l_method->getEPSGCode();
- const ESRIParamMapping *esriParams = nullptr;
- const char *esriMethodName = nullptr;
- getESRIMethodNameAndParams(this, methodName, methodEPSGCode, esriMethodName,
- esriParams);
- return esriMethodName;
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-const char *Conversion::getWKT1GDALMethodName() const {
- const auto &l_method = method();
- const auto methodEPSGCode = l_method->getEPSGCode();
- if (methodEPSGCode ==
- EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
- return "Mercator_1SP";
- }
- const MethodMapping *mapping = getMapping(l_method.get());
- return mapping ? mapping->wkt1_name : nullptr;
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-void Conversion::_exportToWKT(io::WKTFormatter *formatter) const {
- const auto &l_method = method();
- const auto &methodName = l_method->nameStr();
- const auto methodEPSGCode = l_method->getEPSGCode();
- const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
-
- if (!isWKT2 && formatter->useESRIDialect()) {
- if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
- auto eqConv =
- convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
- if (eqConv) {
- eqConv->_exportToWKT(formatter);
- return;
- }
- }
- }
-
- if (isWKT2) {
- formatter->startNode(formatter->useDerivingConversion()
- ? io::WKTConstants::DERIVINGCONVERSION
- : io::WKTConstants::CONVERSION,
- !identifiers().empty());
- formatter->addQuotedString(nameStr());
- } else {
- formatter->enter();
- formatter->pushOutputUnit(false);
- formatter->pushOutputId(false);
- }
-
-#ifdef DEBUG_CONVERSION_ID
- if (sourceCRS() && targetCRS()) {
- formatter->startNode("SOURCECRS_ID", false);
- sourceCRS()->formatID(formatter);
- formatter->endNode();
- formatter->startNode("TARGETCRS_ID", false);
- targetCRS()->formatID(formatter);
- formatter->endNode();
- }
-#endif
-
- bool bAlreadyWritten = false;
- if (!isWKT2 && formatter->useESRIDialect()) {
- const ESRIParamMapping *esriParams = nullptr;
- const char *esriMethodName = nullptr;
- getESRIMethodNameAndParams(this, methodName, methodEPSGCode,
- esriMethodName, esriParams);
- if (esriMethodName && esriParams) {
- formatter->startNode(io::WKTConstants::PROJECTION, false);
- formatter->addQuotedString(esriMethodName);
- formatter->endNode();
-
- for (int i = 0; esriParams[i].esri_name != nullptr; i++) {
- const auto &esriParam = esriParams[i];
- formatter->startNode(io::WKTConstants::PARAMETER, false);
- formatter->addQuotedString(esriParam.esri_name);
- if (esriParam.wkt2_name) {
- const auto &pv = parameterValue(esriParam.wkt2_name,
- esriParam.epsg_code);
- if (pv && pv->type() == ParameterValue::Type::MEASURE) {
- const auto &v = pv->value();
- // as we don't output the natural unit, output
- // to the registered linear / angular unit.
- const auto &unitType = v.unit().type();
- if (unitType == common::UnitOfMeasure::Type::LINEAR) {
- formatter->add(v.convertToUnit(
- *(formatter->axisLinearUnit())));
- } else if (unitType ==
- common::UnitOfMeasure::Type::ANGULAR) {
- const auto &angUnit =
- *(formatter->axisAngularUnit());
- double val = v.convertToUnit(angUnit);
- if (angUnit == common::UnitOfMeasure::DEGREE) {
- if (val > 180.0) {
- val -= 360.0;
- } else if (val < -180.0) {
- val += 360.0;
- }
- }
- formatter->add(val);
- } else {
- formatter->add(v.getSIValue());
- }
- } else if (ci_find(esriParam.esri_name, "scale") !=
- std::string::npos) {
- formatter->add(1.0);
- } else {
- formatter->add(0.0);
- }
- } else {
- formatter->add(esriParam.fixed_value);
- }
- formatter->endNode();
- }
- bAlreadyWritten = true;
- }
- } else if (!isWKT2) {
- if (methodEPSGCode ==
- EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
- const double latitudeOrigin = parameterValueNumeric(
- EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
- common::UnitOfMeasure::DEGREE);
- if (latitudeOrigin != 0) {
- throw io::FormattingException(
- std::string("Unsupported value for ") +
- EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
- }
-
- bAlreadyWritten = true;
- formatter->startNode(io::WKTConstants::PROJECTION, false);
- formatter->addQuotedString("Mercator_1SP");
- formatter->endNode();
-
- formatter->startNode(io::WKTConstants::PARAMETER, false);
- formatter->addQuotedString("central_meridian");
- const double centralMeridian = parameterValueNumeric(
- EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
- common::UnitOfMeasure::DEGREE);
- formatter->add(centralMeridian);
- formatter->endNode();
-
- formatter->startNode(io::WKTConstants::PARAMETER, false);
- formatter->addQuotedString("scale_factor");
- formatter->add(1.0);
- formatter->endNode();
-
- formatter->startNode(io::WKTConstants::PARAMETER, false);
- formatter->addQuotedString("false_easting");
- const double falseEasting =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING);
- formatter->add(falseEasting);
- formatter->endNode();
-
- formatter->startNode(io::WKTConstants::PARAMETER, false);
- formatter->addQuotedString("false_northing");
- const double falseNorthing =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
- formatter->add(falseNorthing);
- formatter->endNode();
- } else if (starts_with(methodName, "PROJ ")) {
- bAlreadyWritten = true;
- formatter->startNode(io::WKTConstants::PROJECTION, false);
- formatter->addQuotedString("custom_proj4");
- formatter->endNode();
- }
- }
-
- if (!bAlreadyWritten) {
- l_method->_exportToWKT(formatter);
-
- const MethodMapping *mapping =
- !isWKT2 ? getMapping(l_method.get()) : nullptr;
- for (const auto &genOpParamvalue : parameterValues()) {
-
- // EPSG has normally no Latitude of natural origin for Equidistant
- // Cylindrical but PROJ can handle it, so output the parameter if
- // not zero
- if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) {
- auto opParamvalue =
- dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue &&
- opParamvalue->parameter()->getEPSGCode() ==
- EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) {
- const auto &paramValue = opParamvalue->parameterValue();
- if (paramValue->type() == ParameterValue::Type::MEASURE) {
- const auto &measure = paramValue->value();
- if (measure.getSIValue() == 0) {
- continue;
- }
- }
- }
- }
- // Same for false easting / false northing for Vertical Perspective
- else if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE) {
- auto opParamvalue =
- dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto paramEPSGCode =
- opParamvalue->parameter()->getEPSGCode();
- if (paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_EASTING ||
- paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_NORTHING) {
- const auto &paramValue = opParamvalue->parameterValue();
- if (paramValue->type() ==
- ParameterValue::Type::MEASURE) {
- const auto &measure = paramValue->value();
- if (measure.getSIValue() == 0) {
- continue;
- }
- }
- }
- }
- }
- genOpParamvalue->_exportToWKT(formatter, mapping);
- }
- }
-
- if (isWKT2) {
- if (formatter->outputId()) {
- formatID(formatter);
- }
- formatter->endNode();
- } else {
- formatter->popOutputUnit();
- formatter->popOutputId();
- formatter->leave();
- }
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-void Conversion::_exportToJSON(
- io::JSONFormatter *formatter) const // throw(FormattingException)
-{
- auto writer = formatter->writer();
- auto objectContext(
- formatter->MakeObjectContext("Conversion", !identifiers().empty()));
-
- writer->AddObjKey("name");
- auto l_name = nameStr();
- if (l_name.empty()) {
- writer->Add("unnamed");
- } else {
- writer->Add(l_name);
- }
-
- writer->AddObjKey("method");
- formatter->setOmitTypeInImmediateChild();
- formatter->setAllowIDInImmediateChild();
- method()->_exportToJSON(formatter);
-
- const auto &l_parameterValues = parameterValues();
- if (!l_parameterValues.empty()) {
- writer->AddObjKey("parameters");
- {
- auto parametersContext(writer->MakeArrayContext(false));
- for (const auto &genOpParamvalue : l_parameterValues) {
- formatter->setAllowIDInImmediateChild();
- formatter->setOmitTypeInImmediateChild();
- genOpParamvalue->_exportToJSON(formatter);
- }
- }
- }
-
- if (formatter->outputId()) {
- formatID(formatter);
- }
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static bool createPROJ4WebMercator(const Conversion *conv,
- io::PROJStringFormatter *formatter) {
- const double centralMeridian = conv->parameterValueNumeric(
- EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
- common::UnitOfMeasure::DEGREE);
-
- const double falseEasting =
- conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING);
-
- const double falseNorthing =
- conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
-
- auto sourceCRS = conv->sourceCRS();
- auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
- if (!geogCRS) {
- return false;
- }
-
- std::string units("m");
- auto targetCRS = conv->targetCRS();
- auto targetProjCRS =
- dynamic_cast<const crs::ProjectedCRS *>(targetCRS.get());
- if (targetProjCRS) {
- const auto &axisList = targetProjCRS->coordinateSystem()->axisList();
- const auto &unit = axisList[0]->unit();
- if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE,
- util::IComparable::Criterion::EQUIVALENT)) {
- auto projUnit = unit.exportToPROJString();
- if (!projUnit.empty()) {
- units = projUnit;
- } else {
- return false;
- }
- }
- }
-
- formatter->addStep("merc");
- const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
- formatter->addParam("a", a);
- formatter->addParam("b", a);
- formatter->addParam("lat_ts", 0.0);
- formatter->addParam("lon_0", centralMeridian);
- formatter->addParam("x_0", falseEasting);
- formatter->addParam("y_0", falseNorthing);
- formatter->addParam("k", 1.0);
- formatter->addParam("units", units);
- formatter->addParam("nadgrids", "@null");
- formatter->addParam("wktext");
- formatter->addParam("no_defs");
- return true;
-}
-
-// ---------------------------------------------------------------------------
-
-static bool
-createPROJExtensionFromCustomProj(const Conversion *conv,
- io::PROJStringFormatter *formatter,
- bool forExtensionNode) {
- const auto &methodName = conv->method()->nameStr();
- assert(starts_with(methodName, "PROJ "));
- auto tokens = split(methodName, ' ');
-
- formatter->addStep(tokens[1]);
-
- if (forExtensionNode) {
- auto sourceCRS = conv->sourceCRS();
- auto geogCRS =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
- if (!geogCRS) {
- return false;
- }
- geogCRS->addDatumInfoToPROJString(formatter);
- }
-
- for (size_t i = 2; i < tokens.size(); i++) {
- auto kv = split(tokens[i], '=');
- if (kv.size() == 2) {
- formatter->addParam(kv[0], kv[1]);
- } else {
- formatter->addParam(tokens[i]);
- }
- }
-
- for (const auto &genOpParamvalue : conv->parameterValues()) {
- auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto &paramName = opParamvalue->parameter()->nameStr();
- const auto &paramValue = opParamvalue->parameterValue();
- if (paramValue->type() == ParameterValue::Type::MEASURE) {
- const auto &measure = paramValue->value();
- const auto unitType = measure.unit().type();
- if (unitType == common::UnitOfMeasure::Type::LINEAR) {
- formatter->addParam(paramName, measure.getSIValue());
- } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
- formatter->addParam(
- paramName,
- measure.convertToUnit(common::UnitOfMeasure::DEGREE));
- } else {
- formatter->addParam(paramName, measure.value());
- }
- }
- }
- }
-
- if (forExtensionNode) {
- formatter->addParam("wktext");
- formatter->addParam("no_defs");
- }
- return true;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-bool Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const {
- const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
- if (!isWKT2) {
- const auto &l_method = method();
- const auto &methodName = l_method->nameStr();
- const int methodEPSGCode = l_method->getEPSGCode();
- if (l_method->getPrivate()->projMethodOverride_ == "tmerc approx" ||
- l_method->getPrivate()->projMethodOverride_ == "utm approx") {
- auto projFormatter = io::PROJStringFormatter::create();
- projFormatter->setCRSExport(true);
- projFormatter->setUseApproxTMerc(true);
- formatter->startNode(io::WKTConstants::EXTENSION, false);
- formatter->addQuotedString("PROJ4");
- _exportToPROJString(projFormatter.get());
- projFormatter->addParam("no_defs");
- formatter->addQuotedString(projFormatter->toString());
- formatter->endNode();
- return true;
- } else if (methodEPSGCode ==
- EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR ||
- nameStr() == "Popular Visualisation Mercator") {
-
- auto projFormatter = io::PROJStringFormatter::create();
- projFormatter->setCRSExport(true);
- if (createPROJ4WebMercator(this, projFormatter.get())) {
- formatter->startNode(io::WKTConstants::EXTENSION, false);
- formatter->addQuotedString("PROJ4");
- formatter->addQuotedString(projFormatter->toString());
- formatter->endNode();
- return true;
- }
- } else if (starts_with(methodName, "PROJ ")) {
- auto projFormatter = io::PROJStringFormatter::create();
- projFormatter->setCRSExport(true);
- if (createPROJExtensionFromCustomProj(this, projFormatter.get(),
- true)) {
- formatter->startNode(io::WKTConstants::EXTENSION, false);
- formatter->addQuotedString("PROJ4");
- formatter->addQuotedString(projFormatter->toString());
- formatter->endNode();
- return true;
- }
- } else if (methodName ==
- PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) {
- auto projFormatter = io::PROJStringFormatter::create();
- projFormatter->setCRSExport(true);
- formatter->startNode(io::WKTConstants::EXTENSION, false);
- formatter->addQuotedString("PROJ4");
- _exportToPROJString(projFormatter.get());
- projFormatter->addParam("no_defs");
- formatter->addQuotedString(projFormatter->toString());
- formatter->endNode();
- return true;
- }
- }
- return false;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-void Conversion::_exportToPROJString(
- io::PROJStringFormatter *formatter) const // throw(FormattingException)
-{
- const auto &l_method = method();
- const auto &methodName = l_method->nameStr();
- const int methodEPSGCode = l_method->getEPSGCode();
- const bool isZUnitConversion =
- methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT;
- const bool isAffineParametric =
- 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 &&
- !isHeightDepthReversal;
- bool applyTargetCRSModifiers = applySourceCRSModifiers;
-
- if (formatter->getCRSExport()) {
- if (methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC ||
- methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) {
- throw io::FormattingException("Transformation cannot be exported "
- "as a PROJ.4 string (but can be part "
- "of a PROJ pipeline)");
- }
- }
-
- auto l_sourceCRS = sourceCRS();
- crs::GeographicCRSPtr srcGeogCRS;
- if (!formatter->getCRSExport() && l_sourceCRS && applySourceCRSModifiers) {
-
- crs::CRSPtr horiz = l_sourceCRS;
- const auto compound =
- dynamic_cast<const crs::CompoundCRS *>(l_sourceCRS.get());
- if (compound) {
- const auto &components = compound->componentReferenceSystems();
- if (!components.empty()) {
- horiz = components.front().as_nullable();
- }
- }
-
- srcGeogCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(horiz);
- if (srcGeogCRS) {
- formatter->setOmitProjLongLatIfPossible(true);
- formatter->startInversion();
- srcGeogCRS->_exportToPROJString(formatter);
- formatter->stopInversion();
- formatter->setOmitProjLongLatIfPossible(false);
- }
-
- auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz.get());
- if (projCRS) {
- formatter->startInversion();
- formatter->pushOmitZUnitConversion();
- projCRS->addUnitConvertAndAxisSwap(formatter, false);
- formatter->popOmitZUnitConversion();
- formatter->stopInversion();
- }
- }
-
- const auto &convName = nameStr();
- bool bConversionDone = false;
- bool bEllipsoidParametersDone = false;
- bool useApprox = false;
- if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
- // Check for UTM
- int zone = 0;
- bool north = true;
- useApprox =
- formatter->getUseApproxTMerc() ||
- l_method->getPrivate()->projMethodOverride_ == "tmerc approx" ||
- l_method->getPrivate()->projMethodOverride_ == "utm approx";
- if (isUTM(zone, north)) {
- bConversionDone = true;
- formatter->addStep("utm");
- if (useApprox) {
- formatter->addParam("approx");
- }
- formatter->addParam("zone", zone);
- if (!north) {
- formatter->addParam("south");
- }
- }
- } else if (methodEPSGCode ==
- EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) {
- const double azimuth =
- parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE,
- common::UnitOfMeasure::DEGREE);
- const double angleRectifiedToSkewGrid = parameterValueNumeric(
- EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
- common::UnitOfMeasure::DEGREE);
- // Map to Swiss Oblique Mercator / somerc
- if (std::fabs(azimuth - 90) < 1e-4 &&
- std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
- bConversionDone = true;
- formatter->addStep("somerc");
- formatter->addParam(
- "lat_0", parameterValueNumeric(
- EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
- common::UnitOfMeasure::DEGREE));
- formatter->addParam(
- "lon_0", parameterValueNumeric(
- EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
- common::UnitOfMeasure::DEGREE));
- formatter->addParam(
- "k_0", parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE));
- formatter->addParam("x_0", parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_FALSE_EASTING));
- formatter->addParam("y_0", parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_FALSE_NORTHING));
- }
- } else if (methodEPSGCode ==
- EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) {
- const double azimuth =
- parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE,
- common::UnitOfMeasure::DEGREE);
- const double angleRectifiedToSkewGrid = parameterValueNumeric(
- EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
- common::UnitOfMeasure::DEGREE);
- // Map to Swiss Oblique Mercator / somerc
- if (std::fabs(azimuth - 90) < 1e-4 &&
- std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
- bConversionDone = true;
- formatter->addStep("somerc");
- formatter->addParam(
- "lat_0", parameterValueNumeric(
- EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
- common::UnitOfMeasure::DEGREE));
- formatter->addParam(
- "lon_0", parameterValueNumeric(
- EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
- common::UnitOfMeasure::DEGREE));
- formatter->addParam(
- "k_0", parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE));
- formatter->addParam(
- "x_0", parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE));
- formatter->addParam(
- "y_0", parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE));
- }
- } else if (methodEPSGCode == EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED) {
- double colatitude =
- parameterValueNumeric(EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS,
- common::UnitOfMeasure::DEGREE);
- double latitudePseudoStandardParallel = parameterValueNumeric(
- EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL,
- common::UnitOfMeasure::DEGREE);
- // 30deg 17' 17.30311'' = 30.28813975277777776
- // 30deg 17' 17.303'' = 30.288139722222223 as used in GDAL WKT1
- if (std::fabs(colatitude - 30.2881397) > 1e-7) {
- throw io::FormattingException(
- std::string("Unsupported value for ") +
- EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS);
- }
- if (std::fabs(latitudePseudoStandardParallel - 78.5) > 1e-8) {
- throw io::FormattingException(
- std::string("Unsupported value for ") +
- EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL);
- }
- } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
- double latitudeOrigin = parameterValueNumeric(
- EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
- common::UnitOfMeasure::DEGREE);
- if (latitudeOrigin != 0) {
- throw io::FormattingException(
- std::string("Unsupported value for ") +
- EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
- }
- } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B) {
- const auto &scaleFactor = parameterValueMeasure(WKT1_SCALE_FACTOR, 0);
- if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN &&
- std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) {
- throw io::FormattingException(
- "Unexpected presence of scale factor in Mercator (variant B)");
- }
- double latitudeOrigin = parameterValueNumeric(
- EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
- common::UnitOfMeasure::DEGREE);
- if (latitudeOrigin != 0) {
- throw io::FormattingException(
- std::string("Unsupported value for ") +
- EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
- }
- } else if (methodEPSGCode ==
- EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
- // We map TMSO to tmerc with axis=wsu. This only works if false easting
- // and northings are zero, which is the case in practice for South
- // African and Namibian EPSG CRS
- const auto falseEasting = parameterValueNumeric(
- EPSG_CODE_PARAMETER_FALSE_EASTING, common::UnitOfMeasure::METRE);
- if (falseEasting != 0) {
- throw io::FormattingException(
- std::string("Unsupported value for ") +
- EPSG_NAME_PARAMETER_FALSE_EASTING);
- }
- const auto falseNorthing = parameterValueNumeric(
- EPSG_CODE_PARAMETER_FALSE_NORTHING, common::UnitOfMeasure::METRE);
- if (falseNorthing != 0) {
- throw io::FormattingException(
- std::string("Unsupported value for ") +
- EPSG_NAME_PARAMETER_FALSE_NORTHING);
- }
- // PROJ.4 specific hack for webmercator
- } else if (formatter->getCRSExport() &&
- methodEPSGCode ==
- EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
- if (!createPROJ4WebMercator(this, formatter)) {
- throw io::FormattingException(
- std::string("Cannot export ") +
- EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR +
- " as PROJ.4 string outside of a ProjectedCRS context");
- }
- bConversionDone = true;
- bEllipsoidParametersDone = true;
- applyTargetCRSModifiers = false;
- } else if (ci_equal(convName, "Popular Visualisation Mercator")) {
- if (formatter->getCRSExport()) {
- if (!createPROJ4WebMercator(this, formatter)) {
- throw io::FormattingException(concat(
- "Cannot export ", convName,
- " as PROJ.4 string outside of a ProjectedCRS context"));
- }
- applyTargetCRSModifiers = false;
- } else {
- formatter->addStep("webmerc");
- if (l_sourceCRS) {
- datum::Ellipsoid::WGS84->_exportToPROJString(formatter);
- }
- }
- bConversionDone = true;
- bEllipsoidParametersDone = true;
- } else if (starts_with(methodName, "PROJ ")) {
- bConversionDone = true;
- createPROJExtensionFromCustomProj(this, formatter, false);
- } else if (ci_equal(methodName,
- PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION)) {
- double southPoleLat = parameterValueNumeric(
- PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION,
- common::UnitOfMeasure::DEGREE);
- double southPoleLon = parameterValueNumeric(
- PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION,
- common::UnitOfMeasure::DEGREE);
- double rotation = parameterValueNumeric(
- PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION,
- common::UnitOfMeasure::DEGREE);
- formatter->addStep("ob_tran");
- formatter->addParam("o_proj", "longlat");
- formatter->addParam("o_lon_p", -rotation);
- formatter->addParam("o_lat_p", -southPoleLat);
- formatter->addParam("lon_0", southPoleLon);
- bConversionDone = true;
- } else if (ci_equal(methodName, "Adams_Square_II")) {
- // Look for ESRI method and parameter names (to be opposed
- // to the OGC WKT2 names we use elsewhere, because there's no mapping
- // of those parameters to OGC WKT2)
- // We also reject non-default values for a number of parameters,
- // because they are not implemented on PROJ side. The subset we
- // support can handle ESRI:54098 WGS_1984_Adams_Square_II, but not
- // ESRI:54099 WGS_1984_Spilhaus_Ocean_Map_in_Square
- const double falseEasting = parameterValueNumeric(
- "False_Easting", common::UnitOfMeasure::METRE);
- const double falseNorthing = parameterValueNumeric(
- "False_Northing", common::UnitOfMeasure::METRE);
- const double scaleFactor =
- parameterValue("Scale_Factor", 0)
- ? parameterValueNumeric("Scale_Factor",
- common::UnitOfMeasure::SCALE_UNITY)
- : 1.0;
- const double azimuth =
- parameterValueNumeric("Azimuth", common::UnitOfMeasure::DEGREE);
- const double longitudeOfCenter = parameterValueNumeric(
- "Longitude_Of_Center", common::UnitOfMeasure::DEGREE);
- const double latitudeOfCenter = parameterValueNumeric(
- "Latitude_Of_Center", common::UnitOfMeasure::DEGREE);
- const double XYPlaneRotation = parameterValueNumeric(
- "XY_Plane_Rotation", common::UnitOfMeasure::DEGREE);
- if (scaleFactor != 1.0 || azimuth != 0.0 || latitudeOfCenter != 0.0 ||
- XYPlaneRotation != 0.0) {
- throw io::FormattingException("Unsupported value for one or "
- "several parameters of "
- "Adams_Square_II");
- }
- formatter->addStep("adams_ws2");
- formatter->addParam("lon_0", longitudeOfCenter);
- formatter->addParam("x_0", falseEasting);
- formatter->addParam("y_0", falseNorthing);
- bConversionDone = true;
- } else if (formatter->convention() ==
- io::PROJStringFormatter::Convention::PROJ_5 &&
- isZUnitConversion) {
- double convFactor = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
- auto uom = common::UnitOfMeasure(std::string(), convFactor,
- common::UnitOfMeasure::Type::LINEAR)
- .exportToPROJString();
- auto reverse_uom =
- convFactor == 0.0
- ? std::string()
- : common::UnitOfMeasure(std::string(), 1.0 / convFactor,
- common::UnitOfMeasure::Type::LINEAR)
- .exportToPROJString();
- if (uom == "m") {
- // do nothing
- } else if (!uom.empty()) {
- formatter->addStep("unitconvert");
- formatter->addParam("z_in", uom);
- formatter->addParam("z_out", "m");
- } else if (!reverse_uom.empty()) {
- formatter->addStep("unitconvert");
- formatter->addParam("z_in", "m");
- formatter->addParam("z_out", reverse_uom);
- } else {
- formatter->addStep("affine");
- formatter->addParam("s33", convFactor);
- }
- bConversionDone = true;
- bEllipsoidParametersDone = true;
- } else if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) {
- if (!srcGeogCRS) {
- throw io::FormattingException(
- "Export of Geographic/Topocentric conversion to a PROJ string "
- "requires an input geographic CRS");
- }
-
- formatter->addStep("cart");
- srcGeogCRS->ellipsoid()->_exportToPROJString(formatter);
-
- formatter->addStep("topocentric");
- const auto latOrigin = parameterValueNumeric(
- EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN,
- common::UnitOfMeasure::DEGREE);
- const auto lonOrigin = parameterValueNumeric(
- EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN,
- common::UnitOfMeasure::DEGREE);
- const auto heightOrigin = parameterValueNumeric(
- EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN,
- common::UnitOfMeasure::METRE);
- formatter->addParam("lat_0", latOrigin);
- formatter->addParam("lon_0", lonOrigin);
- formatter->addParam("h_0", heightOrigin);
- bConversionDone = true;
- }
-
- auto l_targetCRS = targetCRS();
-
- bool bAxisSpecFound = false;
- if (!bConversionDone) {
- const MethodMapping *mapping = getMapping(l_method.get());
- if (mapping && mapping->proj_name_main) {
- formatter->addStep(mapping->proj_name_main);
- if (useApprox) {
- formatter->addParam("approx");
- }
- if (mapping->proj_name_aux) {
- bool addAux = true;
- if (internal::starts_with(mapping->proj_name_aux, "axis=")) {
- if (mapping->epsg_code == EPSG_CODE_METHOD_KROVAK) {
- auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(
- l_targetCRS.get());
- if (projCRS) {
- const auto &axisList =
- projCRS->coordinateSystem()->axisList();
- if (axisList[0]->direction() ==
- cs::AxisDirection::WEST &&
- axisList[1]->direction() ==
- cs::AxisDirection::SOUTH) {
- formatter->addParam("czech");
- addAux = false;
- }
- }
- }
- bAxisSpecFound = true;
- }
-
- // No need to add explicit f=0 if the ellipsoid is a sphere
- if (strcmp(mapping->proj_name_aux, "f=0") == 0) {
- crs::CRS *horiz = l_sourceCRS.get();
- const auto compound =
- dynamic_cast<const crs::CompoundCRS *>(horiz);
- if (compound) {
- const auto &components =
- compound->componentReferenceSystems();
- if (!components.empty()) {
- horiz = components.front().get();
- }
- }
-
- auto geogCRS =
- dynamic_cast<const crs::GeographicCRS *>(horiz);
- if (geogCRS && geogCRS->ellipsoid()->isSphere()) {
- addAux = false;
- }
- }
-
- if (addAux) {
- auto kv = split(mapping->proj_name_aux, '=');
- if (kv.size() == 2) {
- formatter->addParam(kv[0], kv[1]);
- } else {
- formatter->addParam(mapping->proj_name_aux);
- }
- }
- }
-
- if (mapping->epsg_code ==
- EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
- double latitudeStdParallel = parameterValueNumeric(
- EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
- common::UnitOfMeasure::DEGREE);
- formatter->addParam("lat_0",
- (latitudeStdParallel >= 0) ? 90.0 : -90.0);
- }
-
- for (int i = 0; mapping->params[i] != nullptr; i++) {
- const auto *param = mapping->params[i];
- if (!param->proj_name) {
- continue;
- }
- const auto &value =
- parameterValueMeasure(param->wkt2_name, param->epsg_code);
- double valueConverted = 0;
- if (value == nullMeasure) {
- // Deal with missing values. In an ideal world, this would
- // not happen
- if (param->epsg_code ==
- EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
- valueConverted = 1.0;
- }
- } else if (param->unit_type ==
- common::UnitOfMeasure::Type::ANGULAR) {
- valueConverted =
- value.convertToUnit(common::UnitOfMeasure::DEGREE);
- } else {
- valueConverted = value.getSIValue();
- }
-
- if (mapping->epsg_code ==
- EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
- strcmp(param->proj_name, "lat_1") == 0) {
- formatter->addParam(param->proj_name, valueConverted);
- formatter->addParam("lat_0", valueConverted);
- } else {
- formatter->addParam(param->proj_name, valueConverted);
- }
- }
-
- } else {
- if (!exportToPROJStringGeneric(formatter)) {
- throw io::FormattingException(
- concat("Unsupported conversion method: ", methodName));
- }
- }
- }
-
- if (l_targetCRS && applyTargetCRSModifiers) {
- crs::CRS *horiz = l_targetCRS.get();
- const auto compound = dynamic_cast<const crs::CompoundCRS *>(horiz);
- if (compound) {
- const auto &components = compound->componentReferenceSystems();
- if (!components.empty()) {
- horiz = components.front().get();
- }
- }
-
- if (!bEllipsoidParametersDone) {
- auto targetGeodCRS = horiz->extractGeodeticCRS();
- auto targetGeogCRS =
- std::dynamic_pointer_cast<crs::GeographicCRS>(targetGeodCRS);
- if (targetGeogCRS) {
- if (formatter->getCRSExport()) {
- targetGeogCRS->addDatumInfoToPROJString(formatter);
- } else {
- targetGeogCRS->ellipsoid()->_exportToPROJString(formatter);
- targetGeogCRS->primeMeridian()->_exportToPROJString(
- formatter);
- }
- } else if (targetGeodCRS) {
- targetGeodCRS->ellipsoid()->_exportToPROJString(formatter);
- }
- }
-
- auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz);
- if (projCRS) {
- formatter->pushOmitZUnitConversion();
- projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound);
- formatter->popOmitZUnitConversion();
- }
-
- auto derivedGeographicCRS =
- dynamic_cast<const crs::DerivedGeographicCRS *>(horiz);
- if (!formatter->getCRSExport() && derivedGeographicCRS) {
- formatter->setOmitProjLongLatIfPossible(true);
- derivedGeographicCRS->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->setOmitProjLongLatIfPossible(false);
- }
- }
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return whether a conversion is a [Universal Transverse Mercator]
- * (https://proj.org/operations/projections/utm.html) conversion.
- *
- * @param[out] zone UTM zone number between 1 and 60.
- * @param[out] north true for UTM northern hemisphere, false for UTM southern
- * hemisphere.
- * @return true if it is a UTM conversion.
- */
-bool Conversion::isUTM(int &zone, bool &north) const {
- zone = 0;
- north = true;
-
- if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
- // Check for UTM
-
- bool bLatitudeNatOriginUTM = false;
- bool bScaleFactorUTM = false;
- bool bFalseEastingUTM = false;
- bool bFalseNorthingUTM = false;
- for (const auto &genOpParamvalue : parameterValues()) {
- auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto epsg_code = opParamvalue->parameter()->getEPSGCode();
- const auto &l_parameterValue = opParamvalue->parameterValue();
- if (l_parameterValue->type() == ParameterValue::Type::MEASURE) {
- const auto &measure = l_parameterValue->value();
- if (epsg_code ==
- EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN &&
- std::fabs(measure.value() -
- UTM_LATITUDE_OF_NATURAL_ORIGIN) < 1e-10) {
- bLatitudeNatOriginUTM = true;
- } else if (
- (epsg_code ==
- EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN ||
- epsg_code ==
- EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) &&
- measure.unit()._isEquivalentTo(
- common::UnitOfMeasure::DEGREE,
- util::IComparable::Criterion::EQUIVALENT)) {
- double dfZone = (measure.value() + 183.0) / 6.0;
- if (dfZone > 0.9 && dfZone < 60.1 &&
- std::abs(dfZone - std::round(dfZone)) < 1e-10) {
- zone = static_cast<int>(std::lround(dfZone));
- }
- } else if (
- epsg_code ==
- EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN &&
- measure.unit()._isEquivalentTo(
- common::UnitOfMeasure::SCALE_UNITY,
- util::IComparable::Criterion::EQUIVALENT) &&
- std::fabs(measure.value() - UTM_SCALE_FACTOR) < 1e-10) {
- bScaleFactorUTM = true;
- } else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_EASTING &&
- measure.value() == UTM_FALSE_EASTING &&
- measure.unit()._isEquivalentTo(
- common::UnitOfMeasure::METRE,
- util::IComparable::Criterion::EQUIVALENT)) {
- bFalseEastingUTM = true;
- } else if (epsg_code ==
- EPSG_CODE_PARAMETER_FALSE_NORTHING &&
- measure.unit()._isEquivalentTo(
- common::UnitOfMeasure::METRE,
- util::IComparable::Criterion::EQUIVALENT)) {
- if (std::fabs(measure.value() -
- UTM_NORTH_FALSE_NORTHING) < 1e-10) {
- bFalseNorthingUTM = true;
- north = true;
- } else if (std::fabs(measure.value() -
- UTM_SOUTH_FALSE_NORTHING) <
- 1e-10) {
- bFalseNorthingUTM = true;
- north = false;
- }
- }
- }
- }
- }
- if (bLatitudeNatOriginUTM && zone > 0 && bScaleFactorUTM &&
- bFalseEastingUTM && bFalseNorthingUTM) {
- return true;
- }
- }
- return false;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return a Conversion object where some parameters are better
- * identified.
- *
- * @return a new Conversion.
- */
-ConversionNNPtr Conversion::identify() const {
- auto newConversion = Conversion::nn_make_shared<Conversion>(*this);
- newConversion->assignSelf(newConversion);
-
- if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
- // Check for UTM
- int zone = 0;
- bool north = true;
- if (isUTM(zone, north)) {
- newConversion->setProperties(
- getUTMConversionProperty(util::PropertyMap(), zone, north));
- }
- }
-
- return newConversion;
-}
-
-//! @cond Doxygen_Suppress
-// ---------------------------------------------------------------------------
-
-InvalidOperation::InvalidOperation(const char *message) : Exception(message) {}
-
-// ---------------------------------------------------------------------------
-
-InvalidOperation::InvalidOperation(const std::string &message)
- : Exception(message) {}
-
-// ---------------------------------------------------------------------------
-
-InvalidOperation::InvalidOperation(const InvalidOperation &) = default;
-
-// ---------------------------------------------------------------------------
-
-InvalidOperation::~InvalidOperation() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct Transformation::Private {
-
- TransformationPtr forwardOperation_{};
-
- static TransformationNNPtr registerInv(const Transformation *thisIn,
- TransformationNNPtr invTransform);
-};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-Transformation::Transformation(
- const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
- const crs::CRSPtr &interpolationCRSIn, const OperationMethodNNPtr &methodIn,
- const std::vector<GeneralParameterValueNNPtr> &values,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies)
- : SingleOperation(methodIn), d(internal::make_unique<Private>()) {
- setParameterValues(values);
- setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn);
- setAccuracies(accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-Transformation::~Transformation() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-Transformation::Transformation(const Transformation &other)
- : CoordinateOperation(other), SingleOperation(other),
- d(internal::make_unique<Private>(*other.d)) {}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the source crs::CRS of the transformation.
- *
- * @return the source CRS.
- */
-const crs::CRSNNPtr &Transformation::sourceCRS() PROJ_PURE_DEFN {
- return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the target crs::CRS of the transformation.
- *
- * @return the target CRS.
- */
-const crs::CRSNNPtr &Transformation::targetCRS() PROJ_PURE_DEFN {
- return CoordinateOperation::getPrivate()->strongRef_->targetCRS_;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-TransformationNNPtr Transformation::shallowClone() const {
- auto transf = Transformation::nn_make_shared<Transformation>(*this);
- transf->assignSelf(transf);
- transf->setCRSs(this, false);
- if (transf->d->forwardOperation_) {
- transf->d->forwardOperation_ =
- transf->d->forwardOperation_->shallowClone().as_nullable();
- }
- return transf;
-}
-
-CoordinateOperationNNPtr Transformation::_shallowClone() const {
- return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
-}
-
-// ---------------------------------------------------------------------------
-
-TransformationNNPtr
-Transformation::promoteTo3D(const std::string &,
- const io::DatabaseContextPtr &dbContext) const {
- auto transf = shallowClone();
- transf->setCRSs(sourceCRS()->promoteTo3D(std::string(), dbContext),
- targetCRS()->promoteTo3D(std::string(), dbContext),
- interpolationCRS());
- return transf;
-}
-
-// ---------------------------------------------------------------------------
-
-TransformationNNPtr
-Transformation::demoteTo2D(const std::string &,
- const io::DatabaseContextPtr &dbContext) const {
- auto transf = shallowClone();
- transf->setCRSs(sourceCRS()->demoteTo2D(std::string(), dbContext),
- targetCRS()->demoteTo2D(std::string(), dbContext),
- interpolationCRS());
- return transf;
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-/** \brief Return the TOWGS84 parameters of the transformation.
- *
- * If this transformation uses Coordinate Frame Rotation, Position Vector
- * transformation or Geocentric translations, a vector of 7 double values
- * using the Position Vector convention (EPSG:9606) is returned. Those values
- * can be used as the value of the WKT1 TOWGS84 parameter or
- * PROJ +towgs84 parameter.
- *
- * @return a vector of 7 values if valid, otherwise a io::FormattingException
- * is thrown.
- * @throws io::FormattingException
- */
-std::vector<double>
-Transformation::getTOWGS84Parameters() const // throw(io::FormattingException)
-{
- // GDAL WKT1 assumes EPSG:9606 / Position Vector convention
-
- bool sevenParamsTransform = false;
- bool threeParamsTransform = false;
- bool invertRotSigns = false;
- const auto &l_method = method();
- const auto &methodName = l_method->nameStr();
- const int methodEPSGCode = l_method->getEPSGCode();
- const auto paramCount = parameterValues().size();
- if ((paramCount == 7 &&
- ci_find(methodName, "Coordinate Frame") != std::string::npos) ||
- methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
- methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
- methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
- sevenParamsTransform = true;
- invertRotSigns = true;
- } else if ((paramCount == 7 &&
- ci_find(methodName, "Position Vector") != std::string::npos) ||
- methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
- sevenParamsTransform = true;
- invertRotSigns = false;
- } else if ((paramCount == 3 &&
- ci_find(methodName, "Geocentric translations") !=
- std::string::npos) ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
- threeParamsTransform = true;
- }
-
- if (threeParamsTransform || sevenParamsTransform) {
- std::vector<double> params(7, 0.0);
- bool foundX = false;
- bool foundY = false;
- bool foundZ = false;
- bool foundRotX = false;
- bool foundRotY = false;
- bool foundRotZ = false;
- bool foundScale = false;
- const double rotSign = invertRotSigns ? -1.0 : 1.0;
-
- const auto fixNegativeZero = [](double x) {
- if (x == 0.0)
- return 0.0;
- return x;
- };
-
- for (const auto &genOpParamvalue : parameterValues()) {
- auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
- genOpParamvalue.get());
- if (opParamvalue) {
- const auto &parameter = opParamvalue->parameter();
- const auto epsg_code = parameter->getEPSGCode();
- const auto &l_parameterValue = opParamvalue->parameterValue();
- if (l_parameterValue->type() == ParameterValue::Type::MEASURE) {
- const auto &measure = l_parameterValue->value();
- if (epsg_code == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) {
- params[0] = measure.getSIValue();
- foundX = true;
- } else if (epsg_code ==
- EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) {
- params[1] = measure.getSIValue();
- foundY = true;
- } else if (epsg_code ==
- EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) {
- params[2] = measure.getSIValue();
- foundZ = true;
- } else if (epsg_code ==
- EPSG_CODE_PARAMETER_X_AXIS_ROTATION) {
- params[3] = fixNegativeZero(
- rotSign *
- measure.convertToUnit(
- common::UnitOfMeasure::ARC_SECOND));
- foundRotX = true;
- } else if (epsg_code ==
- EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) {
- params[4] = fixNegativeZero(
- rotSign *
- measure.convertToUnit(
- common::UnitOfMeasure::ARC_SECOND));
- foundRotY = true;
- } else if (epsg_code ==
- EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) {
- params[5] = fixNegativeZero(
- rotSign *
- measure.convertToUnit(
- common::UnitOfMeasure::ARC_SECOND));
- foundRotZ = true;
- } else if (epsg_code ==
- EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) {
- params[6] = measure.convertToUnit(
- common::UnitOfMeasure::PARTS_PER_MILLION);
- foundScale = true;
- }
- }
- }
- }
- if (foundX && foundY && foundZ &&
- (threeParamsTransform ||
- (foundRotX && foundRotY && foundRotZ && foundScale))) {
- return params;
- } else {
- throw io::FormattingException(
- "Missing required parameter values in transformation");
- }
- }
-
-#if 0
- if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS ||
- methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
- auto offsetLat =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
- auto offsetLong =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
-
- auto offsetHeight =
- parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
-
- if (offsetLat.getSIValue() == 0.0 && offsetLong.getSIValue() == 0.0 &&
- offsetHeight.getSIValue() == 0.0) {
- std::vector<double> params(7, 0.0);
- return params;
- }
- }
-#endif
-
- throw io::FormattingException(
- "Transformation cannot be formatted as WKT1 TOWGS84 parameters");
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation from a vector of GeneralParameterValue.
- *
- * @param properties See \ref general_properties. At minimum the name should be
- * defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param interpolationCRSIn Interpolation CRS (might be null)
- * @param methodIn Operation method.
- * @param values Vector of GeneralOperationParameterNNPtr.
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- * @throws InvalidOperation
- */
-TransformationNNPtr Transformation::create(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn,
- const OperationMethodNNPtr &methodIn,
- const std::vector<GeneralParameterValueNNPtr> &values,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- if (methodIn->parameters().size() != values.size()) {
- throw InvalidOperation(
- "Inconsistent number of parameters and parameter values");
- }
- auto transf = Transformation::nn_make_shared<Transformation>(
- sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values,
- accuracies);
- transf->assignSelf(transf);
- transf->setProperties(properties);
- std::string name;
- if (properties.getStringValue(common::IdentifiedObject::NAME_KEY, name) &&
- ci_find(name, "ballpark") != std::string::npos) {
- transf->setHasBallparkTransformation(true);
- }
- return transf;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation ands its OperationMethod.
- *
- * @param propertiesTransformation The \ref general_properties of the
- * Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param interpolationCRSIn Interpolation CRS (might be null)
- * @param propertiesOperationMethod The \ref general_properties of the
- * OperationMethod.
- * At minimum the name should be defined.
- * @param parameters Vector of parameters of the operation method.
- * @param values Vector of ParameterValueNNPtr. Constraint:
- * values.size() == parameters.size()
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- * @throws InvalidOperation
- */
-TransformationNNPtr
-Transformation::create(const util::PropertyMap &propertiesTransformation,
- const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn,
- const crs::CRSPtr &interpolationCRSIn,
- const util::PropertyMap &propertiesOperationMethod,
- const std::vector<OperationParameterNNPtr> &parameters,
- const std::vector<ParameterValueNNPtr> &values,
- const std::vector<metadata::PositionalAccuracyNNPtr>
- &accuracies) // throw InvalidOperation
-{
- OperationMethodNNPtr op(
- OperationMethod::create(propertiesOperationMethod, parameters));
-
- if (parameters.size() != values.size()) {
- throw InvalidOperation(
- "Inconsistent number of parameters and parameter values");
- }
- std::vector<GeneralParameterValueNNPtr> generalParameterValues;
- generalParameterValues.reserve(values.size());
- for (size_t i = 0; i < values.size(); i++) {
- generalParameterValues.push_back(
- OperationParameterValue::create(parameters[i], values[i]));
- }
- return create(propertiesTransformation, sourceCRSIn, targetCRSIn,
- interpolationCRSIn, op, generalParameterValues, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-// ---------------------------------------------------------------------------
-
-static TransformationNNPtr createSevenParamsTransform(
- const util::PropertyMap &properties,
- const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
- double translationYMetre, double translationZMetre,
- double rotationXArcSecond, double rotationYArcSecond,
- double rotationZArcSecond, double scaleDifferencePPM,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return Transformation::create(
- properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties,
- VectorOfParameters{
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE),
- },
- createParams(common::Length(translationXMetre),
- common::Length(translationYMetre),
- common::Length(translationZMetre),
- common::Angle(rotationXArcSecond,
- common::UnitOfMeasure::ARC_SECOND),
- common::Angle(rotationYArcSecond,
- common::UnitOfMeasure::ARC_SECOND),
- common::Angle(rotationZArcSecond,
- common::UnitOfMeasure::ARC_SECOND),
- common::Scale(scaleDifferencePPM,
- common::UnitOfMeasure::PARTS_PER_MILLION)),
- accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn,
- bool &isGeocentric, bool &isGeog2D,
- bool &isGeog3D) {
- auto sourceCRSGeod =
- dynamic_cast<const crs::GeodeticCRS *>(sourceCRSIn.get());
- auto targetCRSGeod =
- dynamic_cast<const crs::GeodeticCRS *>(targetCRSIn.get());
- isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() &&
- targetCRSGeod && targetCRSGeod->isGeocentric();
- if (isGeocentric) {
- isGeog2D = false;
- isGeog3D = false;
- return;
- }
- isGeocentric = false;
-
- auto sourceCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRSIn.get());
- auto targetCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(targetCRSIn.get());
- if (!sourceCRSGeog || !targetCRSGeog) {
- throw InvalidOperation("Inconsistent CRS type");
- }
- const auto nSrcAxisCount =
- sourceCRSGeog->coordinateSystem()->axisList().size();
- const auto nTargetAxisCount =
- targetCRSGeog->coordinateSystem()->axisList().size();
- isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2;
- isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2;
-}
-
-// ---------------------------------------------------------------------------
-
-static int
-useOperationMethodEPSGCodeIfPresent(const util::PropertyMap &properties,
- int nDefaultOperationMethodEPSGCode) {
- const auto *operationMethodEPSGCode =
- properties.get("OPERATION_METHOD_EPSG_CODE");
- if (operationMethodEPSGCode) {
- const auto boxedValue = dynamic_cast<const util::BoxedValue *>(
- (*operationMethodEPSGCode).get());
- if (boxedValue &&
- boxedValue->type() == util::BoxedValue::Type::INTEGER) {
- return boxedValue->integerValue();
- }
- }
- return nDefaultOperationMethodEPSGCode;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with Geocentric Translations method.
- *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param translationXMetre Value of the Translation_X parameter (in metre).
- * @param translationYMetre Value of the Translation_Y parameter (in metre).
- * @param translationZMetre Value of the Translation_Z parameter (in metre).
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createGeocentricTranslations(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
- double translationYMetre, double translationZMetre,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- bool isGeocentric;
- bool isGeog2D;
- bool isGeog3D;
- getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
- isGeog3D);
- return create(
- properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
- properties,
- isGeocentric
- ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC
- : isGeog2D
- ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D
- : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D)),
- VectorOfParameters{
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
- },
- createParams(common::Length(translationXMetre),
- common::Length(translationYMetre),
- common::Length(translationZMetre)),
- accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with Position vector transformation
- * method.
- *
- * This is similar to createCoordinateFrameRotation(), except that the sign of
- * the rotation terms is inverted.
- *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param translationXMetre Value of the Translation_X parameter (in metre).
- * @param translationYMetre Value of the Translation_Y parameter (in metre).
- * @param translationZMetre Value of the Translation_Z parameter (in metre).
- * @param rotationXArcSecond Value of the Rotation_X parameter (in
- * arc-second).
- * @param rotationYArcSecond Value of the Rotation_Y parameter (in
- * arc-second).
- * @param rotationZArcSecond Value of the Rotation_Z parameter (in
- * arc-second).
- * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
- * parts-per-million).
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createPositionVector(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
- double translationYMetre, double translationZMetre,
- double rotationXArcSecond, double rotationYArcSecond,
- double rotationZArcSecond, double scaleDifferencePPM,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
-
- bool isGeocentric;
- bool isGeog2D;
- bool isGeog3D;
- getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
- isGeog3D);
- return createSevenParamsTransform(
- properties,
- createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
- properties,
- isGeocentric
- ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC
- : isGeog2D ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
- : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D)),
- sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
- translationZMetre, rotationXArcSecond, rotationYArcSecond,
- rotationZArcSecond, scaleDifferencePPM, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with Coordinate Frame Rotation method.
- *
- * This is similar to createPositionVector(), except that the sign of
- * the rotation terms is inverted.
- *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param translationXMetre Value of the Translation_X parameter (in metre).
- * @param translationYMetre Value of the Translation_Y parameter (in metre).
- * @param translationZMetre Value of the Translation_Z parameter (in metre).
- * @param rotationXArcSecond Value of the Rotation_X parameter (in
- * arc-second).
- * @param rotationYArcSecond Value of the Rotation_Y parameter (in
- * arc-second).
- * @param rotationZArcSecond Value of the Rotation_Z parameter (in
- * arc-second).
- * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
- * parts-per-million).
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createCoordinateFrameRotation(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
- double translationYMetre, double translationZMetre,
- double rotationXArcSecond, double rotationYArcSecond,
- double rotationZArcSecond, double scaleDifferencePPM,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- bool isGeocentric;
- bool isGeog2D;
- bool isGeog3D;
- getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
- isGeog3D);
- return createSevenParamsTransform(
- properties,
- createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
- properties,
- isGeocentric
- ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC
- : isGeog2D ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D
- : EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D)),
- sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
- translationZMetre, rotationXArcSecond, rotationYArcSecond,
- rotationZArcSecond, scaleDifferencePPM, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static TransformationNNPtr createFifteenParamsTransform(
- const util::PropertyMap &properties,
- const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
- double translationYMetre, double translationZMetre,
- double rotationXArcSecond, double rotationYArcSecond,
- double rotationZArcSecond, double scaleDifferencePPM,
- double rateTranslationX, double rateTranslationY, double rateTranslationZ,
- double rateRotationX, double rateRotationY, double rateRotationZ,
- double rateScaleDifference, double referenceEpochYear,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return Transformation::create(
- properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties,
- VectorOfParameters{
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE),
-
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION),
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE),
-
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_REFERENCE_EPOCH),
- },
- VectorOfValues{
- common::Length(translationXMetre),
- common::Length(translationYMetre),
- common::Length(translationZMetre),
- common::Angle(rotationXArcSecond,
- common::UnitOfMeasure::ARC_SECOND),
- common::Angle(rotationYArcSecond,
- common::UnitOfMeasure::ARC_SECOND),
- common::Angle(rotationZArcSecond,
- common::UnitOfMeasure::ARC_SECOND),
- common::Scale(scaleDifferencePPM,
- common::UnitOfMeasure::PARTS_PER_MILLION),
- common::Measure(rateTranslationX,
- common::UnitOfMeasure::METRE_PER_YEAR),
- common::Measure(rateTranslationY,
- common::UnitOfMeasure::METRE_PER_YEAR),
- common::Measure(rateTranslationZ,
- common::UnitOfMeasure::METRE_PER_YEAR),
- common::Measure(rateRotationX,
- common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
- common::Measure(rateRotationY,
- common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
- common::Measure(rateRotationZ,
- common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
- common::Measure(rateScaleDifference,
- common::UnitOfMeasure::PPM_PER_YEAR),
- common::Measure(referenceEpochYear, common::UnitOfMeasure::YEAR),
- },
- accuracies);
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with Time Dependent position vector
- * transformation method.
- *
- * This is similar to createTimeDependentCoordinateFrameRotation(), except that
- * the sign of
- * the rotation terms is inverted.
- *
- * This method is defined as [EPSG:1053]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1053)
- *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param translationXMetre Value of the Translation_X parameter (in metre).
- * @param translationYMetre Value of the Translation_Y parameter (in metre).
- * @param translationZMetre Value of the Translation_Z parameter (in metre).
- * @param rotationXArcSecond Value of the Rotation_X parameter (in
- * arc-second).
- * @param rotationYArcSecond Value of the Rotation_Y parameter (in
- * arc-second).
- * @param rotationZArcSecond Value of the Rotation_Z parameter (in
- * arc-second).
- * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
- * parts-per-million).
- * @param rateTranslationX Value of the rate of change of X-axis translation (in
- * metre/year)
- * @param rateTranslationY Value of the rate of change of Y-axis translation (in
- * metre/year)
- * @param rateTranslationZ Value of the rate of change of Z-axis translation (in
- * metre/year)
- * @param rateRotationX Value of the rate of change of X-axis rotation (in
- * arc-second/year)
- * @param rateRotationY Value of the rate of change of Y-axis rotation (in
- * arc-second/year)
- * @param rateRotationZ Value of the rate of change of Z-axis rotation (in
- * arc-second/year)
- * @param rateScaleDifference Value of the rate of change of scale difference
- * (in PPM/year)
- * @param referenceEpochYear Parameter reference epoch (in decimal year)
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createTimeDependentPositionVector(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
- double translationYMetre, double translationZMetre,
- double rotationXArcSecond, double rotationYArcSecond,
- double rotationZArcSecond, double scaleDifferencePPM,
- double rateTranslationX, double rateTranslationY, double rateTranslationZ,
- double rateRotationX, double rateRotationY, double rateRotationZ,
- double rateScaleDifference, double referenceEpochYear,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- bool isGeocentric;
- bool isGeog2D;
- bool isGeog3D;
- getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
- isGeog3D);
- return createFifteenParamsTransform(
- properties,
- createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
- properties,
- isGeocentric
- ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC
- : isGeog2D
- ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D
- : EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D)),
- sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
- translationZMetre, rotationXArcSecond, rotationYArcSecond,
- rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
- rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY,
- rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with Time Dependent Position coordinate
- * frame rotation transformation method.
- *
- * This is similar to createTimeDependentPositionVector(), except that the sign
- * of
- * the rotation terms is inverted.
- *
- * This method is defined as [EPSG:1056]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1056)
- *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param translationXMetre Value of the Translation_X parameter (in metre).
- * @param translationYMetre Value of the Translation_Y parameter (in metre).
- * @param translationZMetre Value of the Translation_Z parameter (in metre).
- * @param rotationXArcSecond Value of the Rotation_X parameter (in
- * arc-second).
- * @param rotationYArcSecond Value of the Rotation_Y parameter (in
- * arc-second).
- * @param rotationZArcSecond Value of the Rotation_Z parameter (in
- * arc-second).
- * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
- * parts-per-million).
- * @param rateTranslationX Value of the rate of change of X-axis translation (in
- * metre/year)
- * @param rateTranslationY Value of the rate of change of Y-axis translation (in
- * metre/year)
- * @param rateTranslationZ Value of the rate of change of Z-axis translation (in
- * metre/year)
- * @param rateRotationX Value of the rate of change of X-axis rotation (in
- * arc-second/year)
- * @param rateRotationY Value of the rate of change of Y-axis rotation (in
- * arc-second/year)
- * @param rateRotationZ Value of the rate of change of Z-axis rotation (in
- * arc-second/year)
- * @param rateScaleDifference Value of the rate of change of scale difference
- * (in PPM/year)
- * @param referenceEpochYear Parameter reference epoch (in decimal year)
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- * @throws InvalidOperation
- */
-TransformationNNPtr Transformation::createTimeDependentCoordinateFrameRotation(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
- double translationYMetre, double translationZMetre,
- double rotationXArcSecond, double rotationYArcSecond,
- double rotationZArcSecond, double scaleDifferencePPM,
- double rateTranslationX, double rateTranslationY, double rateTranslationZ,
- double rateRotationX, double rateRotationY, double rateRotationZ,
- double rateScaleDifference, double referenceEpochYear,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
-
- bool isGeocentric;
- bool isGeog2D;
- bool isGeog3D;
- getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
- isGeog3D);
- return createFifteenParamsTransform(
- properties,
- createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
- properties,
- isGeocentric
- ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC
- : isGeog2D
- ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D
- : EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D)),
- sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
- translationZMetre, rotationXArcSecond, rotationYArcSecond,
- rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
- rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY,
- rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static TransformationNNPtr _createMolodensky(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, int methodEPSGCode,
- double translationXMetre, double translationYMetre,
- double translationZMetre, double semiMajorAxisDifferenceMetre,
- double flattingDifference,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return Transformation::create(
- properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(methodEPSGCode),
- VectorOfParameters{
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE),
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE),
- },
- createParams(
- common::Length(translationXMetre),
- common::Length(translationYMetre),
- common::Length(translationZMetre),
- common::Length(semiMajorAxisDifferenceMetre),
- common::Measure(flattingDifference, common::UnitOfMeasure::NONE)),
- accuracies);
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with Molodensky method.
- *
- * @see createAbridgedMolodensky() for a related method.
- *
- * This method is defined as [EPSG:9604]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9604)
- *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param translationXMetre Value of the Translation_X parameter (in metre).
- * @param translationYMetre Value of the Translation_Y parameter (in metre).
- * @param translationZMetre Value of the Translation_Z parameter (in metre).
- * @param semiMajorAxisDifferenceMetre The difference between the semi-major
- * axis values of the ellipsoids used in the target and source CRS (in metre).
- * @param flattingDifference The difference between the flattening values of
- * the ellipsoids used in the target and source CRS.
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- * @throws InvalidOperation
- */
-TransformationNNPtr Transformation::createMolodensky(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
- double translationYMetre, double translationZMetre,
- double semiMajorAxisDifferenceMetre, double flattingDifference,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return _createMolodensky(
- properties, sourceCRSIn, targetCRSIn, EPSG_CODE_METHOD_MOLODENSKY,
- translationXMetre, translationYMetre, translationZMetre,
- semiMajorAxisDifferenceMetre, flattingDifference, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with Abridged Molodensky method.
- *
- * @see createdMolodensky() for a related method.
- *
- * This method is defined as [EPSG:9605]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9605)
- *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param translationXMetre Value of the Translation_X parameter (in metre).
- * @param translationYMetre Value of the Translation_Y parameter (in metre).
- * @param translationZMetre Value of the Translation_Z parameter (in metre).
- * @param semiMajorAxisDifferenceMetre The difference between the semi-major
- * axis values of the ellipsoids used in the target and source CRS (in metre).
- * @param flattingDifference The difference between the flattening values of
- * the ellipsoids used in the target and source CRS.
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- * @throws InvalidOperation
- */
-TransformationNNPtr Transformation::createAbridgedMolodensky(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
- double translationYMetre, double translationZMetre,
- double semiMajorAxisDifferenceMetre, double flattingDifference,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return _createMolodensky(properties, sourceCRSIn, targetCRSIn,
- EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY,
- translationXMetre, translationYMetre,
- translationZMetre, semiMajorAxisDifferenceMetre,
- flattingDifference, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation from TOWGS84 parameters.
- *
- * This is a helper of createPositionVector() with the source CRS being the
- * GeographicCRS of sourceCRSIn, and the target CRS being EPSG:4326
- *
- * @param sourceCRSIn Source CRS.
- * @param TOWGS84Parameters The vector of 3 double values (Translation_X,_Y,_Z)
- * or 7 double values (Translation_X,_Y,_Z, Rotation_X,_Y,_Z, Scale_Difference)
- * passed to createPositionVector()
- * @return new Transformation.
- * @throws InvalidOperation
- */
-TransformationNNPtr Transformation::createTOWGS84(
- const crs::CRSNNPtr &sourceCRSIn,
- const std::vector<double> &TOWGS84Parameters) // throw InvalidOperation
-{
- if (TOWGS84Parameters.size() != 3 && TOWGS84Parameters.size() != 7) {
- throw InvalidOperation(
- "Invalid number of elements in TOWGS84Parameters");
- }
-
- crs::CRSPtr transformSourceCRS = sourceCRSIn->extractGeodeticCRS();
- if (!transformSourceCRS) {
- throw InvalidOperation(
- "Cannot find GeodeticCRS in sourceCRS of TOWGS84 transformation");
- }
-
- util::PropertyMap properties;
- properties.set(common::IdentifiedObject::NAME_KEY,
- concat("Transformation from ", transformSourceCRS->nameStr(),
- " to WGS84"));
-
- auto targetCRS =
- dynamic_cast<const crs::GeographicCRS *>(transformSourceCRS.get())
- ? util::nn_static_pointer_cast<crs::CRS>(
- crs::GeographicCRS::EPSG_4326)
- : util::nn_static_pointer_cast<crs::CRS>(
- crs::GeodeticCRS::EPSG_4978);
-
- if (TOWGS84Parameters.size() == 3) {
- return createGeocentricTranslations(
- properties, NN_NO_CHECK(transformSourceCRS), targetCRS,
- TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2],
- {});
- }
-
- return createPositionVector(properties, NN_NO_CHECK(transformSourceCRS),
- targetCRS, TOWGS84Parameters[0],
- TOWGS84Parameters[1], TOWGS84Parameters[2],
- TOWGS84Parameters[3], TOWGS84Parameters[4],
- TOWGS84Parameters[5], TOWGS84Parameters[6], {});
-}
-
-// ---------------------------------------------------------------------------
-/** \brief Instantiate a transformation with NTv2 method.
- *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param filename NTv2 filename.
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createNTv2(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, const std::string &filename,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
-
- return create(properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV2),
- VectorOfParameters{createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)},
- VectorOfValues{ParameterValue::createFilename(filename)},
- accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static TransformationNNPtr _createGravityRelatedHeightToGeographic3D(
- const util::PropertyMap &properties, bool inverse,
- const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
- const crs::CRSPtr &interpolationCRSIn, const std::string &filename,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
-
- return Transformation::create(
- properties, sourceCRSIn, targetCRSIn, interpolationCRSIn,
- util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- inverse ? INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D
- : PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D),
- VectorOfParameters{createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)},
- VectorOfValues{ParameterValue::createFilename(filename)}, accuracies);
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-/** \brief Instantiate a transformation from GravityRelatedHeight to
- * Geographic3D
- *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param interpolationCRSIn Interpolation CRS. (might be null)
- * @param filename GRID filename.
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createGravityRelatedHeightToGeographic3D(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn,
- const std::string &filename,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
-
- return _createGravityRelatedHeightToGeographic3D(
- properties, false, sourceCRSIn, targetCRSIn, interpolationCRSIn,
- filename, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with method VERTCON
- *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param filename GRID filename.
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createVERTCON(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, const std::string &filename,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
-
- return create(properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTCON),
- VectorOfParameters{createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)},
- VectorOfValues{ParameterValue::createFilename(filename)},
- accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static inline std::vector<metadata::PositionalAccuracyNNPtr>
-buildAccuracyZero() {
- return std::vector<metadata::PositionalAccuracyNNPtr>{
- metadata::PositionalAccuracy::create("0")};
-}
-
-// ---------------------------------------------------------------------------
-
-//! @endcond
-
-/** \brief Instantiate a transformation with method Longitude rotation
- *
- * This method is defined as [EPSG:9601]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9601)
- * *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param offset Longitude offset to add.
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createLongitudeRotation(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, const common::Angle &offset) {
-
- return create(
- properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(EPSG_CODE_METHOD_LONGITUDE_ROTATION),
- VectorOfParameters{
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
- VectorOfValues{ParameterValue::create(offset)}, buildAccuracyZero());
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-bool Transformation::isLongitudeRotation() const {
- return method()->getEPSGCode() == EPSG_CODE_METHOD_LONGITUDE_ROTATION;
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with method Geographic 2D offsets
- *
- * This method is defined as [EPSG:9619]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9619)
- * *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param offsetLat Latitude offset to add.
- * @param offsetLon Longitude offset to add.
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createGeographic2DOffsets(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
- const common::Angle &offsetLon,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return create(
- properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS),
- VectorOfParameters{
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
- VectorOfValues{offsetLat, offsetLon}, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with method Geographic 3D offsets
- *
- * This method is defined as [EPSG:9660]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9660)
- * *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param offsetLat Latitude offset to add.
- * @param offsetLon Longitude offset to add.
- * @param offsetHeight Height offset to add.
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createGeographic3DOffsets(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
- const common::Angle &offsetLon, const common::Length &offsetHeight,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return create(
- properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS),
- VectorOfParameters{
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
- VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with method Geographic 2D with
- * height
- * offsets
- *
- * This method is defined as [EPSG:9618]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9618)
- * *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param offsetLat Latitude offset to add.
- * @param offsetLon Longitude offset to add.
- * @param offsetHeight Geoid undulation to add.
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createGeographic2DWithHeightOffsets(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
- const common::Angle &offsetLon, const common::Length &offsetHeight,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return create(
- properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(
- EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS),
- VectorOfParameters{
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
- createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_GEOID_UNDULATION)},
- VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation with method Vertical Offset.
- *
- * This method is defined as [EPSG:9616]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9616)
- * *
- * @param properties See \ref general_properties of the Transformation.
- * At minimum the name should be defined.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param offsetHeight Geoid undulation to add.
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- */
-TransformationNNPtr Transformation::createVerticalOffset(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, const common::Length &offsetHeight,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return create(properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET),
- VectorOfParameters{createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
- VectorOfValues{offsetHeight}, accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a transformation based on the Change of Vertical Unit
- * method.
- *
- * This method is defined as [EPSG:1069]
- * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1069)
- *
- * @param properties See \ref general_properties of the conversion. If the name
- * is not provided, it is automatically set.
- * @param sourceCRSIn Source CRS.
- * @param targetCRSIn Target CRS.
- * @param factor Conversion factor
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return a new Transformation.
- */
-TransformationNNPtr Transformation::createChangeVerticalUnit(
- const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
- const crs::CRSNNPtr &targetCRSIn, const common::Scale &factor,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return create(
- properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT),
- VectorOfParameters{
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR),
- },
- VectorOfValues{
- factor,
- },
- accuracies);
-}
-
-// ---------------------------------------------------------------------------
-
-static util::PropertyMap
-createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom,
- bool approximateInversion) {
- assert(op);
- util::PropertyMap map;
-
- // The domain(s) are unchanged by the inverse operation
- addDomains(map, op);
-
- const std::string &forwardName = op->nameStr();
-
- // Forge a name for the inverse, either from the forward name, or
- // from the source and target CRS names
- const char *opType;
- if (starts_with(forwardName, BALLPARK_GEOCENTRIC_TRANSLATION)) {
- opType = BALLPARK_GEOCENTRIC_TRANSLATION;
- } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) {
- opType = BALLPARK_GEOGRAPHIC_OFFSET;
- } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) {
- opType = NULL_GEOGRAPHIC_OFFSET;
- } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) {
- opType = NULL_GEOCENTRIC_TRANSLATION;
- } else if (dynamic_cast<const Transformation *>(op) ||
- starts_with(forwardName, "Transformation from ")) {
- opType = "Transformation";
- } else if (dynamic_cast<const Conversion *>(op)) {
- opType = "Conversion";
- } else {
- opType = "Operation";
- }
-
- auto sourceCRS = op->sourceCRS();
- auto targetCRS = op->targetCRS();
- std::string name;
- if (!forwardName.empty()) {
- if (dynamic_cast<const Transformation *>(op) == nullptr &&
- dynamic_cast<const ConcatenatedOperation *>(op) == nullptr &&
- (starts_with(forwardName, INVERSE_OF) ||
- forwardName.find(" + ") != std::string::npos)) {
- std::vector<std::string> tokens;
- std::string curToken;
- bool inString = false;
- for (size_t i = 0; i < forwardName.size(); ++i) {
- if (inString) {
- curToken += forwardName[i];
- if (forwardName[i] == '\'') {
- inString = false;
- }
- } else if (i + 3 < forwardName.size() &&
- memcmp(&forwardName[i], " + ", 3) == 0) {
- tokens.push_back(curToken);
- curToken.clear();
- i += 2;
- } else if (forwardName[i] == '\'') {
- inString = true;
- curToken += forwardName[i];
- } else {
- curToken += forwardName[i];
- }
- }
- if (!curToken.empty()) {
- tokens.push_back(curToken);
- }
- for (size_t i = tokens.size(); i > 0;) {
- i--;
- if (!name.empty()) {
- name += " + ";
- }
- if (starts_with(tokens[i], INVERSE_OF)) {
- name += tokens[i].substr(INVERSE_OF.size());
- } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME ||
- tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) {
- name += tokens[i];
- } else {
- name += INVERSE_OF + tokens[i];
- }
- }
- } else if (!sourceCRS || !targetCRS ||
- forwardName != buildOpName(opType, sourceCRS, targetCRS)) {
- if (forwardName.find(" + ") != std::string::npos) {
- name = INVERSE_OF + '\'' + forwardName + '\'';
- } else {
- name = INVERSE_OF + forwardName;
- }
- }
- }
- if (name.empty() && sourceCRS && targetCRS) {
- name = buildOpName(opType, targetCRS, sourceCRS);
- }
- if (approximateInversion) {
- name += " (approx. inversion)";
- }
-
- if (!name.empty()) {
- map.set(common::IdentifiedObject::NAME_KEY, name);
- }
-
- const std::string &remarks = op->remarks();
- if (!remarks.empty()) {
- map.set(common::IdentifiedObject::REMARKS_KEY, remarks);
- }
-
- addModifiedIdentifier(map, op, true, derivedFrom);
-
- const auto so = dynamic_cast<const SingleOperation *>(op);
- if (so) {
- const int soMethodEPSGCode = so->method()->getEPSGCode();
- if (soMethodEPSGCode > 0) {
- map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode);
- }
- }
-
- return map;
-}
-
-// ---------------------------------------------------------------------------
-
-static bool isTimeDependent(const std::string &methodName) {
- return ci_find(methodName, "Time dependent") != std::string::npos ||
- ci_find(methodName, "Time-dependent") != std::string::npos;
-}
-
-// ---------------------------------------------------------------------------
-
-// to avoid -0...
-static double negate(double val) {
- if (val != 0) {
- return -val;
- }
- return 0.0;
-}
-
-// ---------------------------------------------------------------------------
-
-static CoordinateOperationPtr
-createApproximateInverseIfPossible(const Transformation *op) {
- bool sevenParamsTransform = false;
- bool fifteenParamsTransform = false;
- const auto &method = op->method();
- const auto &methodName = method->nameStr();
- const int methodEPSGCode = method->getEPSGCode();
- const auto paramCount = op->parameterValues().size();
- const bool isPositionVector =
- ci_find(methodName, "Position Vector") != std::string::npos;
- const bool isCoordinateFrame =
- ci_find(methodName, "Coordinate Frame") != std::string::npos;
-
- // See end of "2.4.3.3 Helmert 7-parameter transformations"
- // in EPSG 7-2 guidance
- // For practical purposes, the inverse of 7- or 15-parameters Helmert
- // can be obtained by using the forward method with all parameters
- // negated
- // (except reference epoch!)
- // So for WKT export use that. But for PROJ string, we use the +inv flag
- // so as to get "perfect" round-tripability.
- if ((paramCount == 7 && isCoordinateFrame &&
- !isTimeDependent(methodName)) ||
- methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
- methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
- methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
- sevenParamsTransform = true;
- } else if (
- (paramCount == 15 && isCoordinateFrame &&
- isTimeDependent(methodName)) ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) {
- fifteenParamsTransform = true;
- } else if ((paramCount == 7 && isPositionVector &&
- !isTimeDependent(methodName)) ||
- methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
- sevenParamsTransform = true;
- } else if (
- (paramCount == 15 && isPositionVector && isTimeDependent(methodName)) ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) {
- fifteenParamsTransform = true;
- }
- if (sevenParamsTransform || fifteenParamsTransform) {
- double neg_x = negate(op->parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
- double neg_y = negate(op->parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
- double neg_z = negate(op->parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
- double neg_rx = negate(
- op->parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND));
- double neg_ry = negate(
- op->parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND));
- double neg_rz = negate(
- op->parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND));
- double neg_scaleDiff = negate(op->parameterValueNumeric(
- EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
- common::UnitOfMeasure::PARTS_PER_MILLION));
- auto methodProperties = util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY, methodName);
- int method_epsg_code = method->getEPSGCode();
- if (method_epsg_code) {
- methodProperties
- .set(metadata::Identifier::CODESPACE_KEY,
- metadata::Identifier::EPSG)
- .set(metadata::Identifier::CODE_KEY, method_epsg_code);
- }
- if (fifteenParamsTransform) {
- double neg_rate_x = negate(op->parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION,
- common::UnitOfMeasure::METRE_PER_YEAR));
- double neg_rate_y = negate(op->parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION,
- common::UnitOfMeasure::METRE_PER_YEAR));
- double neg_rate_z = negate(op->parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION,
- common::UnitOfMeasure::METRE_PER_YEAR));
- double neg_rate_rx = negate(op->parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
- double neg_rate_ry = negate(op->parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
- double neg_rate_rz = negate(op->parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
- double neg_rate_scaleDiff = negate(op->parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE,
- common::UnitOfMeasure::PPM_PER_YEAR));
- double referenceEpochYear =
- op->parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH,
- common::UnitOfMeasure::YEAR);
- return util::nn_static_pointer_cast<CoordinateOperation>(
- createFifteenParamsTransform(
- createPropertiesForInverse(op, false, true),
- methodProperties, op->targetCRS(), op->sourceCRS(),
- neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz,
- neg_scaleDiff, neg_rate_x, neg_rate_y, neg_rate_z,
- neg_rate_rx, neg_rate_ry, neg_rate_rz,
- neg_rate_scaleDiff, referenceEpochYear,
- op->coordinateOperationAccuracies()))
- .as_nullable();
- } else {
- return util::nn_static_pointer_cast<CoordinateOperation>(
- createSevenParamsTransform(
- createPropertiesForInverse(op, false, true),
- methodProperties, op->targetCRS(), op->sourceCRS(),
- neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz,
- neg_scaleDiff, op->coordinateOperationAccuracies()))
- .as_nullable();
- }
- }
-
- return nullptr;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-TransformationNNPtr
-Transformation::Private::registerInv(const Transformation *thisIn,
- TransformationNNPtr invTransform) {
- invTransform->d->forwardOperation_ = thisIn->shallowClone().as_nullable();
- invTransform->setHasBallparkTransformation(
- thisIn->hasBallparkTransformation());
- return invTransform;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperationNNPtr Transformation::inverse() const {
- return inverseAsTransformation();
-}
-
-// ---------------------------------------------------------------------------
-
-TransformationNNPtr Transformation::inverseAsTransformation() const {
-
- if (d->forwardOperation_) {
- return NN_NO_CHECK(d->forwardOperation_);
- }
- const auto &l_method = method();
- const auto &methodName = l_method->nameStr();
- const int methodEPSGCode = l_method->getEPSGCode();
- const auto &l_sourceCRS = sourceCRS();
- const auto &l_targetCRS = targetCRS();
-
- // For geocentric translation, the inverse is exactly the negation of
- // the parameters.
- if (ci_find(methodName, "Geocentric translations") != std::string::npos ||
- methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
- double x =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
- double y =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
- double z =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
- auto properties = createPropertiesForInverse(this, false, false);
- return Private::registerInv(
- this, create(properties, l_targetCRS, l_sourceCRS, nullptr,
- createMethodMapNameEPSGCode(
- useOperationMethodEPSGCodeIfPresent(
- properties, methodEPSGCode)),
- VectorOfParameters{
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
- },
- createParams(common::Length(negate(x)),
- common::Length(negate(y)),
- common::Length(negate(z))),
- coordinateOperationAccuracies()));
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
- methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
- double x =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
- double y =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
- double z =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
- double da = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE);
- double df = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE);
-
- if (methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
- return Private::registerInv(
- this,
- createAbridgedMolodensky(
- createPropertiesForInverse(this, false, false), l_targetCRS,
- l_sourceCRS, negate(x), negate(y), negate(z), negate(da),
- negate(df), coordinateOperationAccuracies()));
- } else {
- return Private::registerInv(
- this,
- createMolodensky(createPropertiesForInverse(this, false, false),
- l_targetCRS, l_sourceCRS, negate(x), negate(y),
- negate(z), negate(da), negate(df),
- coordinateOperationAccuracies()));
- }
- }
-
- if (isLongitudeRotation()) {
- auto offset =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
- const common::Angle newOffset(negate(offset.value()), offset.unit());
- return Private::registerInv(
- this, createLongitudeRotation(
- createPropertiesForInverse(this, false, false),
- l_targetCRS, l_sourceCRS, newOffset));
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
- auto offsetLat =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
- const common::Angle newOffsetLat(negate(offsetLat.value()),
- offsetLat.unit());
-
- auto offsetLong =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
- const common::Angle newOffsetLong(negate(offsetLong.value()),
- offsetLong.unit());
-
- return Private::registerInv(
- this, createGeographic2DOffsets(
- createPropertiesForInverse(this, false, false),
- l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
- coordinateOperationAccuracies()));
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
- auto offsetLat =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
- const common::Angle newOffsetLat(negate(offsetLat.value()),
- offsetLat.unit());
-
- auto offsetLong =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
- const common::Angle newOffsetLong(negate(offsetLong.value()),
- offsetLong.unit());
-
- auto offsetHeight =
- parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
- const common::Length newOffsetHeight(negate(offsetHeight.value()),
- offsetHeight.unit());
-
- return Private::registerInv(
- this, createGeographic3DOffsets(
- createPropertiesForInverse(this, false, false),
- l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
- newOffsetHeight, coordinateOperationAccuracies()));
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) {
- auto offsetLat =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
- const common::Angle newOffsetLat(negate(offsetLat.value()),
- offsetLat.unit());
-
- auto offsetLong =
- parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
- const common::Angle newOffsetLong(negate(offsetLong.value()),
- offsetLong.unit());
-
- auto offsetHeight =
- parameterValueMeasure(EPSG_CODE_PARAMETER_GEOID_UNDULATION);
- const common::Length newOffsetHeight(negate(offsetHeight.value()),
- offsetHeight.unit());
-
- return Private::registerInv(
- this, createGeographic2DWithHeightOffsets(
- createPropertiesForInverse(this, false, false),
- l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
- newOffsetHeight, coordinateOperationAccuracies()));
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) {
-
- auto offsetHeight =
- parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
- const common::Length newOffsetHeight(negate(offsetHeight.value()),
- offsetHeight.unit());
-
- return Private::registerInv(
- this,
- createVerticalOffset(createPropertiesForInverse(this, false, false),
- l_targetCRS, l_sourceCRS, newOffsetHeight,
- coordinateOperationAccuracies()));
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
- const double convFactor = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
- return Private::registerInv(
- this, createChangeVerticalUnit(
- createPropertiesForInverse(this, false, false),
- l_targetCRS, l_sourceCRS, common::Scale(1.0 / convFactor),
- coordinateOperationAccuracies()));
- }
-
-#ifdef notdef
- // We don't need that currently, but we might...
- if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
- return Private::registerInv(
- 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())));
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-// ---------------------------------------------------------------------------
-
-InverseTransformation::InverseTransformation(const TransformationNNPtr &forward)
- : Transformation(
- forward->targetCRS(), forward->sourceCRS(),
- forward->interpolationCRS(),
- OperationMethod::create(createPropertiesForInverse(forward->method()),
- forward->method()->parameters()),
- forward->parameterValues(), forward->coordinateOperationAccuracies()),
- InverseCoordinateOperation(forward, true) {
- setPropertiesFromForward();
-}
-
-// ---------------------------------------------------------------------------
-
-InverseTransformation::~InverseTransformation() = default;
-
-// ---------------------------------------------------------------------------
-
-TransformationNNPtr
-InverseTransformation::create(const TransformationNNPtr &forward) {
- auto conv = util::nn_make_shared<InverseTransformation>(forward);
- conv->assignSelf(conv);
- return conv;
-}
-
-// ---------------------------------------------------------------------------
-
-TransformationNNPtr InverseTransformation::inverseAsTransformation() const {
- return NN_NO_CHECK(
- util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_));
-}
-
-// ---------------------------------------------------------------------------
-
-void InverseTransformation::_exportToWKT(io::WKTFormatter *formatter) const {
-
- auto approxInverse = createApproximateInverseIfPossible(
- util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_).get());
- if (approxInverse) {
- approxInverse->_exportToWKT(formatter);
- } else {
- Transformation::_exportToWKT(formatter);
- }
-}
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperationNNPtr InverseTransformation::_shallowClone() const {
- auto op = InverseTransformation::nn_make_shared<InverseTransformation>(
- inverseAsTransformation()->shallowClone());
- op->assignSelf(op);
- op->setCRSs(this, false);
- return util::nn_static_pointer_cast<CoordinateOperation>(op);
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-void Transformation::_exportToWKT(io::WKTFormatter *formatter) const {
- exportTransformationToWKT(formatter);
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-void Transformation::_exportToJSON(
- io::JSONFormatter *formatter) const // throw(FormattingException)
-{
- auto writer = formatter->writer();
- auto objectContext(formatter->MakeObjectContext(
- formatter->abridgedTransformation() ? "AbridgedTransformation"
- : "Transformation",
- !identifiers().empty()));
-
- writer->AddObjKey("name");
- auto l_name = nameStr();
- if (l_name.empty()) {
- writer->Add("unnamed");
- } else {
- writer->Add(l_name);
- }
-
- if (!formatter->abridgedTransformation()) {
- writer->AddObjKey("source_crs");
- formatter->setAllowIDInImmediateChild();
- sourceCRS()->_exportToJSON(formatter);
-
- writer->AddObjKey("target_crs");
- formatter->setAllowIDInImmediateChild();
- targetCRS()->_exportToJSON(formatter);
-
- const auto &l_interpolationCRS = interpolationCRS();
- if (l_interpolationCRS) {
- writer->AddObjKey("interpolation_crs");
- formatter->setAllowIDInImmediateChild();
- l_interpolationCRS->_exportToJSON(formatter);
- }
- }
-
- writer->AddObjKey("method");
- formatter->setOmitTypeInImmediateChild();
- formatter->setAllowIDInImmediateChild();
- method()->_exportToJSON(formatter);
-
- writer->AddObjKey("parameters");
- {
- auto parametersContext(writer->MakeArrayContext(false));
- for (const auto &genOpParamvalue : parameterValues()) {
- formatter->setAllowIDInImmediateChild();
- formatter->setOmitTypeInImmediateChild();
- genOpParamvalue->_exportToJSON(formatter);
- }
- }
-
- if (!formatter->abridgedTransformation()) {
- if (!coordinateOperationAccuracies().empty()) {
- writer->AddObjKey("accuracy");
- writer->Add(coordinateOperationAccuracies()[0]->value());
- }
- }
-
- if (formatter->abridgedTransformation()) {
- if (formatter->outputId()) {
- formatID(formatter);
- }
- } else {
- ObjectUsage::baseExportToJSON(formatter);
- }
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-static void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co,
- io::WKTFormatter *formatter) {
- auto l_sourceCRS = co->sourceCRS();
- assert(l_sourceCRS);
- auto l_targetCRS = co->targetCRS();
- assert(l_targetCRS);
- const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
- const bool canExportCRSId =
- (isWKT2 && formatter->use2019Keywords() &&
- !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId()));
-
- const bool hasDomains = !co->domains().empty();
- if (hasDomains) {
- formatter->pushDisableUsage();
- }
-
- formatter->startNode(io::WKTConstants::SOURCECRS, false);
- if (canExportCRSId && !l_sourceCRS->identifiers().empty()) {
- // fake that top node has no id, so that the sourceCRS id is
- // considered
- formatter->pushHasId(false);
- l_sourceCRS->_exportToWKT(formatter);
- formatter->popHasId();
- } else {
- l_sourceCRS->_exportToWKT(formatter);
- }
- formatter->endNode();
-
- formatter->startNode(io::WKTConstants::TARGETCRS, false);
- if (canExportCRSId && !l_targetCRS->identifiers().empty()) {
- // fake that top node has no id, so that the targetCRS id is
- // considered
- formatter->pushHasId(false);
- l_targetCRS->_exportToWKT(formatter);
- formatter->popHasId();
- } else {
- l_targetCRS->_exportToWKT(formatter);
- }
- formatter->endNode();
-
- if (hasDomains) {
- formatter->popDisableUsage();
- }
-}
-
-// ---------------------------------------------------------------------------
-
-void SingleOperation::exportTransformationToWKT(
- io::WKTFormatter *formatter) const {
- const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
- if (!isWKT2) {
- throw io::FormattingException(
- "Transformation can only be exported to WKT2");
- }
-
- if (formatter->abridgedTransformation()) {
- formatter->startNode(io::WKTConstants::ABRIDGEDTRANSFORMATION,
- !identifiers().empty());
- } else {
- formatter->startNode(io::WKTConstants::COORDINATEOPERATION,
- !identifiers().empty());
- }
-
- formatter->addQuotedString(nameStr());
-
- if (formatter->use2019Keywords()) {
- const auto &version = operationVersion();
- if (version.has_value()) {
- formatter->startNode(io::WKTConstants::VERSION, false);
- formatter->addQuotedString(*version);
- formatter->endNode();
- }
- }
-
- if (!formatter->abridgedTransformation()) {
- exportSourceCRSAndTargetCRSToWKT(this, formatter);
- }
-
- method()->_exportToWKT(formatter);
-
- for (const auto &paramValue : parameterValues()) {
- paramValue->_exportToWKT(formatter, nullptr);
- }
-
- if (!formatter->abridgedTransformation()) {
- if (interpolationCRS()) {
- formatter->startNode(io::WKTConstants::INTERPOLATIONCRS, false);
- interpolationCRS()->_exportToWKT(formatter);
- formatter->endNode();
- }
-
- if (!coordinateOperationAccuracies().empty()) {
- formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false);
- formatter->add(coordinateOperationAccuracies()[0]->value());
- formatter->endNode();
- }
- }
-
- ObjectUsage::baseExportToWKT(formatter);
- formatter->endNode();
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static const std::string nullString;
-
-static const std::string &_getNTv2Filename(const Transformation *op,
- bool allowInverse) {
-
- const auto &l_method = op->method();
- if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV2 ||
- (allowInverse &&
- ci_equal(l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_NTV2))) {
- const auto &fileParameter = op->parameterValue(
- EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
- EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
- return fileParameter->valueFile();
- }
- }
- return nullString;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-//! @cond Doxygen_Suppress
-const std::string &Transformation::getNTv2Filename() const {
-
- return _getNTv2Filename(this, false);
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static const std::string &_getNTv1Filename(const Transformation *op,
- bool allowInverse) {
-
- const auto &l_method = op->method();
- const auto &methodName = l_method->nameStr();
- if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV1 ||
- (allowInverse &&
- ci_equal(methodName, INVERSE_OF + EPSG_NAME_METHOD_NTV1))) {
- const auto &fileParameter = op->parameterValue(
- EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
- EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
- return fileParameter->valueFile();
- }
- }
- return nullString;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static const std::string &_getCTABLE2Filename(const Transformation *op,
- bool allowInverse) {
- const auto &l_method = op->method();
- const auto &methodName = l_method->nameStr();
- if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_CTABLE2) ||
- (allowInverse &&
- ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_CTABLE2))) {
- const auto &fileParameter = op->parameterValue(
- EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
- EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
- return fileParameter->valueFile();
- }
- }
- return nullString;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static const std::string &
-_getHorizontalShiftGTIFFFilename(const Transformation *op, bool allowInverse) {
- const auto &l_method = op->method();
- const auto &methodName = l_method->nameStr();
- if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF) ||
- (allowInverse &&
- ci_equal(methodName,
- INVERSE_OF + PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF))) {
- const auto &fileParameter = op->parameterValue(
- EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
- EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
- return fileParameter->valueFile();
- }
- }
- return nullString;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static const std::string &
-_getGeocentricTranslationFilename(const Transformation *op, bool allowInverse) {
-
- const auto &l_method = op->method();
- const auto &methodName = l_method->nameStr();
- if (l_method->getEPSGCode() ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN ||
- (allowInverse &&
- ci_equal(
- methodName,
- INVERSE_OF +
- EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN))) {
- const auto &fileParameter =
- op->parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE,
- EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
- return fileParameter->valueFile();
- }
- }
- return nullString;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static const std::string &
-_getHeightToGeographic3DFilename(const Transformation *op, bool allowInverse) {
-
- const auto &methodName = op->method()->nameStr();
-
- if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D) ||
- (allowInverse &&
- ci_equal(methodName,
- INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D))) {
- const auto &fileParameter =
- op->parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
- EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
- return fileParameter->valueFile();
- }
- }
- return nullString;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static bool
-isGeographic3DToGravityRelatedHeight(const OperationMethodNNPtr &method,
- bool allowInverse) {
- const auto &methodName = method->nameStr();
- static const char *const methodCodes[] = {
- "1025", // Geographic3D to GravityRelatedHeight (EGM2008)
- "1030", // Geographic3D to GravityRelatedHeight (NZgeoid)
- "1045", // Geographic3D to GravityRelatedHeight (OSGM02-Ire)
- "1047", // Geographic3D to GravityRelatedHeight (Gravsoft)
- "1048", // Geographic3D to GravityRelatedHeight (Ausgeoid v2)
- "1050", // Geographic3D to GravityRelatedHeight (CI)
- "1059", // Geographic3D to GravityRelatedHeight (PNG)
- "1060", // Geographic3D to GravityRelatedHeight (CGG2013)
- "1072", // Geographic3D to GravityRelatedHeight (OSGM15-Ire)
- "1073", // Geographic3D to GravityRelatedHeight (IGN2009)
- "1081", // Geographic3D to GravityRelatedHeight (BEV AT)
- "1083", // Geog3D to Geog2D+Vertical (AUSGeoid v2)
- "9661", // Geographic3D to GravityRelatedHeight (EGM)
- "9662", // Geographic3D to GravityRelatedHeight (Ausgeoid98)
- "9663", // Geographic3D to GravityRelatedHeight (OSGM-GB)
- "9664", // Geographic3D to GravityRelatedHeight (IGN1997)
- "9665", // Geographic3D to GravityRelatedHeight (US .gtx)
- "9635", // Geog3D to Geog2D+GravityRelatedHeight (US .gtx)
- };
-
- if (ci_find(methodName, "Geographic3D to GravityRelatedHeight") == 0) {
- return true;
- }
- if (allowInverse &&
- ci_find(methodName,
- INVERSE_OF + "Geographic3D to GravityRelatedHeight") == 0) {
- return true;
- }
-
- for (const auto &code : methodCodes) {
- for (const auto &idSrc : method->identifiers()) {
- const auto &srcAuthName = *(idSrc->codeSpace());
- const auto &srcCode = idSrc->code();
- if (ci_equal(srcAuthName, "EPSG") && srcCode == code) {
- return true;
- }
- if (allowInverse && ci_equal(srcAuthName, "INVERSE(EPSG)") &&
- srcCode == code) {
- return true;
- }
- }
- }
- return false;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-const std::string &Transformation::getHeightToGeographic3DFilename() const {
-
- const std::string &ret = _getHeightToGeographic3DFilename(this, false);
- if (!ret.empty())
- return ret;
- if (isGeographic3DToGravityRelatedHeight(method(), false)) {
- const auto &fileParameter =
- parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
- EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
- return fileParameter->valueFile();
- }
- }
- return nullString;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static util::PropertyMap
-createSimilarPropertiesMethod(common::IdentifiedObjectNNPtr obj) {
- util::PropertyMap map;
-
- const std::string &forwardName = obj->nameStr();
- if (!forwardName.empty()) {
- map.set(common::IdentifiedObject::NAME_KEY, forwardName);
- }
-
- {
- auto ar = util::ArrayOfBaseObject::create();
- for (const auto &idSrc : obj->identifiers()) {
- const auto &srcAuthName = *(idSrc->codeSpace());
- const auto &srcCode = idSrc->code();
- auto idsProp = util::PropertyMap().set(
- metadata::Identifier::CODESPACE_KEY, srcAuthName);
- ar->add(metadata::Identifier::create(srcCode, idsProp));
- }
- if (!ar->empty()) {
- map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar);
- }
- }
-
- return map;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static util::PropertyMap
-createSimilarPropertiesTransformation(TransformationNNPtr obj) {
- util::PropertyMap map;
-
- // The domain(s) are unchanged
- addDomains(map, obj.get());
-
- const std::string &forwardName = obj->nameStr();
- if (!forwardName.empty()) {
- map.set(common::IdentifiedObject::NAME_KEY, forwardName);
- }
-
- const std::string &remarks = obj->remarks();
- if (!remarks.empty()) {
- map.set(common::IdentifiedObject::REMARKS_KEY, remarks);
- }
-
- addModifiedIdentifier(map, obj.get(), false, true);
-
- return map;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static TransformationNNPtr
-createNTv1(const util::PropertyMap &properties,
- const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
- const std::string &filename,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- return Transformation::create(
- properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV1),
- {OperationParameter::create(
- util::PropertyMap()
- .set(common::IdentifiedObject::NAME_KEY,
- EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)
- .set(metadata::Identifier::CODESPACE_KEY,
- metadata::Identifier::EPSG)
- .set(metadata::Identifier::CODE_KEY,
- EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE))},
- {ParameterValue::createFilename(filename)}, accuracies);
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return an equivalent transformation to the current one, but using
- * PROJ alternative grid names.
- */
-TransformationNNPtr Transformation::substitutePROJAlternativeGridNames(
- io::DatabaseContextNNPtr databaseContext) const {
- auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>(
- shared_from_this().as_nullable()));
-
- const auto &l_method = method();
- const int methodEPSGCode = l_method->getEPSGCode();
-
- std::string projFilename;
- std::string projGridFormat;
- bool inverseDirection = false;
-
- const auto &NTv1Filename = _getNTv1Filename(this, false);
- const auto &NTv2Filename = _getNTv2Filename(this, false);
- std::string lasFilename;
- if (methodEPSGCode == EPSG_CODE_METHOD_NADCON) {
- const auto &latitudeFileParameter =
- parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE,
- EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE);
- const auto &longitudeFileParameter =
- parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE,
- EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE);
- if (latitudeFileParameter &&
- latitudeFileParameter->type() == ParameterValue::Type::FILENAME &&
- longitudeFileParameter &&
- longitudeFileParameter->type() == ParameterValue::Type::FILENAME) {
- lasFilename = latitudeFileParameter->valueFile();
- }
- }
- const auto &horizontalGridName =
- !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty()
- ? NTv2Filename
- : lasFilename;
-
- if (!horizontalGridName.empty() &&
- databaseContext->lookForGridAlternative(horizontalGridName,
- projFilename, projGridFormat,
- inverseDirection)) {
-
- if (horizontalGridName == projFilename) {
- if (inverseDirection) {
- throw util::UnsupportedOperationException(
- "Inverse direction for " + projFilename + " not supported");
- }
- return self;
- }
-
- const auto &l_sourceCRS = sourceCRS();
- const auto &l_targetCRS = targetCRS();
- const auto &l_accuracies = coordinateOperationAccuracies();
- if (projGridFormat == "GTiff") {
- auto parameters =
- std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)};
- auto methodProperties = util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF);
- auto values = std::vector<ParameterValueNNPtr>{
- ParameterValue::createFilename(projFilename)};
- if (inverseDirection) {
- return create(createPropertiesForInverse(
- self.as_nullable().get(), true, false),
- l_targetCRS, l_sourceCRS, nullptr,
- methodProperties, parameters, values,
- l_accuracies)
- ->inverseAsTransformation();
-
- } else {
- return create(createSimilarPropertiesTransformation(self),
- l_sourceCRS, l_targetCRS, nullptr,
- methodProperties, parameters, values,
- l_accuracies);
- }
- } else if (projGridFormat == "NTv1") {
- if (inverseDirection) {
- return createNTv1(createPropertiesForInverse(
- self.as_nullable().get(), true, false),
- l_targetCRS, l_sourceCRS, projFilename,
- l_accuracies)
- ->inverseAsTransformation();
- } else {
- return createNTv1(createSimilarPropertiesTransformation(self),
- l_sourceCRS, l_targetCRS, projFilename,
- l_accuracies);
- }
- } else if (projGridFormat == "NTv2") {
- if (inverseDirection) {
- return createNTv2(createPropertiesForInverse(
- self.as_nullable().get(), true, false),
- l_targetCRS, l_sourceCRS, projFilename,
- l_accuracies)
- ->inverseAsTransformation();
- } else {
- return createNTv2(createSimilarPropertiesTransformation(self),
- l_sourceCRS, l_targetCRS, projFilename,
- l_accuracies);
- }
- } else if (projGridFormat == "CTable2") {
- auto parameters =
- std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)};
- auto methodProperties =
- util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
- PROJ_WKT2_NAME_METHOD_CTABLE2);
- auto values = std::vector<ParameterValueNNPtr>{
- ParameterValue::createFilename(projFilename)};
- if (inverseDirection) {
- return create(createPropertiesForInverse(
- self.as_nullable().get(), true, false),
- l_targetCRS, l_sourceCRS, nullptr,
- methodProperties, parameters, values,
- l_accuracies)
- ->inverseAsTransformation();
-
- } else {
- return create(createSimilarPropertiesTransformation(self),
- l_sourceCRS, l_targetCRS, nullptr,
- methodProperties, parameters, values,
- l_accuracies);
- }
- }
- }
-
- if (isGeographic3DToGravityRelatedHeight(method(), false)) {
- const auto &fileParameter =
- parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
- EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
- auto filename = fileParameter->valueFile();
- if (databaseContext->lookForGridAlternative(
- filename, projFilename, projGridFormat, inverseDirection)) {
-
- if (inverseDirection) {
- throw util::UnsupportedOperationException(
- "Inverse direction for "
- "Geographic3DToGravityRelatedHeight not supported");
- }
-
- if (filename == projFilename) {
- return self;
- }
-
- auto parameters = std::vector<OperationParameterNNPtr>{
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)};
-#ifdef disabled_for_now
- if (inverseDirection) {
- return create(createPropertiesForInverse(
- self.as_nullable().get(), true, false),
- targetCRS(), sourceCRS(), nullptr,
- createSimilarPropertiesMethod(method()),
- parameters, {ParameterValue::createFilename(
- projFilename)},
- coordinateOperationAccuracies())
- ->inverseAsTransformation();
- } else
-#endif
- {
- return create(
- createSimilarPropertiesTransformation(self),
- sourceCRS(), targetCRS(), nullptr,
- createSimilarPropertiesMethod(method()), parameters,
- {ParameterValue::createFilename(projFilename)},
- coordinateOperationAccuracies());
- }
- }
- }
- }
-
- const auto &geocentricTranslationFilename =
- _getGeocentricTranslationFilename(this, false);
- if (!geocentricTranslationFilename.empty()) {
- if (databaseContext->lookForGridAlternative(
- geocentricTranslationFilename, projFilename, projGridFormat,
- inverseDirection)) {
-
- if (inverseDirection) {
- throw util::UnsupportedOperationException(
- "Inverse direction for "
- "GeocentricTranslation not supported");
- }
-
- if (geocentricTranslationFilename == projFilename) {
- return self;
- }
-
- auto parameters =
- std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE)};
- return create(createSimilarPropertiesTransformation(self),
- sourceCRS(), targetCRS(), interpolationCRS(),
- createSimilarPropertiesMethod(method()), parameters,
- {ParameterValue::createFilename(projFilename)},
- coordinateOperationAccuracies());
- }
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON ||
- methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD ||
- methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT ||
- methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) {
- auto fileParameter =
- parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
- EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
-
- auto filename = fileParameter->valueFile();
- if (databaseContext->lookForGridAlternative(
- filename, projFilename, projGridFormat, inverseDirection)) {
-
- if (filename == projFilename) {
- if (inverseDirection) {
- throw util::UnsupportedOperationException(
- "Inverse direction for " + projFilename +
- " not supported");
- }
- return self;
- }
-
- auto parameters = std::vector<OperationParameterNNPtr>{
- createOpParamNameEPSGCode(
- EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)};
- if (inverseDirection) {
- return create(createPropertiesForInverse(
- self.as_nullable().get(), true, false),
- targetCRS(), sourceCRS(), nullptr,
- createSimilarPropertiesMethod(method()),
- parameters, {ParameterValue::createFilename(
- projFilename)},
- coordinateOperationAccuracies())
- ->inverseAsTransformation();
- } else {
- return create(
- createSimilarPropertiesTransformation(self),
- sourceCRS(), targetCRS(), nullptr,
- createSimilarPropertiesMethod(method()), parameters,
- {ParameterValue::createFilename(projFilename)},
- coordinateOperationAccuracies());
- }
- }
- }
- }
-
- return self;
-}
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-static void ThrowExceptionNotGeodeticGeographic(const char *trfrm_name) {
- throw io::FormattingException(concat("Can apply ", std::string(trfrm_name),
- " only to GeodeticCRS / "
- "GeographicCRS"));
-}
-
-// ---------------------------------------------------------------------------
-
-// If crs is a geographic CRS, or a compound CRS of a geographic CRS,
-// or a compoundCRS of a bound CRS of a geographic CRS, return that
-// geographic CRS
-static crs::GeographicCRSPtr
-extractGeographicCRSIfGeographicCRSOrEquivalent(const crs::CRSNNPtr &crs) {
- auto geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(crs);
- if (!geogCRS) {
- auto compoundCRS = util::nn_dynamic_pointer_cast<crs::CompoundCRS>(crs);
- if (compoundCRS) {
- const auto &components = compoundCRS->componentReferenceSystems();
- if (!components.empty()) {
- geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
- components[0]);
- if (!geogCRS) {
- auto boundCRS =
- util::nn_dynamic_pointer_cast<crs::BoundCRS>(
- components[0]);
- if (boundCRS) {
- geogCRS =
- util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
- boundCRS->baseCRS());
- }
- }
- }
- } else {
- auto boundCRS = util::nn_dynamic_pointer_cast<crs::BoundCRS>(crs);
- if (boundCRS) {
- geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
- boundCRS->baseCRS());
- }
- }
- }
- return geogCRS;
-}
-
-// ---------------------------------------------------------------------------
-
-static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter,
- const crs::CRSNNPtr &crs, bool addPushV3,
- const char *trfrm_name) {
- auto sourceCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
- if (sourceCRSGeog) {
- formatter->startInversion();
- sourceCRSGeog->_exportToPROJString(formatter);
- formatter->stopInversion();
- if (util::isOfExactType<crs::DerivedGeographicCRS>(
- *(sourceCRSGeog.get()))) {
- // The export of a DerivedGeographicCRS in non-CRS mode adds
- // unit conversion and axis swapping. We must compensate for that
- formatter->startInversion();
- sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->stopInversion();
- }
-
- if (addPushV3) {
- formatter->addStep("push");
- formatter->addParam("v_3");
- }
-
- formatter->addStep("cart");
- sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
- } else {
- auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
- if (!sourceCRSGeod) {
- ThrowExceptionNotGeodeticGeographic(trfrm_name);
- }
- formatter->startInversion();
- sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter);
- formatter->stopInversion();
- }
-}
-// ---------------------------------------------------------------------------
-
-static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter,
- const crs::CRSNNPtr &crs, bool addPopV3,
- const char *trfrm_name) {
- auto targetCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
- if (targetCRSGeog) {
- formatter->addStep("cart");
- formatter->setCurrentStepInverted(true);
- targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
-
- if (addPopV3) {
- formatter->addStep("pop");
- formatter->addParam("v_3");
- }
- if (util::isOfExactType<crs::DerivedGeographicCRS>(
- *(targetCRSGeog.get()))) {
- // The export of a DerivedGeographicCRS in non-CRS mode adds
- // unit conversion and axis swapping. We must compensate for that
- targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- }
- targetCRSGeog->_exportToPROJString(formatter);
- } else {
- auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
- if (!targetCRSGeod) {
- ThrowExceptionNotGeodeticGeographic(trfrm_name);
- }
- targetCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter);
- }
-}
-
-//! @endcond
-// ---------------------------------------------------------------------------
-
-void Transformation::_exportToPROJString(
- io::PROJStringFormatter *formatter) const // throw(FormattingException)
-{
- if (formatter->convention() ==
- io::PROJStringFormatter::Convention::PROJ_4) {
- throw io::FormattingException(
- "Transformation cannot be exported as a PROJ.4 string");
- }
-
- formatter->setCoordinateOperationOptimizations(true);
-
- bool positionVectorConvention = true;
- bool sevenParamsTransform = false;
- bool threeParamsTransform = false;
- bool fifteenParamsTransform = false;
- const auto &l_method = method();
- const int methodEPSGCode = l_method->getEPSGCode();
- const auto &methodName = l_method->nameStr();
- const auto paramCount = parameterValues().size();
- const bool l_isTimeDependent = isTimeDependent(methodName);
- const bool isPositionVector =
- ci_find(methodName, "Position Vector") != std::string::npos ||
- ci_find(methodName, "PV") != std::string::npos;
- const bool isCoordinateFrame =
- ci_find(methodName, "Coordinate Frame") != std::string::npos ||
- ci_find(methodName, "CF") != std::string::npos;
- if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) ||
- methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
- methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
- methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
- positionVectorConvention = false;
- sevenParamsTransform = true;
- } else if (
- (paramCount == 15 && isCoordinateFrame && l_isTimeDependent) ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) {
- positionVectorConvention = false;
- fifteenParamsTransform = true;
- } else if ((paramCount == 7 && isPositionVector && !l_isTimeDependent) ||
- methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
- sevenParamsTransform = true;
- } else if (
- (paramCount == 15 && isPositionVector && l_isTimeDependent) ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) {
- fifteenParamsTransform = true;
- } else if ((paramCount == 3 &&
- ci_find(methodName, "Geocentric translations") !=
- std::string::npos) ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
- threeParamsTransform = true;
- }
- if (threeParamsTransform || sevenParamsTransform ||
- fifteenParamsTransform) {
- double x =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
- double y =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
- double z =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
-
- auto sourceCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
- auto targetCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
- const bool addPushPopV3 =
- !CoordinateOperation::getPrivate()->use3DHelmert_ &&
- ((sourceCRSGeog &&
- sourceCRSGeog->coordinateSystem()->axisList().size() == 2) ||
- (targetCRSGeog &&
- targetCRSGeog->coordinateSystem()->axisList().size() == 2));
-
- setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3,
- "Helmert");
-
- formatter->addStep("helmert");
- formatter->addParam("x", x);
- formatter->addParam("y", y);
- formatter->addParam("z", z);
- if (sevenParamsTransform || fifteenParamsTransform) {
- double rx =
- parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND);
- double ry =
- parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND);
- double rz =
- parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND);
- double scaleDiff =
- parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
- common::UnitOfMeasure::PARTS_PER_MILLION);
- formatter->addParam("rx", rx);
- formatter->addParam("ry", ry);
- formatter->addParam("rz", rz);
- formatter->addParam("s", scaleDiff);
- if (fifteenParamsTransform) {
- double rate_x = parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION,
- common::UnitOfMeasure::METRE_PER_YEAR);
- double rate_y = parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION,
- common::UnitOfMeasure::METRE_PER_YEAR);
- double rate_z = parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION,
- common::UnitOfMeasure::METRE_PER_YEAR);
- double rate_rx = parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
- double rate_ry = parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
- double rate_rz = parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
- double rate_scaleDiff = parameterValueNumeric(
- EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE,
- common::UnitOfMeasure::PPM_PER_YEAR);
- double referenceEpochYear =
- parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH,
- common::UnitOfMeasure::YEAR);
- formatter->addParam("dx", rate_x);
- formatter->addParam("dy", rate_y);
- formatter->addParam("dz", rate_z);
- formatter->addParam("drx", rate_rx);
- formatter->addParam("dry", rate_ry);
- formatter->addParam("drz", rate_rz);
- formatter->addParam("ds", rate_scaleDiff);
- formatter->addParam("t_epoch", referenceEpochYear);
- }
- if (positionVectorConvention) {
- formatter->addParam("convention", "position_vector");
- } else {
- formatter->addParam("convention", "coordinate_frame");
- }
- }
-
- setupPROJGeodeticTargetCRS(formatter, targetCRS(), addPushPopV3,
- "Helmert");
-
- return;
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC ||
- methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D) {
-
- positionVectorConvention =
- isPositionVector ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D;
-
- double x =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
- double y =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
- double z =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
- double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND);
- double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND);
- double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
- common::UnitOfMeasure::ARC_SECOND);
- double scaleDiff =
- parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
- common::UnitOfMeasure::PARTS_PER_MILLION);
-
- double px = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT);
- double py = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT);
- double pz = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT);
-
- bool addPushPopV3 =
- (methodEPSGCode ==
- EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D ||
- methodEPSGCode ==
- EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D);
-
- setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3,
- "Molodensky-Badekas");
-
- formatter->addStep("molobadekas");
- formatter->addParam("x", x);
- formatter->addParam("y", y);
- formatter->addParam("z", z);
- formatter->addParam("rx", rx);
- formatter->addParam("ry", ry);
- formatter->addParam("rz", rz);
- formatter->addParam("s", scaleDiff);
- formatter->addParam("px", px);
- formatter->addParam("py", py);
- formatter->addParam("pz", pz);
- if (positionVectorConvention) {
- formatter->addParam("convention", "position_vector");
- } else {
- formatter->addParam("convention", "coordinate_frame");
- }
-
- setupPROJGeodeticTargetCRS(formatter, targetCRS(), addPushPopV3,
- "Molodensky-Badekas");
-
- return;
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
- methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
- double x =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
- double y =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
- double z =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
- double da = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE);
- double df = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE);
-
- auto sourceCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
- if (!sourceCRSGeog) {
- throw io::FormattingException(
- "Can apply Molodensky only to GeographicCRS");
- }
-
- auto targetCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
- if (!targetCRSGeog) {
- throw io::FormattingException(
- "Can apply Molodensky only to GeographicCRS");
- }
-
- formatter->startInversion();
- sourceCRSGeog->_exportToPROJString(formatter);
- formatter->stopInversion();
-
- formatter->addStep("molodensky");
- sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
- formatter->addParam("dx", x);
- formatter->addParam("dy", y);
- formatter->addParam("dz", z);
- formatter->addParam("da", da);
- formatter->addParam("df", df);
-
- if (ci_find(methodName, "Abridged") != std::string::npos ||
- methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
- formatter->addParam("abridged");
- }
-
- targetCRSGeog->_exportToPROJString(formatter);
-
- return;
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
- double offsetLat =
- parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
- common::UnitOfMeasure::ARC_SECOND);
- double offsetLong =
- parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
- common::UnitOfMeasure::ARC_SECOND);
-
- auto sourceCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
- if (!sourceCRSGeog) {
- throw io::FormattingException(
- "Can apply Geographic 2D offsets only to GeographicCRS");
- }
-
- auto targetCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
- if (!targetCRSGeog) {
- throw io::FormattingException(
- "Can apply Geographic 2D offsets only to GeographicCRS");
- }
-
- formatter->startInversion();
- sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->stopInversion();
-
- if (offsetLat != 0.0 || offsetLong != 0.0) {
- formatter->addStep("geogoffset");
- formatter->addParam("dlat", offsetLat);
- formatter->addParam("dlon", offsetLong);
- }
-
- targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
-
- return;
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
- double offsetLat =
- parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
- common::UnitOfMeasure::ARC_SECOND);
- double offsetLong =
- parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
- common::UnitOfMeasure::ARC_SECOND);
- double offsetHeight =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
-
- auto sourceCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
- if (!sourceCRSGeog) {
- throw io::FormattingException(
- "Can apply Geographic 3D offsets only to GeographicCRS");
- }
-
- auto targetCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
- if (!targetCRSGeog) {
- throw io::FormattingException(
- "Can apply Geographic 3D offsets only to GeographicCRS");
- }
-
- formatter->startInversion();
- sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->stopInversion();
-
- if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) {
- formatter->addStep("geogoffset");
- formatter->addParam("dlat", offsetLat);
- formatter->addParam("dlon", offsetLong);
- formatter->addParam("dh", offsetHeight);
- }
-
- targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
-
- return;
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) {
- double offsetLat =
- parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
- common::UnitOfMeasure::ARC_SECOND);
- double offsetLong =
- parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
- common::UnitOfMeasure::ARC_SECOND);
- double offsetHeight =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_UNDULATION);
-
- auto sourceCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
- if (!sourceCRSGeog) {
- auto sourceCRSCompound =
- dynamic_cast<const crs::CompoundCRS *>(sourceCRS().get());
- if (sourceCRSCompound) {
- sourceCRSGeog = sourceCRSCompound->extractGeographicCRS().get();
- }
- if (!sourceCRSGeog) {
- throw io::FormattingException("Can apply Geographic 2D with "
- "height offsets only to "
- "GeographicCRS / CompoundCRS");
- }
- }
-
- auto targetCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
- if (!targetCRSGeog) {
- auto targetCRSCompound =
- dynamic_cast<const crs::CompoundCRS *>(targetCRS().get());
- if (targetCRSCompound) {
- targetCRSGeog = targetCRSCompound->extractGeographicCRS().get();
- }
- if (!targetCRSGeog) {
- throw io::FormattingException("Can apply Geographic 2D with "
- "height offsets only to "
- "GeographicCRS / CompoundCRS");
- }
- }
-
- formatter->startInversion();
- sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->stopInversion();
-
- if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) {
- formatter->addStep("geogoffset");
- formatter->addParam("dlat", offsetLat);
- formatter->addParam("dlon", offsetLong);
- formatter->addParam("dh", offsetHeight);
- }
-
- targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
-
- return;
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) {
-
- auto sourceCRSVert =
- dynamic_cast<const crs::VerticalCRS *>(sourceCRS().get());
- if (!sourceCRSVert) {
- throw io::FormattingException(
- "Can apply Vertical offset only to VerticalCRS");
- }
-
- auto targetCRSVert =
- dynamic_cast<const crs::VerticalCRS *>(targetCRS().get());
- if (!targetCRSVert) {
- throw io::FormattingException(
- "Can apply Vertical offset only to VerticalCRS");
- }
-
- auto offsetHeight =
- parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
-
- formatter->startInversion();
- sourceCRSVert->addLinearUnitConvert(formatter);
- formatter->stopInversion();
-
- formatter->addStep("geogoffset");
- formatter->addParam("dh", offsetHeight);
-
- targetCRSVert->addLinearUnitConvert(formatter);
-
- return;
- }
-
- // Substitute grid names with PROJ friendly names.
- if (formatter->databaseContext()) {
- auto alternate = substitutePROJAlternativeGridNames(
- NN_NO_CHECK(formatter->databaseContext()));
- auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>(
- shared_from_this().as_nullable()));
-
- if (alternate != self) {
- alternate->_exportToPROJString(formatter);
- return;
- }
- }
-
- const bool isMethodInverseOf = starts_with(methodName, INVERSE_OF);
-
- const auto &NTv1Filename = _getNTv1Filename(this, true);
- const auto &NTv2Filename = _getNTv2Filename(this, true);
- const auto &CTABLE2Filename = _getCTABLE2Filename(this, true);
- const auto &HorizontalShiftGTIFFFilename =
- _getHorizontalShiftGTIFFFilename(this, true);
- const auto &hGridShiftFilename =
- !HorizontalShiftGTIFFFilename.empty()
- ? HorizontalShiftGTIFFFilename
- : !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty()
- ? NTv2Filename
- : CTABLE2Filename;
- if (!hGridShiftFilename.empty()) {
- auto sourceCRSGeog =
- extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS());
- if (!sourceCRSGeog) {
- throw io::FormattingException(
- concat("Can apply ", methodName, " only to GeographicCRS"));
- }
-
- auto targetCRSGeog =
- extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS());
- if (!targetCRSGeog) {
- throw io::FormattingException(
- concat("Can apply ", methodName, " only to GeographicCRS"));
- }
-
- formatter->startInversion();
- sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->stopInversion();
-
- if (isMethodInverseOf) {
- formatter->startInversion();
- }
- formatter->addStep("hgridshift");
- formatter->addParam("grids", hGridShiftFilename);
- if (isMethodInverseOf) {
- formatter->stopInversion();
- }
-
- targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
-
- return;
- }
-
- const auto &geocentricTranslationFilename =
- _getGeocentricTranslationFilename(this, true);
- if (!geocentricTranslationFilename.empty()) {
- auto sourceCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
- if (!sourceCRSGeog) {
- throw io::FormattingException(
- concat("Can apply ", methodName, " only to GeographicCRS"));
- }
-
- auto targetCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
- if (!targetCRSGeog) {
- throw io::FormattingException(
- concat("Can apply ", methodName, " only to GeographicCRS"));
- }
-
- const auto &interpCRS = interpolationCRS();
- if (!interpCRS) {
- throw io::FormattingException(
- "InterpolationCRS required "
- "for"
- " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN);
- }
- const bool interpIsSrc = interpCRS->_isEquivalentTo(
- sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT);
- const bool interpIsTarget = interpCRS->_isEquivalentTo(
- targetCRS().get(), util::IComparable::Criterion::EQUIVALENT);
- if (!interpIsSrc && !interpIsTarget) {
- throw io::FormattingException(
- "For"
- " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN
- ", interpolation CRS should be the source or target CRS");
- }
-
- formatter->startInversion();
- sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->stopInversion();
-
- if (isMethodInverseOf) {
- formatter->startInversion();
- }
-
- formatter->addStep("push");
- formatter->addParam("v_3");
-
- formatter->addStep("cart");
- sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
-
- formatter->addStep("xyzgridshift");
- formatter->addParam("grids", geocentricTranslationFilename);
- formatter->addParam("grid_ref",
- interpIsTarget ? "output_crs" : "input_crs");
- (interpIsTarget ? targetCRSGeog : sourceCRSGeog)
- ->ellipsoid()
- ->_exportToPROJString(formatter);
-
- formatter->startInversion();
- formatter->addStep("cart");
- targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
- formatter->stopInversion();
-
- formatter->addStep("pop");
- formatter->addParam("v_3");
-
- if (isMethodInverseOf) {
- formatter->stopInversion();
- }
-
- targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
-
- return;
- }
-
- const auto &heightFilename = _getHeightToGeographic3DFilename(this, true);
- if (!heightFilename.empty()) {
- auto targetCRSGeog =
- extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS());
- if (!targetCRSGeog) {
- throw io::FormattingException(
- concat("Can apply ", methodName, " only to GeographicCRS"));
- }
-
- if (!formatter->omitHorizontalConversionInVertTransformation()) {
- formatter->startInversion();
- formatter->pushOmitZUnitConversion();
- targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->popOmitZUnitConversion();
- formatter->stopInversion();
- }
-
- if (isMethodInverseOf) {
- formatter->startInversion();
- }
- formatter->addStep("vgridshift");
- formatter->addParam("grids", heightFilename);
- formatter->addParam("multiplier", 1.0);
- if (isMethodInverseOf) {
- formatter->stopInversion();
- }
-
- if (!formatter->omitHorizontalConversionInVertTransformation()) {
- formatter->pushOmitZUnitConversion();
- targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->popOmitZUnitConversion();
- }
-
- return;
- }
-
- if (isGeographic3DToGravityRelatedHeight(method(), true)) {
- auto fileParameter =
- parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
- EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
- auto filename = fileParameter->valueFile();
-
- auto sourceCRSGeog =
- extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS());
- if (!sourceCRSGeog) {
- throw io::FormattingException(
- concat("Can apply ", methodName, " only to GeographicCRS"));
- }
-
- if (!formatter->omitHorizontalConversionInVertTransformation()) {
- formatter->startInversion();
- formatter->pushOmitZUnitConversion();
- sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->popOmitZUnitConversion();
- formatter->stopInversion();
- }
-
- bool doInversion = isMethodInverseOf;
- // The EPSG Geog3DToHeight is the reverse convention of PROJ !
- doInversion = !doInversion;
- if (doInversion) {
- formatter->startInversion();
- }
- formatter->addStep("vgridshift");
- formatter->addParam("grids", filename);
- formatter->addParam("multiplier", 1.0);
- if (doInversion) {
- formatter->stopInversion();
- }
-
- if (!formatter->omitHorizontalConversionInVertTransformation()) {
- formatter->pushOmitZUnitConversion();
- sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->popOmitZUnitConversion();
- }
-
- return;
- }
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON) {
- auto fileParameter =
- parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
- EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
- formatter->addStep("vgridshift");
- formatter->addParam("grids", fileParameter->valueFile());
- if (fileParameter->valueFile().find(".tif") != std::string::npos) {
- formatter->addParam("multiplier", 1.0);
- } else {
- // The vertcon grids go from NGVD 29 to NAVD 88, with units
- // in millimeter (see
- // https://github.com/OSGeo/proj.4/issues/1071), for gtx files
- formatter->addParam("multiplier", 0.001);
- }
- return;
- }
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD ||
- methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT ||
- methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) {
- auto fileParameter =
- parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
- EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
- if (fileParameter &&
- fileParameter->type() == ParameterValue::Type::FILENAME) {
- formatter->addStep("vgridshift");
- formatter->addParam("grids", fileParameter->valueFile());
- formatter->addParam("multiplier", 1.0);
- return;
- }
- }
-
- if (isLongitudeRotation()) {
- double offsetDeg =
- parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
- common::UnitOfMeasure::DEGREE);
-
- auto sourceCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
- if (!sourceCRSGeog) {
- throw io::FormattingException(
- concat("Can apply ", methodName, " only to GeographicCRS"));
- }
-
- auto targetCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
- if (!targetCRSGeog) {
- throw io::FormattingException(
- concat("Can apply ", methodName + " only to GeographicCRS"));
- }
-
- if (!sourceCRSGeog->ellipsoid()->_isEquivalentTo(
- targetCRSGeog->ellipsoid().get(),
- util::IComparable::Criterion::EQUIVALENT)) {
- // This is arguable if we should check this...
- throw io::FormattingException("Can apply Longitude rotation "
- "only to SRS with same "
- "ellipsoid");
- }
-
- formatter->startInversion();
- sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->stopInversion();
-
- bool done = false;
- if (offsetDeg != 0.0) {
- // Optimization: as we are doing nominally a +step=inv,
- // if the negation of the offset value is a well-known name,
- // then use forward case with this name.
- auto projPMName = datum::PrimeMeridian::getPROJStringWellKnownName(
- common::Angle(-offsetDeg));
- if (!projPMName.empty()) {
- done = true;
- formatter->addStep("longlat");
- sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
- formatter->addParam("pm", projPMName);
- }
- }
- if (!done) {
- // To actually add the offset, we must use the reverse longlat
- // operation.
- formatter->startInversion();
- formatter->addStep("longlat");
- sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
- datum::PrimeMeridian::create(util::PropertyMap(),
- common::Angle(offsetDeg))
- ->_exportToPROJString(formatter);
- formatter->stopInversion();
- }
-
- targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
-
- return;
- }
-
- if (exportToPROJStringGeneric(formatter)) {
- return;
- }
-
- throw io::FormattingException("Unimplemented");
-}
-
-// ---------------------------------------------------------------------------
-
-bool SingleOperation::exportToPROJStringGeneric(
- io::PROJStringFormatter *formatter) const {
- const int methodEPSGCode = method()->getEPSGCode();
-
- if (methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION) {
- const double A0 = parameterValueMeasure(EPSG_CODE_PARAMETER_A0).value();
- const double A1 = parameterValueMeasure(EPSG_CODE_PARAMETER_A1).value();
- const double A2 = parameterValueMeasure(EPSG_CODE_PARAMETER_A2).value();
- const double B0 = parameterValueMeasure(EPSG_CODE_PARAMETER_B0).value();
- const double B1 = parameterValueMeasure(EPSG_CODE_PARAMETER_B1).value();
- const double B2 = parameterValueMeasure(EPSG_CODE_PARAMETER_B2).value();
-
- // Do not mess with axis unit and order for that transformation
-
- formatter->addStep("affine");
- formatter->addParam("xoff", A0);
- formatter->addParam("s11", A1);
- formatter->addParam("s12", A2);
- formatter->addParam("yoff", B0);
- formatter->addParam("s21", B1);
- formatter->addParam("s22", B2);
-
- return true;
- }
-
- if (isAxisOrderReversal(methodEPSGCode)) {
- formatter->addStep("axisswap");
- formatter->addParam("order", "2,1");
- auto sourceCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
- auto targetCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
- if (sourceCRSGeog && targetCRSGeog) {
- const auto &unitSrc =
- sourceCRSGeog->coordinateSystem()->axisList()[0]->unit();
- const auto &unitDst =
- targetCRSGeog->coordinateSystem()->axisList()[0]->unit();
- if (!unitSrc._isEquivalentTo(
- unitDst, util::IComparable::Criterion::EQUIVALENT)) {
- formatter->addStep("unitconvert");
- auto projUnit = unitSrc.exportToPROJString();
- if (projUnit.empty()) {
- formatter->addParam("xy_in", unitSrc.conversionToSI());
- } else {
- formatter->addParam("xy_in", projUnit);
- }
- projUnit = unitDst.exportToPROJString();
- if (projUnit.empty()) {
- formatter->addParam("xy_out", unitDst.conversionToSI());
- } else {
- formatter->addParam("xy_out", projUnit);
- }
- }
- }
- return true;
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) {
-
- auto sourceCRSGeod =
- dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get());
- auto targetCRSGeod =
- dynamic_cast<const crs::GeodeticCRS *>(targetCRS().get());
- if (sourceCRSGeod && targetCRSGeod) {
- auto sourceCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod);
- auto targetCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod);
- bool isSrcGeocentric = sourceCRSGeod->isGeocentric();
- bool isSrcGeographic = sourceCRSGeog != nullptr;
- bool isTargetGeocentric = targetCRSGeod->isGeocentric();
- bool isTargetGeographic = targetCRSGeog != nullptr;
- if ((isSrcGeocentric && isTargetGeographic) ||
- (isSrcGeographic && isTargetGeocentric)) {
-
- formatter->startInversion();
- sourceCRSGeod->_exportToPROJString(formatter);
- formatter->stopInversion();
-
- targetCRSGeod->_exportToPROJString(formatter);
-
- return true;
- }
- }
-
- throw io::FormattingException("Invalid nature of source and/or "
- "targetCRS for Geographic/Geocentric "
- "conversion");
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
- double convFactor = parameterValueNumericAsSI(
- EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
- auto uom = common::UnitOfMeasure(std::string(), convFactor,
- common::UnitOfMeasure::Type::LINEAR)
- .exportToPROJString();
- auto reverse_uom =
- common::UnitOfMeasure(std::string(), 1.0 / convFactor,
- common::UnitOfMeasure::Type::LINEAR)
- .exportToPROJString();
- if (uom == "m") {
- // do nothing
- } else if (!uom.empty()) {
- formatter->addStep("unitconvert");
- formatter->addParam("z_in", uom);
- formatter->addParam("z_out", "m");
- } else if (!reverse_uom.empty()) {
- formatter->addStep("unitconvert");
- formatter->addParam("z_in", "m");
- formatter->addParam("z_out", reverse_uom);
- } else {
- formatter->addStep("affine");
- formatter->addParam("s33", convFactor);
- }
- return true;
- }
-
- if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
- formatter->addStep("axisswap");
- formatter->addParam("order", "1,2,-3");
- return true;
- }
-
- return false;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-PointMotionOperation::~PointMotionOperation() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct ConcatenatedOperation::Private {
- std::vector<CoordinateOperationNNPtr> operations_{};
- bool computedName_ = false;
-
- explicit Private(const std::vector<CoordinateOperationNNPtr> &operationsIn)
- : operations_(operationsIn) {}
- Private(const Private &) = default;
-};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-ConcatenatedOperation::~ConcatenatedOperation() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-ConcatenatedOperation::ConcatenatedOperation(const ConcatenatedOperation &other)
- : CoordinateOperation(other),
- d(internal::make_unique<Private>(*(other.d))) {}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-ConcatenatedOperation::ConcatenatedOperation(
- const std::vector<CoordinateOperationNNPtr> &operationsIn)
- : CoordinateOperation(), d(internal::make_unique<Private>(operationsIn)) {}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the operation steps of the concatenated operation.
- *
- * @return the operation steps.
- */
-const std::vector<CoordinateOperationNNPtr> &
-ConcatenatedOperation::operations() const {
- return d->operations_;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static bool compareStepCRS(const crs::CRS *a, const crs::CRS *b) {
- const auto &aIds = a->identifiers();
- const auto &bIds = b->identifiers();
- if (aIds.size() == 1 && bIds.size() == 1 &&
- aIds[0]->code() == bIds[0]->code() &&
- *aIds[0]->codeSpace() == *bIds[0]->codeSpace()) {
- return true;
- }
- return a->_isEquivalentTo(b, util::IComparable::Criterion::EQUIVALENT);
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a ConcatenatedOperation
- *
- * @param properties See \ref general_properties. At minimum the name should
- * be
- * defined.
- * @param operationsIn Vector of the CoordinateOperation steps.
- * @param accuracies Vector of positional accuracy (might be empty).
- * @return new Transformation.
- * @throws InvalidOperation
- */
-ConcatenatedOperationNNPtr ConcatenatedOperation::create(
- const util::PropertyMap &properties,
- const std::vector<CoordinateOperationNNPtr> &operationsIn,
- const std::vector<metadata::PositionalAccuracyNNPtr>
- &accuracies) // throw InvalidOperation
-{
- if (operationsIn.size() < 2) {
- throw InvalidOperation(
- "ConcatenatedOperation must have at least 2 operations");
- }
- crs::CRSPtr lastTargetCRS;
-
- crs::CRSPtr interpolationCRS;
- bool interpolationCRSValid = true;
- for (size_t i = 0; i < operationsIn.size(); i++) {
- auto l_sourceCRS = operationsIn[i]->sourceCRS();
- auto l_targetCRS = operationsIn[i]->targetCRS();
-
- if (interpolationCRSValid) {
- auto subOpInterpCRS = operationsIn[i]->interpolationCRS();
- if (interpolationCRS == nullptr)
- interpolationCRS = subOpInterpCRS;
- else if (subOpInterpCRS == nullptr ||
- !(subOpInterpCRS->isEquivalentTo(
- interpolationCRS.get(),
- util::IComparable::Criterion::EQUIVALENT))) {
- interpolationCRS = nullptr;
- interpolationCRSValid = false;
- }
- }
-
- if (l_sourceCRS == nullptr || l_targetCRS == nullptr) {
- throw InvalidOperation("At least one of the operation lacks a "
- "source and/or target CRS");
- }
- if (i >= 1) {
- if (!compareStepCRS(l_sourceCRS.get(), lastTargetCRS.get())) {
-#ifdef DEBUG_CONCATENATED_OPERATION
- std::cerr << "Step " << i - 1 << ": "
- << operationsIn[i - 1]->nameStr() << std::endl;
- std::cerr << "Step " << i << ": " << operationsIn[i]->nameStr()
- << std::endl;
- {
- auto f(io::WKTFormatter::create(
- io::WKTFormatter::Convention::WKT2_2019));
- std::cerr << "Source CRS of step " << i << ":" << std::endl;
- std::cerr << l_sourceCRS->exportToWKT(f.get()) << std::endl;
- }
- {
- auto f(io::WKTFormatter::create(
- io::WKTFormatter::Convention::WKT2_2019));
- std::cerr << "Target CRS of step " << i - 1 << ":"
- << std::endl;
- std::cerr << lastTargetCRS->exportToWKT(f.get())
- << std::endl;
- }
-#endif
- throw InvalidOperation(
- "Inconsistent chaining of CRS in operations");
- }
- }
- lastTargetCRS = l_targetCRS;
- }
- auto op = ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>(
- operationsIn);
- op->assignSelf(op);
- op->setProperties(properties);
- op->setCRSs(NN_NO_CHECK(operationsIn[0]->sourceCRS()),
- NN_NO_CHECK(operationsIn.back()->targetCRS()),
- interpolationCRS);
- op->setAccuracies(accuracies);
-#ifdef DEBUG_CONCATENATED_OPERATION
- {
- auto f(
- io::WKTFormatter::create(io::WKTFormatter::Convention::WKT2_2019));
- std::cerr << "ConcatenatedOperation::create()" << std::endl;
- std::cerr << op->exportToWKT(f.get()) << std::endl;
- }
-#endif
- return op;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-// ---------------------------------------------------------------------------
-
-void ConcatenatedOperation::fixStepsDirection(
- const crs::CRSNNPtr &concatOpSourceCRS,
- const crs::CRSNNPtr &concatOpTargetCRS,
- std::vector<CoordinateOperationNNPtr> &operationsInOut) {
-
- // Set of heuristics to assign CRS to steps, and possibly reverse them.
-
- const auto isGeographic = [](const crs::CRS *crs) -> bool {
- return dynamic_cast<const crs::GeographicCRS *>(crs) != nullptr;
- };
-
- const auto isGeocentric = [](const crs::CRS *crs) -> bool {
- auto geodCRS = dynamic_cast<const crs::GeodeticCRS *>(crs);
- if (geodCRS && geodCRS->coordinateSystem()->axisList().size() == 3)
- return true;
- return false;
- };
-
- for (size_t i = 0; i < operationsInOut.size(); ++i) {
- auto &op = operationsInOut[i];
- auto l_sourceCRS = op->sourceCRS();
- auto l_targetCRS = op->targetCRS();
- auto conv = dynamic_cast<const Conversion *>(op.get());
- if (conv && i == 0 && !l_sourceCRS && !l_targetCRS) {
- auto derivedCRS =
- dynamic_cast<const crs::DerivedCRS *>(concatOpSourceCRS.get());
- if (derivedCRS) {
- if (i + 1 < operationsInOut.size()) {
- // use the sourceCRS of the next operation as our target CRS
- l_targetCRS = operationsInOut[i + 1]->sourceCRS();
- // except if it looks like the next operation should
- // actually be reversed !!!
- if (l_targetCRS &&
- !compareStepCRS(l_targetCRS.get(),
- derivedCRS->baseCRS().get()) &&
- operationsInOut[i + 1]->targetCRS() &&
- compareStepCRS(
- operationsInOut[i + 1]->targetCRS().get(),
- derivedCRS->baseCRS().get())) {
- l_targetCRS = operationsInOut[i + 1]->targetCRS();
- }
- }
- if (!l_targetCRS) {
- l_targetCRS = derivedCRS->baseCRS().as_nullable();
- }
- auto invConv =
- util::nn_dynamic_pointer_cast<InverseConversion>(op);
- auto nn_targetCRS = NN_NO_CHECK(l_targetCRS);
- if (invConv) {
- invConv->inverse()->setCRSs(nn_targetCRS, concatOpSourceCRS,
- nullptr);
- op->setCRSs(concatOpSourceCRS, nn_targetCRS, nullptr);
- } else {
- op->setCRSs(nn_targetCRS, concatOpSourceCRS, nullptr);
- op = op->inverse();
- }
- } else if (i + 1 < operationsInOut.size()) {
- /* coverity[copy_paste_error] */
- l_targetCRS = operationsInOut[i + 1]->sourceCRS();
- if (l_targetCRS) {
- op->setCRSs(concatOpSourceCRS, NN_NO_CHECK(l_targetCRS),
- nullptr);
- }
- }
- } else if (conv && i + 1 == operationsInOut.size() && !l_sourceCRS &&
- !l_targetCRS) {
- auto derivedCRS =
- dynamic_cast<const crs::DerivedCRS *>(concatOpTargetCRS.get());
- if (derivedCRS) {
- if (i >= 1) {
- // use the sourceCRS of the previous operation as our source
- // CRS
- l_sourceCRS = operationsInOut[i - 1]->targetCRS();
- // except if it looks like the previous operation should
- // actually be reversed !!!
- if (l_sourceCRS &&
- !compareStepCRS(l_sourceCRS.get(),
- derivedCRS->baseCRS().get()) &&
- operationsInOut[i - 1]->sourceCRS() &&
- compareStepCRS(
- operationsInOut[i - 1]->sourceCRS().get(),
- derivedCRS->baseCRS().get())) {
- l_targetCRS = operationsInOut[i - 1]->sourceCRS();
- }
- }
- if (!l_sourceCRS) {
- l_sourceCRS = derivedCRS->baseCRS().as_nullable();
- }
- op->setCRSs(NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS,
- nullptr);
- } else if (i >= 1) {
- l_sourceCRS = operationsInOut[i - 1]->targetCRS();
- if (l_sourceCRS) {
- derivedCRS = dynamic_cast<const crs::DerivedCRS *>(
- l_sourceCRS.get());
- if (conv->isEquivalentTo(
- derivedCRS->derivingConversion().get(),
- util::IComparable::Criterion::EQUIVALENT)) {
- op->setCRSs(concatOpTargetCRS, NN_NO_CHECK(l_sourceCRS),
- nullptr);
- op = op->inverse();
- }
- op->setCRSs(NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS,
- nullptr);
- }
- }
- } else if (conv && i > 0 && i < operationsInOut.size() - 1) {
- // For an intermediate conversion, use the target CRS of the
- // previous step and the source CRS of the next step
- l_sourceCRS = operationsInOut[i - 1]->targetCRS();
- l_targetCRS = operationsInOut[i + 1]->sourceCRS();
- if (l_sourceCRS && l_targetCRS) {
- op->setCRSs(NN_NO_CHECK(l_sourceCRS), NN_NO_CHECK(l_targetCRS),
- nullptr);
- }
- } else if (!conv && l_sourceCRS && l_targetCRS) {
-
- // Transformations might be mentioned in their forward directions,
- // whereas we should instead use the reverse path.
- auto prevOpTarget = (i == 0) ? concatOpSourceCRS.as_nullable()
- : operationsInOut[i - 1]->targetCRS();
- if (compareStepCRS(l_sourceCRS.get(), prevOpTarget.get())) {
- // do nothing
- } else if (compareStepCRS(l_targetCRS.get(), prevOpTarget.get())) {
- op = op->inverse();
- }
- // Below is needed for EPSG:9103 which chains NAD83(2011) geographic
- // 2D with NAD83(2011) geocentric
- else if (l_sourceCRS->nameStr() == prevOpTarget->nameStr() &&
- ((isGeographic(l_sourceCRS.get()) &&
- isGeocentric(prevOpTarget.get())) ||
- (isGeocentric(l_sourceCRS.get()) &&
- isGeographic(prevOpTarget.get())))) {
- auto newOp(Conversion::createGeographicGeocentric(
- NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_sourceCRS)));
- operationsInOut.insert(operationsInOut.begin() + i, newOp);
- } else if (l_targetCRS->nameStr() == prevOpTarget->nameStr() &&
- ((isGeographic(l_targetCRS.get()) &&
- isGeocentric(prevOpTarget.get())) ||
- (isGeocentric(l_targetCRS.get()) &&
- isGeographic(prevOpTarget.get())))) {
- auto newOp(Conversion::createGeographicGeocentric(
- NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_targetCRS)));
- operationsInOut.insert(operationsInOut.begin() + i, newOp);
- }
- }
- }
-
- if (!operationsInOut.empty()) {
- auto l_sourceCRS = operationsInOut.front()->sourceCRS();
- if (l_sourceCRS &&
- !compareStepCRS(l_sourceCRS.get(), concatOpSourceCRS.get())) {
- throw InvalidOperation("The source CRS of the first step of "
- "concatenated operation is not the same "
- "as the source CRS of the concatenated "
- "operation itself");
- }
-
- auto l_targetCRS = operationsInOut.back()->targetCRS();
- if (l_targetCRS &&
- !compareStepCRS(l_targetCRS.get(), concatOpTargetCRS.get())) {
- if (l_targetCRS->nameStr() == concatOpTargetCRS->nameStr() &&
- ((isGeographic(l_targetCRS.get()) &&
- isGeocentric(concatOpTargetCRS.get())) ||
- (isGeocentric(l_targetCRS.get()) &&
- isGeographic(concatOpTargetCRS.get())))) {
- auto newOp(Conversion::createGeographicGeocentric(
- NN_NO_CHECK(l_targetCRS), concatOpTargetCRS));
- operationsInOut.push_back(newOp);
- } else {
- throw InvalidOperation("The target CRS of the last step of "
- "concatenated operation is not the same "
- "as the target CRS of the concatenated "
- "operation itself");
- }
- }
- }
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static std::string computeConcatenatedName(
- const std::vector<CoordinateOperationNNPtr> &flattenOps) {
- std::string name;
- for (const auto &subOp : flattenOps) {
- if (!name.empty()) {
- name += " + ";
- }
- const auto &l_name = subOp->nameStr();
- if (l_name.empty()) {
- name += "unnamed";
- } else {
- name += l_name;
- }
- }
- return name;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a ConcatenatedOperation, or return a single
- * coordinate
- * operation.
- *
- * This computes its accuracy from the sum of its member operations, its
- * extent
- *
- * @param operationsIn Vector of the CoordinateOperation steps.
- * @param checkExtent Whether we should check the non-emptyness of the
- * intersection
- * of the extents of the operations
- * @throws InvalidOperation
- */
-CoordinateOperationNNPtr ConcatenatedOperation::createComputeMetadata(
- const std::vector<CoordinateOperationNNPtr> &operationsIn,
- bool checkExtent) // throw InvalidOperation
-{
- util::PropertyMap properties;
-
- if (operationsIn.size() == 1) {
- return operationsIn[0];
- }
-
- std::vector<CoordinateOperationNNPtr> flattenOps;
- bool hasBallparkTransformation = false;
- for (const auto &subOp : operationsIn) {
- hasBallparkTransformation |= subOp->hasBallparkTransformation();
- auto subOpConcat =
- dynamic_cast<const ConcatenatedOperation *>(subOp.get());
- if (subOpConcat) {
- auto subOps = subOpConcat->operations();
- for (const auto &subSubOp : subOps) {
- flattenOps.emplace_back(subSubOp);
- }
- } else {
- flattenOps.emplace_back(subOp);
- }
- }
- if (flattenOps.size() == 1) {
- return flattenOps[0];
- }
-
- properties.set(common::IdentifiedObject::NAME_KEY,
- computeConcatenatedName(flattenOps));
-
- bool emptyIntersection = false;
- auto extent = getExtent(flattenOps, false, emptyIntersection);
- if (checkExtent && emptyIntersection) {
- std::string msg(
- "empty intersection of area of validity of concatenated "
- "operations");
- throw InvalidOperationEmptyIntersection(msg);
- }
- if (extent) {
- properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- NN_NO_CHECK(extent));
- }
-
- std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
- const double accuracy = getAccuracy(flattenOps);
- if (accuracy >= 0.0) {
- accuracies.emplace_back(
- metadata::PositionalAccuracy::create(toString(accuracy)));
- }
-
- auto op = create(properties, flattenOps, accuracies);
- op->setHasBallparkTransformation(hasBallparkTransformation);
- op->d->computedName_ = true;
- return op;
-}
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperationNNPtr ConcatenatedOperation::inverse() const {
- std::vector<CoordinateOperationNNPtr> inversedOperations;
- auto l_operations = operations();
- inversedOperations.reserve(l_operations.size());
- for (const auto &operation : l_operations) {
- inversedOperations.emplace_back(operation->inverse());
- }
- std::reverse(inversedOperations.begin(), inversedOperations.end());
-
- auto properties = createPropertiesForInverse(this, false, false);
- if (d->computedName_) {
- properties.set(common::IdentifiedObject::NAME_KEY,
- computeConcatenatedName(inversedOperations));
- }
-
- auto op =
- create(properties, inversedOperations, coordinateOperationAccuracies());
- op->d->computedName_ = d->computedName_;
- op->setHasBallparkTransformation(hasBallparkTransformation());
- return op;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-void ConcatenatedOperation::_exportToWKT(io::WKTFormatter *formatter) const {
- const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
- if (!isWKT2 || !formatter->use2019Keywords()) {
- throw io::FormattingException(
- "Transformation can only be exported to WKT2:2019");
- }
-
- formatter->startNode(io::WKTConstants::CONCATENATEDOPERATION,
- !identifiers().empty());
- formatter->addQuotedString(nameStr());
-
- if (formatter->use2019Keywords()) {
- const auto &version = operationVersion();
- if (version.has_value()) {
- formatter->startNode(io::WKTConstants::VERSION, false);
- formatter->addQuotedString(*version);
- formatter->endNode();
- }
- }
-
- exportSourceCRSAndTargetCRSToWKT(this, formatter);
-
- const bool canExportOperationId =
- !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId());
-
- const bool hasDomains = !domains().empty();
- if (hasDomains) {
- formatter->pushDisableUsage();
- }
-
- for (const auto &operation : operations()) {
- formatter->startNode(io::WKTConstants::STEP, false);
- if (canExportOperationId && !operation->identifiers().empty()) {
- // fake that top node has no id, so that the operation id is
- // considered
- formatter->pushHasId(false);
- operation->_exportToWKT(formatter);
- formatter->popHasId();
- } else {
- operation->_exportToWKT(formatter);
- }
- formatter->endNode();
- }
-
- if (hasDomains) {
- formatter->popDisableUsage();
- }
-
- ObjectUsage::baseExportToWKT(formatter);
- formatter->endNode();
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-void ConcatenatedOperation::_exportToJSON(
- io::JSONFormatter *formatter) const // throw(FormattingException)
-{
- auto writer = formatter->writer();
- auto objectContext(formatter->MakeObjectContext("ConcatenatedOperation",
- !identifiers().empty()));
-
- writer->AddObjKey("name");
- auto l_name = nameStr();
- if (l_name.empty()) {
- writer->Add("unnamed");
- } else {
- writer->Add(l_name);
- }
-
- writer->AddObjKey("source_crs");
- formatter->setAllowIDInImmediateChild();
- sourceCRS()->_exportToJSON(formatter);
-
- writer->AddObjKey("target_crs");
- formatter->setAllowIDInImmediateChild();
- targetCRS()->_exportToJSON(formatter);
-
- writer->AddObjKey("steps");
- {
- auto parametersContext(writer->MakeArrayContext(false));
- for (const auto &operation : operations()) {
- formatter->setAllowIDInImmediateChild();
- operation->_exportToJSON(formatter);
- }
- }
-
- ObjectUsage::baseExportToJSON(formatter);
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-CoordinateOperationNNPtr ConcatenatedOperation::_shallowClone() const {
- auto op =
- ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>(*this);
- std::vector<CoordinateOperationNNPtr> ops;
- for (const auto &subOp : d->operations_) {
- ops.emplace_back(subOp->shallowClone());
- }
- op->d->operations_ = ops;
- op->assignSelf(op);
- op->setCRSs(this, false);
- return util::nn_static_pointer_cast<CoordinateOperation>(op);
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-void ConcatenatedOperation::_exportToPROJString(
- io::PROJStringFormatter *formatter) const // throw(FormattingException)
-{
- for (const auto &operation : operations()) {
- operation->_exportToPROJString(formatter);
- }
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-bool ConcatenatedOperation::_isEquivalentTo(
- const util::IComparable *other, util::IComparable::Criterion criterion,
- const io::DatabaseContextPtr &dbContext) const {
- auto otherCO = dynamic_cast<const ConcatenatedOperation *>(other);
- if (otherCO == nullptr ||
- (criterion == util::IComparable::Criterion::STRICT &&
- !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) {
- return false;
- }
- const auto &steps = operations();
- const auto &otherSteps = otherCO->operations();
- if (steps.size() != otherSteps.size()) {
- return false;
- }
- for (size_t i = 0; i < steps.size(); i++) {
- if (!steps[i]->_isEquivalentTo(otherSteps[i].get(), criterion,
- dbContext)) {
- return false;
- }
- }
- return true;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-std::set<GridDescription> ConcatenatedOperation::gridsNeeded(
- const io::DatabaseContextPtr &databaseContext,
- bool considerKnownGridsAsAvailable) const {
- std::set<GridDescription> res;
- for (const auto &operation : operations()) {
- const auto l_gridsNeeded = operation->gridsNeeded(
- databaseContext, considerKnownGridsAsAvailable);
- for (const auto &gridDesc : l_gridsNeeded) {
- res.insert(gridDesc);
- }
- }
- return res;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct CoordinateOperationContext::Private {
- io::AuthorityFactoryPtr authorityFactory_{};
- metadata::ExtentPtr extent_{};
- double accuracy_ = 0.0;
- SourceTargetCRSExtentUse sourceAndTargetCRSExtentUse_ =
- CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST;
- SpatialCriterion spatialCriterion_ =
- CoordinateOperationContext::SpatialCriterion::STRICT_CONTAINMENT;
- bool usePROJNames_ = true;
- GridAvailabilityUse gridAvailabilityUse_ =
- GridAvailabilityUse::USE_FOR_SORTING;
- IntermediateCRSUse allowUseIntermediateCRS_ = CoordinateOperationContext::
- IntermediateCRSUse::IF_NO_DIRECT_TRANSFORMATION;
- std::vector<std::pair<std::string, std::string>>
- intermediateCRSAuthCodes_{};
- bool discardSuperseded_ = true;
- bool allowBallpark_ = true;
-};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-CoordinateOperationContext::~CoordinateOperationContext() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperationContext::CoordinateOperationContext()
- : d(internal::make_unique<Private>()) {}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the authority factory, or null */
-const io::AuthorityFactoryPtr &
-CoordinateOperationContext::getAuthorityFactory() const {
- return d->authorityFactory_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the desired area of interest, or null */
-const metadata::ExtentPtr &
-CoordinateOperationContext::getAreaOfInterest() const {
- return d->extent_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Set the desired area of interest, or null */
-void CoordinateOperationContext::setAreaOfInterest(
- const metadata::ExtentPtr &extent) {
- d->extent_ = extent;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the desired accuracy (in metre), or 0 */
-double CoordinateOperationContext::getDesiredAccuracy() const {
- return d->accuracy_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Set the desired accuracy (in metre), or 0 */
-void CoordinateOperationContext::setDesiredAccuracy(double accuracy) {
- d->accuracy_ = accuracy;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return whether ballpark transformations are allowed */
-bool CoordinateOperationContext::getAllowBallparkTransformations() const {
- return d->allowBallpark_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Set whether ballpark transformations are allowed */
-void CoordinateOperationContext::setAllowBallparkTransformations(bool allow) {
- d->allowBallpark_ = allow;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Set how source and target CRS extent should be used
- * when considering if a transformation can be used (only takes effect if
- * no area of interest is explicitly defined).
- *
- * The default is
- * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST.
- */
-void CoordinateOperationContext::setSourceAndTargetCRSExtentUse(
- SourceTargetCRSExtentUse use) {
- d->sourceAndTargetCRSExtentUse_ = use;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return how source and target CRS extent should be used
- * when considering if a transformation can be used (only takes effect if
- * no area of interest is explicitly defined).
- *
- * The default is
- * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST.
- */
-CoordinateOperationContext::SourceTargetCRSExtentUse
-CoordinateOperationContext::getSourceAndTargetCRSExtentUse() const {
- return d->sourceAndTargetCRSExtentUse_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Set the spatial criterion to use when comparing the area of
- * validity
- * of coordinate operations with the area of interest / area of validity of
- * source and target CRS.
- *
- * The default is STRICT_CONTAINMENT.
- */
-void CoordinateOperationContext::setSpatialCriterion(
- SpatialCriterion criterion) {
- d->spatialCriterion_ = criterion;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the spatial criterion to use when comparing the area of
- * validity
- * of coordinate operations with the area of interest / area of validity of
- * source and target CRS.
- *
- * The default is STRICT_CONTAINMENT.
- */
-CoordinateOperationContext::SpatialCriterion
-CoordinateOperationContext::getSpatialCriterion() const {
- return d->spatialCriterion_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Set whether PROJ alternative grid names should be substituted to
- * the official authority names.
- *
- * This only has effect is an authority factory with a non-null database context
- * has been attached to this context.
- *
- * If set to false, it is still possible to
- * obtain later the substitution by using io::PROJStringFormatter::create()
- * with a non-null database context.
- *
- * The default is true.
- */
-void CoordinateOperationContext::setUsePROJAlternativeGridNames(
- bool usePROJNames) {
- d->usePROJNames_ = usePROJNames;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return whether PROJ alternative grid names should be substituted to
- * the official authority names.
- *
- * The default is true.
- */
-bool CoordinateOperationContext::getUsePROJAlternativeGridNames() const {
- return d->usePROJNames_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return whether transformations that are superseded (but not
- * deprecated)
- * should be discarded.
- *
- * The default is true.
- */
-bool CoordinateOperationContext::getDiscardSuperseded() const {
- return d->discardSuperseded_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Set whether transformations that are superseded (but not deprecated)
- * should be discarded.
- *
- * The default is true.
- */
-void CoordinateOperationContext::setDiscardSuperseded(bool discard) {
- d->discardSuperseded_ = discard;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Set how grid availability is used.
- *
- * The default is USE_FOR_SORTING.
- */
-void CoordinateOperationContext::setGridAvailabilityUse(
- GridAvailabilityUse use) {
- d->gridAvailabilityUse_ = use;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return how grid availability is used.
- *
- * The default is USE_FOR_SORTING.
- */
-CoordinateOperationContext::GridAvailabilityUse
-CoordinateOperationContext::getGridAvailabilityUse() const {
- return d->gridAvailabilityUse_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Set whether an intermediate pivot CRS can be used for researching
- * coordinate operations between a source and target CRS.
- *
- * Concretely if in the database there is an operation from A to C
- * (or C to A), and another one from C to B (or B to C), but no direct
- * operation between A and B, setting this parameter to
- * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations.
- *
- * The current implementation is limited to researching one intermediate
- * step.
- *
- * By default, with the IF_NO_DIRECT_TRANSFORMATION stratgey, all potential
- * C candidates will be used if there is no direct transformation.
- */
-void CoordinateOperationContext::setAllowUseIntermediateCRS(
- IntermediateCRSUse use) {
- d->allowUseIntermediateCRS_ = use;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return whether an intermediate pivot CRS can be used for researching
- * coordinate operations between a source and target CRS.
- *
- * Concretely if in the database there is an operation from A to C
- * (or C to A), and another one from C to B (or B to C), but no direct
- * operation between A and B, setting this parameter to
- * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations.
- *
- * The default is IF_NO_DIRECT_TRANSFORMATION.
- */
-CoordinateOperationContext::IntermediateCRSUse
-CoordinateOperationContext::getAllowUseIntermediateCRS() const {
- return d->allowUseIntermediateCRS_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Restrict the potential pivot CRSs that can be used when trying to
- * build a coordinate operation between two CRS that have no direct operation.
- *
- * @param intermediateCRSAuthCodes a vector of (auth_name, code) that can be
- * used as potential pivot RS
- */
-void CoordinateOperationContext::setIntermediateCRS(
- const std::vector<std::pair<std::string, std::string>>
- &intermediateCRSAuthCodes) {
- d->intermediateCRSAuthCodes_ = intermediateCRSAuthCodes;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Return the potential pivot CRSs that can be used when trying to
- * build a coordinate operation between two CRS that have no direct operation.
- *
- */
-const std::vector<std::pair<std::string, std::string>> &
-CoordinateOperationContext::getIntermediateCRS() const {
- return d->intermediateCRSAuthCodes_;
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Creates a context for a coordinate operation.
- *
- * If a non null authorityFactory is provided, the resulting context should
- * not be used simultaneously by more than one thread.
- *
- * If authorityFactory->getAuthority() is the empty string, then coordinate
- * operations from any authority will be searched, with the restrictions set
- * in the authority_to_authority_preference database table.
- * If authorityFactory->getAuthority() is set to "any", then coordinate
- * operations from any authority will be searched
- * If authorityFactory->getAuthority() is a non-empty string different of "any",
- * then coordinate operatiosn will be searched only in that authority namespace.
- *
- * @param authorityFactory Authority factory, or null if no database lookup
- * is allowed.
- * Use io::authorityFactory::create(context, std::string()) to allow all
- * authorities to be used.
- * @param extent Area of interest, or null if none is known.
- * @param accuracy Maximum allowed accuracy in metre, as specified in or
- * 0 to get best accuracy.
- * @return a new context.
- */
-CoordinateOperationContextNNPtr CoordinateOperationContext::create(
- const io::AuthorityFactoryPtr &authorityFactory,
- const metadata::ExtentPtr &extent, double accuracy) {
- auto ctxt = NN_NO_CHECK(
- CoordinateOperationContext::make_unique<CoordinateOperationContext>());
- ctxt->d->authorityFactory_ = authorityFactory;
- ctxt->d->extent_ = extent;
- ctxt->d->accuracy_ = accuracy;
- return ctxt;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-struct CoordinateOperationFactory::Private {
-
- struct Context {
- // This is the extent of the source CRS and target CRS of the initial
- // CoordinateOperationFactory::createOperations() public call, not
- // necessarily the ones of intermediate
- // CoordinateOperationFactory::Private::createOperations() calls.
- // This is used to compare transformations area of use against the
- // area of use of the source & target CRS.
- const metadata::ExtentPtr &extent1;
- const metadata::ExtentPtr &extent2;
- const CoordinateOperationContextNNPtr &context;
- bool inCreateOperationsWithDatumPivotAntiRecursion = false;
- bool inCreateOperationsGeogToVertWithAlternativeGeog = false;
- bool inCreateOperationsGeogToVertWithIntermediateVert = false;
- bool skipHorizontalTransformation = false;
- std::map<std::pair<io::AuthorityFactory::ObjectType, std::string>,
- std::list<std::pair<std::string, std::string>>>
- cacheNameToCRS{};
-
- Context(const metadata::ExtentPtr &extent1In,
- const metadata::ExtentPtr &extent2In,
- const CoordinateOperationContextNNPtr &contextIn)
- : extent1(extent1In), extent2(extent2In), context(contextIn) {}
- };
-
- static std::vector<CoordinateOperationNNPtr>
- createOperations(const crs::CRSNNPtr &sourceCRS,
- const crs::CRSNNPtr &targetCRS, Context &context);
-
- private:
- static constexpr bool disallowEmptyIntersection = true;
-
- static void
- buildCRSIds(const crs::CRSNNPtr &crs, Private::Context &context,
- std::list<std::pair<std::string, std::string>> &ids);
-
- static std::vector<CoordinateOperationNNPtr> findOpsInRegistryDirect(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, bool &resNonEmptyBeforeFiltering);
-
- static std::vector<CoordinateOperationNNPtr>
- findOpsInRegistryDirectTo(const crs::CRSNNPtr &targetCRS,
- Private::Context &context);
-
- static std::vector<CoordinateOperationNNPtr>
- findsOpsInRegistryWithIntermediate(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context,
- bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates);
-
- static void createOperationsFromProj4Ext(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static bool createOperationsFromDatabase(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::GeodeticCRS *geodSrc,
- const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc,
- const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc,
- const crs::VerticalCRS *vertDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static std::vector<CoordinateOperationNNPtr>
- createOperationsGeogToVertFromGeoid(const crs::CRSNNPtr &sourceCRS,
- const crs::CRSNNPtr &targetCRS,
- const crs::VerticalCRS *vertDst,
- Context &context);
-
- 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,
- const crs::GeodeticCRS *geodDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static void createOperationsDerivedTo(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::DerivedCRS *derivedSrc,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static void createOperationsBoundToGeog(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::BoundCRS *boundSrc,
- const crs::GeographicCRS *geogDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static void createOperationsBoundToVert(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::BoundCRS *boundSrc,
- const crs::VerticalCRS *vertDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static void createOperationsVertToVert(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::VerticalCRS *vertSrc,
- const crs::VerticalCRS *vertDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static void createOperationsVertToGeog(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::VerticalCRS *vertSrc,
- const crs::GeographicCRS *geogDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static void createOperationsVertToGeogBallpark(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::VerticalCRS *vertSrc,
- const crs::GeographicCRS *geogDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static void createOperationsBoundToBound(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::BoundCRS *boundSrc,
- const crs::BoundCRS *boundDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static void createOperationsCompoundToGeog(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::CompoundCRS *compoundSrc,
- const crs::GeographicCRS *geogDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static void createOperationsToGeod(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::GeodeticCRS *geodDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static void createOperationsCompoundToCompound(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::CompoundCRS *compoundSrc,
- const crs::CompoundCRS *compoundDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static void createOperationsBoundToCompound(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::BoundCRS *boundSrc,
- const crs::CompoundCRS *compoundDst,
- std::vector<CoordinateOperationNNPtr> &res);
-
- static std::vector<CoordinateOperationNNPtr> createOperationsGeogToGeog(
- std::vector<CoordinateOperationNNPtr> &res,
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::GeographicCRS *geogSrc,
- const crs::GeographicCRS *geogDst);
-
- static void createOperationsWithDatumPivot(
- std::vector<CoordinateOperationNNPtr> &res,
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst,
- Context &context);
-
- static bool
- hasPerfectAccuracyResult(const std::vector<CoordinateOperationNNPtr> &res,
- const Context &context);
-
- static void setCRSs(CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS,
- const crs::CRSNNPtr &targetCRS);
-};
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-CoordinateOperationFactory::~CoordinateOperationFactory() = default;
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperationFactory::CoordinateOperationFactory() : d(nullptr) {}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Find a CoordinateOperation from sourceCRS to targetCRS.
- *
- * This is a helper of createOperations(), using a coordinate operation
- * context
- * with no authority factory (so no catalog searching is done), no desired
- * accuracy and no area of interest.
- * This returns the first operation of the result set of createOperations(),
- * or null if none found.
- *
- * @param sourceCRS source CRS.
- * @param targetCRS source CRS.
- * @return a CoordinateOperation or nullptr.
- */
-CoordinateOperationPtr CoordinateOperationFactory::createOperation(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) const {
- auto res = createOperations(
- sourceCRS, targetCRS,
- CoordinateOperationContext::create(nullptr, nullptr, 0.0));
- if (!res.empty()) {
- return res[0];
- }
- return nullptr;
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-// ---------------------------------------------------------------------------
-
-struct PrecomputedOpCharacteristics {
- double area_{};
- double accuracy_{};
- bool isPROJExportable_ = false;
- bool hasGrids_ = false;
- bool gridsAvailable_ = false;
- bool gridsKnown_ = false;
- size_t stepCount_ = 0;
- bool isApprox_ = false;
- bool hasBallparkVertical_ = false;
- bool isNullTransformation_ = false;
-
- PrecomputedOpCharacteristics() = default;
- PrecomputedOpCharacteristics(double area, double accuracy,
- bool isPROJExportable, bool hasGrids,
- bool gridsAvailable, bool gridsKnown,
- size_t stepCount, bool isApprox,
- bool hasBallparkVertical,
- bool isNullTransformation)
- : area_(area), accuracy_(accuracy), isPROJExportable_(isPROJExportable),
- hasGrids_(hasGrids), gridsAvailable_(gridsAvailable),
- gridsKnown_(gridsKnown), stepCount_(stepCount), isApprox_(isApprox),
- hasBallparkVertical_(hasBallparkVertical),
- isNullTransformation_(isNullTransformation) {}
-};
-
-// ---------------------------------------------------------------------------
-
-// We could have used a lambda instead of this old-school way, but
-// filterAndSort() is already huge.
-struct SortFunction {
-
- const std::map<CoordinateOperation *, PrecomputedOpCharacteristics> &map;
-
- explicit SortFunction(const std::map<CoordinateOperation *,
- PrecomputedOpCharacteristics> &mapIn)
- : map(mapIn) {}
-
- // Sorting function
- // Return true if a < b
- bool compare(const CoordinateOperationNNPtr &a,
- const CoordinateOperationNNPtr &b) const {
- auto iterA = map.find(a.get());
- assert(iterA != map.end());
- auto iterB = map.find(b.get());
- assert(iterB != map.end());
-
- // CAUTION: the order of the comparisons is extremely important
- // to get the intended result.
-
- if (iterA->second.isPROJExportable_ &&
- !iterB->second.isPROJExportable_) {
- return true;
- }
- if (!iterA->second.isPROJExportable_ &&
- iterB->second.isPROJExportable_) {
- return false;
- }
-
- if (!iterA->second.isApprox_ && iterB->second.isApprox_) {
- return true;
- }
- if (iterA->second.isApprox_ && !iterB->second.isApprox_) {
- return false;
- }
-
- if (!iterA->second.hasBallparkVertical_ &&
- iterB->second.hasBallparkVertical_) {
- return true;
- }
- if (iterA->second.hasBallparkVertical_ &&
- !iterB->second.hasBallparkVertical_) {
- return false;
- }
-
- if (!iterA->second.isNullTransformation_ &&
- iterB->second.isNullTransformation_) {
- return true;
- }
- if (iterA->second.isNullTransformation_ &&
- !iterB->second.isNullTransformation_) {
- return false;
- }
-
- // Operations where grids are all available go before other
- if (iterA->second.gridsAvailable_ && !iterB->second.gridsAvailable_) {
- return true;
- }
- if (iterB->second.gridsAvailable_ && !iterA->second.gridsAvailable_) {
- return false;
- }
-
- // Operations where grids are all known in our DB go before other
- if (iterA->second.gridsKnown_ && !iterB->second.gridsKnown_) {
- return true;
- }
- if (iterB->second.gridsKnown_ && !iterA->second.gridsKnown_) {
- return false;
- }
-
- // Operations with known accuracy go before those with unknown accuracy
- const double accuracyA = iterA->second.accuracy_;
- const double accuracyB = iterB->second.accuracy_;
- if (accuracyA >= 0 && accuracyB < 0) {
- return true;
- }
- if (accuracyB >= 0 && accuracyA < 0) {
- return false;
- }
-
- if (accuracyA < 0 && accuracyB < 0) {
- // unknown accuracy ? then prefer operations with grids, which
- // are likely to have best practical accuracy
- if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) {
- return true;
- }
- if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) {
- return false;
- }
- }
-
- // Operations with larger non-zero area of use go before those with
- // lower one
- const double areaA = iterA->second.area_;
- const double areaB = iterB->second.area_;
- if (areaA > 0) {
- if (areaA > areaB) {
- return true;
- }
- if (areaA < areaB) {
- return false;
- }
- } else if (areaB > 0) {
- return false;
- }
-
- // Operations with better accuracy go before those with worse one
- if (accuracyA >= 0 && accuracyA < accuracyB) {
- return true;
- }
- if (accuracyB >= 0 && accuracyB < accuracyA) {
- return false;
- }
-
- if (accuracyA >= 0 && accuracyA == accuracyB) {
- // same accuracy ? then prefer operations without grids
- if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) {
- return true;
- }
- if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) {
- return false;
- }
- }
-
- // The less intermediate steps, the better
- if (iterA->second.stepCount_ < iterB->second.stepCount_) {
- return true;
- }
- if (iterB->second.stepCount_ < iterA->second.stepCount_) {
- return false;
- }
-
- const auto &a_name = a->nameStr();
- const auto &b_name = b->nameStr();
- // The shorter name, the better ?
- if (a_name.size() < b_name.size()) {
- return true;
- }
- if (b_name.size() < a_name.size()) {
- return false;
- }
-
- // Arbitrary final criterion. We actually return the greater element
- // first, so that "Amersfoort to WGS 84 (4)" is presented before
- // "Amersfoort to WGS 84 (3)", which is probably a better guess.
-
- // Except for French NTF (Paris) to NTF, where the (1) conversion
- // should be preferred because in the remarks of (2), it is mentioned
- // OGP prefers value from IGN Paris (code 1467)...
- if (a_name.find("NTF (Paris) to NTF (1)") != std::string::npos &&
- b_name.find("NTF (Paris) to NTF (2)") != std::string::npos) {
- return true;
- }
- if (a_name.find("NTF (Paris) to NTF (2)") != std::string::npos &&
- b_name.find("NTF (Paris) to NTF (1)") != std::string::npos) {
- return false;
- }
- if (a_name.find("NTF (Paris) to RGF93 (1)") != std::string::npos &&
- b_name.find("NTF (Paris) to RGF93 (2)") != std::string::npos) {
- return true;
- }
- if (a_name.find("NTF (Paris) to RGF93 (2)") != std::string::npos &&
- b_name.find("NTF (Paris) to RGF93 (1)") != std::string::npos) {
- return false;
- }
-
- return a_name > b_name;
- }
-
- bool operator()(const CoordinateOperationNNPtr &a,
- const CoordinateOperationNNPtr &b) const {
- const bool ret = compare(a, b);
-#if 0
- std::cerr << a->nameStr() << " < " << b->nameStr() << " : " << ret << std::endl;
-#endif
- return ret;
- }
-};
-
-// ---------------------------------------------------------------------------
-
-static size_t getStepCount(const CoordinateOperationNNPtr &op) {
- auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get());
- size_t stepCount = 1;
- if (concat) {
- stepCount = concat->operations().size();
- }
- return stepCount;
-}
-
-// ---------------------------------------------------------------------------
-
-// Return number of steps that are transformations (and not conversions)
-static size_t getTransformationStepCount(const CoordinateOperationNNPtr &op) {
- auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get());
- size_t stepCount = 1;
- if (concat) {
- stepCount = 0;
- for (const auto &subOp : concat->operations()) {
- if (dynamic_cast<const Conversion *>(subOp.get()) == nullptr) {
- stepCount++;
- }
- }
- }
- return stepCount;
-}
-
-// ---------------------------------------------------------------------------
-
-static bool isNullTransformation(const std::string &name) {
- if (name.find(" + ") != std::string::npos)
- return false;
- return starts_with(name, BALLPARK_GEOCENTRIC_TRANSLATION) ||
- starts_with(name, BALLPARK_GEOGRAPHIC_OFFSET) ||
- starts_with(name, NULL_GEOGRAPHIC_OFFSET) ||
- starts_with(name, NULL_GEOCENTRIC_TRANSLATION);
-}
-
-// ---------------------------------------------------------------------------
-
-struct FilterResults {
-
- FilterResults(const std::vector<CoordinateOperationNNPtr> &sourceListIn,
- const CoordinateOperationContextNNPtr &contextIn,
- const metadata::ExtentPtr &extent1In,
- const metadata::ExtentPtr &extent2In,
- bool forceStrictContainmentTest)
- : sourceList(sourceListIn), context(contextIn), extent1(extent1In),
- extent2(extent2In), areaOfInterest(context->getAreaOfInterest()),
- desiredAccuracy(context->getDesiredAccuracy()),
- sourceAndTargetCRSExtentUse(
- context->getSourceAndTargetCRSExtentUse()) {
-
- computeAreaOfInterest();
- filterOut(forceStrictContainmentTest);
- }
-
- FilterResults &andSort() {
- sort();
-
- // And now that we have a sorted list, we can remove uninteresting
- // results
- // ...
- removeSyntheticNullTransforms();
- removeUninterestingOps();
- removeDuplicateOps();
- removeSyntheticNullTransforms();
- return *this;
- }
-
- // ----------------------------------------------------------------------
-
- // cppcheck-suppress functionStatic
- const std::vector<CoordinateOperationNNPtr> &getRes() { return res; }
-
- // ----------------------------------------------------------------------
- private:
- const std::vector<CoordinateOperationNNPtr> &sourceList;
- const CoordinateOperationContextNNPtr &context;
- const metadata::ExtentPtr &extent1;
- const metadata::ExtentPtr &extent2;
- metadata::ExtentPtr areaOfInterest;
- const double desiredAccuracy = context->getDesiredAccuracy();
- const CoordinateOperationContext::SourceTargetCRSExtentUse
- sourceAndTargetCRSExtentUse;
-
- bool hasOpThatContainsAreaOfInterestAndNoGrid = false;
- std::vector<CoordinateOperationNNPtr> res{};
-
- // ----------------------------------------------------------------------
- void computeAreaOfInterest() {
-
- // Compute an area of interest from the CRS extent if the user did
- // not specify one
- if (!areaOfInterest) {
- if (sourceAndTargetCRSExtentUse ==
- CoordinateOperationContext::SourceTargetCRSExtentUse::
- INTERSECTION) {
- if (extent1 && extent2) {
- areaOfInterest =
- extent1->intersection(NN_NO_CHECK(extent2));
- }
- } else if (sourceAndTargetCRSExtentUse ==
- CoordinateOperationContext::SourceTargetCRSExtentUse::
- SMALLEST) {
- if (extent1 && extent2) {
- if (getPseudoArea(extent1) < getPseudoArea(extent2)) {
- areaOfInterest = extent1;
- } else {
- areaOfInterest = extent2;
- }
- } else if (extent1) {
- areaOfInterest = extent1;
- } else {
- areaOfInterest = extent2;
- }
- }
- }
- }
-
- // ---------------------------------------------------------------------------
-
- void filterOut(bool forceStrictContainmentTest) {
-
- // Filter out operations that do not match the expected accuracy
- // and area of use.
- const auto spatialCriterion =
- forceStrictContainmentTest
- ? CoordinateOperationContext::SpatialCriterion::
- STRICT_CONTAINMENT
- : context->getSpatialCriterion();
- bool hasOnlyBallpark = true;
- bool hasNonBallparkWithoutExtent = false;
- bool hasNonBallparkOpWithExtent = false;
- const bool allowBallpark = context->getAllowBallparkTransformations();
- for (const auto &op : sourceList) {
- if (desiredAccuracy != 0) {
- const double accuracy = getAccuracy(op);
- if (accuracy < 0 || accuracy > desiredAccuracy) {
- continue;
- }
- }
- if (!allowBallpark && op->hasBallparkTransformation()) {
- continue;
- }
- if (areaOfInterest) {
- bool emptyIntersection = false;
- auto extent = getExtent(op, true, emptyIntersection);
- if (!extent) {
- if (!op->hasBallparkTransformation()) {
- hasNonBallparkWithoutExtent = true;
- }
- continue;
- }
- if (!op->hasBallparkTransformation()) {
- hasNonBallparkOpWithExtent = true;
- }
- bool extentContains =
- extent->contains(NN_NO_CHECK(areaOfInterest));
- if (!hasOpThatContainsAreaOfInterestAndNoGrid &&
- extentContains) {
- if (!op->hasBallparkTransformation() &&
- op->gridsNeeded(nullptr, true).empty()) {
- hasOpThatContainsAreaOfInterestAndNoGrid = true;
- }
- }
- if (spatialCriterion ==
- CoordinateOperationContext::SpatialCriterion::
- STRICT_CONTAINMENT &&
- !extentContains) {
- continue;
- }
- if (spatialCriterion ==
- CoordinateOperationContext::SpatialCriterion::
- PARTIAL_INTERSECTION &&
- !extent->intersects(NN_NO_CHECK(areaOfInterest))) {
- continue;
- }
- } else if (sourceAndTargetCRSExtentUse ==
- CoordinateOperationContext::SourceTargetCRSExtentUse::
- BOTH) {
- bool emptyIntersection = false;
- auto extent = getExtent(op, true, emptyIntersection);
- if (!extent) {
- if (!op->hasBallparkTransformation()) {
- hasNonBallparkWithoutExtent = true;
- }
- continue;
- }
- if (!op->hasBallparkTransformation()) {
- hasNonBallparkOpWithExtent = true;
- }
- bool extentContainsExtent1 =
- !extent1 || extent->contains(NN_NO_CHECK(extent1));
- bool extentContainsExtent2 =
- !extent2 || extent->contains(NN_NO_CHECK(extent2));
- if (!hasOpThatContainsAreaOfInterestAndNoGrid &&
- extentContainsExtent1 && extentContainsExtent2) {
- if (!op->hasBallparkTransformation() &&
- op->gridsNeeded(nullptr, true).empty()) {
- hasOpThatContainsAreaOfInterestAndNoGrid = true;
- }
- }
- if (spatialCriterion ==
- CoordinateOperationContext::SpatialCriterion::
- STRICT_CONTAINMENT) {
- if (!extentContainsExtent1 || !extentContainsExtent2) {
- continue;
- }
- } else if (spatialCriterion ==
- CoordinateOperationContext::SpatialCriterion::
- PARTIAL_INTERSECTION) {
- bool extentIntersectsExtent1 =
- !extent1 || extent->intersects(NN_NO_CHECK(extent1));
- bool extentIntersectsExtent2 =
- extent2 && extent->intersects(NN_NO_CHECK(extent2));
- if (!extentIntersectsExtent1 || !extentIntersectsExtent2) {
- continue;
- }
- }
- }
- if (!op->hasBallparkTransformation()) {
- hasOnlyBallpark = false;
- }
- res.emplace_back(op);
- }
-
- // In case no operation has an extent and no result is found,
- // retain all initial operations that match accuracy criterion.
- if ((res.empty() && !hasNonBallparkOpWithExtent) ||
- (hasOnlyBallpark && hasNonBallparkWithoutExtent)) {
- for (const auto &op : sourceList) {
- if (desiredAccuracy != 0) {
- const double accuracy = getAccuracy(op);
- if (accuracy < 0 || accuracy > desiredAccuracy) {
- continue;
- }
- }
- if (!allowBallpark && op->hasBallparkTransformation()) {
- continue;
- }
- res.emplace_back(op);
- }
- }
- }
-
- // ----------------------------------------------------------------------
-
- void sort() {
-
- // Precompute a number of parameters for each operation that will be
- // useful for the sorting.
- std::map<CoordinateOperation *, PrecomputedOpCharacteristics> map;
- const auto gridAvailabilityUse = context->getGridAvailabilityUse();
- for (const auto &op : res) {
- bool dummy = false;
- auto extentOp = getExtent(op, true, dummy);
- double area = 0.0;
- if (extentOp) {
- if (areaOfInterest) {
- area = getPseudoArea(
- extentOp->intersection(NN_NO_CHECK(areaOfInterest)));
- } else if (extent1 && extent2) {
- auto x = extentOp->intersection(NN_NO_CHECK(extent1));
- auto y = extentOp->intersection(NN_NO_CHECK(extent2));
- area = getPseudoArea(x) + getPseudoArea(y) -
- ((x && y)
- ? getPseudoArea(x->intersection(NN_NO_CHECK(y)))
- : 0.0);
- } else if (extent1) {
- area = getPseudoArea(
- extentOp->intersection(NN_NO_CHECK(extent1)));
- } else if (extent2) {
- area = getPseudoArea(
- extentOp->intersection(NN_NO_CHECK(extent2)));
- } else {
- area = getPseudoArea(extentOp);
- }
- }
-
- bool hasGrids = false;
- bool gridsAvailable = true;
- bool gridsKnown = true;
- if (context->getAuthorityFactory()) {
- const auto gridsNeeded = op->gridsNeeded(
- context->getAuthorityFactory()->databaseContext(),
- gridAvailabilityUse ==
- CoordinateOperationContext::GridAvailabilityUse::
- KNOWN_AVAILABLE);
- for (const auto &gridDesc : gridsNeeded) {
- hasGrids = true;
- if (gridAvailabilityUse ==
- CoordinateOperationContext::GridAvailabilityUse::
- USE_FOR_SORTING &&
- !gridDesc.available) {
- gridsAvailable = false;
- }
- if (gridDesc.packageName.empty() &&
- !(!gridDesc.url.empty() && gridDesc.openLicense) &&
- !gridDesc.available) {
- gridsKnown = false;
- }
- }
- }
-
- const auto stepCount = getStepCount(op);
-
- bool isPROJExportable = false;
- auto formatter = io::PROJStringFormatter::create();
- try {
- op->exportToPROJString(formatter.get());
- // Grids might be missing, but at least this is something
- // PROJ could potentially process
- isPROJExportable = true;
- } catch (const std::exception &) {
- }
-
-#if 0
- std::cerr << op->nameStr() << " ";
- std::cerr << area << " ";
- std::cerr << getAccuracy(op) << " ";
- std::cerr << isPROJExportable << " ";
- std::cerr << hasGrids << " ";
- std::cerr << gridsAvailable << " ";
- std::cerr << gridsKnown << " ";
- std::cerr << stepCount << " ";
- std::cerr << op->hasBallparkTransformation() << " ";
- std::cerr << isNullTransformation(op->nameStr()) << " ";
- std::cerr << std::endl;
-#endif
- map[op.get()] = PrecomputedOpCharacteristics(
- area, getAccuracy(op), isPROJExportable, hasGrids,
- gridsAvailable, gridsKnown, stepCount,
- op->hasBallparkTransformation(),
- op->nameStr().find("ballpark vertical transformation") !=
- std::string::npos,
- isNullTransformation(op->nameStr()));
- }
-
- // Sort !
- SortFunction sortFunc(map);
- std::sort(res.begin(), res.end(), sortFunc);
-
-// Debug code to check consistency of the sort function
-#ifdef DEBUG_SORT
- constexpr bool debugSort = true;
-#elif !defined(NDEBUG)
- const bool debugSort = getenv("PROJ_DEBUG_SORT_FUNCT") != nullptr;
-#endif
-#if defined(DEBUG_SORT) || !defined(NDEBUG)
- if (debugSort) {
- const bool assertIfIssue =
- !(getenv("PROJ_DEBUG_SORT_FUNCT_ASSERT") != nullptr);
- for (size_t i = 0; i < res.size(); ++i) {
- for (size_t j = i + 1; j < res.size(); ++j) {
- if (sortFunc(res[j], res[i])) {
-#ifdef DEBUG_SORT
- std::cerr << "Sorting issue with entry " << i << "("
- << res[i]->nameStr() << ") and " << j << "("
- << res[j]->nameStr() << ")" << std::endl;
-#endif
- if (assertIfIssue) {
- assert(false);
- }
- }
- }
- }
- }
-#endif
- }
-
- // ----------------------------------------------------------------------
-
- void removeSyntheticNullTransforms() {
-
- // If we have more than one result, and than the last result is the
- // default "Ballpark geographic offset" or "Ballpark geocentric
- // translation" operations we have synthetized, and that at least one
- // operation has the desired area of interest and does not require the
- // use of grids, remove it as all previous results are necessarily
- // better
- if (hasOpThatContainsAreaOfInterestAndNoGrid && res.size() > 1) {
- const auto &opLast = res.back();
- if (opLast->hasBallparkTransformation() ||
- isNullTransformation(opLast->nameStr())) {
- std::vector<CoordinateOperationNNPtr> resTemp;
- for (size_t i = 0; i < res.size() - 1; i++) {
- resTemp.emplace_back(res[i]);
- }
- res = std::move(resTemp);
- }
- }
- }
-
- // ----------------------------------------------------------------------
-
- void removeUninterestingOps() {
-
- // Eliminate operations that bring nothing, ie for a given area of use,
- // do not keep operations that have similar or worse accuracy, but
- // involve more (non conversion) steps
- std::vector<CoordinateOperationNNPtr> resTemp;
- metadata::ExtentPtr lastExtent;
- double lastAccuracy = -1;
- size_t lastStepCount = 0;
- CoordinateOperationPtr lastOp;
-
- bool first = true;
- for (const auto &op : res) {
- const auto curAccuracy = getAccuracy(op);
- bool dummy = false;
- const auto curExtent = getExtent(op, true, dummy);
- const auto curStepCount = getTransformationStepCount(op);
-
- if (first) {
- resTemp.emplace_back(op);
- first = false;
- } else {
- if (lastOp->_isEquivalentTo(op.get())) {
- continue;
- }
- const bool sameExtent =
- ((!curExtent && !lastExtent) ||
- (curExtent && lastExtent &&
- curExtent->contains(NN_NO_CHECK(lastExtent)) &&
- lastExtent->contains(NN_NO_CHECK(curExtent))));
- if (((curAccuracy >= lastAccuracy && lastAccuracy >= 0) ||
- (curAccuracy < 0 && lastAccuracy >= 0)) &&
- sameExtent && curStepCount > lastStepCount) {
- continue;
- }
-
- resTemp.emplace_back(op);
- }
-
- lastOp = op.as_nullable();
- lastStepCount = curStepCount;
- lastExtent = curExtent;
- lastAccuracy = curAccuracy;
- }
- res = std::move(resTemp);
- }
-
- // ----------------------------------------------------------------------
-
- // cppcheck-suppress functionStatic
- void removeDuplicateOps() {
-
- if (res.size() <= 1) {
- return;
- }
-
- // When going from EPSG:4807 (NTF Paris) to EPSG:4171 (RGC93), we get
- // EPSG:7811, NTF (Paris) to RGF93 (2), 1 m
- // and unknown id, NTF (Paris) to NTF (1) + Inverse of RGF93 to NTF (2),
- // 1 m
- // both have same PROJ string and extent
- // Do not keep the later (that has more steps) as it adds no value.
-
- std::set<std::string> setPROJPlusExtent;
- std::vector<CoordinateOperationNNPtr> resTemp;
- for (const auto &op : res) {
- auto formatter = io::PROJStringFormatter::create();
- try {
- std::string key(op->exportToPROJString(formatter.get()));
- bool dummy = false;
- auto extentOp = getExtent(op, true, dummy);
- if (extentOp) {
- const auto &geogElts = extentOp->geographicElements();
- if (geogElts.size() == 1) {
- auto bbox = dynamic_cast<
- const metadata::GeographicBoundingBox *>(
- geogElts[0].get());
- if (bbox) {
- double w = bbox->westBoundLongitude();
- double s = bbox->southBoundLatitude();
- double e = bbox->eastBoundLongitude();
- double n = bbox->northBoundLatitude();
- key += "-";
- key += toString(w);
- key += "-";
- key += toString(s);
- key += "-";
- key += toString(e);
- key += "-";
- key += toString(n);
- }
- }
- }
-
- if (setPROJPlusExtent.find(key) == setPROJPlusExtent.end()) {
- resTemp.emplace_back(op);
- setPROJPlusExtent.insert(key);
- }
- } catch (const std::exception &) {
- resTemp.emplace_back(op);
- }
- }
- res = std::move(resTemp);
- }
-};
-
-// ---------------------------------------------------------------------------
-
-/** \brief Filter operations and sort them given context.
- *
- * If a desired accuracy is specified, only keep operations whose accuracy
- * is at least the desired one.
- * If an area of interest is specified, only keep operations whose area of
- * use include the area of interest.
- * Then sort remaining operations by descending area of use, and increasing
- * accuracy.
- */
-static std::vector<CoordinateOperationNNPtr>
-filterAndSort(const std::vector<CoordinateOperationNNPtr> &sourceList,
- const CoordinateOperationContextNNPtr &context,
- const metadata::ExtentPtr &extent1,
- const metadata::ExtentPtr &extent2) {
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_FUNCTION();
- logTrace("number of results before filter and sort: " +
- toString(static_cast<int>(sourceList.size())));
-#endif
- auto resFiltered =
- FilterResults(sourceList, context, extent1, extent2, false)
- .andSort()
- .getRes();
-#ifdef TRACE_CREATE_OPERATIONS
- logTrace("number of results after filter and sort: " +
- toString(static_cast<int>(resFiltered.size())));
-#endif
- return resFiltered;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-// Apply the inverse() method on all elements of the input list
-static std::vector<CoordinateOperationNNPtr>
-applyInverse(const std::vector<CoordinateOperationNNPtr> &list) {
- auto res = list;
- for (auto &op : res) {
-#ifdef DEBUG
- auto opNew = op->inverse();
- assert(opNew->targetCRS()->isEquivalentTo(op->sourceCRS().get()));
- assert(opNew->sourceCRS()->isEquivalentTo(op->targetCRS().get()));
- op = opNew;
-#else
- op = op->inverse();
-#endif
- }
- return res;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-void CoordinateOperationFactory::Private::buildCRSIds(
- const crs::CRSNNPtr &crs, Private::Context &context,
- std::list<std::pair<std::string, std::string>> &ids) {
- const auto &authFactory = context.context->getAuthorityFactory();
- assert(authFactory);
- for (const auto &id : crs->identifiers()) {
- const auto &authName = *(id->codeSpace());
- const auto &code = id->code();
- if (!authName.empty()) {
- const auto tmpAuthFactory = io::AuthorityFactory::create(
- authFactory->databaseContext(), authName);
- try {
- // Consistency check for the ID attached to the object.
- // See https://github.com/OSGeo/PROJ/issues/1982 where EPSG:4656
- // is attached to a GeographicCRS whereas it is a ProjectedCRS
- if (tmpAuthFactory->createCoordinateReferenceSystem(code)
- ->_isEquivalentTo(
- crs.get(),
- util::IComparable::Criterion::
- EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)) {
- ids.emplace_back(authName, code);
- } else {
- // TODO? log this inconsistency
- }
- } catch (const std::exception &) {
- // TODO? log this inconsistency
- }
- }
- }
- if (ids.empty()) {
- std::vector<io::AuthorityFactory::ObjectType> allowedObjects;
- auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(crs.get());
- if (geogCRS) {
- allowedObjects.push_back(
- geogCRS->coordinateSystem()->axisList().size() == 2
- ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS
- : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS);
- } else if (dynamic_cast<crs::ProjectedCRS *>(crs.get())) {
- allowedObjects.push_back(
- io::AuthorityFactory::ObjectType::PROJECTED_CRS);
- } else if (dynamic_cast<crs::VerticalCRS *>(crs.get())) {
- allowedObjects.push_back(
- io::AuthorityFactory::ObjectType::VERTICAL_CRS);
- }
- if (!allowedObjects.empty()) {
-
- const std::pair<io::AuthorityFactory::ObjectType, std::string> key(
- allowedObjects[0], crs->nameStr());
- auto iter = context.cacheNameToCRS.find(key);
- if (iter != context.cacheNameToCRS.end()) {
- ids = iter->second;
- return;
- }
-
- const auto &authFactoryName = authFactory->getAuthority();
- try {
- const auto tmpAuthFactory = io::AuthorityFactory::create(
- authFactory->databaseContext(),
- (authFactoryName.empty() || authFactoryName == "any")
- ? std::string()
- : authFactoryName);
-
- auto matches = tmpAuthFactory->createObjectsFromName(
- crs->nameStr(), allowedObjects, false, 2);
- if (matches.size() == 1 &&
- crs->_isEquivalentTo(
- matches.front().get(),
- util::IComparable::Criterion::EQUIVALENT) &&
- !matches.front()->identifiers().empty()) {
- const auto &tmpIds = matches.front()->identifiers();
- ids.emplace_back(*(tmpIds[0]->codeSpace()),
- tmpIds[0]->code());
- }
- } catch (const std::exception &) {
- }
- context.cacheNameToCRS[key] = ids;
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-static std::vector<std::string>
-getCandidateAuthorities(const io::AuthorityFactoryPtr &authFactory,
- const std::string &srcAuthName,
- const std::string &targetAuthName) {
- const auto &authFactoryName = authFactory->getAuthority();
- std::vector<std::string> authorities;
- if (authFactoryName == "any") {
- authorities.emplace_back();
- }
- if (authFactoryName.empty()) {
- authorities = authFactory->databaseContext()->getAllowedAuthorities(
- srcAuthName, targetAuthName);
- if (authorities.empty()) {
- authorities.emplace_back();
- }
- } else {
- authorities.emplace_back(authFactoryName);
- }
- return authorities;
-}
-
-// ---------------------------------------------------------------------------
-
-// Look in the authority registry for operations from sourceCRS to targetCRS
-std::vector<CoordinateOperationNNPtr>
-CoordinateOperationFactory::Private::findOpsInRegistryDirect(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, bool &resNonEmptyBeforeFiltering) {
- const auto &authFactory = context.context->getAuthorityFactory();
- assert(authFactory);
-
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("findOpsInRegistryDirect(" + objectAsStr(sourceCRS.get()) +
- " --> " + objectAsStr(targetCRS.get()) + ")");
-#endif
-
- resNonEmptyBeforeFiltering = false;
- std::list<std::pair<std::string, std::string>> sourceIds;
- std::list<std::pair<std::string, std::string>> targetIds;
- buildCRSIds(sourceCRS, context, sourceIds);
- buildCRSIds(targetCRS, context, targetIds);
-
- const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
- for (const auto &idSrc : sourceIds) {
- const auto &srcAuthName = idSrc.first;
- const auto &srcCode = idSrc.second;
- for (const auto &idTarget : targetIds) {
- const auto &targetAuthName = idTarget.first;
- const auto &targetCode = idTarget.second;
-
- const auto authorities(getCandidateAuthorities(
- authFactory, srcAuthName, targetAuthName));
- std::vector<CoordinateOperationNNPtr> res;
- for (const auto &authority : authorities) {
- const auto authName =
- authority == "any" ? std::string() : authority;
- const auto tmpAuthFactory = io::AuthorityFactory::create(
- authFactory->databaseContext(), authName);
- auto resTmp =
- tmpAuthFactory->createFromCoordinateReferenceSystemCodes(
- srcAuthName, srcCode, targetAuthName, targetCode,
- context.context->getUsePROJAlternativeGridNames(),
- gridAvailabilityUse ==
- CoordinateOperationContext::
- GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID ||
- gridAvailabilityUse ==
- CoordinateOperationContext::
- GridAvailabilityUse::KNOWN_AVAILABLE,
- gridAvailabilityUse ==
- CoordinateOperationContext::GridAvailabilityUse::
- KNOWN_AVAILABLE,
- context.context->getDiscardSuperseded(), true, false,
- context.extent1, context.extent2);
- res.insert(res.end(), resTmp.begin(), resTmp.end());
- if (authName == "PROJ") {
- continue;
- }
- if (!res.empty()) {
- resNonEmptyBeforeFiltering = true;
- auto resFiltered =
- FilterResults(res, context.context, context.extent1,
- context.extent2, false)
- .getRes();
-#ifdef TRACE_CREATE_OPERATIONS
- logTrace("filtering reduced from " +
- toString(static_cast<int>(res.size())) + " to " +
- toString(static_cast<int>(resFiltered.size())));
-#endif
- return resFiltered;
- }
- }
- }
- }
- return std::vector<CoordinateOperationNNPtr>();
-}
-
-// ---------------------------------------------------------------------------
-
-// Look in the authority registry for operations to targetCRS
-std::vector<CoordinateOperationNNPtr>
-CoordinateOperationFactory::Private::findOpsInRegistryDirectTo(
- const crs::CRSNNPtr &targetCRS, Private::Context &context) {
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("findOpsInRegistryDirectTo({any} -->" +
- objectAsStr(targetCRS.get()) + ")");
-#endif
-
- const auto &authFactory = context.context->getAuthorityFactory();
- assert(authFactory);
-
- std::list<std::pair<std::string, std::string>> ids;
- buildCRSIds(targetCRS, context, ids);
-
- const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
- for (const auto &id : ids) {
- const auto &targetAuthName = id.first;
- const auto &targetCode = id.second;
-
- const auto authorities(getCandidateAuthorities(
- authFactory, targetAuthName, targetAuthName));
- for (const auto &authority : authorities) {
- const auto tmpAuthFactory = io::AuthorityFactory::create(
- authFactory->databaseContext(),
- authority == "any" ? std::string() : authority);
- auto res = tmpAuthFactory->createFromCoordinateReferenceSystemCodes(
- std::string(), std::string(), targetAuthName, targetCode,
- context.context->getUsePROJAlternativeGridNames(),
-
- gridAvailabilityUse ==
- CoordinateOperationContext::GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID ||
- gridAvailabilityUse ==
- CoordinateOperationContext::GridAvailabilityUse::
- KNOWN_AVAILABLE,
- gridAvailabilityUse == CoordinateOperationContext::
- GridAvailabilityUse::KNOWN_AVAILABLE,
- context.context->getDiscardSuperseded(), true, true,
- context.extent1, context.extent2);
- if (!res.empty()) {
- auto resFiltered =
- FilterResults(res, context.context, context.extent1,
- context.extent2, false)
- .getRes();
-#ifdef TRACE_CREATE_OPERATIONS
- logTrace("filtering reduced from " +
- toString(static_cast<int>(res.size())) + " to " +
- toString(static_cast<int>(resFiltered.size())));
-#endif
- return resFiltered;
- }
- }
- }
- return std::vector<CoordinateOperationNNPtr>();
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-// Look in the authority registry for operations from sourceCRS to targetCRS
-// using an intermediate pivot
-std::vector<CoordinateOperationNNPtr>
-CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context,
- bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) {
-
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("findsOpsInRegistryWithIntermediate(" +
- objectAsStr(sourceCRS.get()) + " --> " +
- objectAsStr(targetCRS.get()) + ")");
-#endif
-
- const auto &authFactory = context.context->getAuthorityFactory();
- assert(authFactory);
-
- std::list<std::pair<std::string, std::string>> sourceIds;
- std::list<std::pair<std::string, std::string>> targetIds;
- buildCRSIds(sourceCRS, context, sourceIds);
- buildCRSIds(targetCRS, context, targetIds);
-
- const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
- for (const auto &idSrc : sourceIds) {
- const auto &srcAuthName = idSrc.first;
- const auto &srcCode = idSrc.second;
- for (const auto &idTarget : targetIds) {
- const auto &targetAuthName = idTarget.first;
- const auto &targetCode = idTarget.second;
-
- const auto authorities(getCandidateAuthorities(
- authFactory, srcAuthName, targetAuthName));
- assert(!authorities.empty());
-
- const auto tmpAuthFactory = io::AuthorityFactory::create(
- authFactory->databaseContext(),
- (authFactory->getAuthority() == "any" || authorities.size() > 1)
- ? std::string()
- : authorities.front());
-
- std::vector<CoordinateOperationNNPtr> res;
- if (useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) {
- res =
- tmpAuthFactory
- ->createBetweenGeodeticCRSWithDatumBasedIntermediates(
- sourceCRS, srcAuthName, srcCode, targetCRS,
- targetAuthName, targetCode,
- context.context->getUsePROJAlternativeGridNames(),
- gridAvailabilityUse ==
- CoordinateOperationContext::
- GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID ||
- gridAvailabilityUse ==
- CoordinateOperationContext::
- GridAvailabilityUse::KNOWN_AVAILABLE,
- gridAvailabilityUse ==
- CoordinateOperationContext::
- GridAvailabilityUse::KNOWN_AVAILABLE,
- context.context->getDiscardSuperseded(),
- authFactory->getAuthority() != "any" &&
- authorities.size() > 1
- ? authorities
- : std::vector<std::string>(),
- context.extent1, context.extent2);
- } else {
- io::AuthorityFactory::ObjectType intermediateObjectType =
- io::AuthorityFactory::ObjectType::CRS;
-
- // If doing GeogCRS --> GeogCRS, only use GeogCRS as
- // intermediate CRS
- // Avoid weird behavior when doing NAD83 -> NAD83(2011)
- // that would go through NAVD88 otherwise.
- if (context.context->getIntermediateCRS().empty() &&
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()) &&
- dynamic_cast<const crs::GeographicCRS *>(targetCRS.get())) {
- intermediateObjectType =
- io::AuthorityFactory::ObjectType::GEOGRAPHIC_CRS;
- }
- res = tmpAuthFactory->createFromCRSCodesWithIntermediates(
- srcAuthName, srcCode, targetAuthName, targetCode,
- context.context->getUsePROJAlternativeGridNames(),
- gridAvailabilityUse ==
- CoordinateOperationContext::GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID ||
- gridAvailabilityUse ==
- CoordinateOperationContext::GridAvailabilityUse::
- KNOWN_AVAILABLE,
- gridAvailabilityUse ==
- CoordinateOperationContext::GridAvailabilityUse::
- KNOWN_AVAILABLE,
- context.context->getDiscardSuperseded(),
- context.context->getIntermediateCRS(),
- intermediateObjectType,
- authFactory->getAuthority() != "any" &&
- authorities.size() > 1
- ? authorities
- : std::vector<std::string>(),
- context.extent1, context.extent2);
- }
- if (!res.empty()) {
-
- auto resFiltered =
- FilterResults(res, context.context, context.extent1,
- context.extent2, false)
- .getRes();
-#ifdef TRACE_CREATE_OPERATIONS
- logTrace("filtering reduced from " +
- toString(static_cast<int>(res.size())) + " to " +
- toString(static_cast<int>(resFiltered.size())));
-#endif
- return resFiltered;
- }
- }
- }
- return std::vector<CoordinateOperationNNPtr>();
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-static TransformationNNPtr
-createBallparkGeographicOffset(const crs::CRSNNPtr &sourceCRS,
- const crs::CRSNNPtr &targetCRS,
- const io::DatabaseContextPtr &dbContext) {
-
- const crs::GeographicCRS *geogSrc =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
- const crs::GeographicCRS *geogDst =
- dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
- const bool isSameDatum = geogSrc && geogDst &&
- geogSrc->datumNonNull(dbContext)->_isEquivalentTo(
- geogDst->datumNonNull(dbContext).get(),
- util::IComparable::Criterion::EQUIVALENT);
-
- auto name = buildOpName(isSameDatum ? NULL_GEOGRAPHIC_OFFSET
- : BALLPARK_GEOGRAPHIC_OFFSET,
- sourceCRS, targetCRS);
-
- const auto &sourceCRSExtent = getExtent(sourceCRS);
- const auto &targetCRSExtent = getExtent(targetCRS);
- const bool sameExtent =
- sourceCRSExtent && targetCRSExtent &&
- sourceCRSExtent->_isEquivalentTo(
- targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT);
-
- util::PropertyMap map;
- map.set(common::IdentifiedObject::NAME_KEY, name)
- .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- sameExtent ? NN_NO_CHECK(sourceCRSExtent)
- : metadata::Extent::WORLD);
- const common::Angle angle0(0);
-
- std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
- if (isSameDatum) {
- accuracies.emplace_back(metadata::PositionalAccuracy::create("0"));
- }
-
- if (dynamic_cast<const crs::SingleCRS *>(sourceCRS.get())
- ->coordinateSystem()
- ->axisList()
- .size() == 3 ||
- dynamic_cast<const crs::SingleCRS *>(targetCRS.get())
- ->coordinateSystem()
- ->axisList()
- .size() == 3) {
- return Transformation::createGeographic3DOffsets(
- map, sourceCRS, targetCRS, angle0, angle0, common::Length(0),
- accuracies);
- } else {
- return Transformation::createGeographic2DOffsets(
- map, sourceCRS, targetCRS, angle0, angle0, accuracies);
- }
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-// ---------------------------------------------------------------------------
-
-struct MyPROJStringExportableGeodToGeod final
- : public io::IPROJStringExportable {
- crs::GeodeticCRSPtr geodSrc{};
- crs::GeodeticCRSPtr geodDst{};
-
- MyPROJStringExportableGeodToGeod(const crs::GeodeticCRSPtr &geodSrcIn,
- const crs::GeodeticCRSPtr &geodDstIn)
- : geodSrc(geodSrcIn), geodDst(geodDstIn) {}
-
- ~MyPROJStringExportableGeodToGeod() override;
-
- void
- // cppcheck-suppress functionStatic
- _exportToPROJString(io::PROJStringFormatter *formatter) const override {
-
- formatter->startInversion();
- geodSrc->_exportToPROJString(formatter);
- formatter->stopInversion();
- geodDst->_exportToPROJString(formatter);
- }
-};
-
-MyPROJStringExportableGeodToGeod::~MyPROJStringExportableGeodToGeod() = default;
-
-// ---------------------------------------------------------------------------
-
-struct MyPROJStringExportableHorizVertical final
- : public io::IPROJStringExportable {
- CoordinateOperationPtr horizTransform{};
- CoordinateOperationPtr verticalTransform{};
- crs::GeographicCRSPtr geogDst{};
-
- MyPROJStringExportableHorizVertical(
- const CoordinateOperationPtr &horizTransformIn,
- const CoordinateOperationPtr &verticalTransformIn,
- const crs::GeographicCRSPtr &geogDstIn)
- : horizTransform(horizTransformIn),
- verticalTransform(verticalTransformIn), geogDst(geogDstIn) {}
-
- ~MyPROJStringExportableHorizVertical() override;
-
- void
- // cppcheck-suppress functionStatic
- _exportToPROJString(io::PROJStringFormatter *formatter) const override {
-
- formatter->pushOmitZUnitConversion();
-
- horizTransform->_exportToPROJString(formatter);
-
- formatter->startInversion();
- geogDst->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->stopInversion();
-
- formatter->popOmitZUnitConversion();
-
- formatter->pushOmitHorizontalConversionInVertTransformation();
- verticalTransform->_exportToPROJString(formatter);
- formatter->popOmitHorizontalConversionInVertTransformation();
-
- formatter->pushOmitZUnitConversion();
- geogDst->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->popOmitZUnitConversion();
- }
-};
-
-MyPROJStringExportableHorizVertical::~MyPROJStringExportableHorizVertical() =
- default;
-
-// ---------------------------------------------------------------------------
-
-struct MyPROJStringExportableHorizVerticalHorizPROJBased final
- : public io::IPROJStringExportable {
- CoordinateOperationPtr opSrcCRSToGeogCRS{};
- CoordinateOperationPtr verticalTransform{};
- CoordinateOperationPtr opGeogCRStoDstCRS{};
- crs::GeographicCRSPtr interpolationGeogCRS{};
-
- MyPROJStringExportableHorizVerticalHorizPROJBased(
- const CoordinateOperationPtr &opSrcCRSToGeogCRSIn,
- const CoordinateOperationPtr &verticalTransformIn,
- const CoordinateOperationPtr &opGeogCRStoDstCRSIn,
- const crs::GeographicCRSPtr &interpolationGeogCRSIn)
- : opSrcCRSToGeogCRS(opSrcCRSToGeogCRSIn),
- verticalTransform(verticalTransformIn),
- opGeogCRStoDstCRS(opGeogCRStoDstCRSIn),
- interpolationGeogCRS(interpolationGeogCRSIn) {}
-
- ~MyPROJStringExportableHorizVerticalHorizPROJBased() override;
-
- void
- // cppcheck-suppress functionStatic
- _exportToPROJString(io::PROJStringFormatter *formatter) const override {
-
- formatter->pushOmitZUnitConversion();
-
- opSrcCRSToGeogCRS->_exportToPROJString(formatter);
-
- formatter->startInversion();
- interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter);
- formatter->stopInversion();
-
- formatter->popOmitZUnitConversion();
-
- formatter->pushOmitHorizontalConversionInVertTransformation();
- verticalTransform->_exportToPROJString(formatter);
- formatter->popOmitHorizontalConversionInVertTransformation();
-
- formatter->pushOmitZUnitConversion();
-
- interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter);
-
- opGeogCRStoDstCRS->_exportToPROJString(formatter);
-
- formatter->popOmitZUnitConversion();
- }
-};
-
-MyPROJStringExportableHorizVerticalHorizPROJBased::
- ~MyPROJStringExportableHorizVerticalHorizPROJBased() = default;
-
-//! @endcond
-
-} // namespace operation
-NS_PROJ_END
-
-#if 0
-namespace dropbox{ namespace oxygen {
-template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableGeodToGeod>>::~nn() = default;
-template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVertical>>::~nn() = default;
-template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVerticalHorizPROJBased>>::~nn() = default;
-}}
-#endif
-
-NS_PROJ_START
-namespace operation {
-
-//! @cond Doxygen_Suppress
-
-// ---------------------------------------------------------------------------
-
-static std::string buildTransfName(const std::string &srcName,
- const std::string &targetName) {
- std::string name("Transformation from ");
- name += srcName;
- name += " to ";
- name += targetName;
- return name;
-}
-
-// ---------------------------------------------------------------------------
-
-static CoordinateOperationNNPtr
-createGeodToGeodPROJBased(const crs::CRSNNPtr &geodSrc,
- const crs::CRSNNPtr &geodDst) {
-
- auto exportable = util::nn_make_shared<MyPROJStringExportableGeodToGeod>(
- util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodSrc),
- util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodDst));
-
- auto properties = util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- buildTransfName(geodSrc->nameStr(), geodDst->nameStr()));
- return createPROJBased(properties, exportable, geodSrc, geodDst, nullptr,
- {}, false);
-}
-
-// ---------------------------------------------------------------------------
-
-static std::string
-getRemarks(const std::vector<operation::CoordinateOperationNNPtr> &ops) {
- std::string remarks;
- for (const auto &op : ops) {
- const auto &opRemarks = op->remarks();
- if (!opRemarks.empty()) {
- if (!remarks.empty()) {
- remarks += '\n';
- }
-
- std::string opName(op->nameStr());
- if (starts_with(opName, INVERSE_OF)) {
- opName = opName.substr(INVERSE_OF.size());
- }
-
- remarks += "For ";
- remarks += opName;
-
- const auto &ids = op->identifiers();
- if (!ids.empty()) {
- std::string authority(*ids.front()->codeSpace());
- if (starts_with(authority, "INVERSE(") &&
- authority.back() == ')') {
- authority = authority.substr(strlen("INVERSE("),
- authority.size() - 1 -
- strlen("INVERSE("));
- }
- if (starts_with(authority, "DERIVED_FROM(") &&
- authority.back() == ')') {
- authority = authority.substr(strlen("DERIVED_FROM("),
- authority.size() - 1 -
- strlen("DERIVED_FROM("));
- }
-
- remarks += " (";
- remarks += authority;
- remarks += ':';
- remarks += ids.front()->code();
- remarks += ')';
- }
- remarks += ": ";
- remarks += opRemarks;
- }
- }
- return remarks;
-}
-
-// ---------------------------------------------------------------------------
-
-static CoordinateOperationNNPtr createHorizVerticalPROJBased(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- const operation::CoordinateOperationNNPtr &horizTransform,
- const operation::CoordinateOperationNNPtr &verticalTransform,
- bool checkExtent) {
-
- auto geogDst = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(targetCRS);
- assert(geogDst);
-
- auto exportable = util::nn_make_shared<MyPROJStringExportableHorizVertical>(
- horizTransform, verticalTransform, geogDst);
-
- const bool horizTransformIsNoOp =
- starts_with(horizTransform->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
- horizTransform->nameStr().find(" + ") == std::string::npos;
- if (horizTransformIsNoOp) {
- auto properties = util::PropertyMap();
- properties.set(common::IdentifiedObject::NAME_KEY,
- verticalTransform->nameStr());
- bool dummy = false;
- auto extent = getExtent(verticalTransform, true, dummy);
- if (extent) {
- properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- NN_NO_CHECK(extent));
- }
- const auto &remarks = verticalTransform->remarks();
- if (!remarks.empty()) {
- properties.set(common::IdentifiedObject::REMARKS_KEY, remarks);
- }
- return createPROJBased(
- properties, exportable, sourceCRS, targetCRS, nullptr,
- verticalTransform->coordinateOperationAccuracies(),
- verticalTransform->hasBallparkTransformation());
- } else {
- bool emptyIntersection = false;
- auto ops = std::vector<CoordinateOperationNNPtr>{horizTransform,
- verticalTransform};
- auto extent = getExtent(ops, true, emptyIntersection);
- if (checkExtent && emptyIntersection) {
- std::string msg(
- "empty intersection of area of validity of concatenated "
- "operations");
- throw InvalidOperationEmptyIntersection(msg);
- }
- auto properties = util::PropertyMap();
- properties.set(common::IdentifiedObject::NAME_KEY,
- computeConcatenatedName(ops));
-
- if (extent) {
- properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- NN_NO_CHECK(extent));
- }
-
- const auto remarks = getRemarks(ops);
- if (!remarks.empty()) {
- properties.set(common::IdentifiedObject::REMARKS_KEY, remarks);
- }
-
- std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
- const double accuracy = getAccuracy(ops);
- if (accuracy >= 0.0) {
- accuracies.emplace_back(
- metadata::PositionalAccuracy::create(toString(accuracy)));
- }
-
- return createPROJBased(
- properties, exportable, sourceCRS, targetCRS, nullptr, accuracies,
- horizTransform->hasBallparkTransformation() ||
- verticalTransform->hasBallparkTransformation());
- }
-}
-
-// ---------------------------------------------------------------------------
-
-static CoordinateOperationNNPtr createHorizVerticalHorizPROJBased(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- const operation::CoordinateOperationNNPtr &opSrcCRSToGeogCRS,
- const operation::CoordinateOperationNNPtr &verticalTransform,
- const operation::CoordinateOperationNNPtr &opGeogCRStoDstCRS,
- const crs::GeographicCRSPtr &interpolationGeogCRS, bool checkExtent) {
-
- auto exportable =
- util::nn_make_shared<MyPROJStringExportableHorizVerticalHorizPROJBased>(
- opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS,
- interpolationGeogCRS);
-
- std::vector<CoordinateOperationNNPtr> ops;
- if (!(starts_with(opSrcCRSToGeogCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
- opSrcCRSToGeogCRS->nameStr().find(" + ") == std::string::npos)) {
- ops.emplace_back(opSrcCRSToGeogCRS);
- }
- ops.emplace_back(verticalTransform);
- if (!(starts_with(opGeogCRStoDstCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
- opGeogCRStoDstCRS->nameStr().find(" + ") == std::string::npos)) {
- ops.emplace_back(opGeogCRStoDstCRS);
- }
-
- bool hasBallparkTransformation = false;
- for (const auto &op : ops) {
- hasBallparkTransformation |= op->hasBallparkTransformation();
- }
- bool emptyIntersection = false;
- auto extent = getExtent(ops, false, emptyIntersection);
- if (checkExtent && emptyIntersection) {
- std::string msg(
- "empty intersection of area of validity of concatenated "
- "operations");
- throw InvalidOperationEmptyIntersection(msg);
- }
- auto properties = util::PropertyMap();
- properties.set(common::IdentifiedObject::NAME_KEY,
- computeConcatenatedName(ops));
-
- if (extent) {
- properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- NN_NO_CHECK(extent));
- }
-
- const auto remarks = getRemarks(ops);
- if (!remarks.empty()) {
- properties.set(common::IdentifiedObject::REMARKS_KEY, remarks);
- }
-
- std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
- const double accuracy = getAccuracy(ops);
- if (accuracy >= 0.0) {
- accuracies.emplace_back(
- metadata::PositionalAccuracy::create(toString(accuracy)));
- }
-
- return createPROJBased(properties, exportable, sourceCRS, targetCRS,
- nullptr, accuracies, hasBallparkTransformation);
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-std::vector<CoordinateOperationNNPtr>
-CoordinateOperationFactory::Private::createOperationsGeogToGeog(
- std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS,
- const crs::CRSNNPtr &targetCRS, Private::Context &context,
- const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst) {
-
- assert(sourceCRS.get() == geogSrc);
- assert(targetCRS.get() == geogDst);
-
- const auto &src_pm = geogSrc->primeMeridian()->longitude();
- const auto &dst_pm = geogDst->primeMeridian()->longitude();
- auto offset_pm =
- (src_pm.unit() == dst_pm.unit())
- ? common::Angle(src_pm.value() - dst_pm.value(), src_pm.unit())
- : common::Angle(
- src_pm.convertToUnit(common::UnitOfMeasure::DEGREE) -
- dst_pm.convertToUnit(common::UnitOfMeasure::DEGREE),
- common::UnitOfMeasure::DEGREE);
-
- double vconvSrc = 1.0;
- const auto &srcCS = geogSrc->coordinateSystem();
- const auto &srcAxisList = srcCS->axisList();
- if (srcAxisList.size() == 3) {
- vconvSrc = srcAxisList[2]->unit().conversionToSI();
- }
- double vconvDst = 1.0;
- const auto &dstCS = geogDst->coordinateSystem();
- const auto &dstAxisList = dstCS->axisList();
- if (dstAxisList.size() == 3) {
- vconvDst = dstAxisList[2]->unit().conversionToSI();
- }
-
- std::string name(buildTransfName(geogSrc->nameStr(), geogDst->nameStr()));
-
- const auto &authFactory = context.context->getAuthorityFactory();
- const auto dbContext =
- authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
-
- const bool sameDatum = geogSrc->datumNonNull(dbContext)->_isEquivalentTo(
- geogDst->datumNonNull(dbContext).get(),
- util::IComparable::Criterion::EQUIVALENT);
-
- // Do the CRS differ by their axis order ?
- bool axisReversal2D = false;
- bool axisReversal3D = false;
- if (!srcCS->_isEquivalentTo(dstCS.get(),
- util::IComparable::Criterion::EQUIVALENT)) {
- auto srcOrder = srcCS->axisOrder();
- auto dstOrder = dstCS->axisOrder();
- if (((srcOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST ||
- srcOrder == cs::EllipsoidalCS::AxisOrder::
- LAT_NORTH_LONG_EAST_HEIGHT_UP) &&
- (dstOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
- dstOrder == cs::EllipsoidalCS::AxisOrder::
- LONG_EAST_LAT_NORTH_HEIGHT_UP)) ||
- ((srcOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
- srcOrder == cs::EllipsoidalCS::AxisOrder::
- LONG_EAST_LAT_NORTH_HEIGHT_UP) &&
- (dstOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST ||
- dstOrder == cs::EllipsoidalCS::AxisOrder::
- LAT_NORTH_LONG_EAST_HEIGHT_UP))) {
- if (srcAxisList.size() == 3 || dstAxisList.size() == 3)
- axisReversal3D = true;
- else
- axisReversal2D = true;
- }
- }
-
- // Do they differ by vertical units ?
- if (vconvSrc != vconvDst &&
- geogSrc->ellipsoid()->_isEquivalentTo(
- geogDst->ellipsoid().get(),
- util::IComparable::Criterion::EQUIVALENT)) {
- if (offset_pm.value() == 0 && !axisReversal2D && !axisReversal3D) {
- // If only by vertical units, use a Change of Vertical
- // Unit
- // transformation
- const double factor = vconvSrc / vconvDst;
- auto conv = Conversion::createChangeVerticalUnit(
- util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
- name),
- common::Scale(factor));
- conv->setCRSs(sourceCRS, targetCRS, nullptr);
- conv->setHasBallparkTransformation(!sameDatum);
- res.push_back(conv);
- return res;
- } else {
- auto op = createGeodToGeodPROJBased(sourceCRS, targetCRS);
- op->setHasBallparkTransformation(!sameDatum);
- res.emplace_back(op);
- return res;
- }
- }
-
- // Do the CRS differ only by their axis order ?
- if (sameDatum && (axisReversal2D || axisReversal3D)) {
- auto conv = Conversion::createAxisOrderReversal(axisReversal3D);
- conv->setCRSs(sourceCRS, targetCRS, nullptr);
- res.emplace_back(conv);
- return res;
- }
-
- std::vector<CoordinateOperationNNPtr> steps;
- // If both are geographic and only differ by their prime
- // meridian,
- // apply a longitude rotation transformation.
- if (geogSrc->ellipsoid()->_isEquivalentTo(
- geogDst->ellipsoid().get(),
- util::IComparable::Criterion::EQUIVALENT) &&
- src_pm.getSIValue() != dst_pm.getSIValue()) {
-
- steps.emplace_back(Transformation::createLongitudeRotation(
- util::PropertyMap()
- .set(common::IdentifiedObject::NAME_KEY, name)
- .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- metadata::Extent::WORLD),
- sourceCRS, targetCRS, offset_pm));
- // If only the target has a non-zero prime meridian, chain a
- // null geographic offset and then the longitude rotation
- } else if (src_pm.getSIValue() == 0 && dst_pm.getSIValue() != 0) {
- auto datum = datum::GeodeticReferenceFrame::create(
- util::PropertyMap(), geogDst->ellipsoid(),
- util::optional<std::string>(), geogSrc->primeMeridian());
- std::string interm_crs_name(geogDst->nameStr());
- interm_crs_name += " altered to use prime meridian of ";
- interm_crs_name += geogSrc->nameStr();
- auto interm_crs =
- util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create(
- util::PropertyMap()
- .set(common::IdentifiedObject::NAME_KEY, interm_crs_name)
- .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- metadata::Extent::WORLD),
- datum, dstCS));
-
- steps.emplace_back(
- createBallparkGeographicOffset(sourceCRS, interm_crs, dbContext));
-
- steps.emplace_back(Transformation::createLongitudeRotation(
- util::PropertyMap()
- .set(common::IdentifiedObject::NAME_KEY,
- buildTransfName(geogSrc->nameStr(), interm_crs->nameStr()))
- .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- metadata::Extent::WORLD),
- interm_crs, targetCRS, offset_pm));
-
- } else {
- // If the prime meridians are different, chain a longitude
- // rotation and the null geographic offset.
- if (src_pm.getSIValue() != dst_pm.getSIValue()) {
- auto datum = datum::GeodeticReferenceFrame::create(
- util::PropertyMap(), geogSrc->ellipsoid(),
- util::optional<std::string>(), geogDst->primeMeridian());
- std::string interm_crs_name(geogSrc->nameStr());
- interm_crs_name += " altered to use prime meridian of ";
- interm_crs_name += geogDst->nameStr();
- auto interm_crs = util::nn_static_pointer_cast<crs::CRS>(
- crs::GeographicCRS::create(
- util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
- interm_crs_name),
- datum, srcCS));
-
- steps.emplace_back(Transformation::createLongitudeRotation(
- util::PropertyMap()
- .set(common::IdentifiedObject::NAME_KEY,
- buildTransfName(geogSrc->nameStr(),
- interm_crs->nameStr()))
- .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- metadata::Extent::WORLD),
- sourceCRS, interm_crs, offset_pm));
- steps.emplace_back(createBallparkGeographicOffset(
- interm_crs, targetCRS, dbContext));
- } else {
- steps.emplace_back(createBallparkGeographicOffset(
- sourceCRS, targetCRS, dbContext));
- }
- }
-
- auto op = ConcatenatedOperation::createComputeMetadata(
- steps, disallowEmptyIntersection);
- op->setHasBallparkTransformation(!sameDatum);
- res.emplace_back(op);
- return res;
-}
-
-// ---------------------------------------------------------------------------
-
-static bool hasIdentifiers(const CoordinateOperationNNPtr &op) {
- if (!op->identifiers().empty()) {
- return true;
- }
- auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
- if (concatenated) {
- for (const auto &subOp : concatenated->operations()) {
- if (hasIdentifiers(subOp)) {
- return true;
- }
- }
- }
- return false;
-}
-
-// ---------------------------------------------------------------------------
-
-static std::vector<crs::CRSNNPtr>
-findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory,
- const crs::GeodeticCRS *crs,
- const datum::GeodeticReferenceFrame *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()) {
- const auto crsIds = crs->identifiers();
- const auto tmpFactory =
- (crsIds.size() == 1 &&
- *(crsIds.front()->codeSpace()) == authName)
- ? io::AuthorityFactory::create(
- authFactory->databaseContext(), authName)
- .as_nullable()
- : authFactory;
- auto l_candidates = tmpFactory->createGeodeticCRSFromDatum(
- authName, code, std::string());
- for (const auto &candidate : l_candidates) {
- candidates.emplace_back(candidate);
- }
- }
- }
- } else if (datumName != "unknown" && datumName != "unnamed") {
- auto matches = authFactory->createObjectsFromName(
- datumName,
- {io::AuthorityFactory::ObjectType::GEODETIC_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 findCandidateGeodCRSForDatum(
- authFactory, crs,
- dynamic_cast<const datum::GeodeticReferenceFrame *>(
- match.get()));
- }
- }
- }
- return candidates;
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::setCRSs(
- CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS,
- const crs::CRSNNPtr &targetCRS) {
- co->setCRSs(sourceCRS, targetCRS, nullptr);
-
- auto invCO = dynamic_cast<InverseCoordinateOperation *>(co);
- if (invCO) {
- invCO->forwardOperation()->setCRSs(targetCRS, sourceCRS, nullptr);
- }
-
- auto transf = dynamic_cast<Transformation *>(co);
- if (transf) {
- transf->inverseAsTransformation()->setCRSs(targetCRS, sourceCRS,
- nullptr);
- }
-
- auto concat = dynamic_cast<ConcatenatedOperation *>(co);
- if (concat) {
- auto first = concat->operations().front().get();
- auto &firstTarget(first->targetCRS());
- if (firstTarget) {
- setCRSs(first, sourceCRS, NN_NO_CHECK(firstTarget));
- }
- auto last = concat->operations().back().get();
- auto &lastSource(last->sourceCRS());
- if (lastSource) {
- setCRSs(last, NN_NO_CHECK(lastSource), targetCRS);
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-static bool hasResultSetOnlyResultsWithPROJStep(
- const std::vector<CoordinateOperationNNPtr> &res) {
- for (const auto &op : res) {
- auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get());
- if (concat) {
- bool hasPROJStep = false;
- const auto &steps = concat->operations();
- for (const auto &step : steps) {
- const auto &ids = step->identifiers();
- if (!ids.empty()) {
- const auto &opAuthority = *(ids.front()->codeSpace());
- if (opAuthority == "PROJ" ||
- opAuthority == "INVERSE(PROJ)" ||
- opAuthority == "DERIVED_FROM(PROJ)") {
- hasPROJStep = true;
- break;
- }
- }
- }
- if (!hasPROJStep) {
- return false;
- }
- } else {
- return false;
- }
- }
- return true;
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsWithDatumPivot(
- std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS,
- const crs::CRSNNPtr &targetCRS, const crs::GeodeticCRS *geodSrc,
- const crs::GeodeticCRS *geodDst, Private::Context &context) {
-
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("createOperationsWithDatumPivot(" +
- objectAsStr(sourceCRS.get()) + "," +
- objectAsStr(targetCRS.get()) + ")");
-#endif
-
- struct CreateOperationsWithDatumPivotAntiRecursion {
- Context &context;
-
- explicit CreateOperationsWithDatumPivotAntiRecursion(Context &contextIn)
- : context(contextIn) {
- assert(!context.inCreateOperationsWithDatumPivotAntiRecursion);
- context.inCreateOperationsWithDatumPivotAntiRecursion = true;
- }
-
- ~CreateOperationsWithDatumPivotAntiRecursion() {
- context.inCreateOperationsWithDatumPivotAntiRecursion = false;
- }
- };
- CreateOperationsWithDatumPivotAntiRecursion guard(context);
-
- const auto &authFactory = context.context->getAuthorityFactory();
- const auto &dbContext = authFactory->databaseContext();
-
- const auto candidatesSrcGeod(findCandidateGeodCRSForDatum(
- authFactory, geodSrc,
- geodSrc->datumNonNull(dbContext.as_nullable()).get()));
- const auto candidatesDstGeod(findCandidateGeodCRSForDatum(
- authFactory, geodDst,
- geodDst->datumNonNull(dbContext.as_nullable()).get()));
-
- const bool sourceAndTargetAre3D =
- geodSrc->coordinateSystem()->axisList().size() == 3 &&
- geodDst->coordinateSystem()->axisList().size() == 3;
-
- auto createTransformations = [&](const crs::CRSNNPtr &candidateSrcGeod,
- const crs::CRSNNPtr &candidateDstGeod,
- const CoordinateOperationNNPtr &opFirst,
- bool isNullFirst) {
- const auto opsSecond =
- createOperations(candidateSrcGeod, candidateDstGeod, context);
- const auto opsThird =
- createOperations(candidateDstGeod, targetCRS, context);
- assert(!opsThird.empty());
-
- for (auto &opSecond : opsSecond) {
- // Check that it is not a transformation synthetized by
- // ourselves
- if (!hasIdentifiers(opSecond)) {
- continue;
- }
- // And even if it is a referenced transformation, check that
- // it is not a trivial one
- auto so = dynamic_cast<const SingleOperation *>(opSecond.get());
- if (so && isAxisOrderReversal(so->method()->getEPSGCode())) {
- continue;
- }
-
- std::vector<CoordinateOperationNNPtr> subOps;
- const bool isNullThird =
- isNullTransformation(opsThird[0]->nameStr());
- CoordinateOperationNNPtr opSecondCloned(
- (isNullFirst || isNullThird || sourceAndTargetAre3D)
- ? opSecond->shallowClone()
- : opSecond);
- if (isNullFirst || isNullThird) {
- if (opSecondCloned->identifiers().size() == 1 &&
- (*opSecondCloned->identifiers()[0]->codeSpace())
- .find("DERIVED_FROM") == std::string::npos) {
- {
- util::PropertyMap map;
- addModifiedIdentifier(map, opSecondCloned.get(), false,
- true);
- opSecondCloned->setProperties(map);
- }
- auto invCO = dynamic_cast<InverseCoordinateOperation *>(
- opSecondCloned.get());
- if (invCO) {
- auto invCOForward = invCO->forwardOperation().get();
- if (invCOForward->identifiers().size() == 1 &&
- (*invCOForward->identifiers()[0]->codeSpace())
- .find("DERIVED_FROM") ==
- std::string::npos) {
- util::PropertyMap map;
- addModifiedIdentifier(map, invCOForward, false,
- true);
- invCOForward->setProperties(map);
- }
- }
- }
- }
- if (sourceAndTargetAre3D) {
- opSecondCloned->getPrivate()->use3DHelmert_ = true;
- auto invCO = dynamic_cast<InverseCoordinateOperation *>(
- opSecondCloned.get());
- if (invCO) {
- auto invCOForward = invCO->forwardOperation().get();
- invCOForward->getPrivate()->use3DHelmert_ = true;
- }
- }
- if (isNullFirst) {
- auto oldTarget(NN_CHECK_ASSERT(opSecondCloned->targetCRS()));
- setCRSs(opSecondCloned.get(), sourceCRS, oldTarget);
- } else {
- subOps.emplace_back(opFirst);
- }
- if (isNullThird) {
- auto oldSource(NN_CHECK_ASSERT(opSecondCloned->sourceCRS()));
- setCRSs(opSecondCloned.get(), oldSource, targetCRS);
- subOps.emplace_back(opSecondCloned);
- } else {
- subOps.emplace_back(opSecondCloned);
- subOps.emplace_back(opsThird[0]);
- }
-#ifdef TRACE_CREATE_OPERATIONS
- std::string debugStr;
- for (const auto &op : subOps) {
- if (!debugStr.empty()) {
- debugStr += " + ";
- }
- debugStr += objectAsStr(op.get());
- debugStr += " (";
- debugStr += objectAsStr(op->sourceCRS().get());
- debugStr += "->";
- debugStr += objectAsStr(op->targetCRS().get());
- debugStr += ")";
- }
- logTrace("transformation " + debugStr);
-#endif
- try {
- res.emplace_back(ConcatenatedOperation::createComputeMetadata(
- subOps, disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- }
- };
-
- // Start in priority with candidates that have exactly the same name as
- // the sourcCRS and targetCRS. Typically for the case of init=IGNF:XXXX
-
- // Transformation from IGNF:NTFP to IGNF:RGF93G,
- // using
- // NTF geographiques Paris (gr) vers NTF GEOGRAPHIQUES GREENWICH (DMS) +
- // NOUVELLE TRIANGULATION DE LA FRANCE (NTF) vers RGF93 (ETRS89)
- // that is using ntf_r93.gsb, is horribly dependent
- // of IGNF:RGF93G being returned before IGNF:RGF93GEO in candidatesDstGeod.
- // If RGF93GEO is returned before then we go through WGS84 and use
- // instead a Helmert transformation.
- // The below logic is thus quite fragile, and attempts at changing it
- // result in degraded results for other use cases...
-
- for (const auto &candidateSrcGeod : candidatesSrcGeod) {
- if (candidateSrcGeod->nameStr() == sourceCRS->nameStr()) {
- for (const auto &candidateDstGeod : candidatesDstGeod) {
- if (candidateDstGeod->nameStr() == targetCRS->nameStr()) {
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("try " + objectAsStr(sourceCRS.get()) + "->" +
- objectAsStr(candidateSrcGeod.get()) + "->" +
- objectAsStr(candidateDstGeod.get()) + "->" +
- objectAsStr(targetCRS.get()) + ")");
-#endif
- const auto opsFirst =
- createOperations(sourceCRS, candidateSrcGeod, context);
- assert(!opsFirst.empty());
- const bool isNullFirst =
- isNullTransformation(opsFirst[0]->nameStr());
- createTransformations(candidateSrcGeod, candidateDstGeod,
- opsFirst[0], isNullFirst);
- if (!res.empty()) {
- if (hasResultSetOnlyResultsWithPROJStep(res)) {
- continue;
- }
- return;
- }
- }
- }
- }
- }
-
- for (const auto &candidateSrcGeod : candidatesSrcGeod) {
- const bool bSameSrcName =
- candidateSrcGeod->nameStr() == sourceCRS->nameStr();
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("");
-#endif
- const auto opsFirst =
- createOperations(sourceCRS, candidateSrcGeod, context);
- assert(!opsFirst.empty());
- const bool isNullFirst = isNullTransformation(opsFirst[0]->nameStr());
-
- for (const auto &candidateDstGeod : candidatesDstGeod) {
- if (bSameSrcName &&
- candidateDstGeod->nameStr() == targetCRS->nameStr()) {
- continue;
- }
-
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("try " + objectAsStr(sourceCRS.get()) + "->" +
- objectAsStr(candidateSrcGeod.get()) + "->" +
- objectAsStr(candidateDstGeod.get()) + "->" +
- objectAsStr(targetCRS.get()) + ")");
-#endif
- createTransformations(candidateSrcGeod, candidateDstGeod,
- opsFirst[0], isNullFirst);
- if (!res.empty() && !hasResultSetOnlyResultsWithPROJStep(res)) {
- return;
- }
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-static CoordinateOperationNNPtr
-createBallparkGeocentricTranslation(const crs::CRSNNPtr &sourceCRS,
- const crs::CRSNNPtr &targetCRS) {
- std::string name(BALLPARK_GEOCENTRIC_TRANSLATION);
- name += " from ";
- name += sourceCRS->nameStr();
- name += " to ";
- name += targetCRS->nameStr();
-
- return util::nn_static_pointer_cast<CoordinateOperation>(
- Transformation::createGeocentricTranslations(
- util::PropertyMap()
- .set(common::IdentifiedObject::NAME_KEY, name)
- .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- metadata::Extent::WORLD),
- sourceCRS, targetCRS, 0.0, 0.0, 0.0, {}));
-}
-
-// ---------------------------------------------------------------------------
-
-bool CoordinateOperationFactory::Private::hasPerfectAccuracyResult(
- const std::vector<CoordinateOperationNNPtr> &res, const Context &context) {
- auto resTmp = FilterResults(res, context.context, context.extent1,
- context.extent2, true)
- .getRes();
- for (const auto &op : resTmp) {
- const double acc = getAccuracy(op);
- if (acc == 0.0) {
- return true;
- }
- }
- return false;
-}
-
-// ---------------------------------------------------------------------------
-
-std::vector<CoordinateOperationNNPtr>
-CoordinateOperationFactory::Private::createOperations(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context) {
-
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("createOperations(" + objectAsStr(sourceCRS.get()) + " --> " +
- objectAsStr(targetCRS.get()) + ")");
-#endif
-
- std::vector<CoordinateOperationNNPtr> res;
-
- auto boundSrc = dynamic_cast<const crs::BoundCRS *>(sourceCRS.get());
- auto boundDst = dynamic_cast<const crs::BoundCRS *>(targetCRS.get());
-
- const auto &sourceProj4Ext = boundSrc
- ? boundSrc->baseCRS()->getExtensionProj4()
- : sourceCRS->getExtensionProj4();
- const auto &targetProj4Ext = boundDst
- ? boundDst->baseCRS()->getExtensionProj4()
- : targetCRS->getExtensionProj4();
- if (!sourceProj4Ext.empty() || !targetProj4Ext.empty()) {
- createOperationsFromProj4Ext(sourceCRS, targetCRS, boundSrc, boundDst,
- res);
- return res;
- }
-
- auto geodSrc = dynamic_cast<const crs::GeodeticCRS *>(sourceCRS.get());
- auto geodDst = dynamic_cast<const crs::GeodeticCRS *>(targetCRS.get());
- auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
- auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
- auto vertSrc = dynamic_cast<const crs::VerticalCRS *>(sourceCRS.get());
- auto vertDst = dynamic_cast<const crs::VerticalCRS *>(targetCRS.get());
-
- // First look-up if the registry provide us with operations.
- auto derivedSrc = dynamic_cast<const crs::DerivedCRS *>(sourceCRS.get());
- auto derivedDst = dynamic_cast<const crs::DerivedCRS *>(targetCRS.get());
- const auto &authFactory = context.context->getAuthorityFactory();
- if (authFactory &&
- (derivedSrc == nullptr ||
- !derivedSrc->baseCRS()->_isEquivalentTo(
- targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) &&
- (derivedDst == nullptr ||
- !derivedDst->baseCRS()->_isEquivalentTo(
- sourceCRS.get(), util::IComparable::Criterion::EQUIVALENT))) {
-
- if (createOperationsFromDatabase(sourceCRS, targetCRS, context, geodSrc,
- geodDst, geogSrc, geogDst, vertSrc,
- vertDst, res)) {
- return res;
- }
- }
-
- // Special case if both CRS are geodetic
- if (geodSrc && geodDst && !derivedSrc && !derivedDst) {
- createOperationsGeodToGeod(sourceCRS, targetCRS, context, geodSrc,
- geodDst, res);
- return res;
- }
-
- // If the source is a derived CRS, then chain the inverse of its
- // deriving conversion, with transforms from its baseCRS to the
- // targetCRS
- if (derivedSrc) {
- createOperationsDerivedTo(sourceCRS, targetCRS, context, derivedSrc,
- res);
- return res;
- }
-
- // reverse of previous case
- if (derivedDst) {
- return applyInverse(createOperations(targetCRS, sourceCRS, context));
- }
-
- // Order of comparison between the geogDst vs geodDst is impotant
- if (boundSrc && geogDst) {
- createOperationsBoundToGeog(sourceCRS, targetCRS, context, boundSrc,
- geogDst, res);
- return res;
- } else if (boundSrc && geodDst) {
- createOperationsToGeod(sourceCRS, targetCRS, context, geodDst, res);
- return res;
- }
-
- // reverse of previous case
- if (geodSrc && boundDst) {
- return applyInverse(createOperations(targetCRS, sourceCRS, context));
- }
-
- // vertCRS (as boundCRS with transformation to target vertCRS) to
- // vertCRS
- if (boundSrc && vertDst) {
- createOperationsBoundToVert(sourceCRS, targetCRS, context, boundSrc,
- vertDst, res);
- return res;
- }
-
- // reverse of previous case
- if (boundDst && vertSrc) {
- return applyInverse(createOperations(targetCRS, sourceCRS, context));
- }
-
- if (vertSrc && vertDst) {
- createOperationsVertToVert(sourceCRS, targetCRS, context, vertSrc,
- vertDst, res);
- return res;
- }
-
- // A bit odd case as we are comparing apples to oranges, but in case
- // the vertical unit differ, do something useful.
- if (vertSrc && geogDst) {
- createOperationsVertToGeog(sourceCRS, targetCRS, context, vertSrc,
- geogDst, res);
- return res;
- }
-
- // reverse of previous case
- if (vertDst && geogSrc) {
- return applyInverse(createOperations(targetCRS, sourceCRS, context));
- }
-
- // boundCRS to boundCRS
- if (boundSrc && boundDst) {
- createOperationsBoundToBound(sourceCRS, targetCRS, context, boundSrc,
- boundDst, res);
- return res;
- }
-
- auto compoundSrc = dynamic_cast<crs::CompoundCRS *>(sourceCRS.get());
- // Order of comparison between the geogDst vs geodDst is impotant
- if (compoundSrc && geogDst) {
- createOperationsCompoundToGeog(sourceCRS, targetCRS, context,
- compoundSrc, geogDst, res);
- return res;
- } else if (compoundSrc && geodDst) {
- createOperationsToGeod(sourceCRS, targetCRS, context, geodDst, res);
- return res;
- }
-
- // reverse of previous cases
- auto compoundDst = dynamic_cast<const crs::CompoundCRS *>(targetCRS.get());
- if (geodSrc && compoundDst) {
- return applyInverse(createOperations(targetCRS, sourceCRS, context));
- }
-
- if (compoundSrc && compoundDst) {
- createOperationsCompoundToCompound(sourceCRS, targetCRS, context,
- compoundSrc, compoundDst, res);
- return res;
- }
-
- // '+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +type=crs' to
- // '+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx
- // +type=crs'
- if (boundSrc && compoundDst) {
- createOperationsBoundToCompound(sourceCRS, targetCRS, context, boundSrc,
- compoundDst, res);
- return res;
- }
-
- // reverse of previous case
- if (boundDst && compoundSrc) {
- return applyInverse(createOperations(targetCRS, sourceCRS, context));
- }
-
- return res;
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsFromProj4Ext(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- ENTER_FUNCTION();
-
- auto sourceProjExportable = dynamic_cast<const io::IPROJStringExportable *>(
- boundSrc ? boundSrc : sourceCRS.get());
- auto targetProjExportable = dynamic_cast<const io::IPROJStringExportable *>(
- boundDst ? boundDst : targetCRS.get());
- if (!sourceProjExportable) {
- throw InvalidOperation("Source CRS is not PROJ exportable");
- }
- if (!targetProjExportable) {
- throw InvalidOperation("Target CRS is not PROJ exportable");
- }
- auto projFormatter = io::PROJStringFormatter::create();
- projFormatter->setCRSExport(true);
- projFormatter->setLegacyCRSToCRSContext(true);
- projFormatter->startInversion();
- sourceProjExportable->_exportToPROJString(projFormatter.get());
- auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
- if (geogSrc) {
- auto tmpFormatter = io::PROJStringFormatter::create();
- geogSrc->addAngularUnitConvertAndAxisSwap(tmpFormatter.get());
- projFormatter->ingestPROJString(tmpFormatter->toString());
- }
-
- projFormatter->stopInversion();
-
- targetProjExportable->_exportToPROJString(projFormatter.get());
- auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
- if (geogDst) {
- auto tmpFormatter = io::PROJStringFormatter::create();
- geogDst->addAngularUnitConvertAndAxisSwap(tmpFormatter.get());
- projFormatter->ingestPROJString(tmpFormatter->toString());
- }
-
- const auto PROJString = projFormatter->toString();
- auto properties = util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()));
- res.emplace_back(SingleOperation::createPROJBased(
- properties, PROJString, sourceCRS, targetCRS, {}));
-}
-
-// ---------------------------------------------------------------------------
-
-bool CoordinateOperationFactory::Private::createOperationsFromDatabase(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::GeodeticCRS *geodSrc,
- const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc,
- const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc,
- const crs::VerticalCRS *vertDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- ENTER_FUNCTION();
-
- if (geogSrc && vertDst) {
- createOperationsFromDatabase(targetCRS, sourceCRS, context, geodDst,
- geodSrc, geogDst, geogSrc, vertDst,
- vertSrc, res);
- res = applyInverse(res);
- } else if (geogDst && vertSrc) {
- res = applyInverse(createOperationsGeogToVertFromGeoid(
- targetCRS, sourceCRS, vertSrc, context));
- if (!res.empty()) {
- createOperationsVertToGeogBallpark(sourceCRS, targetCRS, context,
- vertSrc, geogDst, res);
- }
- }
-
- if (!res.empty()) {
- return true;
- }
-
- bool resFindDirectNonEmptyBeforeFiltering = false;
- res = findOpsInRegistryDirect(sourceCRS, targetCRS, context,
- resFindDirectNonEmptyBeforeFiltering);
-
- // If we get at least a result with perfect accuracy, do not
- // bother generating synthetic transforms.
- if (hasPerfectAccuracyResult(res, context)) {
- return true;
- }
-
- bool doFilterAndCheckPerfectOp = false;
-
- bool sameGeodeticDatum = false;
-
- if (vertSrc || vertDst) {
- if (res.empty()) {
- if (geogSrc &&
- geogSrc->coordinateSystem()->axisList().size() == 2 &&
- vertDst) {
- auto dbContext =
- context.context->getAuthorityFactory()->databaseContext();
- auto resTmp = findOpsInRegistryDirect(
- sourceCRS->promoteTo3D(std::string(), dbContext), targetCRS,
- context, resFindDirectNonEmptyBeforeFiltering);
- for (auto &op : resTmp) {
- auto newOp = op->shallowClone();
- setCRSs(newOp.get(), sourceCRS, targetCRS);
- res.emplace_back(newOp);
- }
- } else if (geogDst &&
- geogDst->coordinateSystem()->axisList().size() == 2 &&
- vertSrc) {
- auto dbContext =
- context.context->getAuthorityFactory()->databaseContext();
- auto resTmp = findOpsInRegistryDirect(
- sourceCRS, targetCRS->promoteTo3D(std::string(), dbContext),
- context, resFindDirectNonEmptyBeforeFiltering);
- for (auto &op : resTmp) {
- auto newOp = op->shallowClone();
- setCRSs(newOp.get(), sourceCRS, targetCRS);
- res.emplace_back(newOp);
- }
- }
- }
- if (res.empty()) {
- createOperationsFromDatabaseWithVertCRS(sourceCRS, targetCRS,
- context, geogSrc, geogDst,
- vertSrc, vertDst, res);
- }
- } else if (geodSrc && geodDst) {
-
- const auto &authFactory = context.context->getAuthorityFactory();
- const auto dbContext = authFactory->databaseContext().as_nullable();
-
- const auto srcDatum = geodSrc->datumNonNull(dbContext);
- const auto dstDatum = geodDst->datumNonNull(dbContext);
- sameGeodeticDatum = srcDatum->_isEquivalentTo(
- dstDatum.get(), util::IComparable::Criterion::EQUIVALENT);
-
- if (res.empty() && !sameGeodeticDatum &&
- !context.inCreateOperationsWithDatumPivotAntiRecursion) {
- // 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
- // GeodeticCRSs
- // 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();
- }
- }
-
- bool foundInstantiableOp = false;
- // FIXME: the limitation to .size() == 1 is just for the
- // -s EPSG:4959+5759 -t "EPSG:4959+7839" case
- // finding EPSG:7860 'NZVD2016 height to Auckland 1946
- // height (1)', which uses the EPSG:1071 'Vertical Offset by Grid
- // Interpolation (NZLVD)' method which is not currently implemented by PROJ
- // (cannot deal with .csv files)
- // Initially the test was written to iterate over for all operations of a
- // non-empty res, but this causes failures in the test suite when no grids
- // are installed at all. Ideally we should tweak the test suite to be
- // robust to that, or skip some tests.
- if (res.size() == 1) {
- try {
- res.front()->exportToPROJString(
- io::PROJStringFormatter::create().get());
- foundInstantiableOp = true;
- } catch (const std::exception &) {
- }
- if (!foundInstantiableOp) {
- resFindDirectNonEmptyBeforeFiltering = false;
- }
- } else if (res.size() > 1) {
- foundInstantiableOp = true;
- }
-
- // NAD27 to NAD83 has tens of results already. No need to look
- // for a pivot
- if (!sameGeodeticDatum &&
- (((res.empty() || !foundInstantiableOp) &&
- !resFindDirectNonEmptyBeforeFiltering &&
- context.context->getAllowUseIntermediateCRS() ==
- CoordinateOperationContext::IntermediateCRSUse::
- IF_NO_DIRECT_TRANSFORMATION) ||
- context.context->getAllowUseIntermediateCRS() ==
- CoordinateOperationContext::IntermediateCRSUse::ALWAYS ||
- getenv("PROJ_FORCE_SEARCH_PIVOT"))) {
- auto resWithIntermediate = findsOpsInRegistryWithIntermediate(
- sourceCRS, targetCRS, context, false);
- res.insert(res.end(), resWithIntermediate.begin(),
- resWithIntermediate.end());
- doFilterAndCheckPerfectOp = !res.empty();
-
- } else if (!context.inCreateOperationsWithDatumPivotAntiRecursion &&
- !resFindDirectNonEmptyBeforeFiltering && geodSrc && geodDst &&
- !sameGeodeticDatum &&
- context.context->getIntermediateCRS().empty() &&
- context.context->getAllowUseIntermediateCRS() !=
- CoordinateOperationContext::IntermediateCRSUse::NEVER) {
-
- bool tryWithGeodeticDatumIntermediate = res.empty();
- if (!tryWithGeodeticDatumIntermediate) {
- // This is in particular for the GDA94 to WGS 84 (G1762) case
- // As we have a WGS 84 -> WGS 84 (G1762) null-transformation in the
- // PROJ authority, previous steps might have use that WGS 84
- // intermediate directly. They might also have generated a path
- // through ITRF2008, as there is a path
- // GDA94 (geoc.) -> ITRF2008 (geoc.) -> WGS84 (G1762) (geoc.)
- // But there's a better path using
- // GDA94 (geog.) --> GDA2020 (geog.) and
- // GDA2020 (geoc.) -> WGS84 (G1762) (geoc.) that requires to
- // explore intermediates through their datum, and not directly
- // trough the CRS code.
- // Do that only if the number of results we got through other
- // algorithms is small, or if all results we have go through an
- // operation in the PROJ authority.
- constexpr size_t ARBITRARY_SMALL_NUMBER = 5U;
- tryWithGeodeticDatumIntermediate =
- res.size() < ARBITRARY_SMALL_NUMBER ||
- hasResultSetOnlyResultsWithPROJStep(res);
- }
- if (tryWithGeodeticDatumIntermediate) {
- auto resWithIntermediate = findsOpsInRegistryWithIntermediate(
- sourceCRS, targetCRS, context, true);
- res.insert(res.end(), resWithIntermediate.begin(),
- resWithIntermediate.end());
- doFilterAndCheckPerfectOp = !res.empty();
- }
- }
-
- if (doFilterAndCheckPerfectOp) {
- // If we get at least a result with perfect accuracy, do not bother
- // generating synthetic transforms.
- if (hasPerfectAccuracyResult(res, context)) {
- return true;
- }
- }
- return false;
-}
-
-// ---------------------------------------------------------------------------
-
-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::createOperationsGeogToVertFromGeoid(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- const crs::VerticalCRS *vertDst, Private::Context &context) {
-
- ENTER_FUNCTION();
-
- const auto useTransf = [&targetCRS, &context,
- vertDst](const CoordinateOperationNNPtr &op) {
- const auto targetOp =
- dynamic_cast<const crs::VerticalCRS *>(op->targetCRS().get());
- assert(targetOp);
- if (targetOp->_isEquivalentTo(
- vertDst, util::IComparable::Criterion::EQUIVALENT)) {
- return op;
- }
- std::vector<CoordinateOperationNNPtr> tmp;
- createOperationsVertToVert(NN_NO_CHECK(op->targetCRS()), targetCRS,
- context, targetOp, vertDst, tmp);
- assert(!tmp.empty());
- auto ret = ConcatenatedOperation::createComputeMetadata(
- {op, tmp.front()}, disallowEmptyIntersection);
- return ret;
- };
-
- const auto getProjGeoidTransformation = [&sourceCRS, &targetCRS, &vertDst,
- &context](
- const CoordinateOperationNNPtr &model,
- const std::string &projFilename) {
-
- const auto getNameVertCRSMetre = [](const std::string &name) {
- if (name.empty())
- return std::string("unnamed");
- auto ret(name);
- bool haveOriginalUnit = false;
- if (name.back() == ')') {
- const auto pos = ret.rfind(" (");
- if (pos != std::string::npos) {
- haveOriginalUnit = true;
- ret = ret.substr(0, pos);
- }
- }
- const auto pos = ret.rfind(" depth");
- if (pos != std::string::npos) {
- ret = ret.substr(0, pos) + " height";
- }
- if (!haveOriginalUnit) {
- ret += " (metre)";
- }
- return ret;
- };
-
- const auto &axis = vertDst->coordinateSystem()->axisList()[0];
- const auto geogSrcCRS =
- dynamic_cast<crs::GeographicCRS *>(model->interpolationCRS().get())
- ? NN_NO_CHECK(model->interpolationCRS())
- : sourceCRS;
- const auto vertCRSMetre =
- axis->unit() == common::UnitOfMeasure::METRE &&
- axis->direction() == cs::AxisDirection::UP
- ? targetCRS
- : util::nn_static_pointer_cast<crs::CRS>(
- crs::VerticalCRS::create(
- util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- getNameVertCRSMetre(targetCRS->nameStr())),
- vertDst->datum(), vertDst->datumEnsemble(),
- cs::VerticalCS::createGravityRelatedHeight(
- common::UnitOfMeasure::METRE)));
- const auto properties = util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- buildOpName("Transformation", vertCRSMetre, geogSrcCRS));
-
- // Try to find a representative value for the accuracy of this grid
- // from the registered transformations.
- std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
- const auto &modelAccuracies = model->coordinateOperationAccuracies();
- if (modelAccuracies.empty()) {
- const auto &authFactory = context.context->getAuthorityFactory();
- if (authFactory) {
- const auto transformationsForGrid =
- io::DatabaseContext::getTransformationsForGridName(
- authFactory->databaseContext(), projFilename);
- double accuracy = -1;
- for (const auto &transf : transformationsForGrid) {
- accuracy = std::max(accuracy, getAccuracy(transf));
- }
- if (accuracy >= 0) {
- accuracies.emplace_back(
- metadata::PositionalAccuracy::create(
- toString(accuracy)));
- }
- }
- }
-
- return Transformation::createGravityRelatedHeightToGeographic3D(
- properties, vertCRSMetre, geogSrcCRS, nullptr, projFilename,
- !modelAccuracies.empty() ? modelAccuracies : accuracies);
- };
-
- std::vector<CoordinateOperationNNPtr> res;
- const auto &authFactory = context.context->getAuthorityFactory();
- if (authFactory) {
- const auto &models = vertDst->geoidModel();
- for (const auto &model : models) {
- const auto &modelName = model->nameStr();
- const auto transformations =
- starts_with(modelName, "PROJ ")
- ? std::vector<
- CoordinateOperationNNPtr>{getProjGeoidTransformation(
- model, modelName.substr(strlen("PROJ ")))}
- : authFactory->getTransformationsForGeoid(
- modelName,
- context.context->getUsePROJAlternativeGridNames());
- for (const auto &transf : transformations) {
- if (dynamic_cast<crs::GeographicCRS *>(
- transf->sourceCRS().get()) &&
- dynamic_cast<crs::VerticalCRS *>(
- transf->targetCRS().get())) {
- res.push_back(useTransf(transf));
- } else if (dynamic_cast<crs::GeographicCRS *>(
- transf->targetCRS().get()) &&
- dynamic_cast<crs::VerticalCRS *>(
- transf->sourceCRS().get())) {
- res.push_back(useTransf(transf->inverse()));
- }
- }
- }
- }
-
- return res;
-}
-
-// ---------------------------------------------------------------------------
-
-std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private::
- createOperationsGeogToVertWithIntermediateVert(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- const crs::VerticalCRS *vertDst, Private::Context &context) {
-
- ENTER_FUNCTION();
-
- 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();
- const auto dbContext = authFactory->databaseContext().as_nullable();
-
- auto candidatesVert = findCandidateVertCRSForDatum(
- authFactory, vertDst->datumNonNull(dbContext).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)) {
- if (candidateVert->_isEquivalentTo(
- targetCRS.get(),
- util::IComparable::Criterion::EQUIVALENT)) {
- res.emplace_back(opFirst);
- } else {
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- {opFirst, opsSecond.front()},
- disallowEmptyIntersection));
- }
- }
- }
- 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) {
-
- ENTER_FUNCTION();
-
- 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);
-
- // Generally EPSG has operations from GeogCrs to VertCRS
- auto ops = findOpsInRegistryDirectTo(targetCRS, context);
-
- for (const auto &op : ops) {
- const auto tmpCRS = op->sourceCRS();
- if (tmpCRS && dynamic_cast<const crs::GeographicCRS *>(tmpCRS.get())) {
- res.emplace_back(op);
- }
- }
-
- 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
- // promoted 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) {
- const auto &authFactory = context.context->getAuthorityFactory();
- const auto dbContext =
- authFactory ? authFactory->databaseContext().as_nullable()
- : nullptr;
- const auto candidatesSrcGeod(findCandidateGeodCRSForDatum(
- authFactory, geogSrcIn,
- geogSrcIn->datumNonNull(dbContext).get()));
- for (const auto &candidate : candidatesSrcGeod) {
- auto geogCandidate =
- util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
- candidate);
- if (geogCandidate &&
- geogCandidate->coordinateSystem()->axisList().size() == 2) {
- bool ignored;
- res =
- findOpsInRegistryDirect(NN_NO_CHECK(geogCandidate),
- targetCRSIn, context, ignored);
- 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,
- const crs::GeodeticCRS *geodDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- ENTER_FUNCTION();
-
- if (geodSrc->ellipsoid()->celestialBody() !=
- geodDst->ellipsoid()->celestialBody()) {
- throw util::UnsupportedOperationException(
- "Source and target ellipsoid do not belong to the same "
- "celestial body");
- }
-
- auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(geodSrc);
- auto geogDst = dynamic_cast<const crs::GeographicCRS *>(geodDst);
-
- if (geogSrc && geogDst) {
- createOperationsGeogToGeog(res, sourceCRS, targetCRS, context, geogSrc,
- geogDst);
- return;
- }
-
- const bool isSrcGeocentric = geodSrc->isGeocentric();
- const bool isSrcGeographic = geogSrc != nullptr;
- const bool isTargetGeocentric = geodDst->isGeocentric();
- const bool isTargetGeographic = geogDst != nullptr;
-
- const auto IsSameDatum = [&context, &geodSrc, &geodDst]() {
- const auto &authFactory = context.context->getAuthorityFactory();
- const auto dbContext =
- authFactory ? authFactory->databaseContext().as_nullable()
- : nullptr;
-
- return geodSrc->datumNonNull(dbContext)->_isEquivalentTo(
- geodDst->datumNonNull(dbContext).get(),
- util::IComparable::Criterion::EQUIVALENT);
- };
-
- if (((isSrcGeocentric && isTargetGeographic) ||
- (isSrcGeographic && isTargetGeocentric))) {
-
- // Same datum ?
- if (IsSameDatum()) {
- res.emplace_back(
- Conversion::createGeographicGeocentric(sourceCRS, targetCRS));
- } else if (isSrcGeocentric && geogDst) {
- std::string interm_crs_name(geogDst->nameStr());
- interm_crs_name += " (geocentric)";
- auto interm_crs =
- util::nn_static_pointer_cast<crs::CRS>(crs::GeodeticCRS::create(
- addDomains(util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- interm_crs_name),
- geogDst),
- geogDst->datum(), geogDst->datumEnsemble(),
- NN_CHECK_ASSERT(
- util::nn_dynamic_pointer_cast<cs::CartesianCS>(
- geodSrc->coordinateSystem()))));
- auto opFirst =
- createBallparkGeocentricTranslation(sourceCRS, interm_crs);
- auto opSecond =
- Conversion::createGeographicGeocentric(interm_crs, targetCRS);
- res.emplace_back(ConcatenatedOperation::createComputeMetadata(
- {opFirst, opSecond}, disallowEmptyIntersection));
- } else {
- // Apply previous case in reverse way
- std::vector<CoordinateOperationNNPtr> resTmp;
- createOperationsGeodToGeod(targetCRS, sourceCRS, context, geodDst,
- geodSrc, resTmp);
- assert(resTmp.size() == 1);
- res.emplace_back(resTmp.front()->inverse());
- }
-
- return;
- }
-
- if (isSrcGeocentric && isTargetGeocentric) {
- if (sourceCRS->_isEquivalentTo(
- targetCRS.get(), util::IComparable::Criterion::EQUIVALENT) ||
- IsSameDatum()) {
- std::string name(NULL_GEOCENTRIC_TRANSLATION);
- name += " from ";
- name += sourceCRS->nameStr();
- name += " to ";
- name += targetCRS->nameStr();
- res.emplace_back(Transformation::createGeocentricTranslations(
- util::PropertyMap()
- .set(common::IdentifiedObject::NAME_KEY, name)
- .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- metadata::Extent::WORLD),
- sourceCRS, targetCRS, 0.0, 0.0, 0.0,
- {metadata::PositionalAccuracy::create("0")}));
- } else {
- res.emplace_back(
- createBallparkGeocentricTranslation(sourceCRS, targetCRS));
- }
- return;
- }
-
- // Transformation between two geodetic systems of unknown type
- // This should normally not be triggered with "standard" CRS
- res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS));
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsDerivedTo(
- const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::DerivedCRS *derivedSrc,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- ENTER_FUNCTION();
-
- auto opFirst = derivedSrc->derivingConversion()->inverse();
- // Small optimization if the targetCRS is the baseCRS of the source
- // derivedCRS.
- if (derivedSrc->baseCRS()->_isEquivalentTo(
- targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) {
- res.emplace_back(opFirst);
- return;
- }
- auto opsSecond =
- createOperations(derivedSrc->baseCRS(), targetCRS, context);
- for (const auto &opSecond : opsSecond) {
- try {
- res.emplace_back(ConcatenatedOperation::createComputeMetadata(
- {opFirst, opSecond}, disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsBoundToGeog(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::BoundCRS *boundSrc,
- const crs::GeographicCRS *geogDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- ENTER_FUNCTION();
-
- const auto &hubSrc = boundSrc->hubCRS();
- auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get());
- auto geogCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractGeographicCRS();
- {
- // If geogCRSOfBaseOfBoundSrc is a DerivedGeographicCRS, use its base
- // instead (if it is a GeographicCRS)
- auto derivedGeogCRS =
- std::dynamic_pointer_cast<crs::DerivedGeographicCRS>(
- geogCRSOfBaseOfBoundSrc);
- if (derivedGeogCRS) {
- auto baseCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(
- derivedGeogCRS->baseCRS().as_nullable());
- if (baseCRS) {
- geogCRSOfBaseOfBoundSrc = baseCRS;
- }
- }
- }
-
- const auto &authFactory = context.context->getAuthorityFactory();
- const auto dbContext =
- authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
-
- const auto geogDstDatum = geogDst->datumNonNull(dbContext);
-
- // If the underlying datum of the source is the same as the target, do
- // not consider the boundCRS at all, but just its base
- if (geogCRSOfBaseOfBoundSrc) {
- auto geogCRSOfBaseOfBoundSrcDatum =
- geogCRSOfBaseOfBoundSrc->datumNonNull(dbContext);
- if (geogCRSOfBaseOfBoundSrcDatum->_isEquivalentTo(
- geogDstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) {
- res = createOperations(boundSrc->baseCRS(), targetCRS, context);
- return;
- }
- }
-
- bool triedBoundCrsToGeogCRSSameAsHubCRS = false;
- // Is it: boundCRS to a geogCRS that is the same as the hubCRS ?
- if (hubSrcGeog && geogCRSOfBaseOfBoundSrc &&
- (hubSrcGeog->_isEquivalentTo(
- geogDst, util::IComparable::Criterion::EQUIVALENT) ||
- hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext))) {
- triedBoundCrsToGeogCRSSameAsHubCRS = true;
-
- CoordinateOperationPtr opIntermediate;
- if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo(
- boundSrc->transformation()->sourceCRS().get(),
- util::IComparable::Criterion::EQUIVALENT)) {
- auto opsIntermediate = createOperations(
- NN_NO_CHECK(geogCRSOfBaseOfBoundSrc),
- boundSrc->transformation()->sourceCRS(), context);
- assert(!opsIntermediate.empty());
- opIntermediate = opsIntermediate.front();
- }
-
- if (boundSrc->baseCRS() == geogCRSOfBaseOfBoundSrc) {
- if (opIntermediate) {
- try {
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- {NN_NO_CHECK(opIntermediate),
- boundSrc->transformation()},
- disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- } else {
- // Optimization to avoid creating a useless concatenated
- // operation
- res.emplace_back(boundSrc->transformation());
- }
- return;
- }
- auto opsFirst = createOperations(
- boundSrc->baseCRS(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context);
- if (!opsFirst.empty()) {
- for (const auto &opFirst : opsFirst) {
- try {
- std::vector<CoordinateOperationNNPtr> subops;
- subops.emplace_back(opFirst);
- if (opIntermediate) {
- subops.emplace_back(NN_NO_CHECK(opIntermediate));
- }
- subops.emplace_back(boundSrc->transformation());
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- subops, disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- }
- if (!res.empty()) {
- return;
- }
- }
- // If the datum are equivalent, this is also fine
- } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog &&
- hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo(
- geogDstDatum.get(),
- util::IComparable::Criterion::EQUIVALENT)) {
- auto opsFirst = createOperations(
- boundSrc->baseCRS(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context);
- auto opsLast = createOperations(hubSrc, targetCRS, context);
- if (!opsFirst.empty() && !opsLast.empty()) {
- CoordinateOperationPtr opIntermediate;
- if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo(
- boundSrc->transformation()->sourceCRS().get(),
- util::IComparable::Criterion::EQUIVALENT)) {
- auto opsIntermediate = createOperations(
- NN_NO_CHECK(geogCRSOfBaseOfBoundSrc),
- boundSrc->transformation()->sourceCRS(), context);
- assert(!opsIntermediate.empty());
- opIntermediate = opsIntermediate.front();
- }
- for (const auto &opFirst : opsFirst) {
- for (const auto &opLast : opsLast) {
- try {
- std::vector<CoordinateOperationNNPtr> subops;
- subops.emplace_back(opFirst);
- if (opIntermediate) {
- subops.emplace_back(NN_NO_CHECK(opIntermediate));
- }
- subops.emplace_back(boundSrc->transformation());
- subops.emplace_back(opLast);
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- subops, disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- }
- }
- if (!res.empty()) {
- return;
- }
- }
- // Consider WGS 84 and NAD83 as equivalent in that context if the
- // geogCRSOfBaseOfBoundSrc ellipsoid is Clarke66 (for NAD27)
- // Case of "+proj=latlong +ellps=clrk66
- // +nadgrids=ntv1_can.dat,conus"
- // to "+proj=latlong +datum=NAD83"
- } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog &&
- geogCRSOfBaseOfBoundSrc->ellipsoid()->_isEquivalentTo(
- datum::Ellipsoid::CLARKE_1866.get(),
- util::IComparable::Criterion::EQUIVALENT) &&
- hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo(
- datum::GeodeticReferenceFrame::EPSG_6326.get(),
- util::IComparable::Criterion::EQUIVALENT) &&
- geogDstDatum->_isEquivalentTo(
- datum::GeodeticReferenceFrame::EPSG_6269.get(),
- util::IComparable::Criterion::EQUIVALENT)) {
- auto nnGeogCRSOfBaseOfBoundSrc = NN_NO_CHECK(geogCRSOfBaseOfBoundSrc);
- if (boundSrc->baseCRS()->_isEquivalentTo(
- nnGeogCRSOfBaseOfBoundSrc.get(),
- util::IComparable::Criterion::EQUIVALENT)) {
- auto transf = boundSrc->transformation()->shallowClone();
- transf->setProperties(util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- buildTransfName(boundSrc->baseCRS()->nameStr(),
- targetCRS->nameStr())));
- transf->setCRSs(boundSrc->baseCRS(), targetCRS, nullptr);
- res.emplace_back(transf);
- return;
- } else {
- auto opsFirst = createOperations(
- boundSrc->baseCRS(), nnGeogCRSOfBaseOfBoundSrc, context);
- auto transf = boundSrc->transformation()->shallowClone();
- transf->setProperties(util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- buildTransfName(nnGeogCRSOfBaseOfBoundSrc->nameStr(),
- targetCRS->nameStr())));
- transf->setCRSs(nnGeogCRSOfBaseOfBoundSrc, targetCRS, nullptr);
- if (!opsFirst.empty()) {
- for (const auto &opFirst : opsFirst) {
- try {
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- {opFirst, transf}, disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- }
- if (!res.empty()) {
- return;
- }
- }
- }
- }
-
- if (hubSrcGeog &&
- hubSrcGeog->_isEquivalentTo(geogDst,
- util::IComparable::Criterion::EQUIVALENT) &&
- dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get())) {
- auto transfSrc = boundSrc->transformation()->sourceCRS();
- if (dynamic_cast<const crs::VerticalCRS *>(transfSrc.get()) &&
- !boundSrc->baseCRS()->_isEquivalentTo(
- transfSrc.get(), util::IComparable::Criterion::EQUIVALENT)) {
- auto opsFirst =
- createOperations(boundSrc->baseCRS(), transfSrc, context);
- for (const auto &opFirst : opsFirst) {
- try {
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- {opFirst, boundSrc->transformation()},
- disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- }
- return;
- }
-
- res.emplace_back(boundSrc->transformation());
- return;
- }
-
- if (!triedBoundCrsToGeogCRSSameAsHubCRS && hubSrcGeog &&
- geogCRSOfBaseOfBoundSrc) {
- // This one should go to the above 'Is it: boundCRS to a geogCRS
- // that is the same as the hubCRS ?' case
- auto opsFirst = createOperations(sourceCRS, hubSrc, context);
- auto opsLast = createOperations(hubSrc, targetCRS, context);
- if (!opsFirst.empty() && !opsLast.empty()) {
- for (const auto &opFirst : opsFirst) {
- for (const auto &opLast : opsLast) {
- // Exclude artificial transformations from the hub
- // to the target CRS, if it is the only one.
- if (opsLast.size() > 1 ||
- !opLast->hasBallparkTransformation()) {
- try {
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- {opFirst, opLast},
- disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- } else {
- // std::cerr << "excluded " << opLast->nameStr() <<
- // std::endl;
- }
- }
- }
- if (!res.empty()) {
- return;
- }
- }
- }
-
- auto vertCRSOfBaseOfBoundSrc =
- dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get());
- if (vertCRSOfBaseOfBoundSrc && hubSrcGeog) {
- auto opsFirst = createOperations(sourceCRS, hubSrc, context);
- if (context.skipHorizontalTransformation) {
- if (!opsFirst.empty()) {
- const auto &hubAxisList =
- hubSrcGeog->coordinateSystem()->axisList();
- const auto &targetAxisList =
- geogDst->coordinateSystem()->axisList();
- if (hubAxisList.size() == 3 && targetAxisList.size() == 3 &&
- !hubAxisList[2]->_isEquivalentTo(
- targetAxisList[2].get(),
- util::IComparable::Criterion::EQUIVALENT)) {
-
- const auto &srcAxis = hubAxisList[2];
- const double convSrc = srcAxis->unit().conversionToSI();
- const auto &dstAxis = targetAxisList[2];
- 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 conv = Conversion::createChangeVerticalUnit(
- util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- "Change of vertical unit"),
- common::Scale(heightDepthReversal ? -factor : factor));
-
- conv->setCRSs(
- hubSrc,
- hubSrc->demoteTo2D(std::string(), dbContext)
- ->promoteTo3D(std::string(), dbContext, dstAxis),
- nullptr);
-
- for (const auto &op : opsFirst) {
- try {
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- {op, conv}, disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- }
- } else {
- res = opsFirst;
- }
- }
- return;
- } else {
- auto opsSecond = createOperations(hubSrc, targetCRS, context);
- if (!opsFirst.empty() && !opsSecond.empty()) {
- for (const auto &opFirst : opsFirst) {
- for (const auto &opLast : opsSecond) {
- // Exclude artificial transformations from the hub
- // to the target CRS
- if (!opLast->hasBallparkTransformation()) {
- try {
- res.emplace_back(
- ConcatenatedOperation::
- createComputeMetadata(
- {opFirst, opLast},
- disallowEmptyIntersection));
- } catch (
- const InvalidOperationEmptyIntersection &) {
- }
- } else {
- // std::cerr << "excluded " << opLast->nameStr() <<
- // std::endl;
- }
- }
- }
- if (!res.empty()) {
- return;
- }
- }
- }
- }
-
- res = createOperations(boundSrc->baseCRS(), targetCRS, context);
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsBoundToVert(
- const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::BoundCRS *boundSrc,
- const crs::VerticalCRS *vertDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- ENTER_FUNCTION();
-
- auto baseSrcVert =
- dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get());
- const auto &hubSrc = boundSrc->hubCRS();
- auto hubSrcVert = dynamic_cast<const crs::VerticalCRS *>(hubSrc.get());
- if (baseSrcVert && hubSrcVert &&
- vertDst->_isEquivalentTo(hubSrcVert,
- util::IComparable::Criterion::EQUIVALENT)) {
- res.emplace_back(boundSrc->transformation());
- return;
- }
-
- res = createOperations(boundSrc->baseCRS(), targetCRS, context);
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsVertToVert(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::VerticalCRS *vertSrc,
- const crs::VerticalCRS *vertDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- ENTER_FUNCTION();
-
- const auto &authFactory = context.context->getAuthorityFactory();
- const auto dbContext =
- authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
-
- const auto srcDatum = vertSrc->datumNonNull(dbContext);
- const auto dstDatum = vertDst->datumNonNull(dbContext);
- const bool equivalentVDatum = srcDatum->_isEquivalentTo(
- dstDatum.get(), util::IComparable::Criterion::EQUIVALENT);
-
- 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());
- if (!equivalentVDatum) {
- name += BALLPARK_VERTICAL_TRANSFORMATION;
- auto conv = Transformation::createChangeVerticalUnit(
- util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name),
- 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 || !heightDepthReversal) {
- auto conv = Conversion::createChangeVerticalUnit(
- util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name),
- // 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 {
- auto conv = Conversion::createHeightDepthReversal(
- util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name));
- conv->setCRSs(sourceCRS, targetCRS, nullptr);
- res.push_back(conv);
- }
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsVertToGeog(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::VerticalCRS *vertSrc,
- const crs::GeographicCRS *geogDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- ENTER_FUNCTION();
-
- if (vertSrc->identifiers().empty()) {
- const auto &vertSrcName = vertSrc->nameStr();
- const auto &authFactory = context.context->getAuthorityFactory();
- if (authFactory != nullptr && vertSrcName != "unnamed" &&
- vertSrcName != "unknown") {
- auto matches = authFactory->createObjectsFromName(
- vertSrcName, {io::AuthorityFactory::ObjectType::VERTICAL_CRS},
- false, 2);
- if (matches.size() == 1) {
- const auto &match = matches.front();
- if (vertSrc->_isEquivalentTo(
- match.get(),
- util::IComparable::Criterion::EQUIVALENT) &&
- !match->identifiers().empty()) {
- auto resTmp = createOperations(
- NN_NO_CHECK(
- util::nn_dynamic_pointer_cast<crs::VerticalCRS>(
- match)),
- targetCRS, context);
- res.insert(res.end(), resTmp.begin(), resTmp.end());
- return;
- }
- }
- }
- }
-
- createOperationsVertToGeogBallpark(sourceCRS, targetCRS, context, vertSrc,
- geogDst, res);
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsVertToGeogBallpark(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &, const crs::VerticalCRS *vertSrc,
- const crs::GeographicCRS *geogDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- ENTER_FUNCTION();
-
- const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0];
- const double convSrc = srcAxis->unit().conversionToSI();
- double convDst = 1.0;
- const auto &geogAxis = geogDst->coordinateSystem()->axisList();
- bool dstIsUp = true;
- bool dstIsDown = false;
- if (geogAxis.size() == 3) {
- const auto &dstAxis = geogAxis[2];
- convDst = dstAxis->unit().conversionToSI();
- dstIsUp = dstAxis->direction() == cs::AxisDirection::UP;
- dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN;
- }
- const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP;
- const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN;
- const bool heightDepthReversal =
- ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp));
-
- const double factor = convSrc / convDst;
-
- const auto &sourceCRSExtent = getExtent(sourceCRS);
- const auto &targetCRSExtent = getExtent(targetCRS);
- const bool sameExtent =
- sourceCRSExtent && targetCRSExtent &&
- sourceCRSExtent->_isEquivalentTo(
- targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT);
-
- util::PropertyMap map;
- map.set(common::IdentifiedObject::NAME_KEY,
- buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()) +
- BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT)
- .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- sameExtent ? NN_NO_CHECK(sourceCRSExtent)
- : metadata::Extent::WORLD);
-
- auto conv = Transformation::createChangeVerticalUnit(
- map, sourceCRS, targetCRS,
- common::Scale(heightDepthReversal ? -factor : factor), {});
- conv->setHasBallparkTransformation(true);
- res.push_back(conv);
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsBoundToBound(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::BoundCRS *boundSrc,
- const crs::BoundCRS *boundDst, std::vector<CoordinateOperationNNPtr> &res) {
-
- ENTER_FUNCTION();
-
- // BoundCRS to BoundCRS of horizontal CRS using the same (geographic) hub
- const auto &hubSrc = boundSrc->hubCRS();
- auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get());
- const auto &hubDst = boundDst->hubCRS();
- auto hubDstGeog = dynamic_cast<const crs::GeographicCRS *>(hubDst.get());
- auto geogCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractGeographicCRS();
- auto geogCRSOfBaseOfBoundDst = boundDst->baseCRS()->extractGeographicCRS();
- if (hubSrcGeog && hubDstGeog &&
- hubSrcGeog->_isEquivalentTo(hubDstGeog,
- util::IComparable::Criterion::EQUIVALENT) &&
- geogCRSOfBaseOfBoundSrc && geogCRSOfBaseOfBoundDst) {
- const bool firstIsNoOp = geogCRSOfBaseOfBoundSrc->_isEquivalentTo(
- boundSrc->baseCRS().get(),
- util::IComparable::Criterion::EQUIVALENT);
- const bool lastIsNoOp = geogCRSOfBaseOfBoundDst->_isEquivalentTo(
- boundDst->baseCRS().get(),
- util::IComparable::Criterion::EQUIVALENT);
- auto opsFirst = createOperations(
- boundSrc->baseCRS(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context);
- auto opsLast = createOperations(NN_NO_CHECK(geogCRSOfBaseOfBoundDst),
- boundDst->baseCRS(), context);
- if (!opsFirst.empty() && !opsLast.empty()) {
- const auto &opSecond = boundSrc->transformation();
- auto opThird = boundDst->transformation()->inverse();
- for (const auto &opFirst : opsFirst) {
- for (const auto &opLast : opsLast) {
- try {
- std::vector<CoordinateOperationNNPtr> ops;
- if (!firstIsNoOp) {
- ops.push_back(opFirst);
- }
- ops.push_back(opSecond);
- ops.push_back(opThird);
- if (!lastIsNoOp) {
- ops.push_back(opLast);
- }
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- ops, disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- }
- }
- if (!res.empty()) {
- return;
- }
- }
- }
-
- // BoundCRS to BoundCRS of vertical CRS using the same vertical datum
- // ==> ignore the bound transformation
- auto baseOfBoundSrcAsVertCRS =
- dynamic_cast<crs::VerticalCRS *>(boundSrc->baseCRS().get());
- auto baseOfBoundDstAsVertCRS =
- dynamic_cast<crs::VerticalCRS *>(boundDst->baseCRS().get());
- if (baseOfBoundSrcAsVertCRS && baseOfBoundDstAsVertCRS) {
-
- const auto &authFactory = context.context->getAuthorityFactory();
- const auto dbContext =
- authFactory ? authFactory->databaseContext().as_nullable()
- : nullptr;
-
- const auto datumSrc = baseOfBoundSrcAsVertCRS->datumNonNull(dbContext);
- const auto datumDst = baseOfBoundDstAsVertCRS->datumNonNull(dbContext);
- if (datumSrc->nameStr() == datumDst->nameStr() &&
- (datumSrc->nameStr() != "unknown" ||
- boundSrc->transformation()->_isEquivalentTo(
- boundDst->transformation().get(),
- util::IComparable::Criterion::EQUIVALENT))) {
- res = createOperations(boundSrc->baseCRS(), boundDst->baseCRS(),
- context);
- return;
- }
- }
-
- // BoundCRS to BoundCRS of vertical CRS
- auto vertCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractVerticalCRS();
- auto vertCRSOfBaseOfBoundDst = boundDst->baseCRS()->extractVerticalCRS();
- if (hubSrcGeog && hubDstGeog &&
- hubSrcGeog->_isEquivalentTo(hubDstGeog,
- util::IComparable::Criterion::EQUIVALENT) &&
- vertCRSOfBaseOfBoundSrc && vertCRSOfBaseOfBoundDst) {
- auto opsFirst = createOperations(sourceCRS, hubSrc, context);
- auto opsLast = createOperations(hubSrc, targetCRS, context);
- if (!opsFirst.empty() && !opsLast.empty()) {
- for (const auto &opFirst : opsFirst) {
- for (const auto &opLast : opsLast) {
- try {
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- {opFirst, opLast}, disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- }
- }
- if (!res.empty()) {
- return;
- }
- }
- }
-
- res = createOperations(boundSrc->baseCRS(), boundDst->baseCRS(), context);
-}
-
-// ---------------------------------------------------------------------------
-
-static std::vector<CoordinateOperationNNPtr>
-getOps(const CoordinateOperationNNPtr &op) {
- auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
- if (concatenated)
- return concatenated->operations();
- return {op};
-}
-
-// ---------------------------------------------------------------------------
-
-static bool useDifferentTransformationsForSameSourceTarget(
- const CoordinateOperationNNPtr &opA, const CoordinateOperationNNPtr &opB) {
- auto subOpsA = getOps(opA);
- auto subOpsB = getOps(opB);
- for (const auto &subOpA : subOpsA) {
- if (!dynamic_cast<const Transformation *>(subOpA.get()))
- continue;
- if (subOpA->sourceCRS()->nameStr() == "unknown" ||
- subOpA->targetCRS()->nameStr() == "unknown")
- continue;
- for (const auto &subOpB : subOpsB) {
- if (!dynamic_cast<const Transformation *>(subOpB.get()))
- continue;
- if (subOpB->sourceCRS()->nameStr() == "unknown" ||
- subOpB->targetCRS()->nameStr() == "unknown")
- continue;
-
- if (subOpA->sourceCRS()->nameStr() ==
- subOpB->sourceCRS()->nameStr() &&
- subOpA->targetCRS()->nameStr() ==
- subOpB->targetCRS()->nameStr()) {
- if (starts_with(subOpA->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
- starts_with(subOpB->nameStr(), NULL_GEOGRAPHIC_OFFSET)) {
- continue;
- }
-
- if (!subOpA->isEquivalentTo(subOpB.get())) {
- return true;
- }
- } else if (subOpA->sourceCRS()->nameStr() ==
- subOpB->targetCRS()->nameStr() &&
- subOpA->targetCRS()->nameStr() ==
- subOpB->sourceCRS()->nameStr()) {
- if (starts_with(subOpA->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
- starts_with(subOpB->nameStr(), NULL_GEOGRAPHIC_OFFSET)) {
- continue;
- }
-
- if (!subOpA->isEquivalentTo(subOpB->inverse().get())) {
- return true;
- }
- }
- }
- }
- 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,
- const crs::GeographicCRS *geogDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- ENTER_FUNCTION();
-
- const auto &authFactory = context.context->getAuthorityFactory();
- const auto &componentsSrc = compoundSrc->componentReferenceSystems();
- if (!componentsSrc.empty()) {
-
- if (componentsSrc.size() == 2) {
- auto derivedHSrc =
- dynamic_cast<const crs::DerivedCRS *>(componentsSrc[0].get());
- if (derivedHSrc) {
- std::vector<crs::CRSNNPtr> intermComponents{
- derivedHSrc->baseCRS(), componentsSrc[1]};
- auto properties = util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- intermComponents[0]->nameStr() + " + " +
- intermComponents[1]->nameStr());
- auto intermCompound =
- crs::CompoundCRS::create(properties, intermComponents);
- auto opsFirst =
- createOperations(sourceCRS, intermCompound, context);
- assert(!opsFirst.empty());
- auto opsLast =
- createOperations(intermCompound, targetCRS, context);
- for (const auto &opLast : opsLast) {
- try {
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- {opsFirst.front(), opLast},
- disallowEmptyIntersection));
- } catch (const std::exception &) {
- }
- }
- return;
- }
- }
-
- std::vector<CoordinateOperationNNPtr> horizTransforms;
- auto srcGeogCRS = componentsSrc[0]->extractGeographicCRS();
- if (srcGeogCRS) {
- horizTransforms =
- createOperations(componentsSrc[0], targetCRS, context);
- }
- std::vector<CoordinateOperationNNPtr> verticalTransforms;
-
- const auto dbContext =
- authFactory ? authFactory->databaseContext().as_nullable()
- : nullptr;
- if (componentsSrc.size() >= 2 &&
- componentsSrc[1]->extractVerticalCRS()) {
-
- struct SetSkipHorizontalTransform {
- Context &context;
-
- explicit SetSkipHorizontalTransform(Context &contextIn)
- : context(contextIn) {
- assert(!context.skipHorizontalTransformation);
- context.skipHorizontalTransformation = true;
- }
-
- ~SetSkipHorizontalTransform() {
- context.skipHorizontalTransformation = false;
- }
- };
- SetSkipHorizontalTransform setSkipHorizontalTransform(context);
-
- verticalTransforms = createOperations(
- componentsSrc[1],
- targetCRS->promoteTo3D(std::string(), dbContext), context);
- bool foundRegisteredTransformWithAllGridsAvailable = false;
- const auto gridAvailabilityUse =
- context.context->getGridAvailabilityUse();
- const bool ignoreMissingGrids =
- gridAvailabilityUse ==
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY;
- for (const auto &op : verticalTransforms) {
- if (hasIdentifiers(op) && dbContext) {
- bool missingGrid = false;
- if (!ignoreMissingGrids) {
- const auto gridsNeeded = op->gridsNeeded(
- dbContext,
- gridAvailabilityUse ==
- CoordinateOperationContext::
- GridAvailabilityUse::KNOWN_AVAILABLE);
- for (const auto &gridDesc : gridsNeeded) {
- if (!gridDesc.available) {
- missingGrid = true;
- break;
- }
- }
- }
- if (!missingGrid) {
- foundRegisteredTransformWithAllGridsAvailable = true;
- break;
- }
- }
- }
- if (!foundRegisteredTransformWithAllGridsAvailable && srcGeogCRS &&
- !srcGeogCRS->_isEquivalentTo(
- geogDst, util::IComparable::Criterion::EQUIVALENT) &&
- !srcGeogCRS->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext)) {
- auto verticalTransformsTmp = createOperations(
- componentsSrc[1],
- NN_NO_CHECK(srcGeogCRS)
- ->promoteTo3D(std::string(), dbContext),
- context);
- bool foundRegisteredTransform = false;
- foundRegisteredTransformWithAllGridsAvailable = false;
- for (const auto &op : verticalTransformsTmp) {
- if (hasIdentifiers(op) && dbContext) {
- bool missingGrid = false;
- if (!ignoreMissingGrids) {
- const auto gridsNeeded = op->gridsNeeded(
- dbContext,
- gridAvailabilityUse ==
- CoordinateOperationContext::
- GridAvailabilityUse::KNOWN_AVAILABLE);
- for (const auto &gridDesc : gridsNeeded) {
- if (!gridDesc.available) {
- missingGrid = true;
- break;
- }
- }
- }
- foundRegisteredTransform = true;
- if (!missingGrid) {
- foundRegisteredTransformWithAllGridsAvailable =
- true;
- break;
- }
- }
- }
- if (foundRegisteredTransformWithAllGridsAvailable) {
- verticalTransforms = verticalTransformsTmp;
- } else if (foundRegisteredTransform) {
- verticalTransforms.insert(verticalTransforms.end(),
- verticalTransformsTmp.begin(),
- verticalTransformsTmp.end());
- }
- }
- }
-
- if (horizTransforms.empty() || verticalTransforms.empty()) {
- res = horizTransforms;
- return;
- }
-
- typedef std::pair<std::vector<CoordinateOperationNNPtr>,
- std::vector<CoordinateOperationNNPtr>>
- PairOfTransforms;
- std::map<std::string, PairOfTransforms>
- cacheHorizToInterpAndInterpToTarget;
-
- for (const auto &verticalTransform : verticalTransforms) {
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("Considering vertical transform " +
- objectAsStr(verticalTransform.get()));
-#endif
- crs::GeographicCRSPtr interpolationGeogCRS =
- getInterpolationGeogCRS(verticalTransform, dbContext);
- if (interpolationGeogCRS) {
-#ifdef TRACE_CREATE_OPERATIONS
- logTrace("Using " + objectAsStr(interpolationGeogCRS.get()) +
- " as interpolation CRS");
-#endif
- std::vector<CoordinateOperationNNPtr> srcToInterpOps;
- std::vector<CoordinateOperationNNPtr> interpToTargetOps;
-
- std::string key;
- const auto &ids = interpolationGeogCRS->identifiers();
- if (!ids.empty()) {
- key =
- (*ids.front()->codeSpace()) + ':' + ids.front()->code();
- }
-
- const auto computeOpsToInterp =
- [&srcToInterpOps, &interpToTargetOps, &componentsSrc,
- &interpolationGeogCRS, &targetCRS, &dbContext,
- &context]() {
- srcToInterpOps = createOperations(
- componentsSrc[0], NN_NO_CHECK(interpolationGeogCRS),
- context);
- auto target2D =
- targetCRS->demoteTo2D(std::string(), dbContext);
- if (!componentsSrc[0]->isEquivalentTo(
- target2D.get(),
- util::IComparable::Criterion::EQUIVALENT)) {
- // We do the transformation from the
- // interpolationCRS
- // to the target one in 3D (see #2225)
- // But we don't do that between sourceCRS and
- // interpolationCRS, as this would mess with an
- // orthometric elevation.
- auto interp3D = interpolationGeogCRS->promoteTo3D(
- std::string(), dbContext);
- interpToTargetOps =
- createOperations(interp3D, targetCRS, context);
- }
- };
-
- if (!key.empty()) {
- auto iter = cacheHorizToInterpAndInterpToTarget.find(key);
- if (iter == cacheHorizToInterpAndInterpToTarget.end()) {
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("looking for horizontal transformation "
- "from source to interpCRS and interpCRS to "
- "target");
-#endif
- computeOpsToInterp();
- cacheHorizToInterpAndInterpToTarget[key] =
- PairOfTransforms(srcToInterpOps, interpToTargetOps);
- } else {
- srcToInterpOps = iter->second.first;
- interpToTargetOps = iter->second.second;
- }
- } else {
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("looking for horizontal transformation "
- "from source to interpCRS and interpCRS to "
- "target");
-#endif
- computeOpsToInterp();
- }
-
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_BLOCK("creating HorizVerticalHorizPROJBased operations");
-#endif
- for (const auto &srcToInterp : srcToInterpOps) {
- if (interpToTargetOps.empty()) {
- try {
- auto op = createHorizVerticalHorizPROJBased(
- sourceCRS, targetCRS, srcToInterp,
- verticalTransform, srcToInterp->inverse(),
- interpolationGeogCRS, true);
- res.emplace_back(op);
- } catch (const std::exception &) {
- }
- } else {
- for (const auto &interpToTarget : interpToTargetOps) {
-
- if (useDifferentTransformationsForSameSourceTarget(
- srcToInterp, interpToTarget)) {
- continue;
- }
-
- try {
- auto op = createHorizVerticalHorizPROJBased(
- sourceCRS, targetCRS, srcToInterp,
- verticalTransform, interpToTarget,
- interpolationGeogCRS, true);
- res.emplace_back(op);
- } catch (const std::exception &) {
- }
- }
- }
- }
- } else {
- // This case is probably only correct if
- // verticalTransform and horizTransform are independent
- // and in particular that verticalTransform does not
- // involve a grid, because of the rather arbitrary order
- // horizontal then vertical applied
- for (const auto &horizTransform : horizTransforms) {
- try {
- auto op = createHorizVerticalPROJBased(
- sourceCRS, targetCRS, horizTransform,
- verticalTransform, disallowEmptyIntersection);
- res.emplace_back(op);
- } catch (const std::exception &) {
- }
- }
- }
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsToGeod(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::GeodeticCRS *geodDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- auto cs = cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight(
- common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE);
- auto intermGeog3DCRS =
- util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create(
- util::PropertyMap()
- .set(common::IdentifiedObject::NAME_KEY, geodDst->nameStr())
- .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- metadata::Extent::WORLD),
- geodDst->datum(), geodDst->datumEnsemble(), cs));
- auto sourceToGeog3DOps =
- createOperations(sourceCRS, intermGeog3DCRS, context);
- auto geog3DToTargetOps =
- createOperations(intermGeog3DCRS, targetCRS, context);
- if (!geog3DToTargetOps.empty()) {
- for (const auto &op : sourceToGeog3DOps) {
- auto newOp = op->shallowClone();
- setCRSs(newOp.get(), sourceCRS, intermGeog3DCRS);
- try {
- res.emplace_back(ConcatenatedOperation::createComputeMetadata(
- {newOp, geog3DToTargetOps.front()},
- disallowEmptyIntersection));
- } catch (const InvalidOperationEmptyIntersection &) {
- }
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsCompoundToCompound(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::CompoundCRS *compoundSrc,
- const crs::CompoundCRS *compoundDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- const auto &componentsSrc = compoundSrc->componentReferenceSystems();
- const auto &componentsDst = compoundDst->componentReferenceSystems();
- if (componentsSrc.empty() || componentsSrc.size() != componentsDst.size()) {
- return;
- }
- const auto srcGeog = componentsSrc[0]->extractGeographicCRS();
- const auto dstGeog = componentsDst[0]->extractGeographicCRS();
- if (srcGeog == nullptr || dstGeog == nullptr) {
- return;
- }
-
- std::vector<CoordinateOperationNNPtr> verticalTransforms;
- if (componentsSrc.size() >= 2 && componentsSrc[1]->extractVerticalCRS() &&
- componentsDst[1]->extractVerticalCRS()) {
- if (!componentsSrc[1]->_isEquivalentTo(componentsDst[1].get())) {
- verticalTransforms =
- createOperations(componentsSrc[1], componentsDst[1], context);
- }
- }
-
- // If we didn't find a non-ballpark transformation between
- // the 2 vertical CRS, then try through intermediate geographic CRS
- // For example
- // WGS 84 + EGM96 --> ETRS89 + Belfast height where
- // there is a geoid model for EGM96 referenced to WGS 84
- // and a geoid model for Belfast height referenced to ETRS89
- if (verticalTransforms.size() == 1 &&
- verticalTransforms.front()->hasBallparkTransformation()) {
- auto dbContext =
- context.context->getAuthorityFactory()->databaseContext();
- const auto intermGeogSrc =
- srcGeog->promoteTo3D(std::string(), dbContext);
- const bool intermGeogSrcIsSameAsIntermGeogDst =
- srcGeog->_isEquivalentTo(dstGeog.get());
- const auto intermGeogDst =
- intermGeogSrcIsSameAsIntermGeogDst
- ? intermGeogSrc
- : dstGeog->promoteTo3D(std::string(), dbContext);
- const auto opsSrcToGeog =
- createOperations(sourceCRS, intermGeogSrc, context);
- const auto opsGeogToTarget =
- createOperations(intermGeogDst, targetCRS, context);
- const bool hasNonTrivalSrcTransf =
- !opsSrcToGeog.empty() &&
- !opsSrcToGeog.front()->hasBallparkTransformation();
- const bool hasNonTrivialTargetTransf =
- !opsGeogToTarget.empty() &&
- !opsGeogToTarget.front()->hasBallparkTransformation();
- if (hasNonTrivalSrcTransf && hasNonTrivialTargetTransf) {
- const auto opsGeogSrcToGeogDst =
- createOperations(intermGeogSrc, intermGeogDst, context);
- for (const auto &op1 : opsSrcToGeog) {
- if (op1->hasBallparkTransformation()) {
- // std::cerr << "excluded " << op1->nameStr() << std::endl;
- continue;
- }
- for (const auto &op2 : opsGeogSrcToGeogDst) {
- for (const auto &op3 : opsGeogToTarget) {
- if (op3->hasBallparkTransformation()) {
- // std::cerr << "excluded " << op3->nameStr() <<
- // std::endl;
- continue;
- }
- try {
- res.emplace_back(
- ConcatenatedOperation::createComputeMetadata(
- intermGeogSrcIsSameAsIntermGeogDst
- ? std::vector<
- CoordinateOperationNNPtr>{op1,
- op3}
- : std::vector<
- CoordinateOperationNNPtr>{op1,
- op2,
- op3},
- disallowEmptyIntersection));
- } catch (const std::exception &) {
- }
- }
- }
- }
- }
- if (!res.empty()) {
- return;
- }
- }
-
- for (const auto &verticalTransform : verticalTransforms) {
- auto interpolationGeogCRS = NN_NO_CHECK(srcGeog);
- auto interpTransformCRS = verticalTransform->interpolationCRS();
- if (interpTransformCRS) {
- auto nn_interpTransformCRS = NN_NO_CHECK(interpTransformCRS);
- if (dynamic_cast<const crs::GeographicCRS *>(
- nn_interpTransformCRS.get())) {
- interpolationGeogCRS = NN_NO_CHECK(
- util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
- nn_interpTransformCRS));
- }
- } else {
- auto compSrc0BoundCrs =
- dynamic_cast<crs::BoundCRS *>(componentsSrc[0].get());
- auto compDst0BoundCrs =
- dynamic_cast<crs::BoundCRS *>(componentsDst[0].get());
- if (compSrc0BoundCrs && compDst0BoundCrs &&
- dynamic_cast<crs::GeographicCRS *>(
- compSrc0BoundCrs->hubCRS().get()) &&
- compSrc0BoundCrs->hubCRS()->_isEquivalentTo(
- compDst0BoundCrs->hubCRS().get())) {
- interpolationGeogCRS = NN_NO_CHECK(
- util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
- compSrc0BoundCrs->hubCRS()));
- }
- }
- auto opSrcCRSToGeogCRS =
- createOperations(componentsSrc[0], interpolationGeogCRS, context);
- auto opGeogCRStoDstCRS =
- createOperations(interpolationGeogCRS, componentsDst[0], context);
- for (const auto &opSrc : opSrcCRSToGeogCRS) {
- for (const auto &opDst : opGeogCRStoDstCRS) {
-
- try {
- auto op = createHorizVerticalHorizPROJBased(
- sourceCRS, targetCRS, opSrc, verticalTransform, opDst,
- interpolationGeogCRS, true);
- res.emplace_back(op);
- } catch (const InvalidOperationEmptyIntersection &) {
- } catch (const io::FormattingException &) {
- }
- }
- }
- }
-
- if (verticalTransforms.empty()) {
- auto resTmp =
- createOperations(componentsSrc[0], componentsDst[0], context);
- for (const auto &op : resTmp) {
- auto opClone = op->shallowClone();
- setCRSs(opClone.get(), sourceCRS, targetCRS);
- res.emplace_back(opClone);
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-void CoordinateOperationFactory::Private::createOperationsBoundToCompound(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- Private::Context &context, const crs::BoundCRS *boundSrc,
- const crs::CompoundCRS *compoundDst,
- std::vector<CoordinateOperationNNPtr> &res) {
-
- const auto &authFactory = context.context->getAuthorityFactory();
- const auto dbContext =
- authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
-
- const auto &componentsDst = compoundDst->componentReferenceSystems();
- if (!componentsDst.empty()) {
- auto compDst0BoundCrs =
- dynamic_cast<crs::BoundCRS *>(componentsDst[0].get());
- if (compDst0BoundCrs) {
- auto boundSrcHubAsGeogCRS =
- dynamic_cast<crs::GeographicCRS *>(boundSrc->hubCRS().get());
- auto compDst0BoundCrsHubAsGeogCRS =
- dynamic_cast<crs::GeographicCRS *>(
- compDst0BoundCrs->hubCRS().get());
- if (boundSrcHubAsGeogCRS && compDst0BoundCrsHubAsGeogCRS) {
- const auto boundSrcHubAsGeogCRSDatum =
- boundSrcHubAsGeogCRS->datumNonNull(dbContext);
- const auto compDst0BoundCrsHubAsGeogCRSDatum =
- compDst0BoundCrsHubAsGeogCRS->datumNonNull(dbContext);
- if (boundSrcHubAsGeogCRSDatum->_isEquivalentTo(
- compDst0BoundCrsHubAsGeogCRSDatum.get())) {
- auto cs = cs::EllipsoidalCS::
- createLatitudeLongitudeEllipsoidalHeight(
- common::UnitOfMeasure::DEGREE,
- common::UnitOfMeasure::METRE);
- auto intermGeog3DCRS = util::nn_static_pointer_cast<
- crs::CRS>(crs::GeographicCRS::create(
- util::PropertyMap()
- .set(common::IdentifiedObject::NAME_KEY,
- boundSrcHubAsGeogCRS->nameStr())
- .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
- metadata::Extent::WORLD),
- boundSrcHubAsGeogCRS->datum(),
- boundSrcHubAsGeogCRS->datumEnsemble(), cs));
- auto sourceToGeog3DOps =
- createOperations(sourceCRS, intermGeog3DCRS, context);
- auto geog3DToTargetOps =
- createOperations(intermGeog3DCRS, targetCRS, context);
- for (const auto &opSrc : sourceToGeog3DOps) {
- for (const auto &opDst : geog3DToTargetOps) {
- if (opSrc->targetCRS() && opDst->sourceCRS() &&
- !opSrc->targetCRS()->_isEquivalentTo(
- opDst->sourceCRS().get())) {
- // Shouldn't happen normally, but typically
- // one of them can be 2D and the other 3D
- // due to above createOperations() not
- // exactly setting the expected source and
- // target CRS.
- // So create an adapter operation...
- auto intermOps = createOperations(
- NN_NO_CHECK(opSrc->targetCRS()),
- NN_NO_CHECK(opDst->sourceCRS()), context);
- if (!intermOps.empty()) {
- res.emplace_back(
- ConcatenatedOperation::
- createComputeMetadata(
- {opSrc, intermOps.front(),
- opDst},
- disallowEmptyIntersection));
- }
- } else {
- res.emplace_back(
- ConcatenatedOperation::
- createComputeMetadata(
- {opSrc, opDst},
- disallowEmptyIntersection));
- }
- }
- }
- return;
- }
- }
- }
- }
-
- // There might be better things to do, but for now just ignore the
- // transformation of the bound CRS
- res = createOperations(boundSrc->baseCRS(), targetCRS, context);
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-/** \brief Find a list of CoordinateOperation from sourceCRS to targetCRS.
- *
- * The operations are sorted with the most relevant ones first: by
- * descending
- * area (intersection of the transformation area with the area of interest,
- * or intersection of the transformation with the area of use of the CRS),
- * and
- * by increasing accuracy. Operations with unknown accuracy are sorted last,
- * whatever their area.
- *
- * When one of the source or target CRS has a vertical component but not the
- * other one, the one that has no vertical component is automatically promoted
- * to a 3D version, where its vertical axis is the ellipsoidal height in metres,
- * using the ellipsoid of the base geodetic CRS.
- *
- * @param sourceCRS source CRS.
- * @param targetCRS target CRS.
- * @param context Search context.
- * @return a list
- */
-std::vector<CoordinateOperationNNPtr>
-CoordinateOperationFactory::createOperations(
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- const CoordinateOperationContextNNPtr &context) const {
-
-#ifdef TRACE_CREATE_OPERATIONS
- ENTER_FUNCTION();
-#endif
- // Look if we are called on CRS that have a link to a 'canonical'
- // BoundCRS
- // If so, use that one as input
- const auto &srcBoundCRS = sourceCRS->canonicalBoundCRS();
- const auto &targetBoundCRS = targetCRS->canonicalBoundCRS();
- auto l_sourceCRS = srcBoundCRS ? NN_NO_CHECK(srcBoundCRS) : sourceCRS;
- auto l_targetCRS = targetBoundCRS ? NN_NO_CHECK(targetBoundCRS) : targetCRS;
- const auto &authFactory = context->getAuthorityFactory();
-
- metadata::ExtentPtr sourceCRSExtent;
- auto l_resolvedSourceCRS =
- crs::CRS::getResolvedCRS(l_sourceCRS, authFactory, sourceCRSExtent);
- metadata::ExtentPtr targetCRSExtent;
- auto l_resolvedTargetCRS =
- crs::CRS::getResolvedCRS(l_targetCRS, authFactory, targetCRSExtent);
- Private::Context contextPrivate(sourceCRSExtent, targetCRSExtent, context);
-
- if (context->getSourceAndTargetCRSExtentUse() ==
- CoordinateOperationContext::SourceTargetCRSExtentUse::INTERSECTION) {
- if (sourceCRSExtent && targetCRSExtent &&
- !sourceCRSExtent->intersects(NN_NO_CHECK(targetCRSExtent))) {
- return std::vector<CoordinateOperationNNPtr>();
- }
- }
-
- return filterAndSort(Private::createOperations(l_resolvedSourceCRS,
- l_resolvedTargetCRS,
- contextPrivate),
- context, sourceCRSExtent, targetCRSExtent);
-}
-
-// ---------------------------------------------------------------------------
-
-/** \brief Instantiate a CoordinateOperationFactory.
- */
-CoordinateOperationFactoryNNPtr CoordinateOperationFactory::create() {
- return NN_NO_CHECK(
- CoordinateOperationFactory::make_unique<CoordinateOperationFactory>());
-}
-
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-InverseCoordinateOperation::~InverseCoordinateOperation() = default;
-
-// ---------------------------------------------------------------------------
-
-InverseCoordinateOperation::InverseCoordinateOperation(
- const CoordinateOperationNNPtr &forwardOperationIn,
- bool wktSupportsInversion)
- : forwardOperation_(forwardOperationIn),
- wktSupportsInversion_(wktSupportsInversion) {}
-
-// ---------------------------------------------------------------------------
-
-void InverseCoordinateOperation::setPropertiesFromForward() {
- setProperties(
- createPropertiesForInverse(forwardOperation_.get(), false, false));
- setAccuracies(forwardOperation_->coordinateOperationAccuracies());
- if (forwardOperation_->sourceCRS() && forwardOperation_->targetCRS()) {
- setCRSs(forwardOperation_.get(), true);
- }
- setHasBallparkTransformation(
- forwardOperation_->hasBallparkTransformation());
-}
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperationNNPtr InverseCoordinateOperation::inverse() const {
- return forwardOperation_;
-}
-
-// ---------------------------------------------------------------------------
-
-void InverseCoordinateOperation::_exportToPROJString(
- io::PROJStringFormatter *formatter) const {
- formatter->startInversion();
- forwardOperation_->_exportToPROJString(formatter);
- formatter->stopInversion();
-}
-
-// ---------------------------------------------------------------------------
-
-bool InverseCoordinateOperation::_isEquivalentTo(
- const util::IComparable *other, util::IComparable::Criterion criterion,
- const io::DatabaseContextPtr &dbContext) const {
- auto otherICO = dynamic_cast<const InverseCoordinateOperation *>(other);
- if (otherICO == nullptr ||
- !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) {
- return false;
- }
- return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion,
- dbContext);
-}
-
-// ---------------------------------------------------------------------------
-
-PROJBasedOperation::~PROJBasedOperation() = default;
-
-// ---------------------------------------------------------------------------
-
-PROJBasedOperation::PROJBasedOperation(const OperationMethodNNPtr &methodIn)
- : SingleOperation(methodIn) {}
-
-// ---------------------------------------------------------------------------
-
-PROJBasedOperationNNPtr PROJBasedOperation::create(
- const util::PropertyMap &properties, const std::string &PROJString,
- const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
- auto method = OperationMethod::create(
- util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
- "PROJ-based operation method: " + PROJString),
- std::vector<GeneralOperationParameterNNPtr>{});
- auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method);
- op->assignSelf(op);
- op->projString_ = PROJString;
- if (sourceCRS && targetCRS) {
- op->setCRSs(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), nullptr);
- }
- op->setProperties(
- addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation"));
- op->setAccuracies(accuracies);
- return op;
-}
-
-// ---------------------------------------------------------------------------
-
-PROJBasedOperationNNPtr PROJBasedOperation::create(
- const util::PropertyMap &properties,
- const io::IPROJStringExportableNNPtr &projExportable, bool inverse,
- const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
- const crs::CRSPtr &interpolationCRS,
- const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies,
- bool hasBallparkTransformation) {
-
- auto formatter = io::PROJStringFormatter::create();
- if (inverse) {
- formatter->startInversion();
- }
- projExportable->_exportToPROJString(formatter.get());
- if (inverse) {
- formatter->stopInversion();
- }
- auto projString = formatter->toString();
-
- auto method = OperationMethod::create(
- util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
- "PROJ-based operation method (approximate): " +
- projString),
- std::vector<GeneralOperationParameterNNPtr>{});
- auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method);
- op->assignSelf(op);
- op->projString_ = projString;
- op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
- op->setProperties(
- addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation"));
- op->setAccuracies(accuracies);
- op->projStringExportable_ = projExportable.as_nullable();
- op->inverse_ = inverse;
- op->setHasBallparkTransformation(hasBallparkTransformation);
- return op;
-}
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperationNNPtr PROJBasedOperation::inverse() const {
-
- if (projStringExportable_ && sourceCRS() && targetCRS()) {
- return util::nn_static_pointer_cast<CoordinateOperation>(
- PROJBasedOperation::create(
- createPropertiesForInverse(this, false, false),
- NN_NO_CHECK(projStringExportable_), !inverse_,
- NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()),
- interpolationCRS(), coordinateOperationAccuracies(),
- hasBallparkTransformation()));
- }
-
- auto formatter = io::PROJStringFormatter::create();
- formatter->startInversion();
- try {
- formatter->ingestPROJString(projString_);
- } catch (const io::ParsingException &e) {
- throw util::UnsupportedOperationException(
- std::string("PROJBasedOperation::inverse() failed: ") + e.what());
- }
- formatter->stopInversion();
-
- auto op = PROJBasedOperation::create(
- createPropertiesForInverse(this, false, false), formatter->toString(),
- targetCRS(), sourceCRS(), coordinateOperationAccuracies());
- if (sourceCRS() && targetCRS()) {
- op->setCRSs(NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()),
- interpolationCRS());
- }
- op->setHasBallparkTransformation(hasBallparkTransformation());
- return util::nn_static_pointer_cast<CoordinateOperation>(op);
-}
-
-// ---------------------------------------------------------------------------
-
-void PROJBasedOperation::_exportToWKT(io::WKTFormatter *formatter) const {
-
- if (sourceCRS() && targetCRS()) {
- exportTransformationToWKT(formatter);
- return;
- }
-
- const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
- if (!isWKT2) {
- throw io::FormattingException(
- "PROJBasedOperation can only be exported to WKT2");
- }
-
- formatter->startNode(io::WKTConstants::CONVERSION, false);
- formatter->addQuotedString(nameStr());
- method()->_exportToWKT(formatter);
-
- for (const auto &paramValue : parameterValues()) {
- paramValue->_exportToWKT(formatter);
- }
- formatter->endNode();
-}
-
-// ---------------------------------------------------------------------------
-
-void PROJBasedOperation::_exportToJSON(
- io::JSONFormatter *formatter) const // throw(FormattingException)
-{
- auto writer = formatter->writer();
- auto objectContext(formatter->MakeObjectContext(
- (sourceCRS() && targetCRS()) ? "Transformation" : "Conversion",
- !identifiers().empty()));
-
- writer->AddObjKey("name");
- auto l_name = nameStr();
- if (l_name.empty()) {
- writer->Add("unnamed");
- } else {
- writer->Add(l_name);
- }
-
- if (sourceCRS() && targetCRS()) {
- writer->AddObjKey("source_crs");
- formatter->setAllowIDInImmediateChild();
- sourceCRS()->_exportToJSON(formatter);
-
- writer->AddObjKey("target_crs");
- formatter->setAllowIDInImmediateChild();
- targetCRS()->_exportToJSON(formatter);
- }
-
- writer->AddObjKey("method");
- formatter->setOmitTypeInImmediateChild();
- formatter->setAllowIDInImmediateChild();
- method()->_exportToJSON(formatter);
-
- const auto &l_parameterValues = parameterValues();
- if (!l_parameterValues.empty()) {
- writer->AddObjKey("parameters");
- {
- auto parametersContext(writer->MakeArrayContext(false));
- for (const auto &genOpParamvalue : l_parameterValues) {
- formatter->setAllowIDInImmediateChild();
- formatter->setOmitTypeInImmediateChild();
- genOpParamvalue->_exportToJSON(formatter);
- }
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-void PROJBasedOperation::_exportToPROJString(
- io::PROJStringFormatter *formatter) const {
- if (projStringExportable_) {
- if (inverse_) {
- formatter->startInversion();
- }
- projStringExportable_->_exportToPROJString(formatter);
- if (inverse_) {
- formatter->stopInversion();
- }
- return;
- }
-
- try {
- formatter->ingestPROJString(projString_);
- } catch (const io::ParsingException &e) {
- throw io::FormattingException(
- std::string("PROJBasedOperation::exportToPROJString() failed: ") +
- e.what());
- }
-}
-
-// ---------------------------------------------------------------------------
-
-CoordinateOperationNNPtr PROJBasedOperation::_shallowClone() const {
- auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(*this);
- op->assignSelf(op);
- op->setCRSs(this, false);
- return util::nn_static_pointer_cast<CoordinateOperation>(op);
-}
-
-// ---------------------------------------------------------------------------
-
-std::set<GridDescription>
-PROJBasedOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
- bool considerKnownGridsAsAvailable) const {
- std::set<GridDescription> res;
-
- try {
- auto formatterOut = io::PROJStringFormatter::create();
- auto formatter = io::PROJStringFormatter::create();
- formatter->ingestPROJString(exportToPROJString(formatterOut.get()));
- const auto usedGridNames = formatter->getUsedGridNames();
- for (const auto &shortName : usedGridNames) {
- GridDescription desc;
- desc.shortName = shortName;
- if (databaseContext) {
- databaseContext->lookForGridInfo(
- desc.shortName, considerKnownGridsAsAvailable,
- desc.fullName, desc.packageName, desc.url,
- desc.directDownload, desc.openLicense, desc.available);
- }
- res.insert(desc);
- }
- } catch (const io::ParsingException &) {
- }
-
- return res;
-}
-
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
-} // namespace operation
-
-namespace crs {
-// ---------------------------------------------------------------------------
-
-//! @cond Doxygen_Suppress
-
-crs::CRSNNPtr CRS::getResolvedCRS(const crs::CRSNNPtr &crs,
- const io::AuthorityFactoryPtr &authFactory,
- metadata::ExtentPtr &extentOut) {
- const auto &ids = crs->identifiers();
- const auto &name = crs->nameStr();
-
- bool approxExtent;
- extentOut = getExtentPossiblySynthetized(crs, approxExtent);
-
- // We try to "identify" the provided CRS with the ones of the database,
- // but in a more restricted way that what identify() does.
- // If we get a match from id in priority, and from name as a fallback, and
- // that they are equivalent to the input CRS, then use the identified CRS.
- // Even if they aren't equivalent, we update extentOut with the one of the
- // identified CRS if our input one is absent/not reliable.
-
- const auto tryToIdentifyByName = [&crs, &name, &authFactory, approxExtent,
- &extentOut](
- io::AuthorityFactory::ObjectType objectType) {
- if (name != "unknown" && name != "unnamed") {
- auto matches = authFactory->createObjectsFromName(
- name, {objectType}, false, 2);
- if (matches.size() == 1) {
- const auto match =
- util::nn_static_pointer_cast<crs::CRS>(matches.front());
- if (approxExtent || !extentOut) {
- extentOut = getExtent(match);
- }
- if (match->isEquivalentTo(
- crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
- return match;
- }
- }
- }
- return crs;
- };
-
- auto geogCRS = dynamic_cast<crs::GeographicCRS *>(crs.get());
- if (geogCRS && authFactory) {
- if (!ids.empty()) {
- const auto tmpAuthFactory = io::AuthorityFactory::create(
- authFactory->databaseContext(), *ids.front()->codeSpace());
- try {
- auto resolvedCrs(
- tmpAuthFactory->createGeographicCRS(ids.front()->code()));
- if (approxExtent || !extentOut) {
- extentOut = getExtent(resolvedCrs);
- }
- if (resolvedCrs->isEquivalentTo(
- crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
- return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs);
- }
- } catch (const std::exception &) {
- }
- } else {
- return tryToIdentifyByName(
- geogCRS->coordinateSystem()->axisList().size() == 2
- ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS
- : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS);
- }
- }
-
- auto projectedCrs = dynamic_cast<crs::ProjectedCRS *>(crs.get());
- if (projectedCrs && authFactory) {
- if (!ids.empty()) {
- const auto tmpAuthFactory = io::AuthorityFactory::create(
- authFactory->databaseContext(), *ids.front()->codeSpace());
- try {
- auto resolvedCrs(
- tmpAuthFactory->createProjectedCRS(ids.front()->code()));
- if (approxExtent || !extentOut) {
- extentOut = getExtent(resolvedCrs);
- }
- if (resolvedCrs->isEquivalentTo(
- crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
- return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs);
- }
- } catch (const std::exception &) {
- }
- } else {
- return tryToIdentifyByName(
- io::AuthorityFactory::ObjectType::PROJECTED_CRS);
- }
- }
-
- auto compoundCrs = dynamic_cast<crs::CompoundCRS *>(crs.get());
- if (compoundCrs && authFactory) {
- if (!ids.empty()) {
- const auto tmpAuthFactory = io::AuthorityFactory::create(
- authFactory->databaseContext(), *ids.front()->codeSpace());
- try {
- auto resolvedCrs(
- tmpAuthFactory->createCompoundCRS(ids.front()->code()));
- if (approxExtent || !extentOut) {
- extentOut = getExtent(resolvedCrs);
- }
- if (resolvedCrs->isEquivalentTo(
- crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
- return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs);
- }
- } catch (const std::exception &) {
- }
- } else {
- auto outCrs = tryToIdentifyByName(
- io::AuthorityFactory::ObjectType::COMPOUND_CRS);
- const auto &components = compoundCrs->componentReferenceSystems();
- if (outCrs.get() != crs.get()) {
- bool hasGeoid = false;
- if (components.size() == 2) {
- auto vertCRS =
- dynamic_cast<crs::VerticalCRS *>(components[1].get());
- if (vertCRS && !vertCRS->geoidModel().empty()) {
- hasGeoid = true;
- }
- }
- if (!hasGeoid) {
- return outCrs;
- }
- }
- if (approxExtent || !extentOut) {
- // If we still did not get a reliable extent, then try to
- // resolve the components of the compoundCRS, and take the
- // intersection of their extent.
- extentOut = metadata::ExtentPtr();
- for (const auto &component : components) {
- metadata::ExtentPtr componentExtent;
- getResolvedCRS(component, authFactory, componentExtent);
- if (extentOut && componentExtent)
- extentOut = extentOut->intersection(
- NN_NO_CHECK(componentExtent));
- else if (componentExtent)
- extentOut = componentExtent;
- }
- }
- }
- }
- return crs;
-}
-
-//! @endcond
-
-} // namespace crs
-NS_PROJ_END
diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp
index 573dd6db..9d58f733 100644
--- a/src/iso19111/crs.cpp
+++ b/src/iso19111/crs.cpp
@@ -5188,7 +5188,8 @@ BoundCRSNNPtr BoundCRS::createFromNadgrids(const CRSNNPtr &baseCRSIn,
" (with Greenwich prime meridian)"),
sourceGeographicCRS->datumNonNull(nullptr)->ellipsoid(),
util::optional<std::string>(), datum::PrimeMeridian::GREENWICH),
- sourceGeographicCRS->coordinateSystem());
+ cs::EllipsoidalCS::createLatitudeLongitude(
+ common::UnitOfMeasure::DEGREE));
}
std::string transformationName = transformationSourceCRS->nameStr();
transformationName += " to WGS84";
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index 2a03fd4e..0a5f58a4 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -39,12 +39,14 @@
#include "proj/metadata.hpp"
#include "proj/util.hpp"
-#include "proj/internal/coordinateoperation_internal.hpp"
#include "proj/internal/internal.hpp"
#include "proj/internal/io_internal.hpp"
#include "proj/internal/lru_cache.hpp"
#include "proj/internal/tracing.hpp"
+#include "operation/coordinateoperation_internal.hpp"
+#include "operation/parammappings.hpp"
+
#include "sqlite3_utils.hpp"
#include <cmath>
diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp
index 8a42e7ee..dc51c5d9 100644
--- a/src/iso19111/io.cpp
+++ b/src/iso19111/io.cpp
@@ -53,7 +53,11 @@
#include "proj/metadata.hpp"
#include "proj/util.hpp"
-#include "proj/internal/coordinateoperation_internal.hpp"
+#include "operation/coordinateoperation_internal.hpp"
+#include "operation/esriparammappings.hpp"
+#include "operation/oputils.hpp"
+#include "operation/parammappings.hpp"
+
#include "proj/internal/coordinatesystem_internal.hpp"
#include "proj/internal/internal.hpp"
#include "proj/internal/io_internal.hpp"
@@ -4234,7 +4238,8 @@ createBoundCRSSourceTransformationCRS(const crs::CRSPtr &sourceCRS,
sourceGeographicCRS->datum()->ellipsoid(),
util::optional<std::string>(),
datum::PrimeMeridian::GREENWICH),
- sourceGeographicCRS->coordinateSystem())
+ cs::EllipsoidalCS::createLatitudeLongitude(
+ common::UnitOfMeasure::DEGREE))
.as_nullable();
}
} else {
diff --git a/src/iso19111/operation/concatenatedoperation.cpp b/src/iso19111/operation/concatenatedoperation.cpp
new file mode 100644
index 00000000..ce4b015a
--- /dev/null
+++ b/src/iso19111/operation/concatenatedoperation.cpp
@@ -0,0 +1,710 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/crs.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
+
+#include "coordinateoperation_internal.hpp"
+#include "oputils.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+
+#include "proj_json_streaming_writer.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct ConcatenatedOperation::Private {
+ std::vector<CoordinateOperationNNPtr> operations_{};
+ bool computedName_ = false;
+
+ explicit Private(const std::vector<CoordinateOperationNNPtr> &operationsIn)
+ : operations_(operationsIn) {}
+ Private(const Private &) = default;
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ConcatenatedOperation::~ConcatenatedOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ConcatenatedOperation::ConcatenatedOperation(const ConcatenatedOperation &other)
+ : CoordinateOperation(other),
+ d(internal::make_unique<Private>(*(other.d))) {}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+ConcatenatedOperation::ConcatenatedOperation(
+ const std::vector<CoordinateOperationNNPtr> &operationsIn)
+ : CoordinateOperation(), d(internal::make_unique<Private>(operationsIn)) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the operation steps of the concatenated operation.
+ *
+ * @return the operation steps.
+ */
+const std::vector<CoordinateOperationNNPtr> &
+ConcatenatedOperation::operations() const {
+ return d->operations_;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static bool compareStepCRS(const crs::CRS *a, const crs::CRS *b) {
+ const auto &aIds = a->identifiers();
+ const auto &bIds = b->identifiers();
+ if (aIds.size() == 1 && bIds.size() == 1 &&
+ aIds[0]->code() == bIds[0]->code() &&
+ *aIds[0]->codeSpace() == *bIds[0]->codeSpace()) {
+ return true;
+ }
+ return a->_isEquivalentTo(b, util::IComparable::Criterion::EQUIVALENT);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a ConcatenatedOperation
+ *
+ * @param properties See \ref general_properties. At minimum the name should
+ * be
+ * defined.
+ * @param operationsIn Vector of the CoordinateOperation steps.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+ConcatenatedOperationNNPtr ConcatenatedOperation::create(
+ const util::PropertyMap &properties,
+ const std::vector<CoordinateOperationNNPtr> &operationsIn,
+ const std::vector<metadata::PositionalAccuracyNNPtr>
+ &accuracies) // throw InvalidOperation
+{
+ if (operationsIn.size() < 2) {
+ throw InvalidOperation(
+ "ConcatenatedOperation must have at least 2 operations");
+ }
+ crs::CRSPtr lastTargetCRS;
+
+ crs::CRSPtr interpolationCRS;
+ bool interpolationCRSValid = true;
+ for (size_t i = 0; i < operationsIn.size(); i++) {
+ auto l_sourceCRS = operationsIn[i]->sourceCRS();
+ auto l_targetCRS = operationsIn[i]->targetCRS();
+
+ if (interpolationCRSValid) {
+ auto subOpInterpCRS = operationsIn[i]->interpolationCRS();
+ if (interpolationCRS == nullptr)
+ interpolationCRS = subOpInterpCRS;
+ else if (subOpInterpCRS == nullptr ||
+ !(subOpInterpCRS->isEquivalentTo(
+ interpolationCRS.get(),
+ util::IComparable::Criterion::EQUIVALENT))) {
+ interpolationCRS = nullptr;
+ interpolationCRSValid = false;
+ }
+ }
+
+ if (l_sourceCRS == nullptr || l_targetCRS == nullptr) {
+ throw InvalidOperation("At least one of the operation lacks a "
+ "source and/or target CRS");
+ }
+ if (i >= 1) {
+ if (!compareStepCRS(l_sourceCRS.get(), lastTargetCRS.get())) {
+#ifdef DEBUG_CONCATENATED_OPERATION
+ std::cerr << "Step " << i - 1 << ": "
+ << operationsIn[i - 1]->nameStr() << std::endl;
+ std::cerr << "Step " << i << ": " << operationsIn[i]->nameStr()
+ << std::endl;
+ {
+ auto f(io::WKTFormatter::create(
+ io::WKTFormatter::Convention::WKT2_2019));
+ std::cerr << "Source CRS of step " << i << ":" << std::endl;
+ std::cerr << l_sourceCRS->exportToWKT(f.get()) << std::endl;
+ }
+ {
+ auto f(io::WKTFormatter::create(
+ io::WKTFormatter::Convention::WKT2_2019));
+ std::cerr << "Target CRS of step " << i - 1 << ":"
+ << std::endl;
+ std::cerr << lastTargetCRS->exportToWKT(f.get())
+ << std::endl;
+ }
+#endif
+ throw InvalidOperation(
+ "Inconsistent chaining of CRS in operations");
+ }
+ }
+ lastTargetCRS = l_targetCRS;
+ }
+ auto op = ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>(
+ operationsIn);
+ op->assignSelf(op);
+ op->setProperties(properties);
+ op->setCRSs(NN_NO_CHECK(operationsIn[0]->sourceCRS()),
+ NN_NO_CHECK(operationsIn.back()->targetCRS()),
+ interpolationCRS);
+ op->setAccuracies(accuracies);
+#ifdef DEBUG_CONCATENATED_OPERATION
+ {
+ auto f(
+ io::WKTFormatter::create(io::WKTFormatter::Convention::WKT2_2019));
+ std::cerr << "ConcatenatedOperation::create()" << std::endl;
+ std::cerr << op->exportToWKT(f.get()) << std::endl;
+ }
+#endif
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+void ConcatenatedOperation::fixStepsDirection(
+ const crs::CRSNNPtr &concatOpSourceCRS,
+ const crs::CRSNNPtr &concatOpTargetCRS,
+ std::vector<CoordinateOperationNNPtr> &operationsInOut) {
+
+ // Set of heuristics to assign CRS to steps, and possibly reverse them.
+
+ const auto isGeographic = [](const crs::CRS *crs) -> bool {
+ return dynamic_cast<const crs::GeographicCRS *>(crs) != nullptr;
+ };
+
+ const auto isGeocentric = [](const crs::CRS *crs) -> bool {
+ auto geodCRS = dynamic_cast<const crs::GeodeticCRS *>(crs);
+ if (geodCRS && geodCRS->coordinateSystem()->axisList().size() == 3)
+ return true;
+ return false;
+ };
+
+ for (size_t i = 0; i < operationsInOut.size(); ++i) {
+ auto &op = operationsInOut[i];
+ auto l_sourceCRS = op->sourceCRS();
+ auto l_targetCRS = op->targetCRS();
+ auto conv = dynamic_cast<const Conversion *>(op.get());
+ if (conv && i == 0 && !l_sourceCRS && !l_targetCRS) {
+ auto derivedCRS =
+ dynamic_cast<const crs::DerivedCRS *>(concatOpSourceCRS.get());
+ if (derivedCRS) {
+ if (i + 1 < operationsInOut.size()) {
+ // use the sourceCRS of the next operation as our target CRS
+ l_targetCRS = operationsInOut[i + 1]->sourceCRS();
+ // except if it looks like the next operation should
+ // actually be reversed !!!
+ if (l_targetCRS &&
+ !compareStepCRS(l_targetCRS.get(),
+ derivedCRS->baseCRS().get()) &&
+ operationsInOut[i + 1]->targetCRS() &&
+ compareStepCRS(
+ operationsInOut[i + 1]->targetCRS().get(),
+ derivedCRS->baseCRS().get())) {
+ l_targetCRS = operationsInOut[i + 1]->targetCRS();
+ }
+ }
+ if (!l_targetCRS) {
+ l_targetCRS = derivedCRS->baseCRS().as_nullable();
+ }
+ auto invConv =
+ util::nn_dynamic_pointer_cast<InverseConversion>(op);
+ auto nn_targetCRS = NN_NO_CHECK(l_targetCRS);
+ if (invConv) {
+ invConv->inverse()->setCRSs(nn_targetCRS, concatOpSourceCRS,
+ nullptr);
+ op->setCRSs(concatOpSourceCRS, nn_targetCRS, nullptr);
+ } else {
+ op->setCRSs(nn_targetCRS, concatOpSourceCRS, nullptr);
+ op = op->inverse();
+ }
+ } else if (i + 1 < operationsInOut.size()) {
+ /* coverity[copy_paste_error] */
+ l_targetCRS = operationsInOut[i + 1]->sourceCRS();
+ if (l_targetCRS) {
+ op->setCRSs(concatOpSourceCRS, NN_NO_CHECK(l_targetCRS),
+ nullptr);
+ }
+ }
+ } else if (conv && i + 1 == operationsInOut.size() && !l_sourceCRS &&
+ !l_targetCRS) {
+ auto derivedCRS =
+ dynamic_cast<const crs::DerivedCRS *>(concatOpTargetCRS.get());
+ if (derivedCRS) {
+ if (i >= 1) {
+ // use the sourceCRS of the previous operation as our source
+ // CRS
+ l_sourceCRS = operationsInOut[i - 1]->targetCRS();
+ // except if it looks like the previous operation should
+ // actually be reversed !!!
+ if (l_sourceCRS &&
+ !compareStepCRS(l_sourceCRS.get(),
+ derivedCRS->baseCRS().get()) &&
+ operationsInOut[i - 1]->sourceCRS() &&
+ compareStepCRS(
+ operationsInOut[i - 1]->sourceCRS().get(),
+ derivedCRS->baseCRS().get())) {
+ l_targetCRS = operationsInOut[i - 1]->sourceCRS();
+ }
+ }
+ if (!l_sourceCRS) {
+ l_sourceCRS = derivedCRS->baseCRS().as_nullable();
+ }
+ op->setCRSs(NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS,
+ nullptr);
+ } else if (i >= 1) {
+ l_sourceCRS = operationsInOut[i - 1]->targetCRS();
+ if (l_sourceCRS) {
+ derivedCRS = dynamic_cast<const crs::DerivedCRS *>(
+ l_sourceCRS.get());
+ if (conv->isEquivalentTo(
+ derivedCRS->derivingConversion().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ op->setCRSs(concatOpTargetCRS, NN_NO_CHECK(l_sourceCRS),
+ nullptr);
+ op = op->inverse();
+ }
+ op->setCRSs(NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS,
+ nullptr);
+ }
+ }
+ } else if (conv && i > 0 && i < operationsInOut.size() - 1) {
+ // For an intermediate conversion, use the target CRS of the
+ // previous step and the source CRS of the next step
+ l_sourceCRS = operationsInOut[i - 1]->targetCRS();
+ l_targetCRS = operationsInOut[i + 1]->sourceCRS();
+ if (l_sourceCRS && l_targetCRS) {
+ op->setCRSs(NN_NO_CHECK(l_sourceCRS), NN_NO_CHECK(l_targetCRS),
+ nullptr);
+ }
+ } else if (!conv && l_sourceCRS && l_targetCRS) {
+
+ // Transformations might be mentioned in their forward directions,
+ // whereas we should instead use the reverse path.
+ auto prevOpTarget = (i == 0) ? concatOpSourceCRS.as_nullable()
+ : operationsInOut[i - 1]->targetCRS();
+ if (compareStepCRS(l_sourceCRS.get(), prevOpTarget.get())) {
+ // do nothing
+ } else if (compareStepCRS(l_targetCRS.get(), prevOpTarget.get())) {
+ op = op->inverse();
+ }
+ // Below is needed for EPSG:9103 which chains NAD83(2011) geographic
+ // 2D with NAD83(2011) geocentric
+ else if (l_sourceCRS->nameStr() == prevOpTarget->nameStr() &&
+ ((isGeographic(l_sourceCRS.get()) &&
+ isGeocentric(prevOpTarget.get())) ||
+ (isGeocentric(l_sourceCRS.get()) &&
+ isGeographic(prevOpTarget.get())))) {
+ auto newOp(Conversion::createGeographicGeocentric(
+ NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_sourceCRS)));
+ operationsInOut.insert(operationsInOut.begin() + i, newOp);
+ } else if (l_targetCRS->nameStr() == prevOpTarget->nameStr() &&
+ ((isGeographic(l_targetCRS.get()) &&
+ isGeocentric(prevOpTarget.get())) ||
+ (isGeocentric(l_targetCRS.get()) &&
+ isGeographic(prevOpTarget.get())))) {
+ auto newOp(Conversion::createGeographicGeocentric(
+ NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_targetCRS)));
+ operationsInOut.insert(operationsInOut.begin() + i, newOp);
+ }
+ }
+ }
+
+ if (!operationsInOut.empty()) {
+ auto l_sourceCRS = operationsInOut.front()->sourceCRS();
+ if (l_sourceCRS &&
+ !compareStepCRS(l_sourceCRS.get(), concatOpSourceCRS.get())) {
+ throw InvalidOperation("The source CRS of the first step of "
+ "concatenated operation is not the same "
+ "as the source CRS of the concatenated "
+ "operation itself");
+ }
+
+ auto l_targetCRS = operationsInOut.back()->targetCRS();
+ if (l_targetCRS &&
+ !compareStepCRS(l_targetCRS.get(), concatOpTargetCRS.get())) {
+ if (l_targetCRS->nameStr() == concatOpTargetCRS->nameStr() &&
+ ((isGeographic(l_targetCRS.get()) &&
+ isGeocentric(concatOpTargetCRS.get())) ||
+ (isGeocentric(l_targetCRS.get()) &&
+ isGeographic(concatOpTargetCRS.get())))) {
+ auto newOp(Conversion::createGeographicGeocentric(
+ NN_NO_CHECK(l_targetCRS), concatOpTargetCRS));
+ operationsInOut.push_back(newOp);
+ } else {
+ throw InvalidOperation("The target CRS of the last step of "
+ "concatenated operation is not the same "
+ "as the target CRS of the concatenated "
+ "operation itself");
+ }
+ }
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a ConcatenatedOperation, or return a single
+ * coordinate
+ * operation.
+ *
+ * This computes its accuracy from the sum of its member operations, its
+ * extent
+ *
+ * @param operationsIn Vector of the CoordinateOperation steps.
+ * @param checkExtent Whether we should check the non-emptyness of the
+ * intersection
+ * of the extents of the operations
+ * @throws InvalidOperation
+ */
+CoordinateOperationNNPtr ConcatenatedOperation::createComputeMetadata(
+ const std::vector<CoordinateOperationNNPtr> &operationsIn,
+ bool checkExtent) // throw InvalidOperation
+{
+ util::PropertyMap properties;
+
+ if (operationsIn.size() == 1) {
+ return operationsIn[0];
+ }
+
+ std::vector<CoordinateOperationNNPtr> flattenOps;
+ bool hasBallparkTransformation = false;
+ for (const auto &subOp : operationsIn) {
+ hasBallparkTransformation |= subOp->hasBallparkTransformation();
+ auto subOpConcat =
+ dynamic_cast<const ConcatenatedOperation *>(subOp.get());
+ if (subOpConcat) {
+ auto subOps = subOpConcat->operations();
+ for (const auto &subSubOp : subOps) {
+ flattenOps.emplace_back(subSubOp);
+ }
+ } else {
+ flattenOps.emplace_back(subOp);
+ }
+ }
+
+ // Remove consecutive inverse operations
+ if (flattenOps.size() > 2) {
+ std::vector<size_t> indices;
+ for (size_t i = 0; i < flattenOps.size(); ++i)
+ indices.push_back(i);
+ while (true) {
+ bool bHasChanged = false;
+ for (size_t i = 0; i + 1 < indices.size(); ++i) {
+ if (flattenOps[indices[i]]->_isEquivalentTo(
+ flattenOps[indices[i + 1]]->inverse().get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ flattenOps[indices[i]]->sourceCRS()->_isEquivalentTo(
+ flattenOps[indices[i + 1]]->targetCRS().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ indices.erase(indices.begin() + i, indices.begin() + i + 2);
+ bHasChanged = true;
+ break;
+ }
+ }
+ // We bail out if indices.size() == 2, because potentially
+ // the last 2 remaining ones could auto-cancel, and we would have
+ // to have a special case for that (and this happens in practice).
+ if (!bHasChanged || indices.size() <= 2)
+ break;
+ }
+ if (indices.size() < flattenOps.size()) {
+ std::vector<CoordinateOperationNNPtr> flattenOpsNew;
+ for (size_t i = 0; i < indices.size(); ++i) {
+ flattenOpsNew.emplace_back(flattenOps[indices[i]]);
+ }
+ flattenOps = std::move(flattenOpsNew);
+ }
+ }
+
+ if (flattenOps.size() == 1) {
+ return flattenOps[0];
+ }
+
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ computeConcatenatedName(flattenOps));
+
+ bool emptyIntersection = false;
+ auto extent = getExtent(flattenOps, false, emptyIntersection);
+ if (checkExtent && emptyIntersection) {
+ std::string msg(
+ "empty intersection of area of validity of concatenated "
+ "operations");
+ throw InvalidOperationEmptyIntersection(msg);
+ }
+ if (extent) {
+ properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ NN_NO_CHECK(extent));
+ }
+
+ std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
+ const double accuracy = getAccuracy(flattenOps);
+ if (accuracy >= 0.0) {
+ accuracies.emplace_back(
+ metadata::PositionalAccuracy::create(toString(accuracy)));
+ }
+
+ auto op = create(properties, flattenOps, accuracies);
+ op->setHasBallparkTransformation(hasBallparkTransformation);
+ op->d->computedName_ = true;
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr ConcatenatedOperation::inverse() const {
+ std::vector<CoordinateOperationNNPtr> inversedOperations;
+ auto l_operations = operations();
+ inversedOperations.reserve(l_operations.size());
+ for (const auto &operation : l_operations) {
+ inversedOperations.emplace_back(operation->inverse());
+ }
+ std::reverse(inversedOperations.begin(), inversedOperations.end());
+
+ auto properties = createPropertiesForInverse(this, false, false);
+ if (d->computedName_) {
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ computeConcatenatedName(inversedOperations));
+ }
+
+ auto op =
+ create(properties, inversedOperations, coordinateOperationAccuracies());
+ op->d->computedName_ = d->computedName_;
+ op->setHasBallparkTransformation(hasBallparkTransformation());
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void ConcatenatedOperation::_exportToWKT(io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (!isWKT2 || !formatter->use2019Keywords()) {
+ throw io::FormattingException(
+ "Transformation can only be exported to WKT2:2019");
+ }
+
+ formatter->startNode(io::WKTConstants::CONCATENATEDOPERATION,
+ !identifiers().empty());
+ formatter->addQuotedString(nameStr());
+
+ if (formatter->use2019Keywords()) {
+ const auto &version = operationVersion();
+ if (version.has_value()) {
+ formatter->startNode(io::WKTConstants::VERSION, false);
+ formatter->addQuotedString(*version);
+ formatter->endNode();
+ }
+ }
+
+ exportSourceCRSAndTargetCRSToWKT(this, formatter);
+
+ const bool canExportOperationId =
+ !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId());
+
+ const bool hasDomains = !domains().empty();
+ if (hasDomains) {
+ formatter->pushDisableUsage();
+ }
+
+ for (const auto &operation : operations()) {
+ formatter->startNode(io::WKTConstants::STEP, false);
+ if (canExportOperationId && !operation->identifiers().empty()) {
+ // fake that top node has no id, so that the operation id is
+ // considered
+ formatter->pushHasId(false);
+ operation->_exportToWKT(formatter);
+ formatter->popHasId();
+ } else {
+ operation->_exportToWKT(formatter);
+ }
+ formatter->endNode();
+ }
+
+ if (hasDomains) {
+ formatter->popDisableUsage();
+ }
+
+ ObjectUsage::baseExportToWKT(formatter);
+ formatter->endNode();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void ConcatenatedOperation::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext("ConcatenatedOperation",
+ !identifiers().empty()));
+
+ writer->AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer->Add("unnamed");
+ } else {
+ writer->Add(l_name);
+ }
+
+ writer->AddObjKey("source_crs");
+ formatter->setAllowIDInImmediateChild();
+ sourceCRS()->_exportToJSON(formatter);
+
+ writer->AddObjKey("target_crs");
+ formatter->setAllowIDInImmediateChild();
+ targetCRS()->_exportToJSON(formatter);
+
+ writer->AddObjKey("steps");
+ {
+ auto parametersContext(writer->MakeArrayContext(false));
+ for (const auto &operation : operations()) {
+ formatter->setAllowIDInImmediateChild();
+ operation->_exportToJSON(formatter);
+ }
+ }
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CoordinateOperationNNPtr ConcatenatedOperation::_shallowClone() const {
+ auto op =
+ ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>(*this);
+ std::vector<CoordinateOperationNNPtr> ops;
+ for (const auto &subOp : d->operations_) {
+ ops.emplace_back(subOp->shallowClone());
+ }
+ op->d->operations_ = ops;
+ op->assignSelf(op);
+ op->setCRSs(this, false);
+ return util::nn_static_pointer_cast<CoordinateOperation>(op);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+void ConcatenatedOperation::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const // throw(FormattingException)
+{
+ for (const auto &operation : operations()) {
+ operation->_exportToPROJString(formatter);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool ConcatenatedOperation::_isEquivalentTo(
+ const util::IComparable *other, util::IComparable::Criterion criterion,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto otherCO = dynamic_cast<const ConcatenatedOperation *>(other);
+ if (otherCO == nullptr ||
+ (criterion == util::IComparable::Criterion::STRICT &&
+ !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) {
+ return false;
+ }
+ const auto &steps = operations();
+ const auto &otherSteps = otherCO->operations();
+ if (steps.size() != otherSteps.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < steps.size(); i++) {
+ if (!steps[i]->_isEquivalentTo(otherSteps[i].get(), criterion,
+ dbContext)) {
+ return false;
+ }
+ }
+ return true;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+std::set<GridDescription> ConcatenatedOperation::gridsNeeded(
+ const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
+ std::set<GridDescription> res;
+ for (const auto &operation : operations()) {
+ const auto l_gridsNeeded = operation->gridsNeeded(
+ databaseContext, considerKnownGridsAsAvailable);
+ for (const auto &gridDesc : l_gridsNeeded) {
+ res.insert(gridDesc);
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/src/iso19111/operation/conversion.cpp b/src/iso19111/operation/conversion.cpp
new file mode 100644
index 00000000..1808dbe7
--- /dev/null
+++ b/src/iso19111/operation/conversion.cpp
@@ -0,0 +1,3955 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/crs.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
+#include "proj/internal/tracing.hpp"
+
+#include "coordinateoperation_internal.hpp"
+#include "esriparammappings.hpp"
+#include "operationmethod_private.hpp"
+#include "oputils.hpp"
+#include "parammappings.hpp"
+#include "vectorofvaluesparams.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+#include "proj_constants.h"
+
+#include "proj_json_streaming_writer.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+//! @cond Doxygen_Suppress
+constexpr double UTM_LATITUDE_OF_NATURAL_ORIGIN = 0.0;
+constexpr double UTM_SCALE_FACTOR = 0.9996;
+constexpr double UTM_FALSE_EASTING = 500000.0;
+constexpr double UTM_NORTH_FALSE_NORTHING = 0.0;
+constexpr double UTM_SOUTH_FALSE_NORTHING = 10000000.0;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct Conversion::Private {};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+Conversion::Conversion(const OperationMethodNNPtr &methodIn,
+ const std::vector<GeneralParameterValueNNPtr> &values)
+ : SingleOperation(methodIn), d(nullptr) {
+ setParameterValues(values);
+}
+
+// ---------------------------------------------------------------------------
+
+Conversion::Conversion(const Conversion &other)
+ : CoordinateOperation(other), SingleOperation(other), d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+Conversion::~Conversion() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ConversionNNPtr Conversion::shallowClone() const {
+ auto conv = Conversion::nn_make_shared<Conversion>(*this);
+ conv->assignSelf(conv);
+ conv->setCRSs(this, false);
+ return conv;
+}
+
+CoordinateOperationNNPtr Conversion::_shallowClone() const {
+ return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ConversionNNPtr
+Conversion::alterParametersLinearUnit(const common::UnitOfMeasure &unit,
+ bool convertToNewUnit) const {
+
+ std::vector<GeneralParameterValueNNPtr> newValues;
+ bool changesDone = false;
+ for (const auto &genOpParamvalue : parameterValues()) {
+ bool updated = false;
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &paramValue = opParamvalue->parameterValue();
+ if (paramValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &measure = paramValue->value();
+ if (measure.unit().type() ==
+ common::UnitOfMeasure::Type::LINEAR) {
+ if (!measure.unit()._isEquivalentTo(
+ unit, util::IComparable::Criterion::EQUIVALENT)) {
+ const double newValue =
+ convertToNewUnit ? measure.convertToUnit(unit)
+ : measure.value();
+ newValues.emplace_back(OperationParameterValue::create(
+ opParamvalue->parameter(),
+ ParameterValue::create(
+ common::Measure(newValue, unit))));
+ updated = true;
+ }
+ }
+ }
+ }
+ if (updated) {
+ changesDone = true;
+ } else {
+ newValues.emplace_back(genOpParamvalue);
+ }
+ }
+ if (changesDone) {
+ auto conv = create(util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, "unknown"),
+ method(), newValues);
+ conv->setCRSs(this, false);
+ return conv;
+ } else {
+ return NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<Conversion>(shared_from_this()));
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a Conversion from a vector of GeneralParameterValue.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @param methodIn the operation method.
+ * @param values the values.
+ * @return a new Conversion.
+ * @throws InvalidOperation
+ */
+ConversionNNPtr Conversion::create(const util::PropertyMap &properties,
+ const OperationMethodNNPtr &methodIn,
+ const std::vector<GeneralParameterValueNNPtr>
+ &values) // throw InvalidOperation
+{
+ if (methodIn->parameters().size() != values.size()) {
+ throw InvalidOperation(
+ "Inconsistent number of parameters and parameter values");
+ }
+ auto conv = Conversion::nn_make_shared<Conversion>(methodIn, values);
+ conv->assignSelf(conv);
+ conv->setProperties(properties);
+ return conv;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a Conversion and its OperationMethod
+ *
+ * @param propertiesConversion See \ref general_properties of the conversion.
+ * At minimum the name should be defined.
+ * @param propertiesOperationMethod See \ref general_properties of the operation
+ * method. At minimum the name should be defined.
+ * @param parameters the operation parameters.
+ * @param values the operation values. Constraint:
+ * values.size() == parameters.size()
+ * @return a new Conversion.
+ * @throws InvalidOperation
+ */
+ConversionNNPtr Conversion::create(
+ const util::PropertyMap &propertiesConversion,
+ const util::PropertyMap &propertiesOperationMethod,
+ const std::vector<OperationParameterNNPtr> &parameters,
+ const std::vector<ParameterValueNNPtr> &values) // throw InvalidOperation
+{
+ OperationMethodNNPtr op(
+ OperationMethod::create(propertiesOperationMethod, parameters));
+
+ if (parameters.size() != values.size()) {
+ throw InvalidOperation(
+ "Inconsistent number of parameters and parameter values");
+ }
+ std::vector<GeneralParameterValueNNPtr> generalParameterValues;
+ generalParameterValues.reserve(values.size());
+ for (size_t i = 0; i < values.size(); i++) {
+ generalParameterValues.push_back(
+ OperationParameterValue::create(parameters[i], values[i]));
+ }
+ return create(propertiesConversion, op, generalParameterValues);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+static util::PropertyMap
+getUTMConversionProperty(const util::PropertyMap &properties, int zone,
+ bool north) {
+ if (!properties.get(common::IdentifiedObject::NAME_KEY)) {
+ std::string conversionName("UTM zone ");
+ conversionName += toString(zone);
+ conversionName += (north ? 'N' : 'S');
+
+ return createMapNameEPSGCode(conversionName,
+ (north ? 16000 : 17000) + zone);
+ } else {
+ return properties;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static ConversionNNPtr
+createConversion(const util::PropertyMap &properties,
+ const MethodMapping *mapping,
+ const std::vector<ParameterValueNNPtr> &values) {
+
+ std::vector<OperationParameterNNPtr> parameters;
+ for (int i = 0; mapping->params[i] != nullptr; i++) {
+ const auto *param = mapping->params[i];
+ auto paramProperties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, param->wkt2_name);
+ if (param->epsg_code != 0) {
+ paramProperties
+ .set(metadata::Identifier::CODESPACE_KEY,
+ metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, param->epsg_code);
+ }
+ auto parameter = OperationParameter::create(paramProperties);
+ parameters.push_back(parameter);
+ }
+
+ auto methodProperties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, mapping->wkt2_name);
+ if (mapping->epsg_code != 0) {
+ methodProperties
+ .set(metadata::Identifier::CODESPACE_KEY,
+ metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, mapping->epsg_code);
+ }
+ return Conversion::create(
+ addDefaultNameIfNeeded(properties, mapping->wkt2_name),
+ methodProperties, parameters, values);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr
+Conversion::create(const util::PropertyMap &properties, int method_epsg_code,
+ const std::vector<ParameterValueNNPtr> &values) {
+ const MethodMapping *mapping = getMapping(method_epsg_code);
+ assert(mapping);
+ return createConversion(properties, mapping, values);
+}
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr
+Conversion::create(const util::PropertyMap &properties,
+ const char *method_wkt2_name,
+ const std::vector<ParameterValueNNPtr> &values) {
+ const MethodMapping *mapping = getMapping(method_wkt2_name);
+ assert(mapping);
+ return createConversion(properties, mapping, values);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a [Universal Transverse Mercator]
+ *(https://proj.org/operations/projections/utm.html) conversion.
+ *
+ * UTM is a family of conversions, of EPSG codes from 16001 to 16060 for the
+ * northern hemisphere, and 17001 to 17060 for the southern hemisphere,
+ * based on the Transverse Mercator projection method.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param zone UTM zone number between 1 and 60.
+ * @param north true for UTM northern hemisphere, false for UTM southern
+ * hemisphere.
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createUTM(const util::PropertyMap &properties,
+ int zone, bool north) {
+ return create(
+ getUTMConversionProperty(properties, zone, north),
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
+ createParams(common::Angle(UTM_LATITUDE_OF_NATURAL_ORIGIN),
+ common::Angle(zone * 6.0 - 183.0),
+ common::Scale(UTM_SCALE_FACTOR),
+ common::Length(UTM_FALSE_EASTING),
+ common::Length(north ? UTM_NORTH_FALSE_NORTHING
+ : UTM_SOUTH_FALSE_NORTHING)));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Transverse Mercator]
+ *(https://proj.org/operations/projections/tmerc.html) projection method.
+ *
+ * This method is defined as [EPSG:9807]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9807)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createTransverseMercator(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Gauss Schreiber Transverse
+ *Mercator]
+ *(https://proj.org/operations/projections/gstmerc.html) projection method.
+ *
+ * This method is also known as Gauss-Laborde Reunion.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGaussSchreiberTransverseMercator(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties,
+ PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Transverse Mercator South
+ *Orientated]
+ *(https://proj.org/operations/projections/tmerc.html) projection method.
+ *
+ * This method is defined as [EPSG:9808]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9808)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createTransverseMercatorSouthOriented(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties,
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Two Point Equidistant]
+ *(https://proj.org/operations/projections/tpeqd.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstPoint Latitude of first point.
+ * @param longitudeFirstPoint Longitude of first point.
+ * @param latitudeSecondPoint Latitude of second point.
+ * @param longitudeSeconPoint Longitude of second point.
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr
+Conversion::createTwoPointEquidistant(const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstPoint,
+ const common::Angle &longitudeFirstPoint,
+ const common::Angle &latitudeSecondPoint,
+ const common::Angle &longitudeSeconPoint,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT,
+ createParams(latitudeFirstPoint, longitudeFirstPoint,
+ latitudeSecondPoint, longitudeSeconPoint,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the Tunisia Mapping Grid projection
+ * method.
+ *
+ * This method is defined as [EPSG:9816]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9816)
+ *
+ * \note There is currently no implementation of the method formulas in PROJ.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createTunisiaMappingGrid(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Albers Conic Equal Area]
+ *(https://proj.org/operations/projections/aea.html) projection method.
+ *
+ * This method is defined as [EPSG:9822]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9822)
+ *
+ * @note the order of arguments is conformant with the corresponding EPSG
+ * mode and different than OGRSpatialReference::setACEA() of GDAL &lt;= 2.3
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFalseOrigin See \ref latitude_false_origin
+ * @param longitudeFalseOrigin See \ref longitude_false_origin
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param eastingFalseOrigin See \ref easting_false_origin
+ * @param northingFalseOrigin See \ref northing_false_origin
+ * @return a new Conversion.
+ */
+ConversionNNPtr
+Conversion::createAlbersEqualArea(const util::PropertyMap &properties,
+ const common::Angle &latitudeFalseOrigin,
+ const common::Angle &longitudeFalseOrigin,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &eastingFalseOrigin,
+ const common::Length &northingFalseOrigin) {
+ return create(properties, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA,
+ createParams(latitudeFalseOrigin, longitudeFalseOrigin,
+ latitudeFirstParallel, latitudeSecondParallel,
+ eastingFalseOrigin, northingFalseOrigin));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Lambert Conic Conformal 1SP]
+ *(https://proj.org/operations/projections/lcc.html) projection method.
+ *
+ * This method is defined as [EPSG:9801]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9801)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertConicConformal_1SP(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP)]
+ *(https://proj.org/operations/projections/lcc.html) projection method.
+ *
+ * This method is defined as [EPSG:9802]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9802)
+ *
+ * @note the order of arguments is conformant with the corresponding EPSG
+ * mode and different than OGRSpatialReference::setLCC() of GDAL &lt;= 2.3
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFalseOrigin See \ref latitude_false_origin
+ * @param longitudeFalseOrigin See \ref longitude_false_origin
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param eastingFalseOrigin See \ref easting_false_origin
+ * @param northingFalseOrigin See \ref northing_false_origin
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertConicConformal_2SP(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFalseOrigin,
+ const common::Angle &longitudeFalseOrigin,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &eastingFalseOrigin,
+ const common::Length &northingFalseOrigin) {
+ return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP,
+ createParams(latitudeFalseOrigin, longitudeFalseOrigin,
+ latitudeFirstParallel, latitudeSecondParallel,
+ eastingFalseOrigin, northingFalseOrigin));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP
+ *Michigan)]
+ *(https://proj.org/operations/projections/lcc.html) projection method.
+ *
+ * This method is defined as [EPSG:1051]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1051)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFalseOrigin See \ref latitude_false_origin
+ * @param longitudeFalseOrigin See \ref longitude_false_origin
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param eastingFalseOrigin See \ref easting_false_origin
+ * @param northingFalseOrigin See \ref northing_false_origin
+ * @param ellipsoidScalingFactor Ellipsoid scaling factor.
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertConicConformal_2SP_Michigan(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFalseOrigin,
+ const common::Angle &longitudeFalseOrigin,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &eastingFalseOrigin,
+ const common::Length &northingFalseOrigin,
+ const common::Scale &ellipsoidScalingFactor) {
+ return create(properties,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN,
+ createParams(latitudeFalseOrigin, longitudeFalseOrigin,
+ latitudeFirstParallel, latitudeSecondParallel,
+ eastingFalseOrigin, northingFalseOrigin,
+ ellipsoidScalingFactor));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP
+ *Belgium)]
+ *(https://proj.org/operations/projections/lcc.html) projection method.
+ *
+ * This method is defined as [EPSG:9803]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9803)
+ *
+ * \warning The formulas used currently in PROJ are, incorrectly, the ones of
+ * the regular LCC_2SP method.
+ *
+ * @note the order of arguments is conformant with the corresponding EPSG
+ * mode and different than OGRSpatialReference::setLCCB() of GDAL &lt;= 2.3
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFalseOrigin See \ref latitude_false_origin
+ * @param longitudeFalseOrigin See \ref longitude_false_origin
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param eastingFalseOrigin See \ref easting_false_origin
+ * @param northingFalseOrigin See \ref northing_false_origin
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertConicConformal_2SP_Belgium(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFalseOrigin,
+ const common::Angle &longitudeFalseOrigin,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &eastingFalseOrigin,
+ const common::Length &northingFalseOrigin) {
+
+ return create(properties,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM,
+ createParams(latitudeFalseOrigin, longitudeFalseOrigin,
+ latitudeFirstParallel, latitudeSecondParallel,
+ eastingFalseOrigin, northingFalseOrigin));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Modified Azimuthal
+ *Equidistant]
+ *(https://proj.org/operations/projections/aeqd.html) projection method.
+ *
+ * This method is defined as [EPSG:9832]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9832)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeNatOrigin See \ref center_latitude
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createAzimuthalEquidistant(
+ const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT,
+ createParams(latitudeNatOrigin, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Guam Projection]
+ *(https://proj.org/operations/projections/aeqd.html) projection method.
+ *
+ * This method is defined as [EPSG:9831]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9831)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ *is
+ * not provided, it is automatically set.
+ * @param latitudeNatOrigin See \ref center_latitude
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGuamProjection(
+ const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_GUAM_PROJECTION,
+ createParams(latitudeNatOrigin, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Bonne]
+ *(https://proj.org/operations/projections/bonne.html) projection method.
+ *
+ * This method is defined as [EPSG:9827]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9827)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeNatOrigin See \ref center_latitude . PROJ calls its the
+ * standard parallel 1.
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createBonne(const util::PropertyMap &properties,
+ const common::Angle &latitudeNatOrigin,
+ const common::Angle &longitudeNatOrigin,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_BONNE,
+ createParams(latitudeNatOrigin, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Lambert Cylindrical Equal Area
+ *(Spherical)]
+ *(https://proj.org/operations/projections/cea.html) projection method.
+ *
+ * This method is defined as [EPSG:9834]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9834)
+ *
+ * \warning The PROJ cea computation code would select the ellipsoidal form if
+ * a non-spherical ellipsoid is used for the base GeographicCRS.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertCylindricalEqualAreaSpherical(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties,
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL,
+ createParams(latitudeFirstParallel, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Lambert Cylindrical Equal Area
+ *(ellipsoidal form)]
+ *(https://proj.org/operations/projections/cea.html) projection method.
+ *
+ * This method is defined as [EPSG:9835]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9835)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertCylindricalEqualArea(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA,
+ createParams(latitudeFirstParallel, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Cassini-Soldner]
+ * (https://proj.org/operations/projections/cass.html) projection method.
+ *
+ * This method is defined as [EPSG:9806]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9806)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createCassiniSoldner(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_CASSINI_SOLDNER,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Equidistant Conic]
+ *(https://proj.org/operations/projections/eqdc.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @note Although not found in EPSG, the order of arguments is conformant with
+ * the "spirit" of EPSG and different than OGRSpatialReference::setEC() of GDAL
+ *&lt;= 2.3 * @param properties See \ref general_properties of the conversion.
+ *If the name
+ * is not provided, it is automatically set.
+ *
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEquidistantConic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC,
+ createParams(centerLat, centerLong, latitudeFirstParallel,
+ latitudeSecondParallel, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Eckert I]
+ * (https://proj.org/operations/projections/eck1.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertI(const util::PropertyMap &properties,
+ const common::Angle &centerLong,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_I,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Eckert II]
+ * (https://proj.org/operations/projections/eck2.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertII(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_II,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Eckert III]
+ * (https://proj.org/operations/projections/eck3.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertIII(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_III,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Eckert IV]
+ * (https://proj.org/operations/projections/eck4.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertIV(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_IV,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Eckert V]
+ * (https://proj.org/operations/projections/eck5.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertV(const util::PropertyMap &properties,
+ const common::Angle &centerLong,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_V,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Eckert VI]
+ * (https://proj.org/operations/projections/eck6.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertVI(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_VI,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Equidistant Cylindrical]
+ *(https://proj.org/operations/projections/eqc.html) projection method.
+ *
+ * This is also known as the Equirectangular method, and in the particular case
+ * where the latitude of first parallel is 0.
+ *
+ * This method is defined as [EPSG:1028]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1028)
+ *
+ * @note This is the equivalent OGRSpatialReference::SetEquirectangular2(
+ * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL &lt;= 2.3,
+ * where the lat_0 / center_latitude parameter is forced to 0.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEquidistantCylindrical(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL,
+ createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Equidistant Cylindrical
+ *(Spherical)]
+ *(https://proj.org/operations/projections/eqc.html) projection method.
+ *
+ * This is also known as the Equirectangular method, and in the particular case
+ * where the latitude of first parallel is 0.
+ *
+ * This method is defined as [EPSG:1029]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1029)
+ *
+ * @note This is the equivalent OGRSpatialReference::SetEquirectangular2(
+ * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL &lt;= 2.3,
+ * where the lat_0 / center_latitude parameter is forced to 0.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEquidistantCylindricalSpherical(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties,
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL,
+ createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Gall (Stereographic)]
+ * (https://proj.org/operations/projections/gall.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGall(const util::PropertyMap &properties,
+ const common::Angle &centerLong,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Goode Homolosine]
+ * (https://proj.org/operations/projections/goode.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGoodeHomolosine(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Interrupted Goode Homolosine]
+ * (https://proj.org/operations/projections/igh.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @note OGRSpatialReference::SetIGH() of GDAL &lt;= 2.3 assumes the 3
+ * projection
+ * parameters to be zero and this is the nominal case.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createInterruptedGoodeHomolosine(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties,
+ PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Geostationary Satellite View]
+ * (https://proj.org/operations/projections/geos.html) projection method,
+ * with the sweep angle axis of the viewing instrument being x
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param height Height of the view point above the Earth.
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGeostationarySatelliteSweepX(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &height, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X,
+ createParams(centerLong, height, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Geostationary Satellite View]
+ * (https://proj.org/operations/projections/geos.html) projection method,
+ * with the sweep angle axis of the viewing instrument being y.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param height Height of the view point above the Earth.
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGeostationarySatelliteSweepY(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &height, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y,
+ createParams(centerLong, height, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Gnomonic]
+ *(https://proj.org/operations/projections/gnom.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGnomonic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, PROJ_WKT2_NAME_METHOD_GNOMONIC,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator
+ *(Variant A)]
+ *(https://proj.org/operations/projections/omerc.html) projection method
+ *
+ * This is the variant with the no_uoff parameter, which corresponds to
+ * GDAL &gt;=2.3 Hotine_Oblique_Mercator projection.
+ * In this variant, the false grid coordinates are
+ * defined at the intersection of the initial line and the aposphere (the
+ * equator on one of the intermediate surfaces inherent in the method), that is
+ * at the natural origin of the coordinate system).
+ *
+ * This method is defined as [EPSG:9812]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9812)
+ *
+ * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid =
+ *90deg,
+ * this maps to the [Swiss Oblique Mercator]
+ *(https://proj.org/operations/projections/somerc.html) formulas.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param longitudeProjectionCentre See \ref longitude_projection_centre
+ * @param azimuthInitialLine See \ref azimuth_initial_line
+ * @param angleFromRectifiedToSkrewGrid See
+ * \ref angle_from_recitfied_to_skrew_grid
+ * @param scale See \ref scale_factor_initial_line
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createHotineObliqueMercatorVariantA(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &longitudeProjectionCentre,
+ const common::Angle &azimuthInitialLine,
+ const common::Angle &angleFromRectifiedToSkrewGrid,
+ const common::Scale &scale, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A,
+ createParams(latitudeProjectionCentre, longitudeProjectionCentre,
+ azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator
+ *(Variant B)]
+ *(https://proj.org/operations/projections/omerc.html) projection method
+ *
+ * This is the variant without the no_uoff parameter, which corresponds to
+ * GDAL &gt;=2.3 Hotine_Oblique_Mercator_Azimuth_Center projection.
+ * In this variant, the false grid coordinates are defined at the projection
+ *centre.
+ *
+ * This method is defined as [EPSG:9815]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9815)
+ *
+ * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid =
+ *90deg,
+ * this maps to the [Swiss Oblique Mercator]
+ *(https://proj.org/operations/projections/somerc.html) formulas.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param longitudeProjectionCentre See \ref longitude_projection_centre
+ * @param azimuthInitialLine See \ref azimuth_initial_line
+ * @param angleFromRectifiedToSkrewGrid See
+ * \ref angle_from_recitfied_to_skrew_grid
+ * @param scale See \ref scale_factor_initial_line
+ * @param eastingProjectionCentre See \ref easting_projection_centre
+ * @param northingProjectionCentre See \ref northing_projection_centre
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createHotineObliqueMercatorVariantB(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &longitudeProjectionCentre,
+ const common::Angle &azimuthInitialLine,
+ const common::Angle &angleFromRectifiedToSkrewGrid,
+ const common::Scale &scale, const common::Length &eastingProjectionCentre,
+ const common::Length &northingProjectionCentre) {
+ return create(
+ properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B,
+ createParams(latitudeProjectionCentre, longitudeProjectionCentre,
+ azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale,
+ eastingProjectionCentre, northingProjectionCentre));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator Two
+ *Point Natural Origin]
+ *(https://proj.org/operations/projections/omerc.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param latitudePoint1 Latitude of point 1.
+ * @param longitudePoint1 Latitude of point 1.
+ * @param latitudePoint2 Latitude of point 2.
+ * @param longitudePoint2 Longitude of point 2.
+ * @param scale See \ref scale_factor_initial_line
+ * @param eastingProjectionCentre See \ref easting_projection_centre
+ * @param northingProjectionCentre See \ref northing_projection_centre
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &latitudePoint1, const common::Angle &longitudePoint1,
+ const common::Angle &latitudePoint2, const common::Angle &longitudePoint2,
+ const common::Scale &scale, const common::Length &eastingProjectionCentre,
+ const common::Length &northingProjectionCentre) {
+ return create(
+ properties,
+ PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN,
+ {
+ ParameterValue::create(latitudeProjectionCentre),
+ ParameterValue::create(latitudePoint1),
+ ParameterValue::create(longitudePoint1),
+ ParameterValue::create(latitudePoint2),
+ ParameterValue::create(longitudePoint2),
+ ParameterValue::create(scale),
+ ParameterValue::create(eastingProjectionCentre),
+ ParameterValue::create(northingProjectionCentre),
+ });
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Laborde Oblique Mercator]
+ *(https://proj.org/operations/projections/labrd.html) projection method.
+ *
+ * This method is defined as [EPSG:9813]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9813)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param longitudeProjectionCentre See \ref longitude_projection_centre
+ * @param azimuthInitialLine See \ref azimuth_initial_line
+ * @param scale See \ref scale_factor_initial_line
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLabordeObliqueMercator(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &longitudeProjectionCentre,
+ const common::Angle &azimuthInitialLine, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR,
+ createParams(latitudeProjectionCentre,
+ longitudeProjectionCentre, azimuthInitialLine,
+ scale, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [International Map of the World
+ *Polyconic]
+ *(https://proj.org/operations/projections/imw_p.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @note the order of arguments is conformant with the corresponding EPSG
+ * mode and different than OGRSpatialReference::SetIWMPolyconic() of GDAL &lt;=
+ *2.3
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createInternationalMapWorldPolyconic(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC,
+ createParams(centerLong, latitudeFirstParallel,
+ latitudeSecondParallel, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Krovak (north oriented)]
+ *(https://proj.org/operations/projections/krovak.html) projection method.
+ *
+ * This method is defined as [EPSG:1041]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1041)
+ *
+ * The coordinates are returned in the "GIS friendly" order: easting, northing.
+ * This method is similar to createKrovak(), except that the later returns
+ * projected values as southing, westing, where
+ * southing(Krovak) = -northing(Krovak_North) and
+ * westing(Krovak) = -easting(Krovak_North).
+ *
+ * @note The current PROJ implementation of Krovak hard-codes
+ * colatitudeConeAxis = 30deg17'17.30311"
+ * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for
+ * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221).
+ * It also hard-codes the parameters of the Bessel ellipsoid typically used for
+ * Krovak.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param longitudeOfOrigin See \ref longitude_of_origin
+ * @param colatitudeConeAxis See \ref colatitude_cone_axis
+ * @param latitudePseudoStandardParallel See \ref
+ *latitude_pseudo_standard_parallel
+ * @param scaleFactorPseudoStandardParallel See \ref
+ *scale_factor_pseudo_standard_parallel
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createKrovakNorthOriented(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &longitudeOfOrigin,
+ const common::Angle &colatitudeConeAxis,
+ const common::Angle &latitudePseudoStandardParallel,
+ const common::Scale &scaleFactorPseudoStandardParallel,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED,
+ createParams(latitudeProjectionCentre, longitudeOfOrigin,
+ colatitudeConeAxis,
+ latitudePseudoStandardParallel,
+ scaleFactorPseudoStandardParallel, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Krovak]
+ *(https://proj.org/operations/projections/krovak.html) projection method.
+ *
+ * This method is defined as [EPSG:9819]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9819)
+ *
+ * The coordinates are returned in the historical order: southing, westing
+ * This method is similar to createKrovakNorthOriented(), except that the later
+ *returns
+ * projected values as easting, northing, where
+ * easting(Krovak_North) = -westing(Krovak) and
+ * northing(Krovak_North) = -southing(Krovak).
+ *
+ * @note The current PROJ implementation of Krovak hard-codes
+ * colatitudeConeAxis = 30deg17'17.30311"
+ * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for
+ * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221).
+ * It also hard-codes the parameters of the Bessel ellipsoid typically used for
+ * Krovak.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param longitudeOfOrigin See \ref longitude_of_origin
+ * @param colatitudeConeAxis See \ref colatitude_cone_axis
+ * @param latitudePseudoStandardParallel See \ref
+ *latitude_pseudo_standard_parallel
+ * @param scaleFactorPseudoStandardParallel See \ref
+ *scale_factor_pseudo_standard_parallel
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr
+Conversion::createKrovak(const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &longitudeOfOrigin,
+ const common::Angle &colatitudeConeAxis,
+ const common::Angle &latitudePseudoStandardParallel,
+ const common::Scale &scaleFactorPseudoStandardParallel,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_KROVAK,
+ createParams(latitudeProjectionCentre, longitudeOfOrigin,
+ colatitudeConeAxis,
+ latitudePseudoStandardParallel,
+ scaleFactorPseudoStandardParallel, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Lambert Azimuthal Equal Area]
+ *(https://proj.org/operations/projections/laea.html) projection method.
+ *
+ * This method is defined as [EPSG:9820]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9820)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeNatOrigin See \ref center_latitude
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertAzimuthalEqualArea(
+ const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA,
+ createParams(latitudeNatOrigin, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Miller Cylindrical]
+ *(https://proj.org/operations/projections/mill.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createMillerCylindrical(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Mercator]
+ *(https://proj.org/operations/projections/merc.html) projection method.
+ *
+ * This is the variant, also known as Mercator (1SP), defined with the scale
+ * factor. Note that latitude of natural origin (centerLat) is a parameter,
+ * but unused in the transformation formulas.
+ *
+ * This method is defined as [EPSG:9804]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9804)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude . Should be 0.
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createMercatorVariantA(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_A,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Mercator]
+ *(https://proj.org/operations/projections/merc.html) projection method.
+ *
+ * This is the variant, also known as Mercator (2SP), defined with the latitude
+ * of the first standard parallel (the second standard parallel is implicitly
+ * the opposite value). The latitude of natural origin is fixed to zero.
+ *
+ * This method is defined as [EPSG:9805]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9805)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createMercatorVariantB(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstParallel, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_B,
+ createParams(latitudeFirstParallel, centerLong, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Popular Visualisation Pseudo
+ *Mercator]
+ *(https://proj.org/operations/projections/webmerc.html) projection method.
+ *
+ * Also known as WebMercator. Mostly/only used for Projected CRS EPSG:3857
+ * (WGS 84 / Pseudo-Mercator)
+ *
+ * This method is defined as [EPSG:1024]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1024)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude . Usually 0
+ * @param centerLong See \ref center_longitude . Usually 0
+ * @param falseEasting See \ref false_easting . Usually 0
+ * @param falseNorthing See \ref false_northing . Usually 0
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createPopularVisualisationPseudoMercator(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Mollweide]
+ * (https://proj.org/operations/projections/moll.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createMollweide(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_MOLLWEIDE,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [New Zealand Map Grid]
+ * (https://proj.org/operations/projections/nzmg.html) projection method.
+ *
+ * This method is defined as [EPSG:9811]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9811)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createNewZealandMappingGrid(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_NZMG,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Oblique Stereographic
+ *(Alternative)]
+ *(https://proj.org/operations/projections/sterea.html) projection method.
+ *
+ * This method is defined as [EPSG:9809]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9809)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createObliqueStereographic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Orthographic]
+ *(https://proj.org/operations/projections/ortho.html) projection method.
+ *
+ * This method is defined as [EPSG:9840]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9840)
+ *
+ * \note Before PROJ 7.2, only the spherical formulation was implemented.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createOrthographic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_ORTHOGRAPHIC,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [American Polyconic]
+ *(https://proj.org/operations/projections/poly.html) projection method.
+ *
+ * This method is defined as [EPSG:9818]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9818)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createAmericanPolyconic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_AMERICAN_POLYCONIC,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Polar Stereographic (Variant
+ *A)]
+ *(https://proj.org/operations/projections/stere.html) projection method.
+ *
+ * This method is defined as [EPSG:9810]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9810)
+ *
+ * This is the variant of polar stereographic defined with a scale factor.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude . Should be 90 deg ou -90 deg.
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createPolarStereographicVariantA(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Polar Stereographic (Variant
+ *B)]
+ *(https://proj.org/operations/projections/stere.html) projection method.
+ *
+ * This method is defined as [EPSG:9829]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9829)
+ *
+ * This is the variant of polar stereographic defined with a latitude of
+ * standard parallel.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeStandardParallel See \ref latitude_std_parallel
+ * @param longitudeOfOrigin See \ref longitude_of_origin
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createPolarStereographicVariantB(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeStandardParallel,
+ const common::Angle &longitudeOfOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B,
+ createParams(latitudeStandardParallel, longitudeOfOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Robinson]
+ * (https://proj.org/operations/projections/robin.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createRobinson(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ROBINSON,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Sinusoidal]
+ * (https://proj.org/operations/projections/sinu.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createSinusoidal(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_SINUSOIDAL,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Stereographic]
+ *(https://proj.org/operations/projections/stere.html) projection method.
+ *
+ * There is no equivalent in EPSG. This method implements the original "Oblique
+ * Stereographic" method described in "Snyder's Map Projections - A Working
+ *manual",
+ * which is different from the "Oblique Stereographic (alternative") method
+ * implemented in createObliqueStereographic().
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createStereographic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Van der Grinten]
+ * (https://proj.org/operations/projections/vandg.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createVanDerGrinten(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Wagner I]
+ * (https://proj.org/operations/projections/wag1.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerI(const util::PropertyMap &properties,
+ const common::Angle &centerLong,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_I,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Wagner II]
+ * (https://proj.org/operations/projections/wag2.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerII(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_II,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Wagner III]
+ * (https://proj.org/operations/projections/wag3.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeTrueScale Latitude of true scale.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerIII(
+ const util::PropertyMap &properties, const common::Angle &latitudeTrueScale,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_III,
+ createParams(latitudeTrueScale, centerLong, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Wagner IV]
+ * (https://proj.org/operations/projections/wag4.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerIV(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_IV,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Wagner V]
+ * (https://proj.org/operations/projections/wag5.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerV(const util::PropertyMap &properties,
+ const common::Angle &centerLong,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_V,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Wagner VI]
+ * (https://proj.org/operations/projections/wag6.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerVI(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VI,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Wagner VII]
+ * (https://proj.org/operations/projections/wag7.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerVII(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VII,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Quadrilateralized Spherical
+ *Cube]
+ *(https://proj.org/operations/projections/qsc.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createQuadrilateralizedSphericalCube(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Spherical Cross-Track Height]
+ *(https://proj.org/operations/projections/sch.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param pegPointLat Peg point latitude.
+ * @param pegPointLong Peg point longitude.
+ * @param pegPointHeading Peg point heading.
+ * @param pegPointHeight Peg point height.
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createSphericalCrossTrackHeight(
+ const util::PropertyMap &properties, const common::Angle &pegPointLat,
+ const common::Angle &pegPointLong, const common::Angle &pegPointHeading,
+ const common::Length &pegPointHeight) {
+ return create(properties,
+ PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT,
+ createParams(pegPointLat, pegPointLong, pegPointHeading,
+ pegPointHeight));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Equal Earth]
+ * (https://proj.org/operations/projections/eqearth.html) projection method.
+ *
+ * This method is defined as [EPSG:1078]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1078)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEqualEarth(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_EQUAL_EARTH,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Vertical Perspective]
+ * (https://proj.org/operations/projections/nsper.html) projection method.
+ *
+ * This method is defined as [EPSG:9838]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9838)
+ *
+ * The PROJ implementation of the EPSG Vertical Perspective has the current
+ * limitations with respect to the method described in EPSG:
+ * <ul>
+ * <li> it is a 2D-only method, ignoring the ellipsoidal height of the point to
+ * project.</li>
+ * <li> it has only a spherical development.</li>
+ * <li> the height of the topocentric origin is ignored, and thus assumed to be
+ * 0.</li>
+ * </ul>
+ *
+ * For completeness, PROJ adds the falseEasting and falseNorthing parameter,
+ * which are not described in EPSG. They should usually be set to 0.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param topoOriginLat Latitude of topocentric origin
+ * @param topoOriginLong Longitude of topocentric origin
+ * @param topoOriginHeight Ellipsoidal height of topocentric origin. Ignored by
+ * PROJ (that is assumed to be 0)
+ * @param viewPointHeight Viewpoint height with respect to the
+ * topocentric/mapping plane. In the case where topoOriginHeight = 0, this is
+ * the height above the ellipsoid surface at topoOriginLat, topoOriginLong.
+ * @param falseEasting See \ref false_easting . (not in EPSG)
+ * @param falseNorthing See \ref false_northing . (not in EPSG)
+ * @return a new Conversion.
+ *
+ * @since 6.3
+ */
+ConversionNNPtr Conversion::createVerticalPerspective(
+ const util::PropertyMap &properties, const common::Angle &topoOriginLat,
+ const common::Angle &topoOriginLong, const common::Length &topoOriginHeight,
+ const common::Length &viewPointHeight, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE,
+ createParams(topoOriginLat, topoOriginLong, topoOriginHeight,
+ viewPointHeight, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the Pole Rotation method, using
+ * the conventions of the GRIB 1 and GRIB 2 data formats.
+ *
+ * Those are mentioned in the Note 2 of
+ * https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_temp3-1.shtml
+ *
+ * Several conventions for the pole rotation method exists.
+ * The parameters provided in this method are remapped to the PROJ ob_tran
+ * operation with:
+ * <pre>
+ * +proj=ob_tran +o_proj=longlat +o_lon_p=-rotationAngle
+ * +o_lat_p=-southPoleLatInUnrotatedCRS
+ * +lon_0=southPoleLongInUnrotatedCRS
+ * </pre>
+ *
+ * Another implementation of that convention is also in the netcdf-java library:
+ * https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedLatLon.java
+ *
+ * The PROJ implementation of this method assumes a spherical ellipsoid.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param southPoleLatInUnrotatedCRS Latitude of the point from the unrotated
+ * CRS, expressed in the unrotated CRS, that will become the south pole of the
+ * rotated CRS.
+ * @param southPoleLongInUnrotatedCRS Longitude of the point from the unrotated
+ * CRS, expressed in the unrotated CRS, that will become the south pole of the
+ * rotated CRS.
+ * @param axisRotation The angle of rotation about the new polar
+ * axis (measured clockwise when looking from the southern to the northern pole)
+ * of the coordinate system, assuming the new axis to have been obtained by
+ * first rotating the sphere through southPoleLongInUnrotatedCRS degrees about
+ * the geographic polar axis and then rotating through
+ * (90 + southPoleLatInUnrotatedCRS) degrees so that the southern pole moved
+ * along the (previously rotated) Greenwich meridian.
+ * @return a new Conversion.
+ *
+ * @since 7.0
+ */
+ConversionNNPtr Conversion::createPoleRotationGRIBConvention(
+ const util::PropertyMap &properties,
+ const common::Angle &southPoleLatInUnrotatedCRS,
+ const common::Angle &southPoleLongInUnrotatedCRS,
+ const common::Angle &axisRotation) {
+ return create(properties,
+ PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION,
+ createParams(southPoleLatInUnrotatedCRS,
+ southPoleLongInUnrotatedCRS, axisRotation));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the Change of Vertical Unit
+ * method.
+ *
+ * This method is defined as [EPSG:1069]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1069)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param factor Conversion factor
+ * @return a new Conversion.
+ */
+ConversionNNPtr
+Conversion::createChangeVerticalUnit(const util::PropertyMap &properties,
+ const common::Scale &factor) {
+ return create(properties, createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR),
+ },
+ VectorOfValues{
+ factor,
+ });
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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 6.3
+ */
+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.
+ *
+ * This method is defined as [EPSG:9843]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9843),
+ * or for 3D as [EPSG:9844]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9844)
+ *
+ * @param is3D Whether this should apply on 3D geographicCRS
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createAxisOrderReversal(bool is3D) {
+ if (is3D) {
+ return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_3D_NAME, 15499),
+ createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D),
+ {}, {});
+ } else {
+ return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_2D_NAME, 15498),
+ createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D),
+ {}, {});
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the Geographic/Geocentric method.
+ *
+ * This method is defined as [EPSG:9602]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9602),
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @return a new Conversion.
+ */
+ConversionNNPtr
+Conversion::createGeographicGeocentric(const util::PropertyMap &properties) {
+ return create(properties, createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC),
+ {}, {});
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+ConversionNNPtr
+Conversion::createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS) {
+ auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildOpName("Conversion", sourceCRS, targetCRS));
+ auto conv = createGeographicGeocentric(properties);
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ return conv;
+}
+
+// ---------------------------------------------------------------------------
+
+InverseConversion::InverseConversion(const ConversionNNPtr &forward)
+ : Conversion(
+ OperationMethod::create(createPropertiesForInverse(forward->method()),
+ forward->method()->parameters()),
+ forward->parameterValues()),
+ InverseCoordinateOperation(forward, true) {
+ setPropertiesFromForward();
+}
+
+// ---------------------------------------------------------------------------
+
+InverseConversion::~InverseConversion() = default;
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr InverseConversion::inverseAsConversion() const {
+ return NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<Conversion>(forwardOperation_));
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr
+InverseConversion::create(const ConversionNNPtr &forward) {
+ auto conv = util::nn_make_shared<InverseConversion>(forward);
+ conv->assignSelf(conv);
+ return conv;
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr InverseConversion::_shallowClone() const {
+ auto op = InverseConversion::nn_make_shared<InverseConversion>(
+ inverseAsConversion()->shallowClone());
+ op->assignSelf(op);
+ op->setCRSs(this, false);
+ return util::nn_static_pointer_cast<CoordinateOperation>(op);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static bool isAxisOrderReversal2D(int methodEPSGCode) {
+ return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D;
+}
+
+static bool isAxisOrderReversal3D(int methodEPSGCode) {
+ return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D;
+}
+
+bool isAxisOrderReversal(int methodEPSGCode) {
+ return isAxisOrderReversal2D(methodEPSGCode) ||
+ isAxisOrderReversal3D(methodEPSGCode);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr Conversion::inverse() const {
+ const int methodEPSGCode = method()->getEPSGCode();
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
+ const double convFactor = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
+ auto conv = createChangeVerticalUnit(
+ createPropertiesForInverse(this, false, false),
+ common::Scale(1.0 / convFactor));
+ conv->setCRSs(this, true);
+ return conv;
+ }
+
+ const bool l_isAxisOrderReversal2D = isAxisOrderReversal2D(methodEPSGCode);
+ const bool l_isAxisOrderReversal3D = isAxisOrderReversal3D(methodEPSGCode);
+ if (l_isAxisOrderReversal2D || l_isAxisOrderReversal3D) {
+ auto conv = createAxisOrderReversal(l_isAxisOrderReversal3D);
+ conv->setCRSs(this, true);
+ return conv;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) {
+
+ auto conv = createGeographicGeocentric(
+ createPropertiesForInverse(this, false, false));
+ conv->setCRSs(this, true);
+ 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())));
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static double msfn(double phi, double e2) {
+ const double sinphi = std::sin(phi);
+ const double cosphi = std::cos(phi);
+ return pj_msfn(sinphi, cosphi, e2);
+}
+
+// ---------------------------------------------------------------------------
+
+static double tsfn(double phi, double ec) {
+ const double sinphi = std::sin(phi);
+ return pj_tsfn(phi, sinphi, ec);
+}
+
+// ---------------------------------------------------------------------------
+
+// Function whose zeroes are the sin of the standard parallels of LCC_2SP
+static double lcc_1sp_to_2sp_f(double sinphi, double K, double ec, double n) {
+ const double x = sinphi;
+ const double ecx = ec * x;
+ return (1 - x * x) / (1 - ecx * ecx) -
+ K * K * std::pow((1.0 - x) / (1.0 + x) *
+ std::pow((1.0 + ecx) / (1.0 - ecx), ec),
+ n);
+}
+
+// ---------------------------------------------------------------------------
+
+// Find the sin of the standard parallels of LCC_2SP
+static double find_zero_lcc_1sp_to_2sp_f(double sinphi0, bool bNorth, double K,
+ double ec) {
+ double a, b;
+ double f_a;
+ if (bNorth) {
+ // Look for zero above phi0
+ a = sinphi0;
+ b = 1.0; // sin(North pole)
+ f_a = 1.0; // some positive value, but we only care about the sign
+ } else {
+ // Look for zero below phi0
+ a = -1.0; // sin(South pole)
+ b = sinphi0;
+ f_a = -1.0; // minus infinity in fact, but we only care about the sign
+ }
+ // We use dichotomy search. lcc_1sp_to_2sp_f() is positive at sinphi_init,
+ // has a zero in ]-1,sinphi0[ and ]sinphi0,1[ ranges
+ for (int N = 0; N < 100; N++) {
+ double c = (a + b) / 2;
+ double f_c = lcc_1sp_to_2sp_f(c, K, ec, sinphi0);
+ if (f_c == 0.0 || (b - a) < 1e-18) {
+ return c;
+ }
+ if ((f_c > 0 && f_a > 0) || (f_c < 0 && f_a < 0)) {
+ a = c;
+ f_a = f_c;
+ } else {
+ b = c;
+ }
+ }
+ return (a + b) / 2;
+}
+
+static inline double DegToRad(double x) { return x / 180.0 * M_PI; }
+static inline double RadToDeg(double x) { return x / M_PI * 180.0; }
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/**
+ * \brief Return an equivalent projection.
+ *
+ * Currently implemented:
+ * <ul>
+ * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to
+ * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)</li>
+ * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to
+ * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)</li>
+ * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to
+ * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP</li>
+ * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to
+ * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP</li>
+ * </ul>
+ *
+ * @param targetEPSGCode EPSG code of the target method.
+ * @return new conversion, or nullptr
+ */
+ConversionPtr Conversion::convertToOtherMethod(int targetEPSGCode) const {
+ const int current_epsg_code = method()->getEPSGCode();
+ if (current_epsg_code == targetEPSGCode) {
+ return util::nn_dynamic_pointer_cast<Conversion>(shared_from_this());
+ }
+
+ auto geogCRS = dynamic_cast<crs::GeodeticCRS *>(sourceCRS().get());
+ if (!geogCRS) {
+ return nullptr;
+ }
+
+ const double e2 = geogCRS->ellipsoid()->squaredEccentricity();
+ if (e2 < 0) {
+ return nullptr;
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_A &&
+ targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
+ parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) {
+ const double k0 = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
+ if (!(k0 > 0 && k0 <= 1.0 + 1e-10))
+ return nullptr;
+ const double dfStdP1Lat =
+ (k0 >= 1.0)
+ ? 0.0
+ : std::acos(std::sqrt((1.0 - e2) / ((1.0 / (k0 * k0)) - e2)));
+ auto latitudeFirstParallel = common::Angle(
+ common::Angle(dfStdP1Lat, common::UnitOfMeasure::RADIAN)
+ .convertToUnit(common::UnitOfMeasure::DEGREE),
+ common::UnitOfMeasure::DEGREE);
+ auto conv = createMercatorVariantB(
+ util::PropertyMap(), latitudeFirstParallel,
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
+ targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
+ const double phi1 = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL);
+ if (!(std::fabs(phi1) < M_PI / 2))
+ return nullptr;
+ const double k0 = msfn(phi1, e2);
+ auto conv = createMercatorVariantA(
+ util::PropertyMap(),
+ common::Angle(0.0, common::UnitOfMeasure::DEGREE),
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Scale(k0, common::UnitOfMeasure::SCALE_UNITY),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
+ targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ // Notations m0, t0, n, m1, t1, F are those of the EPSG guidance
+ // "1.3.1.1 Lambert Conic Conformal (2SP)" and
+ // "1.3.1.2 Lambert Conic Conformal (1SP)" and
+ // or Snyder pages 106-109
+ auto latitudeOfOrigin = common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN));
+ const double phi0 = latitudeOfOrigin.getSIValue();
+ const double k0 = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
+ if (!(std::fabs(phi0) < M_PI / 2))
+ return nullptr;
+ if (!(k0 > 0 && k0 <= 1.0 + 1e-10))
+ return nullptr;
+ const double ec = std::sqrt(e2);
+ const double m0 = msfn(phi0, e2);
+ const double t0 = tsfn(phi0, ec);
+ const double n = sin(phi0);
+ if (std::fabs(n) < 1e-10)
+ return nullptr;
+ if (fabs(k0 - 1.0) <= 1e-10) {
+ auto conv = createLambertConicConformal_2SP(
+ util::PropertyMap(), latitudeOfOrigin,
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ latitudeOfOrigin, latitudeOfOrigin,
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ } else {
+ const double K = k0 * m0 / std::pow(t0, n);
+ const double phi1 =
+ std::asin(find_zero_lcc_1sp_to_2sp_f(n, true, K, ec));
+ const double phi2 =
+ std::asin(find_zero_lcc_1sp_to_2sp_f(n, false, K, ec));
+ double phi1Deg = RadToDeg(phi1);
+ double phi2Deg = RadToDeg(phi2);
+
+ // Try to round to hundreth of degree if very close to it
+ if (std::fabs(phi1Deg * 1000 - std::floor(phi1Deg * 1000 + 0.5)) <
+ 1e-8)
+ phi1Deg = floor(phi1Deg * 1000 + 0.5) / 1000;
+ if (std::fabs(phi2Deg * 1000 - std::floor(phi2Deg * 1000 + 0.5)) <
+ 1e-8)
+ phi2Deg = std::floor(phi2Deg * 1000 + 0.5) / 1000;
+
+ // The following improvement is too turn the LCC1SP equivalent of
+ // EPSG:2154 to the real LCC2SP
+ // If the computed latitude of origin is close to .0 or .5 degrees
+ // then check if rounding it to it will get a false northing
+ // close to an integer
+ const double FN =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
+ const double latitudeOfOriginDeg =
+ latitudeOfOrigin.convertToUnit(common::UnitOfMeasure::DEGREE);
+ if (std::fabs(latitudeOfOriginDeg * 2 -
+ std::floor(latitudeOfOriginDeg * 2 + 0.5)) < 0.2) {
+ const double dfRoundedLatOfOrig =
+ std::floor(latitudeOfOriginDeg * 2 + 0.5) / 2;
+ const double m1 = msfn(phi1, e2);
+ const double t1 = tsfn(phi1, ec);
+ const double F = m1 / (n * std::pow(t1, n));
+ const double a =
+ geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
+ const double tRoundedLatOfOrig =
+ tsfn(DegToRad(dfRoundedLatOfOrig), ec);
+ const double FN_correction =
+ a * F * (std::pow(tRoundedLatOfOrig, n) - std::pow(t0, n));
+ const double FN_corrected = FN - FN_correction;
+ const double FN_corrected_rounded =
+ std::floor(FN_corrected + 0.5);
+ if (std::fabs(FN_corrected - FN_corrected_rounded) < 1e-8) {
+ auto conv = createLambertConicConformal_2SP(
+ util::PropertyMap(),
+ common::Angle(dfRoundedLatOfOrig,
+ common::UnitOfMeasure::DEGREE),
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE),
+ common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE),
+ common::Length(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(FN_corrected_rounded));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ }
+ }
+
+ auto conv = createLambertConicConformal_2SP(
+ util::PropertyMap(), latitudeOfOrigin,
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE),
+ common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(FN));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ }
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
+ targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP) {
+ // Notations m0, t0, m1, t1, m2, t2 n, F are those of the EPSG guidance
+ // "1.3.1.1 Lambert Conic Conformal (2SP)" and
+ // "1.3.1.2 Lambert Conic Conformal (1SP)" and
+ // or Snyder pages 106-109
+ const double phiF =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN)
+ .getSIValue();
+ const double phi1 =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)
+ .getSIValue();
+ const double phi2 =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)
+ .getSIValue();
+ if (!(std::fabs(phiF) < M_PI / 2))
+ return nullptr;
+ if (!(std::fabs(phi1) < M_PI / 2))
+ return nullptr;
+ if (!(std::fabs(phi2) < M_PI / 2))
+ return nullptr;
+ const double ec = std::sqrt(e2);
+ const double m1 = msfn(phi1, e2);
+ const double m2 = msfn(phi2, e2);
+ const double t1 = tsfn(phi1, ec);
+ const double t2 = tsfn(phi2, ec);
+ const double n_denom = std::log(t1) - std::log(t2);
+ const double n = (std::fabs(n_denom) < 1e-10)
+ ? std::sin(phi1)
+ : (std::log(m1) - std::log(m2)) / n_denom;
+ if (std::fabs(n) < 1e-10)
+ return nullptr;
+ const double F = m1 / (n * std::pow(t1, n));
+ const double phi0 = std::asin(n);
+ const double m0 = msfn(phi0, e2);
+ const double t0 = tsfn(phi0, ec);
+ const double F0 = m0 / (n * std::pow(t0, n));
+ const double k0 = F / F0;
+ const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
+ const double tF = tsfn(phiF, ec);
+ const double FN_correction =
+ a * F * (std::pow(tF, n) - std::pow(t0, n));
+
+ double phi0Deg = RadToDeg(phi0);
+ // Try to round to thousandth of degree if very close to it
+ if (std::fabs(phi0Deg * 1000 - std::floor(phi0Deg * 1000 + 0.5)) < 1e-8)
+ phi0Deg = std::floor(phi0Deg * 1000 + 0.5) / 1000;
+
+ auto conv = createLambertConicConformal_1SP(
+ util::PropertyMap(),
+ common::Angle(phi0Deg, common::UnitOfMeasure::DEGREE),
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN)),
+ common::Scale(k0), common::Length(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN)),
+ common::Length(
+ parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN) +
+ (std::fabs(FN_correction) > 1e-8 ? FN_correction : 0)));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ }
+
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static const ESRIMethodMapping *getESRIMapping(const std::string &wkt2_name,
+ int epsg_code) {
+ size_t nEsriMappings = 0;
+ const auto esriMappings = getEsriMappings(nEsriMappings);
+ for (size_t i = 0; i < nEsriMappings; ++i) {
+ const auto &mapping = esriMappings[i];
+ if ((epsg_code != 0 && mapping.epsg_code == epsg_code) ||
+ ci_equal(wkt2_name, mapping.wkt2_name)) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+static void getESRIMethodNameAndParams(const Conversion *conv,
+ const std::string &methodName,
+ int methodEPSGCode,
+ const char *&esriMethodName,
+ const ESRIParamMapping *&esriParams) {
+ esriParams = nullptr;
+ esriMethodName = nullptr;
+ const auto *esriMapping = getESRIMapping(methodName, methodEPSGCode);
+ const auto l_targetCRS = conv->targetCRS();
+ if (esriMapping) {
+ esriParams = esriMapping->params;
+ esriMethodName = esriMapping->esri_name;
+ if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
+ esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) {
+ if (l_targetCRS &&
+ ci_find(l_targetCRS->nameStr(), "Plate Carree") !=
+ std::string::npos &&
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) {
+ esriParams = paramsESRI_Plate_Carree;
+ esriMethodName = "Plate_Carree";
+ } else {
+ esriParams = paramsESRI_Equidistant_Cylindrical;
+ esriMethodName = "Equidistant_Cylindrical";
+ }
+ } else if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
+ if (ci_find(conv->nameStr(), "Gauss Kruger") != std::string::npos ||
+ (l_targetCRS && (ci_find(l_targetCRS->nameStr(), "Gauss") !=
+ std::string::npos ||
+ ci_find(l_targetCRS->nameStr(), "GK_") !=
+ std::string::npos))) {
+ esriParams = paramsESRI_Gauss_Kruger;
+ esriMethodName = "Gauss_Kruger";
+ } else {
+ esriParams = paramsESRI_Transverse_Mercator;
+ esriMethodName = "Transverse_Mercator";
+ }
+ } else if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) {
+ if (std::abs(
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) -
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) <
+ 1e-15) {
+ esriParams =
+ paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin;
+ esriMethodName =
+ "Hotine_Oblique_Mercator_Azimuth_Natural_Origin";
+ } else {
+ esriParams =
+ paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin;
+ esriMethodName = "Rectified_Skew_Orthomorphic_Natural_Origin";
+ }
+ } else if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) {
+ if (std::abs(
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) -
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) <
+ 1e-15) {
+ esriParams = paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center;
+ esriMethodName = "Hotine_Oblique_Mercator_Azimuth_Center";
+ } else {
+ esriParams = paramsESRI_Rectified_Skew_Orthomorphic_Center;
+ esriMethodName = "Rectified_Skew_Orthomorphic_Center";
+ }
+ } else if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
+ if (conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL) > 0) {
+ esriMethodName = "Stereographic_North_Pole";
+ } else {
+ esriMethodName = "Stereographic_South_Pole";
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+const char *Conversion::getESRIMethodName() const {
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const auto methodEPSGCode = l_method->getEPSGCode();
+ const ESRIParamMapping *esriParams = nullptr;
+ const char *esriMethodName = nullptr;
+ getESRIMethodNameAndParams(this, methodName, methodEPSGCode, esriMethodName,
+ esriParams);
+ return esriMethodName;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+const char *Conversion::getWKT1GDALMethodName() const {
+ const auto &l_method = method();
+ const auto methodEPSGCode = l_method->getEPSGCode();
+ if (methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
+ return "Mercator_1SP";
+ }
+ const MethodMapping *mapping = getMapping(l_method.get());
+ return mapping ? mapping->wkt1_name : nullptr;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+void Conversion::_exportToWKT(io::WKTFormatter *formatter) const {
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const auto methodEPSGCode = l_method->getEPSGCode();
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+
+ if (!isWKT2 && formatter->useESRIDialect()) {
+ if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
+ auto eqConv =
+ convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
+ if (eqConv) {
+ eqConv->_exportToWKT(formatter);
+ return;
+ }
+ }
+ }
+
+ if (isWKT2) {
+ formatter->startNode(formatter->useDerivingConversion()
+ ? io::WKTConstants::DERIVINGCONVERSION
+ : io::WKTConstants::CONVERSION,
+ !identifiers().empty());
+ formatter->addQuotedString(nameStr());
+ } else {
+ formatter->enter();
+ formatter->pushOutputUnit(false);
+ formatter->pushOutputId(false);
+ }
+
+#ifdef DEBUG_CONVERSION_ID
+ if (sourceCRS() && targetCRS()) {
+ formatter->startNode("SOURCECRS_ID", false);
+ sourceCRS()->formatID(formatter);
+ formatter->endNode();
+ formatter->startNode("TARGETCRS_ID", false);
+ targetCRS()->formatID(formatter);
+ formatter->endNode();
+ }
+#endif
+
+ bool bAlreadyWritten = false;
+ if (!isWKT2 && formatter->useESRIDialect()) {
+ const ESRIParamMapping *esriParams = nullptr;
+ const char *esriMethodName = nullptr;
+ getESRIMethodNameAndParams(this, methodName, methodEPSGCode,
+ esriMethodName, esriParams);
+ if (esriMethodName && esriParams) {
+ formatter->startNode(io::WKTConstants::PROJECTION, false);
+ formatter->addQuotedString(esriMethodName);
+ formatter->endNode();
+
+ for (int i = 0; esriParams[i].esri_name != nullptr; i++) {
+ const auto &esriParam = esriParams[i];
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString(esriParam.esri_name);
+ if (esriParam.wkt2_name) {
+ const auto &pv = parameterValue(esriParam.wkt2_name,
+ esriParam.epsg_code);
+ if (pv && pv->type() == ParameterValue::Type::MEASURE) {
+ const auto &v = pv->value();
+ // as we don't output the natural unit, output
+ // to the registered linear / angular unit.
+ const auto &unitType = v.unit().type();
+ if (unitType == common::UnitOfMeasure::Type::LINEAR) {
+ formatter->add(v.convertToUnit(
+ *(formatter->axisLinearUnit())));
+ } else if (unitType ==
+ common::UnitOfMeasure::Type::ANGULAR) {
+ const auto &angUnit =
+ *(formatter->axisAngularUnit());
+ double val = v.convertToUnit(angUnit);
+ if (angUnit == common::UnitOfMeasure::DEGREE) {
+ if (val > 180.0) {
+ val -= 360.0;
+ } else if (val < -180.0) {
+ val += 360.0;
+ }
+ }
+ formatter->add(val);
+ } else {
+ formatter->add(v.getSIValue());
+ }
+ } else if (ci_find(esriParam.esri_name, "scale") !=
+ std::string::npos) {
+ formatter->add(1.0);
+ } else {
+ formatter->add(0.0);
+ }
+ } else {
+ formatter->add(esriParam.fixed_value);
+ }
+ formatter->endNode();
+ }
+ bAlreadyWritten = true;
+ }
+ } else if (!isWKT2) {
+ if (methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
+ const double latitudeOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ if (latitudeOrigin != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ }
+
+ bAlreadyWritten = true;
+ formatter->startNode(io::WKTConstants::PROJECTION, false);
+ formatter->addQuotedString("Mercator_1SP");
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("central_meridian");
+ const double centralMeridian = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ formatter->add(centralMeridian);
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("scale_factor");
+ formatter->add(1.0);
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("false_easting");
+ const double falseEasting =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING);
+ formatter->add(falseEasting);
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("false_northing");
+ const double falseNorthing =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
+ formatter->add(falseNorthing);
+ formatter->endNode();
+ } else if (starts_with(methodName, "PROJ ")) {
+ bAlreadyWritten = true;
+ formatter->startNode(io::WKTConstants::PROJECTION, false);
+ formatter->addQuotedString("custom_proj4");
+ formatter->endNode();
+ }
+ }
+
+ if (!bAlreadyWritten) {
+ l_method->_exportToWKT(formatter);
+
+ const MethodMapping *mapping =
+ !isWKT2 ? getMapping(l_method.get()) : nullptr;
+ for (const auto &genOpParamvalue : parameterValues()) {
+
+ // EPSG has normally no Latitude of natural origin for Equidistant
+ // Cylindrical but PROJ can handle it, so output the parameter if
+ // not zero
+ if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) {
+ auto opParamvalue =
+ dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue &&
+ opParamvalue->parameter()->getEPSGCode() ==
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) {
+ const auto &paramValue = opParamvalue->parameterValue();
+ if (paramValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &measure = paramValue->value();
+ if (measure.getSIValue() == 0) {
+ continue;
+ }
+ }
+ }
+ }
+ // Same for false easting / false northing for Vertical Perspective
+ else if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE) {
+ auto opParamvalue =
+ dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto paramEPSGCode =
+ opParamvalue->parameter()->getEPSGCode();
+ if (paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_EASTING ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_NORTHING) {
+ const auto &paramValue = opParamvalue->parameterValue();
+ if (paramValue->type() ==
+ ParameterValue::Type::MEASURE) {
+ const auto &measure = paramValue->value();
+ if (measure.getSIValue() == 0) {
+ continue;
+ }
+ }
+ }
+ }
+ }
+ genOpParamvalue->_exportToWKT(formatter, mapping);
+ }
+ }
+
+ if (isWKT2) {
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+ formatter->endNode();
+ } else {
+ formatter->popOutputUnit();
+ formatter->popOutputId();
+ formatter->leave();
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void Conversion::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("Conversion", !identifiers().empty()));
+
+ writer->AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer->Add("unnamed");
+ } else {
+ writer->Add(l_name);
+ }
+
+ writer->AddObjKey("method");
+ formatter->setOmitTypeInImmediateChild();
+ formatter->setAllowIDInImmediateChild();
+ method()->_exportToJSON(formatter);
+
+ const auto &l_parameterValues = parameterValues();
+ if (!l_parameterValues.empty()) {
+ writer->AddObjKey("parameters");
+ {
+ auto parametersContext(writer->MakeArrayContext(false));
+ for (const auto &genOpParamvalue : l_parameterValues) {
+ formatter->setAllowIDInImmediateChild();
+ formatter->setOmitTypeInImmediateChild();
+ genOpParamvalue->_exportToJSON(formatter);
+ }
+ }
+ }
+
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static bool createPROJ4WebMercator(const Conversion *conv,
+ io::PROJStringFormatter *formatter) {
+ const double centralMeridian = conv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+
+ const double falseEasting =
+ conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING);
+
+ const double falseNorthing =
+ conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
+
+ auto sourceCRS = conv->sourceCRS();
+ auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ if (!geogCRS) {
+ return false;
+ }
+
+ std::string units("m");
+ auto targetCRS = conv->targetCRS();
+ auto targetProjCRS =
+ dynamic_cast<const crs::ProjectedCRS *>(targetCRS.get());
+ if (targetProjCRS) {
+ const auto &axisList = targetProjCRS->coordinateSystem()->axisList();
+ const auto &unit = axisList[0]->unit();
+ if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto projUnit = unit.exportToPROJString();
+ if (!projUnit.empty()) {
+ units = projUnit;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ formatter->addStep("merc");
+ const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
+ formatter->addParam("a", a);
+ formatter->addParam("b", a);
+ formatter->addParam("lat_ts", 0.0);
+ formatter->addParam("lon_0", centralMeridian);
+ formatter->addParam("x_0", falseEasting);
+ formatter->addParam("y_0", falseNorthing);
+ formatter->addParam("k", 1.0);
+ formatter->addParam("units", units);
+ formatter->addParam("nadgrids", "@null");
+ formatter->addParam("wktext");
+ formatter->addParam("no_defs");
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+static bool
+createPROJExtensionFromCustomProj(const Conversion *conv,
+ io::PROJStringFormatter *formatter,
+ bool forExtensionNode) {
+ const auto &methodName = conv->method()->nameStr();
+ assert(starts_with(methodName, "PROJ "));
+ auto tokens = split(methodName, ' ');
+
+ formatter->addStep(tokens[1]);
+
+ if (forExtensionNode) {
+ auto sourceCRS = conv->sourceCRS();
+ auto geogCRS =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ if (!geogCRS) {
+ return false;
+ }
+ geogCRS->addDatumInfoToPROJString(formatter);
+ }
+
+ for (size_t i = 2; i < tokens.size(); i++) {
+ auto kv = split(tokens[i], '=');
+ if (kv.size() == 2) {
+ formatter->addParam(kv[0], kv[1]);
+ } else {
+ formatter->addParam(tokens[i]);
+ }
+ }
+
+ for (const auto &genOpParamvalue : conv->parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &paramValue = opParamvalue->parameterValue();
+ if (paramValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &measure = paramValue->value();
+ const auto unitType = measure.unit().type();
+ if (unitType == common::UnitOfMeasure::Type::LINEAR) {
+ formatter->addParam(paramName, measure.getSIValue());
+ } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
+ formatter->addParam(
+ paramName,
+ measure.convertToUnit(common::UnitOfMeasure::DEGREE));
+ } else {
+ formatter->addParam(paramName, measure.value());
+ }
+ }
+ }
+ }
+
+ if (forExtensionNode) {
+ formatter->addParam("wktext");
+ formatter->addParam("no_defs");
+ }
+ return true;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+bool Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (!isWKT2) {
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ if (l_method->getPrivate()->projMethodOverride_ == "tmerc approx" ||
+ l_method->getPrivate()->projMethodOverride_ == "utm approx") {
+ auto projFormatter = io::PROJStringFormatter::create();
+ projFormatter->setCRSExport(true);
+ projFormatter->setUseApproxTMerc(true);
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ _exportToPROJString(projFormatter.get());
+ projFormatter->addParam("no_defs");
+ formatter->addQuotedString(projFormatter->toString());
+ formatter->endNode();
+ return true;
+ } else if (methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR ||
+ nameStr() == "Popular Visualisation Mercator") {
+
+ auto projFormatter = io::PROJStringFormatter::create();
+ projFormatter->setCRSExport(true);
+ if (createPROJ4WebMercator(this, projFormatter.get())) {
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ formatter->addQuotedString(projFormatter->toString());
+ formatter->endNode();
+ return true;
+ }
+ } else if (starts_with(methodName, "PROJ ")) {
+ auto projFormatter = io::PROJStringFormatter::create();
+ projFormatter->setCRSExport(true);
+ if (createPROJExtensionFromCustomProj(this, projFormatter.get(),
+ true)) {
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ formatter->addQuotedString(projFormatter->toString());
+ formatter->endNode();
+ return true;
+ }
+ } else if (methodName ==
+ PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) {
+ auto projFormatter = io::PROJStringFormatter::create();
+ projFormatter->setCRSExport(true);
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ _exportToPROJString(projFormatter.get());
+ projFormatter->addParam("no_defs");
+ formatter->addQuotedString(projFormatter->toString());
+ formatter->endNode();
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void Conversion::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const // throw(FormattingException)
+{
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ const bool isZUnitConversion =
+ methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT;
+ const bool isAffineParametric =
+ 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 &&
+ !isHeightDepthReversal;
+ bool applyTargetCRSModifiers = applySourceCRSModifiers;
+
+ if (formatter->getCRSExport()) {
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) {
+ throw io::FormattingException("Transformation cannot be exported "
+ "as a PROJ.4 string (but can be part "
+ "of a PROJ pipeline)");
+ }
+ }
+
+ auto l_sourceCRS = sourceCRS();
+ crs::GeographicCRSPtr srcGeogCRS;
+ if (!formatter->getCRSExport() && l_sourceCRS && applySourceCRSModifiers) {
+
+ crs::CRSPtr horiz = l_sourceCRS;
+ const auto compound =
+ dynamic_cast<const crs::CompoundCRS *>(l_sourceCRS.get());
+ if (compound) {
+ const auto &components = compound->componentReferenceSystems();
+ if (!components.empty()) {
+ horiz = components.front().as_nullable();
+ }
+ }
+
+ srcGeogCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(horiz);
+ if (srcGeogCRS) {
+ formatter->setOmitProjLongLatIfPossible(true);
+ formatter->startInversion();
+ srcGeogCRS->_exportToPROJString(formatter);
+ formatter->stopInversion();
+ formatter->setOmitProjLongLatIfPossible(false);
+ }
+
+ auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz.get());
+ if (projCRS) {
+ formatter->startInversion();
+ formatter->pushOmitZUnitConversion();
+ projCRS->addUnitConvertAndAxisSwap(formatter, false);
+ formatter->popOmitZUnitConversion();
+ formatter->stopInversion();
+ }
+ }
+
+ const auto &convName = nameStr();
+ bool bConversionDone = false;
+ bool bEllipsoidParametersDone = false;
+ bool useApprox = false;
+ if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
+ // Check for UTM
+ int zone = 0;
+ bool north = true;
+ useApprox =
+ formatter->getUseApproxTMerc() ||
+ l_method->getPrivate()->projMethodOverride_ == "tmerc approx" ||
+ l_method->getPrivate()->projMethodOverride_ == "utm approx";
+ if (isUTM(zone, north)) {
+ bConversionDone = true;
+ formatter->addStep("utm");
+ if (useApprox) {
+ formatter->addParam("approx");
+ }
+ formatter->addParam("zone", zone);
+ if (!north) {
+ formatter->addParam("south");
+ }
+ }
+ } else if (methodEPSGCode ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) {
+ const double azimuth =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE,
+ common::UnitOfMeasure::DEGREE);
+ const double angleRectifiedToSkewGrid = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
+ common::UnitOfMeasure::DEGREE);
+ // Map to Swiss Oblique Mercator / somerc
+ if (std::fabs(azimuth - 90) < 1e-4 &&
+ std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
+ bConversionDone = true;
+ formatter->addStep("somerc");
+ formatter->addParam(
+ "lat_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "lon_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "k_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE));
+ formatter->addParam("x_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_FALSE_EASTING));
+ formatter->addParam("y_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_FALSE_NORTHING));
+ }
+ } else if (methodEPSGCode ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) {
+ const double azimuth =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE,
+ common::UnitOfMeasure::DEGREE);
+ const double angleRectifiedToSkewGrid = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
+ common::UnitOfMeasure::DEGREE);
+ // Map to Swiss Oblique Mercator / somerc
+ if (std::fabs(azimuth - 90) < 1e-4 &&
+ std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
+ bConversionDone = true;
+ formatter->addStep("somerc");
+ formatter->addParam(
+ "lat_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "lon_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "k_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE));
+ formatter->addParam(
+ "x_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE));
+ formatter->addParam(
+ "y_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE));
+ }
+ } else if (methodEPSGCode == EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED) {
+ double colatitude =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS,
+ common::UnitOfMeasure::DEGREE);
+ double latitudePseudoStandardParallel = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL,
+ common::UnitOfMeasure::DEGREE);
+ // 30deg 17' 17.30311'' = 30.28813975277777776
+ // 30deg 17' 17.303'' = 30.288139722222223 as used in GDAL WKT1
+ if (std::fabs(colatitude - 30.2881397) > 1e-7) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS);
+ }
+ if (std::fabs(latitudePseudoStandardParallel - 78.5) > 1e-8) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL);
+ }
+ } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
+ double latitudeOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ if (latitudeOrigin != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ }
+ } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B) {
+ const auto &scaleFactor = parameterValueMeasure(WKT1_SCALE_FACTOR, 0);
+ if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN &&
+ std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) {
+ throw io::FormattingException(
+ "Unexpected presence of scale factor in Mercator (variant B)");
+ }
+ double latitudeOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ if (latitudeOrigin != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ }
+ } else if (methodEPSGCode ==
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
+ // We map TMSO to tmerc with axis=wsu. This only works if false easting
+ // and northings are zero, which is the case in practice for South
+ // African and Namibian EPSG CRS
+ const auto falseEasting = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_EASTING, common::UnitOfMeasure::METRE);
+ if (falseEasting != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_FALSE_EASTING);
+ }
+ const auto falseNorthing = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, common::UnitOfMeasure::METRE);
+ if (falseNorthing != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_FALSE_NORTHING);
+ }
+ // PROJ.4 specific hack for webmercator
+ } else if (formatter->getCRSExport() &&
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
+ if (!createPROJ4WebMercator(this, formatter)) {
+ throw io::FormattingException(
+ std::string("Cannot export ") +
+ EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR +
+ " as PROJ.4 string outside of a ProjectedCRS context");
+ }
+ bConversionDone = true;
+ bEllipsoidParametersDone = true;
+ applyTargetCRSModifiers = false;
+ } else if (ci_equal(convName, "Popular Visualisation Mercator")) {
+ if (formatter->getCRSExport()) {
+ if (!createPROJ4WebMercator(this, formatter)) {
+ throw io::FormattingException(concat(
+ "Cannot export ", convName,
+ " as PROJ.4 string outside of a ProjectedCRS context"));
+ }
+ applyTargetCRSModifiers = false;
+ } else {
+ formatter->addStep("webmerc");
+ if (l_sourceCRS) {
+ datum::Ellipsoid::WGS84->_exportToPROJString(formatter);
+ }
+ }
+ bConversionDone = true;
+ bEllipsoidParametersDone = true;
+ } else if (starts_with(methodName, "PROJ ")) {
+ bConversionDone = true;
+ createPROJExtensionFromCustomProj(this, formatter, false);
+ } else if (ci_equal(methodName,
+ PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION)) {
+ double southPoleLat = parameterValueNumeric(
+ PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION,
+ common::UnitOfMeasure::DEGREE);
+ double southPoleLon = parameterValueNumeric(
+ PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION,
+ common::UnitOfMeasure::DEGREE);
+ double rotation = parameterValueNumeric(
+ PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION,
+ common::UnitOfMeasure::DEGREE);
+ formatter->addStep("ob_tran");
+ formatter->addParam("o_proj", "longlat");
+ formatter->addParam("o_lon_p", -rotation);
+ formatter->addParam("o_lat_p", -southPoleLat);
+ formatter->addParam("lon_0", southPoleLon);
+ bConversionDone = true;
+ } else if (ci_equal(methodName, "Adams_Square_II")) {
+ // Look for ESRI method and parameter names (to be opposed
+ // to the OGC WKT2 names we use elsewhere, because there's no mapping
+ // of those parameters to OGC WKT2)
+ // We also reject non-default values for a number of parameters,
+ // because they are not implemented on PROJ side. The subset we
+ // support can handle ESRI:54098 WGS_1984_Adams_Square_II, but not
+ // ESRI:54099 WGS_1984_Spilhaus_Ocean_Map_in_Square
+ const double falseEasting = parameterValueNumeric(
+ "False_Easting", common::UnitOfMeasure::METRE);
+ const double falseNorthing = parameterValueNumeric(
+ "False_Northing", common::UnitOfMeasure::METRE);
+ const double scaleFactor =
+ parameterValue("Scale_Factor", 0)
+ ? parameterValueNumeric("Scale_Factor",
+ common::UnitOfMeasure::SCALE_UNITY)
+ : 1.0;
+ const double azimuth =
+ parameterValueNumeric("Azimuth", common::UnitOfMeasure::DEGREE);
+ const double longitudeOfCenter = parameterValueNumeric(
+ "Longitude_Of_Center", common::UnitOfMeasure::DEGREE);
+ const double latitudeOfCenter = parameterValueNumeric(
+ "Latitude_Of_Center", common::UnitOfMeasure::DEGREE);
+ const double XYPlaneRotation = parameterValueNumeric(
+ "XY_Plane_Rotation", common::UnitOfMeasure::DEGREE);
+ if (scaleFactor != 1.0 || azimuth != 0.0 || latitudeOfCenter != 0.0 ||
+ XYPlaneRotation != 0.0) {
+ throw io::FormattingException("Unsupported value for one or "
+ "several parameters of "
+ "Adams_Square_II");
+ }
+ formatter->addStep("adams_ws2");
+ formatter->addParam("lon_0", longitudeOfCenter);
+ formatter->addParam("x_0", falseEasting);
+ formatter->addParam("y_0", falseNorthing);
+ bConversionDone = true;
+ } else if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_5 &&
+ isZUnitConversion) {
+ double convFactor = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
+ auto uom = common::UnitOfMeasure(std::string(), convFactor,
+ common::UnitOfMeasure::Type::LINEAR)
+ .exportToPROJString();
+ auto reverse_uom =
+ convFactor == 0.0
+ ? std::string()
+ : common::UnitOfMeasure(std::string(), 1.0 / convFactor,
+ common::UnitOfMeasure::Type::LINEAR)
+ .exportToPROJString();
+ if (uom == "m") {
+ // do nothing
+ } else if (!uom.empty()) {
+ formatter->addStep("unitconvert");
+ formatter->addParam("z_in", uom);
+ formatter->addParam("z_out", "m");
+ } else if (!reverse_uom.empty()) {
+ formatter->addStep("unitconvert");
+ formatter->addParam("z_in", "m");
+ formatter->addParam("z_out", reverse_uom);
+ } else {
+ formatter->addStep("affine");
+ formatter->addParam("s33", convFactor);
+ }
+ bConversionDone = true;
+ bEllipsoidParametersDone = true;
+ } else if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) {
+ if (!srcGeogCRS) {
+ throw io::FormattingException(
+ "Export of Geographic/Topocentric conversion to a PROJ string "
+ "requires an input geographic CRS");
+ }
+
+ formatter->addStep("cart");
+ srcGeogCRS->ellipsoid()->_exportToPROJString(formatter);
+
+ formatter->addStep("topocentric");
+ const auto latOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ const auto lonOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ const auto heightOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN,
+ common::UnitOfMeasure::METRE);
+ formatter->addParam("lat_0", latOrigin);
+ formatter->addParam("lon_0", lonOrigin);
+ formatter->addParam("h_0", heightOrigin);
+ bConversionDone = true;
+ }
+
+ auto l_targetCRS = targetCRS();
+
+ bool bAxisSpecFound = false;
+ if (!bConversionDone) {
+ const MethodMapping *mapping = getMapping(l_method.get());
+ if (mapping && mapping->proj_name_main) {
+ formatter->addStep(mapping->proj_name_main);
+ if (useApprox) {
+ formatter->addParam("approx");
+ }
+ if (mapping->proj_name_aux) {
+ bool addAux = true;
+ if (internal::starts_with(mapping->proj_name_aux, "axis=")) {
+ if (mapping->epsg_code == EPSG_CODE_METHOD_KROVAK) {
+ auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(
+ l_targetCRS.get());
+ if (projCRS) {
+ const auto &axisList =
+ projCRS->coordinateSystem()->axisList();
+ if (axisList[0]->direction() ==
+ cs::AxisDirection::WEST &&
+ axisList[1]->direction() ==
+ cs::AxisDirection::SOUTH) {
+ formatter->addParam("czech");
+ addAux = false;
+ }
+ }
+ }
+ bAxisSpecFound = true;
+ }
+
+ // No need to add explicit f=0 if the ellipsoid is a sphere
+ if (strcmp(mapping->proj_name_aux, "f=0") == 0) {
+ crs::CRS *horiz = l_sourceCRS.get();
+ const auto compound =
+ dynamic_cast<const crs::CompoundCRS *>(horiz);
+ if (compound) {
+ const auto &components =
+ compound->componentReferenceSystems();
+ if (!components.empty()) {
+ horiz = components.front().get();
+ }
+ }
+
+ auto geogCRS =
+ dynamic_cast<const crs::GeographicCRS *>(horiz);
+ if (geogCRS && geogCRS->ellipsoid()->isSphere()) {
+ addAux = false;
+ }
+ }
+
+ if (addAux) {
+ auto kv = split(mapping->proj_name_aux, '=');
+ if (kv.size() == 2) {
+ formatter->addParam(kv[0], kv[1]);
+ } else {
+ formatter->addParam(mapping->proj_name_aux);
+ }
+ }
+ }
+
+ if (mapping->epsg_code ==
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
+ double latitudeStdParallel = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
+ common::UnitOfMeasure::DEGREE);
+ formatter->addParam("lat_0",
+ (latitudeStdParallel >= 0) ? 90.0 : -90.0);
+ }
+
+ for (int i = 0; mapping->params[i] != nullptr; i++) {
+ const auto *param = mapping->params[i];
+ if (!param->proj_name) {
+ continue;
+ }
+ const auto &value =
+ parameterValueMeasure(param->wkt2_name, param->epsg_code);
+ double valueConverted = 0;
+ if (value == nullMeasure) {
+ // Deal with missing values. In an ideal world, this would
+ // not happen
+ if (param->epsg_code ==
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
+ valueConverted = 1.0;
+ }
+ } else if (param->unit_type ==
+ common::UnitOfMeasure::Type::ANGULAR) {
+ valueConverted =
+ value.convertToUnit(common::UnitOfMeasure::DEGREE);
+ } else {
+ valueConverted = value.getSIValue();
+ }
+
+ if (mapping->epsg_code ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
+ strcmp(param->proj_name, "lat_1") == 0) {
+ formatter->addParam(param->proj_name, valueConverted);
+ formatter->addParam("lat_0", valueConverted);
+ } else {
+ formatter->addParam(param->proj_name, valueConverted);
+ }
+ }
+
+ } else {
+ if (!exportToPROJStringGeneric(formatter)) {
+ throw io::FormattingException(
+ concat("Unsupported conversion method: ", methodName));
+ }
+ }
+ }
+
+ if (l_targetCRS && applyTargetCRSModifiers) {
+ crs::CRS *horiz = l_targetCRS.get();
+ const auto compound = dynamic_cast<const crs::CompoundCRS *>(horiz);
+ if (compound) {
+ const auto &components = compound->componentReferenceSystems();
+ if (!components.empty()) {
+ horiz = components.front().get();
+ }
+ }
+
+ if (!bEllipsoidParametersDone) {
+ auto targetGeodCRS = horiz->extractGeodeticCRS();
+ auto targetGeogCRS =
+ std::dynamic_pointer_cast<crs::GeographicCRS>(targetGeodCRS);
+ if (targetGeogCRS) {
+ if (formatter->getCRSExport()) {
+ targetGeogCRS->addDatumInfoToPROJString(formatter);
+ } else {
+ targetGeogCRS->ellipsoid()->_exportToPROJString(formatter);
+ targetGeogCRS->primeMeridian()->_exportToPROJString(
+ formatter);
+ }
+ } else if (targetGeodCRS) {
+ targetGeodCRS->ellipsoid()->_exportToPROJString(formatter);
+ }
+ }
+
+ auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz);
+ if (projCRS) {
+ formatter->pushOmitZUnitConversion();
+ projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound);
+ formatter->popOmitZUnitConversion();
+ }
+
+ auto derivedGeographicCRS =
+ dynamic_cast<const crs::DerivedGeographicCRS *>(horiz);
+ if (!formatter->getCRSExport() && derivedGeographicCRS) {
+ formatter->setOmitProjLongLatIfPossible(true);
+ derivedGeographicCRS->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->setOmitProjLongLatIfPossible(false);
+ }
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether a conversion is a [Universal Transverse Mercator]
+ * (https://proj.org/operations/projections/utm.html) conversion.
+ *
+ * @param[out] zone UTM zone number between 1 and 60.
+ * @param[out] north true for UTM northern hemisphere, false for UTM southern
+ * hemisphere.
+ * @return true if it is a UTM conversion.
+ */
+bool Conversion::isUTM(int &zone, bool &north) const {
+ zone = 0;
+ north = true;
+
+ if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
+ // Check for UTM
+
+ bool bLatitudeNatOriginUTM = false;
+ bool bScaleFactorUTM = false;
+ bool bFalseEastingUTM = false;
+ bool bFalseNorthingUTM = false;
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto epsg_code = opParamvalue->parameter()->getEPSGCode();
+ const auto &l_parameterValue = opParamvalue->parameterValue();
+ if (l_parameterValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &measure = l_parameterValue->value();
+ if (epsg_code ==
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN &&
+ std::fabs(measure.value() -
+ UTM_LATITUDE_OF_NATURAL_ORIGIN) < 1e-10) {
+ bLatitudeNatOriginUTM = true;
+ } else if (
+ (epsg_code ==
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN ||
+ epsg_code ==
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) &&
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::DEGREE,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ double dfZone = (measure.value() + 183.0) / 6.0;
+ if (dfZone > 0.9 && dfZone < 60.1 &&
+ std::abs(dfZone - std::round(dfZone)) < 1e-10) {
+ zone = static_cast<int>(std::lround(dfZone));
+ }
+ } else if (
+ epsg_code ==
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN &&
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::SCALE_UNITY,
+ util::IComparable::Criterion::EQUIVALENT) &&
+ std::fabs(measure.value() - UTM_SCALE_FACTOR) < 1e-10) {
+ bScaleFactorUTM = true;
+ } else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_EASTING &&
+ measure.value() == UTM_FALSE_EASTING &&
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ bFalseEastingUTM = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_FALSE_NORTHING &&
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ if (std::fabs(measure.value() -
+ UTM_NORTH_FALSE_NORTHING) < 1e-10) {
+ bFalseNorthingUTM = true;
+ north = true;
+ } else if (std::fabs(measure.value() -
+ UTM_SOUTH_FALSE_NORTHING) <
+ 1e-10) {
+ bFalseNorthingUTM = true;
+ north = false;
+ }
+ }
+ }
+ }
+ }
+ if (bLatitudeNatOriginUTM && zone > 0 && bScaleFactorUTM &&
+ bFalseEastingUTM && bFalseNorthingUTM) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a Conversion object where some parameters are better
+ * identified.
+ *
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::identify() const {
+ auto newConversion = Conversion::nn_make_shared<Conversion>(*this);
+ newConversion->assignSelf(newConversion);
+
+ if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
+ // Check for UTM
+ int zone = 0;
+ bool north = true;
+ if (isUTM(zone, north)) {
+ newConversion->setProperties(
+ getUTMConversionProperty(util::PropertyMap(), zone, north));
+ }
+ }
+
+ return newConversion;
+}
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/include/proj/internal/coordinateoperation_internal.hpp b/src/iso19111/operation/coordinateoperation_internal.hpp
index 322d71b7..d890f710 100644
--- a/include/proj/internal/coordinateoperation_internal.hpp
+++ b/src/iso19111/operation/coordinateoperation_internal.hpp
@@ -43,53 +43,6 @@ NS_PROJ_START
namespace operation {
-struct ParamMapping {
- const char *wkt2_name;
- const int epsg_code;
- const char *wkt1_name;
- const common::UnitOfMeasure::Type unit_type;
- const char *proj_name;
-};
-
-struct MethodMapping {
- const char *wkt2_name;
- const int epsg_code;
- const char *wkt1_name;
- const char *proj_name_main;
- const char *proj_name_aux;
- const ParamMapping *const *params;
-};
-
-const MethodMapping *getMapping(int epsg_code) noexcept;
-const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept;
-const MethodMapping *getMapping(const char *wkt2_name) noexcept;
-const MethodMapping *getMapping(const OperationMethod *method) noexcept;
-std::vector<const MethodMapping *>
-getMappingsFromPROJName(const std::string &projName);
-const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping,
- const std::string &wkt1_name);
-bool areEquivalentParameters(const std::string &a, const std::string &b);
-
-// ---------------------------------------------------------------------------
-
-struct ESRIParamMapping {
- const char *esri_name;
- const char *wkt2_name;
- int epsg_code;
- const char *fixed_value;
- bool is_fixed_value;
-};
-
-struct ESRIMethodMapping {
- const char *esri_name;
- const char *wkt2_name;
- int epsg_code;
- const ESRIParamMapping *const params;
-};
-
-std::vector<const ESRIMethodMapping *>
-getMappingsFromESRI(const std::string &esri_name);
-
// ---------------------------------------------------------------------------
bool isAxisOrderReversal(int methodEPSGCode);
@@ -303,6 +256,15 @@ class PROJBasedOperation : public SingleOperation {
bool inverse_ = false;
};
+// ---------------------------------------------------------------------------
+
+class InvalidOperationEmptyIntersection : public InvalidOperation {
+ public:
+ explicit InvalidOperationEmptyIntersection(const std::string &message);
+ InvalidOperationEmptyIntersection(
+ const InvalidOperationEmptyIntersection &other);
+ ~InvalidOperationEmptyIntersection() override;
+};
} // namespace operation
NS_PROJ_END
diff --git a/src/iso19111/operation/coordinateoperation_private.hpp b/src/iso19111/operation/coordinateoperation_private.hpp
new file mode 100644
index 00000000..42bedd4b
--- /dev/null
+++ b/src/iso19111/operation/coordinateoperation_private.hpp
@@ -0,0 +1,88 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef COORDINATEROPERATION_PRIVATE_HPP
+#define COORDINATEROPERATION_PRIVATE_HPP
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/util.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct CoordinateOperation::Private {
+ util::optional<std::string> operationVersion_{};
+ std::vector<metadata::PositionalAccuracyNNPtr>
+ coordinateOperationAccuracies_{};
+ std::weak_ptr<crs::CRS> sourceCRSWeak_{};
+ std::weak_ptr<crs::CRS> targetCRSWeak_{};
+ crs::CRSPtr interpolationCRS_{};
+ util::optional<common::DataEpoch> sourceCoordinateEpoch_{};
+ util::optional<common::DataEpoch> targetCoordinateEpoch_{};
+ bool hasBallparkTransformation_ = false;
+ bool use3DHelmert_ = false;
+
+ // do not set this for a ProjectedCRS.definingConversion
+ struct CRSStrongRef {
+ crs::CRSNNPtr sourceCRS_;
+ crs::CRSNNPtr targetCRS_;
+ CRSStrongRef(const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn)
+ : sourceCRS_(sourceCRSIn), targetCRS_(targetCRSIn) {}
+ };
+ std::unique_ptr<CRSStrongRef> strongRef_{};
+
+ Private() = default;
+ Private(const Private &other)
+ : operationVersion_(other.operationVersion_),
+ coordinateOperationAccuracies_(other.coordinateOperationAccuracies_),
+ sourceCRSWeak_(other.sourceCRSWeak_),
+ targetCRSWeak_(other.targetCRSWeak_),
+ interpolationCRS_(other.interpolationCRS_),
+ sourceCoordinateEpoch_(other.sourceCoordinateEpoch_),
+ targetCoordinateEpoch_(other.targetCoordinateEpoch_),
+ hasBallparkTransformation_(other.hasBallparkTransformation_),
+ strongRef_(other.strongRef_ ? internal::make_unique<CRSStrongRef>(
+ *(other.strongRef_))
+ : nullptr) {}
+
+ Private &operator=(const Private &) = delete;
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // COORDINATEROPERATION_PRIVATE_HPP
diff --git a/src/iso19111/operation/coordinateoperationfactory.cpp b/src/iso19111/operation/coordinateoperationfactory.cpp
new file mode 100644
index 00000000..b8a9bcdf
--- /dev/null
+++ b/src/iso19111/operation/coordinateoperationfactory.cpp
@@ -0,0 +1,5236 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/common.hpp"
+#include "proj/crs.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
+#include "proj/internal/tracing.hpp"
+
+#include "coordinateoperation_internal.hpp"
+#include "coordinateoperation_private.hpp"
+#include "oputils.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+// #define TRACE_CREATE_OPERATIONS
+// #define DEBUG_SORT
+// #define DEBUG_CONCATENATED_OPERATION
+#if defined(DEBUG_SORT) || defined(DEBUG_CONCATENATED_OPERATION)
+#include <iostream>
+
+void dumpWKT(const NS_PROJ::crs::CRS *crs);
+void dumpWKT(const NS_PROJ::crs::CRS *crs) {
+ auto f(NS_PROJ::io::WKTFormatter::create(
+ NS_PROJ::io::WKTFormatter::Convention::WKT2_2019));
+ std::cerr << crs->exportToWKT(f.get()) << std::endl;
+}
+
+void dumpWKT(const NS_PROJ::crs::CRSPtr &crs);
+void dumpWKT(const NS_PROJ::crs::CRSPtr &crs) { dumpWKT(crs.get()); }
+
+void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs);
+void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs) {
+ dumpWKT(crs.as_nullable().get());
+}
+
+void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs);
+void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs) { dumpWKT(crs.get()); }
+
+void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs);
+void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs) {
+ dumpWKT(crs.as_nullable().get());
+}
+
+#endif
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+#ifdef TRACE_CREATE_OPERATIONS
+
+//! @cond Doxygen_Suppress
+
+static std::string objectAsStr(const common::IdentifiedObject *obj) {
+ std::string ret(obj->nameStr());
+ const auto &ids = obj->identifiers();
+ if (!ids.empty()) {
+ ret += " (";
+ ret += (*ids[0]->codeSpace()) + ":" + ids[0]->code();
+ ret += ")";
+ }
+ return ret;
+}
+//! @endcond
+
+#endif
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static double getPseudoArea(const metadata::ExtentPtr &extent) {
+ if (!extent)
+ return 0.0;
+ const auto &geographicElements = extent->geographicElements();
+ if (geographicElements.empty())
+ return 0.0;
+ auto bbox = dynamic_cast<const metadata::GeographicBoundingBox *>(
+ geographicElements[0].get());
+ if (!bbox)
+ return 0;
+ double w = bbox->westBoundLongitude();
+ double s = bbox->southBoundLatitude();
+ double e = bbox->eastBoundLongitude();
+ double n = bbox->northBoundLatitude();
+ if (w > e) {
+ e += 360.0;
+ }
+ // Integrate cos(lat) between south_lat and north_lat
+ return (e - w) * (std::sin(common::Angle(n).getSIValue()) -
+ std::sin(common::Angle(s).getSIValue()));
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct CoordinateOperationContext::Private {
+ io::AuthorityFactoryPtr authorityFactory_{};
+ metadata::ExtentPtr extent_{};
+ double accuracy_ = 0.0;
+ SourceTargetCRSExtentUse sourceAndTargetCRSExtentUse_ =
+ CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST;
+ SpatialCriterion spatialCriterion_ =
+ CoordinateOperationContext::SpatialCriterion::STRICT_CONTAINMENT;
+ bool usePROJNames_ = true;
+ GridAvailabilityUse gridAvailabilityUse_ =
+ GridAvailabilityUse::USE_FOR_SORTING;
+ IntermediateCRSUse allowUseIntermediateCRS_ = CoordinateOperationContext::
+ IntermediateCRSUse::IF_NO_DIRECT_TRANSFORMATION;
+ std::vector<std::pair<std::string, std::string>>
+ intermediateCRSAuthCodes_{};
+ bool discardSuperseded_ = true;
+ bool allowBallpark_ = true;
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CoordinateOperationContext::~CoordinateOperationContext() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationContext::CoordinateOperationContext()
+ : d(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the authority factory, or null */
+const io::AuthorityFactoryPtr &
+CoordinateOperationContext::getAuthorityFactory() const {
+ return d->authorityFactory_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the desired area of interest, or null */
+const metadata::ExtentPtr &
+CoordinateOperationContext::getAreaOfInterest() const {
+ return d->extent_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set the desired area of interest, or null */
+void CoordinateOperationContext::setAreaOfInterest(
+ const metadata::ExtentPtr &extent) {
+ d->extent_ = extent;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the desired accuracy (in metre), or 0 */
+double CoordinateOperationContext::getDesiredAccuracy() const {
+ return d->accuracy_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set the desired accuracy (in metre), or 0 */
+void CoordinateOperationContext::setDesiredAccuracy(double accuracy) {
+ d->accuracy_ = accuracy;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether ballpark transformations are allowed */
+bool CoordinateOperationContext::getAllowBallparkTransformations() const {
+ return d->allowBallpark_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether ballpark transformations are allowed */
+void CoordinateOperationContext::setAllowBallparkTransformations(bool allow) {
+ d->allowBallpark_ = allow;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set how source and target CRS extent should be used
+ * when considering if a transformation can be used (only takes effect if
+ * no area of interest is explicitly defined).
+ *
+ * The default is
+ * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST.
+ */
+void CoordinateOperationContext::setSourceAndTargetCRSExtentUse(
+ SourceTargetCRSExtentUse use) {
+ d->sourceAndTargetCRSExtentUse_ = use;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return how source and target CRS extent should be used
+ * when considering if a transformation can be used (only takes effect if
+ * no area of interest is explicitly defined).
+ *
+ * The default is
+ * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST.
+ */
+CoordinateOperationContext::SourceTargetCRSExtentUse
+CoordinateOperationContext::getSourceAndTargetCRSExtentUse() const {
+ return d->sourceAndTargetCRSExtentUse_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set the spatial criterion to use when comparing the area of
+ * validity
+ * of coordinate operations with the area of interest / area of validity of
+ * source and target CRS.
+ *
+ * The default is STRICT_CONTAINMENT.
+ */
+void CoordinateOperationContext::setSpatialCriterion(
+ SpatialCriterion criterion) {
+ d->spatialCriterion_ = criterion;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the spatial criterion to use when comparing the area of
+ * validity
+ * of coordinate operations with the area of interest / area of validity of
+ * source and target CRS.
+ *
+ * The default is STRICT_CONTAINMENT.
+ */
+CoordinateOperationContext::SpatialCriterion
+CoordinateOperationContext::getSpatialCriterion() const {
+ return d->spatialCriterion_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether PROJ alternative grid names should be substituted to
+ * the official authority names.
+ *
+ * This only has effect is an authority factory with a non-null database context
+ * has been attached to this context.
+ *
+ * If set to false, it is still possible to
+ * obtain later the substitution by using io::PROJStringFormatter::create()
+ * with a non-null database context.
+ *
+ * The default is true.
+ */
+void CoordinateOperationContext::setUsePROJAlternativeGridNames(
+ bool usePROJNames) {
+ d->usePROJNames_ = usePROJNames;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether PROJ alternative grid names should be substituted to
+ * the official authority names.
+ *
+ * The default is true.
+ */
+bool CoordinateOperationContext::getUsePROJAlternativeGridNames() const {
+ return d->usePROJNames_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether transformations that are superseded (but not
+ * deprecated)
+ * should be discarded.
+ *
+ * The default is true.
+ */
+bool CoordinateOperationContext::getDiscardSuperseded() const {
+ return d->discardSuperseded_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether transformations that are superseded (but not deprecated)
+ * should be discarded.
+ *
+ * The default is true.
+ */
+void CoordinateOperationContext::setDiscardSuperseded(bool discard) {
+ d->discardSuperseded_ = discard;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set how grid availability is used.
+ *
+ * The default is USE_FOR_SORTING.
+ */
+void CoordinateOperationContext::setGridAvailabilityUse(
+ GridAvailabilityUse use) {
+ d->gridAvailabilityUse_ = use;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return how grid availability is used.
+ *
+ * The default is USE_FOR_SORTING.
+ */
+CoordinateOperationContext::GridAvailabilityUse
+CoordinateOperationContext::getGridAvailabilityUse() const {
+ return d->gridAvailabilityUse_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether an intermediate pivot CRS can be used for researching
+ * coordinate operations between a source and target CRS.
+ *
+ * Concretely if in the database there is an operation from A to C
+ * (or C to A), and another one from C to B (or B to C), but no direct
+ * operation between A and B, setting this parameter to
+ * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations.
+ *
+ * The current implementation is limited to researching one intermediate
+ * step.
+ *
+ * By default, with the IF_NO_DIRECT_TRANSFORMATION stratgey, all potential
+ * C candidates will be used if there is no direct transformation.
+ */
+void CoordinateOperationContext::setAllowUseIntermediateCRS(
+ IntermediateCRSUse use) {
+ d->allowUseIntermediateCRS_ = use;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether an intermediate pivot CRS can be used for researching
+ * coordinate operations between a source and target CRS.
+ *
+ * Concretely if in the database there is an operation from A to C
+ * (or C to A), and another one from C to B (or B to C), but no direct
+ * operation between A and B, setting this parameter to
+ * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations.
+ *
+ * The default is IF_NO_DIRECT_TRANSFORMATION.
+ */
+CoordinateOperationContext::IntermediateCRSUse
+CoordinateOperationContext::getAllowUseIntermediateCRS() const {
+ return d->allowUseIntermediateCRS_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Restrict the potential pivot CRSs that can be used when trying to
+ * build a coordinate operation between two CRS that have no direct operation.
+ *
+ * @param intermediateCRSAuthCodes a vector of (auth_name, code) that can be
+ * used as potential pivot RS
+ */
+void CoordinateOperationContext::setIntermediateCRS(
+ const std::vector<std::pair<std::string, std::string>>
+ &intermediateCRSAuthCodes) {
+ d->intermediateCRSAuthCodes_ = intermediateCRSAuthCodes;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the potential pivot CRSs that can be used when trying to
+ * build a coordinate operation between two CRS that have no direct operation.
+ *
+ */
+const std::vector<std::pair<std::string, std::string>> &
+CoordinateOperationContext::getIntermediateCRS() const {
+ return d->intermediateCRSAuthCodes_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Creates a context for a coordinate operation.
+ *
+ * If a non null authorityFactory is provided, the resulting context should
+ * not be used simultaneously by more than one thread.
+ *
+ * If authorityFactory->getAuthority() is the empty string, then coordinate
+ * operations from any authority will be searched, with the restrictions set
+ * in the authority_to_authority_preference database table.
+ * If authorityFactory->getAuthority() is set to "any", then coordinate
+ * operations from any authority will be searched
+ * If authorityFactory->getAuthority() is a non-empty string different of "any",
+ * then coordinate operatiosn will be searched only in that authority namespace.
+ *
+ * @param authorityFactory Authority factory, or null if no database lookup
+ * is allowed.
+ * Use io::authorityFactory::create(context, std::string()) to allow all
+ * authorities to be used.
+ * @param extent Area of interest, or null if none is known.
+ * @param accuracy Maximum allowed accuracy in metre, as specified in or
+ * 0 to get best accuracy.
+ * @return a new context.
+ */
+CoordinateOperationContextNNPtr CoordinateOperationContext::create(
+ const io::AuthorityFactoryPtr &authorityFactory,
+ const metadata::ExtentPtr &extent, double accuracy) {
+ auto ctxt = NN_NO_CHECK(
+ CoordinateOperationContext::make_unique<CoordinateOperationContext>());
+ ctxt->d->authorityFactory_ = authorityFactory;
+ ctxt->d->extent_ = extent;
+ ctxt->d->accuracy_ = accuracy;
+ return ctxt;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct CoordinateOperationFactory::Private {
+
+ struct Context {
+ // This is the extent of the source CRS and target CRS of the initial
+ // CoordinateOperationFactory::createOperations() public call, not
+ // necessarily the ones of intermediate
+ // CoordinateOperationFactory::Private::createOperations() calls.
+ // This is used to compare transformations area of use against the
+ // area of use of the source & target CRS.
+ const metadata::ExtentPtr &extent1;
+ const metadata::ExtentPtr &extent2;
+ const CoordinateOperationContextNNPtr &context;
+ bool inCreateOperationsWithDatumPivotAntiRecursion = false;
+ bool inCreateOperationsGeogToVertWithAlternativeGeog = false;
+ bool inCreateOperationsGeogToVertWithIntermediateVert = false;
+ bool skipHorizontalTransformation = false;
+ std::map<std::pair<io::AuthorityFactory::ObjectType, std::string>,
+ std::list<std::pair<std::string, std::string>>>
+ cacheNameToCRS{};
+
+ Context(const metadata::ExtentPtr &extent1In,
+ const metadata::ExtentPtr &extent2In,
+ const CoordinateOperationContextNNPtr &contextIn)
+ : extent1(extent1In), extent2(extent2In), context(contextIn) {}
+ };
+
+ static std::vector<CoordinateOperationNNPtr>
+ createOperations(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS, Context &context);
+
+ private:
+ static constexpr bool disallowEmptyIntersection = true;
+
+ static void
+ buildCRSIds(const crs::CRSNNPtr &crs, Private::Context &context,
+ std::list<std::pair<std::string, std::string>> &ids);
+
+ static std::vector<CoordinateOperationNNPtr> findOpsInRegistryDirect(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, bool &resNonEmptyBeforeFiltering);
+
+ static std::vector<CoordinateOperationNNPtr>
+ findOpsInRegistryDirectTo(const crs::CRSNNPtr &targetCRS,
+ Private::Context &context);
+
+ static std::vector<CoordinateOperationNNPtr>
+ findsOpsInRegistryWithIntermediate(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context,
+ bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates);
+
+ static void createOperationsFromProj4Ext(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static bool createOperationsFromDatabase(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeodeticCRS *geodSrc,
+ const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc,
+ const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static std::vector<CoordinateOperationNNPtr>
+ createOperationsGeogToVertFromGeoid(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS,
+ const crs::VerticalCRS *vertDst,
+ Context &context);
+
+ 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,
+ const crs::GeodeticCRS *geodDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsDerivedTo(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::DerivedCRS *derivedSrc,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsBoundToGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsBoundToVert(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsVertToVert(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsVertToGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::VerticalCRS *vertSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsVertToGeogBallpark(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::VerticalCRS *vertSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsBoundToBound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::BoundCRS *boundDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsCompoundToGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::CompoundCRS *compoundSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsToGeod(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeodeticCRS *geodDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsCompoundToCompound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::CompoundCRS *compoundSrc,
+ const crs::CompoundCRS *compoundDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsBoundToCompound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::CompoundCRS *compoundDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static std::vector<CoordinateOperationNNPtr> createOperationsGeogToGeog(
+ std::vector<CoordinateOperationNNPtr> &res,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeographicCRS *geogSrc,
+ const crs::GeographicCRS *geogDst);
+
+ static void createOperationsWithDatumPivot(
+ std::vector<CoordinateOperationNNPtr> &res,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst,
+ Context &context);
+
+ static bool
+ hasPerfectAccuracyResult(const std::vector<CoordinateOperationNNPtr> &res,
+ const Context &context);
+
+ static void setCRSs(CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS);
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CoordinateOperationFactory::~CoordinateOperationFactory() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationFactory::CoordinateOperationFactory() : d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Find a CoordinateOperation from sourceCRS to targetCRS.
+ *
+ * This is a helper of createOperations(), using a coordinate operation
+ * context
+ * with no authority factory (so no catalog searching is done), no desired
+ * accuracy and no area of interest.
+ * This returns the first operation of the result set of createOperations(),
+ * or null if none found.
+ *
+ * @param sourceCRS source CRS.
+ * @param targetCRS source CRS.
+ * @return a CoordinateOperation or nullptr.
+ */
+CoordinateOperationPtr CoordinateOperationFactory::createOperation(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) const {
+ auto res = createOperations(
+ sourceCRS, targetCRS,
+ CoordinateOperationContext::create(nullptr, nullptr, 0.0));
+ if (!res.empty()) {
+ return res[0];
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+struct PrecomputedOpCharacteristics {
+ double area_{};
+ double accuracy_{};
+ bool isPROJExportable_ = false;
+ bool hasGrids_ = false;
+ bool gridsAvailable_ = false;
+ bool gridsKnown_ = false;
+ size_t stepCount_ = 0;
+ bool isApprox_ = false;
+ bool hasBallparkVertical_ = false;
+ bool isNullTransformation_ = false;
+
+ PrecomputedOpCharacteristics() = default;
+ PrecomputedOpCharacteristics(double area, double accuracy,
+ bool isPROJExportable, bool hasGrids,
+ bool gridsAvailable, bool gridsKnown,
+ size_t stepCount, bool isApprox,
+ bool hasBallparkVertical,
+ bool isNullTransformation)
+ : area_(area), accuracy_(accuracy), isPROJExportable_(isPROJExportable),
+ hasGrids_(hasGrids), gridsAvailable_(gridsAvailable),
+ gridsKnown_(gridsKnown), stepCount_(stepCount), isApprox_(isApprox),
+ hasBallparkVertical_(hasBallparkVertical),
+ isNullTransformation_(isNullTransformation) {}
+};
+
+// ---------------------------------------------------------------------------
+
+// We could have used a lambda instead of this old-school way, but
+// filterAndSort() is already huge.
+struct SortFunction {
+
+ const std::map<CoordinateOperation *, PrecomputedOpCharacteristics> &map;
+
+ explicit SortFunction(const std::map<CoordinateOperation *,
+ PrecomputedOpCharacteristics> &mapIn)
+ : map(mapIn) {}
+
+ // Sorting function
+ // Return true if a < b
+ bool compare(const CoordinateOperationNNPtr &a,
+ const CoordinateOperationNNPtr &b) const {
+ auto iterA = map.find(a.get());
+ assert(iterA != map.end());
+ auto iterB = map.find(b.get());
+ assert(iterB != map.end());
+
+ // CAUTION: the order of the comparisons is extremely important
+ // to get the intended result.
+
+ if (iterA->second.isPROJExportable_ &&
+ !iterB->second.isPROJExportable_) {
+ return true;
+ }
+ if (!iterA->second.isPROJExportable_ &&
+ iterB->second.isPROJExportable_) {
+ return false;
+ }
+
+ if (!iterA->second.isApprox_ && iterB->second.isApprox_) {
+ return true;
+ }
+ if (iterA->second.isApprox_ && !iterB->second.isApprox_) {
+ return false;
+ }
+
+ if (!iterA->second.hasBallparkVertical_ &&
+ iterB->second.hasBallparkVertical_) {
+ return true;
+ }
+ if (iterA->second.hasBallparkVertical_ &&
+ !iterB->second.hasBallparkVertical_) {
+ return false;
+ }
+
+ if (!iterA->second.isNullTransformation_ &&
+ iterB->second.isNullTransformation_) {
+ return true;
+ }
+ if (iterA->second.isNullTransformation_ &&
+ !iterB->second.isNullTransformation_) {
+ return false;
+ }
+
+ // Operations where grids are all available go before other
+ if (iterA->second.gridsAvailable_ && !iterB->second.gridsAvailable_) {
+ return true;
+ }
+ if (iterB->second.gridsAvailable_ && !iterA->second.gridsAvailable_) {
+ return false;
+ }
+
+ // Operations where grids are all known in our DB go before other
+ if (iterA->second.gridsKnown_ && !iterB->second.gridsKnown_) {
+ return true;
+ }
+ if (iterB->second.gridsKnown_ && !iterA->second.gridsKnown_) {
+ return false;
+ }
+
+ // Operations with known accuracy go before those with unknown accuracy
+ const double accuracyA = iterA->second.accuracy_;
+ const double accuracyB = iterB->second.accuracy_;
+ if (accuracyA >= 0 && accuracyB < 0) {
+ return true;
+ }
+ if (accuracyB >= 0 && accuracyA < 0) {
+ return false;
+ }
+
+ if (accuracyA < 0 && accuracyB < 0) {
+ // unknown accuracy ? then prefer operations with grids, which
+ // are likely to have best practical accuracy
+ if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) {
+ return true;
+ }
+ if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) {
+ return false;
+ }
+ }
+
+ // Operations with larger non-zero area of use go before those with
+ // lower one
+ const double areaA = iterA->second.area_;
+ const double areaB = iterB->second.area_;
+ if (areaA > 0) {
+ if (areaA > areaB) {
+ return true;
+ }
+ if (areaA < areaB) {
+ return false;
+ }
+ } else if (areaB > 0) {
+ return false;
+ }
+
+ // Operations with better accuracy go before those with worse one
+ if (accuracyA >= 0 && accuracyA < accuracyB) {
+ return true;
+ }
+ if (accuracyB >= 0 && accuracyB < accuracyA) {
+ return false;
+ }
+
+ if (accuracyA >= 0 && accuracyA == accuracyB) {
+ // same accuracy ? then prefer operations without grids
+ if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) {
+ return true;
+ }
+ if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) {
+ return false;
+ }
+ }
+
+ // The less intermediate steps, the better
+ if (iterA->second.stepCount_ < iterB->second.stepCount_) {
+ return true;
+ }
+ if (iterB->second.stepCount_ < iterA->second.stepCount_) {
+ return false;
+ }
+
+ const auto &a_name = a->nameStr();
+ const auto &b_name = b->nameStr();
+ // The shorter name, the better ?
+ if (a_name.size() < b_name.size()) {
+ return true;
+ }
+ if (b_name.size() < a_name.size()) {
+ return false;
+ }
+
+ // Arbitrary final criterion. We actually return the greater element
+ // first, so that "Amersfoort to WGS 84 (4)" is presented before
+ // "Amersfoort to WGS 84 (3)", which is probably a better guess.
+
+ // Except for French NTF (Paris) to NTF, where the (1) conversion
+ // should be preferred because in the remarks of (2), it is mentioned
+ // OGP prefers value from IGN Paris (code 1467)...
+ if (a_name.find("NTF (Paris) to NTF (1)") != std::string::npos &&
+ b_name.find("NTF (Paris) to NTF (2)") != std::string::npos) {
+ return true;
+ }
+ if (a_name.find("NTF (Paris) to NTF (2)") != std::string::npos &&
+ b_name.find("NTF (Paris) to NTF (1)") != std::string::npos) {
+ return false;
+ }
+ if (a_name.find("NTF (Paris) to RGF93 (1)") != std::string::npos &&
+ b_name.find("NTF (Paris) to RGF93 (2)") != std::string::npos) {
+ return true;
+ }
+ if (a_name.find("NTF (Paris) to RGF93 (2)") != std::string::npos &&
+ b_name.find("NTF (Paris) to RGF93 (1)") != std::string::npos) {
+ return false;
+ }
+
+ return a_name > b_name;
+ }
+
+ bool operator()(const CoordinateOperationNNPtr &a,
+ const CoordinateOperationNNPtr &b) const {
+ const bool ret = compare(a, b);
+#if 0
+ std::cerr << a->nameStr() << " < " << b->nameStr() << " : " << ret << std::endl;
+#endif
+ return ret;
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+static size_t getStepCount(const CoordinateOperationNNPtr &op) {
+ auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ size_t stepCount = 1;
+ if (concat) {
+ stepCount = concat->operations().size();
+ }
+ return stepCount;
+}
+
+// ---------------------------------------------------------------------------
+
+// Return number of steps that are transformations (and not conversions)
+static size_t getTransformationStepCount(const CoordinateOperationNNPtr &op) {
+ auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ size_t stepCount = 1;
+ if (concat) {
+ stepCount = 0;
+ for (const auto &subOp : concat->operations()) {
+ if (dynamic_cast<const Conversion *>(subOp.get()) == nullptr) {
+ stepCount++;
+ }
+ }
+ }
+ return stepCount;
+}
+
+// ---------------------------------------------------------------------------
+
+static bool isNullTransformation(const std::string &name) {
+ if (name.find(" + ") != std::string::npos)
+ return false;
+ return starts_with(name, BALLPARK_GEOCENTRIC_TRANSLATION) ||
+ starts_with(name, BALLPARK_GEOGRAPHIC_OFFSET) ||
+ starts_with(name, NULL_GEOGRAPHIC_OFFSET) ||
+ starts_with(name, NULL_GEOCENTRIC_TRANSLATION);
+}
+
+// ---------------------------------------------------------------------------
+
+struct FilterResults {
+
+ FilterResults(const std::vector<CoordinateOperationNNPtr> &sourceListIn,
+ const CoordinateOperationContextNNPtr &contextIn,
+ const metadata::ExtentPtr &extent1In,
+ const metadata::ExtentPtr &extent2In,
+ bool forceStrictContainmentTest)
+ : sourceList(sourceListIn), context(contextIn), extent1(extent1In),
+ extent2(extent2In), areaOfInterest(context->getAreaOfInterest()),
+ desiredAccuracy(context->getDesiredAccuracy()),
+ sourceAndTargetCRSExtentUse(
+ context->getSourceAndTargetCRSExtentUse()) {
+
+ computeAreaOfInterest();
+ filterOut(forceStrictContainmentTest);
+ }
+
+ FilterResults &andSort() {
+ sort();
+
+ // And now that we have a sorted list, we can remove uninteresting
+ // results
+ // ...
+ removeSyntheticNullTransforms();
+ removeUninterestingOps();
+ removeDuplicateOps();
+ removeSyntheticNullTransforms();
+ return *this;
+ }
+
+ // ----------------------------------------------------------------------
+
+ // cppcheck-suppress functionStatic
+ const std::vector<CoordinateOperationNNPtr> &getRes() { return res; }
+
+ // ----------------------------------------------------------------------
+ private:
+ const std::vector<CoordinateOperationNNPtr> &sourceList;
+ const CoordinateOperationContextNNPtr &context;
+ const metadata::ExtentPtr &extent1;
+ const metadata::ExtentPtr &extent2;
+ metadata::ExtentPtr areaOfInterest;
+ const double desiredAccuracy = context->getDesiredAccuracy();
+ const CoordinateOperationContext::SourceTargetCRSExtentUse
+ sourceAndTargetCRSExtentUse;
+
+ bool hasOpThatContainsAreaOfInterestAndNoGrid = false;
+ std::vector<CoordinateOperationNNPtr> res{};
+
+ // ----------------------------------------------------------------------
+ void computeAreaOfInterest() {
+
+ // Compute an area of interest from the CRS extent if the user did
+ // not specify one
+ if (!areaOfInterest) {
+ if (sourceAndTargetCRSExtentUse ==
+ CoordinateOperationContext::SourceTargetCRSExtentUse::
+ INTERSECTION) {
+ if (extent1 && extent2) {
+ areaOfInterest =
+ extent1->intersection(NN_NO_CHECK(extent2));
+ }
+ } else if (sourceAndTargetCRSExtentUse ==
+ CoordinateOperationContext::SourceTargetCRSExtentUse::
+ SMALLEST) {
+ if (extent1 && extent2) {
+ if (getPseudoArea(extent1) < getPseudoArea(extent2)) {
+ areaOfInterest = extent1;
+ } else {
+ areaOfInterest = extent2;
+ }
+ } else if (extent1) {
+ areaOfInterest = extent1;
+ } else {
+ areaOfInterest = extent2;
+ }
+ }
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+
+ void filterOut(bool forceStrictContainmentTest) {
+
+ // Filter out operations that do not match the expected accuracy
+ // and area of use.
+ const auto spatialCriterion =
+ forceStrictContainmentTest
+ ? CoordinateOperationContext::SpatialCriterion::
+ STRICT_CONTAINMENT
+ : context->getSpatialCriterion();
+ bool hasOnlyBallpark = true;
+ bool hasNonBallparkWithoutExtent = false;
+ bool hasNonBallparkOpWithExtent = false;
+ const bool allowBallpark = context->getAllowBallparkTransformations();
+ for (const auto &op : sourceList) {
+ if (desiredAccuracy != 0) {
+ const double accuracy = getAccuracy(op);
+ if (accuracy < 0 || accuracy > desiredAccuracy) {
+ continue;
+ }
+ }
+ if (!allowBallpark && op->hasBallparkTransformation()) {
+ continue;
+ }
+ if (areaOfInterest) {
+ bool emptyIntersection = false;
+ auto extent = getExtent(op, true, emptyIntersection);
+ if (!extent) {
+ if (!op->hasBallparkTransformation()) {
+ hasNonBallparkWithoutExtent = true;
+ }
+ continue;
+ }
+ if (!op->hasBallparkTransformation()) {
+ hasNonBallparkOpWithExtent = true;
+ }
+ bool extentContains =
+ extent->contains(NN_NO_CHECK(areaOfInterest));
+ if (!hasOpThatContainsAreaOfInterestAndNoGrid &&
+ extentContains) {
+ if (!op->hasBallparkTransformation() &&
+ op->gridsNeeded(nullptr, true).empty()) {
+ hasOpThatContainsAreaOfInterestAndNoGrid = true;
+ }
+ }
+ if (spatialCriterion ==
+ CoordinateOperationContext::SpatialCriterion::
+ STRICT_CONTAINMENT &&
+ !extentContains) {
+ continue;
+ }
+ if (spatialCriterion ==
+ CoordinateOperationContext::SpatialCriterion::
+ PARTIAL_INTERSECTION &&
+ !extent->intersects(NN_NO_CHECK(areaOfInterest))) {
+ continue;
+ }
+ } else if (sourceAndTargetCRSExtentUse ==
+ CoordinateOperationContext::SourceTargetCRSExtentUse::
+ BOTH) {
+ bool emptyIntersection = false;
+ auto extent = getExtent(op, true, emptyIntersection);
+ if (!extent) {
+ if (!op->hasBallparkTransformation()) {
+ hasNonBallparkWithoutExtent = true;
+ }
+ continue;
+ }
+ if (!op->hasBallparkTransformation()) {
+ hasNonBallparkOpWithExtent = true;
+ }
+ bool extentContainsExtent1 =
+ !extent1 || extent->contains(NN_NO_CHECK(extent1));
+ bool extentContainsExtent2 =
+ !extent2 || extent->contains(NN_NO_CHECK(extent2));
+ if (!hasOpThatContainsAreaOfInterestAndNoGrid &&
+ extentContainsExtent1 && extentContainsExtent2) {
+ if (!op->hasBallparkTransformation() &&
+ op->gridsNeeded(nullptr, true).empty()) {
+ hasOpThatContainsAreaOfInterestAndNoGrid = true;
+ }
+ }
+ if (spatialCriterion ==
+ CoordinateOperationContext::SpatialCriterion::
+ STRICT_CONTAINMENT) {
+ if (!extentContainsExtent1 || !extentContainsExtent2) {
+ continue;
+ }
+ } else if (spatialCriterion ==
+ CoordinateOperationContext::SpatialCriterion::
+ PARTIAL_INTERSECTION) {
+ bool extentIntersectsExtent1 =
+ !extent1 || extent->intersects(NN_NO_CHECK(extent1));
+ bool extentIntersectsExtent2 =
+ extent2 && extent->intersects(NN_NO_CHECK(extent2));
+ if (!extentIntersectsExtent1 || !extentIntersectsExtent2) {
+ continue;
+ }
+ }
+ }
+ if (!op->hasBallparkTransformation()) {
+ hasOnlyBallpark = false;
+ }
+ res.emplace_back(op);
+ }
+
+ // In case no operation has an extent and no result is found,
+ // retain all initial operations that match accuracy criterion.
+ if ((res.empty() && !hasNonBallparkOpWithExtent) ||
+ (hasOnlyBallpark && hasNonBallparkWithoutExtent)) {
+ for (const auto &op : sourceList) {
+ if (desiredAccuracy != 0) {
+ const double accuracy = getAccuracy(op);
+ if (accuracy < 0 || accuracy > desiredAccuracy) {
+ continue;
+ }
+ }
+ if (!allowBallpark && op->hasBallparkTransformation()) {
+ continue;
+ }
+ res.emplace_back(op);
+ }
+ }
+ }
+
+ // ----------------------------------------------------------------------
+
+ void sort() {
+
+ // Precompute a number of parameters for each operation that will be
+ // useful for the sorting.
+ std::map<CoordinateOperation *, PrecomputedOpCharacteristics> map;
+ const auto gridAvailabilityUse = context->getGridAvailabilityUse();
+ for (const auto &op : res) {
+ bool dummy = false;
+ auto extentOp = getExtent(op, true, dummy);
+ double area = 0.0;
+ if (extentOp) {
+ if (areaOfInterest) {
+ area = getPseudoArea(
+ extentOp->intersection(NN_NO_CHECK(areaOfInterest)));
+ } else if (extent1 && extent2) {
+ auto x = extentOp->intersection(NN_NO_CHECK(extent1));
+ auto y = extentOp->intersection(NN_NO_CHECK(extent2));
+ area = getPseudoArea(x) + getPseudoArea(y) -
+ ((x && y)
+ ? getPseudoArea(x->intersection(NN_NO_CHECK(y)))
+ : 0.0);
+ } else if (extent1) {
+ area = getPseudoArea(
+ extentOp->intersection(NN_NO_CHECK(extent1)));
+ } else if (extent2) {
+ area = getPseudoArea(
+ extentOp->intersection(NN_NO_CHECK(extent2)));
+ } else {
+ area = getPseudoArea(extentOp);
+ }
+ }
+
+ bool hasGrids = false;
+ bool gridsAvailable = true;
+ bool gridsKnown = true;
+ if (context->getAuthorityFactory()) {
+ const auto gridsNeeded = op->gridsNeeded(
+ context->getAuthorityFactory()->databaseContext(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE);
+ for (const auto &gridDesc : gridsNeeded) {
+ hasGrids = true;
+ if (gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ USE_FOR_SORTING &&
+ !gridDesc.available) {
+ gridsAvailable = false;
+ }
+ if (gridDesc.packageName.empty() &&
+ !(!gridDesc.url.empty() && gridDesc.openLicense) &&
+ !gridDesc.available) {
+ gridsKnown = false;
+ }
+ }
+ }
+
+ const auto stepCount = getStepCount(op);
+
+ bool isPROJExportable = false;
+ auto formatter = io::PROJStringFormatter::create();
+ try {
+ op->exportToPROJString(formatter.get());
+ // Grids might be missing, but at least this is something
+ // PROJ could potentially process
+ isPROJExportable = true;
+ } catch (const std::exception &) {
+ }
+
+#if 0
+ std::cerr << op->nameStr() << " ";
+ std::cerr << area << " ";
+ std::cerr << getAccuracy(op) << " ";
+ std::cerr << isPROJExportable << " ";
+ std::cerr << hasGrids << " ";
+ std::cerr << gridsAvailable << " ";
+ std::cerr << gridsKnown << " ";
+ std::cerr << stepCount << " ";
+ std::cerr << op->hasBallparkTransformation() << " ";
+ std::cerr << isNullTransformation(op->nameStr()) << " ";
+ std::cerr << std::endl;
+#endif
+ map[op.get()] = PrecomputedOpCharacteristics(
+ area, getAccuracy(op), isPROJExportable, hasGrids,
+ gridsAvailable, gridsKnown, stepCount,
+ op->hasBallparkTransformation(),
+ op->nameStr().find("ballpark vertical transformation") !=
+ std::string::npos,
+ isNullTransformation(op->nameStr()));
+ }
+
+ // Sort !
+ SortFunction sortFunc(map);
+ std::sort(res.begin(), res.end(), sortFunc);
+
+// Debug code to check consistency of the sort function
+#ifdef DEBUG_SORT
+ constexpr bool debugSort = true;
+#elif !defined(NDEBUG)
+ const bool debugSort = getenv("PROJ_DEBUG_SORT_FUNCT") != nullptr;
+#endif
+#if defined(DEBUG_SORT) || !defined(NDEBUG)
+ if (debugSort) {
+ const bool assertIfIssue =
+ !(getenv("PROJ_DEBUG_SORT_FUNCT_ASSERT") != nullptr);
+ for (size_t i = 0; i < res.size(); ++i) {
+ for (size_t j = i + 1; j < res.size(); ++j) {
+ if (sortFunc(res[j], res[i])) {
+#ifdef DEBUG_SORT
+ std::cerr << "Sorting issue with entry " << i << "("
+ << res[i]->nameStr() << ") and " << j << "("
+ << res[j]->nameStr() << ")" << std::endl;
+#endif
+ if (assertIfIssue) {
+ assert(false);
+ }
+ }
+ }
+ }
+ }
+#endif
+ }
+
+ // ----------------------------------------------------------------------
+
+ void removeSyntheticNullTransforms() {
+
+ // If we have more than one result, and than the last result is the
+ // default "Ballpark geographic offset" or "Ballpark geocentric
+ // translation" operations we have synthetized, and that at least one
+ // operation has the desired area of interest and does not require the
+ // use of grids, remove it as all previous results are necessarily
+ // better
+ if (hasOpThatContainsAreaOfInterestAndNoGrid && res.size() > 1) {
+ const auto &opLast = res.back();
+ if (opLast->hasBallparkTransformation() ||
+ isNullTransformation(opLast->nameStr())) {
+ std::vector<CoordinateOperationNNPtr> resTemp;
+ for (size_t i = 0; i < res.size() - 1; i++) {
+ resTemp.emplace_back(res[i]);
+ }
+ res = std::move(resTemp);
+ }
+ }
+ }
+
+ // ----------------------------------------------------------------------
+
+ void removeUninterestingOps() {
+
+ // Eliminate operations that bring nothing, ie for a given area of use,
+ // do not keep operations that have similar or worse accuracy, but
+ // involve more (non conversion) steps
+ std::vector<CoordinateOperationNNPtr> resTemp;
+ metadata::ExtentPtr lastExtent;
+ double lastAccuracy = -1;
+ size_t lastStepCount = 0;
+ CoordinateOperationPtr lastOp;
+
+ bool first = true;
+ for (const auto &op : res) {
+ const auto curAccuracy = getAccuracy(op);
+ bool dummy = false;
+ const auto curExtent = getExtent(op, true, dummy);
+ const auto curStepCount = getTransformationStepCount(op);
+
+ if (first) {
+ resTemp.emplace_back(op);
+ first = false;
+ } else {
+ if (lastOp->_isEquivalentTo(op.get())) {
+ continue;
+ }
+ const bool sameExtent =
+ ((!curExtent && !lastExtent) ||
+ (curExtent && lastExtent &&
+ curExtent->contains(NN_NO_CHECK(lastExtent)) &&
+ lastExtent->contains(NN_NO_CHECK(curExtent))));
+ if (((curAccuracy >= lastAccuracy && lastAccuracy >= 0) ||
+ (curAccuracy < 0 && lastAccuracy >= 0)) &&
+ sameExtent && curStepCount > lastStepCount) {
+ continue;
+ }
+
+ resTemp.emplace_back(op);
+ }
+
+ lastOp = op.as_nullable();
+ lastStepCount = curStepCount;
+ lastExtent = curExtent;
+ lastAccuracy = curAccuracy;
+ }
+ res = std::move(resTemp);
+ }
+
+ // ----------------------------------------------------------------------
+
+ // cppcheck-suppress functionStatic
+ void removeDuplicateOps() {
+
+ if (res.size() <= 1) {
+ return;
+ }
+
+ // When going from EPSG:4807 (NTF Paris) to EPSG:4171 (RGC93), we get
+ // EPSG:7811, NTF (Paris) to RGF93 (2), 1 m
+ // and unknown id, NTF (Paris) to NTF (1) + Inverse of RGF93 to NTF (2),
+ // 1 m
+ // both have same PROJ string and extent
+ // Do not keep the later (that has more steps) as it adds no value.
+
+ std::set<std::string> setPROJPlusExtent;
+ std::vector<CoordinateOperationNNPtr> resTemp;
+ for (const auto &op : res) {
+ auto formatter = io::PROJStringFormatter::create();
+ try {
+ std::string key(op->exportToPROJString(formatter.get()));
+ bool dummy = false;
+ auto extentOp = getExtent(op, true, dummy);
+ if (extentOp) {
+ const auto &geogElts = extentOp->geographicElements();
+ if (geogElts.size() == 1) {
+ auto bbox = dynamic_cast<
+ const metadata::GeographicBoundingBox *>(
+ geogElts[0].get());
+ if (bbox) {
+ double w = bbox->westBoundLongitude();
+ double s = bbox->southBoundLatitude();
+ double e = bbox->eastBoundLongitude();
+ double n = bbox->northBoundLatitude();
+ key += "-";
+ key += toString(w);
+ key += "-";
+ key += toString(s);
+ key += "-";
+ key += toString(e);
+ key += "-";
+ key += toString(n);
+ }
+ }
+ }
+
+ if (setPROJPlusExtent.find(key) == setPROJPlusExtent.end()) {
+ resTemp.emplace_back(op);
+ setPROJPlusExtent.insert(key);
+ }
+ } catch (const std::exception &) {
+ resTemp.emplace_back(op);
+ }
+ }
+ res = std::move(resTemp);
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+/** \brief Filter operations and sort them given context.
+ *
+ * If a desired accuracy is specified, only keep operations whose accuracy
+ * is at least the desired one.
+ * If an area of interest is specified, only keep operations whose area of
+ * use include the area of interest.
+ * Then sort remaining operations by descending area of use, and increasing
+ * accuracy.
+ */
+static std::vector<CoordinateOperationNNPtr>
+filterAndSort(const std::vector<CoordinateOperationNNPtr> &sourceList,
+ const CoordinateOperationContextNNPtr &context,
+ const metadata::ExtentPtr &extent1,
+ const metadata::ExtentPtr &extent2) {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_FUNCTION();
+ logTrace("number of results before filter and sort: " +
+ toString(static_cast<int>(sourceList.size())));
+#endif
+ auto resFiltered =
+ FilterResults(sourceList, context, extent1, extent2, false)
+ .andSort()
+ .getRes();
+#ifdef TRACE_CREATE_OPERATIONS
+ logTrace("number of results after filter and sort: " +
+ toString(static_cast<int>(resFiltered.size())));
+#endif
+ return resFiltered;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+// Apply the inverse() method on all elements of the input list
+static std::vector<CoordinateOperationNNPtr>
+applyInverse(const std::vector<CoordinateOperationNNPtr> &list) {
+ auto res = list;
+ for (auto &op : res) {
+#ifdef DEBUG
+ auto opNew = op->inverse();
+ assert(opNew->targetCRS()->isEquivalentTo(op->sourceCRS().get()));
+ assert(opNew->sourceCRS()->isEquivalentTo(op->targetCRS().get()));
+ op = opNew;
+#else
+ op = op->inverse();
+#endif
+ }
+ return res;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+void CoordinateOperationFactory::Private::buildCRSIds(
+ const crs::CRSNNPtr &crs, Private::Context &context,
+ std::list<std::pair<std::string, std::string>> &ids) {
+ const auto &authFactory = context.context->getAuthorityFactory();
+ assert(authFactory);
+ for (const auto &id : crs->identifiers()) {
+ const auto &authName = *(id->codeSpace());
+ const auto &code = id->code();
+ if (!authName.empty()) {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(), authName);
+ try {
+ // Consistency check for the ID attached to the object.
+ // See https://github.com/OSGeo/PROJ/issues/1982 where EPSG:4656
+ // is attached to a GeographicCRS whereas it is a ProjectedCRS
+ if (tmpAuthFactory->createCoordinateReferenceSystem(code)
+ ->_isEquivalentTo(
+ crs.get(),
+ util::IComparable::Criterion::
+ EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)) {
+ ids.emplace_back(authName, code);
+ } else {
+ // TODO? log this inconsistency
+ }
+ } catch (const std::exception &) {
+ // TODO? log this inconsistency
+ }
+ }
+ }
+ if (ids.empty()) {
+ std::vector<io::AuthorityFactory::ObjectType> allowedObjects;
+ auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(crs.get());
+ if (geogCRS) {
+ allowedObjects.push_back(
+ geogCRS->coordinateSystem()->axisList().size() == 2
+ ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS
+ : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS);
+ } else if (dynamic_cast<crs::ProjectedCRS *>(crs.get())) {
+ allowedObjects.push_back(
+ io::AuthorityFactory::ObjectType::PROJECTED_CRS);
+ } else if (dynamic_cast<crs::VerticalCRS *>(crs.get())) {
+ allowedObjects.push_back(
+ io::AuthorityFactory::ObjectType::VERTICAL_CRS);
+ }
+ if (!allowedObjects.empty()) {
+
+ const std::pair<io::AuthorityFactory::ObjectType, std::string> key(
+ allowedObjects[0], crs->nameStr());
+ auto iter = context.cacheNameToCRS.find(key);
+ if (iter != context.cacheNameToCRS.end()) {
+ ids = iter->second;
+ return;
+ }
+
+ const auto &authFactoryName = authFactory->getAuthority();
+ try {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(),
+ (authFactoryName.empty() || authFactoryName == "any")
+ ? std::string()
+ : authFactoryName);
+
+ auto matches = tmpAuthFactory->createObjectsFromName(
+ crs->nameStr(), allowedObjects, false, 2);
+ if (matches.size() == 1 &&
+ crs->_isEquivalentTo(
+ matches.front().get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ !matches.front()->identifiers().empty()) {
+ const auto &tmpIds = matches.front()->identifiers();
+ ids.emplace_back(*(tmpIds[0]->codeSpace()),
+ tmpIds[0]->code());
+ }
+ } catch (const std::exception &) {
+ }
+ context.cacheNameToCRS[key] = ids;
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static std::vector<std::string>
+getCandidateAuthorities(const io::AuthorityFactoryPtr &authFactory,
+ const std::string &srcAuthName,
+ const std::string &targetAuthName) {
+ const auto &authFactoryName = authFactory->getAuthority();
+ std::vector<std::string> authorities;
+ if (authFactoryName == "any") {
+ authorities.emplace_back();
+ }
+ if (authFactoryName.empty()) {
+ authorities = authFactory->databaseContext()->getAllowedAuthorities(
+ srcAuthName, targetAuthName);
+ if (authorities.empty()) {
+ authorities.emplace_back();
+ }
+ } else {
+ authorities.emplace_back(authFactoryName);
+ }
+ return authorities;
+}
+
+// ---------------------------------------------------------------------------
+
+// Look in the authority registry for operations from sourceCRS to targetCRS
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::findOpsInRegistryDirect(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, bool &resNonEmptyBeforeFiltering) {
+ const auto &authFactory = context.context->getAuthorityFactory();
+ assert(authFactory);
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("findOpsInRegistryDirect(" + objectAsStr(sourceCRS.get()) +
+ " --> " + objectAsStr(targetCRS.get()) + ")");
+#endif
+
+ resNonEmptyBeforeFiltering = false;
+ std::list<std::pair<std::string, std::string>> sourceIds;
+ std::list<std::pair<std::string, std::string>> targetIds;
+ buildCRSIds(sourceCRS, context, sourceIds);
+ buildCRSIds(targetCRS, context, targetIds);
+
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
+ for (const auto &idSrc : sourceIds) {
+ const auto &srcAuthName = idSrc.first;
+ const auto &srcCode = idSrc.second;
+ for (const auto &idTarget : targetIds) {
+ const auto &targetAuthName = idTarget.first;
+ const auto &targetCode = idTarget.second;
+
+ const auto authorities(getCandidateAuthorities(
+ authFactory, srcAuthName, targetAuthName));
+ std::vector<CoordinateOperationNNPtr> res;
+ for (const auto &authority : authorities) {
+ const auto authName =
+ authority == "any" ? std::string() : authority;
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(), authName);
+ auto resTmp =
+ tmpAuthFactory->createFromCoordinateReferenceSystemCodes(
+ srcAuthName, srcCode, targetAuthName, targetCode,
+ context.context->getUsePROJAlternativeGridNames(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ context.context->getDiscardSuperseded(), true, false,
+ context.extent1, context.extent2);
+ res.insert(res.end(), resTmp.begin(), resTmp.end());
+ if (authName == "PROJ") {
+ continue;
+ }
+ if (!res.empty()) {
+ resNonEmptyBeforeFiltering = true;
+ auto resFiltered =
+ FilterResults(res, context.context, context.extent1,
+ context.extent2, false)
+ .getRes();
+#ifdef TRACE_CREATE_OPERATIONS
+ logTrace("filtering reduced from " +
+ toString(static_cast<int>(res.size())) + " to " +
+ toString(static_cast<int>(resFiltered.size())));
+#endif
+ return resFiltered;
+ }
+ }
+ }
+ }
+ return std::vector<CoordinateOperationNNPtr>();
+}
+
+// ---------------------------------------------------------------------------
+
+// Look in the authority registry for operations to targetCRS
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::findOpsInRegistryDirectTo(
+ const crs::CRSNNPtr &targetCRS, Private::Context &context) {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("findOpsInRegistryDirectTo({any} -->" +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ assert(authFactory);
+
+ std::list<std::pair<std::string, std::string>> ids;
+ buildCRSIds(targetCRS, context, ids);
+
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
+ for (const auto &id : ids) {
+ const auto &targetAuthName = id.first;
+ const auto &targetCode = id.second;
+
+ const auto authorities(getCandidateAuthorities(
+ authFactory, targetAuthName, targetAuthName));
+ for (const auto &authority : authorities) {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(),
+ authority == "any" ? std::string() : authority);
+ auto res = tmpAuthFactory->createFromCoordinateReferenceSystemCodes(
+ std::string(), std::string(), targetAuthName, targetCode,
+ context.context->getUsePROJAlternativeGridNames(),
+
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ gridAvailabilityUse == CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ context.context->getDiscardSuperseded(), true, true,
+ context.extent1, context.extent2);
+ if (!res.empty()) {
+ auto resFiltered =
+ FilterResults(res, context.context, context.extent1,
+ context.extent2, false)
+ .getRes();
+#ifdef TRACE_CREATE_OPERATIONS
+ logTrace("filtering reduced from " +
+ toString(static_cast<int>(res.size())) + " to " +
+ toString(static_cast<int>(resFiltered.size())));
+#endif
+ return resFiltered;
+ }
+ }
+ }
+ return std::vector<CoordinateOperationNNPtr>();
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// Look in the authority registry for operations from sourceCRS to targetCRS
+// using an intermediate pivot
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context,
+ bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) {
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("findsOpsInRegistryWithIntermediate(" +
+ objectAsStr(sourceCRS.get()) + " --> " +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ assert(authFactory);
+
+ std::list<std::pair<std::string, std::string>> sourceIds;
+ std::list<std::pair<std::string, std::string>> targetIds;
+ buildCRSIds(sourceCRS, context, sourceIds);
+ buildCRSIds(targetCRS, context, targetIds);
+
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
+ for (const auto &idSrc : sourceIds) {
+ const auto &srcAuthName = idSrc.first;
+ const auto &srcCode = idSrc.second;
+ for (const auto &idTarget : targetIds) {
+ const auto &targetAuthName = idTarget.first;
+ const auto &targetCode = idTarget.second;
+
+ const auto authorities(getCandidateAuthorities(
+ authFactory, srcAuthName, targetAuthName));
+ assert(!authorities.empty());
+
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(),
+ (authFactory->getAuthority() == "any" || authorities.size() > 1)
+ ? std::string()
+ : authorities.front());
+
+ std::vector<CoordinateOperationNNPtr> res;
+ if (useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) {
+ res =
+ tmpAuthFactory
+ ->createBetweenGeodeticCRSWithDatumBasedIntermediates(
+ sourceCRS, srcAuthName, srcCode, targetCRS,
+ targetAuthName, targetCode,
+ context.context->getUsePROJAlternativeGridNames(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ context.context->getDiscardSuperseded(),
+ authFactory->getAuthority() != "any" &&
+ authorities.size() > 1
+ ? authorities
+ : std::vector<std::string>(),
+ context.extent1, context.extent2);
+ } else {
+ io::AuthorityFactory::ObjectType intermediateObjectType =
+ io::AuthorityFactory::ObjectType::CRS;
+
+ // If doing GeogCRS --> GeogCRS, only use GeogCRS as
+ // intermediate CRS
+ // Avoid weird behavior when doing NAD83 -> NAD83(2011)
+ // that would go through NAVD88 otherwise.
+ if (context.context->getIntermediateCRS().empty() &&
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()) &&
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS.get())) {
+ intermediateObjectType =
+ io::AuthorityFactory::ObjectType::GEOGRAPHIC_CRS;
+ }
+ res = tmpAuthFactory->createFromCRSCodesWithIntermediates(
+ srcAuthName, srcCode, targetAuthName, targetCode,
+ context.context->getUsePROJAlternativeGridNames(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ context.context->getDiscardSuperseded(),
+ context.context->getIntermediateCRS(),
+ intermediateObjectType,
+ authFactory->getAuthority() != "any" &&
+ authorities.size() > 1
+ ? authorities
+ : std::vector<std::string>(),
+ context.extent1, context.extent2);
+ }
+ if (!res.empty()) {
+
+ auto resFiltered =
+ FilterResults(res, context.context, context.extent1,
+ context.extent2, false)
+ .getRes();
+#ifdef TRACE_CREATE_OPERATIONS
+ logTrace("filtering reduced from " +
+ toString(static_cast<int>(res.size())) + " to " +
+ toString(static_cast<int>(resFiltered.size())));
+#endif
+ return resFiltered;
+ }
+ }
+ }
+ return std::vector<CoordinateOperationNNPtr>();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static TransformationNNPtr
+createBallparkGeographicOffset(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS,
+ const io::DatabaseContextPtr &dbContext) {
+
+ const crs::GeographicCRS *geogSrc =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ const crs::GeographicCRS *geogDst =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
+ const bool isSameDatum = geogSrc && geogDst &&
+ geogSrc->datumNonNull(dbContext)->_isEquivalentTo(
+ geogDst->datumNonNull(dbContext).get(),
+ util::IComparable::Criterion::EQUIVALENT);
+
+ auto name = buildOpName(isSameDatum ? NULL_GEOGRAPHIC_OFFSET
+ : BALLPARK_GEOGRAPHIC_OFFSET,
+ sourceCRS, targetCRS);
+
+ const auto &sourceCRSExtent = getExtent(sourceCRS);
+ const auto &targetCRSExtent = getExtent(targetCRS);
+ const bool sameExtent =
+ sourceCRSExtent && targetCRSExtent &&
+ sourceCRSExtent->_isEquivalentTo(
+ targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT);
+
+ util::PropertyMap map;
+ map.set(common::IdentifiedObject::NAME_KEY, name)
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ sameExtent ? NN_NO_CHECK(sourceCRSExtent)
+ : metadata::Extent::WORLD);
+ const common::Angle angle0(0);
+
+ std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
+ if (isSameDatum) {
+ accuracies.emplace_back(metadata::PositionalAccuracy::create("0"));
+ }
+
+ if (dynamic_cast<const crs::SingleCRS *>(sourceCRS.get())
+ ->coordinateSystem()
+ ->axisList()
+ .size() == 3 ||
+ dynamic_cast<const crs::SingleCRS *>(targetCRS.get())
+ ->coordinateSystem()
+ ->axisList()
+ .size() == 3) {
+ return Transformation::createGeographic3DOffsets(
+ map, sourceCRS, targetCRS, angle0, angle0, common::Length(0),
+ accuracies);
+ } else {
+ return Transformation::createGeographic2DOffsets(
+ map, sourceCRS, targetCRS, angle0, angle0, accuracies);
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+struct MyPROJStringExportableGeodToGeod final
+ : public io::IPROJStringExportable {
+ crs::GeodeticCRSPtr geodSrc{};
+ crs::GeodeticCRSPtr geodDst{};
+
+ MyPROJStringExportableGeodToGeod(const crs::GeodeticCRSPtr &geodSrcIn,
+ const crs::GeodeticCRSPtr &geodDstIn)
+ : geodSrc(geodSrcIn), geodDst(geodDstIn) {}
+
+ ~MyPROJStringExportableGeodToGeod() override;
+
+ void
+ // cppcheck-suppress functionStatic
+ _exportToPROJString(io::PROJStringFormatter *formatter) const override {
+
+ formatter->startInversion();
+ geodSrc->_exportToPROJString(formatter);
+ formatter->stopInversion();
+ geodDst->_exportToPROJString(formatter);
+ }
+};
+
+MyPROJStringExportableGeodToGeod::~MyPROJStringExportableGeodToGeod() = default;
+
+// ---------------------------------------------------------------------------
+
+struct MyPROJStringExportableHorizVertical final
+ : public io::IPROJStringExportable {
+ CoordinateOperationPtr horizTransform{};
+ CoordinateOperationPtr verticalTransform{};
+ crs::GeographicCRSPtr geogDst{};
+
+ MyPROJStringExportableHorizVertical(
+ const CoordinateOperationPtr &horizTransformIn,
+ const CoordinateOperationPtr &verticalTransformIn,
+ const crs::GeographicCRSPtr &geogDstIn)
+ : horizTransform(horizTransformIn),
+ verticalTransform(verticalTransformIn), geogDst(geogDstIn) {}
+
+ ~MyPROJStringExportableHorizVertical() override;
+
+ void
+ // cppcheck-suppress functionStatic
+ _exportToPROJString(io::PROJStringFormatter *formatter) const override {
+
+ formatter->pushOmitZUnitConversion();
+
+ horizTransform->_exportToPROJString(formatter);
+
+ formatter->startInversion();
+ geogDst->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ formatter->popOmitZUnitConversion();
+
+ formatter->pushOmitHorizontalConversionInVertTransformation();
+ verticalTransform->_exportToPROJString(formatter);
+ formatter->popOmitHorizontalConversionInVertTransformation();
+
+ formatter->pushOmitZUnitConversion();
+ geogDst->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->popOmitZUnitConversion();
+ }
+};
+
+MyPROJStringExportableHorizVertical::~MyPROJStringExportableHorizVertical() =
+ default;
+
+// ---------------------------------------------------------------------------
+
+struct MyPROJStringExportableHorizVerticalHorizPROJBased final
+ : public io::IPROJStringExportable {
+ CoordinateOperationPtr opSrcCRSToGeogCRS{};
+ CoordinateOperationPtr verticalTransform{};
+ CoordinateOperationPtr opGeogCRStoDstCRS{};
+ crs::GeographicCRSPtr interpolationGeogCRS{};
+
+ MyPROJStringExportableHorizVerticalHorizPROJBased(
+ const CoordinateOperationPtr &opSrcCRSToGeogCRSIn,
+ const CoordinateOperationPtr &verticalTransformIn,
+ const CoordinateOperationPtr &opGeogCRStoDstCRSIn,
+ const crs::GeographicCRSPtr &interpolationGeogCRSIn)
+ : opSrcCRSToGeogCRS(opSrcCRSToGeogCRSIn),
+ verticalTransform(verticalTransformIn),
+ opGeogCRStoDstCRS(opGeogCRStoDstCRSIn),
+ interpolationGeogCRS(interpolationGeogCRSIn) {}
+
+ ~MyPROJStringExportableHorizVerticalHorizPROJBased() override;
+
+ void
+ // cppcheck-suppress functionStatic
+ _exportToPROJString(io::PROJStringFormatter *formatter) const override {
+
+ formatter->pushOmitZUnitConversion();
+
+ opSrcCRSToGeogCRS->_exportToPROJString(formatter);
+
+ formatter->startInversion();
+ interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ formatter->popOmitZUnitConversion();
+
+ formatter->pushOmitHorizontalConversionInVertTransformation();
+ verticalTransform->_exportToPROJString(formatter);
+ formatter->popOmitHorizontalConversionInVertTransformation();
+
+ formatter->pushOmitZUnitConversion();
+
+ interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter);
+
+ opGeogCRStoDstCRS->_exportToPROJString(formatter);
+
+ formatter->popOmitZUnitConversion();
+ }
+};
+
+MyPROJStringExportableHorizVerticalHorizPROJBased::
+ ~MyPROJStringExportableHorizVerticalHorizPROJBased() = default;
+
+//! @endcond
+
+} // namespace operation
+NS_PROJ_END
+
+#if 0
+namespace dropbox{ namespace oxygen {
+template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableGeodToGeod>>::~nn() = default;
+template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVertical>>::~nn() = default;
+template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVerticalHorizPROJBased>>::~nn() = default;
+}}
+#endif
+
+NS_PROJ_START
+namespace operation {
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+static std::string buildTransfName(const std::string &srcName,
+ const std::string &targetName) {
+ std::string name("Transformation from ");
+ name += srcName;
+ name += " to ";
+ name += targetName;
+ return name;
+}
+
+// ---------------------------------------------------------------------------
+
+static SingleOperationNNPtr createPROJBased(
+ const util::PropertyMap &properties,
+ const io::IPROJStringExportableNNPtr &projExportable,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::CRSPtr &interpolationCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies,
+ bool hasBallparkTransformation) {
+ return util::nn_static_pointer_cast<SingleOperation>(
+ PROJBasedOperation::create(properties, projExportable, false, sourceCRS,
+ targetCRS, interpolationCRS, accuracies,
+ hasBallparkTransformation));
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationNNPtr
+createGeodToGeodPROJBased(const crs::CRSNNPtr &geodSrc,
+ const crs::CRSNNPtr &geodDst) {
+
+ auto exportable = util::nn_make_shared<MyPROJStringExportableGeodToGeod>(
+ util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodSrc),
+ util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodDst));
+
+ auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildTransfName(geodSrc->nameStr(), geodDst->nameStr()));
+ return createPROJBased(properties, exportable, geodSrc, geodDst, nullptr,
+ {}, false);
+}
+
+// ---------------------------------------------------------------------------
+
+static std::string
+getRemarks(const std::vector<operation::CoordinateOperationNNPtr> &ops) {
+ std::string remarks;
+ for (const auto &op : ops) {
+ const auto &opRemarks = op->remarks();
+ if (!opRemarks.empty()) {
+ if (!remarks.empty()) {
+ remarks += '\n';
+ }
+
+ std::string opName(op->nameStr());
+ if (starts_with(opName, INVERSE_OF)) {
+ opName = opName.substr(INVERSE_OF.size());
+ }
+
+ remarks += "For ";
+ remarks += opName;
+
+ const auto &ids = op->identifiers();
+ if (!ids.empty()) {
+ std::string authority(*ids.front()->codeSpace());
+ if (starts_with(authority, "INVERSE(") &&
+ authority.back() == ')') {
+ authority = authority.substr(strlen("INVERSE("),
+ authority.size() - 1 -
+ strlen("INVERSE("));
+ }
+ if (starts_with(authority, "DERIVED_FROM(") &&
+ authority.back() == ')') {
+ authority = authority.substr(strlen("DERIVED_FROM("),
+ authority.size() - 1 -
+ strlen("DERIVED_FROM("));
+ }
+
+ remarks += " (";
+ remarks += authority;
+ remarks += ':';
+ remarks += ids.front()->code();
+ remarks += ')';
+ }
+ remarks += ": ";
+ remarks += opRemarks;
+ }
+ }
+ return remarks;
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationNNPtr createHorizVerticalPROJBased(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const operation::CoordinateOperationNNPtr &horizTransform,
+ const operation::CoordinateOperationNNPtr &verticalTransform,
+ bool checkExtent) {
+
+ auto geogDst = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(targetCRS);
+ assert(geogDst);
+
+ auto exportable = util::nn_make_shared<MyPROJStringExportableHorizVertical>(
+ horizTransform, verticalTransform, geogDst);
+
+ const bool horizTransformIsNoOp =
+ starts_with(horizTransform->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ horizTransform->nameStr().find(" + ") == std::string::npos;
+ if (horizTransformIsNoOp) {
+ auto properties = util::PropertyMap();
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ verticalTransform->nameStr());
+ bool dummy = false;
+ auto extent = getExtent(verticalTransform, true, dummy);
+ if (extent) {
+ properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ NN_NO_CHECK(extent));
+ }
+ const auto &remarks = verticalTransform->remarks();
+ if (!remarks.empty()) {
+ properties.set(common::IdentifiedObject::REMARKS_KEY, remarks);
+ }
+ return createPROJBased(
+ properties, exportable, sourceCRS, targetCRS, nullptr,
+ verticalTransform->coordinateOperationAccuracies(),
+ verticalTransform->hasBallparkTransformation());
+ } else {
+ bool emptyIntersection = false;
+ auto ops = std::vector<CoordinateOperationNNPtr>{horizTransform,
+ verticalTransform};
+ auto extent = getExtent(ops, true, emptyIntersection);
+ if (checkExtent && emptyIntersection) {
+ std::string msg(
+ "empty intersection of area of validity of concatenated "
+ "operations");
+ throw InvalidOperationEmptyIntersection(msg);
+ }
+ auto properties = util::PropertyMap();
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ computeConcatenatedName(ops));
+
+ if (extent) {
+ properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ NN_NO_CHECK(extent));
+ }
+
+ const auto remarks = getRemarks(ops);
+ if (!remarks.empty()) {
+ properties.set(common::IdentifiedObject::REMARKS_KEY, remarks);
+ }
+
+ std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
+ const double accuracy = getAccuracy(ops);
+ if (accuracy >= 0.0) {
+ accuracies.emplace_back(
+ metadata::PositionalAccuracy::create(toString(accuracy)));
+ }
+
+ return createPROJBased(
+ properties, exportable, sourceCRS, targetCRS, nullptr, accuracies,
+ horizTransform->hasBallparkTransformation() ||
+ verticalTransform->hasBallparkTransformation());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationNNPtr createHorizVerticalHorizPROJBased(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const operation::CoordinateOperationNNPtr &opSrcCRSToGeogCRS,
+ const operation::CoordinateOperationNNPtr &verticalTransform,
+ const operation::CoordinateOperationNNPtr &opGeogCRStoDstCRS,
+ const crs::GeographicCRSPtr &interpolationGeogCRS, bool checkExtent) {
+
+ auto exportable =
+ util::nn_make_shared<MyPROJStringExportableHorizVerticalHorizPROJBased>(
+ opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS,
+ interpolationGeogCRS);
+
+ std::vector<CoordinateOperationNNPtr> ops;
+ if (!(starts_with(opSrcCRSToGeogCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ opSrcCRSToGeogCRS->nameStr().find(" + ") == std::string::npos)) {
+ ops.emplace_back(opSrcCRSToGeogCRS);
+ }
+ ops.emplace_back(verticalTransform);
+ if (!(starts_with(opGeogCRStoDstCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ opGeogCRStoDstCRS->nameStr().find(" + ") == std::string::npos)) {
+ ops.emplace_back(opGeogCRStoDstCRS);
+ }
+
+ bool hasBallparkTransformation = false;
+ for (const auto &op : ops) {
+ hasBallparkTransformation |= op->hasBallparkTransformation();
+ }
+ bool emptyIntersection = false;
+ auto extent = getExtent(ops, false, emptyIntersection);
+ if (checkExtent && emptyIntersection) {
+ std::string msg(
+ "empty intersection of area of validity of concatenated "
+ "operations");
+ throw InvalidOperationEmptyIntersection(msg);
+ }
+ auto properties = util::PropertyMap();
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ computeConcatenatedName(ops));
+
+ if (extent) {
+ properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ NN_NO_CHECK(extent));
+ }
+
+ const auto remarks = getRemarks(ops);
+ if (!remarks.empty()) {
+ properties.set(common::IdentifiedObject::REMARKS_KEY, remarks);
+ }
+
+ std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
+ const double accuracy = getAccuracy(ops);
+ if (accuracy >= 0.0) {
+ accuracies.emplace_back(
+ metadata::PositionalAccuracy::create(toString(accuracy)));
+ }
+
+ return createPROJBased(properties, exportable, sourceCRS, targetCRS,
+ nullptr, accuracies, hasBallparkTransformation);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::createOperationsGeogToGeog(
+ std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS, Private::Context &context,
+ const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst) {
+
+ assert(sourceCRS.get() == geogSrc);
+ assert(targetCRS.get() == geogDst);
+
+ const auto &src_pm = geogSrc->primeMeridian()->longitude();
+ const auto &dst_pm = geogDst->primeMeridian()->longitude();
+ auto offset_pm =
+ (src_pm.unit() == dst_pm.unit())
+ ? common::Angle(src_pm.value() - dst_pm.value(), src_pm.unit())
+ : common::Angle(
+ src_pm.convertToUnit(common::UnitOfMeasure::DEGREE) -
+ dst_pm.convertToUnit(common::UnitOfMeasure::DEGREE),
+ common::UnitOfMeasure::DEGREE);
+
+ double vconvSrc = 1.0;
+ const auto &srcCS = geogSrc->coordinateSystem();
+ const auto &srcAxisList = srcCS->axisList();
+ if (srcAxisList.size() == 3) {
+ vconvSrc = srcAxisList[2]->unit().conversionToSI();
+ }
+ double vconvDst = 1.0;
+ const auto &dstCS = geogDst->coordinateSystem();
+ const auto &dstAxisList = dstCS->axisList();
+ if (dstAxisList.size() == 3) {
+ vconvDst = dstAxisList[2]->unit().conversionToSI();
+ }
+
+ std::string name(buildTransfName(geogSrc->nameStr(), geogDst->nameStr()));
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
+
+ const bool sameDatum = geogSrc->datumNonNull(dbContext)->_isEquivalentTo(
+ geogDst->datumNonNull(dbContext).get(),
+ util::IComparable::Criterion::EQUIVALENT);
+
+ // Do the CRS differ by their axis order ?
+ bool axisReversal2D = false;
+ bool axisReversal3D = false;
+ if (!srcCS->_isEquivalentTo(dstCS.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto srcOrder = srcCS->axisOrder();
+ auto dstOrder = dstCS->axisOrder();
+ if (((srcOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST ||
+ srcOrder == cs::EllipsoidalCS::AxisOrder::
+ LAT_NORTH_LONG_EAST_HEIGHT_UP) &&
+ (dstOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
+ dstOrder == cs::EllipsoidalCS::AxisOrder::
+ LONG_EAST_LAT_NORTH_HEIGHT_UP)) ||
+ ((srcOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
+ srcOrder == cs::EllipsoidalCS::AxisOrder::
+ LONG_EAST_LAT_NORTH_HEIGHT_UP) &&
+ (dstOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST ||
+ dstOrder == cs::EllipsoidalCS::AxisOrder::
+ LAT_NORTH_LONG_EAST_HEIGHT_UP))) {
+ if (srcAxisList.size() == 3 || dstAxisList.size() == 3)
+ axisReversal3D = true;
+ else
+ axisReversal2D = true;
+ }
+ }
+
+ // Do they differ by vertical units ?
+ if (vconvSrc != vconvDst &&
+ geogSrc->ellipsoid()->_isEquivalentTo(
+ geogDst->ellipsoid().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ if (offset_pm.value() == 0 && !axisReversal2D && !axisReversal3D) {
+ // If only by vertical units, use a Change of Vertical
+ // Unit
+ // transformation
+ const double factor = vconvSrc / vconvDst;
+ auto conv = Conversion::createChangeVerticalUnit(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ name),
+ common::Scale(factor));
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ conv->setHasBallparkTransformation(!sameDatum);
+ res.push_back(conv);
+ return res;
+ } else {
+ auto op = createGeodToGeodPROJBased(sourceCRS, targetCRS);
+ op->setHasBallparkTransformation(!sameDatum);
+ res.emplace_back(op);
+ return res;
+ }
+ }
+
+ // Do the CRS differ only by their axis order ?
+ if (sameDatum && (axisReversal2D || axisReversal3D)) {
+ auto conv = Conversion::createAxisOrderReversal(axisReversal3D);
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ res.emplace_back(conv);
+ return res;
+ }
+
+ std::vector<CoordinateOperationNNPtr> steps;
+ // If both are geographic and only differ by their prime
+ // meridian,
+ // apply a longitude rotation transformation.
+ if (geogSrc->ellipsoid()->_isEquivalentTo(
+ geogDst->ellipsoid().get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ src_pm.getSIValue() != dst_pm.getSIValue()) {
+
+ steps.emplace_back(Transformation::createLongitudeRotation(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, name)
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ sourceCRS, targetCRS, offset_pm));
+ // If only the target has a non-zero prime meridian, chain a
+ // null geographic offset and then the longitude rotation
+ } else if (src_pm.getSIValue() == 0 && dst_pm.getSIValue() != 0) {
+ auto datum = datum::GeodeticReferenceFrame::create(
+ util::PropertyMap(), geogDst->ellipsoid(),
+ util::optional<std::string>(), geogSrc->primeMeridian());
+ std::string interm_crs_name(geogDst->nameStr());
+ interm_crs_name += " altered to use prime meridian of ";
+ interm_crs_name += geogSrc->nameStr();
+ auto interm_crs =
+ util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, interm_crs_name)
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ datum, dstCS));
+
+ steps.emplace_back(
+ createBallparkGeographicOffset(sourceCRS, interm_crs, dbContext));
+
+ steps.emplace_back(Transformation::createLongitudeRotation(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY,
+ buildTransfName(geogSrc->nameStr(), interm_crs->nameStr()))
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ interm_crs, targetCRS, offset_pm));
+
+ } else {
+ // If the prime meridians are different, chain a longitude
+ // rotation and the null geographic offset.
+ if (src_pm.getSIValue() != dst_pm.getSIValue()) {
+ auto datum = datum::GeodeticReferenceFrame::create(
+ util::PropertyMap(), geogSrc->ellipsoid(),
+ util::optional<std::string>(), geogDst->primeMeridian());
+ std::string interm_crs_name(geogSrc->nameStr());
+ interm_crs_name += " altered to use prime meridian of ";
+ interm_crs_name += geogDst->nameStr();
+ auto interm_crs = util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeographicCRS::create(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ interm_crs_name),
+ datum, srcCS));
+
+ steps.emplace_back(Transformation::createLongitudeRotation(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY,
+ buildTransfName(geogSrc->nameStr(),
+ interm_crs->nameStr()))
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ sourceCRS, interm_crs, offset_pm));
+ steps.emplace_back(createBallparkGeographicOffset(
+ interm_crs, targetCRS, dbContext));
+ } else {
+ steps.emplace_back(createBallparkGeographicOffset(
+ sourceCRS, targetCRS, dbContext));
+ }
+ }
+
+ auto op = ConcatenatedOperation::createComputeMetadata(
+ steps, disallowEmptyIntersection);
+ op->setHasBallparkTransformation(!sameDatum);
+ res.emplace_back(op);
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+static bool hasIdentifiers(const CoordinateOperationNNPtr &op) {
+ if (!op->identifiers().empty()) {
+ return true;
+ }
+ auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ if (concatenated) {
+ for (const auto &subOp : concatenated->operations()) {
+ if (hasIdentifiers(subOp)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+static std::vector<crs::CRSNNPtr>
+findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory,
+ const crs::GeodeticCRS *crs,
+ const datum::GeodeticReferenceFrame *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()) {
+ const auto crsIds = crs->identifiers();
+ const auto tmpFactory =
+ (crsIds.size() == 1 &&
+ *(crsIds.front()->codeSpace()) == authName)
+ ? io::AuthorityFactory::create(
+ authFactory->databaseContext(), authName)
+ .as_nullable()
+ : authFactory;
+ auto l_candidates = tmpFactory->createGeodeticCRSFromDatum(
+ authName, code, std::string());
+ for (const auto &candidate : l_candidates) {
+ candidates.emplace_back(candidate);
+ }
+ }
+ }
+ } else if (datumName != "unknown" && datumName != "unnamed") {
+ auto matches = authFactory->createObjectsFromName(
+ datumName,
+ {io::AuthorityFactory::ObjectType::GEODETIC_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 findCandidateGeodCRSForDatum(
+ authFactory, crs,
+ dynamic_cast<const datum::GeodeticReferenceFrame *>(
+ match.get()));
+ }
+ }
+ }
+ return candidates;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::setCRSs(
+ CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS) {
+ co->setCRSs(sourceCRS, targetCRS, nullptr);
+
+ auto invCO = dynamic_cast<InverseCoordinateOperation *>(co);
+ if (invCO) {
+ invCO->forwardOperation()->setCRSs(targetCRS, sourceCRS, nullptr);
+ }
+
+ auto transf = dynamic_cast<Transformation *>(co);
+ if (transf) {
+ transf->inverseAsTransformation()->setCRSs(targetCRS, sourceCRS,
+ nullptr);
+ }
+
+ auto concat = dynamic_cast<ConcatenatedOperation *>(co);
+ if (concat) {
+ auto first = concat->operations().front().get();
+ auto &firstTarget(first->targetCRS());
+ if (firstTarget) {
+ setCRSs(first, sourceCRS, NN_NO_CHECK(firstTarget));
+ }
+ auto last = concat->operations().back().get();
+ auto &lastSource(last->sourceCRS());
+ if (lastSource) {
+ setCRSs(last, NN_NO_CHECK(lastSource), targetCRS);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static bool hasResultSetOnlyResultsWithPROJStep(
+ const std::vector<CoordinateOperationNNPtr> &res) {
+ for (const auto &op : res) {
+ auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ if (concat) {
+ bool hasPROJStep = false;
+ const auto &steps = concat->operations();
+ for (const auto &step : steps) {
+ const auto &ids = step->identifiers();
+ if (!ids.empty()) {
+ const auto &opAuthority = *(ids.front()->codeSpace());
+ if (opAuthority == "PROJ" ||
+ opAuthority == "INVERSE(PROJ)" ||
+ opAuthority == "DERIVED_FROM(PROJ)") {
+ hasPROJStep = true;
+ break;
+ }
+ }
+ }
+ if (!hasPROJStep) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsWithDatumPivot(
+ std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS, const crs::GeodeticCRS *geodSrc,
+ const crs::GeodeticCRS *geodDst, Private::Context &context) {
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("createOperationsWithDatumPivot(" +
+ objectAsStr(sourceCRS.get()) + "," +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+
+ struct CreateOperationsWithDatumPivotAntiRecursion {
+ Context &context;
+
+ explicit CreateOperationsWithDatumPivotAntiRecursion(Context &contextIn)
+ : context(contextIn) {
+ assert(!context.inCreateOperationsWithDatumPivotAntiRecursion);
+ context.inCreateOperationsWithDatumPivotAntiRecursion = true;
+ }
+
+ ~CreateOperationsWithDatumPivotAntiRecursion() {
+ context.inCreateOperationsWithDatumPivotAntiRecursion = false;
+ }
+ };
+ CreateOperationsWithDatumPivotAntiRecursion guard(context);
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto &dbContext = authFactory->databaseContext();
+
+ const auto candidatesSrcGeod(findCandidateGeodCRSForDatum(
+ authFactory, geodSrc,
+ geodSrc->datumNonNull(dbContext.as_nullable()).get()));
+ const auto candidatesDstGeod(findCandidateGeodCRSForDatum(
+ authFactory, geodDst,
+ geodDst->datumNonNull(dbContext.as_nullable()).get()));
+
+ const bool sourceAndTargetAre3D =
+ geodSrc->coordinateSystem()->axisList().size() == 3 &&
+ geodDst->coordinateSystem()->axisList().size() == 3;
+
+ auto createTransformations = [&](const crs::CRSNNPtr &candidateSrcGeod,
+ const crs::CRSNNPtr &candidateDstGeod,
+ const CoordinateOperationNNPtr &opFirst,
+ bool isNullFirst) {
+ const auto opsSecond =
+ createOperations(candidateSrcGeod, candidateDstGeod, context);
+ const auto opsThird =
+ createOperations(candidateDstGeod, targetCRS, context);
+ assert(!opsThird.empty());
+
+ for (auto &opSecond : opsSecond) {
+ // Check that it is not a transformation synthetized by
+ // ourselves
+ if (!hasIdentifiers(opSecond)) {
+ continue;
+ }
+ // And even if it is a referenced transformation, check that
+ // it is not a trivial one
+ auto so = dynamic_cast<const SingleOperation *>(opSecond.get());
+ if (so && isAxisOrderReversal(so->method()->getEPSGCode())) {
+ continue;
+ }
+
+ std::vector<CoordinateOperationNNPtr> subOps;
+ const bool isNullThird =
+ isNullTransformation(opsThird[0]->nameStr());
+ CoordinateOperationNNPtr opSecondCloned(
+ (isNullFirst || isNullThird || sourceAndTargetAre3D)
+ ? opSecond->shallowClone()
+ : opSecond);
+ if (isNullFirst || isNullThird) {
+ if (opSecondCloned->identifiers().size() == 1 &&
+ (*opSecondCloned->identifiers()[0]->codeSpace())
+ .find("DERIVED_FROM") == std::string::npos) {
+ {
+ util::PropertyMap map;
+ addModifiedIdentifier(map, opSecondCloned.get(), false,
+ true);
+ opSecondCloned->setProperties(map);
+ }
+ auto invCO = dynamic_cast<InverseCoordinateOperation *>(
+ opSecondCloned.get());
+ if (invCO) {
+ auto invCOForward = invCO->forwardOperation().get();
+ if (invCOForward->identifiers().size() == 1 &&
+ (*invCOForward->identifiers()[0]->codeSpace())
+ .find("DERIVED_FROM") ==
+ std::string::npos) {
+ util::PropertyMap map;
+ addModifiedIdentifier(map, invCOForward, false,
+ true);
+ invCOForward->setProperties(map);
+ }
+ }
+ }
+ }
+ if (sourceAndTargetAre3D) {
+ opSecondCloned->getPrivate()->use3DHelmert_ = true;
+ auto invCO = dynamic_cast<InverseCoordinateOperation *>(
+ opSecondCloned.get());
+ if (invCO) {
+ auto invCOForward = invCO->forwardOperation().get();
+ invCOForward->getPrivate()->use3DHelmert_ = true;
+ }
+ }
+ if (isNullFirst) {
+ auto oldTarget(NN_CHECK_ASSERT(opSecondCloned->targetCRS()));
+ setCRSs(opSecondCloned.get(), sourceCRS, oldTarget);
+ } else {
+ subOps.emplace_back(opFirst);
+ }
+ if (isNullThird) {
+ auto oldSource(NN_CHECK_ASSERT(opSecondCloned->sourceCRS()));
+ setCRSs(opSecondCloned.get(), oldSource, targetCRS);
+ subOps.emplace_back(opSecondCloned);
+ } else {
+ subOps.emplace_back(opSecondCloned);
+ subOps.emplace_back(opsThird[0]);
+ }
+#ifdef TRACE_CREATE_OPERATIONS
+ std::string debugStr;
+ for (const auto &op : subOps) {
+ if (!debugStr.empty()) {
+ debugStr += " + ";
+ }
+ debugStr += objectAsStr(op.get());
+ debugStr += " (";
+ debugStr += objectAsStr(op->sourceCRS().get());
+ debugStr += "->";
+ debugStr += objectAsStr(op->targetCRS().get());
+ debugStr += ")";
+ }
+ logTrace("transformation " + debugStr);
+#endif
+ try {
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ subOps, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ };
+
+ // Start in priority with candidates that have exactly the same name as
+ // the sourcCRS and targetCRS. Typically for the case of init=IGNF:XXXX
+
+ // Transformation from IGNF:NTFP to IGNF:RGF93G,
+ // using
+ // NTF geographiques Paris (gr) vers NTF GEOGRAPHIQUES GREENWICH (DMS) +
+ // NOUVELLE TRIANGULATION DE LA FRANCE (NTF) vers RGF93 (ETRS89)
+ // that is using ntf_r93.gsb, is horribly dependent
+ // of IGNF:RGF93G being returned before IGNF:RGF93GEO in candidatesDstGeod.
+ // If RGF93GEO is returned before then we go through WGS84 and use
+ // instead a Helmert transformation.
+ // The below logic is thus quite fragile, and attempts at changing it
+ // result in degraded results for other use cases...
+
+ for (const auto &candidateSrcGeod : candidatesSrcGeod) {
+ if (candidateSrcGeod->nameStr() == sourceCRS->nameStr()) {
+ for (const auto &candidateDstGeod : candidatesDstGeod) {
+ if (candidateDstGeod->nameStr() == targetCRS->nameStr()) {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("try " + objectAsStr(sourceCRS.get()) + "->" +
+ objectAsStr(candidateSrcGeod.get()) + "->" +
+ objectAsStr(candidateDstGeod.get()) + "->" +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+ const auto opsFirst =
+ createOperations(sourceCRS, candidateSrcGeod, context);
+ assert(!opsFirst.empty());
+ const bool isNullFirst =
+ isNullTransformation(opsFirst[0]->nameStr());
+ createTransformations(candidateSrcGeod, candidateDstGeod,
+ opsFirst[0], isNullFirst);
+ if (!res.empty()) {
+ if (hasResultSetOnlyResultsWithPROJStep(res)) {
+ continue;
+ }
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ for (const auto &candidateSrcGeod : candidatesSrcGeod) {
+ const bool bSameSrcName =
+ candidateSrcGeod->nameStr() == sourceCRS->nameStr();
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("");
+#endif
+ const auto opsFirst =
+ createOperations(sourceCRS, candidateSrcGeod, context);
+ assert(!opsFirst.empty());
+ const bool isNullFirst = isNullTransformation(opsFirst[0]->nameStr());
+
+ for (const auto &candidateDstGeod : candidatesDstGeod) {
+ if (bSameSrcName &&
+ candidateDstGeod->nameStr() == targetCRS->nameStr()) {
+ continue;
+ }
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("try " + objectAsStr(sourceCRS.get()) + "->" +
+ objectAsStr(candidateSrcGeod.get()) + "->" +
+ objectAsStr(candidateDstGeod.get()) + "->" +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+ createTransformations(candidateSrcGeod, candidateDstGeod,
+ opsFirst[0], isNullFirst);
+ if (!res.empty() && !hasResultSetOnlyResultsWithPROJStep(res)) {
+ return;
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationNNPtr
+createBallparkGeocentricTranslation(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS) {
+ std::string name(BALLPARK_GEOCENTRIC_TRANSLATION);
+ name += " from ";
+ name += sourceCRS->nameStr();
+ name += " to ";
+ name += targetCRS->nameStr();
+
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ Transformation::createGeocentricTranslations(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, name)
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ sourceCRS, targetCRS, 0.0, 0.0, 0.0, {}));
+}
+
+// ---------------------------------------------------------------------------
+
+bool CoordinateOperationFactory::Private::hasPerfectAccuracyResult(
+ const std::vector<CoordinateOperationNNPtr> &res, const Context &context) {
+ auto resTmp = FilterResults(res, context.context, context.extent1,
+ context.extent2, true)
+ .getRes();
+ for (const auto &op : resTmp) {
+ const double acc = getAccuracy(op);
+ if (acc == 0.0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::createOperations(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context) {
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("createOperations(" + objectAsStr(sourceCRS.get()) + " --> " +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+
+ std::vector<CoordinateOperationNNPtr> res;
+
+ auto boundSrc = dynamic_cast<const crs::BoundCRS *>(sourceCRS.get());
+ auto boundDst = dynamic_cast<const crs::BoundCRS *>(targetCRS.get());
+
+ const auto &sourceProj4Ext = boundSrc
+ ? boundSrc->baseCRS()->getExtensionProj4()
+ : sourceCRS->getExtensionProj4();
+ const auto &targetProj4Ext = boundDst
+ ? boundDst->baseCRS()->getExtensionProj4()
+ : targetCRS->getExtensionProj4();
+ if (!sourceProj4Ext.empty() || !targetProj4Ext.empty()) {
+ createOperationsFromProj4Ext(sourceCRS, targetCRS, boundSrc, boundDst,
+ res);
+ return res;
+ }
+
+ auto geodSrc = dynamic_cast<const crs::GeodeticCRS *>(sourceCRS.get());
+ auto geodDst = dynamic_cast<const crs::GeodeticCRS *>(targetCRS.get());
+ auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
+ auto vertSrc = dynamic_cast<const crs::VerticalCRS *>(sourceCRS.get());
+ auto vertDst = dynamic_cast<const crs::VerticalCRS *>(targetCRS.get());
+
+ // First look-up if the registry provide us with operations.
+ auto derivedSrc = dynamic_cast<const crs::DerivedCRS *>(sourceCRS.get());
+ auto derivedDst = dynamic_cast<const crs::DerivedCRS *>(targetCRS.get());
+ const auto &authFactory = context.context->getAuthorityFactory();
+ if (authFactory &&
+ (derivedSrc == nullptr ||
+ !derivedSrc->baseCRS()->_isEquivalentTo(
+ targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) &&
+ (derivedDst == nullptr ||
+ !derivedDst->baseCRS()->_isEquivalentTo(
+ sourceCRS.get(), util::IComparable::Criterion::EQUIVALENT))) {
+
+ if (createOperationsFromDatabase(sourceCRS, targetCRS, context, geodSrc,
+ geodDst, geogSrc, geogDst, vertSrc,
+ vertDst, res)) {
+ return res;
+ }
+ }
+
+ // Special case if both CRS are geodetic
+ if (geodSrc && geodDst && !derivedSrc && !derivedDst) {
+ createOperationsGeodToGeod(sourceCRS, targetCRS, context, geodSrc,
+ geodDst, res);
+ return res;
+ }
+
+ // If the source is a derived CRS, then chain the inverse of its
+ // deriving conversion, with transforms from its baseCRS to the
+ // targetCRS
+ if (derivedSrc) {
+ createOperationsDerivedTo(sourceCRS, targetCRS, context, derivedSrc,
+ res);
+ return res;
+ }
+
+ // reverse of previous case
+ if (derivedDst) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ // Order of comparison between the geogDst vs geodDst is impotant
+ if (boundSrc && geogDst) {
+ createOperationsBoundToGeog(sourceCRS, targetCRS, context, boundSrc,
+ geogDst, res);
+ return res;
+ } else if (boundSrc && geodDst) {
+ createOperationsToGeod(sourceCRS, targetCRS, context, geodDst, res);
+ return res;
+ }
+
+ // reverse of previous case
+ if (geodSrc && boundDst) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ // vertCRS (as boundCRS with transformation to target vertCRS) to
+ // vertCRS
+ if (boundSrc && vertDst) {
+ createOperationsBoundToVert(sourceCRS, targetCRS, context, boundSrc,
+ vertDst, res);
+ return res;
+ }
+
+ // reverse of previous case
+ if (boundDst && vertSrc) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ if (vertSrc && vertDst) {
+ createOperationsVertToVert(sourceCRS, targetCRS, context, vertSrc,
+ vertDst, res);
+ return res;
+ }
+
+ // A bit odd case as we are comparing apples to oranges, but in case
+ // the vertical unit differ, do something useful.
+ if (vertSrc && geogDst) {
+ createOperationsVertToGeog(sourceCRS, targetCRS, context, vertSrc,
+ geogDst, res);
+ return res;
+ }
+
+ // reverse of previous case
+ if (vertDst && geogSrc) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ // boundCRS to boundCRS
+ if (boundSrc && boundDst) {
+ createOperationsBoundToBound(sourceCRS, targetCRS, context, boundSrc,
+ boundDst, res);
+ return res;
+ }
+
+ auto compoundSrc = dynamic_cast<crs::CompoundCRS *>(sourceCRS.get());
+ // Order of comparison between the geogDst vs geodDst is impotant
+ if (compoundSrc && geogDst) {
+ createOperationsCompoundToGeog(sourceCRS, targetCRS, context,
+ compoundSrc, geogDst, res);
+ return res;
+ } else if (compoundSrc && geodDst) {
+ createOperationsToGeod(sourceCRS, targetCRS, context, geodDst, res);
+ return res;
+ }
+
+ // reverse of previous cases
+ auto compoundDst = dynamic_cast<const crs::CompoundCRS *>(targetCRS.get());
+ if (geodSrc && compoundDst) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ if (compoundSrc && compoundDst) {
+ createOperationsCompoundToCompound(sourceCRS, targetCRS, context,
+ compoundSrc, compoundDst, res);
+ return res;
+ }
+
+ // '+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +type=crs' to
+ // '+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx
+ // +type=crs'
+ if (boundSrc && compoundDst) {
+ createOperationsBoundToCompound(sourceCRS, targetCRS, context, boundSrc,
+ compoundDst, res);
+ return res;
+ }
+
+ // reverse of previous case
+ if (boundDst && compoundSrc) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsFromProj4Ext(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ auto sourceProjExportable = dynamic_cast<const io::IPROJStringExportable *>(
+ boundSrc ? boundSrc : sourceCRS.get());
+ auto targetProjExportable = dynamic_cast<const io::IPROJStringExportable *>(
+ boundDst ? boundDst : targetCRS.get());
+ if (!sourceProjExportable) {
+ throw InvalidOperation("Source CRS is not PROJ exportable");
+ }
+ if (!targetProjExportable) {
+ throw InvalidOperation("Target CRS is not PROJ exportable");
+ }
+ auto projFormatter = io::PROJStringFormatter::create();
+ projFormatter->setCRSExport(true);
+ projFormatter->setLegacyCRSToCRSContext(true);
+ projFormatter->startInversion();
+ sourceProjExportable->_exportToPROJString(projFormatter.get());
+ auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ if (geogSrc) {
+ auto tmpFormatter = io::PROJStringFormatter::create();
+ geogSrc->addAngularUnitConvertAndAxisSwap(tmpFormatter.get());
+ projFormatter->ingestPROJString(tmpFormatter->toString());
+ }
+
+ projFormatter->stopInversion();
+
+ targetProjExportable->_exportToPROJString(projFormatter.get());
+ auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
+ if (geogDst) {
+ auto tmpFormatter = io::PROJStringFormatter::create();
+ geogDst->addAngularUnitConvertAndAxisSwap(tmpFormatter.get());
+ projFormatter->ingestPROJString(tmpFormatter->toString());
+ }
+
+ const auto PROJString = projFormatter->toString();
+ auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()));
+ res.emplace_back(SingleOperation::createPROJBased(
+ properties, PROJString, sourceCRS, targetCRS, {}));
+}
+
+// ---------------------------------------------------------------------------
+
+bool CoordinateOperationFactory::Private::createOperationsFromDatabase(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeodeticCRS *geodSrc,
+ const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc,
+ const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ if (geogSrc && vertDst) {
+ createOperationsFromDatabase(targetCRS, sourceCRS, context, geodDst,
+ geodSrc, geogDst, geogSrc, vertDst,
+ vertSrc, res);
+ res = applyInverse(res);
+ } else if (geogDst && vertSrc) {
+ res = applyInverse(createOperationsGeogToVertFromGeoid(
+ targetCRS, sourceCRS, vertSrc, context));
+ if (!res.empty()) {
+ createOperationsVertToGeogBallpark(sourceCRS, targetCRS, context,
+ vertSrc, geogDst, res);
+ }
+ }
+
+ if (!res.empty()) {
+ return true;
+ }
+
+ bool resFindDirectNonEmptyBeforeFiltering = false;
+ res = findOpsInRegistryDirect(sourceCRS, targetCRS, context,
+ resFindDirectNonEmptyBeforeFiltering);
+
+ // If we get at least a result with perfect accuracy, do not
+ // bother generating synthetic transforms.
+ if (hasPerfectAccuracyResult(res, context)) {
+ return true;
+ }
+
+ bool doFilterAndCheckPerfectOp = false;
+
+ bool sameGeodeticDatum = false;
+
+ if (vertSrc || vertDst) {
+ if (res.empty()) {
+ if (geogSrc &&
+ geogSrc->coordinateSystem()->axisList().size() == 2 &&
+ vertDst) {
+ auto dbContext =
+ context.context->getAuthorityFactory()->databaseContext();
+ auto resTmp = findOpsInRegistryDirect(
+ sourceCRS->promoteTo3D(std::string(), dbContext), targetCRS,
+ context, resFindDirectNonEmptyBeforeFiltering);
+ for (auto &op : resTmp) {
+ auto newOp = op->shallowClone();
+ setCRSs(newOp.get(), sourceCRS, targetCRS);
+ res.emplace_back(newOp);
+ }
+ } else if (geogDst &&
+ geogDst->coordinateSystem()->axisList().size() == 2 &&
+ vertSrc) {
+ auto dbContext =
+ context.context->getAuthorityFactory()->databaseContext();
+ auto resTmp = findOpsInRegistryDirect(
+ sourceCRS, targetCRS->promoteTo3D(std::string(), dbContext),
+ context, resFindDirectNonEmptyBeforeFiltering);
+ for (auto &op : resTmp) {
+ auto newOp = op->shallowClone();
+ setCRSs(newOp.get(), sourceCRS, targetCRS);
+ res.emplace_back(newOp);
+ }
+ }
+ }
+ if (res.empty()) {
+ createOperationsFromDatabaseWithVertCRS(sourceCRS, targetCRS,
+ context, geogSrc, geogDst,
+ vertSrc, vertDst, res);
+ }
+ } else if (geodSrc && geodDst) {
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext = authFactory->databaseContext().as_nullable();
+
+ const auto srcDatum = geodSrc->datumNonNull(dbContext);
+ const auto dstDatum = geodDst->datumNonNull(dbContext);
+ sameGeodeticDatum = srcDatum->_isEquivalentTo(
+ dstDatum.get(), util::IComparable::Criterion::EQUIVALENT);
+
+ if (res.empty() && !sameGeodeticDatum &&
+ !context.inCreateOperationsWithDatumPivotAntiRecursion) {
+ // 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
+ // GeodeticCRSs
+ // 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();
+ }
+ }
+
+ bool foundInstantiableOp = false;
+ // FIXME: the limitation to .size() == 1 is just for the
+ // -s EPSG:4959+5759 -t "EPSG:4959+7839" case
+ // finding EPSG:7860 'NZVD2016 height to Auckland 1946
+ // height (1)', which uses the EPSG:1071 'Vertical Offset by Grid
+ // Interpolation (NZLVD)' method which is not currently implemented by PROJ
+ // (cannot deal with .csv files)
+ // Initially the test was written to iterate over for all operations of a
+ // non-empty res, but this causes failures in the test suite when no grids
+ // are installed at all. Ideally we should tweak the test suite to be
+ // robust to that, or skip some tests.
+ if (res.size() == 1) {
+ try {
+ res.front()->exportToPROJString(
+ io::PROJStringFormatter::create().get());
+ foundInstantiableOp = true;
+ } catch (const std::exception &) {
+ }
+ if (!foundInstantiableOp) {
+ resFindDirectNonEmptyBeforeFiltering = false;
+ }
+ } else if (res.size() > 1) {
+ foundInstantiableOp = true;
+ }
+
+ // NAD27 to NAD83 has tens of results already. No need to look
+ // for a pivot
+ if (!sameGeodeticDatum &&
+ (((res.empty() || !foundInstantiableOp) &&
+ !resFindDirectNonEmptyBeforeFiltering &&
+ context.context->getAllowUseIntermediateCRS() ==
+ CoordinateOperationContext::IntermediateCRSUse::
+ IF_NO_DIRECT_TRANSFORMATION) ||
+ context.context->getAllowUseIntermediateCRS() ==
+ CoordinateOperationContext::IntermediateCRSUse::ALWAYS ||
+ getenv("PROJ_FORCE_SEARCH_PIVOT"))) {
+ auto resWithIntermediate = findsOpsInRegistryWithIntermediate(
+ sourceCRS, targetCRS, context, false);
+ res.insert(res.end(), resWithIntermediate.begin(),
+ resWithIntermediate.end());
+ doFilterAndCheckPerfectOp = !res.empty();
+
+ } else if (!context.inCreateOperationsWithDatumPivotAntiRecursion &&
+ !resFindDirectNonEmptyBeforeFiltering && geodSrc && geodDst &&
+ !sameGeodeticDatum &&
+ context.context->getIntermediateCRS().empty() &&
+ context.context->getAllowUseIntermediateCRS() !=
+ CoordinateOperationContext::IntermediateCRSUse::NEVER) {
+
+ bool tryWithGeodeticDatumIntermediate = res.empty();
+ if (!tryWithGeodeticDatumIntermediate) {
+ // This is in particular for the GDA94 to WGS 84 (G1762) case
+ // As we have a WGS 84 -> WGS 84 (G1762) null-transformation in the
+ // PROJ authority, previous steps might have use that WGS 84
+ // intermediate directly. They might also have generated a path
+ // through ITRF2008, as there is a path
+ // GDA94 (geoc.) -> ITRF2008 (geoc.) -> WGS84 (G1762) (geoc.)
+ // But there's a better path using
+ // GDA94 (geog.) --> GDA2020 (geog.) and
+ // GDA2020 (geoc.) -> WGS84 (G1762) (geoc.) that requires to
+ // explore intermediates through their datum, and not directly
+ // trough the CRS code.
+ // Do that only if the number of results we got through other
+ // algorithms is small, or if all results we have go through an
+ // operation in the PROJ authority.
+ constexpr size_t ARBITRARY_SMALL_NUMBER = 5U;
+ tryWithGeodeticDatumIntermediate =
+ res.size() < ARBITRARY_SMALL_NUMBER ||
+ hasResultSetOnlyResultsWithPROJStep(res);
+ }
+ if (tryWithGeodeticDatumIntermediate) {
+ auto resWithIntermediate = findsOpsInRegistryWithIntermediate(
+ sourceCRS, targetCRS, context, true);
+ res.insert(res.end(), resWithIntermediate.begin(),
+ resWithIntermediate.end());
+ doFilterAndCheckPerfectOp = !res.empty();
+ }
+ }
+
+ if (doFilterAndCheckPerfectOp) {
+ // If we get at least a result with perfect accuracy, do not bother
+ // generating synthetic transforms.
+ if (hasPerfectAccuracyResult(res, context)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+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::createOperationsGeogToVertFromGeoid(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::VerticalCRS *vertDst, Private::Context &context) {
+
+ ENTER_FUNCTION();
+
+ const auto useTransf = [&targetCRS, &context,
+ vertDst](const CoordinateOperationNNPtr &op) {
+ const auto targetOp =
+ dynamic_cast<const crs::VerticalCRS *>(op->targetCRS().get());
+ assert(targetOp);
+ if (targetOp->_isEquivalentTo(
+ vertDst, util::IComparable::Criterion::EQUIVALENT)) {
+ return op;
+ }
+ std::vector<CoordinateOperationNNPtr> tmp;
+ createOperationsVertToVert(NN_NO_CHECK(op->targetCRS()), targetCRS,
+ context, targetOp, vertDst, tmp);
+ assert(!tmp.empty());
+ auto ret = ConcatenatedOperation::createComputeMetadata(
+ {op, tmp.front()}, disallowEmptyIntersection);
+ return ret;
+ };
+
+ const auto getProjGeoidTransformation = [&sourceCRS, &targetCRS, &vertDst,
+ &context](
+ const CoordinateOperationNNPtr &model,
+ const std::string &projFilename) {
+
+ const auto getNameVertCRSMetre = [](const std::string &name) {
+ if (name.empty())
+ return std::string("unnamed");
+ auto ret(name);
+ bool haveOriginalUnit = false;
+ if (name.back() == ')') {
+ const auto pos = ret.rfind(" (");
+ if (pos != std::string::npos) {
+ haveOriginalUnit = true;
+ ret = ret.substr(0, pos);
+ }
+ }
+ const auto pos = ret.rfind(" depth");
+ if (pos != std::string::npos) {
+ ret = ret.substr(0, pos) + " height";
+ }
+ if (!haveOriginalUnit) {
+ ret += " (metre)";
+ }
+ return ret;
+ };
+
+ const auto &axis = vertDst->coordinateSystem()->axisList()[0];
+ const auto geogSrcCRS =
+ dynamic_cast<crs::GeographicCRS *>(model->interpolationCRS().get())
+ ? NN_NO_CHECK(model->interpolationCRS())
+ : sourceCRS;
+ const auto vertCRSMetre =
+ axis->unit() == common::UnitOfMeasure::METRE &&
+ axis->direction() == cs::AxisDirection::UP
+ ? targetCRS
+ : util::nn_static_pointer_cast<crs::CRS>(
+ crs::VerticalCRS::create(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ getNameVertCRSMetre(targetCRS->nameStr())),
+ vertDst->datum(), vertDst->datumEnsemble(),
+ cs::VerticalCS::createGravityRelatedHeight(
+ common::UnitOfMeasure::METRE)));
+ const auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildOpName("Transformation", vertCRSMetre, geogSrcCRS));
+
+ // Try to find a representative value for the accuracy of this grid
+ // from the registered transformations.
+ std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
+ const auto &modelAccuracies = model->coordinateOperationAccuracies();
+ if (modelAccuracies.empty()) {
+ const auto &authFactory = context.context->getAuthorityFactory();
+ if (authFactory) {
+ const auto transformationsForGrid =
+ io::DatabaseContext::getTransformationsForGridName(
+ authFactory->databaseContext(), projFilename);
+ double accuracy = -1;
+ for (const auto &transf : transformationsForGrid) {
+ accuracy = std::max(accuracy, getAccuracy(transf));
+ }
+ if (accuracy >= 0) {
+ accuracies.emplace_back(
+ metadata::PositionalAccuracy::create(
+ toString(accuracy)));
+ }
+ }
+ }
+
+ return Transformation::createGravityRelatedHeightToGeographic3D(
+ properties, vertCRSMetre, geogSrcCRS, nullptr, projFilename,
+ !modelAccuracies.empty() ? modelAccuracies : accuracies);
+ };
+
+ std::vector<CoordinateOperationNNPtr> res;
+ const auto &authFactory = context.context->getAuthorityFactory();
+ if (authFactory) {
+ const auto &models = vertDst->geoidModel();
+ for (const auto &model : models) {
+ const auto &modelName = model->nameStr();
+ const auto transformations =
+ starts_with(modelName, "PROJ ")
+ ? std::vector<
+ CoordinateOperationNNPtr>{getProjGeoidTransformation(
+ model, modelName.substr(strlen("PROJ ")))}
+ : authFactory->getTransformationsForGeoid(
+ modelName,
+ context.context->getUsePROJAlternativeGridNames());
+ for (const auto &transf : transformations) {
+ if (dynamic_cast<crs::GeographicCRS *>(
+ transf->sourceCRS().get()) &&
+ dynamic_cast<crs::VerticalCRS *>(
+ transf->targetCRS().get())) {
+ res.push_back(useTransf(transf));
+ } else if (dynamic_cast<crs::GeographicCRS *>(
+ transf->targetCRS().get()) &&
+ dynamic_cast<crs::VerticalCRS *>(
+ transf->sourceCRS().get())) {
+ res.push_back(useTransf(transf->inverse()));
+ }
+ }
+ }
+ }
+
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private::
+ createOperationsGeogToVertWithIntermediateVert(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::VerticalCRS *vertDst, Private::Context &context) {
+
+ ENTER_FUNCTION();
+
+ 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();
+ const auto dbContext = authFactory->databaseContext().as_nullable();
+
+ auto candidatesVert = findCandidateVertCRSForDatum(
+ authFactory, vertDst->datumNonNull(dbContext).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)) {
+ if (candidateVert->_isEquivalentTo(
+ targetCRS.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ res.emplace_back(opFirst);
+ } else {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opsSecond.front()},
+ disallowEmptyIntersection));
+ }
+ }
+ }
+ 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) {
+
+ ENTER_FUNCTION();
+
+ 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);
+
+ // Generally EPSG has operations from GeogCrs to VertCRS
+ auto ops = findOpsInRegistryDirectTo(targetCRS, context);
+
+ for (const auto &op : ops) {
+ const auto tmpCRS = op->sourceCRS();
+ if (tmpCRS && dynamic_cast<const crs::GeographicCRS *>(tmpCRS.get())) {
+ res.emplace_back(op);
+ }
+ }
+
+ 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
+ // promoted 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) {
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable()
+ : nullptr;
+ const auto candidatesSrcGeod(findCandidateGeodCRSForDatum(
+ authFactory, geogSrcIn,
+ geogSrcIn->datumNonNull(dbContext).get()));
+ for (const auto &candidate : candidatesSrcGeod) {
+ auto geogCandidate =
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ candidate);
+ if (geogCandidate &&
+ geogCandidate->coordinateSystem()->axisList().size() == 2) {
+ bool ignored;
+ res =
+ findOpsInRegistryDirect(NN_NO_CHECK(geogCandidate),
+ targetCRSIn, context, ignored);
+ 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,
+ const crs::GeodeticCRS *geodDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ if (geodSrc->ellipsoid()->celestialBody() !=
+ geodDst->ellipsoid()->celestialBody()) {
+ throw util::UnsupportedOperationException(
+ "Source and target ellipsoid do not belong to the same "
+ "celestial body");
+ }
+
+ auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(geodSrc);
+ auto geogDst = dynamic_cast<const crs::GeographicCRS *>(geodDst);
+
+ if (geogSrc && geogDst) {
+ createOperationsGeogToGeog(res, sourceCRS, targetCRS, context, geogSrc,
+ geogDst);
+ return;
+ }
+
+ const bool isSrcGeocentric = geodSrc->isGeocentric();
+ const bool isSrcGeographic = geogSrc != nullptr;
+ const bool isTargetGeocentric = geodDst->isGeocentric();
+ const bool isTargetGeographic = geogDst != nullptr;
+
+ const auto IsSameDatum = [&context, &geodSrc, &geodDst]() {
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable()
+ : nullptr;
+
+ return geodSrc->datumNonNull(dbContext)->_isEquivalentTo(
+ geodDst->datumNonNull(dbContext).get(),
+ util::IComparable::Criterion::EQUIVALENT);
+ };
+
+ if (((isSrcGeocentric && isTargetGeographic) ||
+ (isSrcGeographic && isTargetGeocentric))) {
+
+ // Same datum ?
+ if (IsSameDatum()) {
+ res.emplace_back(
+ Conversion::createGeographicGeocentric(sourceCRS, targetCRS));
+ } else if (isSrcGeocentric && geogDst) {
+ std::string interm_crs_name(geogDst->nameStr());
+ interm_crs_name += " (geocentric)";
+ auto interm_crs =
+ util::nn_static_pointer_cast<crs::CRS>(crs::GeodeticCRS::create(
+ addDomains(util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ interm_crs_name),
+ geogDst),
+ geogDst->datum(), geogDst->datumEnsemble(),
+ NN_CHECK_ASSERT(
+ util::nn_dynamic_pointer_cast<cs::CartesianCS>(
+ geodSrc->coordinateSystem()))));
+ auto opFirst =
+ createBallparkGeocentricTranslation(sourceCRS, interm_crs);
+ auto opSecond =
+ Conversion::createGeographicGeocentric(interm_crs, targetCRS);
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opSecond}, disallowEmptyIntersection));
+ } else {
+ // Apply previous case in reverse way
+ std::vector<CoordinateOperationNNPtr> resTmp;
+ createOperationsGeodToGeod(targetCRS, sourceCRS, context, geodDst,
+ geodSrc, resTmp);
+ assert(resTmp.size() == 1);
+ res.emplace_back(resTmp.front()->inverse());
+ }
+
+ return;
+ }
+
+ if (isSrcGeocentric && isTargetGeocentric) {
+ if (sourceCRS->_isEquivalentTo(
+ targetCRS.get(), util::IComparable::Criterion::EQUIVALENT) ||
+ IsSameDatum()) {
+ std::string name(NULL_GEOCENTRIC_TRANSLATION);
+ name += " from ";
+ name += sourceCRS->nameStr();
+ name += " to ";
+ name += targetCRS->nameStr();
+ res.emplace_back(Transformation::createGeocentricTranslations(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, name)
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ sourceCRS, targetCRS, 0.0, 0.0, 0.0,
+ {metadata::PositionalAccuracy::create("0")}));
+ } else {
+ res.emplace_back(
+ createBallparkGeocentricTranslation(sourceCRS, targetCRS));
+ }
+ return;
+ }
+
+ // Transformation between two geodetic systems of unknown type
+ // This should normally not be triggered with "standard" CRS
+ res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS));
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsDerivedTo(
+ const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::DerivedCRS *derivedSrc,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ auto opFirst = derivedSrc->derivingConversion()->inverse();
+ // Small optimization if the targetCRS is the baseCRS of the source
+ // derivedCRS.
+ if (derivedSrc->baseCRS()->_isEquivalentTo(
+ targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ res.emplace_back(opFirst);
+ return;
+ }
+ auto opsSecond =
+ createOperations(derivedSrc->baseCRS(), targetCRS, context);
+ for (const auto &opSecond : opsSecond) {
+ try {
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opSecond}, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsBoundToGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ const auto &hubSrc = boundSrc->hubCRS();
+ auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get());
+ auto geogCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractGeographicCRS();
+ {
+ // If geogCRSOfBaseOfBoundSrc is a DerivedGeographicCRS, use its base
+ // instead (if it is a GeographicCRS)
+ auto derivedGeogCRS =
+ std::dynamic_pointer_cast<crs::DerivedGeographicCRS>(
+ geogCRSOfBaseOfBoundSrc);
+ if (derivedGeogCRS) {
+ auto baseCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(
+ derivedGeogCRS->baseCRS().as_nullable());
+ if (baseCRS) {
+ geogCRSOfBaseOfBoundSrc = baseCRS;
+ }
+ }
+ }
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
+
+ const auto geogDstDatum = geogDst->datumNonNull(dbContext);
+
+ // If the underlying datum of the source is the same as the target, do
+ // not consider the boundCRS at all, but just its base
+ if (geogCRSOfBaseOfBoundSrc) {
+ auto geogCRSOfBaseOfBoundSrcDatum =
+ geogCRSOfBaseOfBoundSrc->datumNonNull(dbContext);
+ if (geogCRSOfBaseOfBoundSrcDatum->_isEquivalentTo(
+ geogDstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ res = createOperations(boundSrc->baseCRS(), targetCRS, context);
+ return;
+ }
+ }
+
+ bool triedBoundCrsToGeogCRSSameAsHubCRS = false;
+ // Is it: boundCRS to a geogCRS that is the same as the hubCRS ?
+ if (hubSrcGeog && geogCRSOfBaseOfBoundSrc &&
+ (hubSrcGeog->_isEquivalentTo(
+ geogDst, util::IComparable::Criterion::EQUIVALENT) ||
+ hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext))) {
+ triedBoundCrsToGeogCRSSameAsHubCRS = true;
+
+ CoordinateOperationPtr opIntermediate;
+ if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo(
+ boundSrc->transformation()->sourceCRS().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto opsIntermediate = createOperations(
+ NN_NO_CHECK(geogCRSOfBaseOfBoundSrc),
+ boundSrc->transformation()->sourceCRS(), context);
+ assert(!opsIntermediate.empty());
+ opIntermediate = opsIntermediate.front();
+ }
+
+ if (boundSrc->baseCRS() == geogCRSOfBaseOfBoundSrc) {
+ if (opIntermediate) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {NN_NO_CHECK(opIntermediate),
+ boundSrc->transformation()},
+ disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ } else {
+ // Optimization to avoid creating a useless concatenated
+ // operation
+ res.emplace_back(boundSrc->transformation());
+ }
+ return;
+ }
+ auto opsFirst = createOperations(
+ boundSrc->baseCRS(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context);
+ if (!opsFirst.empty()) {
+ for (const auto &opFirst : opsFirst) {
+ try {
+ std::vector<CoordinateOperationNNPtr> subops;
+ subops.emplace_back(opFirst);
+ if (opIntermediate) {
+ subops.emplace_back(NN_NO_CHECK(opIntermediate));
+ }
+ subops.emplace_back(boundSrc->transformation());
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ subops, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ // If the datum are equivalent, this is also fine
+ } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog &&
+ hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo(
+ geogDstDatum.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto opsFirst = createOperations(
+ boundSrc->baseCRS(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context);
+ auto opsLast = createOperations(hubSrc, targetCRS, context);
+ if (!opsFirst.empty() && !opsLast.empty()) {
+ CoordinateOperationPtr opIntermediate;
+ if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo(
+ boundSrc->transformation()->sourceCRS().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto opsIntermediate = createOperations(
+ NN_NO_CHECK(geogCRSOfBaseOfBoundSrc),
+ boundSrc->transformation()->sourceCRS(), context);
+ assert(!opsIntermediate.empty());
+ opIntermediate = opsIntermediate.front();
+ }
+ for (const auto &opFirst : opsFirst) {
+ for (const auto &opLast : opsLast) {
+ try {
+ std::vector<CoordinateOperationNNPtr> subops;
+ subops.emplace_back(opFirst);
+ if (opIntermediate) {
+ subops.emplace_back(NN_NO_CHECK(opIntermediate));
+ }
+ subops.emplace_back(boundSrc->transformation());
+ subops.emplace_back(opLast);
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ subops, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ // Consider WGS 84 and NAD83 as equivalent in that context if the
+ // geogCRSOfBaseOfBoundSrc ellipsoid is Clarke66 (for NAD27)
+ // Case of "+proj=latlong +ellps=clrk66
+ // +nadgrids=ntv1_can.dat,conus"
+ // to "+proj=latlong +datum=NAD83"
+ } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog &&
+ geogCRSOfBaseOfBoundSrc->ellipsoid()->_isEquivalentTo(
+ datum::Ellipsoid::CLARKE_1866.get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo(
+ datum::GeodeticReferenceFrame::EPSG_6326.get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ geogDstDatum->_isEquivalentTo(
+ datum::GeodeticReferenceFrame::EPSG_6269.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto nnGeogCRSOfBaseOfBoundSrc = NN_NO_CHECK(geogCRSOfBaseOfBoundSrc);
+ if (boundSrc->baseCRS()->_isEquivalentTo(
+ nnGeogCRSOfBaseOfBoundSrc.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto transf = boundSrc->transformation()->shallowClone();
+ transf->setProperties(util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildTransfName(boundSrc->baseCRS()->nameStr(),
+ targetCRS->nameStr())));
+ transf->setCRSs(boundSrc->baseCRS(), targetCRS, nullptr);
+ res.emplace_back(transf);
+ return;
+ } else {
+ auto opsFirst = createOperations(
+ boundSrc->baseCRS(), nnGeogCRSOfBaseOfBoundSrc, context);
+ auto transf = boundSrc->transformation()->shallowClone();
+ transf->setProperties(util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildTransfName(nnGeogCRSOfBaseOfBoundSrc->nameStr(),
+ targetCRS->nameStr())));
+ transf->setCRSs(nnGeogCRSOfBaseOfBoundSrc, targetCRS, nullptr);
+ if (!opsFirst.empty()) {
+ for (const auto &opFirst : opsFirst) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, transf}, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ }
+ }
+
+ if (hubSrcGeog &&
+ hubSrcGeog->_isEquivalentTo(geogDst,
+ util::IComparable::Criterion::EQUIVALENT) &&
+ dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get())) {
+ auto transfSrc = boundSrc->transformation()->sourceCRS();
+ if (dynamic_cast<const crs::VerticalCRS *>(transfSrc.get()) &&
+ !boundSrc->baseCRS()->_isEquivalentTo(
+ transfSrc.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ auto opsFirst =
+ createOperations(boundSrc->baseCRS(), transfSrc, context);
+ for (const auto &opFirst : opsFirst) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, boundSrc->transformation()},
+ disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ return;
+ }
+
+ res.emplace_back(boundSrc->transformation());
+ return;
+ }
+
+ if (!triedBoundCrsToGeogCRSSameAsHubCRS && hubSrcGeog &&
+ geogCRSOfBaseOfBoundSrc) {
+ // This one should go to the above 'Is it: boundCRS to a geogCRS
+ // that is the same as the hubCRS ?' case
+ auto opsFirst = createOperations(sourceCRS, hubSrc, context);
+ auto opsLast = createOperations(hubSrc, targetCRS, context);
+ if (!opsFirst.empty() && !opsLast.empty()) {
+ for (const auto &opFirst : opsFirst) {
+ for (const auto &opLast : opsLast) {
+ // Exclude artificial transformations from the hub
+ // to the target CRS, if it is the only one.
+ if (opsLast.size() > 1 ||
+ !opLast->hasBallparkTransformation()) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opLast},
+ disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ } else {
+ // std::cerr << "excluded " << opLast->nameStr() <<
+ // std::endl;
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ }
+
+ auto vertCRSOfBaseOfBoundSrc =
+ dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get());
+ if (vertCRSOfBaseOfBoundSrc && hubSrcGeog) {
+ auto opsFirst = createOperations(sourceCRS, hubSrc, context);
+ if (context.skipHorizontalTransformation) {
+ if (!opsFirst.empty()) {
+ const auto &hubAxisList =
+ hubSrcGeog->coordinateSystem()->axisList();
+ const auto &targetAxisList =
+ geogDst->coordinateSystem()->axisList();
+ if (hubAxisList.size() == 3 && targetAxisList.size() == 3 &&
+ !hubAxisList[2]->_isEquivalentTo(
+ targetAxisList[2].get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+
+ const auto &srcAxis = hubAxisList[2];
+ const double convSrc = srcAxis->unit().conversionToSI();
+ const auto &dstAxis = targetAxisList[2];
+ 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 conv = Conversion::createChangeVerticalUnit(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ "Change of vertical unit"),
+ common::Scale(heightDepthReversal ? -factor : factor));
+
+ conv->setCRSs(
+ hubSrc,
+ hubSrc->demoteTo2D(std::string(), dbContext)
+ ->promoteTo3D(std::string(), dbContext, dstAxis),
+ nullptr);
+
+ for (const auto &op : opsFirst) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {op, conv}, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ } else {
+ res = opsFirst;
+ }
+ }
+ return;
+ } else {
+ auto opsSecond = createOperations(hubSrc, targetCRS, context);
+ if (!opsFirst.empty() && !opsSecond.empty()) {
+ for (const auto &opFirst : opsFirst) {
+ for (const auto &opLast : opsSecond) {
+ // Exclude artificial transformations from the hub
+ // to the target CRS
+ if (!opLast->hasBallparkTransformation()) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::
+ createComputeMetadata(
+ {opFirst, opLast},
+ disallowEmptyIntersection));
+ } catch (
+ const InvalidOperationEmptyIntersection &) {
+ }
+ } else {
+ // std::cerr << "excluded " << opLast->nameStr() <<
+ // std::endl;
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ }
+ }
+
+ res = createOperations(boundSrc->baseCRS(), targetCRS, context);
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsBoundToVert(
+ const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ auto baseSrcVert =
+ dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get());
+ const auto &hubSrc = boundSrc->hubCRS();
+ auto hubSrcVert = dynamic_cast<const crs::VerticalCRS *>(hubSrc.get());
+ if (baseSrcVert && hubSrcVert &&
+ vertDst->_isEquivalentTo(hubSrcVert,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ res.emplace_back(boundSrc->transformation());
+ return;
+ }
+
+ res = createOperations(boundSrc->baseCRS(), targetCRS, context);
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsVertToVert(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
+
+ const auto srcDatum = vertSrc->datumNonNull(dbContext);
+ const auto dstDatum = vertDst->datumNonNull(dbContext);
+ const bool equivalentVDatum = srcDatum->_isEquivalentTo(
+ dstDatum.get(), util::IComparable::Criterion::EQUIVALENT);
+
+ 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());
+ if (!equivalentVDatum) {
+ name += BALLPARK_VERTICAL_TRANSFORMATION;
+ auto conv = Transformation::createChangeVerticalUnit(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name),
+ 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 || !heightDepthReversal) {
+ auto conv = Conversion::createChangeVerticalUnit(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name),
+ // 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 {
+ auto conv = Conversion::createHeightDepthReversal(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name));
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ res.push_back(conv);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsVertToGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::VerticalCRS *vertSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ if (vertSrc->identifiers().empty()) {
+ const auto &vertSrcName = vertSrc->nameStr();
+ const auto &authFactory = context.context->getAuthorityFactory();
+ if (authFactory != nullptr && vertSrcName != "unnamed" &&
+ vertSrcName != "unknown") {
+ auto matches = authFactory->createObjectsFromName(
+ vertSrcName, {io::AuthorityFactory::ObjectType::VERTICAL_CRS},
+ false, 2);
+ if (matches.size() == 1) {
+ const auto &match = matches.front();
+ if (vertSrc->_isEquivalentTo(
+ match.get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ !match->identifiers().empty()) {
+ auto resTmp = createOperations(
+ NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<crs::VerticalCRS>(
+ match)),
+ targetCRS, context);
+ res.insert(res.end(), resTmp.begin(), resTmp.end());
+ return;
+ }
+ }
+ }
+ }
+
+ createOperationsVertToGeogBallpark(sourceCRS, targetCRS, context, vertSrc,
+ geogDst, res);
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsVertToGeogBallpark(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &, const crs::VerticalCRS *vertSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0];
+ const double convSrc = srcAxis->unit().conversionToSI();
+ double convDst = 1.0;
+ const auto &geogAxis = geogDst->coordinateSystem()->axisList();
+ bool dstIsUp = true;
+ bool dstIsDown = false;
+ if (geogAxis.size() == 3) {
+ const auto &dstAxis = geogAxis[2];
+ convDst = dstAxis->unit().conversionToSI();
+ dstIsUp = dstAxis->direction() == cs::AxisDirection::UP;
+ dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN;
+ }
+ const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP;
+ const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN;
+ const bool heightDepthReversal =
+ ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp));
+
+ const double factor = convSrc / convDst;
+
+ const auto &sourceCRSExtent = getExtent(sourceCRS);
+ const auto &targetCRSExtent = getExtent(targetCRS);
+ const bool sameExtent =
+ sourceCRSExtent && targetCRSExtent &&
+ sourceCRSExtent->_isEquivalentTo(
+ targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT);
+
+ util::PropertyMap map;
+ map.set(common::IdentifiedObject::NAME_KEY,
+ buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()) +
+ BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT)
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ sameExtent ? NN_NO_CHECK(sourceCRSExtent)
+ : metadata::Extent::WORLD);
+
+ auto conv = Transformation::createChangeVerticalUnit(
+ map, sourceCRS, targetCRS,
+ common::Scale(heightDepthReversal ? -factor : factor), {});
+ conv->setHasBallparkTransformation(true);
+ res.push_back(conv);
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsBoundToBound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::BoundCRS *boundDst, std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ // BoundCRS to BoundCRS of horizontal CRS using the same (geographic) hub
+ const auto &hubSrc = boundSrc->hubCRS();
+ auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get());
+ const auto &hubDst = boundDst->hubCRS();
+ auto hubDstGeog = dynamic_cast<const crs::GeographicCRS *>(hubDst.get());
+ if (hubSrcGeog && hubDstGeog &&
+ hubSrcGeog->_isEquivalentTo(hubDstGeog,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto opsFirst = createOperations(sourceCRS, hubSrc, context);
+ auto opsLast = createOperations(hubSrc, targetCRS, context);
+ for (const auto &opFirst : opsFirst) {
+ for (const auto &opLast : opsLast) {
+ try {
+ std::vector<CoordinateOperationNNPtr> ops;
+ ops.push_back(opFirst);
+ ops.push_back(opLast);
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ ops, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+
+ // BoundCRS to BoundCRS of vertical CRS using the same vertical datum
+ // ==> ignore the bound transformation
+ auto baseOfBoundSrcAsVertCRS =
+ dynamic_cast<crs::VerticalCRS *>(boundSrc->baseCRS().get());
+ auto baseOfBoundDstAsVertCRS =
+ dynamic_cast<crs::VerticalCRS *>(boundDst->baseCRS().get());
+ if (baseOfBoundSrcAsVertCRS && baseOfBoundDstAsVertCRS) {
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable()
+ : nullptr;
+
+ const auto datumSrc = baseOfBoundSrcAsVertCRS->datumNonNull(dbContext);
+ const auto datumDst = baseOfBoundDstAsVertCRS->datumNonNull(dbContext);
+ if (datumSrc->nameStr() == datumDst->nameStr() &&
+ (datumSrc->nameStr() != "unknown" ||
+ boundSrc->transformation()->_isEquivalentTo(
+ boundDst->transformation().get(),
+ util::IComparable::Criterion::EQUIVALENT))) {
+ res = createOperations(boundSrc->baseCRS(), boundDst->baseCRS(),
+ context);
+ return;
+ }
+ }
+
+ // BoundCRS to BoundCRS of vertical CRS
+ auto vertCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractVerticalCRS();
+ auto vertCRSOfBaseOfBoundDst = boundDst->baseCRS()->extractVerticalCRS();
+ if (hubSrcGeog && hubDstGeog &&
+ hubSrcGeog->_isEquivalentTo(hubDstGeog,
+ util::IComparable::Criterion::EQUIVALENT) &&
+ vertCRSOfBaseOfBoundSrc && vertCRSOfBaseOfBoundDst) {
+ auto opsFirst = createOperations(sourceCRS, hubSrc, context);
+ auto opsLast = createOperations(hubSrc, targetCRS, context);
+ if (!opsFirst.empty() && !opsLast.empty()) {
+ for (const auto &opFirst : opsFirst) {
+ for (const auto &opLast : opsLast) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opLast}, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ }
+
+ res = createOperations(boundSrc->baseCRS(), boundDst->baseCRS(), context);
+}
+
+// ---------------------------------------------------------------------------
+
+static std::vector<CoordinateOperationNNPtr>
+getOps(const CoordinateOperationNNPtr &op) {
+ auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ if (concatenated)
+ return concatenated->operations();
+ return {op};
+}
+
+// ---------------------------------------------------------------------------
+
+static bool useDifferentTransformationsForSameSourceTarget(
+ const CoordinateOperationNNPtr &opA, const CoordinateOperationNNPtr &opB) {
+ auto subOpsA = getOps(opA);
+ auto subOpsB = getOps(opB);
+ for (const auto &subOpA : subOpsA) {
+ if (!dynamic_cast<const Transformation *>(subOpA.get()))
+ continue;
+ if (subOpA->sourceCRS()->nameStr() == "unknown" ||
+ subOpA->targetCRS()->nameStr() == "unknown")
+ continue;
+ for (const auto &subOpB : subOpsB) {
+ if (!dynamic_cast<const Transformation *>(subOpB.get()))
+ continue;
+ if (subOpB->sourceCRS()->nameStr() == "unknown" ||
+ subOpB->targetCRS()->nameStr() == "unknown")
+ continue;
+
+ if (subOpA->sourceCRS()->nameStr() ==
+ subOpB->sourceCRS()->nameStr() &&
+ subOpA->targetCRS()->nameStr() ==
+ subOpB->targetCRS()->nameStr()) {
+ if (starts_with(subOpA->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ starts_with(subOpB->nameStr(), NULL_GEOGRAPHIC_OFFSET)) {
+ continue;
+ }
+
+ if (!subOpA->isEquivalentTo(subOpB.get())) {
+ return true;
+ }
+ } else if (subOpA->sourceCRS()->nameStr() ==
+ subOpB->targetCRS()->nameStr() &&
+ subOpA->targetCRS()->nameStr() ==
+ subOpB->sourceCRS()->nameStr()) {
+ if (starts_with(subOpA->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ starts_with(subOpB->nameStr(), NULL_GEOGRAPHIC_OFFSET)) {
+ continue;
+ }
+
+ if (!subOpA->isEquivalentTo(subOpB->inverse().get())) {
+ return true;
+ }
+ }
+ }
+ }
+ 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,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto &componentsSrc = compoundSrc->componentReferenceSystems();
+ if (!componentsSrc.empty()) {
+
+ if (componentsSrc.size() == 2) {
+ auto derivedHSrc =
+ dynamic_cast<const crs::DerivedCRS *>(componentsSrc[0].get());
+ if (derivedHSrc) {
+ std::vector<crs::CRSNNPtr> intermComponents{
+ derivedHSrc->baseCRS(), componentsSrc[1]};
+ auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ intermComponents[0]->nameStr() + " + " +
+ intermComponents[1]->nameStr());
+ auto intermCompound =
+ crs::CompoundCRS::create(properties, intermComponents);
+ auto opsFirst =
+ createOperations(sourceCRS, intermCompound, context);
+ assert(!opsFirst.empty());
+ auto opsLast =
+ createOperations(intermCompound, targetCRS, context);
+ for (const auto &opLast : opsLast) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opsFirst.front(), opLast},
+ disallowEmptyIntersection));
+ } catch (const std::exception &) {
+ }
+ }
+ return;
+ }
+ }
+
+ std::vector<CoordinateOperationNNPtr> horizTransforms;
+ auto srcGeogCRS = componentsSrc[0]->extractGeographicCRS();
+ if (srcGeogCRS) {
+ horizTransforms =
+ createOperations(componentsSrc[0], targetCRS, context);
+ }
+ std::vector<CoordinateOperationNNPtr> verticalTransforms;
+
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable()
+ : nullptr;
+ if (componentsSrc.size() >= 2 &&
+ componentsSrc[1]->extractVerticalCRS()) {
+
+ struct SetSkipHorizontalTransform {
+ Context &context;
+
+ explicit SetSkipHorizontalTransform(Context &contextIn)
+ : context(contextIn) {
+ assert(!context.skipHorizontalTransformation);
+ context.skipHorizontalTransformation = true;
+ }
+
+ ~SetSkipHorizontalTransform() {
+ context.skipHorizontalTransformation = false;
+ }
+ };
+ SetSkipHorizontalTransform setSkipHorizontalTransform(context);
+
+ verticalTransforms = createOperations(
+ componentsSrc[1],
+ targetCRS->promoteTo3D(std::string(), dbContext), context);
+ bool foundRegisteredTransformWithAllGridsAvailable = false;
+ const auto gridAvailabilityUse =
+ context.context->getGridAvailabilityUse();
+ const bool ignoreMissingGrids =
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY;
+ for (const auto &op : verticalTransforms) {
+ if (hasIdentifiers(op) && dbContext) {
+ bool missingGrid = false;
+ if (!ignoreMissingGrids) {
+ const auto gridsNeeded = op->gridsNeeded(
+ dbContext,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE);
+ for (const auto &gridDesc : gridsNeeded) {
+ if (!gridDesc.available) {
+ missingGrid = true;
+ break;
+ }
+ }
+ }
+ if (!missingGrid) {
+ foundRegisteredTransformWithAllGridsAvailable = true;
+ break;
+ }
+ }
+ }
+ if (!foundRegisteredTransformWithAllGridsAvailable && srcGeogCRS &&
+ !srcGeogCRS->_isEquivalentTo(
+ geogDst, util::IComparable::Criterion::EQUIVALENT) &&
+ !srcGeogCRS->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext)) {
+ auto verticalTransformsTmp = createOperations(
+ componentsSrc[1],
+ NN_NO_CHECK(srcGeogCRS)
+ ->promoteTo3D(std::string(), dbContext),
+ context);
+ bool foundRegisteredTransform = false;
+ foundRegisteredTransformWithAllGridsAvailable = false;
+ for (const auto &op : verticalTransformsTmp) {
+ if (hasIdentifiers(op) && dbContext) {
+ bool missingGrid = false;
+ if (!ignoreMissingGrids) {
+ const auto gridsNeeded = op->gridsNeeded(
+ dbContext,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE);
+ for (const auto &gridDesc : gridsNeeded) {
+ if (!gridDesc.available) {
+ missingGrid = true;
+ break;
+ }
+ }
+ }
+ foundRegisteredTransform = true;
+ if (!missingGrid) {
+ foundRegisteredTransformWithAllGridsAvailable =
+ true;
+ break;
+ }
+ }
+ }
+ if (foundRegisteredTransformWithAllGridsAvailable) {
+ verticalTransforms = verticalTransformsTmp;
+ } else if (foundRegisteredTransform) {
+ verticalTransforms.insert(verticalTransforms.end(),
+ verticalTransformsTmp.begin(),
+ verticalTransformsTmp.end());
+ }
+ }
+ }
+
+ if (horizTransforms.empty() || verticalTransforms.empty()) {
+ res = horizTransforms;
+ return;
+ }
+
+ typedef std::pair<std::vector<CoordinateOperationNNPtr>,
+ std::vector<CoordinateOperationNNPtr>>
+ PairOfTransforms;
+ std::map<std::string, PairOfTransforms>
+ cacheHorizToInterpAndInterpToTarget;
+
+ for (const auto &verticalTransform : verticalTransforms) {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("Considering vertical transform " +
+ objectAsStr(verticalTransform.get()));
+#endif
+ crs::GeographicCRSPtr interpolationGeogCRS =
+ getInterpolationGeogCRS(verticalTransform, dbContext);
+ if (interpolationGeogCRS) {
+#ifdef TRACE_CREATE_OPERATIONS
+ logTrace("Using " + objectAsStr(interpolationGeogCRS.get()) +
+ " as interpolation CRS");
+#endif
+ std::vector<CoordinateOperationNNPtr> srcToInterpOps;
+ std::vector<CoordinateOperationNNPtr> interpToTargetOps;
+
+ std::string key;
+ const auto &ids = interpolationGeogCRS->identifiers();
+ if (!ids.empty()) {
+ key =
+ (*ids.front()->codeSpace()) + ':' + ids.front()->code();
+ }
+
+ const auto computeOpsToInterp =
+ [&srcToInterpOps, &interpToTargetOps, &componentsSrc,
+ &interpolationGeogCRS, &targetCRS, &dbContext,
+ &context]() {
+ srcToInterpOps = createOperations(
+ componentsSrc[0], NN_NO_CHECK(interpolationGeogCRS),
+ context);
+ auto target2D =
+ targetCRS->demoteTo2D(std::string(), dbContext);
+ if (!componentsSrc[0]->isEquivalentTo(
+ target2D.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ // We do the transformation from the
+ // interpolationCRS
+ // to the target one in 3D (see #2225)
+ // But we don't do that between sourceCRS and
+ // interpolationCRS, as this would mess with an
+ // orthometric elevation.
+ auto interp3D = interpolationGeogCRS->promoteTo3D(
+ std::string(), dbContext);
+ interpToTargetOps =
+ createOperations(interp3D, targetCRS, context);
+ }
+ };
+
+ if (!key.empty()) {
+ auto iter = cacheHorizToInterpAndInterpToTarget.find(key);
+ if (iter == cacheHorizToInterpAndInterpToTarget.end()) {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("looking for horizontal transformation "
+ "from source to interpCRS and interpCRS to "
+ "target");
+#endif
+ computeOpsToInterp();
+ cacheHorizToInterpAndInterpToTarget[key] =
+ PairOfTransforms(srcToInterpOps, interpToTargetOps);
+ } else {
+ srcToInterpOps = iter->second.first;
+ interpToTargetOps = iter->second.second;
+ }
+ } else {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("looking for horizontal transformation "
+ "from source to interpCRS and interpCRS to "
+ "target");
+#endif
+ computeOpsToInterp();
+ }
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("creating HorizVerticalHorizPROJBased operations");
+#endif
+ for (const auto &srcToInterp : srcToInterpOps) {
+ if (interpToTargetOps.empty()) {
+ try {
+ auto op = createHorizVerticalHorizPROJBased(
+ sourceCRS, targetCRS, srcToInterp,
+ verticalTransform, srcToInterp->inverse(),
+ interpolationGeogCRS, true);
+ res.emplace_back(op);
+ } catch (const std::exception &) {
+ }
+ } else {
+ for (const auto &interpToTarget : interpToTargetOps) {
+
+ if (useDifferentTransformationsForSameSourceTarget(
+ srcToInterp, interpToTarget)) {
+ continue;
+ }
+
+ try {
+ auto op = createHorizVerticalHorizPROJBased(
+ sourceCRS, targetCRS, srcToInterp,
+ verticalTransform, interpToTarget,
+ interpolationGeogCRS, true);
+ res.emplace_back(op);
+ } catch (const std::exception &) {
+ }
+ }
+ }
+ }
+ } else {
+ // This case is probably only correct if
+ // verticalTransform and horizTransform are independent
+ // and in particular that verticalTransform does not
+ // involve a grid, because of the rather arbitrary order
+ // horizontal then vertical applied
+ for (const auto &horizTransform : horizTransforms) {
+ try {
+ auto op = createHorizVerticalPROJBased(
+ sourceCRS, targetCRS, horizTransform,
+ verticalTransform, disallowEmptyIntersection);
+ res.emplace_back(op);
+ } catch (const std::exception &) {
+ }
+ }
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsToGeod(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeodeticCRS *geodDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ auto cs = cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight(
+ common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE);
+ auto intermGeog3DCRS =
+ util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, geodDst->nameStr())
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ geodDst->datum(), geodDst->datumEnsemble(), cs));
+ auto sourceToGeog3DOps =
+ createOperations(sourceCRS, intermGeog3DCRS, context);
+ auto geog3DToTargetOps =
+ createOperations(intermGeog3DCRS, targetCRS, context);
+ if (!geog3DToTargetOps.empty()) {
+ for (const auto &op : sourceToGeog3DOps) {
+ auto newOp = op->shallowClone();
+ setCRSs(newOp.get(), sourceCRS, intermGeog3DCRS);
+ try {
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ {newOp, geog3DToTargetOps.front()},
+ disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsCompoundToCompound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::CompoundCRS *compoundSrc,
+ const crs::CompoundCRS *compoundDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ const auto &componentsSrc = compoundSrc->componentReferenceSystems();
+ const auto &componentsDst = compoundDst->componentReferenceSystems();
+ if (componentsSrc.empty() || componentsSrc.size() != componentsDst.size()) {
+ return;
+ }
+ const auto srcGeog = componentsSrc[0]->extractGeographicCRS();
+ const auto dstGeog = componentsDst[0]->extractGeographicCRS();
+ if (srcGeog == nullptr || dstGeog == nullptr) {
+ return;
+ }
+
+ std::vector<CoordinateOperationNNPtr> verticalTransforms;
+ if (componentsSrc.size() >= 2 && componentsSrc[1]->extractVerticalCRS() &&
+ componentsDst[1]->extractVerticalCRS()) {
+ if (!componentsSrc[1]->_isEquivalentTo(componentsDst[1].get())) {
+ verticalTransforms =
+ createOperations(componentsSrc[1], componentsDst[1], context);
+ }
+ }
+
+ // If we didn't find a non-ballpark transformation between
+ // the 2 vertical CRS, then try through intermediate geographic CRS
+ // For example
+ // WGS 84 + EGM96 --> ETRS89 + Belfast height where
+ // there is a geoid model for EGM96 referenced to WGS 84
+ // and a geoid model for Belfast height referenced to ETRS89
+ if (verticalTransforms.size() == 1 &&
+ verticalTransforms.front()->hasBallparkTransformation()) {
+ auto dbContext =
+ context.context->getAuthorityFactory()->databaseContext();
+ const auto intermGeogSrc =
+ srcGeog->promoteTo3D(std::string(), dbContext);
+ const bool intermGeogSrcIsSameAsIntermGeogDst =
+ srcGeog->_isEquivalentTo(dstGeog.get());
+ const auto intermGeogDst =
+ intermGeogSrcIsSameAsIntermGeogDst
+ ? intermGeogSrc
+ : dstGeog->promoteTo3D(std::string(), dbContext);
+ const auto opsSrcToGeog =
+ createOperations(sourceCRS, intermGeogSrc, context);
+ const auto opsGeogToTarget =
+ createOperations(intermGeogDst, targetCRS, context);
+ const bool hasNonTrivalSrcTransf =
+ !opsSrcToGeog.empty() &&
+ !opsSrcToGeog.front()->hasBallparkTransformation();
+ const bool hasNonTrivialTargetTransf =
+ !opsGeogToTarget.empty() &&
+ !opsGeogToTarget.front()->hasBallparkTransformation();
+ if (hasNonTrivalSrcTransf && hasNonTrivialTargetTransf) {
+ const auto opsGeogSrcToGeogDst =
+ createOperations(intermGeogSrc, intermGeogDst, context);
+ for (const auto &op1 : opsSrcToGeog) {
+ if (op1->hasBallparkTransformation()) {
+ // std::cerr << "excluded " << op1->nameStr() << std::endl;
+ continue;
+ }
+ for (const auto &op2 : opsGeogSrcToGeogDst) {
+ for (const auto &op3 : opsGeogToTarget) {
+ if (op3->hasBallparkTransformation()) {
+ // std::cerr << "excluded " << op3->nameStr() <<
+ // std::endl;
+ continue;
+ }
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ intermGeogSrcIsSameAsIntermGeogDst
+ ? std::vector<
+ CoordinateOperationNNPtr>{op1,
+ op3}
+ : std::vector<
+ CoordinateOperationNNPtr>{op1,
+ op2,
+ op3},
+ disallowEmptyIntersection));
+ } catch (const std::exception &) {
+ }
+ }
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+
+ for (const auto &verticalTransform : verticalTransforms) {
+ auto interpolationGeogCRS = NN_NO_CHECK(srcGeog);
+ auto interpTransformCRS = verticalTransform->interpolationCRS();
+ if (interpTransformCRS) {
+ auto nn_interpTransformCRS = NN_NO_CHECK(interpTransformCRS);
+ if (dynamic_cast<const crs::GeographicCRS *>(
+ nn_interpTransformCRS.get())) {
+ interpolationGeogCRS = NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ nn_interpTransformCRS));
+ }
+ } else {
+ auto compSrc0BoundCrs =
+ dynamic_cast<crs::BoundCRS *>(componentsSrc[0].get());
+ auto compDst0BoundCrs =
+ dynamic_cast<crs::BoundCRS *>(componentsDst[0].get());
+ if (compSrc0BoundCrs && compDst0BoundCrs &&
+ dynamic_cast<crs::GeographicCRS *>(
+ compSrc0BoundCrs->hubCRS().get()) &&
+ compSrc0BoundCrs->hubCRS()->_isEquivalentTo(
+ compDst0BoundCrs->hubCRS().get())) {
+ interpolationGeogCRS = NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ compSrc0BoundCrs->hubCRS()));
+ }
+ }
+ auto opSrcCRSToGeogCRS =
+ createOperations(componentsSrc[0], interpolationGeogCRS, context);
+ auto opGeogCRStoDstCRS =
+ createOperations(interpolationGeogCRS, componentsDst[0], context);
+ for (const auto &opSrc : opSrcCRSToGeogCRS) {
+ for (const auto &opDst : opGeogCRStoDstCRS) {
+
+ try {
+ auto op = createHorizVerticalHorizPROJBased(
+ sourceCRS, targetCRS, opSrc, verticalTransform, opDst,
+ interpolationGeogCRS, true);
+ res.emplace_back(op);
+ } catch (const InvalidOperationEmptyIntersection &) {
+ } catch (const io::FormattingException &) {
+ }
+ }
+ }
+ }
+
+ if (verticalTransforms.empty()) {
+ auto resTmp =
+ createOperations(componentsSrc[0], componentsDst[0], context);
+ for (const auto &op : resTmp) {
+ auto opClone = op->shallowClone();
+ setCRSs(opClone.get(), sourceCRS, targetCRS);
+ res.emplace_back(opClone);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsBoundToCompound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::CompoundCRS *compoundDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
+
+ const auto &componentsDst = compoundDst->componentReferenceSystems();
+ if (!componentsDst.empty()) {
+ auto compDst0BoundCrs =
+ dynamic_cast<crs::BoundCRS *>(componentsDst[0].get());
+ if (compDst0BoundCrs) {
+ auto boundSrcHubAsGeogCRS =
+ dynamic_cast<crs::GeographicCRS *>(boundSrc->hubCRS().get());
+ auto compDst0BoundCrsHubAsGeogCRS =
+ dynamic_cast<crs::GeographicCRS *>(
+ compDst0BoundCrs->hubCRS().get());
+ if (boundSrcHubAsGeogCRS && compDst0BoundCrsHubAsGeogCRS) {
+ const auto boundSrcHubAsGeogCRSDatum =
+ boundSrcHubAsGeogCRS->datumNonNull(dbContext);
+ const auto compDst0BoundCrsHubAsGeogCRSDatum =
+ compDst0BoundCrsHubAsGeogCRS->datumNonNull(dbContext);
+ if (boundSrcHubAsGeogCRSDatum->_isEquivalentTo(
+ compDst0BoundCrsHubAsGeogCRSDatum.get())) {
+ auto cs = cs::EllipsoidalCS::
+ createLatitudeLongitudeEllipsoidalHeight(
+ common::UnitOfMeasure::DEGREE,
+ common::UnitOfMeasure::METRE);
+ auto intermGeog3DCRS = util::nn_static_pointer_cast<
+ crs::CRS>(crs::GeographicCRS::create(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY,
+ boundSrcHubAsGeogCRS->nameStr())
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ boundSrcHubAsGeogCRS->datum(),
+ boundSrcHubAsGeogCRS->datumEnsemble(), cs));
+ auto sourceToGeog3DOps =
+ createOperations(sourceCRS, intermGeog3DCRS, context);
+ auto geog3DToTargetOps =
+ createOperations(intermGeog3DCRS, targetCRS, context);
+ for (const auto &opSrc : sourceToGeog3DOps) {
+ for (const auto &opDst : geog3DToTargetOps) {
+ if (opSrc->targetCRS() && opDst->sourceCRS() &&
+ !opSrc->targetCRS()->_isEquivalentTo(
+ opDst->sourceCRS().get())) {
+ // Shouldn't happen normally, but typically
+ // one of them can be 2D and the other 3D
+ // due to above createOperations() not
+ // exactly setting the expected source and
+ // target CRS.
+ // So create an adapter operation...
+ auto intermOps = createOperations(
+ NN_NO_CHECK(opSrc->targetCRS()),
+ NN_NO_CHECK(opDst->sourceCRS()), context);
+ if (!intermOps.empty()) {
+ res.emplace_back(
+ ConcatenatedOperation::
+ createComputeMetadata(
+ {opSrc, intermOps.front(),
+ opDst},
+ disallowEmptyIntersection));
+ }
+ } else {
+ res.emplace_back(
+ ConcatenatedOperation::
+ createComputeMetadata(
+ {opSrc, opDst},
+ disallowEmptyIntersection));
+ }
+ }
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ // There might be better things to do, but for now just ignore the
+ // transformation of the bound CRS
+ res = createOperations(boundSrc->baseCRS(), targetCRS, context);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Find a list of CoordinateOperation from sourceCRS to targetCRS.
+ *
+ * The operations are sorted with the most relevant ones first: by
+ * descending
+ * area (intersection of the transformation area with the area of interest,
+ * or intersection of the transformation with the area of use of the CRS),
+ * and
+ * by increasing accuracy. Operations with unknown accuracy are sorted last,
+ * whatever their area.
+ *
+ * When one of the source or target CRS has a vertical component but not the
+ * other one, the one that has no vertical component is automatically promoted
+ * to a 3D version, where its vertical axis is the ellipsoidal height in metres,
+ * using the ellipsoid of the base geodetic CRS.
+ *
+ * @param sourceCRS source CRS.
+ * @param targetCRS target CRS.
+ * @param context Search context.
+ * @return a list
+ */
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::createOperations(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const CoordinateOperationContextNNPtr &context) const {
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_FUNCTION();
+#endif
+ // Look if we are called on CRS that have a link to a 'canonical'
+ // BoundCRS
+ // If so, use that one as input
+ const auto &srcBoundCRS = sourceCRS->canonicalBoundCRS();
+ const auto &targetBoundCRS = targetCRS->canonicalBoundCRS();
+ auto l_sourceCRS = srcBoundCRS ? NN_NO_CHECK(srcBoundCRS) : sourceCRS;
+ auto l_targetCRS = targetBoundCRS ? NN_NO_CHECK(targetBoundCRS) : targetCRS;
+ const auto &authFactory = context->getAuthorityFactory();
+
+ metadata::ExtentPtr sourceCRSExtent;
+ auto l_resolvedSourceCRS =
+ crs::CRS::getResolvedCRS(l_sourceCRS, authFactory, sourceCRSExtent);
+ metadata::ExtentPtr targetCRSExtent;
+ auto l_resolvedTargetCRS =
+ crs::CRS::getResolvedCRS(l_targetCRS, authFactory, targetCRSExtent);
+ Private::Context contextPrivate(sourceCRSExtent, targetCRSExtent, context);
+
+ if (context->getSourceAndTargetCRSExtentUse() ==
+ CoordinateOperationContext::SourceTargetCRSExtentUse::INTERSECTION) {
+ if (sourceCRSExtent && targetCRSExtent &&
+ !sourceCRSExtent->intersects(NN_NO_CHECK(targetCRSExtent))) {
+ return std::vector<CoordinateOperationNNPtr>();
+ }
+ }
+
+ return filterAndSort(Private::createOperations(l_resolvedSourceCRS,
+ l_resolvedTargetCRS,
+ contextPrivate),
+ context, sourceCRSExtent, targetCRSExtent);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a CoordinateOperationFactory.
+ */
+CoordinateOperationFactoryNNPtr CoordinateOperationFactory::create() {
+ return NN_NO_CHECK(
+ CoordinateOperationFactory::make_unique<CoordinateOperationFactory>());
+}
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+
+namespace crs {
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+crs::CRSNNPtr CRS::getResolvedCRS(const crs::CRSNNPtr &crs,
+ const io::AuthorityFactoryPtr &authFactory,
+ metadata::ExtentPtr &extentOut) {
+ const auto &ids = crs->identifiers();
+ const auto &name = crs->nameStr();
+
+ bool approxExtent;
+ extentOut = operation::getExtentPossiblySynthetized(crs, approxExtent);
+
+ // We try to "identify" the provided CRS with the ones of the database,
+ // but in a more restricted way that what identify() does.
+ // If we get a match from id in priority, and from name as a fallback, and
+ // that they are equivalent to the input CRS, then use the identified CRS.
+ // Even if they aren't equivalent, we update extentOut with the one of the
+ // identified CRS if our input one is absent/not reliable.
+
+ const auto tryToIdentifyByName = [&crs, &name, &authFactory, approxExtent,
+ &extentOut](
+ io::AuthorityFactory::ObjectType objectType) {
+ if (name != "unknown" && name != "unnamed") {
+ auto matches = authFactory->createObjectsFromName(
+ name, {objectType}, false, 2);
+ if (matches.size() == 1) {
+ const auto match =
+ util::nn_static_pointer_cast<crs::CRS>(matches.front());
+ if (approxExtent || !extentOut) {
+ extentOut = operation::getExtent(match);
+ }
+ if (match->isEquivalentTo(
+ crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ return match;
+ }
+ }
+ }
+ return crs;
+ };
+
+ auto geogCRS = dynamic_cast<crs::GeographicCRS *>(crs.get());
+ if (geogCRS && authFactory) {
+ if (!ids.empty()) {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(), *ids.front()->codeSpace());
+ try {
+ auto resolvedCrs(
+ tmpAuthFactory->createGeographicCRS(ids.front()->code()));
+ if (approxExtent || !extentOut) {
+ extentOut = operation::getExtent(resolvedCrs);
+ }
+ if (resolvedCrs->isEquivalentTo(
+ crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs);
+ }
+ } catch (const std::exception &) {
+ }
+ } else {
+ return tryToIdentifyByName(
+ geogCRS->coordinateSystem()->axisList().size() == 2
+ ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS
+ : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS);
+ }
+ }
+
+ auto projectedCrs = dynamic_cast<crs::ProjectedCRS *>(crs.get());
+ if (projectedCrs && authFactory) {
+ if (!ids.empty()) {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(), *ids.front()->codeSpace());
+ try {
+ auto resolvedCrs(
+ tmpAuthFactory->createProjectedCRS(ids.front()->code()));
+ if (approxExtent || !extentOut) {
+ extentOut = operation::getExtent(resolvedCrs);
+ }
+ if (resolvedCrs->isEquivalentTo(
+ crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs);
+ }
+ } catch (const std::exception &) {
+ }
+ } else {
+ return tryToIdentifyByName(
+ io::AuthorityFactory::ObjectType::PROJECTED_CRS);
+ }
+ }
+
+ auto compoundCrs = dynamic_cast<crs::CompoundCRS *>(crs.get());
+ if (compoundCrs && authFactory) {
+ if (!ids.empty()) {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(), *ids.front()->codeSpace());
+ try {
+ auto resolvedCrs(
+ tmpAuthFactory->createCompoundCRS(ids.front()->code()));
+ if (approxExtent || !extentOut) {
+ extentOut = operation::getExtent(resolvedCrs);
+ }
+ if (resolvedCrs->isEquivalentTo(
+ crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs);
+ }
+ } catch (const std::exception &) {
+ }
+ } else {
+ auto outCrs = tryToIdentifyByName(
+ io::AuthorityFactory::ObjectType::COMPOUND_CRS);
+ const auto &components = compoundCrs->componentReferenceSystems();
+ if (outCrs.get() != crs.get()) {
+ bool hasGeoid = false;
+ if (components.size() == 2) {
+ auto vertCRS =
+ dynamic_cast<crs::VerticalCRS *>(components[1].get());
+ if (vertCRS && !vertCRS->geoidModel().empty()) {
+ hasGeoid = true;
+ }
+ }
+ if (!hasGeoid) {
+ return outCrs;
+ }
+ }
+ if (approxExtent || !extentOut) {
+ // If we still did not get a reliable extent, then try to
+ // resolve the components of the compoundCRS, and take the
+ // intersection of their extent.
+ extentOut = metadata::ExtentPtr();
+ for (const auto &component : components) {
+ metadata::ExtentPtr componentExtent;
+ getResolvedCRS(component, authFactory, componentExtent);
+ if (extentOut && componentExtent)
+ extentOut = extentOut->intersection(
+ NN_NO_CHECK(componentExtent));
+ else if (componentExtent)
+ extentOut = componentExtent;
+ }
+ }
+ }
+ }
+ return crs;
+}
+
+//! @endcond
+
+} // namespace crs
+NS_PROJ_END
diff --git a/include/proj/internal/esri_projection_mappings.hpp b/src/iso19111/operation/esriparammappings.cpp
index 16cda552..44886e95 100644
--- a/include/proj/internal/esri_projection_mappings.hpp
+++ b/src/iso19111/operation/esriparammappings.cpp
@@ -29,26 +29,24 @@
* DEALINGS IN THE SOFTWARE.
****************************************************************************/
-#ifndef FROM_COORDINATE_OPERATION_CPP
-#error This file should only be included from coordinateoperation.cpp
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
#endif
-#ifndef ESRI_PROJECTION_MAPPINGS_HH_INCLUDED
-#define ESRI_PROJECTION_MAPPINGS_HH_INCLUDED
+#include "esriparammappings.hpp"
+#include "proj_constants.h"
-#include "coordinateoperation_internal.hpp"
+#include "proj/internal/internal.hpp"
-//! @cond Doxygen_Suppress
+NS_PROJ_START
-// ---------------------------------------------------------------------------
+using namespace internal;
-// anonymous namespace
-namespace {
+namespace operation {
-using namespace ::NS_PROJ;
-using namespace ::NS_PROJ::operation;
+//! @cond Doxygen_Suppress
-static const ESRIParamMapping paramsESRI_Plate_Carree[] = {
+const ESRIParamMapping paramsESRI_Plate_Carree[] = {
{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
{"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
@@ -57,7 +55,7 @@ static const ESRIParamMapping paramsESRI_Plate_Carree[] = {
EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
{nullptr, nullptr, 0, "0.0", false}};
-static const ESRIParamMapping paramsESRI_Equidistant_Cylindrical[] = {
+const ESRIParamMapping paramsESRI_Equidistant_Cylindrical[] = {
{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
{"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
@@ -88,7 +86,7 @@ static const ESRIParamMapping paramsESRI_Mercator[] = {
EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
{nullptr, nullptr, 0, "0.0", false}};
-static const ESRIParamMapping paramsESRI_Gauss_Kruger[] = {
+const ESRIParamMapping paramsESRI_Gauss_Kruger[] = {
{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
{"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
@@ -101,7 +99,7 @@ static const ESRIParamMapping paramsESRI_Gauss_Kruger[] = {
EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
{nullptr, nullptr, 0, "0.0", false}};
-static const ESRIParamMapping paramsESRI_Transverse_Mercator[] = {
+const ESRIParamMapping paramsESRI_Transverse_Mercator[] = {
{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
{"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
@@ -492,7 +490,7 @@ static const ESRIParamMapping
EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
{nullptr, nullptr, 0, "0.0", false}};
-static const ESRIParamMapping
+const ESRIParamMapping
paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin[] = {
{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
@@ -508,21 +506,20 @@ static const ESRIParamMapping
EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
{nullptr, nullptr, 0, "0.0", false}};
-static const ESRIParamMapping
- paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center[] = {
- {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE,
- EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false},
- {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE,
- EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false},
- {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
- EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
- {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
- EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false},
- {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
- EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false},
- {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
- EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
- {nullptr, nullptr, 0, "0.0", false}};
+const ESRIParamMapping paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
+ {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
static const ESRIParamMapping paramsESRI_Double_Stereographic[] = {
{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
@@ -706,29 +703,11 @@ static const ESRIParamMapping paramsESRI_Stereographic_South_Pole[] = {
EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, "0.0", false},
{nullptr, nullptr, 0, "0.0", false}};
-static const ESRIParamMapping
- paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin[] = {
- {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
- EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
- {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
- EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
- {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
- EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
- {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
- EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false},
- {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
- EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false},
- {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
- EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
- {"XY_Plane_Rotation", EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
- EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, "0.0", false},
- {nullptr, nullptr, 0, "0.0", false}};
-
-static const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Center[] =
- {{"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE,
- EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false},
- {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE,
- EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false},
+const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin[] =
+ {{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
{"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
{"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
@@ -741,6 +720,23 @@ static const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Center[] =
EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, "0.0", false},
{nullptr, nullptr, 0, "0.0", false}};
+const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Center[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
+ {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"XY_Plane_Rotation", EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
static const ESRIParamMapping paramsESRI_Goode_Homolosine_alt1[] = {
{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
@@ -1101,8 +1097,27 @@ static const ESRIMethodMapping esriMappings[] = {
// ---------------------------------------------------------------------------
-} // namespace {
+const ESRIMethodMapping *getEsriMappings(size_t &nElts) {
+ nElts = sizeof(esriMappings) / sizeof(esriMappings[0]);
+ return esriMappings;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<const ESRIMethodMapping *>
+getMappingsFromESRI(const std::string &esri_name) {
+ std::vector<const ESRIMethodMapping *> res;
+ for (const auto &mapping : esriMappings) {
+ if (ci_equal(esri_name, mapping.esri_name)) {
+ res.push_back(&mapping);
+ }
+ }
+ return res;
+}
//! @endcond
-#endif // ESRI_PROJECTION_MAPPINGS_HH_INCLUDED
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/src/iso19111/operation/esriparammappings.hpp b/src/iso19111/operation/esriparammappings.hpp
new file mode 100644
index 00000000..373c1f22
--- /dev/null
+++ b/src/iso19111/operation/esriparammappings.hpp
@@ -0,0 +1,86 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef ESRIPARAMMAPPINGS_HPP
+#define ESRIPARAMMAPPINGS_HPP
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/util.hpp"
+
+#include "esriparammappings.hpp"
+#include <vector>
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+struct ESRIParamMapping {
+ const char *esri_name;
+ const char *wkt2_name;
+ int epsg_code;
+ const char *fixed_value;
+ bool is_fixed_value;
+};
+
+struct ESRIMethodMapping {
+ const char *esri_name;
+ const char *wkt2_name;
+ int epsg_code;
+ const ESRIParamMapping *const params;
+};
+
+extern const ESRIParamMapping paramsESRI_Plate_Carree[];
+extern const ESRIParamMapping paramsESRI_Equidistant_Cylindrical[];
+extern const ESRIParamMapping paramsESRI_Gauss_Kruger[];
+extern const ESRIParamMapping paramsESRI_Transverse_Mercator[];
+extern const ESRIParamMapping
+ paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin[];
+extern const ESRIParamMapping
+ paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin[];
+extern const ESRIParamMapping
+ paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center[];
+extern const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Center[];
+
+const ESRIMethodMapping *getEsriMappings(size_t &nElts);
+
+std::vector<const ESRIMethodMapping *>
+getMappingsFromESRI(const std::string &esri_name);
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // ESRIPARAMMAPPINGS_HPP
diff --git a/src/iso19111/operation/operationmethod_private.hpp b/src/iso19111/operation/operationmethod_private.hpp
new file mode 100644
index 00000000..83d29026
--- /dev/null
+++ b/src/iso19111/operation/operationmethod_private.hpp
@@ -0,0 +1,56 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef OPERATIONMETHOD_PRIVATE_HPP
+#define OPERATIONMETHOD_PRIVATE_HPP
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/util.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct OperationMethod::Private {
+ util::optional<std::string> formula_{};
+ util::optional<metadata::Citation> formulaCitation_{};
+ std::vector<GeneralOperationParameterNNPtr> parameters_{};
+ std::string projMethodOverride_{};
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // OPERATIONMETHOD_PRIVATE_HPP
diff --git a/src/iso19111/operation/oputils.cpp b/src/iso19111/operation/oputils.cpp
new file mode 100644
index 00000000..b5834edf
--- /dev/null
+++ b/src/iso19111/operation/oputils.cpp
@@ -0,0 +1,643 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+
+#include <string.h>
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/crs.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
+
+#include "oputils.hpp"
+#include "parammappings.hpp"
+
+#include "proj_constants.h"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+
+using namespace internal;
+
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+const char *BALLPARK_GEOCENTRIC_TRANSLATION = "Ballpark geocentric translation";
+const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset";
+const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation";
+const char *BALLPARK_GEOGRAPHIC_OFFSET = "Ballpark geographic offset";
+const char *BALLPARK_VERTICAL_TRANSFORMATION =
+ " (ballpark vertical transformation)";
+const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT =
+ " (ballpark vertical transformation, without ellipsoid height to vertical "
+ "height correction)";
+
+// ---------------------------------------------------------------------------
+
+OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
+ const char *name = OperationParameter::getNameForEPSGCode(code);
+ assert(name);
+ return OperationParameter::create(createMapNameEPSGCode(name, code));
+}
+
+// ---------------------------------------------------------------------------
+
+util::PropertyMap createMethodMapNameEPSGCode(int code) {
+ const char *name = nullptr;
+ size_t nMethodNameCodes = 0;
+ const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes);
+ for (size_t i = 0; i < nMethodNameCodes; ++i) {
+ const auto &tuple = methodNameCodes[i];
+ if (tuple.epsg_code == code) {
+ name = tuple.name;
+ break;
+ }
+ }
+ assert(name);
+ return createMapNameEPSGCode(name, code);
+}
+
+// ---------------------------------------------------------------------------
+
+util::PropertyMap createMapNameEPSGCode(const std::string &name, int code) {
+ return util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, name)
+ .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, code);
+}
+
+// ---------------------------------------------------------------------------
+
+util::PropertyMap createMapNameEPSGCode(const char *name, int code) {
+ return util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, name)
+ .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, code);
+}
+
+// ---------------------------------------------------------------------------
+
+util::PropertyMap &addDomains(util::PropertyMap &map,
+ const common::ObjectUsage *obj) {
+
+ auto ar = util::ArrayOfBaseObject::create();
+ for (const auto &domain : obj->domains()) {
+ ar->add(domain);
+ }
+ if (!ar->empty()) {
+ map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar);
+ }
+ return map;
+}
+
+// ---------------------------------------------------------------------------
+
+static const char *getCRSQualifierStr(const crs::CRSPtr &crs) {
+ auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get());
+ if (geod) {
+ if (geod->isGeocentric()) {
+ return " (geocentric)";
+ }
+ auto geog = dynamic_cast<crs::GeographicCRS *>(geod);
+ if (geog) {
+ if (geog->coordinateSystem()->axisList().size() == 2) {
+ return " (geog2D)";
+ } else {
+ return " (geog3D)";
+ }
+ }
+ }
+ return "";
+}
+
+// ---------------------------------------------------------------------------
+
+std::string buildOpName(const char *opType, const crs::CRSPtr &source,
+ const crs::CRSPtr &target) {
+ std::string res(opType);
+ const auto &srcName = source->nameStr();
+ const auto &targetName = target->nameStr();
+ const char *srcQualifier = "";
+ const char *targetQualifier = "";
+ if (srcName == targetName) {
+ srcQualifier = getCRSQualifierStr(source);
+ targetQualifier = getCRSQualifierStr(target);
+ if (strcmp(srcQualifier, targetQualifier) == 0) {
+ srcQualifier = "";
+ targetQualifier = "";
+ }
+ }
+ res += " from ";
+ res += srcName;
+ res += srcQualifier;
+ res += " to ";
+ res += targetName;
+ res += targetQualifier;
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+void addModifiedIdentifier(util::PropertyMap &map,
+ const common::IdentifiedObject *obj, bool inverse,
+ bool derivedFrom) {
+ // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE
+ // as identifier.
+
+ auto ar = util::ArrayOfBaseObject::create();
+ for (const auto &idSrc : obj->identifiers()) {
+ auto authName = *(idSrc->codeSpace());
+ const auto &srcCode = idSrc->code();
+ if (derivedFrom) {
+ authName = concat("DERIVED_FROM(", authName, ")");
+ }
+ if (inverse) {
+ if (starts_with(authName, "INVERSE(") && authName.back() == ')') {
+ authName = authName.substr(strlen("INVERSE("));
+ authName.resize(authName.size() - 1);
+ } else {
+ authName = concat("INVERSE(", authName, ")");
+ }
+ }
+ auto idsProp = util::PropertyMap().set(
+ metadata::Identifier::CODESPACE_KEY, authName);
+ ar->add(metadata::Identifier::create(srcCode, idsProp));
+ }
+ if (!ar->empty()) {
+ map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+util::PropertyMap
+createPropertiesForInverse(const OperationMethodNNPtr &method) {
+ util::PropertyMap map;
+
+ const std::string &forwardName = method->nameStr();
+ if (!forwardName.empty()) {
+ if (starts_with(forwardName, INVERSE_OF)) {
+ map.set(common::IdentifiedObject::NAME_KEY,
+ forwardName.substr(INVERSE_OF.size()));
+ } else {
+ map.set(common::IdentifiedObject::NAME_KEY,
+ INVERSE_OF + forwardName);
+ }
+ }
+
+ addModifiedIdentifier(map, method.get(), true, false);
+
+ return map;
+}
+
+// ---------------------------------------------------------------------------
+
+util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op,
+ bool derivedFrom,
+ bool approximateInversion) {
+ assert(op);
+ util::PropertyMap map;
+
+ // The domain(s) are unchanged by the inverse operation
+ addDomains(map, op);
+
+ const std::string &forwardName = op->nameStr();
+
+ // Forge a name for the inverse, either from the forward name, or
+ // from the source and target CRS names
+ const char *opType;
+ if (starts_with(forwardName, BALLPARK_GEOCENTRIC_TRANSLATION)) {
+ opType = BALLPARK_GEOCENTRIC_TRANSLATION;
+ } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) {
+ opType = BALLPARK_GEOGRAPHIC_OFFSET;
+ } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) {
+ opType = NULL_GEOGRAPHIC_OFFSET;
+ } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) {
+ opType = NULL_GEOCENTRIC_TRANSLATION;
+ } else if (dynamic_cast<const Transformation *>(op) ||
+ starts_with(forwardName, "Transformation from ")) {
+ opType = "Transformation";
+ } else if (dynamic_cast<const Conversion *>(op)) {
+ opType = "Conversion";
+ } else {
+ opType = "Operation";
+ }
+
+ auto sourceCRS = op->sourceCRS();
+ auto targetCRS = op->targetCRS();
+ std::string name;
+ if (!forwardName.empty()) {
+ if (dynamic_cast<const Transformation *>(op) == nullptr &&
+ dynamic_cast<const ConcatenatedOperation *>(op) == nullptr &&
+ (starts_with(forwardName, INVERSE_OF) ||
+ forwardName.find(" + ") != std::string::npos)) {
+ std::vector<std::string> tokens;
+ std::string curToken;
+ bool inString = false;
+ for (size_t i = 0; i < forwardName.size(); ++i) {
+ if (inString) {
+ curToken += forwardName[i];
+ if (forwardName[i] == '\'') {
+ inString = false;
+ }
+ } else if (i + 3 < forwardName.size() &&
+ memcmp(&forwardName[i], " + ", 3) == 0) {
+ tokens.push_back(curToken);
+ curToken.clear();
+ i += 2;
+ } else if (forwardName[i] == '\'') {
+ inString = true;
+ curToken += forwardName[i];
+ } else {
+ curToken += forwardName[i];
+ }
+ }
+ if (!curToken.empty()) {
+ tokens.push_back(curToken);
+ }
+ for (size_t i = tokens.size(); i > 0;) {
+ i--;
+ if (!name.empty()) {
+ name += " + ";
+ }
+ if (starts_with(tokens[i], INVERSE_OF)) {
+ name += tokens[i].substr(INVERSE_OF.size());
+ } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME ||
+ tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) {
+ name += tokens[i];
+ } else {
+ name += INVERSE_OF + tokens[i];
+ }
+ }
+ } else if (!sourceCRS || !targetCRS ||
+ forwardName != buildOpName(opType, sourceCRS, targetCRS)) {
+ if (forwardName.find(" + ") != std::string::npos) {
+ name = INVERSE_OF + '\'' + forwardName + '\'';
+ } else {
+ name = INVERSE_OF + forwardName;
+ }
+ }
+ }
+ if (name.empty() && sourceCRS && targetCRS) {
+ name = buildOpName(opType, targetCRS, sourceCRS);
+ }
+ if (approximateInversion) {
+ name += " (approx. inversion)";
+ }
+
+ if (!name.empty()) {
+ map.set(common::IdentifiedObject::NAME_KEY, name);
+ }
+
+ const std::string &remarks = op->remarks();
+ if (!remarks.empty()) {
+ map.set(common::IdentifiedObject::REMARKS_KEY, remarks);
+ }
+
+ addModifiedIdentifier(map, op, true, derivedFrom);
+
+ const auto so = dynamic_cast<const SingleOperation *>(op);
+ if (so) {
+ const int soMethodEPSGCode = so->method()->getEPSGCode();
+ if (soMethodEPSGCode > 0) {
+ map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode);
+ }
+ }
+
+ return map;
+}
+
+// ---------------------------------------------------------------------------
+
+util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties,
+ const std::string &defaultName) {
+ if (!properties.get(common::IdentifiedObject::NAME_KEY)) {
+ return util::PropertyMap(properties)
+ .set(common::IdentifiedObject::NAME_KEY, defaultName);
+ } else {
+ return properties;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static std::string createEntryEqParam(const std::string &a,
+ const std::string &b) {
+ return a < b ? a + b : b + a;
+}
+
+static std::set<std::string> buildSetEquivalentParameters() {
+
+ std::set<std::string> set;
+
+ const char *const listOfEquivalentParameterNames[][7] = {
+ {"latitude_of_point_1", "Latitude_Of_1st_Point", nullptr},
+ {"longitude_of_point_1", "Longitude_Of_1st_Point", nullptr},
+ {"latitude_of_point_2", "Latitude_Of_2nd_Point", nullptr},
+ {"longitude_of_point_2", "Longitude_Of_2nd_Point", nullptr},
+
+ {"satellite_height", "height", nullptr},
+
+ {EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, nullptr},
+
+ {EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, nullptr},
+
+ {EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR,
+ EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, nullptr},
+
+ {WKT1_LATITUDE_OF_ORIGIN, WKT1_LATITUDE_OF_CENTER,
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, "Central_Parallel",
+ nullptr},
+
+ {WKT1_CENTRAL_MERIDIAN, WKT1_LONGITUDE_OF_CENTER,
+ EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, nullptr},
+
+ {"pseudo_standard_parallel_1", WKT1_STANDARD_PARALLEL_1, nullptr},
+ };
+
+ for (const auto &paramList : listOfEquivalentParameterNames) {
+ for (size_t i = 0; paramList[i]; i++) {
+ auto a = metadata::Identifier::canonicalizeName(paramList[i]);
+ for (size_t j = i + 1; paramList[j]; j++) {
+ auto b = metadata::Identifier::canonicalizeName(paramList[j]);
+ set.insert(createEntryEqParam(a, b));
+ }
+ }
+ }
+ return set;
+}
+
+bool areEquivalentParameters(const std::string &a, const std::string &b) {
+
+ static const std::set<std::string> setEquivalentParameters =
+ buildSetEquivalentParameters();
+
+ auto a_can = metadata::Identifier::canonicalizeName(a);
+ auto b_can = metadata::Identifier::canonicalizeName(b);
+ return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) !=
+ setEquivalentParameters.end();
+}
+
+// ---------------------------------------------------------------------------
+
+bool isTimeDependent(const std::string &methodName) {
+ return ci_find(methodName, "Time dependent") != std::string::npos ||
+ ci_find(methodName, "Time-dependent") != std::string::npos;
+}
+
+// ---------------------------------------------------------------------------
+
+std::string computeConcatenatedName(
+ const std::vector<CoordinateOperationNNPtr> &flattenOps) {
+ std::string name;
+ for (const auto &subOp : flattenOps) {
+ if (!name.empty()) {
+ name += " + ";
+ }
+ const auto &l_name = subOp->nameStr();
+ if (l_name.empty()) {
+ name += "unnamed";
+ } else {
+ name += l_name;
+ }
+ }
+ return name;
+}
+
+// ---------------------------------------------------------------------------
+
+metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op,
+ bool conversionExtentIsWorld,
+ bool &emptyIntersection) {
+ auto conv = dynamic_cast<const Conversion *>(op.get());
+ if (conv) {
+ emptyIntersection = false;
+ return metadata::Extent::WORLD;
+ }
+ const auto &domains = op->domains();
+ if (!domains.empty()) {
+ emptyIntersection = false;
+ return domains[0]->domainOfValidity();
+ }
+ auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ if (!concatenated) {
+ emptyIntersection = false;
+ return nullptr;
+ }
+ return getExtent(concatenated->operations(), conversionExtentIsWorld,
+ emptyIntersection);
+}
+
+// ---------------------------------------------------------------------------
+
+static const metadata::ExtentPtr nullExtent{};
+
+const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) {
+ const auto &domains = crs->domains();
+ if (!domains.empty()) {
+ return domains[0]->domainOfValidity();
+ }
+ const auto *boundCRS = dynamic_cast<const crs::BoundCRS *>(crs.get());
+ if (boundCRS) {
+ return getExtent(boundCRS->baseCRS());
+ }
+ return nullExtent;
+}
+
+const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs,
+ bool &approxOut) {
+ const auto &rawExtent(getExtent(crs));
+ approxOut = false;
+ if (rawExtent)
+ return rawExtent;
+ const auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get());
+ if (compoundCRS) {
+ // For a compoundCRS, take the intersection of the extent of its
+ // components.
+ const auto &components = compoundCRS->componentReferenceSystems();
+ metadata::ExtentPtr extent;
+ approxOut = true;
+ for (const auto &component : components) {
+ const auto &componentExtent(getExtent(component));
+ if (extent && componentExtent)
+ extent = extent->intersection(NN_NO_CHECK(componentExtent));
+ else if (componentExtent)
+ extent = componentExtent;
+ }
+ return extent;
+ }
+ return rawExtent;
+}
+
+// ---------------------------------------------------------------------------
+
+metadata::ExtentPtr getExtent(const std::vector<CoordinateOperationNNPtr> &ops,
+ bool conversionExtentIsWorld,
+ bool &emptyIntersection) {
+ metadata::ExtentPtr res = nullptr;
+ for (const auto &subop : ops) {
+
+ const auto &subExtent =
+ getExtent(subop, conversionExtentIsWorld, emptyIntersection);
+ if (!subExtent) {
+ if (emptyIntersection) {
+ return nullptr;
+ }
+ continue;
+ }
+ if (res == nullptr) {
+ res = subExtent;
+ } else {
+ res = res->intersection(NN_NO_CHECK(subExtent));
+ if (!res) {
+ emptyIntersection = true;
+ return nullptr;
+ }
+ }
+ }
+ emptyIntersection = false;
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+// Returns the accuracy of an operation, or -1 if unknown
+double getAccuracy(const CoordinateOperationNNPtr &op) {
+
+ if (dynamic_cast<const Conversion *>(op.get())) {
+ // A conversion is perfectly accurate.
+ return 0.0;
+ }
+
+ double accuracy = -1.0;
+ const auto &accuracies = op->coordinateOperationAccuracies();
+ if (!accuracies.empty()) {
+ try {
+ accuracy = c_locale_stod(accuracies[0]->value());
+ } catch (const std::exception &) {
+ }
+ } else {
+ auto concatenated =
+ dynamic_cast<const ConcatenatedOperation *>(op.get());
+ if (concatenated) {
+ accuracy = getAccuracy(concatenated->operations());
+ }
+ }
+ return accuracy;
+}
+
+// ---------------------------------------------------------------------------
+
+// Returns the accuracy of a set of concatenated operations, or -1 if unknown
+double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) {
+ double accuracy = -1.0;
+ for (const auto &subop : ops) {
+ const double subops_accuracy = getAccuracy(subop);
+ if (subops_accuracy < 0.0) {
+ return -1.0;
+ }
+ if (accuracy < 0.0) {
+ accuracy = 0.0;
+ }
+ accuracy += subops_accuracy;
+ }
+ return accuracy;
+}
+
+// ---------------------------------------------------------------------------
+
+void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co,
+ io::WKTFormatter *formatter) {
+ auto l_sourceCRS = co->sourceCRS();
+ assert(l_sourceCRS);
+ auto l_targetCRS = co->targetCRS();
+ assert(l_targetCRS);
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ const bool canExportCRSId =
+ (isWKT2 && formatter->use2019Keywords() &&
+ !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId()));
+
+ const bool hasDomains = !co->domains().empty();
+ if (hasDomains) {
+ formatter->pushDisableUsage();
+ }
+
+ formatter->startNode(io::WKTConstants::SOURCECRS, false);
+ if (canExportCRSId && !l_sourceCRS->identifiers().empty()) {
+ // fake that top node has no id, so that the sourceCRS id is
+ // considered
+ formatter->pushHasId(false);
+ l_sourceCRS->_exportToWKT(formatter);
+ formatter->popHasId();
+ } else {
+ l_sourceCRS->_exportToWKT(formatter);
+ }
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::TARGETCRS, false);
+ if (canExportCRSId && !l_targetCRS->identifiers().empty()) {
+ // fake that top node has no id, so that the targetCRS id is
+ // considered
+ formatter->pushHasId(false);
+ l_targetCRS->_exportToWKT(formatter);
+ formatter->popHasId();
+ } else {
+ l_targetCRS->_exportToWKT(formatter);
+ }
+ formatter->endNode();
+
+ if (hasDomains) {
+ formatter->popDisableUsage();
+ }
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/src/iso19111/operation/oputils.hpp b/src/iso19111/operation/oputils.hpp
new file mode 100644
index 00000000..8c2ad1f0
--- /dev/null
+++ b/src/iso19111/operation/oputils.hpp
@@ -0,0 +1,121 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef OPUTILS_HPP
+#define OPUTILS_HPP
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+extern const common::Measure nullMeasure;
+
+extern const std::string INVERSE_OF;
+
+extern const char *BALLPARK_GEOCENTRIC_TRANSLATION;
+extern const char *NULL_GEOGRAPHIC_OFFSET;
+extern const char *NULL_GEOCENTRIC_TRANSLATION;
+extern const char *BALLPARK_GEOGRAPHIC_OFFSET;
+extern const char *BALLPARK_VERTICAL_TRANSFORMATION;
+extern const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT;
+
+extern const std::string AXIS_ORDER_CHANGE_2D_NAME;
+extern const std::string AXIS_ORDER_CHANGE_3D_NAME;
+
+OperationParameterNNPtr createOpParamNameEPSGCode(int code);
+
+util::PropertyMap createMethodMapNameEPSGCode(int code);
+
+util::PropertyMap createMapNameEPSGCode(const std::string &name, int code);
+
+util::PropertyMap createMapNameEPSGCode(const char *name, int code);
+
+util::PropertyMap &addDomains(util::PropertyMap &map,
+ const common::ObjectUsage *obj);
+
+std::string buildOpName(const char *opType, const crs::CRSPtr &source,
+ const crs::CRSPtr &target);
+
+void addModifiedIdentifier(util::PropertyMap &map,
+ const common::IdentifiedObject *obj, bool inverse,
+ bool derivedFrom);
+util::PropertyMap
+createPropertiesForInverse(const OperationMethodNNPtr &method);
+
+util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op,
+ bool derivedFrom,
+ bool approximateInversion);
+
+util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties,
+ const std::string &defaultName);
+
+bool areEquivalentParameters(const std::string &a, const std::string &b);
+
+bool isTimeDependent(const std::string &methodName);
+
+std::string computeConcatenatedName(
+ const std::vector<CoordinateOperationNNPtr> &flattenOps);
+
+metadata::ExtentPtr getExtent(const std::vector<CoordinateOperationNNPtr> &ops,
+ bool conversionExtentIsWorld,
+ bool &emptyIntersection);
+
+metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op,
+ bool conversionExtentIsWorld,
+ bool &emptyIntersection);
+
+const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs);
+
+const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs,
+ bool &approxOut);
+
+double getAccuracy(const CoordinateOperationNNPtr &op);
+
+double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops);
+
+void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co,
+ io::WKTFormatter *formatter);
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // OPUTILS_HPP
diff --git a/include/proj/internal/coordinateoperation_constants.hpp b/src/iso19111/operation/parammappings.cpp
index 0ed3a027..de5f4c7e 100644
--- a/include/proj/internal/coordinateoperation_constants.hpp
+++ b/src/iso19111/operation/parammappings.cpp
@@ -26,36 +26,31 @@
* DEALINGS IN THE SOFTWARE.
****************************************************************************/
-#ifndef FROM_COORDINATE_OPERATION_CPP
-#error This file should only be included from coordinateoperation.cpp
-#endif
+#include "parammappings.hpp"
+#include "oputils.hpp"
+#include "proj_constants.h"
-#ifndef COORDINATEOPERATION_CONSTANTS_HH_INCLUDED
-#define COORDINATEOPERATION_CONSTANTS_HH_INCLUDED
+#include "proj/internal/internal.hpp"
-#include "coordinateoperation_internal.hpp"
-#include "proj_constants.h"
+NS_PROJ_START
-//! @cond Doxygen_Suppress
-// ---------------------------------------------------------------------------
+using namespace internal;
-// anonymous namespace
-namespace {
+namespace operation {
-using namespace ::NS_PROJ;
-using namespace ::NS_PROJ::operation;
+//! @cond Doxygen_Suppress
-static const char *WKT1_LATITUDE_OF_ORIGIN = "latitude_of_origin";
-static const char *WKT1_CENTRAL_MERIDIAN = "central_meridian";
-static const char *WKT1_SCALE_FACTOR = "scale_factor";
-static const char *WKT1_FALSE_EASTING = "false_easting";
-static const char *WKT1_FALSE_NORTHING = "false_northing";
-static const char *WKT1_STANDARD_PARALLEL_1 = "standard_parallel_1";
-static const char *WKT1_STANDARD_PARALLEL_2 = "standard_parallel_2";
-static const char *WKT1_LATITUDE_OF_CENTER = "latitude_of_center";
-static const char *WKT1_LONGITUDE_OF_CENTER = "longitude_of_center";
-static const char *WKT1_AZIMUTH = "azimuth";
-static const char *WKT1_RECTIFIED_GRID_ANGLE = "rectified_grid_angle";
+const char *WKT1_LATITUDE_OF_ORIGIN = "latitude_of_origin";
+const char *WKT1_CENTRAL_MERIDIAN = "central_meridian";
+const char *WKT1_SCALE_FACTOR = "scale_factor";
+const char *WKT1_FALSE_EASTING = "false_easting";
+const char *WKT1_FALSE_NORTHING = "false_northing";
+const char *WKT1_STANDARD_PARALLEL_1 = "standard_parallel_1";
+const char *WKT1_STANDARD_PARALLEL_2 = "standard_parallel_2";
+const char *WKT1_LATITUDE_OF_CENTER = "latitude_of_center";
+const char *WKT1_LONGITUDE_OF_CENTER = "longitude_of_center";
+const char *WKT1_AZIMUTH = "azimuth";
+const char *WKT1_RECTIFIED_GRID_ANGLE = "rectified_grid_angle";
static const char *lat_0 = "lat_0";
static const char *lat_1 = "lat_1";
@@ -73,7 +68,9 @@ static const char *x_0 = "x_0";
static const char *y_0 = "y_0";
static const char *h = "h";
-static const ParamMapping paramLatitudeNatOrigin = {
+// ---------------------------------------------------------------------------
+
+const ParamMapping paramLatitudeNatOrigin = {
EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_ORIGIN,
common::UnitOfMeasure::Type::ANGULAR, lat_0};
@@ -874,13 +871,16 @@ static const MethodMapping projectionMethodMappings[] = {
paramsGeographicTopocentric},
};
+const MethodMapping *getProjectionMethodMappings(size_t &nElts) {
+ nElts =
+ sizeof(projectionMethodMappings) / sizeof(projectionMethodMappings[0]);
+ return projectionMethodMappings;
+}
+
#define METHOD_NAME_CODE(method) \
{ EPSG_NAME_METHOD_##method, EPSG_CODE_METHOD_##method }
-static const struct MethodNameCode {
- const char *name;
- int epsg_code;
-} methodNameCodes[] = {
+const struct MethodNameCode methodNameCodes[] = {
// Projection methods
METHOD_NAME_CODE(TRANSVERSE_MERCATOR),
METHOD_NAME_CODE(TRANSVERSE_MERCATOR_SOUTH_ORIENTATED),
@@ -949,13 +949,15 @@ static const struct MethodNameCode {
METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN),
};
+const MethodNameCode *getMethodNameCodes(size_t &nElts) {
+ nElts = sizeof(methodNameCodes) / sizeof(methodNameCodes[0]);
+ return methodNameCodes;
+}
+
#define PARAM_NAME_CODE(method) \
{ EPSG_NAME_PARAMETER_##method, EPSG_CODE_PARAMETER_##method }
-static const struct ParamNameCode {
- const char *name;
- int epsg_code;
-} paramNameCodes[] = {
+const struct ParamNameCode paramNameCodes[] = {
// Parameters of projection methods
PARAM_NAME_CODE(COLATITUDE_CONE_AXIS),
PARAM_NAME_CODE(LATITUDE_OF_NATURAL_ORIGIN),
@@ -1014,6 +1016,11 @@ static const struct ParamNameCode {
PARAM_NAME_CODE(GEOCENTRIC_TRANSLATION_FILE),
};
+const ParamNameCode *getParamNameCodes(size_t &nElts) {
+ nElts = sizeof(paramNameCodes) / sizeof(paramNameCodes[0]);
+ return paramNameCodes;
+}
+
static const ParamMapping paramUnitConversionScalar = {
EPSG_NAME_PARAMETER_UNIT_CONVERSION_SCALAR,
EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR, nullptr,
@@ -1402,11 +1409,143 @@ static const MethodMapping otherMethodMappings[] = {
nullptr, nullptr, paramsVERTCON},
};
-// end of anonymous namespace
-} // namespace
+const MethodMapping *getOtherMethodMappings(size_t &nElts) {
+ nElts = sizeof(otherMethodMappings) / sizeof(otherMethodMappings[0]);
+ return otherMethodMappings;
+}
+
+// ---------------------------------------------------------------------------
+
+PROJ_NO_INLINE const MethodMapping *getMapping(int epsg_code) noexcept {
+ for (const auto &mapping : projectionMethodMappings) {
+ if (mapping.epsg_code == epsg_code) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+const MethodMapping *getMapping(const OperationMethod *method) noexcept {
+ const std::string &name(method->nameStr());
+ const int epsg_code = method->getEPSGCode();
+ for (const auto &mapping : projectionMethodMappings) {
+ if ((epsg_code != 0 && mapping.epsg_code == epsg_code) ||
+ metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ name.c_str())) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept {
+ // Unusual for a WKT1 projection name, but mentioned in OGC 12-063r5 C.4.2
+ if (ci_starts_with(wkt1_name, "UTM zone")) {
+ return getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
+ }
+
+ for (const auto &mapping : projectionMethodMappings) {
+ if (mapping.wkt1_name && metadata::Identifier::isEquivalentName(
+ mapping.wkt1_name, wkt1_name.c_str())) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+const MethodMapping *getMapping(const char *wkt2_name) noexcept {
+ for (const auto &mapping : projectionMethodMappings) {
+ if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ wkt2_name)) {
+ return &mapping;
+ }
+ }
+ for (const auto &mapping : otherMethodMappings) {
+ if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ wkt2_name)) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
// ---------------------------------------------------------------------------
+std::vector<const MethodMapping *>
+getMappingsFromPROJName(const std::string &projName) {
+ std::vector<const MethodMapping *> res;
+ for (const auto &mapping : projectionMethodMappings) {
+ if (mapping.proj_name_main && projName == mapping.proj_name_main) {
+ res.push_back(&mapping);
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+const ParamMapping *getMapping(const MethodMapping *mapping,
+ const OperationParameterNNPtr &param) {
+ if (mapping->params == nullptr) {
+ return nullptr;
+ }
+
+ // First try with id
+ const int epsg_code = param->getEPSGCode();
+ if (epsg_code) {
+ for (int i = 0; mapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = mapping->params[i];
+ if (paramMapping->epsg_code == epsg_code) {
+ return paramMapping;
+ }
+ }
+ }
+
+ // then equivalent name
+ const std::string &name = param->nameStr();
+ for (int i = 0; mapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = mapping->params[i];
+ if (metadata::Identifier::isEquivalentName(paramMapping->wkt2_name,
+ name.c_str())) {
+ return paramMapping;
+ }
+ }
+
+ // and finally different name, but equivalent parameter
+ for (int i = 0; mapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = mapping->params[i];
+ if (areEquivalentParameters(paramMapping->wkt2_name, name)) {
+ return paramMapping;
+ }
+ }
+
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping,
+ const std::string &wkt1_name) {
+ for (int i = 0; mapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = mapping->params[i];
+ if (paramMapping->wkt1_name &&
+ (metadata::Identifier::isEquivalentName(paramMapping->wkt1_name,
+ wkt1_name.c_str()) ||
+ areEquivalentParameters(paramMapping->wkt1_name, wkt1_name))) {
+ return paramMapping;
+ }
+ }
+ return nullptr;
+}
+
//! @endcond
-#endif // COORDINATEOPERATION_CONSTANTS_HH_INCLUDED
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/src/iso19111/operation/parammappings.hpp b/src/iso19111/operation/parammappings.hpp
new file mode 100644
index 00000000..05fe8a2d
--- /dev/null
+++ b/src/iso19111/operation/parammappings.hpp
@@ -0,0 +1,114 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef PARAMMAPPINGS_HPP
+#define PARAMMAPPINGS_HPP
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/util.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+extern const char *WKT1_LATITUDE_OF_ORIGIN;
+extern const char *WKT1_CENTRAL_MERIDIAN;
+extern const char *WKT1_SCALE_FACTOR;
+extern const char *WKT1_FALSE_EASTING;
+extern const char *WKT1_FALSE_NORTHING;
+extern const char *WKT1_STANDARD_PARALLEL_1;
+extern const char *WKT1_STANDARD_PARALLEL_2;
+extern const char *WKT1_LATITUDE_OF_CENTER;
+extern const char *WKT1_LONGITUDE_OF_CENTER;
+extern const char *WKT1_AZIMUTH;
+extern const char *WKT1_RECTIFIED_GRID_ANGLE;
+
+struct ParamMapping {
+ const char *wkt2_name;
+ const int epsg_code;
+ const char *wkt1_name;
+ const common::UnitOfMeasure::Type unit_type;
+ const char *proj_name;
+};
+
+struct MethodMapping {
+ const char *wkt2_name;
+ const int epsg_code;
+ const char *wkt1_name;
+ const char *proj_name_main;
+ const char *proj_name_aux;
+ const ParamMapping *const *params;
+};
+
+extern const ParamMapping paramLatitudeNatOrigin;
+
+const MethodMapping *getProjectionMethodMappings(size_t &nElts);
+const MethodMapping *getOtherMethodMappings(size_t &nElts);
+
+struct MethodNameCode {
+ const char *name;
+ int epsg_code;
+};
+
+const MethodNameCode *getMethodNameCodes(size_t &nElts);
+
+struct ParamNameCode {
+ const char *name;
+ int epsg_code;
+};
+
+const ParamNameCode *getParamNameCodes(size_t &nElts);
+
+const MethodMapping *getMapping(int epsg_code) noexcept;
+const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept;
+const MethodMapping *getMapping(const char *wkt2_name) noexcept;
+const MethodMapping *getMapping(const OperationMethod *method) noexcept;
+std::vector<const MethodMapping *>
+getMappingsFromPROJName(const std::string &projName);
+const ParamMapping *getMapping(const MethodMapping *mapping,
+ const OperationParameterNNPtr &param);
+const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping,
+ const std::string &wkt1_name);
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // PARAMMAPPINGS_HPP
diff --git a/src/iso19111/operation/projbasedoperation.cpp b/src/iso19111/operation/projbasedoperation.cpp
new file mode 100644
index 00000000..6e0fd109
--- /dev/null
+++ b/src/iso19111/operation/projbasedoperation.cpp
@@ -0,0 +1,316 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/crs.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
+
+#include "coordinateoperation_internal.hpp"
+#include "oputils.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+#include "proj_constants.h"
+#include "proj_json_streaming_writer.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+PROJBasedOperation::~PROJBasedOperation() = default;
+
+// ---------------------------------------------------------------------------
+
+PROJBasedOperation::PROJBasedOperation(const OperationMethodNNPtr &methodIn)
+ : SingleOperation(methodIn) {}
+
+// ---------------------------------------------------------------------------
+
+PROJBasedOperationNNPtr PROJBasedOperation::create(
+ const util::PropertyMap &properties, const std::string &PROJString,
+ const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ auto method = OperationMethod::create(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ "PROJ-based operation method: " + PROJString),
+ std::vector<GeneralOperationParameterNNPtr>{});
+ auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method);
+ op->assignSelf(op);
+ op->projString_ = PROJString;
+ if (sourceCRS && targetCRS) {
+ op->setCRSs(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), nullptr);
+ }
+ op->setProperties(
+ addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation"));
+ op->setAccuracies(accuracies);
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+PROJBasedOperationNNPtr PROJBasedOperation::create(
+ const util::PropertyMap &properties,
+ const io::IPROJStringExportableNNPtr &projExportable, bool inverse,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::CRSPtr &interpolationCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies,
+ bool hasBallparkTransformation) {
+
+ auto formatter = io::PROJStringFormatter::create();
+ if (inverse) {
+ formatter->startInversion();
+ }
+ projExportable->_exportToPROJString(formatter.get());
+ if (inverse) {
+ formatter->stopInversion();
+ }
+ auto projString = formatter->toString();
+
+ auto method = OperationMethod::create(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ "PROJ-based operation method (approximate): " +
+ projString),
+ std::vector<GeneralOperationParameterNNPtr>{});
+ auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method);
+ op->assignSelf(op);
+ op->projString_ = projString;
+ op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
+ op->setProperties(
+ addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation"));
+ op->setAccuracies(accuracies);
+ op->projStringExportable_ = projExportable.as_nullable();
+ op->inverse_ = inverse;
+ op->setHasBallparkTransformation(hasBallparkTransformation);
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr PROJBasedOperation::inverse() const {
+
+ if (projStringExportable_ && sourceCRS() && targetCRS()) {
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ PROJBasedOperation::create(
+ createPropertiesForInverse(this, false, false),
+ NN_NO_CHECK(projStringExportable_), !inverse_,
+ NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()),
+ interpolationCRS(), coordinateOperationAccuracies(),
+ hasBallparkTransformation()));
+ }
+
+ auto formatter = io::PROJStringFormatter::create();
+ formatter->startInversion();
+ try {
+ formatter->ingestPROJString(projString_);
+ } catch (const io::ParsingException &e) {
+ throw util::UnsupportedOperationException(
+ std::string("PROJBasedOperation::inverse() failed: ") + e.what());
+ }
+ formatter->stopInversion();
+
+ auto op = PROJBasedOperation::create(
+ createPropertiesForInverse(this, false, false), formatter->toString(),
+ targetCRS(), sourceCRS(), coordinateOperationAccuracies());
+ if (sourceCRS() && targetCRS()) {
+ op->setCRSs(NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()),
+ interpolationCRS());
+ }
+ op->setHasBallparkTransformation(hasBallparkTransformation());
+ return util::nn_static_pointer_cast<CoordinateOperation>(op);
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJBasedOperation::_exportToWKT(io::WKTFormatter *formatter) const {
+
+ if (sourceCRS() && targetCRS()) {
+ exportTransformationToWKT(formatter);
+ return;
+ }
+
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (!isWKT2) {
+ throw io::FormattingException(
+ "PROJBasedOperation can only be exported to WKT2");
+ }
+
+ formatter->startNode(io::WKTConstants::CONVERSION, false);
+ formatter->addQuotedString(nameStr());
+ method()->_exportToWKT(formatter);
+
+ for (const auto &paramValue : parameterValues()) {
+ paramValue->_exportToWKT(formatter);
+ }
+ formatter->endNode();
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJBasedOperation::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext(
+ (sourceCRS() && targetCRS()) ? "Transformation" : "Conversion",
+ !identifiers().empty()));
+
+ writer->AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer->Add("unnamed");
+ } else {
+ writer->Add(l_name);
+ }
+
+ if (sourceCRS() && targetCRS()) {
+ writer->AddObjKey("source_crs");
+ formatter->setAllowIDInImmediateChild();
+ sourceCRS()->_exportToJSON(formatter);
+
+ writer->AddObjKey("target_crs");
+ formatter->setAllowIDInImmediateChild();
+ targetCRS()->_exportToJSON(formatter);
+ }
+
+ writer->AddObjKey("method");
+ formatter->setOmitTypeInImmediateChild();
+ formatter->setAllowIDInImmediateChild();
+ method()->_exportToJSON(formatter);
+
+ const auto &l_parameterValues = parameterValues();
+ if (!l_parameterValues.empty()) {
+ writer->AddObjKey("parameters");
+ {
+ auto parametersContext(writer->MakeArrayContext(false));
+ for (const auto &genOpParamvalue : l_parameterValues) {
+ formatter->setAllowIDInImmediateChild();
+ formatter->setOmitTypeInImmediateChild();
+ genOpParamvalue->_exportToJSON(formatter);
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJBasedOperation::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const {
+ if (projStringExportable_) {
+ if (inverse_) {
+ formatter->startInversion();
+ }
+ projStringExportable_->_exportToPROJString(formatter);
+ if (inverse_) {
+ formatter->stopInversion();
+ }
+ return;
+ }
+
+ try {
+ formatter->ingestPROJString(projString_);
+ } catch (const io::ParsingException &e) {
+ throw io::FormattingException(
+ std::string("PROJBasedOperation::exportToPROJString() failed: ") +
+ e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr PROJBasedOperation::_shallowClone() const {
+ auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(*this);
+ op->assignSelf(op);
+ op->setCRSs(this, false);
+ return util::nn_static_pointer_cast<CoordinateOperation>(op);
+}
+
+// ---------------------------------------------------------------------------
+
+std::set<GridDescription>
+PROJBasedOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
+ std::set<GridDescription> res;
+
+ try {
+ auto formatterOut = io::PROJStringFormatter::create();
+ auto formatter = io::PROJStringFormatter::create();
+ formatter->ingestPROJString(exportToPROJString(formatterOut.get()));
+ const auto usedGridNames = formatter->getUsedGridNames();
+ for (const auto &shortName : usedGridNames) {
+ GridDescription desc;
+ desc.shortName = shortName;
+ if (databaseContext) {
+ databaseContext->lookForGridInfo(
+ desc.shortName, considerKnownGridsAsAvailable,
+ desc.fullName, desc.packageName, desc.url,
+ desc.directDownload, desc.openLicense, desc.available);
+ }
+ res.insert(desc);
+ }
+ } catch (const io::ParsingException &) {
+ }
+
+ return res;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+
+NS_PROJ_END
diff --git a/src/iso19111/operation/singleoperation.cpp b/src/iso19111/operation/singleoperation.cpp
new file mode 100644
index 00000000..0cd7b57a
--- /dev/null
+++ b/src/iso19111/operation/singleoperation.cpp
@@ -0,0 +1,2218 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/crs.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
+
+#include "coordinateoperation_internal.hpp"
+#include "coordinateoperation_private.hpp"
+#include "operationmethod_private.hpp"
+#include "oputils.hpp"
+#include "parammappings.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+#include "proj_constants.h"
+#include "proj_json_streaming_writer.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection(
+ const std::string &message)
+ : InvalidOperation(message) {}
+
+InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection(
+ const InvalidOperationEmptyIntersection &) = default;
+
+InvalidOperationEmptyIntersection::~InvalidOperationEmptyIntersection() =
+ default;
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+GridDescription::GridDescription()
+ : shortName{}, fullName{}, packageName{}, url{}, directDownload(false),
+ openLicense(false), available(false) {}
+
+GridDescription::~GridDescription() = default;
+
+GridDescription::GridDescription(const GridDescription &) = default;
+
+GridDescription::GridDescription(GridDescription &&other) noexcept
+ : shortName(std::move(other.shortName)),
+ fullName(std::move(other.fullName)),
+ packageName(std::move(other.packageName)),
+ url(std::move(other.url)),
+ directDownload(other.directDownload),
+ openLicense(other.openLicense),
+ available(other.available) {}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperation::CoordinateOperation()
+ : d(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperation::CoordinateOperation(const CoordinateOperation &other)
+ : ObjectUsage(other), d(internal::make_unique<Private>(*other.d)) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CoordinateOperation::~CoordinateOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the version of the coordinate transformation (i.e.
+ * instantiation
+ * due to the stochastic nature of the parameters).
+ *
+ * Mandatory when describing a coordinate transformation or point motion
+ * operation, and should not be supplied for a coordinate conversion.
+ *
+ * @return version or empty.
+ */
+const util::optional<std::string> &
+CoordinateOperation::operationVersion() const {
+ return d->operationVersion_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return estimate(s) of the impact of this coordinate operation on
+ * point accuracy.
+ *
+ * Gives position error estimates for target coordinates of this coordinate
+ * operation, assuming no errors in source coordinates.
+ *
+ * @return estimate(s) or empty vector.
+ */
+const std::vector<metadata::PositionalAccuracyNNPtr> &
+CoordinateOperation::coordinateOperationAccuracies() const {
+ return d->coordinateOperationAccuracies_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the source CRS of this coordinate operation.
+ *
+ * This should not be null, expect for of a derivingConversion of a DerivedCRS
+ * when the owning DerivedCRS has been destroyed.
+ *
+ * @return source CRS, or null.
+ */
+const crs::CRSPtr CoordinateOperation::sourceCRS() const {
+ return d->sourceCRSWeak_.lock();
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the target CRS of this coordinate operation.
+ *
+ * This should not be null, expect for of a derivingConversion of a DerivedCRS
+ * when the owning DerivedCRS has been destroyed.
+ *
+ * @return target CRS, or null.
+ */
+const crs::CRSPtr CoordinateOperation::targetCRS() const {
+ return d->targetCRSWeak_.lock();
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the interpolation CRS of this coordinate operation.
+ *
+ * @return interpolation CRS, or null.
+ */
+const crs::CRSPtr &CoordinateOperation::interpolationCRS() const {
+ return d->interpolationCRS_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the source epoch of coordinates.
+ *
+ * @return source epoch of coordinates, or empty.
+ */
+const util::optional<common::DataEpoch> &
+CoordinateOperation::sourceCoordinateEpoch() const {
+ return d->sourceCoordinateEpoch_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the target epoch of coordinates.
+ *
+ * @return target epoch of coordinates, or empty.
+ */
+const util::optional<common::DataEpoch> &
+CoordinateOperation::targetCoordinateEpoch() const {
+ return d->targetCoordinateEpoch_;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setWeakSourceTargetCRS(
+ std::weak_ptr<crs::CRS> sourceCRSIn, std::weak_ptr<crs::CRS> targetCRSIn) {
+ d->sourceCRSWeak_ = sourceCRSIn;
+ d->targetCRSWeak_ = targetCRSIn;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setCRSs(const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn,
+ const crs::CRSPtr &interpolationCRSIn) {
+ d->strongRef_ =
+ internal::make_unique<Private::CRSStrongRef>(sourceCRSIn, targetCRSIn);
+ d->sourceCRSWeak_ = sourceCRSIn.as_nullable();
+ d->targetCRSWeak_ = targetCRSIn.as_nullable();
+ d->interpolationCRS_ = interpolationCRSIn;
+}
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setCRSs(const CoordinateOperation *in,
+ bool inverseSourceTarget) {
+ auto l_sourceCRS = in->sourceCRS();
+ auto l_targetCRS = in->targetCRS();
+ if (l_sourceCRS && l_targetCRS) {
+ auto nn_sourceCRS = NN_NO_CHECK(l_sourceCRS);
+ auto nn_targetCRS = NN_NO_CHECK(l_targetCRS);
+ if (inverseSourceTarget) {
+ setCRSs(nn_targetCRS, nn_sourceCRS, in->interpolationCRS());
+ } else {
+ setCRSs(nn_sourceCRS, nn_targetCRS, in->interpolationCRS());
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setAccuracies(
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ d->coordinateOperationAccuracies_ = accuracies;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether a coordinate operation can be instantiated as
+ * a PROJ pipeline, checking in particular that referenced grids are
+ * available.
+ */
+bool CoordinateOperation::isPROJInstantiable(
+ const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
+ try {
+ exportToPROJString(io::PROJStringFormatter::create().get());
+ } catch (const std::exception &) {
+ return false;
+ }
+ for (const auto &gridDesc :
+ gridsNeeded(databaseContext, considerKnownGridsAsAvailable)) {
+ if (!gridDesc.available) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether a coordinate operation has a "ballpark"
+ * transformation,
+ * that is a very approximate one, due to lack of more accurate transformations.
+ *
+ * Typically a null geographic offset between two horizontal datum, or a
+ * null vertical offset (or limited to unit changes) between two vertical
+ * datum. Errors of several tens to one hundred meters might be expected,
+ * compared to more accurate transformations.
+ */
+bool CoordinateOperation::hasBallparkTransformation() const {
+ return d->hasBallparkTransformation_;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setHasBallparkTransformation(bool b) {
+ d->hasBallparkTransformation_ = b;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setProperties(
+ const util::PropertyMap &properties) // throw(InvalidValueTypeException)
+{
+ ObjectUsage::setProperties(properties);
+ properties.getStringValue(OPERATION_VERSION_KEY, d->operationVersion_);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a variation of the current coordinate operation whose axis
+ * order is the one expected for visualization purposes.
+ */
+CoordinateOperationNNPtr
+CoordinateOperation::normalizeForVisualization() const {
+ auto l_sourceCRS = sourceCRS();
+ auto l_targetCRS = targetCRS();
+ if (!l_sourceCRS || !l_targetCRS) {
+ throw util::UnsupportedOperationException(
+ "Cannot retrieve source or target CRS");
+ }
+ const bool swapSource =
+ l_sourceCRS->mustAxisOrderBeSwitchedForVisualization();
+ const bool swapTarget =
+ l_targetCRS->mustAxisOrderBeSwitchedForVisualization();
+ auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast<CoordinateOperation>(
+ shared_from_this().as_nullable()));
+ if (!swapSource && !swapTarget) {
+ return l_this;
+ }
+ std::vector<CoordinateOperationNNPtr> subOps;
+ if (swapSource) {
+ auto op = Conversion::createAxisOrderReversal(false);
+ op->setCRSs(l_sourceCRS->normalizeForVisualization(),
+ NN_NO_CHECK(l_sourceCRS), nullptr);
+ subOps.emplace_back(op);
+ }
+ subOps.emplace_back(l_this);
+ if (swapTarget) {
+ auto op = Conversion::createAxisOrderReversal(false);
+ op->setCRSs(NN_NO_CHECK(l_targetCRS),
+ l_targetCRS->normalizeForVisualization(), nullptr);
+ subOps.emplace_back(op);
+ }
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ ConcatenatedOperation::createComputeMetadata(subOps, true));
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CoordinateOperationNNPtr CoordinateOperation::shallowClone() const {
+ return _shallowClone();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+OperationMethod::OperationMethod() : d(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+OperationMethod::OperationMethod(const OperationMethod &other)
+ : IdentifiedObject(other), d(internal::make_unique<Private>(*other.d)) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+OperationMethod::~OperationMethod() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the formula(s) or procedure used by this coordinate operation
+ * method.
+ *
+ * This may be a reference to a publication (in which case use
+ * formulaCitation()).
+ *
+ * Note that the operation method may not be analytic, in which case this
+ * attribute references or contains the procedure, not an analytic formula.
+ *
+ * @return the formula, or empty.
+ */
+const util::optional<std::string> &OperationMethod::formula() PROJ_PURE_DEFN {
+ return d->formula_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a reference to a publication giving the formula(s) or
+ * procedure
+ * used by the coordinate operation method.
+ *
+ * @return the formula citation, or empty.
+ */
+const util::optional<metadata::Citation> &
+OperationMethod::formulaCitation() PROJ_PURE_DEFN {
+ return d->formulaCitation_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameters of this operation method.
+ *
+ * @return the parameters.
+ */
+const std::vector<GeneralOperationParameterNNPtr> &
+OperationMethod::parameters() PROJ_PURE_DEFN {
+ return d->parameters_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a operation method from a vector of
+ * GeneralOperationParameter.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @param parameters Vector of GeneralOperationParameterNNPtr.
+ * @return a new OperationMethod.
+ */
+OperationMethodNNPtr OperationMethod::create(
+ const util::PropertyMap &properties,
+ const std::vector<GeneralOperationParameterNNPtr> &parameters) {
+ OperationMethodNNPtr method(
+ OperationMethod::nn_make_shared<OperationMethod>());
+ method->assignSelf(method);
+ method->setProperties(properties);
+ method->d->parameters_ = parameters;
+ properties.getStringValue("proj_method", method->d->projMethodOverride_);
+ return method;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a operation method from a vector of OperationParameter.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @param parameters Vector of OperationParameterNNPtr.
+ * @return a new OperationMethod.
+ */
+OperationMethodNNPtr OperationMethod::create(
+ const util::PropertyMap &properties,
+ const std::vector<OperationParameterNNPtr> &parameters) {
+ std::vector<GeneralOperationParameterNNPtr> parametersGeneral;
+ parametersGeneral.reserve(parameters.size());
+ for (const auto &p : parameters) {
+ parametersGeneral.push_back(p);
+ }
+ return create(properties, parametersGeneral);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the EPSG code, either directly, or through the name
+ * @return code, or 0 if not found
+ */
+int OperationMethod::getEPSGCode() PROJ_PURE_DEFN {
+ int epsg_code = IdentifiedObject::getEPSGCode();
+ if (epsg_code == 0) {
+ auto l_name = nameStr();
+ if (ends_with(l_name, " (3D)")) {
+ l_name.resize(l_name.size() - strlen(" (3D)"));
+ }
+ size_t nMethodNameCodes = 0;
+ const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes);
+ for (size_t i = 0; i < nMethodNameCodes; ++i) {
+ const auto &tuple = methodNameCodes[i];
+ if (metadata::Identifier::isEquivalentName(l_name.c_str(),
+ tuple.name)) {
+ return tuple.epsg_code;
+ }
+ }
+ }
+ return epsg_code;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ formatter->startNode(isWKT2 ? io::WKTConstants::METHOD
+ : io::WKTConstants::PROJECTION,
+ !identifiers().empty());
+ std::string l_name(nameStr());
+ if (!isWKT2) {
+ const MethodMapping *mapping = getMapping(this);
+ if (mapping == nullptr) {
+ l_name = replaceAll(l_name, " ", "_");
+ } else {
+ if (l_name ==
+ PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) {
+ l_name = "Geostationary_Satellite";
+ } else {
+ if (mapping->wkt1_name == nullptr) {
+ throw io::FormattingException(
+ std::string("Unsupported conversion method: ") +
+ mapping->wkt2_name);
+ }
+ l_name = mapping->wkt1_name;
+ }
+ }
+ }
+ formatter->addQuotedString(l_name);
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+ formatter->endNode();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void OperationMethod::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext("OperationMethod",
+ !identifiers().empty()));
+
+ writer->AddObjKey("name");
+ writer->Add(nameStr());
+
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool OperationMethod::_isEquivalentTo(
+ const util::IComparable *other, util::IComparable::Criterion criterion,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto otherOM = dynamic_cast<const OperationMethod *>(other);
+ if (otherOM == nullptr ||
+ !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) {
+ return false;
+ }
+ // TODO test formula and formulaCitation
+ const auto &params = parameters();
+ const auto &otherParams = otherOM->parameters();
+ const auto paramsSize = params.size();
+ if (paramsSize != otherParams.size()) {
+ return false;
+ }
+ if (criterion == util::IComparable::Criterion::STRICT) {
+ for (size_t i = 0; i < paramsSize; i++) {
+ if (!params[i]->_isEquivalentTo(otherParams[i].get(), criterion,
+ dbContext)) {
+ return false;
+ }
+ }
+ } else {
+ std::vector<bool> candidateIndices(paramsSize, true);
+ for (size_t i = 0; i < paramsSize; i++) {
+ bool found = false;
+ for (size_t j = 0; j < paramsSize; j++) {
+ if (candidateIndices[j] &&
+ params[i]->_isEquivalentTo(otherParams[j].get(), criterion,
+ dbContext)) {
+ candidateIndices[j] = false;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct GeneralParameterValue::Private {};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+GeneralParameterValue::GeneralParameterValue() : d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+GeneralParameterValue::GeneralParameterValue(const GeneralParameterValue &)
+ : d(nullptr) {}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+GeneralParameterValue::~GeneralParameterValue() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct OperationParameterValue::Private {
+ OperationParameterNNPtr parameter;
+ ParameterValueNNPtr parameterValue;
+
+ Private(const OperationParameterNNPtr &parameterIn,
+ const ParameterValueNNPtr &valueIn)
+ : parameter(parameterIn), parameterValue(valueIn) {}
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+OperationParameterValue::OperationParameterValue(
+ const OperationParameterValue &other)
+ : GeneralParameterValue(other),
+ d(internal::make_unique<Private>(*other.d)) {}
+
+// ---------------------------------------------------------------------------
+
+OperationParameterValue::OperationParameterValue(
+ const OperationParameterNNPtr &parameterIn,
+ const ParameterValueNNPtr &valueIn)
+ : GeneralParameterValue(),
+ d(internal::make_unique<Private>(parameterIn, valueIn)) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a OperationParameterValue.
+ *
+ * @param parameterIn Parameter (definition).
+ * @param valueIn Parameter value.
+ * @return a new OperationParameterValue.
+ */
+OperationParameterValueNNPtr
+OperationParameterValue::create(const OperationParameterNNPtr &parameterIn,
+ const ParameterValueNNPtr &valueIn) {
+ return OperationParameterValue::nn_make_shared<OperationParameterValue>(
+ parameterIn, valueIn);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+OperationParameterValue::~OperationParameterValue() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter (definition)
+ *
+ * @return the parameter (definition).
+ */
+const OperationParameterNNPtr &
+OperationParameterValue::parameter() PROJ_PURE_DEFN {
+ return d->parameter;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter value.
+ *
+ * @return the parameter value.
+ */
+const ParameterValueNNPtr &
+OperationParameterValue::parameterValue() PROJ_PURE_DEFN {
+ return d->parameterValue;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void OperationParameterValue::_exportToWKT(
+ // cppcheck-suppress passedByValue
+ io::WKTFormatter *formatter) const {
+ _exportToWKT(formatter, nullptr);
+}
+
+void OperationParameterValue::_exportToWKT(io::WKTFormatter *formatter,
+ const MethodMapping *mapping) const {
+ const ParamMapping *paramMapping =
+ mapping ? getMapping(mapping, d->parameter) : nullptr;
+ if (paramMapping && paramMapping->wkt1_name == nullptr) {
+ return;
+ }
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (isWKT2 && parameterValue()->type() == ParameterValue::Type::FILENAME) {
+ formatter->startNode(io::WKTConstants::PARAMETERFILE,
+ !parameter()->identifiers().empty());
+ } else {
+ formatter->startNode(io::WKTConstants::PARAMETER,
+ !parameter()->identifiers().empty());
+ }
+ if (paramMapping) {
+ formatter->addQuotedString(paramMapping->wkt1_name);
+ } else {
+ formatter->addQuotedString(parameter()->nameStr());
+ }
+ parameterValue()->_exportToWKT(formatter);
+ if (formatter->outputId()) {
+ parameter()->formatID(formatter);
+ }
+ formatter->endNode();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void OperationParameterValue::_exportToJSON(
+ io::JSONFormatter *formatter) const {
+ auto writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext(
+ "ParameterValue", !parameter()->identifiers().empty()));
+
+ writer->AddObjKey("name");
+ writer->Add(parameter()->nameStr());
+
+ const auto &l_value(parameterValue());
+ if (l_value->type() == ParameterValue::Type::MEASURE) {
+ writer->AddObjKey("value");
+ writer->Add(l_value->value().value(), 15);
+ writer->AddObjKey("unit");
+ const auto &l_unit(l_value->value().unit());
+ if (l_unit == common::UnitOfMeasure::METRE ||
+ l_unit == common::UnitOfMeasure::DEGREE ||
+ l_unit == common::UnitOfMeasure::SCALE_UNITY) {
+ writer->Add(l_unit.name());
+ } else {
+ l_unit._exportToJSON(formatter);
+ }
+ } else if (l_value->type() == ParameterValue::Type::FILENAME) {
+ writer->AddObjKey("value");
+ writer->Add(l_value->valueFile());
+ }
+
+ if (formatter->outputId()) {
+ parameter()->formatID(formatter);
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+/** Utility method used on WKT2 import to convert from abridged transformation
+ * to "normal" transformation parameters.
+ */
+bool OperationParameterValue::convertFromAbridged(
+ const std::string &paramName, double &val,
+ const common::UnitOfMeasure *&unit, int &paramEPSGCode) {
+ if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) {
+ unit = &common::UnitOfMeasure::METRE;
+ paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION;
+ return true;
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) {
+ unit = &common::UnitOfMeasure::METRE;
+ paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION;
+ return true;
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) {
+ unit = &common::UnitOfMeasure::METRE;
+ paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION;
+ return true;
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_ROTATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_ROTATION) {
+ unit = &common::UnitOfMeasure::ARC_SECOND;
+ paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_ROTATION;
+ return true;
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_ROTATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) {
+ unit = &common::UnitOfMeasure::ARC_SECOND;
+ paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_ROTATION;
+ return true;
+
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_ROTATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) {
+ unit = &common::UnitOfMeasure::ARC_SECOND;
+ paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_ROTATION;
+ return true;
+
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_SCALE_DIFFERENCE) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) {
+ val = (val - 1.0) * 1e6;
+ unit = &common::UnitOfMeasure::PARTS_PER_MILLION;
+ paramEPSGCode = EPSG_CODE_PARAMETER_SCALE_DIFFERENCE;
+ return true;
+ }
+ return false;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool OperationParameterValue::_isEquivalentTo(
+ const util::IComparable *other, util::IComparable::Criterion criterion,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto otherOPV = dynamic_cast<const OperationParameterValue *>(other);
+ if (otherOPV == nullptr) {
+ return false;
+ }
+ if (!d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(), criterion,
+ dbContext)) {
+ return false;
+ }
+ if (criterion == util::IComparable::Criterion::STRICT) {
+ return d->parameterValue->_isEquivalentTo(
+ otherOPV->d->parameterValue.get(), criterion);
+ }
+ if (d->parameterValue->_isEquivalentTo(otherOPV->d->parameterValue.get(),
+ criterion, dbContext)) {
+ return true;
+ }
+ if (d->parameter->getEPSGCode() ==
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE ||
+ d->parameter->getEPSGCode() ==
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) {
+ if (parameterValue()->type() == ParameterValue::Type::MEASURE &&
+ otherOPV->parameterValue()->type() ==
+ ParameterValue::Type::MEASURE) {
+ const double a = std::fmod(parameterValue()->value().convertToUnit(
+ common::UnitOfMeasure::DEGREE) +
+ 360.0,
+ 360.0);
+ const double b =
+ std::fmod(otherOPV->parameterValue()->value().convertToUnit(
+ common::UnitOfMeasure::DEGREE) +
+ 360.0,
+ 360.0);
+ return std::fabs(a - b) <= 1e-10 * std::fabs(a);
+ }
+ }
+ return false;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct GeneralOperationParameter::Private {};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+GeneralOperationParameter::GeneralOperationParameter() : d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+GeneralOperationParameter::GeneralOperationParameter(
+ const GeneralOperationParameter &other)
+ : IdentifiedObject(other), d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+GeneralOperationParameter::~GeneralOperationParameter() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct OperationParameter::Private {};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+OperationParameter::OperationParameter() : d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+OperationParameter::OperationParameter(const OperationParameter &other)
+ : GeneralOperationParameter(other), d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+OperationParameter::~OperationParameter() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a OperationParameter.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @return a new OperationParameter.
+ */
+OperationParameterNNPtr
+OperationParameter::create(const util::PropertyMap &properties) {
+ OperationParameterNNPtr op(
+ OperationParameter::nn_make_shared<OperationParameter>());
+ op->assignSelf(op);
+ op->setProperties(properties);
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool OperationParameter::_isEquivalentTo(
+ const util::IComparable *other, util::IComparable::Criterion criterion,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto otherOP = dynamic_cast<const OperationParameter *>(other);
+ if (otherOP == nullptr) {
+ return false;
+ }
+ if (criterion == util::IComparable::Criterion::STRICT) {
+ return IdentifiedObject::_isEquivalentTo(other, criterion, dbContext);
+ }
+ if (IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) {
+ return true;
+ }
+ auto l_epsgCode = getEPSGCode();
+ return l_epsgCode != 0 && l_epsgCode == otherOP->getEPSGCode();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+void OperationParameter::_exportToWKT(io::WKTFormatter *) const {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the name of a parameter designed by its EPSG code
+ * @return name, or nullptr if not found
+ */
+const char *OperationParameter::getNameForEPSGCode(int epsg_code) noexcept {
+ size_t nParamNameCodes = 0;
+ const auto paramNameCodes = getParamNameCodes(nParamNameCodes);
+ for (size_t i = 0; i < nParamNameCodes; ++i) {
+ const auto &tuple = paramNameCodes[i];
+ if (tuple.epsg_code == epsg_code) {
+ return tuple.name;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the EPSG code, either directly, or through the name
+ * @return code, or 0 if not found
+ */
+int OperationParameter::getEPSGCode() PROJ_PURE_DEFN {
+ int epsg_code = IdentifiedObject::getEPSGCode();
+ if (epsg_code == 0) {
+ const auto &l_name = nameStr();
+ size_t nParamNameCodes = 0;
+ const auto paramNameCodes = getParamNameCodes(nParamNameCodes);
+ for (size_t i = 0; i < nParamNameCodes; ++i) {
+ const auto &tuple = paramNameCodes[i];
+ if (metadata::Identifier::isEquivalentName(l_name.c_str(),
+ tuple.name)) {
+ return tuple.epsg_code;
+ }
+ }
+ if (metadata::Identifier::isEquivalentName(l_name.c_str(),
+ "Latitude of origin")) {
+ return EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
+ }
+ if (metadata::Identifier::isEquivalentName(l_name.c_str(),
+ "Scale factor")) {
+ return EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN;
+ }
+ }
+ return epsg_code;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct SingleOperation::Private {
+ std::vector<GeneralParameterValueNNPtr> parameterValues_{};
+ OperationMethodNNPtr method_;
+
+ explicit Private(const OperationMethodNNPtr &methodIn)
+ : method_(methodIn) {}
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn)
+ : d(internal::make_unique<Private>(methodIn)) {}
+
+// ---------------------------------------------------------------------------
+
+SingleOperation::SingleOperation(const SingleOperation &other)
+ :
+#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
+ CoordinateOperation(other),
+#endif
+ d(internal::make_unique<Private>(*other.d)) {
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+SingleOperation::~SingleOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter values.
+ *
+ * @return the parameter values.
+ */
+const std::vector<GeneralParameterValueNNPtr> &
+SingleOperation::parameterValues() PROJ_PURE_DEFN {
+ return d->parameterValues_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the operation method associated to the operation.
+ *
+ * @return the operation method.
+ */
+const OperationMethodNNPtr &SingleOperation::method() PROJ_PURE_DEFN {
+ return d->method_;
+}
+
+// ---------------------------------------------------------------------------
+
+void SingleOperation::setParameterValues(
+ const std::vector<GeneralParameterValueNNPtr> &values) {
+ d->parameterValues_ = values;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const ParameterValuePtr nullParameterValue;
+//! @endcond
+
+/** \brief Return the parameter value corresponding to a parameter name or
+ * EPSG code
+ *
+ * @param paramName the parameter name (or empty, in which case epsg_code
+ * should be non zero)
+ * @param epsg_code the parameter EPSG code (possibly zero)
+ * @return the value, or nullptr if not found.
+ */
+const ParameterValuePtr &
+SingleOperation::parameterValue(const std::string &paramName,
+ int epsg_code) const noexcept {
+ if (epsg_code) {
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if (parameter->getEPSGCode() == epsg_code) {
+ return opParamvalue->parameterValue();
+ }
+ }
+ }
+ }
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), parameter->nameStr().c_str())) {
+ return opParamvalue->parameterValue();
+ }
+ }
+ }
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if (areEquivalentParameters(paramName, parameter->nameStr())) {
+ return opParamvalue->parameterValue();
+ }
+ }
+ }
+ return nullParameterValue;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter value corresponding to a EPSG code
+ *
+ * @param epsg_code the parameter EPSG code
+ * @return the value, or nullptr if not found.
+ */
+const ParameterValuePtr &SingleOperation::parameterValue(int epsg_code) const
+ noexcept {
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if (parameter->getEPSGCode() == epsg_code) {
+ return opParamvalue->parameterValue();
+ }
+ }
+ }
+ return nullParameterValue;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter value, as a measure, corresponding to a
+ * parameter name or EPSG code
+ *
+ * @param paramName the parameter name (or empty, in which case epsg_code
+ * should be non zero)
+ * @param epsg_code the parameter EPSG code (possibly zero)
+ * @return the measure, or the empty Measure() object if not found.
+ */
+const common::Measure &
+SingleOperation::parameterValueMeasure(const std::string &paramName,
+ int epsg_code) const noexcept {
+ const auto &val = parameterValue(paramName, epsg_code);
+ if (val && val->type() == ParameterValue::Type::MEASURE) {
+ return val->value();
+ }
+ return nullMeasure;
+}
+
+/** \brief Return the parameter value, as a measure, corresponding to a
+ * EPSG code
+ *
+ * @param epsg_code the parameter EPSG code
+ * @return the measure, or the empty Measure() object if not found.
+ */
+const common::Measure &
+SingleOperation::parameterValueMeasure(int epsg_code) const noexcept {
+ const auto &val = parameterValue(epsg_code);
+ if (val && val->type() == ParameterValue::Type::MEASURE) {
+ return val->value();
+ }
+ return nullMeasure;
+}
+
+//! @cond Doxygen_Suppress
+
+double SingleOperation::parameterValueNumericAsSI(int epsg_code) const
+ noexcept {
+ const auto &val = parameterValue(epsg_code);
+ if (val && val->type() == ParameterValue::Type::MEASURE) {
+ return val->value().getSIValue();
+ }
+ return 0.0;
+}
+
+double SingleOperation::parameterValueNumeric(
+ int epsg_code, const common::UnitOfMeasure &targetUnit) const noexcept {
+ const auto &val = parameterValue(epsg_code);
+ if (val && val->type() == ParameterValue::Type::MEASURE) {
+ return val->value().convertToUnit(targetUnit);
+ }
+ return 0.0;
+}
+
+double SingleOperation::parameterValueNumeric(
+ const char *param_name, const common::UnitOfMeasure &targetUnit) const
+ noexcept {
+ const auto &val = parameterValue(param_name, 0);
+ if (val && val->type() == ParameterValue::Type::MEASURE) {
+ return val->value().convertToUnit(targetUnit);
+ }
+ return 0.0;
+}
+
+//! @endcond
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a PROJ-based single operation.
+ *
+ * \note The operation might internally be a pipeline chaining several
+ * operations.
+ * The use of the SingleOperation modeling here is mostly to be able to get
+ * the PROJ string as a parameter.
+ *
+ * @param properties Properties
+ * @param PROJString the PROJ string.
+ * @param sourceCRS source CRS (might be null).
+ * @param targetCRS target CRS (might be null).
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return the new instance
+ */
+SingleOperationNNPtr SingleOperation::createPROJBased(
+ const util::PropertyMap &properties, const std::string &PROJString,
+ const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return util::nn_static_pointer_cast<SingleOperation>(
+ PROJBasedOperation::create(properties, PROJString, sourceCRS, targetCRS,
+ accuracies));
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool SingleOperation::_isEquivalentTo(
+ const util::IComparable *other, util::IComparable::Criterion criterion,
+ const io::DatabaseContextPtr &dbContext) const {
+ return _isEquivalentTo(other, criterion, dbContext, false);
+}
+
+bool SingleOperation::_isEquivalentTo(const util::IComparable *other,
+ util::IComparable::Criterion criterion,
+ const io::DatabaseContextPtr &dbContext,
+ bool inOtherDirection) const {
+
+ auto otherSO = dynamic_cast<const SingleOperation *>(other);
+ if (otherSO == nullptr ||
+ (criterion == util::IComparable::Criterion::STRICT &&
+ !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) {
+ return false;
+ }
+
+ const int methodEPSGCode = d->method_->getEPSGCode();
+ const int otherMethodEPSGCode = otherSO->d->method_->getEPSGCode();
+
+ bool equivalentMethods =
+ (criterion == util::IComparable::Criterion::EQUIVALENT &&
+ methodEPSGCode != 0 && methodEPSGCode == otherMethodEPSGCode) ||
+ d->method_->_isEquivalentTo(otherSO->d->method_.get(), criterion,
+ dbContext);
+ if (!equivalentMethods &&
+ criterion == util::IComparable::Criterion::EQUIVALENT) {
+ if ((methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) ||
+ (otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA &&
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) ||
+ (methodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) ||
+ (otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA &&
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) ||
+ (methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) ||
+ (otherMethodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) {
+ auto geodCRS =
+ dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get());
+ auto otherGeodCRS = dynamic_cast<const crs::GeodeticCRS *>(
+ otherSO->sourceCRS().get());
+ if (geodCRS && otherGeodCRS && geodCRS->ellipsoid()->isSphere() &&
+ otherGeodCRS->ellipsoid()->isSphere()) {
+ equivalentMethods = true;
+ }
+ }
+ }
+
+ if (!equivalentMethods) {
+ if (criterion == util::IComparable::Criterion::EQUIVALENT) {
+
+ const auto isTOWGS84Transf = [](int code) {
+ return code ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
+ code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
+ code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
+ code ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
+ code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ code ==
+ EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ code ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D ||
+ code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D ||
+ code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D;
+ };
+
+ // Translation vs (PV or CF)
+ // or different PV vs CF convention
+ if (isTOWGS84Transf(methodEPSGCode) &&
+ isTOWGS84Transf(otherMethodEPSGCode)) {
+ auto transf = static_cast<const Transformation *>(this);
+ auto otherTransf = static_cast<const Transformation *>(otherSO);
+ auto params = transf->getTOWGS84Parameters();
+ auto otherParams = otherTransf->getTOWGS84Parameters();
+ assert(params.size() == 7);
+ assert(otherParams.size() == 7);
+ for (size_t i = 0; i < 7; i++) {
+ if (std::fabs(params[i] - otherParams[i]) >
+ 1e-10 * std::fabs(params[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // _1SP methods can sometimes be equivalent to _2SP ones
+ // Check it by using convertToOtherMethod()
+ if (methodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ // Convert from 2SP to 1SP as the other direction has more
+ // degree of liberties.
+ return otherSO->_isEquivalentTo(this, criterion, dbContext);
+ } else if ((methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_B) ||
+ (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_A) ||
+ (methodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) {
+ auto conv = dynamic_cast<const Conversion *>(this);
+ if (conv) {
+ auto eqConv =
+ conv->convertToOtherMethod(otherMethodEPSGCode);
+ if (eqConv) {
+ return eqConv->_isEquivalentTo(other, criterion,
+ dbContext);
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ const auto &values = d->parameterValues_;
+ const auto &otherValues = otherSO->d->parameterValues_;
+ const auto valuesSize = values.size();
+ const auto otherValuesSize = otherValues.size();
+ if (criterion == util::IComparable::Criterion::STRICT) {
+ if (valuesSize != otherValuesSize) {
+ return false;
+ }
+ for (size_t i = 0; i < valuesSize; i++) {
+ if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion,
+ dbContext)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ std::vector<bool> candidateIndices(otherValuesSize, true);
+ bool equivalent = true;
+ bool foundMissingArgs = valuesSize != otherValuesSize;
+
+ for (size_t i = 0; equivalent && i < valuesSize; i++) {
+ auto opParamvalue =
+ dynamic_cast<const OperationParameterValue *>(values[i].get());
+ if (!opParamvalue)
+ return false;
+
+ equivalent = false;
+ bool sameNameDifferentValue = false;
+ for (size_t j = 0; j < otherValuesSize; j++) {
+ if (candidateIndices[j] &&
+ values[i]->_isEquivalentTo(otherValues[j].get(), criterion,
+ dbContext)) {
+ candidateIndices[j] = false;
+ equivalent = true;
+ break;
+ } else if (candidateIndices[j]) {
+ auto otherOpParamvalue =
+ dynamic_cast<const OperationParameterValue *>(
+ otherValues[j].get());
+ if (!otherOpParamvalue)
+ return false;
+ sameNameDifferentValue =
+ opParamvalue->parameter()->_isEquivalentTo(
+ otherOpParamvalue->parameter().get(), criterion,
+ dbContext);
+ if (sameNameDifferentValue) {
+ candidateIndices[j] = false;
+ break;
+ }
+ }
+ }
+
+ if (!equivalent &&
+ methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ // For LCC_2SP, the standard parallels can be switched and
+ // this will result in the same result.
+ const int paramEPSGCode = opParamvalue->parameter()->getEPSGCode();
+ if (paramEPSGCode ==
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL ||
+ paramEPSGCode ==
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
+ auto value_1st = parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL);
+ auto value_2nd = parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL);
+ if (value_1st && value_2nd) {
+ equivalent =
+ value_1st->_isEquivalentTo(
+ otherSO
+ ->parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)
+ .get(),
+ criterion, dbContext) &&
+ value_2nd->_isEquivalentTo(
+ otherSO
+ ->parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)
+ .get(),
+ criterion, dbContext);
+ }
+ }
+ }
+
+ if (equivalent) {
+ continue;
+ }
+
+ if (sameNameDifferentValue) {
+ break;
+ }
+
+ // If there are parameters in this method not found in the other one,
+ // check that they are set to a default neutral value, that is 1
+ // for scale, and 0 otherwise.
+ foundMissingArgs = true;
+ const auto &value = opParamvalue->parameterValue();
+ if (value->type() != ParameterValue::Type::MEASURE) {
+ break;
+ }
+ if (value->value().unit().type() ==
+ common::UnitOfMeasure::Type::SCALE) {
+ equivalent = value->value().getSIValue() == 1.0;
+ } else {
+ equivalent = value->value().getSIValue() == 0.0;
+ }
+ }
+
+ // In the case the arguments don't perfectly match, try the reverse
+ // check.
+ if (equivalent && foundMissingArgs && !inOtherDirection) {
+ return otherSO->_isEquivalentTo(this, criterion, dbContext, true);
+ }
+
+ // Equivalent formulations of 2SP can have different parameters
+ // Then convert to 1SP and compare.
+ if (!equivalent &&
+ methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ auto conv = dynamic_cast<const Conversion *>(this);
+ auto otherConv = dynamic_cast<const Conversion *>(other);
+ if (conv && otherConv) {
+ auto thisAs1SP = conv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ auto otherAs1SP = otherConv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ if (thisAs1SP && otherAs1SP) {
+ equivalent = thisAs1SP->_isEquivalentTo(otherAs1SP.get(),
+ criterion, dbContext);
+ }
+ }
+ }
+ return equivalent;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+std::set<GridDescription>
+SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
+ std::set<GridDescription> res;
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &value = opParamvalue->parameterValue();
+ if (value->type() == ParameterValue::Type::FILENAME) {
+ const auto gridNames = split(value->valueFile(), ",");
+ for (const auto &gridName : gridNames) {
+ GridDescription desc;
+ desc.shortName = gridName;
+ if (databaseContext) {
+ databaseContext->lookForGridInfo(
+ desc.shortName, considerKnownGridsAsAvailable,
+ desc.fullName, desc.packageName, desc.url,
+ desc.directDownload, desc.openLicense,
+ desc.available);
+ }
+ res.insert(desc);
+ }
+ }
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Validate the parameters used by a coordinate operation.
+ *
+ * Return whether the method is known or not, or a list of missing or extra
+ * parameters for the operations recognized by this implementation.
+ */
+std::list<std::string> SingleOperation::validateParameters() const {
+ std::list<std::string> res;
+
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const MethodMapping *methodMapping = nullptr;
+ const auto methodEPSGCode = l_method->getEPSGCode();
+ size_t nProjectionMethodMappings = 0;
+ const auto projectionMethodMappings =
+ getProjectionMethodMappings(nProjectionMethodMappings);
+ for (size_t i = 0; i < nProjectionMethodMappings; ++i) {
+ const auto &mapping = projectionMethodMappings[i];
+ if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ methodName.c_str()) ||
+ (methodEPSGCode != 0 && methodEPSGCode == mapping.epsg_code)) {
+ methodMapping = &mapping;
+ }
+ }
+ if (methodMapping == nullptr) {
+ size_t nOtherMethodMappings = 0;
+ const auto otherMethodMappings =
+ getOtherMethodMappings(nOtherMethodMappings);
+ for (size_t i = 0; i < nOtherMethodMappings; ++i) {
+ const auto &mapping = otherMethodMappings[i];
+ if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ methodName.c_str()) ||
+ (methodEPSGCode != 0 && methodEPSGCode == mapping.epsg_code)) {
+ methodMapping = &mapping;
+ }
+ }
+ }
+ if (!methodMapping) {
+ res.emplace_back("Unknown method " + methodName);
+ return res;
+ }
+ if (methodMapping->wkt2_name != methodName) {
+ if (metadata::Identifier::isEquivalentName(methodMapping->wkt2_name,
+ methodName.c_str())) {
+ std::string msg("Method name ");
+ msg += methodName;
+ msg += " is equivalent to official ";
+ msg += methodMapping->wkt2_name;
+ msg += " but not strictly equal";
+ res.emplace_back(msg);
+ } else {
+ std::string msg("Method name ");
+ msg += methodName;
+ msg += ", matched to ";
+ msg += methodMapping->wkt2_name;
+ msg += ", through its EPSG code has not an equivalent name";
+ res.emplace_back(msg);
+ }
+ }
+ if (methodEPSGCode != 0 && methodEPSGCode != methodMapping->epsg_code) {
+ std::string msg("Method of EPSG code ");
+ msg += toString(methodEPSGCode);
+ msg += " does not match official code (";
+ msg += toString(methodMapping->epsg_code);
+ msg += ')';
+ res.emplace_back(msg);
+ }
+
+ // Check if expected parameters are found
+ for (int i = 0;
+ methodMapping->params && methodMapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = methodMapping->params[i];
+
+ const OperationParameterValue *opv = nullptr;
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if ((paramMapping->epsg_code != 0 &&
+ parameter->getEPSGCode() == paramMapping->epsg_code) ||
+ ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) {
+ opv = opParamvalue;
+ break;
+ }
+ }
+ }
+
+ if (!opv) {
+ if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) &&
+ paramMapping == &paramLatitudeNatOrigin) {
+ // extension of EPSG used by GDAL/PROJ, so we should not
+ // warn on its absence.
+ continue;
+ }
+ std::string msg("Cannot find expected parameter ");
+ msg += paramMapping->wkt2_name;
+ res.emplace_back(msg);
+ continue;
+ }
+ const auto &parameter = opv->parameter();
+ if (paramMapping->wkt2_name != parameter->nameStr()) {
+ if (ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) {
+ std::string msg("Parameter name ");
+ msg += parameter->nameStr();
+ msg += " is equivalent to official ";
+ msg += paramMapping->wkt2_name;
+ msg += " but not strictly equal";
+ res.emplace_back(msg);
+ } else {
+ std::string msg("Parameter name ");
+ msg += parameter->nameStr();
+ msg += ", matched to ";
+ msg += paramMapping->wkt2_name;
+ msg += ", through its EPSG code has not an equivalent name";
+ res.emplace_back(msg);
+ }
+ }
+ const auto paramEPSGCode = parameter->getEPSGCode();
+ if (paramEPSGCode != 0 && paramEPSGCode != paramMapping->epsg_code) {
+ std::string msg("Parameter of EPSG code ");
+ msg += toString(paramEPSGCode);
+ msg += " does not match official code (";
+ msg += toString(paramMapping->epsg_code);
+ msg += ')';
+ res.emplace_back(msg);
+ }
+ }
+
+ // Check if there are extra parameters
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if (!getMapping(methodMapping, parameter)) {
+ std::string msg("Parameter ");
+ msg += parameter->nameStr();
+ msg += " found but not expected for this method";
+ res.emplace_back(msg);
+ }
+ }
+ }
+
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct ParameterValue::Private {
+ ParameterValue::Type type_{ParameterValue::Type::STRING};
+ std::unique_ptr<common::Measure> measure_{};
+ std::unique_ptr<std::string> stringValue_{};
+ int integerValue_{};
+ bool booleanValue_{};
+
+ explicit Private(const common::Measure &valueIn)
+ : type_(ParameterValue::Type::MEASURE),
+ measure_(internal::make_unique<common::Measure>(valueIn)) {}
+
+ Private(const std::string &stringValueIn, ParameterValue::Type typeIn)
+ : type_(typeIn),
+ stringValue_(internal::make_unique<std::string>(stringValueIn)) {}
+
+ explicit Private(int integerValueIn)
+ : type_(ParameterValue::Type::INTEGER), integerValue_(integerValueIn) {}
+
+ explicit Private(bool booleanValueIn)
+ : type_(ParameterValue::Type::BOOLEAN), booleanValue_(booleanValueIn) {}
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ParameterValue::~ParameterValue() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(const common::Measure &measureIn)
+ : d(internal::make_unique<Private>(measureIn)) {}
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(const std::string &stringValueIn,
+ ParameterValue::Type typeIn)
+ : d(internal::make_unique<Private>(stringValueIn, typeIn)) {}
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(int integerValueIn)
+ : d(internal::make_unique<Private>(integerValueIn)) {}
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(bool booleanValueIn)
+ : d(internal::make_unique<Private>(booleanValueIn)) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a ParameterValue from a Measure (i.e. a value associated
+ * with a
+ * unit)
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr ParameterValue::create(const common::Measure &measureIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(measureIn);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a ParameterValue from a string value.
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr ParameterValue::create(const char *stringValueIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(
+ std::string(stringValueIn), ParameterValue::Type::STRING);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a ParameterValue from a string value.
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr ParameterValue::create(const std::string &stringValueIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(
+ stringValueIn, ParameterValue::Type::STRING);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a ParameterValue from a filename.
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr
+ParameterValue::createFilename(const std::string &stringValueIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(
+ stringValueIn, ParameterValue::Type::FILENAME);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a ParameterValue from a integer value.
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr ParameterValue::create(int integerValueIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(integerValueIn);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a ParameterValue from a boolean value.
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr ParameterValue::create(bool booleanValueIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(booleanValueIn);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the type of a parameter value.
+ *
+ * @return the type.
+ */
+const ParameterValue::Type &ParameterValue::type() PROJ_PURE_DEFN {
+ return d->type_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the value as a Measure (assumes type() == Type::MEASURE)
+ * @return the value as a Measure.
+ */
+const common::Measure &ParameterValue::value() PROJ_PURE_DEFN {
+ return *d->measure_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the value as a string (assumes type() == Type::STRING)
+ * @return the value as a string.
+ */
+const std::string &ParameterValue::stringValue() PROJ_PURE_DEFN {
+ return *d->stringValue_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the value as a filename (assumes type() == Type::FILENAME)
+ * @return the value as a filename.
+ */
+const std::string &ParameterValue::valueFile() PROJ_PURE_DEFN {
+ return *d->stringValue_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the value as a integer (assumes type() == Type::INTEGER)
+ * @return the value as a integer.
+ */
+int ParameterValue::integerValue() PROJ_PURE_DEFN { return d->integerValue_; }
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the value as a boolean (assumes type() == Type::BOOLEAN)
+ * @return the value as a boolean.
+ */
+bool ParameterValue::booleanValue() PROJ_PURE_DEFN { return d->booleanValue_; }
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void ParameterValue::_exportToWKT(io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+
+ const auto &l_type = type();
+ if (l_type == Type::MEASURE) {
+ const auto &l_value = value();
+ if (formatter->abridgedTransformation()) {
+ const auto &unit = l_value.unit();
+ const auto &unitType = unit.type();
+ if (unitType == common::UnitOfMeasure::Type::LINEAR) {
+ formatter->add(l_value.getSIValue());
+ } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
+ formatter->add(
+ l_value.convertToUnit(common::UnitOfMeasure::ARC_SECOND));
+ } else if (unit == common::UnitOfMeasure::PARTS_PER_MILLION) {
+ formatter->add(1.0 + l_value.value() * 1e-6);
+ } else {
+ formatter->add(l_value.value());
+ }
+ } else {
+ const auto &unit = l_value.unit();
+ if (isWKT2) {
+ formatter->add(l_value.value());
+ } else {
+ // In WKT1, as we don't output the natural unit, output to the
+ // registered linear / angular unit.
+ const auto &unitType = unit.type();
+ if (unitType == common::UnitOfMeasure::Type::LINEAR) {
+ const auto &targetUnit = *(formatter->axisLinearUnit());
+ if (targetUnit.conversionToSI() == 0.0) {
+ throw io::FormattingException(
+ "cannot convert value to target linear unit");
+ }
+ formatter->add(l_value.convertToUnit(targetUnit));
+ } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
+ const auto &targetUnit = *(formatter->axisAngularUnit());
+ if (targetUnit.conversionToSI() == 0.0) {
+ throw io::FormattingException(
+ "cannot convert value to target angular unit");
+ }
+ formatter->add(l_value.convertToUnit(targetUnit));
+ } else {
+ formatter->add(l_value.getSIValue());
+ }
+ }
+ if (isWKT2 && unit != common::UnitOfMeasure::NONE) {
+ if (!formatter
+ ->primeMeridianOrParameterUnitOmittedIfSameAsAxis() ||
+ (unit != common::UnitOfMeasure::SCALE_UNITY &&
+ unit != *(formatter->axisLinearUnit()) &&
+ unit != *(formatter->axisAngularUnit()))) {
+ unit._exportToWKT(formatter);
+ }
+ }
+ }
+ } else if (l_type == Type::STRING || l_type == Type::FILENAME) {
+ formatter->addQuotedString(stringValue());
+ } else if (l_type == Type::INTEGER) {
+ formatter->add(integerValue());
+ } else {
+ throw io::FormattingException("boolean parameter value not handled");
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool ParameterValue::_isEquivalentTo(const util::IComparable *other,
+ util::IComparable::Criterion criterion,
+ const io::DatabaseContextPtr &) const {
+ auto otherPV = dynamic_cast<const ParameterValue *>(other);
+ if (otherPV == nullptr) {
+ return false;
+ }
+ if (type() != otherPV->type()) {
+ return false;
+ }
+ switch (type()) {
+ case Type::MEASURE: {
+ return value()._isEquivalentTo(otherPV->value(), criterion, 2e-10);
+ }
+
+ case Type::STRING:
+ case Type::FILENAME: {
+ return stringValue() == otherPV->stringValue();
+ }
+
+ case Type::INTEGER: {
+ return integerValue() == otherPV->integerValue();
+ }
+
+ case Type::BOOLEAN: {
+ return booleanValue() == otherPV->booleanValue();
+ }
+
+ default: {
+ assert(false);
+ break;
+ }
+ }
+ return true;
+}
+//! @endcond
+
+//! @cond Doxygen_Suppress
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const char *message) : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const std::string &message)
+ : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const InvalidOperation &) = default;
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::~InvalidOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+void SingleOperation::exportTransformationToWKT(
+ io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (!isWKT2) {
+ throw io::FormattingException(
+ "Transformation can only be exported to WKT2");
+ }
+
+ if (formatter->abridgedTransformation()) {
+ formatter->startNode(io::WKTConstants::ABRIDGEDTRANSFORMATION,
+ !identifiers().empty());
+ } else {
+ formatter->startNode(io::WKTConstants::COORDINATEOPERATION,
+ !identifiers().empty());
+ }
+
+ formatter->addQuotedString(nameStr());
+
+ if (formatter->use2019Keywords()) {
+ const auto &version = operationVersion();
+ if (version.has_value()) {
+ formatter->startNode(io::WKTConstants::VERSION, false);
+ formatter->addQuotedString(*version);
+ formatter->endNode();
+ }
+ }
+
+ if (!formatter->abridgedTransformation()) {
+ exportSourceCRSAndTargetCRSToWKT(this, formatter);
+ }
+
+ method()->_exportToWKT(formatter);
+
+ for (const auto &paramValue : parameterValues()) {
+ paramValue->_exportToWKT(formatter, nullptr);
+ }
+
+ if (!formatter->abridgedTransformation()) {
+ if (interpolationCRS()) {
+ formatter->startNode(io::WKTConstants::INTERPOLATIONCRS, false);
+ interpolationCRS()->_exportToWKT(formatter);
+ formatter->endNode();
+ }
+
+ if (!coordinateOperationAccuracies().empty()) {
+ formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false);
+ formatter->add(coordinateOperationAccuracies()[0]->value());
+ formatter->endNode();
+ }
+ }
+
+ ObjectUsage::baseExportToWKT(formatter);
+ formatter->endNode();
+}
+
+// ---------------------------------------------------------------------------
+
+bool SingleOperation::exportToPROJStringGeneric(
+ io::PROJStringFormatter *formatter) const {
+ const int methodEPSGCode = method()->getEPSGCode();
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION) {
+ const double A0 = parameterValueMeasure(EPSG_CODE_PARAMETER_A0).value();
+ const double A1 = parameterValueMeasure(EPSG_CODE_PARAMETER_A1).value();
+ const double A2 = parameterValueMeasure(EPSG_CODE_PARAMETER_A2).value();
+ const double B0 = parameterValueMeasure(EPSG_CODE_PARAMETER_B0).value();
+ const double B1 = parameterValueMeasure(EPSG_CODE_PARAMETER_B1).value();
+ const double B2 = parameterValueMeasure(EPSG_CODE_PARAMETER_B2).value();
+
+ // Do not mess with axis unit and order for that transformation
+
+ formatter->addStep("affine");
+ formatter->addParam("xoff", A0);
+ formatter->addParam("s11", A1);
+ formatter->addParam("s12", A2);
+ formatter->addParam("yoff", B0);
+ formatter->addParam("s21", B1);
+ formatter->addParam("s22", B2);
+
+ return true;
+ }
+
+ if (isAxisOrderReversal(methodEPSGCode)) {
+ formatter->addStep("axisswap");
+ formatter->addParam("order", "2,1");
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (sourceCRSGeog && targetCRSGeog) {
+ const auto &unitSrc =
+ sourceCRSGeog->coordinateSystem()->axisList()[0]->unit();
+ const auto &unitDst =
+ targetCRSGeog->coordinateSystem()->axisList()[0]->unit();
+ if (!unitSrc._isEquivalentTo(
+ unitDst, util::IComparable::Criterion::EQUIVALENT)) {
+ formatter->addStep("unitconvert");
+ auto projUnit = unitSrc.exportToPROJString();
+ if (projUnit.empty()) {
+ formatter->addParam("xy_in", unitSrc.conversionToSI());
+ } else {
+ formatter->addParam("xy_in", projUnit);
+ }
+ projUnit = unitDst.exportToPROJString();
+ if (projUnit.empty()) {
+ formatter->addParam("xy_out", unitDst.conversionToSI());
+ } else {
+ formatter->addParam("xy_out", projUnit);
+ }
+ }
+ }
+ return true;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) {
+
+ auto sourceCRSGeod =
+ dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get());
+ auto targetCRSGeod =
+ dynamic_cast<const crs::GeodeticCRS *>(targetCRS().get());
+ if (sourceCRSGeod && targetCRSGeod) {
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod);
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod);
+ bool isSrcGeocentric = sourceCRSGeod->isGeocentric();
+ bool isSrcGeographic = sourceCRSGeog != nullptr;
+ bool isTargetGeocentric = targetCRSGeod->isGeocentric();
+ bool isTargetGeographic = targetCRSGeog != nullptr;
+ if ((isSrcGeocentric && isTargetGeographic) ||
+ (isSrcGeographic && isTargetGeocentric)) {
+
+ formatter->startInversion();
+ sourceCRSGeod->_exportToPROJString(formatter);
+ formatter->stopInversion();
+
+ targetCRSGeod->_exportToPROJString(formatter);
+
+ return true;
+ }
+ }
+
+ throw io::FormattingException("Invalid nature of source and/or "
+ "targetCRS for Geographic/Geocentric "
+ "conversion");
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
+ double convFactor = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
+ auto uom = common::UnitOfMeasure(std::string(), convFactor,
+ common::UnitOfMeasure::Type::LINEAR)
+ .exportToPROJString();
+ auto reverse_uom =
+ common::UnitOfMeasure(std::string(), 1.0 / convFactor,
+ common::UnitOfMeasure::Type::LINEAR)
+ .exportToPROJString();
+ if (uom == "m") {
+ // do nothing
+ } else if (!uom.empty()) {
+ formatter->addStep("unitconvert");
+ formatter->addParam("z_in", uom);
+ formatter->addParam("z_out", "m");
+ } else if (!reverse_uom.empty()) {
+ formatter->addStep("unitconvert");
+ formatter->addParam("z_in", "m");
+ formatter->addParam("z_out", reverse_uom);
+ } else {
+ formatter->addStep("affine");
+ formatter->addParam("s33", convFactor);
+ }
+ return true;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
+ formatter->addStep("axisswap");
+ formatter->addParam("order", "1,2,-3");
+ return true;
+ }
+
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+InverseCoordinateOperation::~InverseCoordinateOperation() = default;
+
+// ---------------------------------------------------------------------------
+
+InverseCoordinateOperation::InverseCoordinateOperation(
+ const CoordinateOperationNNPtr &forwardOperationIn,
+ bool wktSupportsInversion)
+ : forwardOperation_(forwardOperationIn),
+ wktSupportsInversion_(wktSupportsInversion) {}
+
+// ---------------------------------------------------------------------------
+
+void InverseCoordinateOperation::setPropertiesFromForward() {
+ setProperties(
+ createPropertiesForInverse(forwardOperation_.get(), false, false));
+ setAccuracies(forwardOperation_->coordinateOperationAccuracies());
+ if (forwardOperation_->sourceCRS() && forwardOperation_->targetCRS()) {
+ setCRSs(forwardOperation_.get(), true);
+ }
+ setHasBallparkTransformation(
+ forwardOperation_->hasBallparkTransformation());
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr InverseCoordinateOperation::inverse() const {
+ return forwardOperation_;
+}
+
+// ---------------------------------------------------------------------------
+
+void InverseCoordinateOperation::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const {
+ formatter->startInversion();
+ forwardOperation_->_exportToPROJString(formatter);
+ formatter->stopInversion();
+}
+
+// ---------------------------------------------------------------------------
+
+bool InverseCoordinateOperation::_isEquivalentTo(
+ const util::IComparable *other, util::IComparable::Criterion criterion,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto otherICO = dynamic_cast<const InverseCoordinateOperation *>(other);
+ if (otherICO == nullptr ||
+ !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) {
+ return false;
+ }
+ return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion,
+ dbContext);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+PointMotionOperation::~PointMotionOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+
+NS_PROJ_END
diff --git a/src/iso19111/operation/transformation.cpp b/src/iso19111/operation/transformation.cpp
new file mode 100644
index 00000000..fec821a1
--- /dev/null
+++ b/src/iso19111/operation/transformation.cpp
@@ -0,0 +1,3274 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/crs.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+
+#include "coordinateoperation_internal.hpp"
+#include "coordinateoperation_private.hpp"
+#include "esriparammappings.hpp"
+#include "operationmethod_private.hpp"
+#include "oputils.hpp"
+#include "parammappings.hpp"
+#include "vectorofvaluesparams.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+#include "proj_constants.h"
+
+#include "proj_json_streaming_writer.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct Transformation::Private {
+
+ TransformationPtr forwardOperation_{};
+
+ static TransformationNNPtr registerInv(const Transformation *thisIn,
+ TransformationNNPtr invTransform);
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+Transformation::Transformation(
+ const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
+ const crs::CRSPtr &interpolationCRSIn, const OperationMethodNNPtr &methodIn,
+ const std::vector<GeneralParameterValueNNPtr> &values,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies)
+ : SingleOperation(methodIn), d(internal::make_unique<Private>()) {
+ setParameterValues(values);
+ setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn);
+ setAccuracies(accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+Transformation::~Transformation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+Transformation::Transformation(const Transformation &other)
+ : CoordinateOperation(other), SingleOperation(other),
+ d(internal::make_unique<Private>(*other.d)) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the source crs::CRS of the transformation.
+ *
+ * @return the source CRS.
+ */
+const crs::CRSNNPtr &Transformation::sourceCRS() PROJ_PURE_DEFN {
+ return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the target crs::CRS of the transformation.
+ *
+ * @return the target CRS.
+ */
+const crs::CRSNNPtr &Transformation::targetCRS() PROJ_PURE_DEFN {
+ return CoordinateOperation::getPrivate()->strongRef_->targetCRS_;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+TransformationNNPtr Transformation::shallowClone() const {
+ auto transf = Transformation::nn_make_shared<Transformation>(*this);
+ transf->assignSelf(transf);
+ transf->setCRSs(this, false);
+ if (transf->d->forwardOperation_) {
+ transf->d->forwardOperation_ =
+ transf->d->forwardOperation_->shallowClone().as_nullable();
+ }
+ return transf;
+}
+
+CoordinateOperationNNPtr Transformation::_shallowClone() const {
+ return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
+}
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr
+Transformation::promoteTo3D(const std::string &,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto transf = shallowClone();
+ transf->setCRSs(sourceCRS()->promoteTo3D(std::string(), dbContext),
+ targetCRS()->promoteTo3D(std::string(), dbContext),
+ interpolationCRS());
+ return transf;
+}
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr
+Transformation::demoteTo2D(const std::string &,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto transf = shallowClone();
+ transf->setCRSs(sourceCRS()->demoteTo2D(std::string(), dbContext),
+ targetCRS()->demoteTo2D(std::string(), dbContext),
+ interpolationCRS());
+ return transf;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+/** \brief Return the TOWGS84 parameters of the transformation.
+ *
+ * If this transformation uses Coordinate Frame Rotation, Position Vector
+ * transformation or Geocentric translations, a vector of 7 double values
+ * using the Position Vector convention (EPSG:9606) is returned. Those values
+ * can be used as the value of the WKT1 TOWGS84 parameter or
+ * PROJ +towgs84 parameter.
+ *
+ * @return a vector of 7 values if valid, otherwise a io::FormattingException
+ * is thrown.
+ * @throws io::FormattingException
+ */
+std::vector<double>
+Transformation::getTOWGS84Parameters() const // throw(io::FormattingException)
+{
+ // GDAL WKT1 assumes EPSG:9606 / Position Vector convention
+
+ bool sevenParamsTransform = false;
+ bool threeParamsTransform = false;
+ bool invertRotSigns = false;
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ const auto paramCount = parameterValues().size();
+ if ((paramCount == 7 &&
+ ci_find(methodName, "Coordinate Frame") != std::string::npos) ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ invertRotSigns = true;
+ } else if ((paramCount == 7 &&
+ ci_find(methodName, "Position Vector") != std::string::npos) ||
+ methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ invertRotSigns = false;
+ } else if ((paramCount == 3 &&
+ ci_find(methodName, "Geocentric translations") !=
+ std::string::npos) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
+ threeParamsTransform = true;
+ }
+
+ if (threeParamsTransform || sevenParamsTransform) {
+ std::vector<double> params(7, 0.0);
+ bool foundX = false;
+ bool foundY = false;
+ bool foundZ = false;
+ bool foundRotX = false;
+ bool foundRotY = false;
+ bool foundRotZ = false;
+ bool foundScale = false;
+ const double rotSign = invertRotSigns ? -1.0 : 1.0;
+
+ const auto fixNegativeZero = [](double x) {
+ if (x == 0.0)
+ return 0.0;
+ return x;
+ };
+
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ const auto epsg_code = parameter->getEPSGCode();
+ const auto &l_parameterValue = opParamvalue->parameterValue();
+ if (l_parameterValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &measure = l_parameterValue->value();
+ if (epsg_code == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) {
+ params[0] = measure.getSIValue();
+ foundX = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) {
+ params[1] = measure.getSIValue();
+ foundY = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) {
+ params[2] = measure.getSIValue();
+ foundZ = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_X_AXIS_ROTATION) {
+ params[3] = fixNegativeZero(
+ rotSign *
+ measure.convertToUnit(
+ common::UnitOfMeasure::ARC_SECOND));
+ foundRotX = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) {
+ params[4] = fixNegativeZero(
+ rotSign *
+ measure.convertToUnit(
+ common::UnitOfMeasure::ARC_SECOND));
+ foundRotY = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) {
+ params[5] = fixNegativeZero(
+ rotSign *
+ measure.convertToUnit(
+ common::UnitOfMeasure::ARC_SECOND));
+ foundRotZ = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) {
+ params[6] = measure.convertToUnit(
+ common::UnitOfMeasure::PARTS_PER_MILLION);
+ foundScale = true;
+ }
+ }
+ }
+ }
+ if (foundX && foundY && foundZ &&
+ (threeParamsTransform ||
+ (foundRotX && foundRotY && foundRotZ && foundScale))) {
+ return params;
+ } else {
+ throw io::FormattingException(
+ "Missing required parameter values in transformation");
+ }
+ }
+
+#if 0
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS ||
+ methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
+ auto offsetLat =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
+ auto offsetLong =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
+
+ auto offsetHeight =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
+
+ if (offsetLat.getSIValue() == 0.0 && offsetLong.getSIValue() == 0.0 &&
+ offsetHeight.getSIValue() == 0.0) {
+ std::vector<double> params(7, 0.0);
+ return params;
+ }
+ }
+#endif
+
+ throw io::FormattingException(
+ "Transformation cannot be formatted as WKT1 TOWGS84 parameters");
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation from a vector of GeneralParameterValue.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param interpolationCRSIn Interpolation CRS (might be null)
+ * @param methodIn Operation method.
+ * @param values Vector of GeneralOperationParameterNNPtr.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr Transformation::create(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn,
+ const OperationMethodNNPtr &methodIn,
+ const std::vector<GeneralParameterValueNNPtr> &values,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ if (methodIn->parameters().size() != values.size()) {
+ throw InvalidOperation(
+ "Inconsistent number of parameters and parameter values");
+ }
+ auto transf = Transformation::nn_make_shared<Transformation>(
+ sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values,
+ accuracies);
+ transf->assignSelf(transf);
+ transf->setProperties(properties);
+ std::string name;
+ if (properties.getStringValue(common::IdentifiedObject::NAME_KEY, name) &&
+ ci_find(name, "ballpark") != std::string::npos) {
+ transf->setHasBallparkTransformation(true);
+ }
+ return transf;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation ands its OperationMethod.
+ *
+ * @param propertiesTransformation The \ref general_properties of the
+ * Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param interpolationCRSIn Interpolation CRS (might be null)
+ * @param propertiesOperationMethod The \ref general_properties of the
+ * OperationMethod.
+ * At minimum the name should be defined.
+ * @param parameters Vector of parameters of the operation method.
+ * @param values Vector of ParameterValueNNPtr. Constraint:
+ * values.size() == parameters.size()
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr
+Transformation::create(const util::PropertyMap &propertiesTransformation,
+ const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn,
+ const crs::CRSPtr &interpolationCRSIn,
+ const util::PropertyMap &propertiesOperationMethod,
+ const std::vector<OperationParameterNNPtr> &parameters,
+ const std::vector<ParameterValueNNPtr> &values,
+ const std::vector<metadata::PositionalAccuracyNNPtr>
+ &accuracies) // throw InvalidOperation
+{
+ OperationMethodNNPtr op(
+ OperationMethod::create(propertiesOperationMethod, parameters));
+
+ if (parameters.size() != values.size()) {
+ throw InvalidOperation(
+ "Inconsistent number of parameters and parameter values");
+ }
+ std::vector<GeneralParameterValueNNPtr> generalParameterValues;
+ generalParameterValues.reserve(values.size());
+ for (size_t i = 0; i < values.size(); i++) {
+ generalParameterValues.push_back(
+ OperationParameterValue::create(parameters[i], values[i]));
+ }
+ return create(propertiesTransformation, sourceCRSIn, targetCRSIn,
+ interpolationCRSIn, op, generalParameterValues, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+static TransformationNNPtr createSevenParamsTransform(
+ const util::PropertyMap &properties,
+ const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return Transformation::create(
+ properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties,
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE),
+ },
+ createParams(common::Length(translationXMetre),
+ common::Length(translationYMetre),
+ common::Length(translationZMetre),
+ common::Angle(rotationXArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Angle(rotationYArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Angle(rotationZArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Scale(scaleDifferencePPM,
+ common::UnitOfMeasure::PARTS_PER_MILLION)),
+ accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn,
+ bool &isGeocentric, bool &isGeog2D,
+ bool &isGeog3D) {
+ auto sourceCRSGeod =
+ dynamic_cast<const crs::GeodeticCRS *>(sourceCRSIn.get());
+ auto targetCRSGeod =
+ dynamic_cast<const crs::GeodeticCRS *>(targetCRSIn.get());
+ isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() &&
+ targetCRSGeod && targetCRSGeod->isGeocentric();
+ if (isGeocentric) {
+ isGeog2D = false;
+ isGeog3D = false;
+ return;
+ }
+ isGeocentric = false;
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRSIn.get());
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRSIn.get());
+ if (!sourceCRSGeog || !targetCRSGeog) {
+ throw InvalidOperation("Inconsistent CRS type");
+ }
+ const auto nSrcAxisCount =
+ sourceCRSGeog->coordinateSystem()->axisList().size();
+ const auto nTargetAxisCount =
+ targetCRSGeog->coordinateSystem()->axisList().size();
+ isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2;
+ isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2;
+}
+
+// ---------------------------------------------------------------------------
+
+static int
+useOperationMethodEPSGCodeIfPresent(const util::PropertyMap &properties,
+ int nDefaultOperationMethodEPSGCode) {
+ const auto *operationMethodEPSGCode =
+ properties.get("OPERATION_METHOD_EPSG_CODE");
+ if (operationMethodEPSGCode) {
+ const auto boxedValue = dynamic_cast<const util::BoxedValue *>(
+ (*operationMethodEPSGCode).get());
+ if (boxedValue &&
+ boxedValue->type() == util::BoxedValue::Type::INTEGER) {
+ return boxedValue->integerValue();
+ }
+ }
+ return nDefaultOperationMethodEPSGCode;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with Geocentric Translations method.
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createGeocentricTranslations(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ bool isGeocentric;
+ bool isGeog2D;
+ bool isGeog3D;
+ getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
+ isGeog3D);
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
+ properties,
+ isGeocentric
+ ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC
+ : isGeog2D
+ ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D
+ : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D)),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
+ },
+ createParams(common::Length(translationXMetre),
+ common::Length(translationYMetre),
+ common::Length(translationZMetre)),
+ accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with Position vector transformation
+ * method.
+ *
+ * This is similar to createCoordinateFrameRotation(), except that the sign of
+ * the rotation terms is inverted.
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param rotationXArcSecond Value of the Rotation_X parameter (in
+ * arc-second).
+ * @param rotationYArcSecond Value of the Rotation_Y parameter (in
+ * arc-second).
+ * @param rotationZArcSecond Value of the Rotation_Z parameter (in
+ * arc-second).
+ * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
+ * parts-per-million).
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createPositionVector(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ bool isGeocentric;
+ bool isGeog2D;
+ bool isGeog3D;
+ getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
+ isGeog3D);
+ return createSevenParamsTransform(
+ properties,
+ createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
+ properties,
+ isGeocentric
+ ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC
+ : isGeog2D ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
+ : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D)),
+ sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
+ translationZMetre, rotationXArcSecond, rotationYArcSecond,
+ rotationZArcSecond, scaleDifferencePPM, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with Coordinate Frame Rotation method.
+ *
+ * This is similar to createPositionVector(), except that the sign of
+ * the rotation terms is inverted.
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param rotationXArcSecond Value of the Rotation_X parameter (in
+ * arc-second).
+ * @param rotationYArcSecond Value of the Rotation_Y parameter (in
+ * arc-second).
+ * @param rotationZArcSecond Value of the Rotation_Z parameter (in
+ * arc-second).
+ * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
+ * parts-per-million).
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createCoordinateFrameRotation(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ bool isGeocentric;
+ bool isGeog2D;
+ bool isGeog3D;
+ getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
+ isGeog3D);
+ return createSevenParamsTransform(
+ properties,
+ createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
+ properties,
+ isGeocentric
+ ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC
+ : isGeog2D ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D
+ : EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D)),
+ sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
+ translationZMetre, rotationXArcSecond, rotationYArcSecond,
+ rotationZArcSecond, scaleDifferencePPM, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static TransformationNNPtr createFifteenParamsTransform(
+ const util::PropertyMap &properties,
+ const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ double rateTranslationX, double rateTranslationY, double rateTranslationZ,
+ double rateRotationX, double rateRotationY, double rateRotationZ,
+ double rateScaleDifference, double referenceEpochYear,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return Transformation::create(
+ properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties,
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE),
+
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE),
+
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_REFERENCE_EPOCH),
+ },
+ VectorOfValues{
+ common::Length(translationXMetre),
+ common::Length(translationYMetre),
+ common::Length(translationZMetre),
+ common::Angle(rotationXArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Angle(rotationYArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Angle(rotationZArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Scale(scaleDifferencePPM,
+ common::UnitOfMeasure::PARTS_PER_MILLION),
+ common::Measure(rateTranslationX,
+ common::UnitOfMeasure::METRE_PER_YEAR),
+ common::Measure(rateTranslationY,
+ common::UnitOfMeasure::METRE_PER_YEAR),
+ common::Measure(rateTranslationZ,
+ common::UnitOfMeasure::METRE_PER_YEAR),
+ common::Measure(rateRotationX,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
+ common::Measure(rateRotationY,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
+ common::Measure(rateRotationZ,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
+ common::Measure(rateScaleDifference,
+ common::UnitOfMeasure::PPM_PER_YEAR),
+ common::Measure(referenceEpochYear, common::UnitOfMeasure::YEAR),
+ },
+ accuracies);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with Time Dependent position vector
+ * transformation method.
+ *
+ * This is similar to createTimeDependentCoordinateFrameRotation(), except that
+ * the sign of
+ * the rotation terms is inverted.
+ *
+ * This method is defined as [EPSG:1053]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1053)
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param rotationXArcSecond Value of the Rotation_X parameter (in
+ * arc-second).
+ * @param rotationYArcSecond Value of the Rotation_Y parameter (in
+ * arc-second).
+ * @param rotationZArcSecond Value of the Rotation_Z parameter (in
+ * arc-second).
+ * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
+ * parts-per-million).
+ * @param rateTranslationX Value of the rate of change of X-axis translation (in
+ * metre/year)
+ * @param rateTranslationY Value of the rate of change of Y-axis translation (in
+ * metre/year)
+ * @param rateTranslationZ Value of the rate of change of Z-axis translation (in
+ * metre/year)
+ * @param rateRotationX Value of the rate of change of X-axis rotation (in
+ * arc-second/year)
+ * @param rateRotationY Value of the rate of change of Y-axis rotation (in
+ * arc-second/year)
+ * @param rateRotationZ Value of the rate of change of Z-axis rotation (in
+ * arc-second/year)
+ * @param rateScaleDifference Value of the rate of change of scale difference
+ * (in PPM/year)
+ * @param referenceEpochYear Parameter reference epoch (in decimal year)
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createTimeDependentPositionVector(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ double rateTranslationX, double rateTranslationY, double rateTranslationZ,
+ double rateRotationX, double rateRotationY, double rateRotationZ,
+ double rateScaleDifference, double referenceEpochYear,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ bool isGeocentric;
+ bool isGeog2D;
+ bool isGeog3D;
+ getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
+ isGeog3D);
+ return createFifteenParamsTransform(
+ properties,
+ createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
+ properties,
+ isGeocentric
+ ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC
+ : isGeog2D
+ ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D
+ : EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D)),
+ sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
+ translationZMetre, rotationXArcSecond, rotationYArcSecond,
+ rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
+ rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY,
+ rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with Time Dependent Position coordinate
+ * frame rotation transformation method.
+ *
+ * This is similar to createTimeDependentPositionVector(), except that the sign
+ * of
+ * the rotation terms is inverted.
+ *
+ * This method is defined as [EPSG:1056]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1056)
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param rotationXArcSecond Value of the Rotation_X parameter (in
+ * arc-second).
+ * @param rotationYArcSecond Value of the Rotation_Y parameter (in
+ * arc-second).
+ * @param rotationZArcSecond Value of the Rotation_Z parameter (in
+ * arc-second).
+ * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
+ * parts-per-million).
+ * @param rateTranslationX Value of the rate of change of X-axis translation (in
+ * metre/year)
+ * @param rateTranslationY Value of the rate of change of Y-axis translation (in
+ * metre/year)
+ * @param rateTranslationZ Value of the rate of change of Z-axis translation (in
+ * metre/year)
+ * @param rateRotationX Value of the rate of change of X-axis rotation (in
+ * arc-second/year)
+ * @param rateRotationY Value of the rate of change of Y-axis rotation (in
+ * arc-second/year)
+ * @param rateRotationZ Value of the rate of change of Z-axis rotation (in
+ * arc-second/year)
+ * @param rateScaleDifference Value of the rate of change of scale difference
+ * (in PPM/year)
+ * @param referenceEpochYear Parameter reference epoch (in decimal year)
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr Transformation::createTimeDependentCoordinateFrameRotation(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ double rateTranslationX, double rateTranslationY, double rateTranslationZ,
+ double rateRotationX, double rateRotationY, double rateRotationZ,
+ double rateScaleDifference, double referenceEpochYear,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ bool isGeocentric;
+ bool isGeog2D;
+ bool isGeog3D;
+ getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
+ isGeog3D);
+ return createFifteenParamsTransform(
+ properties,
+ createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
+ properties,
+ isGeocentric
+ ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC
+ : isGeog2D
+ ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D
+ : EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D)),
+ sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
+ translationZMetre, rotationXArcSecond, rotationYArcSecond,
+ rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
+ rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY,
+ rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static TransformationNNPtr _createMolodensky(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, int methodEPSGCode,
+ double translationXMetre, double translationYMetre,
+ double translationZMetre, double semiMajorAxisDifferenceMetre,
+ double flattingDifference,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return Transformation::create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(methodEPSGCode),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE),
+ },
+ createParams(
+ common::Length(translationXMetre),
+ common::Length(translationYMetre),
+ common::Length(translationZMetre),
+ common::Length(semiMajorAxisDifferenceMetre),
+ common::Measure(flattingDifference, common::UnitOfMeasure::NONE)),
+ accuracies);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with Molodensky method.
+ *
+ * @see createAbridgedMolodensky() for a related method.
+ *
+ * This method is defined as [EPSG:9604]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9604)
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param semiMajorAxisDifferenceMetre The difference between the semi-major
+ * axis values of the ellipsoids used in the target and source CRS (in metre).
+ * @param flattingDifference The difference between the flattening values of
+ * the ellipsoids used in the target and source CRS.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr Transformation::createMolodensky(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double semiMajorAxisDifferenceMetre, double flattingDifference,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return _createMolodensky(
+ properties, sourceCRSIn, targetCRSIn, EPSG_CODE_METHOD_MOLODENSKY,
+ translationXMetre, translationYMetre, translationZMetre,
+ semiMajorAxisDifferenceMetre, flattingDifference, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with Abridged Molodensky method.
+ *
+ * @see createdMolodensky() for a related method.
+ *
+ * This method is defined as [EPSG:9605]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9605)
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param semiMajorAxisDifferenceMetre The difference between the semi-major
+ * axis values of the ellipsoids used in the target and source CRS (in metre).
+ * @param flattingDifference The difference between the flattening values of
+ * the ellipsoids used in the target and source CRS.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr Transformation::createAbridgedMolodensky(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double semiMajorAxisDifferenceMetre, double flattingDifference,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return _createMolodensky(properties, sourceCRSIn, targetCRSIn,
+ EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY,
+ translationXMetre, translationYMetre,
+ translationZMetre, semiMajorAxisDifferenceMetre,
+ flattingDifference, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation from TOWGS84 parameters.
+ *
+ * This is a helper of createPositionVector() with the source CRS being the
+ * GeographicCRS of sourceCRSIn, and the target CRS being EPSG:4326
+ *
+ * @param sourceCRSIn Source CRS.
+ * @param TOWGS84Parameters The vector of 3 double values (Translation_X,_Y,_Z)
+ * or 7 double values (Translation_X,_Y,_Z, Rotation_X,_Y,_Z, Scale_Difference)
+ * passed to createPositionVector()
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr Transformation::createTOWGS84(
+ const crs::CRSNNPtr &sourceCRSIn,
+ const std::vector<double> &TOWGS84Parameters) // throw InvalidOperation
+{
+ if (TOWGS84Parameters.size() != 3 && TOWGS84Parameters.size() != 7) {
+ throw InvalidOperation(
+ "Invalid number of elements in TOWGS84Parameters");
+ }
+
+ crs::CRSPtr transformSourceCRS = sourceCRSIn->extractGeodeticCRS();
+ if (!transformSourceCRS) {
+ throw InvalidOperation(
+ "Cannot find GeodeticCRS in sourceCRS of TOWGS84 transformation");
+ }
+
+ util::PropertyMap properties;
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ concat("Transformation from ", transformSourceCRS->nameStr(),
+ " to WGS84"));
+
+ auto targetCRS =
+ dynamic_cast<const crs::GeographicCRS *>(transformSourceCRS.get())
+ ? util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeographicCRS::EPSG_4326)
+ : util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeodeticCRS::EPSG_4978);
+
+ if (TOWGS84Parameters.size() == 3) {
+ return createGeocentricTranslations(
+ properties, NN_NO_CHECK(transformSourceCRS), targetCRS,
+ TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2],
+ {});
+ }
+
+ return createPositionVector(properties, NN_NO_CHECK(transformSourceCRS),
+ targetCRS, TOWGS84Parameters[0],
+ TOWGS84Parameters[1], TOWGS84Parameters[2],
+ TOWGS84Parameters[3], TOWGS84Parameters[4],
+ TOWGS84Parameters[5], TOWGS84Parameters[6], {});
+}
+
+// ---------------------------------------------------------------------------
+/** \brief Instantiate a transformation with NTv2 method.
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param filename NTv2 filename.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createNTv2(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ return create(properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV2),
+ VectorOfParameters{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)},
+ VectorOfValues{ParameterValue::createFilename(filename)},
+ accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static TransformationNNPtr _createGravityRelatedHeightToGeographic3D(
+ const util::PropertyMap &properties, bool inverse,
+ const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
+ const crs::CRSPtr &interpolationCRSIn, const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ return Transformation::create(
+ properties, sourceCRSIn, targetCRSIn, interpolationCRSIn,
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ inverse ? INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D
+ : PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D),
+ VectorOfParameters{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)},
+ VectorOfValues{ParameterValue::createFilename(filename)}, accuracies);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+/** \brief Instantiate a transformation from GravityRelatedHeight to
+ * Geographic3D
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param interpolationCRSIn Interpolation CRS. (might be null)
+ * @param filename GRID filename.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createGravityRelatedHeightToGeographic3D(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn,
+ const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ return _createGravityRelatedHeightToGeographic3D(
+ properties, false, sourceCRSIn, targetCRSIn, interpolationCRSIn,
+ filename, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with method VERTCON
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param filename GRID filename.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createVERTCON(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ return create(properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTCON),
+ VectorOfParameters{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)},
+ VectorOfValues{ParameterValue::createFilename(filename)},
+ accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static inline std::vector<metadata::PositionalAccuracyNNPtr>
+buildAccuracyZero() {
+ return std::vector<metadata::PositionalAccuracyNNPtr>{
+ metadata::PositionalAccuracy::create("0")};
+}
+
+// ---------------------------------------------------------------------------
+
+//! @endcond
+
+/** \brief Instantiate a transformation with method Longitude rotation
+ *
+ * This method is defined as [EPSG:9601]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9601)
+ * *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param offset Longitude offset to add.
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createLongitudeRotation(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Angle &offset) {
+
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_LONGITUDE_ROTATION),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
+ VectorOfValues{ParameterValue::create(offset)}, buildAccuracyZero());
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool Transformation::isLongitudeRotation() const {
+ return method()->getEPSGCode() == EPSG_CODE_METHOD_LONGITUDE_ROTATION;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with method Geographic 2D offsets
+ *
+ * This method is defined as [EPSG:9619]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9619)
+ * *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param offsetLat Latitude offset to add.
+ * @param offsetLon Longitude offset to add.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createGeographic2DOffsets(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
+ const common::Angle &offsetLon,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
+ VectorOfValues{offsetLat, offsetLon}, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with method Geographic 3D offsets
+ *
+ * This method is defined as [EPSG:9660]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9660)
+ * *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param offsetLat Latitude offset to add.
+ * @param offsetLon Longitude offset to add.
+ * @param offsetHeight Height offset to add.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createGeographic3DOffsets(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
+ const common::Angle &offsetLon, const common::Length &offsetHeight,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
+ VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with method Geographic 2D with
+ * height
+ * offsets
+ *
+ * This method is defined as [EPSG:9618]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9618)
+ * *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param offsetLat Latitude offset to add.
+ * @param offsetLon Longitude offset to add.
+ * @param offsetHeight Geoid undulation to add.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createGeographic2DWithHeightOffsets(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
+ const common::Angle &offsetLon, const common::Length &offsetHeight,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_GEOID_UNDULATION)},
+ VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation with method Vertical Offset.
+ *
+ * This method is defined as [EPSG:9616]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9616)
+ * *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param offsetHeight Geoid undulation to add.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createVerticalOffset(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Length &offsetHeight,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return create(properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET),
+ VectorOfParameters{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
+ VectorOfValues{offsetHeight}, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation based on the Change of Vertical Unit
+ * method.
+ *
+ * This method is defined as [EPSG:1069]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1069)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param factor Conversion factor
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return a new Transformation.
+ */
+TransformationNNPtr Transformation::createChangeVerticalUnit(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Scale &factor,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR),
+ },
+ VectorOfValues{
+ factor,
+ },
+ accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+// to avoid -0...
+static double negate(double val) {
+ if (val != 0) {
+ return -val;
+ }
+ return 0.0;
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationPtr
+createApproximateInverseIfPossible(const Transformation *op) {
+ bool sevenParamsTransform = false;
+ bool fifteenParamsTransform = false;
+ const auto &method = op->method();
+ const auto &methodName = method->nameStr();
+ const int methodEPSGCode = method->getEPSGCode();
+ const auto paramCount = op->parameterValues().size();
+ const bool isPositionVector =
+ ci_find(methodName, "Position Vector") != std::string::npos;
+ const bool isCoordinateFrame =
+ ci_find(methodName, "Coordinate Frame") != std::string::npos;
+
+ // See end of "2.4.3.3 Helmert 7-parameter transformations"
+ // in EPSG 7-2 guidance
+ // For practical purposes, the inverse of 7- or 15-parameters Helmert
+ // can be obtained by using the forward method with all parameters
+ // negated
+ // (except reference epoch!)
+ // So for WKT export use that. But for PROJ string, we use the +inv flag
+ // so as to get "perfect" round-tripability.
+ if ((paramCount == 7 && isCoordinateFrame &&
+ !isTimeDependent(methodName)) ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ } else if (
+ (paramCount == 15 && isCoordinateFrame &&
+ isTimeDependent(methodName)) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ fifteenParamsTransform = true;
+ } else if ((paramCount == 7 && isPositionVector &&
+ !isTimeDependent(methodName)) ||
+ methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ } else if (
+ (paramCount == 15 && isPositionVector && isTimeDependent(methodName)) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ fifteenParamsTransform = true;
+ }
+ if (sevenParamsTransform || fifteenParamsTransform) {
+ double neg_x = negate(op->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
+ double neg_y = negate(op->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
+ double neg_z = negate(op->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
+ double neg_rx = negate(
+ op->parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND));
+ double neg_ry = negate(
+ op->parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND));
+ double neg_rz = negate(
+ op->parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND));
+ double neg_scaleDiff = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
+ common::UnitOfMeasure::PARTS_PER_MILLION));
+ auto methodProperties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, methodName);
+ int method_epsg_code = method->getEPSGCode();
+ if (method_epsg_code) {
+ methodProperties
+ .set(metadata::Identifier::CODESPACE_KEY,
+ metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, method_epsg_code);
+ }
+ if (fifteenParamsTransform) {
+ double neg_rate_x = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR));
+ double neg_rate_y = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR));
+ double neg_rate_z = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR));
+ double neg_rate_rx = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
+ double neg_rate_ry = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
+ double neg_rate_rz = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
+ double neg_rate_scaleDiff = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE,
+ common::UnitOfMeasure::PPM_PER_YEAR));
+ double referenceEpochYear =
+ op->parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH,
+ common::UnitOfMeasure::YEAR);
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ createFifteenParamsTransform(
+ createPropertiesForInverse(op, false, true),
+ methodProperties, op->targetCRS(), op->sourceCRS(),
+ neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz,
+ neg_scaleDiff, neg_rate_x, neg_rate_y, neg_rate_z,
+ neg_rate_rx, neg_rate_ry, neg_rate_rz,
+ neg_rate_scaleDiff, referenceEpochYear,
+ op->coordinateOperationAccuracies()))
+ .as_nullable();
+ } else {
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ createSevenParamsTransform(
+ createPropertiesForInverse(op, false, true),
+ methodProperties, op->targetCRS(), op->sourceCRS(),
+ neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz,
+ neg_scaleDiff, op->coordinateOperationAccuracies()))
+ .as_nullable();
+ }
+ }
+
+ return nullptr;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+TransformationNNPtr
+Transformation::Private::registerInv(const Transformation *thisIn,
+ TransformationNNPtr invTransform) {
+ invTransform->d->forwardOperation_ = thisIn->shallowClone().as_nullable();
+ invTransform->setHasBallparkTransformation(
+ thisIn->hasBallparkTransformation());
+ return invTransform;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr Transformation::inverse() const {
+ return inverseAsTransformation();
+}
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr Transformation::inverseAsTransformation() const {
+
+ if (d->forwardOperation_) {
+ return NN_NO_CHECK(d->forwardOperation_);
+ }
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ const auto &l_sourceCRS = sourceCRS();
+ const auto &l_targetCRS = targetCRS();
+
+ // For geocentric translation, the inverse is exactly the negation of
+ // the parameters.
+ if (ci_find(methodName, "Geocentric translations") != std::string::npos ||
+ methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
+ double x =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
+ double y =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
+ double z =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+ auto properties = createPropertiesForInverse(this, false, false);
+ return Private::registerInv(
+ this, create(properties, l_targetCRS, l_sourceCRS, nullptr,
+ createMethodMapNameEPSGCode(
+ useOperationMethodEPSGCodeIfPresent(
+ properties, methodEPSGCode)),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
+ },
+ createParams(common::Length(negate(x)),
+ common::Length(negate(y)),
+ common::Length(negate(z))),
+ coordinateOperationAccuracies()));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
+ methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
+ double x =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
+ double y =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
+ double z =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+ double da = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE);
+ double df = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE);
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
+ return Private::registerInv(
+ this,
+ createAbridgedMolodensky(
+ createPropertiesForInverse(this, false, false), l_targetCRS,
+ l_sourceCRS, negate(x), negate(y), negate(z), negate(da),
+ negate(df), coordinateOperationAccuracies()));
+ } else {
+ return Private::registerInv(
+ this,
+ createMolodensky(createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, negate(x), negate(y),
+ negate(z), negate(da), negate(df),
+ coordinateOperationAccuracies()));
+ }
+ }
+
+ if (isLongitudeRotation()) {
+ auto offset =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
+ const common::Angle newOffset(negate(offset.value()), offset.unit());
+ return Private::registerInv(
+ this, createLongitudeRotation(
+ createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, newOffset));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
+ auto offsetLat =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
+ const common::Angle newOffsetLat(negate(offsetLat.value()),
+ offsetLat.unit());
+
+ auto offsetLong =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
+ const common::Angle newOffsetLong(negate(offsetLong.value()),
+ offsetLong.unit());
+
+ return Private::registerInv(
+ this, createGeographic2DOffsets(
+ createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
+ coordinateOperationAccuracies()));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
+ auto offsetLat =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
+ const common::Angle newOffsetLat(negate(offsetLat.value()),
+ offsetLat.unit());
+
+ auto offsetLong =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
+ const common::Angle newOffsetLong(negate(offsetLong.value()),
+ offsetLong.unit());
+
+ auto offsetHeight =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
+ const common::Length newOffsetHeight(negate(offsetHeight.value()),
+ offsetHeight.unit());
+
+ return Private::registerInv(
+ this, createGeographic3DOffsets(
+ createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
+ newOffsetHeight, coordinateOperationAccuracies()));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) {
+ auto offsetLat =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
+ const common::Angle newOffsetLat(negate(offsetLat.value()),
+ offsetLat.unit());
+
+ auto offsetLong =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
+ const common::Angle newOffsetLong(negate(offsetLong.value()),
+ offsetLong.unit());
+
+ auto offsetHeight =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_GEOID_UNDULATION);
+ const common::Length newOffsetHeight(negate(offsetHeight.value()),
+ offsetHeight.unit());
+
+ return Private::registerInv(
+ this, createGeographic2DWithHeightOffsets(
+ createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
+ newOffsetHeight, coordinateOperationAccuracies()));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) {
+
+ auto offsetHeight =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
+ const common::Length newOffsetHeight(negate(offsetHeight.value()),
+ offsetHeight.unit());
+
+ return Private::registerInv(
+ this,
+ createVerticalOffset(createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, newOffsetHeight,
+ coordinateOperationAccuracies()));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
+ const double convFactor = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
+ return Private::registerInv(
+ this, createChangeVerticalUnit(
+ createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, common::Scale(1.0 / convFactor),
+ coordinateOperationAccuracies()));
+ }
+
+#ifdef notdef
+ // We don't need that currently, but we might...
+ if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
+ return Private::registerInv(
+ 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())));
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+InverseTransformation::InverseTransformation(const TransformationNNPtr &forward)
+ : Transformation(
+ forward->targetCRS(), forward->sourceCRS(),
+ forward->interpolationCRS(),
+ OperationMethod::create(createPropertiesForInverse(forward->method()),
+ forward->method()->parameters()),
+ forward->parameterValues(), forward->coordinateOperationAccuracies()),
+ InverseCoordinateOperation(forward, true) {
+ setPropertiesFromForward();
+}
+
+// ---------------------------------------------------------------------------
+
+InverseTransformation::~InverseTransformation() = default;
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr
+InverseTransformation::create(const TransformationNNPtr &forward) {
+ auto conv = util::nn_make_shared<InverseTransformation>(forward);
+ conv->assignSelf(conv);
+ return conv;
+}
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr InverseTransformation::inverseAsTransformation() const {
+ return NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_));
+}
+
+// ---------------------------------------------------------------------------
+
+void InverseTransformation::_exportToWKT(io::WKTFormatter *formatter) const {
+
+ auto approxInverse = createApproximateInverseIfPossible(
+ util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_).get());
+ if (approxInverse) {
+ approxInverse->_exportToWKT(formatter);
+ } else {
+ Transformation::_exportToWKT(formatter);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr InverseTransformation::_shallowClone() const {
+ auto op = InverseTransformation::nn_make_shared<InverseTransformation>(
+ inverseAsTransformation()->shallowClone());
+ op->assignSelf(op);
+ op->setCRSs(this, false);
+ return util::nn_static_pointer_cast<CoordinateOperation>(op);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void Transformation::_exportToWKT(io::WKTFormatter *formatter) const {
+ exportTransformationToWKT(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void Transformation::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext(
+ formatter->abridgedTransformation() ? "AbridgedTransformation"
+ : "Transformation",
+ !identifiers().empty()));
+
+ writer->AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer->Add("unnamed");
+ } else {
+ writer->Add(l_name);
+ }
+
+ if (!formatter->abridgedTransformation()) {
+ writer->AddObjKey("source_crs");
+ formatter->setAllowIDInImmediateChild();
+ sourceCRS()->_exportToJSON(formatter);
+
+ writer->AddObjKey("target_crs");
+ formatter->setAllowIDInImmediateChild();
+ targetCRS()->_exportToJSON(formatter);
+
+ const auto &l_interpolationCRS = interpolationCRS();
+ if (l_interpolationCRS) {
+ writer->AddObjKey("interpolation_crs");
+ formatter->setAllowIDInImmediateChild();
+ l_interpolationCRS->_exportToJSON(formatter);
+ }
+ }
+
+ writer->AddObjKey("method");
+ formatter->setOmitTypeInImmediateChild();
+ formatter->setAllowIDInImmediateChild();
+ method()->_exportToJSON(formatter);
+
+ writer->AddObjKey("parameters");
+ {
+ auto parametersContext(writer->MakeArrayContext(false));
+ for (const auto &genOpParamvalue : parameterValues()) {
+ formatter->setAllowIDInImmediateChild();
+ formatter->setOmitTypeInImmediateChild();
+ genOpParamvalue->_exportToJSON(formatter);
+ }
+ }
+
+ if (!formatter->abridgedTransformation()) {
+ if (!coordinateOperationAccuracies().empty()) {
+ writer->AddObjKey("accuracy");
+ writer->Add(coordinateOperationAccuracies()[0]->value());
+ }
+ }
+
+ if (formatter->abridgedTransformation()) {
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+ } else {
+ ObjectUsage::baseExportToJSON(formatter);
+ }
+}
+
+//! @endcond
+
+//! @cond Doxygen_Suppress
+static const std::string nullString;
+
+static const std::string &_getNTv2Filename(const Transformation *op,
+ bool allowInverse) {
+
+ const auto &l_method = op->method();
+ if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV2 ||
+ (allowInverse &&
+ ci_equal(l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_NTV2))) {
+ const auto &fileParameter = op->parameterValue(
+ EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+const std::string &Transformation::getNTv2Filename() const {
+
+ return _getNTv2Filename(this, false);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const std::string &_getNTv1Filename(const Transformation *op,
+ bool allowInverse) {
+
+ const auto &l_method = op->method();
+ const auto &methodName = l_method->nameStr();
+ if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV1 ||
+ (allowInverse &&
+ ci_equal(methodName, INVERSE_OF + EPSG_NAME_METHOD_NTV1))) {
+ const auto &fileParameter = op->parameterValue(
+ EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const std::string &_getCTABLE2Filename(const Transformation *op,
+ bool allowInverse) {
+ const auto &l_method = op->method();
+ const auto &methodName = l_method->nameStr();
+ if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_CTABLE2) ||
+ (allowInverse &&
+ ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_CTABLE2))) {
+ const auto &fileParameter = op->parameterValue(
+ EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const std::string &
+_getHorizontalShiftGTIFFFilename(const Transformation *op, bool allowInverse) {
+ const auto &l_method = op->method();
+ const auto &methodName = l_method->nameStr();
+ if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF) ||
+ (allowInverse &&
+ ci_equal(methodName,
+ INVERSE_OF + PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF))) {
+ const auto &fileParameter = op->parameterValue(
+ EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const std::string &
+_getGeocentricTranslationFilename(const Transformation *op, bool allowInverse) {
+
+ const auto &l_method = op->method();
+ const auto &methodName = l_method->nameStr();
+ if (l_method->getEPSGCode() ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN ||
+ (allowInverse &&
+ ci_equal(
+ methodName,
+ INVERSE_OF +
+ EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN))) {
+ const auto &fileParameter =
+ op->parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE,
+ EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const std::string &
+_getHeightToGeographic3DFilename(const Transformation *op, bool allowInverse) {
+
+ const auto &methodName = op->method()->nameStr();
+
+ if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D) ||
+ (allowInverse &&
+ ci_equal(methodName,
+ INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D))) {
+ const auto &fileParameter =
+ op->parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static bool
+isGeographic3DToGravityRelatedHeight(const OperationMethodNNPtr &method,
+ bool allowInverse) {
+ const auto &methodName = method->nameStr();
+ static const char *const methodCodes[] = {
+ "1025", // Geographic3D to GravityRelatedHeight (EGM2008)
+ "1030", // Geographic3D to GravityRelatedHeight (NZgeoid)
+ "1045", // Geographic3D to GravityRelatedHeight (OSGM02-Ire)
+ "1047", // Geographic3D to GravityRelatedHeight (Gravsoft)
+ "1048", // Geographic3D to GravityRelatedHeight (Ausgeoid v2)
+ "1050", // Geographic3D to GravityRelatedHeight (CI)
+ "1059", // Geographic3D to GravityRelatedHeight (PNG)
+ "1060", // Geographic3D to GravityRelatedHeight (CGG2013)
+ "1072", // Geographic3D to GravityRelatedHeight (OSGM15-Ire)
+ "1073", // Geographic3D to GravityRelatedHeight (IGN2009)
+ "1081", // Geographic3D to GravityRelatedHeight (BEV AT)
+ "1083", // Geog3D to Geog2D+Vertical (AUSGeoid v2)
+ "9661", // Geographic3D to GravityRelatedHeight (EGM)
+ "9662", // Geographic3D to GravityRelatedHeight (Ausgeoid98)
+ "9663", // Geographic3D to GravityRelatedHeight (OSGM-GB)
+ "9664", // Geographic3D to GravityRelatedHeight (IGN1997)
+ "9665", // Geographic3D to GravityRelatedHeight (US .gtx)
+ "9635", // Geog3D to Geog2D+GravityRelatedHeight (US .gtx)
+ };
+
+ if (ci_find(methodName, "Geographic3D to GravityRelatedHeight") == 0) {
+ return true;
+ }
+ if (allowInverse &&
+ ci_find(methodName,
+ INVERSE_OF + "Geographic3D to GravityRelatedHeight") == 0) {
+ return true;
+ }
+
+ for (const auto &code : methodCodes) {
+ for (const auto &idSrc : method->identifiers()) {
+ const auto &srcAuthName = *(idSrc->codeSpace());
+ const auto &srcCode = idSrc->code();
+ if (ci_equal(srcAuthName, "EPSG") && srcCode == code) {
+ return true;
+ }
+ if (allowInverse && ci_equal(srcAuthName, "INVERSE(EPSG)") &&
+ srcCode == code) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+const std::string &Transformation::getHeightToGeographic3DFilename() const {
+
+ const std::string &ret = _getHeightToGeographic3DFilename(this, false);
+ if (!ret.empty())
+ return ret;
+ if (isGeographic3DToGravityRelatedHeight(method(), false)) {
+ const auto &fileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static util::PropertyMap
+createSimilarPropertiesMethod(common::IdentifiedObjectNNPtr obj) {
+ util::PropertyMap map;
+
+ const std::string &forwardName = obj->nameStr();
+ if (!forwardName.empty()) {
+ map.set(common::IdentifiedObject::NAME_KEY, forwardName);
+ }
+
+ {
+ auto ar = util::ArrayOfBaseObject::create();
+ for (const auto &idSrc : obj->identifiers()) {
+ const auto &srcAuthName = *(idSrc->codeSpace());
+ const auto &srcCode = idSrc->code();
+ auto idsProp = util::PropertyMap().set(
+ metadata::Identifier::CODESPACE_KEY, srcAuthName);
+ ar->add(metadata::Identifier::create(srcCode, idsProp));
+ }
+ if (!ar->empty()) {
+ map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar);
+ }
+ }
+
+ return map;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static util::PropertyMap
+createSimilarPropertiesTransformation(TransformationNNPtr obj) {
+ util::PropertyMap map;
+
+ // The domain(s) are unchanged
+ addDomains(map, obj.get());
+
+ const std::string &forwardName = obj->nameStr();
+ if (!forwardName.empty()) {
+ map.set(common::IdentifiedObject::NAME_KEY, forwardName);
+ }
+
+ const std::string &remarks = obj->remarks();
+ if (!remarks.empty()) {
+ map.set(common::IdentifiedObject::REMARKS_KEY, remarks);
+ }
+
+ addModifiedIdentifier(map, obj.get(), false, true);
+
+ return map;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static TransformationNNPtr
+createNTv1(const util::PropertyMap &properties,
+ const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
+ const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return Transformation::create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV1),
+ {OperationParameter::create(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY,
+ EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)
+ .set(metadata::Identifier::CODESPACE_KEY,
+ metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY,
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE))},
+ {ParameterValue::createFilename(filename)}, accuracies);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return an equivalent transformation to the current one, but using
+ * PROJ alternative grid names.
+ */
+TransformationNNPtr Transformation::substitutePROJAlternativeGridNames(
+ io::DatabaseContextNNPtr databaseContext) const {
+ auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>(
+ shared_from_this().as_nullable()));
+
+ const auto &l_method = method();
+ const int methodEPSGCode = l_method->getEPSGCode();
+
+ std::string projFilename;
+ std::string projGridFormat;
+ bool inverseDirection = false;
+
+ const auto &NTv1Filename = _getNTv1Filename(this, false);
+ const auto &NTv2Filename = _getNTv2Filename(this, false);
+ std::string lasFilename;
+ if (methodEPSGCode == EPSG_CODE_METHOD_NADCON) {
+ const auto &latitudeFileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE);
+ const auto &longitudeFileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE);
+ if (latitudeFileParameter &&
+ latitudeFileParameter->type() == ParameterValue::Type::FILENAME &&
+ longitudeFileParameter &&
+ longitudeFileParameter->type() == ParameterValue::Type::FILENAME) {
+ lasFilename = latitudeFileParameter->valueFile();
+ }
+ }
+ const auto &horizontalGridName =
+ !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty()
+ ? NTv2Filename
+ : lasFilename;
+
+ if (!horizontalGridName.empty() &&
+ databaseContext->lookForGridAlternative(horizontalGridName,
+ projFilename, projGridFormat,
+ inverseDirection)) {
+
+ if (horizontalGridName == projFilename) {
+ if (inverseDirection) {
+ throw util::UnsupportedOperationException(
+ "Inverse direction for " + projFilename + " not supported");
+ }
+ return self;
+ }
+
+ const auto &l_sourceCRS = sourceCRS();
+ const auto &l_targetCRS = targetCRS();
+ const auto &l_accuracies = coordinateOperationAccuracies();
+ if (projGridFormat == "GTiff") {
+ auto parameters =
+ std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)};
+ auto methodProperties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF);
+ auto values = std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename(projFilename)};
+ if (inverseDirection) {
+ return create(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ l_targetCRS, l_sourceCRS, nullptr,
+ methodProperties, parameters, values,
+ l_accuracies)
+ ->inverseAsTransformation();
+
+ } else {
+ return create(createSimilarPropertiesTransformation(self),
+ l_sourceCRS, l_targetCRS, nullptr,
+ methodProperties, parameters, values,
+ l_accuracies);
+ }
+ } else if (projGridFormat == "NTv1") {
+ if (inverseDirection) {
+ return createNTv1(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ l_targetCRS, l_sourceCRS, projFilename,
+ l_accuracies)
+ ->inverseAsTransformation();
+ } else {
+ return createNTv1(createSimilarPropertiesTransformation(self),
+ l_sourceCRS, l_targetCRS, projFilename,
+ l_accuracies);
+ }
+ } else if (projGridFormat == "NTv2") {
+ if (inverseDirection) {
+ return createNTv2(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ l_targetCRS, l_sourceCRS, projFilename,
+ l_accuracies)
+ ->inverseAsTransformation();
+ } else {
+ return createNTv2(createSimilarPropertiesTransformation(self),
+ l_sourceCRS, l_targetCRS, projFilename,
+ l_accuracies);
+ }
+ } else if (projGridFormat == "CTable2") {
+ auto parameters =
+ std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)};
+ auto methodProperties =
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ PROJ_WKT2_NAME_METHOD_CTABLE2);
+ auto values = std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename(projFilename)};
+ if (inverseDirection) {
+ return create(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ l_targetCRS, l_sourceCRS, nullptr,
+ methodProperties, parameters, values,
+ l_accuracies)
+ ->inverseAsTransformation();
+
+ } else {
+ return create(createSimilarPropertiesTransformation(self),
+ l_sourceCRS, l_targetCRS, nullptr,
+ methodProperties, parameters, values,
+ l_accuracies);
+ }
+ }
+ }
+
+ if (isGeographic3DToGravityRelatedHeight(method(), false)) {
+ const auto &fileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ auto filename = fileParameter->valueFile();
+ if (databaseContext->lookForGridAlternative(
+ filename, projFilename, projGridFormat, inverseDirection)) {
+
+ if (inverseDirection) {
+ throw util::UnsupportedOperationException(
+ "Inverse direction for "
+ "Geographic3DToGravityRelatedHeight not supported");
+ }
+
+ if (filename == projFilename) {
+ return self;
+ }
+
+ auto parameters = std::vector<OperationParameterNNPtr>{
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)};
+#ifdef disabled_for_now
+ if (inverseDirection) {
+ return create(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ targetCRS(), sourceCRS(), nullptr,
+ createSimilarPropertiesMethod(method()),
+ parameters, {ParameterValue::createFilename(
+ projFilename)},
+ coordinateOperationAccuracies())
+ ->inverseAsTransformation();
+ } else
+#endif
+ {
+ return create(
+ createSimilarPropertiesTransformation(self),
+ sourceCRS(), targetCRS(), nullptr,
+ createSimilarPropertiesMethod(method()), parameters,
+ {ParameterValue::createFilename(projFilename)},
+ coordinateOperationAccuracies());
+ }
+ }
+ }
+ }
+
+ const auto &geocentricTranslationFilename =
+ _getGeocentricTranslationFilename(this, false);
+ if (!geocentricTranslationFilename.empty()) {
+ if (databaseContext->lookForGridAlternative(
+ geocentricTranslationFilename, projFilename, projGridFormat,
+ inverseDirection)) {
+
+ if (inverseDirection) {
+ throw util::UnsupportedOperationException(
+ "Inverse direction for "
+ "GeocentricTranslation not supported");
+ }
+
+ if (geocentricTranslationFilename == projFilename) {
+ return self;
+ }
+
+ auto parameters =
+ std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE)};
+ return create(createSimilarPropertiesTransformation(self),
+ sourceCRS(), targetCRS(), interpolationCRS(),
+ createSimilarPropertiesMethod(method()), parameters,
+ {ParameterValue::createFilename(projFilename)},
+ coordinateOperationAccuracies());
+ }
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON ||
+ methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD ||
+ methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT ||
+ methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) {
+ auto fileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+
+ auto filename = fileParameter->valueFile();
+ if (databaseContext->lookForGridAlternative(
+ filename, projFilename, projGridFormat, inverseDirection)) {
+
+ if (filename == projFilename) {
+ if (inverseDirection) {
+ throw util::UnsupportedOperationException(
+ "Inverse direction for " + projFilename +
+ " not supported");
+ }
+ return self;
+ }
+
+ auto parameters = std::vector<OperationParameterNNPtr>{
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)};
+ if (inverseDirection) {
+ return create(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ targetCRS(), sourceCRS(), nullptr,
+ createSimilarPropertiesMethod(method()),
+ parameters, {ParameterValue::createFilename(
+ projFilename)},
+ coordinateOperationAccuracies())
+ ->inverseAsTransformation();
+ } else {
+ return create(
+ createSimilarPropertiesTransformation(self),
+ sourceCRS(), targetCRS(), nullptr,
+ createSimilarPropertiesMethod(method()), parameters,
+ {ParameterValue::createFilename(projFilename)},
+ coordinateOperationAccuracies());
+ }
+ }
+ }
+ }
+
+ return self;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static void ThrowExceptionNotGeodeticGeographic(const char *trfrm_name) {
+ throw io::FormattingException(concat("Can apply ", std::string(trfrm_name),
+ " only to GeodeticCRS / "
+ "GeographicCRS"));
+}
+
+// ---------------------------------------------------------------------------
+
+// If crs is a geographic CRS, or a compound CRS of a geographic CRS,
+// or a compoundCRS of a bound CRS of a geographic CRS, return that
+// geographic CRS
+static crs::GeographicCRSPtr
+extractGeographicCRSIfGeographicCRSOrEquivalent(const crs::CRSNNPtr &crs) {
+ auto geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(crs);
+ if (!geogCRS) {
+ auto compoundCRS = util::nn_dynamic_pointer_cast<crs::CompoundCRS>(crs);
+ if (compoundCRS) {
+ const auto &components = compoundCRS->componentReferenceSystems();
+ if (!components.empty()) {
+ geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ components[0]);
+ if (!geogCRS) {
+ auto boundCRS =
+ util::nn_dynamic_pointer_cast<crs::BoundCRS>(
+ components[0]);
+ if (boundCRS) {
+ geogCRS =
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ boundCRS->baseCRS());
+ }
+ }
+ }
+ } else {
+ auto boundCRS = util::nn_dynamic_pointer_cast<crs::BoundCRS>(crs);
+ if (boundCRS) {
+ geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ boundCRS->baseCRS());
+ }
+ }
+ }
+ return geogCRS;
+}
+
+// ---------------------------------------------------------------------------
+
+static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter,
+ const crs::CRSNNPtr &crs, bool addPushV3,
+ const char *trfrm_name) {
+ auto sourceCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
+ if (sourceCRSGeog) {
+ formatter->startInversion();
+ sourceCRSGeog->_exportToPROJString(formatter);
+ formatter->stopInversion();
+ if (util::isOfExactType<crs::DerivedGeographicCRS>(
+ *(sourceCRSGeog.get()))) {
+ // The export of a DerivedGeographicCRS in non-CRS mode adds
+ // unit conversion and axis swapping. We must compensate for that
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+ }
+
+ if (addPushV3) {
+ formatter->addStep("push");
+ formatter->addParam("v_3");
+ }
+
+ formatter->addStep("cart");
+ sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+ } else {
+ auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
+ if (!sourceCRSGeod) {
+ ThrowExceptionNotGeodeticGeographic(trfrm_name);
+ }
+ formatter->startInversion();
+ sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter);
+ formatter->stopInversion();
+ }
+}
+// ---------------------------------------------------------------------------
+
+static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter,
+ const crs::CRSNNPtr &crs, bool addPopV3,
+ const char *trfrm_name) {
+ auto targetCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
+ if (targetCRSGeog) {
+ formatter->addStep("cart");
+ formatter->setCurrentStepInverted(true);
+ targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+
+ if (addPopV3) {
+ formatter->addStep("pop");
+ formatter->addParam("v_3");
+ }
+ if (util::isOfExactType<crs::DerivedGeographicCRS>(
+ *(targetCRSGeog.get()))) {
+ // The export of a DerivedGeographicCRS in non-CRS mode adds
+ // unit conversion and axis swapping. We must compensate for that
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ }
+ targetCRSGeog->_exportToPROJString(formatter);
+ } else {
+ auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
+ if (!targetCRSGeod) {
+ ThrowExceptionNotGeodeticGeographic(trfrm_name);
+ }
+ targetCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter);
+ }
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+void Transformation::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const // throw(FormattingException)
+{
+ if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ throw io::FormattingException(
+ "Transformation cannot be exported as a PROJ.4 string");
+ }
+
+ formatter->setCoordinateOperationOptimizations(true);
+
+ bool positionVectorConvention = true;
+ bool sevenParamsTransform = false;
+ bool threeParamsTransform = false;
+ bool fifteenParamsTransform = false;
+ const auto &l_method = method();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ const auto &methodName = l_method->nameStr();
+ const auto paramCount = parameterValues().size();
+ const bool l_isTimeDependent = isTimeDependent(methodName);
+ const bool isPositionVector =
+ ci_find(methodName, "Position Vector") != std::string::npos ||
+ ci_find(methodName, "PV") != std::string::npos;
+ const bool isCoordinateFrame =
+ ci_find(methodName, "Coordinate Frame") != std::string::npos ||
+ ci_find(methodName, "CF") != std::string::npos;
+ if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ positionVectorConvention = false;
+ sevenParamsTransform = true;
+ } else if (
+ (paramCount == 15 && isCoordinateFrame && l_isTimeDependent) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ positionVectorConvention = false;
+ fifteenParamsTransform = true;
+ } else if ((paramCount == 7 && isPositionVector && !l_isTimeDependent) ||
+ methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ } else if (
+ (paramCount == 15 && isPositionVector && l_isTimeDependent) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ fifteenParamsTransform = true;
+ } else if ((paramCount == 3 &&
+ ci_find(methodName, "Geocentric translations") !=
+ std::string::npos) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
+ threeParamsTransform = true;
+ }
+ if (threeParamsTransform || sevenParamsTransform ||
+ fifteenParamsTransform) {
+ double x =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
+ double y =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
+ double z =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ const bool addPushPopV3 =
+ !CoordinateOperation::getPrivate()->use3DHelmert_ &&
+ ((sourceCRSGeog &&
+ sourceCRSGeog->coordinateSystem()->axisList().size() == 2) ||
+ (targetCRSGeog &&
+ targetCRSGeog->coordinateSystem()->axisList().size() == 2));
+
+ setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3,
+ "Helmert");
+
+ formatter->addStep("helmert");
+ formatter->addParam("x", x);
+ formatter->addParam("y", y);
+ formatter->addParam("z", z);
+ if (sevenParamsTransform || fifteenParamsTransform) {
+ double rx =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double ry =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double rz =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double scaleDiff =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
+ common::UnitOfMeasure::PARTS_PER_MILLION);
+ formatter->addParam("rx", rx);
+ formatter->addParam("ry", ry);
+ formatter->addParam("rz", rz);
+ formatter->addParam("s", scaleDiff);
+ if (fifteenParamsTransform) {
+ double rate_x = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR);
+ double rate_y = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR);
+ double rate_z = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR);
+ double rate_rx = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
+ double rate_ry = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
+ double rate_rz = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
+ double rate_scaleDiff = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE,
+ common::UnitOfMeasure::PPM_PER_YEAR);
+ double referenceEpochYear =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH,
+ common::UnitOfMeasure::YEAR);
+ formatter->addParam("dx", rate_x);
+ formatter->addParam("dy", rate_y);
+ formatter->addParam("dz", rate_z);
+ formatter->addParam("drx", rate_rx);
+ formatter->addParam("dry", rate_ry);
+ formatter->addParam("drz", rate_rz);
+ formatter->addParam("ds", rate_scaleDiff);
+ formatter->addParam("t_epoch", referenceEpochYear);
+ }
+ if (positionVectorConvention) {
+ formatter->addParam("convention", "position_vector");
+ } else {
+ formatter->addParam("convention", "coordinate_frame");
+ }
+ }
+
+ setupPROJGeodeticTargetCRS(formatter, targetCRS(), addPushPopV3,
+ "Helmert");
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D) {
+
+ positionVectorConvention =
+ isPositionVector ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D;
+
+ double x =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
+ double y =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
+ double z =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+ double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double scaleDiff =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
+ common::UnitOfMeasure::PARTS_PER_MILLION);
+
+ double px = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT);
+ double py = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT);
+ double pz = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT);
+
+ bool addPushPopV3 =
+ (methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D);
+
+ setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3,
+ "Molodensky-Badekas");
+
+ formatter->addStep("molobadekas");
+ formatter->addParam("x", x);
+ formatter->addParam("y", y);
+ formatter->addParam("z", z);
+ formatter->addParam("rx", rx);
+ formatter->addParam("ry", ry);
+ formatter->addParam("rz", rz);
+ formatter->addParam("s", scaleDiff);
+ formatter->addParam("px", px);
+ formatter->addParam("py", py);
+ formatter->addParam("pz", pz);
+ if (positionVectorConvention) {
+ formatter->addParam("convention", "position_vector");
+ } else {
+ formatter->addParam("convention", "coordinate_frame");
+ }
+
+ setupPROJGeodeticTargetCRS(formatter, targetCRS(), addPushPopV3,
+ "Molodensky-Badekas");
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
+ methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
+ double x =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
+ double y =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
+ double z =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+ double da = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE);
+ double df = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Molodensky only to GeographicCRS");
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Molodensky only to GeographicCRS");
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->_exportToPROJString(formatter);
+ formatter->stopInversion();
+
+ formatter->addStep("molodensky");
+ sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+ formatter->addParam("dx", x);
+ formatter->addParam("dy", y);
+ formatter->addParam("dz", z);
+ formatter->addParam("da", da);
+ formatter->addParam("df", df);
+
+ if (ci_find(methodName, "Abridged") != std::string::npos ||
+ methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
+ formatter->addParam("abridged");
+ }
+
+ targetCRSGeog->_exportToPROJString(formatter);
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
+ double offsetLat =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+ double offsetLong =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Geographic 2D offsets only to GeographicCRS");
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Geographic 2D offsets only to GeographicCRS");
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ if (offsetLat != 0.0 || offsetLong != 0.0) {
+ formatter->addStep("geogoffset");
+ formatter->addParam("dlat", offsetLat);
+ formatter->addParam("dlon", offsetLong);
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
+ double offsetLat =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+ double offsetLong =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+ double offsetHeight =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Geographic 3D offsets only to GeographicCRS");
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Geographic 3D offsets only to GeographicCRS");
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) {
+ formatter->addStep("geogoffset");
+ formatter->addParam("dlat", offsetLat);
+ formatter->addParam("dlon", offsetLong);
+ formatter->addParam("dh", offsetHeight);
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) {
+ double offsetLat =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+ double offsetLong =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+ double offsetHeight =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_UNDULATION);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ auto sourceCRSCompound =
+ dynamic_cast<const crs::CompoundCRS *>(sourceCRS().get());
+ if (sourceCRSCompound) {
+ sourceCRSGeog = sourceCRSCompound->extractGeographicCRS().get();
+ }
+ if (!sourceCRSGeog) {
+ throw io::FormattingException("Can apply Geographic 2D with "
+ "height offsets only to "
+ "GeographicCRS / CompoundCRS");
+ }
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ auto targetCRSCompound =
+ dynamic_cast<const crs::CompoundCRS *>(targetCRS().get());
+ if (targetCRSCompound) {
+ targetCRSGeog = targetCRSCompound->extractGeographicCRS().get();
+ }
+ if (!targetCRSGeog) {
+ throw io::FormattingException("Can apply Geographic 2D with "
+ "height offsets only to "
+ "GeographicCRS / CompoundCRS");
+ }
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) {
+ formatter->addStep("geogoffset");
+ formatter->addParam("dlat", offsetLat);
+ formatter->addParam("dlon", offsetLong);
+ formatter->addParam("dh", offsetHeight);
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) {
+
+ auto sourceCRSVert =
+ dynamic_cast<const crs::VerticalCRS *>(sourceCRS().get());
+ if (!sourceCRSVert) {
+ throw io::FormattingException(
+ "Can apply Vertical offset only to VerticalCRS");
+ }
+
+ auto targetCRSVert =
+ dynamic_cast<const crs::VerticalCRS *>(targetCRS().get());
+ if (!targetCRSVert) {
+ throw io::FormattingException(
+ "Can apply Vertical offset only to VerticalCRS");
+ }
+
+ auto offsetHeight =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
+
+ formatter->startInversion();
+ sourceCRSVert->addLinearUnitConvert(formatter);
+ formatter->stopInversion();
+
+ formatter->addStep("geogoffset");
+ formatter->addParam("dh", offsetHeight);
+
+ targetCRSVert->addLinearUnitConvert(formatter);
+
+ return;
+ }
+
+ // Substitute grid names with PROJ friendly names.
+ if (formatter->databaseContext()) {
+ auto alternate = substitutePROJAlternativeGridNames(
+ NN_NO_CHECK(formatter->databaseContext()));
+ auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>(
+ shared_from_this().as_nullable()));
+
+ if (alternate != self) {
+ alternate->_exportToPROJString(formatter);
+ return;
+ }
+ }
+
+ const bool isMethodInverseOf = starts_with(methodName, INVERSE_OF);
+
+ const auto &NTv1Filename = _getNTv1Filename(this, true);
+ const auto &NTv2Filename = _getNTv2Filename(this, true);
+ const auto &CTABLE2Filename = _getCTABLE2Filename(this, true);
+ const auto &HorizontalShiftGTIFFFilename =
+ _getHorizontalShiftGTIFFFilename(this, true);
+ const auto &hGridShiftFilename =
+ !HorizontalShiftGTIFFFilename.empty()
+ ? HorizontalShiftGTIFFFilename
+ : !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty()
+ ? NTv2Filename
+ : CTABLE2Filename;
+ if (!hGridShiftFilename.empty()) {
+ auto sourceCRSGeog =
+ extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ auto targetCRSGeog =
+ extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ if (isMethodInverseOf) {
+ formatter->startInversion();
+ }
+ formatter->addStep("hgridshift");
+ formatter->addParam("grids", hGridShiftFilename);
+ if (isMethodInverseOf) {
+ formatter->stopInversion();
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ const auto &geocentricTranslationFilename =
+ _getGeocentricTranslationFilename(this, true);
+ if (!geocentricTranslationFilename.empty()) {
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ const auto &interpCRS = interpolationCRS();
+ if (!interpCRS) {
+ throw io::FormattingException(
+ "InterpolationCRS required "
+ "for"
+ " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN);
+ }
+ const bool interpIsSrc = interpCRS->_isEquivalentTo(
+ sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT);
+ const bool interpIsTarget = interpCRS->_isEquivalentTo(
+ targetCRS().get(), util::IComparable::Criterion::EQUIVALENT);
+ if (!interpIsSrc && !interpIsTarget) {
+ throw io::FormattingException(
+ "For"
+ " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN
+ ", interpolation CRS should be the source or target CRS");
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ if (isMethodInverseOf) {
+ formatter->startInversion();
+ }
+
+ formatter->addStep("push");
+ formatter->addParam("v_3");
+
+ formatter->addStep("cart");
+ sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+
+ formatter->addStep("xyzgridshift");
+ formatter->addParam("grids", geocentricTranslationFilename);
+ formatter->addParam("grid_ref",
+ interpIsTarget ? "output_crs" : "input_crs");
+ (interpIsTarget ? targetCRSGeog : sourceCRSGeog)
+ ->ellipsoid()
+ ->_exportToPROJString(formatter);
+
+ formatter->startInversion();
+ formatter->addStep("cart");
+ targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+ formatter->stopInversion();
+
+ formatter->addStep("pop");
+ formatter->addParam("v_3");
+
+ if (isMethodInverseOf) {
+ formatter->stopInversion();
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ const auto &heightFilename = _getHeightToGeographic3DFilename(this, true);
+ if (!heightFilename.empty()) {
+ auto targetCRSGeog =
+ extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ if (!formatter->omitHorizontalConversionInVertTransformation()) {
+ formatter->startInversion();
+ formatter->pushOmitZUnitConversion();
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->popOmitZUnitConversion();
+ formatter->stopInversion();
+ }
+
+ if (isMethodInverseOf) {
+ formatter->startInversion();
+ }
+ formatter->addStep("vgridshift");
+ formatter->addParam("grids", heightFilename);
+ formatter->addParam("multiplier", 1.0);
+ if (isMethodInverseOf) {
+ formatter->stopInversion();
+ }
+
+ if (!formatter->omitHorizontalConversionInVertTransformation()) {
+ formatter->pushOmitZUnitConversion();
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->popOmitZUnitConversion();
+ }
+
+ return;
+ }
+
+ if (isGeographic3DToGravityRelatedHeight(method(), true)) {
+ auto fileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ auto filename = fileParameter->valueFile();
+
+ auto sourceCRSGeog =
+ extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ if (!formatter->omitHorizontalConversionInVertTransformation()) {
+ formatter->startInversion();
+ formatter->pushOmitZUnitConversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->popOmitZUnitConversion();
+ formatter->stopInversion();
+ }
+
+ bool doInversion = isMethodInverseOf;
+ // The EPSG Geog3DToHeight is the reverse convention of PROJ !
+ doInversion = !doInversion;
+ if (doInversion) {
+ formatter->startInversion();
+ }
+ formatter->addStep("vgridshift");
+ formatter->addParam("grids", filename);
+ formatter->addParam("multiplier", 1.0);
+ if (doInversion) {
+ formatter->stopInversion();
+ }
+
+ if (!formatter->omitHorizontalConversionInVertTransformation()) {
+ formatter->pushOmitZUnitConversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->popOmitZUnitConversion();
+ }
+
+ return;
+ }
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON) {
+ auto fileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ formatter->addStep("vgridshift");
+ formatter->addParam("grids", fileParameter->valueFile());
+ if (fileParameter->valueFile().find(".tif") != std::string::npos) {
+ formatter->addParam("multiplier", 1.0);
+ } else {
+ // The vertcon grids go from NGVD 29 to NAVD 88, with units
+ // in millimeter (see
+ // https://github.com/OSGeo/proj.4/issues/1071), for gtx files
+ formatter->addParam("multiplier", 0.001);
+ }
+ return;
+ }
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD ||
+ methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT ||
+ methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) {
+ auto fileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ formatter->addStep("vgridshift");
+ formatter->addParam("grids", fileParameter->valueFile());
+ formatter->addParam("multiplier", 1.0);
+ return;
+ }
+ }
+
+ if (isLongitudeRotation()) {
+ double offsetDeg =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
+ common::UnitOfMeasure::DEGREE);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName + " only to GeographicCRS"));
+ }
+
+ if (!sourceCRSGeog->ellipsoid()->_isEquivalentTo(
+ targetCRSGeog->ellipsoid().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ // This is arguable if we should check this...
+ throw io::FormattingException("Can apply Longitude rotation "
+ "only to SRS with same "
+ "ellipsoid");
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ bool done = false;
+ if (offsetDeg != 0.0) {
+ // Optimization: as we are doing nominally a +step=inv,
+ // if the negation of the offset value is a well-known name,
+ // then use forward case with this name.
+ auto projPMName = datum::PrimeMeridian::getPROJStringWellKnownName(
+ common::Angle(-offsetDeg));
+ if (!projPMName.empty()) {
+ done = true;
+ formatter->addStep("longlat");
+ sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+ formatter->addParam("pm", projPMName);
+ }
+ }
+ if (!done) {
+ // To actually add the offset, we must use the reverse longlat
+ // operation.
+ formatter->startInversion();
+ formatter->addStep("longlat");
+ sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+ datum::PrimeMeridian::create(util::PropertyMap(),
+ common::Angle(offsetDeg))
+ ->_exportToPROJString(formatter);
+ formatter->stopInversion();
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ if (exportToPROJStringGeneric(formatter)) {
+ return;
+ }
+
+ throw io::FormattingException("Unimplemented");
+}
+
+} // namespace crs
+NS_PROJ_END
diff --git a/src/iso19111/operation/vectorofvaluesparams.cpp b/src/iso19111/operation/vectorofvaluesparams.cpp
new file mode 100644
index 00000000..e70ecced
--- /dev/null
+++ b/src/iso19111/operation/vectorofvaluesparams.cpp
@@ -0,0 +1,116 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#include "vectorofvaluesparams.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static std::vector<ParameterValueNNPtr> buildParameterValueFromMeasure(
+ const std::initializer_list<common::Measure> &list) {
+ std::vector<ParameterValueNNPtr> res;
+ for (const auto &v : list) {
+ res.emplace_back(ParameterValue::create(v));
+ }
+ return res;
+}
+
+VectorOfValues::VectorOfValues(std::initializer_list<common::Measure> list)
+ : std::vector<ParameterValueNNPtr>(buildParameterValueFromMeasure(list)) {}
+
+// This way, we disable inlining of destruction, and save a lot of space
+VectorOfValues::~VectorOfValues() = default;
+
+VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3) {
+ return VectorOfValues{ParameterValue::create(m1),
+ ParameterValue::create(m2),
+ ParameterValue::create(m3)};
+}
+
+VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3,
+ const common::Measure &m4) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4)};
+}
+
+VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3,
+ const common::Measure &m4,
+ const common::Measure &m5) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4),
+ ParameterValue::create(m5),
+ };
+}
+
+VectorOfValues
+createParams(const common::Measure &m1, const common::Measure &m2,
+ const common::Measure &m3, const common::Measure &m4,
+ const common::Measure &m5, const common::Measure &m6) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4),
+ ParameterValue::create(m5), ParameterValue::create(m6),
+ };
+}
+
+VectorOfValues
+createParams(const common::Measure &m1, const common::Measure &m2,
+ const common::Measure &m3, const common::Measure &m4,
+ const common::Measure &m5, const common::Measure &m6,
+ const common::Measure &m7) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4),
+ ParameterValue::create(m5), ParameterValue::create(m6),
+ ParameterValue::create(m7),
+ };
+}
+
+// This way, we disable inlining of destruction, and save a lot of space
+VectorOfParameters::~VectorOfParameters() = default;
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/src/iso19111/operation/vectorofvaluesparams.hpp b/src/iso19111/operation/vectorofvaluesparams.hpp
new file mode 100644
index 00000000..6f12543b
--- /dev/null
+++ b/src/iso19111/operation/vectorofvaluesparams.hpp
@@ -0,0 +1,101 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef VECTOROFVALUESPARAMS_HPP
+#define VECTOROFVALUESPARAMS_HPP
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/util.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+struct VectorOfValues : public std::vector<ParameterValueNNPtr> {
+ VectorOfValues() : std::vector<ParameterValueNNPtr>() {}
+ explicit VectorOfValues(std::initializer_list<ParameterValueNNPtr> list)
+ : std::vector<ParameterValueNNPtr>(list) {}
+
+ explicit VectorOfValues(std::initializer_list<common::Measure> list);
+ VectorOfValues(const VectorOfValues &) = delete;
+ VectorOfValues(VectorOfValues &&) = default;
+
+ ~VectorOfValues();
+};
+
+VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3);
+
+VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3,
+ const common::Measure &m4);
+
+VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3,
+ const common::Measure &m4,
+ const common::Measure &m5);
+
+VectorOfValues
+createParams(const common::Measure &m1, const common::Measure &m2,
+ const common::Measure &m3, const common::Measure &m4,
+ const common::Measure &m5, const common::Measure &m6);
+
+VectorOfValues
+createParams(const common::Measure &m1, const common::Measure &m2,
+ const common::Measure &m3, const common::Measure &m4,
+ const common::Measure &m5, const common::Measure &m6,
+ const common::Measure &m7);
+
+// ---------------------------------------------------------------------------
+
+struct VectorOfParameters : public std::vector<OperationParameterNNPtr> {
+ VectorOfParameters() : std::vector<OperationParameterNNPtr>() {}
+ explicit VectorOfParameters(
+ std::initializer_list<OperationParameterNNPtr> list)
+ : std::vector<OperationParameterNNPtr>(list) {}
+ VectorOfParameters(const VectorOfParameters &) = delete;
+
+ ~VectorOfParameters();
+};
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // VECTOROFVALUESPARAMS_HPP
diff --git a/src/iso19111/static.cpp b/src/iso19111/static.cpp
index 11306e35..d099cd6c 100644
--- a/src/iso19111/static.cpp
+++ b/src/iso19111/static.cpp
@@ -39,6 +39,7 @@
#include "proj/metadata.hpp"
#include "proj/util.hpp"
+#include "operation/oputils.hpp"
#include "proj/internal/coordinatesystem_internal.hpp"
#include "proj/internal/io_internal.hpp"
@@ -653,6 +654,17 @@ const GeographicCRSNNPtr
const std::string
operation::CoordinateOperation::OPERATION_VERSION_KEY("operationVersion");
+//! @cond Doxygen_Suppress
+const common::Measure operation::nullMeasure{};
+
+const std::string operation::INVERSE_OF = "Inverse of ";
+
+const std::string operation::AXIS_ORDER_CHANGE_2D_NAME =
+ "axis order change (2D)";
+const std::string operation::AXIS_ORDER_CHANGE_3D_NAME =
+ "axis order change (geographic3D horizontal)";
+//! @endcond
+
// ---------------------------------------------------------------------------
NS_PROJ_END
diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake
index 3a4880c6..95bb222a 100644
--- a/src/lib_proj.cmake
+++ b/src/lib_proj.cmake
@@ -199,11 +199,20 @@ set(SRC_LIBPROJ_ISO19111
iso19111/crs.cpp
iso19111/datum.cpp
iso19111/coordinatesystem.cpp
- iso19111/coordinateoperation.cpp
iso19111/io.cpp
iso19111/internal.cpp
iso19111/factory.cpp
iso19111/c_api.cpp
+ iso19111/operation/concatenatedoperation.cpp
+ iso19111/operation/coordinateoperationfactory.cpp
+ iso19111/operation/conversion.cpp
+ iso19111/operation/esriparammappings.cpp
+ iso19111/operation/oputils.cpp
+ iso19111/operation/parammappings.cpp
+ iso19111/operation/projbasedoperation.cpp
+ iso19111/operation/singleoperation.cpp
+ iso19111/operation/transformation.cpp
+ iso19111/operation/vectorofvaluesparams.cpp
)
set(SRC_LIBPROJ_CORE
diff --git a/src/log.cpp b/src/log.cpp
index c343e65b..c50b0ebc 100644
--- a/src/log.cpp
+++ b/src/log.cpp
@@ -96,3 +96,93 @@ void pj_log( PJ_CONTEXT *ctx, int level, const char *fmt, ... )
pj_vlog( ctx, level, fmt, args );
va_end( args );
}
+
+
+
+/***************************************************************************************/
+PJ_LOG_LEVEL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level) {
+/****************************************************************************************
+ Set logging level 0-3. Higher number means more debug info. 0 turns it off
+****************************************************************************************/
+ PJ_LOG_LEVEL previous;
+ if (nullptr==ctx)
+ ctx = pj_get_default_ctx();
+ if (nullptr==ctx)
+ return PJ_LOG_TELL;
+ previous = static_cast<PJ_LOG_LEVEL>(abs (ctx->debug_level));
+ if (PJ_LOG_TELL==log_level)
+ return previous;
+ ctx->debug_level = log_level;
+ return previous;
+}
+
+/*****************************************************************************/
+static std::string add_short_name_prefix(const PJ* P, const char* fmt)
+/*****************************************************************************/
+{
+ if( P->short_name == nullptr )
+ return fmt;
+ std::string ret(P->short_name);
+ ret += ": ";
+ ret += fmt;
+ return ret;
+}
+
+/*****************************************************************************/
+void proj_log_error (const PJ *P, const char *fmt, ...) {
+/******************************************************************************
+ For reporting the most severe events.
+******************************************************************************/
+ va_list args;
+ va_start( args, fmt );
+ pj_vlog (pj_get_ctx ((PJ*)P), PJ_LOG_ERROR , add_short_name_prefix(P, fmt).c_str(), args);
+ va_end( args );
+}
+
+
+/*****************************************************************************/
+void proj_log_debug (PJ *P, const char *fmt, ...) {
+/******************************************************************************
+ For reporting debugging information.
+******************************************************************************/
+ va_list args;
+ va_start( args, fmt );
+ pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG , add_short_name_prefix(P, fmt).c_str(), args);
+ va_end( args );
+}
+
+/*****************************************************************************/
+void proj_context_log_debug (PJ_CONTEXT *ctx, const char *fmt, ...) {
+/******************************************************************************
+ For reporting debugging information.
+******************************************************************************/
+ va_list args;
+ va_start( args, fmt );
+ pj_vlog (ctx, PJ_LOG_DEBUG , fmt, args);
+ va_end( args );
+}
+
+/*****************************************************************************/
+void proj_log_trace (PJ *P, const char *fmt, ...) {
+/******************************************************************************
+ For reporting embarrassingly detailed debugging information.
+******************************************************************************/
+ va_list args;
+ va_start( args, fmt );
+ pj_vlog (pj_get_ctx (P), PJ_LOG_TRACE , add_short_name_prefix(P, fmt).c_str(), args);
+ va_end( args );
+}
+
+
+/*****************************************************************************/
+void proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf) {
+/******************************************************************************
+ Put a new logging function into P's context. The opaque object app_data is
+ passed as first arg at each call to the logger
+******************************************************************************/
+ if (nullptr==ctx)
+ ctx = pj_get_default_ctx ();
+ ctx->logger_app_data = app_data;
+ if (nullptr!=logf)
+ ctx->logger = logf;
+}
diff --git a/src/mlfn.hpp b/src/mlfn.hpp
index 228c65a4..426dcb0e 100644
--- a/src/mlfn.hpp
+++ b/src/mlfn.hpp
@@ -66,7 +66,7 @@ inline_pj_inv_mlfn(PJ_CONTEXT *ctx, double arg, double es, const double *en,
}
*sinphi = s;
*cosphi = c;
- proj_context_errno_set( ctx, PJD_ERR_NON_CONV_INV_MERI_DIST );
+ proj_context_errno_set( ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
return phi;
}
diff --git a/src/networkfilemanager.cpp b/src/networkfilemanager.cpp
index 9edffaad..102d3d15 100644
--- a/src/networkfilemanager.cpp
+++ b/src/networkfilemanager.cpp
@@ -1315,7 +1315,7 @@ std::unique_ptr<File> NetworkFile::open(PJ_CONTEXT *ctx, const char *filename) {
errorBuffer.resize(strlen(errorBuffer.data()));
pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", filename,
errorBuffer.c_str());
- proj_context_errno_set(ctx, PJD_ERR_NETWORK_ERROR);
+ proj_context_errno_set(ctx, PROJ_ERR_OTHER_NETWORK_ERROR);
}
bool ok = false;
@@ -1404,7 +1404,7 @@ size_t NetworkFile::read(void *buffer, size_t sizeBytes) {
&nRead, errorBuffer.size(), &errorBuffer[0],
m_ctx->networking.user_data);
if (!m_handle) {
- proj_context_errno_set(m_ctx, PJD_ERR_NETWORK_ERROR);
+ proj_context_errno_set(m_ctx, PROJ_ERR_OTHER_NETWORK_ERROR);
return 0;
}
} else {
@@ -1420,7 +1420,7 @@ size_t NetworkFile::read(void *buffer, size_t sizeBytes) {
pj_log(m_ctx, PJ_LOG_ERROR, "Cannot read in %s: %s",
m_url.c_str(), errorBuffer.c_str());
}
- proj_context_errno_set(m_ctx, PJD_ERR_NETWORK_ERROR);
+ proj_context_errno_set(m_ctx, PROJ_ERR_OTHER_NETWORK_ERROR);
return 0;
}
diff --git a/src/param.cpp b/src/param.cpp
index 28d6bc3e..a716f9b9 100644
--- a/src/param.cpp
+++ b/src/param.cpp
@@ -220,7 +220,7 @@ PROJVALUE pj_param (PJ_CONTEXT *ctx, paralist *pl, const char *opt) {
value.i = 1;
break;
default:
- proj_context_errno_set (ctx, PJD_ERR_INVALID_BOOLEAN_PARAM);
+ proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
value.i = 0;
break;
}
diff --git a/src/phi2.cpp b/src/phi2.cpp
index 2f258e47..23793557 100644
--- a/src/phi2.cpp
+++ b/src/phi2.cpp
@@ -103,7 +103,7 @@ double pj_sinhpsi2tanphi(PJ_CONTEXT *ctx, const double taup, const double e) {
break;
}
if (i == 0)
- proj_context_errno_set(ctx, PJD_ERR_NON_CONV_SINHPSI2TANPHI);
+ proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
return tau;
}
diff --git a/src/pipeline.cpp b/src/pipeline.cpp
index e9d11604..b1989824 100644
--- a/src/pipeline.cpp
+++ b/src/pipeline.cpp
@@ -96,7 +96,6 @@ Thomas Knudsen, thokn@sdfe.dk, 2016-05-20
#define PJ_LIB__
-#include <errno.h>
#include <math.h>
#include <stddef.h>
#include <string.h>
@@ -430,8 +429,8 @@ PJ *OPERATION(pipeline,0) {
// proj=pipeline step "x="""," u=" proj=pipeline step ste=""[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline p step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ""x="""""""""""
// Probably an issue with the quoting handling code
// But doesn't hurt to add an extra safety check
- proj_log_error (P, "Pipeline: too deep recursion");
- return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: nested pipelines */
+ proj_log_error (P, _("Pipeline: too deep recursion"));
+ return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: nested pipelines */
}
P->fwd4d = pipeline_forward_4d;
@@ -453,24 +452,24 @@ PJ *OPERATION(pipeline,0) {
P->opaque = new (std::nothrow) Pipeline();
if (nullptr==P->opaque)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_INVALID_OP /* ENOMEM */);
argc = (int)argc_params (P->params);
auto pipeline = static_cast<struct Pipeline*>(P->opaque);
pipeline->argv = argv = argv_params (P->params, argc);
if (nullptr==argv)
- return destructor (P, ENOMEM);
+ return destructor (P, PROJ_ERR_INVALID_OP /* ENOMEM */);
pipeline->current_argv = current_argv = static_cast<char**>(calloc (argc, sizeof (char *)));
if (nullptr==current_argv)
- return destructor (P, ENOMEM);
+ return destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
/* Do some syntactical sanity checking */
for (i = 0; i < argc; i++) {
if (0==strcmp (argv_sentinel, argv[i])) {
if (-1==i_pipeline) {
- proj_log_error (P, "Pipeline: +step before +proj=pipeline");
- return destructor (P, PJD_ERR_MALFORMED_PIPELINE);
+ proj_log_error (P, _("Pipeline: +step before +proj=pipeline"));
+ return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX);
}
if (0==nsteps)
i_first_step = i;
@@ -480,8 +479,8 @@ PJ *OPERATION(pipeline,0) {
if (0==strcmp ("proj=pipeline", argv[i])) {
if (-1 != i_pipeline) {
- proj_log_error (P, "Pipeline: Nesting only allowed when child pipelines are wrapped in '+init's");
- return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: nested pipelines */
+ proj_log_error (P, _("Pipeline: Nesting only allowed when child pipelines are wrapped in '+init's"));
+ return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: nested pipelines */
}
i_pipeline = i;
}
@@ -489,10 +488,10 @@ PJ *OPERATION(pipeline,0) {
nsteps--; /* Last instance of +step is just a sentinel */
if (-1==i_pipeline)
- return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: no pipeline def */
+ return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: no pipeline def */
if (0==nsteps)
- return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: no pipeline def */
+ return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: no pipeline def */
set_ellipsoid(P);
@@ -532,8 +531,8 @@ PJ *OPERATION(pipeline,0) {
/* The step init failed, but possibly without setting errno. If so, we say "malformed" */
int err_to_report = proj_errno(P);
if (0==err_to_report)
- err_to_report = PJD_ERR_MALFORMED_PIPELINE;
- proj_log_error (P, "Pipeline: Bad step definition: %s (%s)", current_argv[0], proj_errno_string (err_to_report));
+ err_to_report = PROJ_ERR_INVALID_OP_WRONG_SYNTAX;
+ proj_log_error (P, _("Pipeline: Bad step definition: %s (%s)"), current_argv[0], proj_context_errno_string (P->ctx, err_to_report));
return destructor (P, err_to_report); /* ERROR: bad pipeline def */
}
next_step->parent = P;
@@ -562,8 +561,8 @@ PJ *OPERATION(pipeline,0) {
(!Q->inverted && (Q->fwd || Q->fwd3d || Q->fwd4d) ) ) {
continue;
} else {
- proj_log_error (P, "Pipeline: A forward operation couldn't be constructed");
- return destructor (P, PJD_ERR_MALFORMED_PIPELINE);
+ proj_log_error (P, _("Pipeline: A forward operation couldn't be constructed"));
+ return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX);
}
}
@@ -612,8 +611,8 @@ PJ *OPERATION(pipeline,0) {
continue;
if ( curr_step_output != next_step_input ) {
- proj_log_error (P, "Pipeline: Mismatched units between step %d and %d", i+1, i+2);
- return destructor (P, PJD_ERR_MALFORMED_PIPELINE);
+ proj_log_error (P, _("Pipeline: Mismatched units between step %d and %d"), i+1, i+2);
+ return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX);
}
}
@@ -682,7 +681,7 @@ static PJ *setup_pushpop(PJ *P) {
auto pushpop = static_cast<struct PushPop*>(calloc (1, sizeof(struct PushPop)));
P->opaque = pushpop;
if (nullptr==P->opaque)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
if (pj_param_exists(P->params, "v_1"))
pushpop->v1 = true;
diff --git a/src/proj.h b/src/proj.h
index d4defc47..b16f54ea 100644
--- a/src/proj.h
+++ b/src/proj.h
@@ -610,6 +610,33 @@ double PROJ_DLL proj_xyz_dist (PJ_COORD a, PJ_COORD b);
/* Geodesic distance (in meter) + fwd and rev azimuth between two points on the ellipsoid */
PJ_COORD PROJ_DLL proj_geod (const PJ *P, PJ_COORD a, PJ_COORD b);
+/* PROJ error codes */
+
+/** Error codes typically related to coordinate operation initalization
+ * Note: some of them can also be emitted during coordinate transformation,
+ * like PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID in case the resource loading
+ * is differed until it is really needed.
+ */
+#define PROJ_ERR_INVALID_OP 1024 /* other/unspecified error related to coordinate operation initialization */
+#define PROJ_ERR_INVALID_OP_WRONG_SYNTAX (PROJ_ERR_INVALID_OP+1) /* invalid pipeline structure, missing +proj argument, etc */
+#define PROJ_ERR_INVALID_OP_MISSING_ARG (PROJ_ERR_INVALID_OP+2) /* missing required operation parameter */
+#define PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE (PROJ_ERR_INVALID_OP+3) /* one of the operation parameter has an illegal value */
+#define PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS (PROJ_ERR_INVALID_OP+4) /* mutually exclusive arguments */
+#define PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID (PROJ_ERR_INVALID_OP+5) /* file not found (particular case of PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE) */
+
+/** Error codes related to transformation on a specific coordinate */
+#define PROJ_ERR_COORD_TRANSFM 2048 /* other error related to coordinate transformation */
+#define PROJ_ERR_COORD_TRANSFM_INVALID_COORD (PROJ_ERR_COORD_TRANSFM+1) /* for e.g lat > 90deg */
+#define PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN (PROJ_ERR_COORD_TRANSFM+2) /* coordinate is outside of the projection domain. e.g approximate mercator with |longitude - lon_0| > 90deg, or iterative convergence method failed */
+#define PROJ_ERR_COORD_TRANSFM_NO_OPERATION (PROJ_ERR_COORD_TRANSFM+3) /* no operation found, e.g if no match the required accuracy, or if ballpark transformations were asked to not be used and they would be only such candidate */
+#define PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID (PROJ_ERR_COORD_TRANSFM+4) /* point to transform falls outside grid or subgrid */
+#define PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA (PROJ_ERR_COORD_TRANSFM+5) /* point to transform falls in a grid cell that evaluates to nodata */
+
+/** Other type of errors */
+#define PROJ_ERR_OTHER 4096
+#define PROJ_ERR_OTHER_API_MISUSE (PROJ_ERR_OTHER+1) /* error related to a misuse of PROJ API */
+#define PROJ_ERR_OTHER_NO_INVERSE_OP (PROJ_ERR_OTHER+2) /* no inverse method available */
+#define PROJ_ERR_OTHER_NETWORK_ERROR (PROJ_ERR_OTHER+3) /* failure when accessing a network resource */
/* Set or read error level */
int PROJ_DLL proj_context_errno (PJ_CONTEXT *ctx);
@@ -617,7 +644,8 @@ int PROJ_DLL proj_errno (const PJ *P);
int PROJ_DLL proj_errno_set (const PJ *P, int err);
int PROJ_DLL proj_errno_reset (const PJ *P);
int PROJ_DLL proj_errno_restore (const PJ *P, int err);
-const char PROJ_DLL * proj_errno_string (int err);
+const char PROJ_DLL * proj_errno_string (int err); /* deprecated. use proj_context_errno_string() */
+const char PROJ_DLL * proj_context_errno_string(PJ_CONTEXT* ctx, int err);
PJ_LOG_LEVEL PROJ_DLL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level);
void PROJ_DLL proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf);
diff --git a/src/proj_internal.h b/src/proj_internal.h
index 32aaa1ec..795db036 100644
--- a/src/proj_internal.h
+++ b/src/proj_internal.h
@@ -189,7 +189,10 @@ PJ_COORD pj_inv4d (PJ_COORD coo, PJ *P);
PJ_COORD PROJ_DLL pj_approx_2D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo);
PJ_COORD PROJ_DLL pj_approx_3D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo);
-void PROJ_DLL proj_log_error (PJ *P, const char *fmt, ...);
+/* Provision for gettext translatable strings */
+#define _(str) (str)
+
+void PROJ_DLL proj_log_error (const PJ *P, const char *fmt, ...);
void proj_log_debug (PJ *P, const char *fmt, ...);
void proj_log_trace (PJ *P, const char *fmt, ...);
@@ -351,6 +354,7 @@ struct PJconsts {
**************************************************************************************/
PJ_CONTEXT *ctx = nullptr;
+ const char *short_name = nullptr; /* From pj_list.h */
const char *descr = nullptr; /* From pj_list.h or individual PJ_*.c file */
paralist *params = nullptr; /* Parameter list */
char *def_full = nullptr; /* Full textual definition (usually 0 - set by proj_pj_info) */
@@ -598,72 +602,6 @@ struct FACTORS {
int code; /* always 0 */
};
-/* library errors */
-#define PJD_ERR_NO_ARGS -1
-#define PJD_ERR_NO_OPTION_IN_INIT_FILE -2
-#define PJD_ERR_NO_COLON_IN_INIT_STRING -3
-#define PJD_ERR_PROJ_NOT_NAMED -4
-#define PJD_ERR_UNKNOWN_PROJECTION_ID -5
-#define PJD_ERR_INVALID_ECCENTRICITY -6
-#define PJD_ERR_UNKNOWN_UNIT_ID -7
-#define PJD_ERR_INVALID_BOOLEAN_PARAM -8
-#define PJD_ERR_UNKNOWN_ELLP_PARAM -9
-#define PJD_ERR_REV_FLATTENING_IS_ZERO -10
-#define PJD_ERR_REF_RAD_LARGER_THAN_90 -11
-#define PJD_ERR_ES_LESS_THAN_ZERO -12
-#define PJD_ERR_MAJOR_AXIS_NOT_GIVEN -13
-#define PJD_ERR_LAT_OR_LON_EXCEED_LIMIT -14
-#define PJD_ERR_INVALID_X_OR_Y -15
-#define PJD_ERR_WRONG_FORMAT_DMS_VALUE -16
-#define PJD_ERR_NON_CONV_INV_MERI_DIST -17
-#define PJD_ERR_NON_CONV_SINHPSI2TANPHI -18
-#define PJD_ERR_ACOS_ASIN_ARG_TOO_LARGE -19
-#define PJD_ERR_TOLERANCE_CONDITION -20
-#define PJD_ERR_CONIC_LAT_EQUAL -21
-#define PJD_ERR_LAT_LARGER_THAN_90 -22
-#define PJD_ERR_LAT1_IS_ZERO -23
-#define PJD_ERR_LAT_TS_LARGER_THAN_90 -24
-#define PJD_ERR_CONTROL_POINT_NO_DIST -25
-#define PJD_ERR_NO_ROTATION_PROJ -26
-#define PJD_ERR_W_OR_M_ZERO_OR_LESS -27
-#define PJD_ERR_LSAT_NOT_IN_RANGE -28
-#define PJD_ERR_PATH_NOT_IN_RANGE -29
-#define PJD_ERR_INVALID_H -30
-#define PJD_ERR_K_LESS_THAN_ZERO -31
-#define PJD_ERR_LAT_1_OR_2_ZERO_OR_90 -32
-#define PJD_ERR_LAT_0_OR_ALPHA_EQ_90 -33
-#define PJD_ERR_ELLIPSOID_USE_REQUIRED -34
-#define PJD_ERR_INVALID_UTM_ZONE -35
-/* -36 no longer used */
-#define PJD_ERR_FAILED_TO_FIND_PROJ -37
-#define PJD_ERR_FAILED_TO_LOAD_GRID -38
-#define PJD_ERR_INVALID_M_OR_N -39
-#define PJD_ERR_N_OUT_OF_RANGE -40
-#define PJD_ERR_LAT_1_2_UNSPECIFIED -41
-#define PJD_ERR_ABS_LAT1_EQ_ABS_LAT2 -42
-#define PJD_ERR_LAT_0_HALF_PI_FROM_MEAN -43
-#define PJD_ERR_UNPARSEABLE_CS_DEF -44
-#define PJD_ERR_GEOCENTRIC -45
-#define PJD_ERR_UNKNOWN_PRIME_MERIDIAN -46
-#define PJD_ERR_AXIS -47
-#define PJD_ERR_GRID_AREA -48
-#define PJD_ERR_INVALID_SWEEP_AXIS -49
-#define PJD_ERR_MALFORMED_PIPELINE -50
-#define PJD_ERR_UNIT_FACTOR_LESS_THAN_0 -51
-#define PJD_ERR_INVALID_SCALE -52
-#define PJD_ERR_NON_CONVERGENT -53
-#define PJD_ERR_MISSING_ARGS -54
-#define PJD_ERR_LAT_0_IS_ZERO -55
-#define PJD_ERR_ELLIPSOIDAL_UNSUPPORTED -56
-#define PJD_ERR_TOO_MANY_INITS -57
-#define PJD_ERR_INVALID_ARG -58
-#define PJD_ERR_INCONSISTENT_UNIT -59
-#define PJD_ERR_MUTUALLY_EXCLUSIVE_ARGS -60
-#define PJD_ERR_GENERIC_ERROR -61
-#define PJD_ERR_NETWORK_ERROR -62
-/* NOTE: Remember to update src/strerrno.cpp, src/apps/gie.cpp and transient_error in */
-/* src/transform.cpp when adding new value */
-
// Legacy
struct projFileAPI_t;
@@ -707,6 +645,7 @@ struct projFileApiCallbackAndData
/* proj thread context */
struct pj_ctx{
+ std::string lastFullErrorMessage{}; // used by proj_context_errno_string
int last_errno = 0;
int debug_level = 0;
void (*logger)(void *, int, const char *) = nullptr;
@@ -781,6 +720,7 @@ C_NAMESPACE PJ *pj_##name (PJ *P) { \
P = pj_new(); \
if (nullptr==P) \
return nullptr; \
+ P->short_name = #name; \
P->descr = des_##name; \
P->need_ellps = NEED_ELLPS; \
P->left = PJ_IO_UNITS_RADIANS; \
@@ -922,15 +862,6 @@ PJ_LP pj_generic_inverse_2d(PJ_XY xy, PJ *P, PJ_LP lpInitial);
extern char const PROJ_DLL pj_release[]; /* global release id string */
-#ifndef PROJ_INTERNAL_H
-/* replaced by enum proj_log_level in proj_internal.h */
-#define PJ_LOG_NONE 0
-#define PJ_LOG_ERROR 1
-#define PJ_LOG_DEBUG_MAJOR 2
-#define PJ_LOG_DEBUG_MINOR 3
-#endif
-
-
/* procedure prototypes */
PJ_CONTEXT PROJ_DLL *pj_get_default_ctx(void);
diff --git a/src/proj_mdist.cpp b/src/proj_mdist.cpp
index ed07ffd1..9a819861 100644
--- a/src/proj_mdist.cpp
+++ b/src/proj_mdist.cpp
@@ -124,6 +124,6 @@ proj_inv_mdist(PJ_CONTEXT *ctx, double dist, const void *data) {
return phi;
}
/* convergence failed */
- proj_context_errno_set(ctx, PJD_ERR_NON_CONV_INV_MERI_DIST);
+ proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return phi;
}
diff --git a/src/projections/adams.cpp b/src/projections/adams.cpp
index d1217ff1..389ca054 100644
--- a/src/projections/adams.cpp
+++ b/src/projections/adams.cpp
@@ -99,7 +99,7 @@ static PJ_XY adams_forward(PJ_LP lp, PJ *P) {
switch (Q->mode) {
case GUYOU:
if ((fabs(lp.lam) - TOL) > M_PI_2) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().xy;
}
@@ -119,7 +119,7 @@ static PJ_XY adams_forward(PJ_LP lp, PJ *P) {
break;
case PEIRCE_Q: {
if( lp.phi < -TOL ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().xy;
}
const double sl = sin(lp.lam);
@@ -134,7 +134,7 @@ static PJ_XY adams_forward(PJ_LP lp, PJ *P) {
case ADAMS_HEMI: {
const double sp = sin(lp.phi);
if ((fabs(lp.lam) - TOL) > M_PI_2) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().xy;
}
a = cos(lp.phi) * sin(lp.lam);
@@ -208,7 +208,7 @@ static PJ *setup(PJ *P, projection_type mode) {
calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->es = 0;
diff --git a/src/projections/aea.cpp b/src/projections/aea.cpp
index af0f292d..4cbbfb56 100644
--- a/src/projections/aea.cpp
+++ b/src/projections/aea.cpp
@@ -107,7 +107,7 @@ static PJ_XY aea_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoid/spheroid, forward
struct pj_opaque *Q = static_cast<struct pj_opaque*>(P->opaque);
Q->rho = Q->c - (Q->ellips ? Q->n * pj_qsfn(sin(lp.phi), P->e, P->one_es) : Q->n2 * sin(lp.phi));;
if (Q->rho < 0.) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
Q->rho = Q->dd * sqrt(Q->rho);
@@ -134,12 +134,12 @@ static PJ_LP aea_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoid/spheroid, inverse
lp.phi = (Q->c - lp.phi * lp.phi) / Q->n;
if (fabs(Q->ec - fabs(lp.phi)) > TOL7) {
if (fabs(lp.phi) > 2 ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
lp.phi = phi1_(lp.phi, P->e, P->one_es);
if (lp.phi == HUGE_VAL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
} else
@@ -167,10 +167,21 @@ static PJ *setup(PJ *P) {
P->inv = aea_e_inverse;
P->fwd = aea_e_forward;
- if (fabs(Q->phi1) > M_HALFPI || fabs(Q->phi2) > M_HALFPI)
- return destructor(P, PJD_ERR_LAT_LARGER_THAN_90);
+ if (fabs(Q->phi1) > M_HALFPI)
+ {
+ proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be <= 90°"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+ if (fabs(Q->phi2) > M_HALFPI)
+ {
+ proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be <= 90°"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (fabs(Q->phi1 + Q->phi2) < EPS10)
- return destructor(P, PJD_ERR_CONIC_LAT_EQUAL);
+ {
+ proj_log_error(P, _("Invalid value for lat_1 and lat_2: |lat_1 + lat_2| should be > 0"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
double sinphi = sin(Q->phi1);
Q->n = sinphi;
double cosphi = cos(Q->phi1);
@@ -197,7 +208,8 @@ static PJ *setup(PJ *P) {
Q->n = (m1 * m1 - m2 * m2) / (ml2 - ml1);
if (Q->n == 0) {
// Not quite, but es is very close to 1...
- return destructor(P, PJD_ERR_INVALID_ECCENTRICITY);
+ proj_log_error(P, _("Invalid value for eccentricity"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
}
Q->ec = 1. - .5 * P->one_es * log((1. - P->e) /
@@ -221,7 +233,7 @@ static PJ *setup(PJ *P) {
PJ *PROJECTION(aea) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
@@ -234,7 +246,7 @@ PJ *PROJECTION(aea) {
PJ *PROJECTION(leac) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
diff --git a/src/projections/aeqd.cpp b/src/projections/aeqd.cpp
index d5d90b62..e6443858 100644
--- a/src/projections/aeqd.cpp
+++ b/src/projections/aeqd.cpp
@@ -149,7 +149,7 @@ static PJ_XY aeqd_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward
oblcon:
if (fabs(fabs(xy.y) - 1.) < TOL)
if (xy.y < 0.) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
else
@@ -168,7 +168,7 @@ oblcon:
/*-fallthrough*/
case S_POLE:
if (fabs(lp.phi - M_HALFPI) < EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
xy.y = (M_HALFPI + lp.phi);
@@ -239,7 +239,7 @@ static PJ_LP aeqd_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
c_rh = hypot(xy.x, xy.y);
if (c_rh > M_PI) {
if (c_rh - EPS10 > M_PI) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
c_rh = M_PI;
@@ -276,7 +276,7 @@ static PJ_LP aeqd_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
PJ *PROJECTION(aeqd) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
diff --git a/src/projections/airy.cpp b/src/projections/airy.cpp
index 15ff60d8..ead2c086 100644
--- a/src/projections/airy.cpp
+++ b/src/projections/airy.cpp
@@ -74,14 +74,14 @@ static PJ_XY airy_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward
if (Q->mode == OBLIQ)
cosz = Q->sinph0 * sinphi + Q->cosph0 * cosz;
if (!Q->no_cut && cosz < -EPS) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
s = 1. - cosz;
if (fabs(s) > EPS) {
t = 0.5 * (1. + cosz);
if(t == 0) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
Krho = -log(t)/s - Q->Cb / t;
@@ -97,7 +97,7 @@ static PJ_XY airy_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward
case N_POLE:
lp.phi = fabs(Q->p_halfpi - lp.phi);
if (!Q->no_cut && (lp.phi - EPS) > M_HALFPI) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
lp.phi *= 0.5;
@@ -122,7 +122,7 @@ PJ *PROJECTION(airy) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
diff --git a/src/projections/aitoff.cpp b/src/projections/aitoff.cpp
index 857ebb80..68f0c2da 100644
--- a/src/projections/aitoff.cpp
+++ b/src/projections/aitoff.cpp
@@ -123,7 +123,7 @@ static PJ_LP aitoff_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
C = 1. - D * D;
const double denom = pow(C, 1.5);
if( denom == 0 ) {
- proj_errno_set(P, PJD_ERR_NON_CONVERGENT);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
D = acos(D) / denom;
@@ -170,7 +170,7 @@ static PJ_LP aitoff_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
if (iter == MAXITER && round == MAXROUND)
{
- proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT );
+ proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
/* fprintf(stderr, "Warning: Accuracy of 1e-12 not reached. Last increments: dlat=%e and dlon=%e\n", dp, dl); */
}
@@ -189,7 +189,7 @@ static PJ *setup(PJ *P) {
PJ *PROJECTION(aitoff) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->mode = AITOFF;
@@ -200,13 +200,16 @@ PJ *PROJECTION(aitoff) {
PJ *PROJECTION(wintri) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->mode = WINKEL_TRIPEL;
if (pj_param(P->ctx, P->params, "tlat_1").i) {
if ((Q->cosphi1 = cos(pj_param(P->ctx, P->params, "rlat_1").f)) == 0.)
- return pj_default_destructor (P, PJD_ERR_LAT_LARGER_THAN_90);
+ {
+ proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
}
else /* 50d28' or acos(2/pi) */
Q->cosphi1 = 0.636619772367581343;
diff --git a/src/projections/bacon.cpp b/src/projections/bacon.cpp
index 7ff2a7ac..bc069a2c 100644
--- a/src/projections/bacon.cpp
+++ b/src/projections/bacon.cpp
@@ -45,7 +45,7 @@ static PJ_XY bacon_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar
PJ *PROJECTION(bacon) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->bacn = 1;
@@ -59,7 +59,7 @@ PJ *PROJECTION(bacon) {
PJ *PROJECTION(apian) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->bacn = Q->ortl = 0;
@@ -72,7 +72,7 @@ PJ *PROJECTION(apian) {
PJ *PROJECTION(ortel) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->bacn = 0;
diff --git a/src/projections/bertin1953.cpp b/src/projections/bertin1953.cpp
index 58509e16..512384b4 100644
--- a/src/projections/bertin1953.cpp
+++ b/src/projections/bertin1953.cpp
@@ -77,7 +77,7 @@ static PJ_XY bertin1953_s_forward (PJ_LP lp, PJ *P) {
PJ *PROJECTION(bertin1953) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->lam0 = 0;
diff --git a/src/projections/bipc.cpp b/src/projections/bipc.cpp
index 743acd1c..0071b4b5 100644
--- a/src/projections/bipc.cpp
+++ b/src/projections/bipc.cpp
@@ -60,7 +60,7 @@ static PJ_XY bipc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward
z = S20 * sphi + C20 * cphi * cdlam;
if (fabs(z) > 1.) {
if (fabs(z) > ONEEPS) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
else z = z < 0. ? -1. : 1.;
@@ -74,7 +74,7 @@ static PJ_XY bipc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward
z = S45 * (sphi + cphi * cdlam);
if (fabs(z) > 1.) {
if (fabs(z) > ONEEPS) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
else z = z < 0. ? -1. : 1.;
@@ -84,19 +84,19 @@ static PJ_XY bipc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward
xy.y = -rhoc;
}
if (z < 0.) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
t = pow(tan(.5 * z), n);
r = F * t;
if ((al = .5 * (R104 - z)) < 0.) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
al = (t + pow(al, n)) / T;
if (fabs(al) > 1.) {
if (fabs(al) > ONEEPS) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
else al = al < 0. ? -1. : 1.;
@@ -153,7 +153,7 @@ static PJ_LP bipc_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
rl = r;
}
if (! i) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
Az = Av - Az / n;
@@ -170,7 +170,7 @@ static PJ_LP bipc_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
PJ *PROJECTION(bipc) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->noskew = pj_param(P->ctx, P->params, "bns").i;
diff --git a/src/projections/bonne.cpp b/src/projections/bonne.cpp
index 7817e968..6923409b 100644
--- a/src/projections/bonne.cpp
+++ b/src/projections/bonne.cpp
@@ -65,7 +65,7 @@ static PJ_LP bonne_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
rh = hypot(xy.x, xy.y);
lp.phi = Q->cphi1 + Q->phi1 - rh;
if (fabs(lp.phi) > M_HALFPI) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10)
@@ -91,7 +91,7 @@ static PJ_LP bonne_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers
} else if (fabs(s - M_HALFPI) <= EPS10)
lp.lam = 0.;
else {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
return lp;
@@ -115,18 +115,21 @@ PJ *PROJECTION(bonne) {
double c;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f;
if (fabs(Q->phi1) < EPS10)
- return destructor (P, PJD_ERR_LAT1_IS_ZERO);
+ {
+ proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be > 0"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (P->es != 0.0) {
Q->en = pj_enfn(P->es);
if (nullptr==Q->en)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
Q->am1 = sin(Q->phi1);
c = cos(Q->phi1);
Q->m1 = pj_mlfn(Q->phi1, Q->am1, c, Q->en);
diff --git a/src/projections/calcofi.cpp b/src/projections/calcofi.cpp
index d1e96de8..b9528ab9 100644
--- a/src/projections/calcofi.cpp
+++ b/src/projections/calcofi.cpp
@@ -44,7 +44,7 @@ static PJ_XY calcofi_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forw
line as xy xy, r, o form a right triangle */
if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
@@ -73,7 +73,7 @@ static PJ_XY calcofi_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forw
double l2;
double ry;
if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
xy.x = lp.lam;
diff --git a/src/projections/cass.cpp b/src/projections/cass.cpp
index f5531f6a..a7fb48be 100644
--- a/src/projections/cass.cpp
+++ b/src/projections/cass.cpp
@@ -113,12 +113,12 @@ PJ *PROJECTION(cass) {
/* otherwise it's ellipsoidal */
P->opaque = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==P->opaque)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->destructor = destructor;
static_cast<struct pj_opaque*>(P->opaque)->en = pj_enfn (P->es);
if (nullptr==static_cast<struct pj_opaque*>(P->opaque)->en)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
static_cast<struct pj_opaque*>(P->opaque)->m0 = pj_mlfn (P->phi0, sin (P->phi0), cos (P->phi0), static_cast<struct pj_opaque*>(P->opaque)->en);
P->inv = cass_e_inverse;
diff --git a/src/projections/cc.cpp b/src/projections/cc.cpp
index 244e185d..f15a95bd 100644
--- a/src/projections/cc.cpp
+++ b/src/projections/cc.cpp
@@ -12,7 +12,7 @@ PROJ_HEAD(cc, "Central Cylindrical") "\n\tCyl, Sph";
static PJ_XY cc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward */
PJ_XY xy = {0.0,0.0};
if (fabs (fabs(lp.phi) - M_HALFPI) <= EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
xy.x = lp.lam;
diff --git a/src/projections/ccon.cpp b/src/projections/ccon.cpp
index 7b3b7105..dc0b0ea4 100644
--- a/src/projections/ccon.cpp
+++ b/src/projections/ccon.cpp
@@ -84,16 +84,18 @@ PJ *PROJECTION(ccon) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f;
if (fabs(Q->phi1) < EPS10)
- return destructor (P, PJD_ERR_LAT1_IS_ZERO);
-
+ {
+ proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be > 0"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (!(Q->en = pj_enfn(P->es)))
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
Q->sinphi1 = sin(Q->phi1);
Q->cosphi1 = cos(Q->phi1);
diff --git a/src/projections/cea.cpp b/src/projections/cea.cpp
index b7874b90..e26a14f3 100644
--- a/src/projections/cea.cpp
+++ b/src/projections/cea.cpp
@@ -53,7 +53,7 @@ static PJ_LP cea_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
lp.phi = asin(xy.y);
lp.lam = xy.x / P->k0;
} else {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
return (lp);
@@ -75,15 +75,19 @@ PJ *PROJECTION(cea) {
double t = 0.0;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
if (pj_param(P->ctx, P->params, "tlat_ts").i) {
- P->k0 = cos(t = pj_param(P->ctx, P->params, "rlat_ts").f);
+ t = pj_param(P->ctx, P->params, "rlat_ts").f;
+ P->k0 = cos(t);
if (P->k0 < 0.)
- return pj_default_destructor (P, PJD_ERR_LAT_TS_LARGER_THAN_90);
+ {
+ proj_log_error(P, _("Invalid value for lat_ts: |lat_ts| should be <= 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
}
if (P->es != 0.0) {
t = sin(t);
@@ -91,7 +95,7 @@ PJ *PROJECTION(cea) {
P->e = sqrt(P->es);
Q->apa = pj_authset(P->es);
if (!(Q->apa))
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
Q->qp = pj_qsfn(1., P->e, P->one_es);
P->inv = cea_e_inverse;
diff --git a/src/projections/chamb.cpp b/src/projections/chamb.cpp
index b315832a..1050dad3 100644
--- a/src/projections/chamb.cpp
+++ b/src/projections/chamb.cpp
@@ -105,7 +105,7 @@ PJ *PROJECTION(chamb) {
char line[10];
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
@@ -123,7 +123,10 @@ PJ *PROJECTION(chamb) {
Q->c[i].v = vect(P->ctx,Q->c[j].phi - Q->c[i].phi, Q->c[i].cosphi, Q->c[i].sinphi,
Q->c[j].cosphi, Q->c[j].sinphi, Q->c[j].lam - Q->c[i].lam);
if (Q->c[i].v.r == 0.0)
- return pj_default_destructor (P, PJD_ERR_CONTROL_POINT_NO_DIST);
+ {
+ proj_log_error(P, _("Invalid value for control points: they should be distinct"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
/* co-linearity problem ignored for now */
}
Q->beta_0 = lc(P->ctx,Q->c[0].v.r, Q->c[2].v.r, Q->c[1].v.r);
diff --git a/src/projections/col_urban.cpp b/src/projections/col_urban.cpp
index de0c178f..ffeb1785 100644
--- a/src/projections/col_urban.cpp
+++ b/src/projections/col_urban.cpp
@@ -56,7 +56,7 @@ static PJ_LP col_urban_inverse (PJ_XY xy, PJ *P) {
PJ *PROJECTION(col_urban) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
const double h0_unscaled = pj_param(P->ctx, P->params, "dh_0").f;
diff --git a/src/projections/collg.cpp b/src/projections/collg.cpp
index 1b9d2da7..958dfddb 100644
--- a/src/projections/collg.cpp
+++ b/src/projections/collg.cpp
@@ -32,7 +32,7 @@ static PJ_LP collg_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
if (fabs(lp.phi) < 1.)
lp.phi = asin(lp.phi);
else if (fabs(lp.phi) > ONEEPS) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
} else {
lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI;
diff --git a/src/projections/comill.cpp b/src/projections/comill.cpp
index 44524990..910d8aa7 100644
--- a/src/projections/comill.cpp
+++ b/src/projections/comill.cpp
@@ -66,7 +66,7 @@ static PJ_LP comill_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
}
}
if( i == 0 )
- proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT );
+ proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
lp.phi = yc;
/* longitude */
diff --git a/src/projections/eck2.cpp b/src/projections/eck2.cpp
index 0d9fd5fa..891ad30e 100644
--- a/src/projections/eck2.cpp
+++ b/src/projections/eck2.cpp
@@ -34,7 +34,7 @@ static PJ_LP eck2_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
lp.phi = (4. - lp.phi * lp.phi) * C13;
if (fabs(lp.phi) >= 1.) {
if (fabs(lp.phi) > ONEEPS) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
} else {
lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI;
diff --git a/src/projections/eck3.cpp b/src/projections/eck3.cpp
index 2563053f..ff1f4c5b 100644
--- a/src/projections/eck3.cpp
+++ b/src/projections/eck3.cpp
@@ -54,7 +54,7 @@ static PJ *setup(PJ *P) {
PJ *PROJECTION(eck3) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->C_x = 0.42223820031577120149;
@@ -69,7 +69,7 @@ PJ *PROJECTION(eck3) {
PJ *PROJECTION(kav7) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
/* Defined twice in original code - Using 0.866...,
@@ -87,7 +87,7 @@ PJ *PROJECTION(kav7) {
PJ *PROJECTION(wag6) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->C_x = 0.94745;
@@ -102,7 +102,7 @@ PJ *PROJECTION(wag6) {
PJ *PROJECTION(putp1) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->C_x = 1.89490;
diff --git a/src/projections/eqc.cpp b/src/projections/eqc.cpp
index 9ebc9286..0e696879 100644
--- a/src/projections/eqc.cpp
+++ b/src/projections/eqc.cpp
@@ -41,11 +41,14 @@ static PJ_LP eqc_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
PJ *PROJECTION(eqc) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
if ((Q->rc = cos(pj_param(P->ctx, P->params, "rlat_ts").f)) <= 0.)
- return pj_default_destructor (P, PJD_ERR_LAT_TS_LARGER_THAN_90);
+ {
+ proj_log_error(P, _("Invalid value for lat_ts: |lat_ts| should be <= 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
P->inv = eqc_s_inverse;
P->fwd = eqc_s_forward;
P->es = 0.;
diff --git a/src/projections/eqdc.cpp b/src/projections/eqdc.cpp
index 28767d74..4407ab62 100644
--- a/src/projections/eqdc.cpp
+++ b/src/projections/eqdc.cpp
@@ -79,20 +79,32 @@ PJ *PROJECTION(eqdc) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f;
Q->phi2 = pj_param(P->ctx, P->params, "rlat_2").f;
- if (fabs(Q->phi1) > M_HALFPI || fabs(Q->phi2) > M_HALFPI)
- return destructor(P, PJD_ERR_LAT_LARGER_THAN_90);
+ if (fabs(Q->phi1) > M_HALFPI)
+ {
+ proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be <= 90°"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+
+ if (fabs(Q->phi2) > M_HALFPI)
+ {
+ proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be <= 90°"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (fabs(Q->phi1 + Q->phi2) < EPS10)
- return destructor (P, PJD_ERR_CONIC_LAT_EQUAL);
+ {
+ proj_log_error(P, _("Invalid value for lat_1 and lat_2: |lat_1 + lat_2| should be > 0"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (!(Q->en = pj_enfn(P->es)))
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
sinphi = sin(Q->phi1);
Q->n = sinphi;
@@ -111,7 +123,8 @@ PJ *PROJECTION(eqdc) {
(pj_mlfn(Q->phi2, sinphi, cosphi, Q->en) - ml1);
if (Q->n == 0) {
// Not quite, but es is very close to 1...
- return destructor(P, PJD_ERR_INVALID_ECCENTRICITY);
+ proj_log_error(P, _("Invalid value for eccentricity"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
}
Q->c = ml1 + m1 / Q->n;
@@ -121,7 +134,10 @@ PJ *PROJECTION(eqdc) {
if (secant)
Q->n = (cosphi - cos(Q->phi2)) / (Q->phi2 - Q->phi1);
if (Q->n == 0)
- return destructor (P, PJD_ERR_CONIC_LAT_EQUAL);
+ {
+ proj_log_error(P, _("Invalid value for lat_1 and lat_2: lat_1 + lat_2 should be > 0"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->c = Q->phi1 + cos(Q->phi1) / Q->n;
Q->rho0 = Q->c - P->phi0;
}
diff --git a/src/projections/eqearth.cpp b/src/projections/eqearth.cpp
index 2ef2775b..2b9ee468 100644
--- a/src/projections/eqearth.cpp
+++ b/src/projections/eqearth.cpp
@@ -110,7 +110,7 @@ static PJ_LP eqearth_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal/sphe
}
if( i == 0 ) {
- proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT );
+ proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
return lp;
}
@@ -145,7 +145,7 @@ static PJ *destructor (PJ *P, int errlev) { /* Destructor */
PJ *PROJECTION(eqearth) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
P->fwd = eqearth_e_forward;
@@ -156,7 +156,7 @@ PJ *PROJECTION(eqearth) {
if (P->es != 0.0) {
Q->apa = pj_authset(P->es); /* For auth_lat(). */
if (nullptr == Q->apa)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
Q->qp = pj_qsfn(1.0, P->e, P->one_es); /* For auth_lat(). */
Q->rqda = sqrt(0.5*Q->qp); /* Authalic radius divided by major axis */
}
diff --git a/src/projections/fouc_s.cpp b/src/projections/fouc_s.cpp
index f7607635..ba6e917d 100644
--- a/src/projections/fouc_s.cpp
+++ b/src/projections/fouc_s.cpp
@@ -57,12 +57,15 @@ static PJ_LP fouc_s_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
PJ *PROJECTION(fouc_s) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->n = pj_param(P->ctx, P->params, "dn").f;
if (Q->n < 0. || Q->n > 1.)
- return pj_default_destructor (P, PJD_ERR_N_OUT_OF_RANGE);
+ {
+ proj_log_error(P, _("Invalid value for n: it should be in [0,1] range."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->n1 = 1. - Q->n;
P->es = 0;
diff --git a/src/projections/geos.cpp b/src/projections/geos.cpp
index 5de4c7ca..528ac4a5 100644
--- a/src/projections/geos.cpp
+++ b/src/projections/geos.cpp
@@ -99,7 +99,7 @@ static PJ_XY geos_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forward
/* Check visibility. */
if (((Q->radius_g - Vx) * Vx - Vy * Vy - Vz * Vz * Q->radius_p_inv2) < 0.) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
@@ -138,7 +138,7 @@ static PJ_LP geos_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
b = 2 * Q->radius_g * Vx;
const double det = (b * b) - 4 * a * Q->C;
if (det < 0.) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
@@ -178,7 +178,7 @@ static PJ_LP geos_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse
b = 2 * Q->radius_g * Vx;
const double det = (b * b) - 4 * a * Q->C;
if (det < 0.) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
@@ -201,7 +201,7 @@ PJ *PROJECTION(geos) {
char *sweep_axis;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->h = pj_param(P->ctx, P->params, "dh").f;
@@ -212,7 +212,10 @@ PJ *PROJECTION(geos) {
else {
if ((sweep_axis[0] != 'x' && sweep_axis[0] != 'y') ||
sweep_axis[1] != '\0')
- return pj_default_destructor (P, PJD_ERR_INVALID_SWEEP_AXIS);
+ {
+ proj_log_error(P, _("Invalid value for sweep: it should be equal to x or y."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (sweep_axis[0] == 'x')
Q->flip_axis = 1;
@@ -222,7 +225,10 @@ PJ *PROJECTION(geos) {
Q->radius_g_1 = Q->h / P->a;
if ( Q->radius_g_1 <= 0 || Q->radius_g_1 > 1e10 )
- return pj_default_destructor (P, PJD_ERR_INVALID_H);
+ {
+ proj_log_error(P, _("Invalid value for h."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->radius_g = 1. + Q->radius_g_1;
Q->C = Q->radius_g * Q->radius_g - 1.0;
if (P->es != 0.0) {
diff --git a/src/projections/gn_sinu.cpp b/src/projections/gn_sinu.cpp
index ef312613..863613b6 100644
--- a/src/projections/gn_sinu.cpp
+++ b/src/projections/gn_sinu.cpp
@@ -46,7 +46,7 @@ static PJ_LP gn_sinu_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inve
} else if ((s - EPS10) < M_HALFPI) {
lp.lam = 0.;
} else {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
}
return lp;
@@ -71,7 +71,7 @@ static PJ_XY gn_sinu_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forw
break;
}
if (!i) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
@@ -123,12 +123,12 @@ static void setup(PJ *P) {
PJ *PROJECTION(sinu) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
if (!(Q->en = pj_enfn(P->es)))
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
if (P->es != 0.0) {
P->inv = gn_sinu_e_inverse;
@@ -145,7 +145,7 @@ PJ *PROJECTION(sinu) {
PJ *PROJECTION(eck6) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
@@ -160,7 +160,7 @@ PJ *PROJECTION(eck6) {
PJ *PROJECTION(mbtfps) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
@@ -175,17 +175,33 @@ PJ *PROJECTION(mbtfps) {
PJ *PROJECTION(gn_sinu) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
- if (pj_param(P->ctx, P->params, "tn").i && pj_param(P->ctx, P->params, "tm").i) {
- Q->n = pj_param(P->ctx, P->params, "dn").f;
- Q->m = pj_param(P->ctx, P->params, "dm").f;
- if (Q->n <= 0 || Q->m < 0)
- return destructor (P, PJD_ERR_INVALID_M_OR_N);
- } else
- return destructor (P, PJD_ERR_INVALID_M_OR_N);
+ if (!pj_param(P->ctx, P->params, "tn").i )
+ {
+ proj_log_error(P, _("Missing parameter n."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
+ if (!pj_param(P->ctx, P->params, "tm").i )
+ {
+ proj_log_error(P, _("Missing parameter m."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
+
+ Q->n = pj_param(P->ctx, P->params, "dn").f;
+ Q->m = pj_param(P->ctx, P->params, "dm").f;
+ if (Q->n <= 0)
+ {
+ proj_log_error(P, _("Invalid value for n: it should be > 0."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+ if (Q->m < 0)
+ {
+ proj_log_error(P, _("Invalid value for m: it should be >= 0."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
setup(P);
diff --git a/src/projections/gnom.cpp b/src/projections/gnom.cpp
index 9abbb7ce..6257008d 100644
--- a/src/projections/gnom.cpp
+++ b/src/projections/gnom.cpp
@@ -54,7 +54,7 @@ static PJ_XY gnom_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward
}
if (xy.y <= EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
@@ -126,7 +126,7 @@ static PJ_LP gnom_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
PJ *PROJECTION(gnom) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
if (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) {
diff --git a/src/projections/goode.cpp b/src/projections/goode.cpp
index c0afd2d8..6729820b 100644
--- a/src/projections/goode.cpp
+++ b/src/projections/goode.cpp
@@ -64,7 +64,7 @@ static PJ *destructor (PJ *P, int errlev) { /* Destructor */
PJ *PROJECTION(goode) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
@@ -72,14 +72,14 @@ PJ *PROJECTION(goode) {
Q->sinu = pj_sinu(nullptr);
Q->moll = pj_moll(nullptr);
if (Q->sinu == nullptr || Q->moll == nullptr)
- return destructor (P, ENOMEM);
+ return destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
Q->sinu->es = 0.;
Q->sinu->ctx = P->ctx;
Q->moll->ctx = P->ctx;
Q->sinu = pj_sinu(Q->sinu);
Q->moll = pj_moll(Q->moll);
if (Q->sinu == nullptr || Q->moll == nullptr)
- return destructor (P, ENOMEM);
+ return destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->fwd = goode_s_forward;
P->inv = goode_s_inverse;
diff --git a/src/projections/gstmerc.cpp b/src/projections/gstmerc.cpp
index b21f6ffd..ea41a33a 100644
--- a/src/projections/gstmerc.cpp
+++ b/src/projections/gstmerc.cpp
@@ -56,7 +56,7 @@ static PJ_LP gstmerc_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inve
PJ *PROJECTION(gstmerc) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->lamc = P->lam0;
diff --git a/src/projections/hammer.cpp b/src/projections/hammer.cpp
index d9bcafc7..b66b757b 100644
--- a/src/projections/hammer.cpp
+++ b/src/projections/hammer.cpp
@@ -28,7 +28,7 @@ static PJ_XY hammer_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwa
lp.lam *= Q->w;
double denom = 1. + cosphi * cos(lp.lam);
if( denom == 0.0 ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().xy;
}
d = sqrt(2./denom);
@@ -47,7 +47,7 @@ static PJ_LP hammer_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
if (fabs(2.*z*z-1.) < EPS) {
lp.lam = HUGE_VAL;
lp.phi = HUGE_VAL;
- proj_errno_set(P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
} else {
lp.lam = aatan2(Q->w * xy.x * z,2. * z * z - 1)/Q->w;
lp.phi = aasin(P->ctx,z * xy.y);
@@ -59,19 +59,25 @@ static PJ_LP hammer_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
PJ *PROJECTION(hammer) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
if (pj_param(P->ctx, P->params, "tW").i) {
Q->w = fabs(pj_param(P->ctx, P->params, "dW").f);
if (Q->w <= 0.)
- return pj_default_destructor (P, PJD_ERR_W_OR_M_ZERO_OR_LESS);
+ {
+ proj_log_error(P, _("Invalid value for W: it should be > 0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
} else
Q->w = .5;
if (pj_param(P->ctx, P->params, "tM").i) {
Q->m = fabs(pj_param(P->ctx, P->params, "dM").f);
if (Q->m <= 0.)
- return pj_default_destructor (P, PJD_ERR_W_OR_M_ZERO_OR_LESS);
+ {
+ proj_log_error(P, _("Invalid value for M: it should be > 0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
} else
Q->m = 1.;
diff --git a/src/projections/hatano.cpp b/src/projections/hatano.cpp
index c10c4e35..4897435f 100644
--- a/src/projections/hatano.cpp
+++ b/src/projections/hatano.cpp
@@ -47,7 +47,7 @@ static PJ_LP hatano_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
th = xy.y * ( xy.y < 0. ? RYCS : RYCN);
if (fabs(th) > 1.) {
if (fabs(th) > ONETOL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
} else {
th = th > 0. ? M_HALFPI : - M_HALFPI;
@@ -61,7 +61,7 @@ static PJ_LP hatano_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
lp.phi = (th + sin(th)) * (xy.y < 0. ? RCS : RCN);
if (fabs(lp.phi) > 1.) {
if (fabs(lp.phi) > ONETOL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
} else {
lp.phi = lp.phi > 0. ? M_HALFPI : - M_HALFPI;
diff --git a/src/projections/healpix.cpp b/src/projections/healpix.cpp
index c778f28f..0340f7dd 100644
--- a/src/projections/healpix.cpp
+++ b/src/projections/healpix.cpp
@@ -524,7 +524,7 @@ static PJ_LP s_healpix_inverse(PJ_XY xy, PJ *P) { /* sphere */
PJ_LP lp;
lp.lam = HUGE_VAL;
lp.phi = HUGE_VAL;
- proj_context_errno_set(P->ctx, PJD_ERR_INVALID_X_OR_Y);
+ proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
return healpix_spherhealpix_e_inverse(xy);
@@ -540,7 +540,7 @@ static PJ_LP e_healpix_inverse(PJ_XY xy, PJ *P) { /* ellipsoid */
if (in_image(xy.x, xy.y, 0, 0, 0) == 0) {
lp.lam = HUGE_VAL;
lp.phi = HUGE_VAL;
- proj_context_errno_set(P->ctx, PJD_ERR_INVALID_X_OR_Y);
+ proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
lp = healpix_spherhealpix_e_inverse(xy);
@@ -574,7 +574,7 @@ static PJ_LP s_rhealpix_inverse(PJ_XY xy, PJ *P) { /* sphere */
PJ_LP lp;
lp.lam = HUGE_VAL;
lp.phi = HUGE_VAL;
- proj_context_errno_set(P->ctx, PJD_ERR_INVALID_X_OR_Y);
+ proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
xy = combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 1);
@@ -590,7 +590,7 @@ static PJ_LP e_rhealpix_inverse(PJ_XY xy, PJ *P) { /* ellipsoid */
if (in_image(xy.x, xy.y, 1, Q->north_square, Q->south_square) == 0) {
lp.lam = HUGE_VAL;
lp.phi = HUGE_VAL;
- proj_context_errno_set(P->ctx, PJD_ERR_INVALID_X_OR_Y);
+ proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
xy = combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 1);
@@ -615,7 +615,7 @@ static PJ *destructor (PJ *P, int errlev) { /* Destructor
PJ *PROJECTION(healpix) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
@@ -625,7 +625,7 @@ PJ *PROJECTION(healpix) {
if (P->es != 0.0) {
Q->apa = pj_authset(P->es); /* For auth_lat(). */
if (nullptr==Q->apa)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
Q->qp = pj_qsfn(1.0, P->e, P->one_es); /* For auth_lat(). */
P->a = P->a*sqrt(0.5*Q->qp); /* Set P->a to authalic radius. */
pj_calc_ellipsoid_params (P, P->a, P->es); /* Ensure we have a consistent parameter set */
@@ -643,7 +643,7 @@ PJ *PROJECTION(healpix) {
PJ *PROJECTION(rhealpix) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
@@ -652,13 +652,19 @@ PJ *PROJECTION(rhealpix) {
/* Check for valid north_square and south_square inputs. */
if (Q->north_square < 0 || Q->north_square > 3)
- return destructor (P, PJD_ERR_AXIS);
+ {
+ proj_log_error(P, _("Invalid value for north_square: it should be in [0,3] range."));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (Q->south_square < 0 || Q->south_square > 3)
- return destructor (P, PJD_ERR_AXIS);
+ {
+ proj_log_error(P, _("Invalid value for south_square: it should be in [0,3] range."));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (P->es != 0.0) {
Q->apa = pj_authset(P->es); /* For auth_lat(). */
if (nullptr==Q->apa)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
Q->qp = pj_qsfn(1.0, P->e, P->one_es); /* For auth_lat(). */
P->a = P->a*sqrt(0.5*Q->qp); /* Set P->a to authalic radius. */
P->ra = 1.0/P->a;
diff --git a/src/projections/igh.cpp b/src/projections/igh.cpp
index 8aaaaba1..a6bac0c4 100644
--- a/src/projections/igh.cpp
+++ b/src/projections/igh.cpp
@@ -210,7 +210,7 @@ PJ *PROJECTION(igh) {
PJ_LP lp = { 0, phi_boundary };
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
@@ -222,7 +222,7 @@ PJ *PROJECTION(igh) {
!setup_zone(P, Q, 7, pj_sinu, d20, 0, d20) ||
!setup_zone(P, Q, 8, pj_sinu, d140, 0, d140))
{
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
}
/* mollweide zones */
@@ -243,7 +243,7 @@ PJ *PROJECTION(igh) {
!setup_zone(P, Q,11, pj_moll, d20, -Q->dy0, d20) ||
!setup_zone(P, Q,12, pj_moll, d140, -Q->dy0, d140))
{
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
}
P->inv = igh_s_inverse;
diff --git a/src/projections/igh_o.cpp b/src/projections/igh_o.cpp
index 80874845..c0931117 100644
--- a/src/projections/igh_o.cpp
+++ b/src/projections/igh_o.cpp
@@ -224,7 +224,7 @@ PJ *PROJECTION(igh_o) {
PJ_LP lp = { 0, phi_boundary };
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
@@ -236,13 +236,13 @@ PJ *PROJECTION(igh_o) {
!setup_zone(P, Q, 8, pj_sinu, d20, 0, d20) ||
!setup_zone(P, Q, 9, pj_sinu, d150, 0, d150))
{
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
}
/* mollweide zones */
if (!setup_zone(P, Q, 1, pj_moll, -d140, 0, -d140)) {
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
}
/* y0 ? */
@@ -260,7 +260,7 @@ PJ *PROJECTION(igh_o) {
!setup_zone(P, Q, 11, pj_moll, d20, -Q->dy0, d20) ||
!setup_zone(P, Q, 12, pj_moll, d150, -Q->dy0, d150))
{
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
}
P->inv = igh_o_s_inverse;
diff --git a/src/projections/imw_p.cpp b/src/projections/imw_p.cpp
index 6e82d287..ed5cb897 100644
--- a/src/projections/imw_p.cpp
+++ b/src/projections/imw_p.cpp
@@ -34,15 +34,27 @@ static int phi12(PJ *P, double *del, double *sig) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(P->opaque);
int err = 0;
- if (!pj_param(P->ctx, P->params, "tlat_1").i ||
- !pj_param(P->ctx, P->params, "tlat_2").i) {
- err = PJD_ERR_LAT_1_2_UNSPECIFIED;
- } else {
+ if (!pj_param(P->ctx, P->params, "tlat_1").i )
+ {
+ proj_log_error(P, _("Missing parameter: lat_1 should be specified"));
+ err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE;
+ }
+ else if ( !pj_param(P->ctx, P->params, "tlat_2").i)
+ {
+ proj_log_error(P, _("Missing parameter: lat_2 should be specified"));
+ err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE;
+ }
+ else
+ {
Q->phi_1 = pj_param(P->ctx, P->params, "rlat_1").f;
Q->phi_2 = pj_param(P->ctx, P->params, "rlat_2").f;
*del = 0.5 * (Q->phi_2 - Q->phi_1);
*sig = 0.5 * (Q->phi_2 + Q->phi_1);
- err = (fabs(*del) < EPS || fabs(*sig) < EPS) ? PJD_ERR_ABS_LAT1_EQ_ABS_LAT2 : 0;
+ err = (fabs(*del) < EPS || fabs(*sig) < EPS) ? PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE : 0;
+ if( err )
+ {
+ proj_log_error(P, _("Illegal value for lat_1 and lat_2: |lat_1 - lat_2| and |lat_1 + lat_2| should be > 0"));
+ }
}
return err;
}
@@ -120,7 +132,7 @@ static PJ_LP imw_p_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers
if( denom != 0 || fabs(t.y - xy.y) > TOL )
{
if( denom == 0 ) {
- proj_errno_set(P, PJD_ERR_NON_CONVERGENT);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().lp;
}
lp.phi = ((lp.phi - Q->phi_1) * (xy.y - yc) / denom) + Q->phi_1;
@@ -133,7 +145,7 @@ static PJ_LP imw_p_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers
if( i == N_MAX_ITER )
{
- proj_errno_set(P, PJD_ERR_NON_CONVERGENT);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().lp;
}
@@ -171,10 +183,10 @@ PJ *PROJECTION(imw_p) {
int err;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
- if (!(Q->en = pj_enfn(P->es))) return pj_default_destructor (P, ENOMEM);
+ if (!(Q->en = pj_enfn(P->es))) return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
if( (err = phi12(P, &del, &sig)) != 0) {
return destructor(P, err);
}
diff --git a/src/projections/isea.cpp b/src/projections/isea.cpp
index 77a5689b..08e558a4 100644
--- a/src/projections/isea.cpp
+++ b/src/projections/isea.cpp
@@ -1026,7 +1026,7 @@ static PJ_XY isea_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward
try {
out = isea_forward(&Q->dgg, &in);
} catch( const char* ) {
- proj_errno_set(P, PJD_ERR_NON_CONVERGENT);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().xy;
}
@@ -1041,7 +1041,7 @@ PJ *PROJECTION(isea) {
char *opt;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
@@ -1059,7 +1059,8 @@ PJ *PROJECTION(isea) {
} else if (!strcmp(opt, "pole")) {
isea_orient_pole(&Q->dgg);
} else {
- return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED);
+ proj_log_error(P, _("Invalid value for orient: only isea or pole are supported"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
}
@@ -1089,8 +1090,8 @@ PJ *PROJECTION(isea) {
Q->dgg.output = ISEA_HEX;
}
else {
- /* TODO verify error code. Possibly eliminate magic */
- return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED);
+ proj_log_error(P, _("Invalid value for mode: only plane, di, dd or hex are supported"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
}
diff --git a/src/projections/krovak.cpp b/src/projections/krovak.cpp
index adc039fe..1ddf7777 100644
--- a/src/projections/krovak.cpp
+++ b/src/projections/krovak.cpp
@@ -181,7 +181,7 @@ static PJ_LP krovak_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal,
fi1 = lp.phi;
}
if( i == 0 )
- proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT );
+ proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
lp.lam -= P->lam0;
@@ -193,7 +193,7 @@ PJ *PROJECTION(krovak) {
double u0, n0, g;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
/* we want Bessel as fixed ellipsoid */
@@ -225,7 +225,8 @@ PJ *PROJECTION(krovak) {
g = pow( (1. + P->e * sin(P->phi0)) / (1. - P->e * sin(P->phi0)) , Q->alpha * P->e / 2. );
double tan_half_phi0_plus_pi_4 = tan(P->phi0 / 2. + M_PI_4);
if( tan_half_phi0_plus_pi_4 == 0.0 ) {
- return pj_default_destructor(P, PJD_ERR_INVALID_ARG);
+ proj_log_error(P, _("Invalid value for lat_0: lat_0 + PI/4 should be different from 0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
Q->k = tan( u0 / 2. + M_PI_4) / pow (tan_half_phi0_plus_pi_4 , Q->alpha) * g;
n0 = sqrt(1. - P->es) / (1. - P->es * pow(sin(P->phi0), 2));
diff --git a/src/projections/labrd.cpp b/src/projections/labrd.cpp
index 4fbcf460..9fa17817 100644
--- a/src/projections/labrd.cpp
+++ b/src/projections/labrd.cpp
@@ -107,11 +107,12 @@ PJ *PROJECTION(labrd) {
double Az, sinp, R, N, t;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
if (P->phi0 == 0.) {
- return pj_default_destructor(P, PJD_ERR_LAT_0_IS_ZERO);
+ proj_log_error(P, _("Invalid value for lat_0: lat_0 should be different from 0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
Az = pj_param(P->ctx, P->params, "razi").f;
diff --git a/src/projections/laea.cpp b/src/projections/laea.cpp
index 2d19cab1..8c7797e8 100644
--- a/src/projections/laea.cpp
+++ b/src/projections/laea.cpp
@@ -65,7 +65,7 @@ static PJ_XY laea_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forward
break;
}
if (fabs(b) < EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
@@ -111,7 +111,7 @@ static PJ_XY laea_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward
xy.y = 1. + Q->sinb1 * sinphi + Q->cosb1 * cosphi * coslam;
oblcon:
if (xy.y <= EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
xy.y = sqrt(2. / xy.y);
@@ -124,7 +124,7 @@ oblcon:
/*-fallthrough*/
case S_POLE:
if (fabs(lp.phi + P->phi0) < EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
xy.y = M_FORTPI - lp.phi * .5;
@@ -193,7 +193,7 @@ static PJ_LP laea_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
rh = hypot(xy.x, xy.y);
if ((lp.phi = rh * .5 ) > 1.) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
lp.phi = 2. * asin(lp.phi);
@@ -244,13 +244,14 @@ PJ *PROJECTION(laea) {
double t;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
t = fabs(P->phi0);
if (t > M_HALFPI + EPS10 ) {
- return destructor(P, PJD_ERR_LAT_LARGER_THAN_90);
+ proj_log_error(P, _("Invalid value for lat_0: |lat_0| should be <= 90°"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
if (fabs(t - M_HALFPI) < EPS10)
Q->mode = P->phi0 < 0. ? S_POLE : N_POLE;
@@ -266,7 +267,7 @@ PJ *PROJECTION(laea) {
Q->mmf = .5 / (1. - P->es);
Q->apa = pj_authset(P->es);
if (nullptr==Q->apa)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
switch (Q->mode) {
case N_POLE:
case S_POLE:
diff --git a/src/projections/lagrng.cpp b/src/projections/lagrng.cpp
index 1029bf8d..228f0b80 100644
--- a/src/projections/lagrng.cpp
+++ b/src/projections/lagrng.cpp
@@ -35,7 +35,7 @@ static PJ_XY lagrng_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwa
lp.lam *= Q->rw;
c = 0.5 * (v + 1./v) + cos(lp.lam);
if (c < TOL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
xy.x = 2. * sin(lp.lam) / c;
@@ -59,7 +59,7 @@ static PJ_LP lagrng_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
y2m = 2. - xy.y;
c = y2p * y2m - x2;
if (fabs(c) < TOL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
lp.phi = 2. * atan(pow((y2p * y2p + x2) / (Q->a2 * (y2m * y2m + x2)), Q->hw)) - M_HALFPI;
@@ -73,7 +73,7 @@ PJ *PROJECTION(lagrng) {
double sin_phi1;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
if( pj_param(P->ctx, P->params, "tW").i )
@@ -81,13 +81,19 @@ PJ *PROJECTION(lagrng) {
else
Q->w = 2;
if (Q->w <= 0)
- return pj_default_destructor(P, PJD_ERR_W_OR_M_ZERO_OR_LESS);
+ {
+ proj_log_error(P, _("Invalid value for W: it should be > 0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->hw = 0.5 * Q->w;
Q->rw = 1. / Q->w;
Q->hrw = 0.5 * Q->rw;
sin_phi1 = sin(pj_param(P->ctx, P->params, "rlat_1").f);
if (fabs(fabs(sin_phi1) - 1.) < TOL)
- return pj_default_destructor(P, PJD_ERR_LAT_LARGER_THAN_90);
+ {
+ proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->a1 = pow((1. - sin_phi1)/(1. + sin_phi1), Q->hrw);
Q->a2 = Q->a1 * Q->a1;
diff --git a/src/projections/lcc.cpp b/src/projections/lcc.cpp
index 525281f4..b47cf67e 100644
--- a/src/projections/lcc.cpp
+++ b/src/projections/lcc.cpp
@@ -27,7 +27,7 @@ static PJ_XY lcc_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forward
if (fabs(fabs(lp.phi) - M_HALFPI) < EPS10) {
if ((lp.phi * Q->n) <= 0.) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
rho = 0.;
@@ -62,7 +62,7 @@ static PJ_LP lcc_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse
if (P->es != 0.) {
lp.phi = pj_phi2(P->ctx, pow(rho / Q->c, 1./Q->n), P->e);
if (lp.phi == HUGE_VAL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
@@ -83,7 +83,7 @@ PJ *PROJECTION(lcc) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc(1, sizeof (struct pj_opaque)));
if (nullptr == Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f;
@@ -94,37 +94,45 @@ PJ *PROJECTION(lcc) {
if (!pj_param(P->ctx, P->params, "tlat_0").i)
P->phi0 = Q->phi1;
}
- if (fabs(Q->phi1) > M_HALFPI || fabs(Q->phi2) > M_HALFPI)
- return pj_default_destructor(P, PJD_ERR_LAT_LARGER_THAN_90);
+
if (fabs(Q->phi1 + Q->phi2) < EPS10)
- return pj_default_destructor(P, PJD_ERR_CONIC_LAT_EQUAL);
+ {
+ proj_log_error(P, _("Invalid value for lat_1 and lat_2: |lat_1 + lat_2| should be > 0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->n = sinphi = sin(Q->phi1);
cosphi = cos(Q->phi1);
+
+ if( fabs(cosphi) < EPS10 || fabs(Q->phi1) >= M_PI_2 ) {
+ proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+ if( fabs(cos(Q->phi2)) < EPS10 || fabs(Q->phi2) >= M_PI_2 ) {
+ proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be < 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+
secant = fabs(Q->phi1 - Q->phi2) >= EPS10;
if (P->es != 0.) {
double ml1, m1;
m1 = pj_msfn(sinphi, cosphi, P->es);
- if( fabs(Q->phi1) == M_HALFPI ) {
- return pj_default_destructor(P, PJD_ERR_LAT_1_OR_2_ZERO_OR_90);
- }
ml1 = pj_tsfn(Q->phi1, sinphi, P->e);
if (secant) { /* secant cone */
sinphi = sin(Q->phi2);
Q->n = log(m1 / pj_msfn(sinphi, cos(Q->phi2), P->es));
if (Q->n == 0) {
// Not quite, but es is very close to 1...
- return pj_default_destructor(P, PJD_ERR_INVALID_ECCENTRICITY);
- }
- if( fabs(Q->phi2) == M_HALFPI ) {
- return pj_default_destructor(P, PJD_ERR_LAT_1_OR_2_ZERO_OR_90);
+ proj_log_error(P, _("Invalid value for eccentricity"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
const double ml2 = pj_tsfn(Q->phi2, sinphi, P->e);
const double denom = log(ml1 / ml2);
if( denom == 0 ) {
// Not quite, but es is very close to 1...
- return pj_default_destructor(P, PJD_ERR_INVALID_ECCENTRICITY);
+ proj_log_error(P, _("Invalid value for eccentricity"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
Q->n /= denom;
}
@@ -133,9 +141,6 @@ PJ *PROJECTION(lcc) {
Q->rho0 *= (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) ? 0. :
pow(pj_tsfn(P->phi0, sin(P->phi0), P->e), Q->n);
} else {
- if( fabs(cosphi) < EPS10 || fabs(cos(Q->phi2)) < EPS10 ) {
- return pj_default_destructor(P, PJD_ERR_LAT_1_OR_2_ZERO_OR_90);
- }
if (secant)
Q->n = log(cosphi / cos(Q->phi2)) /
log(tan(M_FORTPI + .5 * Q->phi2) /
@@ -143,7 +148,8 @@ PJ *PROJECTION(lcc) {
if( Q->n == 0 ) {
// Likely reason is that phi1 / phi2 are too close to zero.
// Can be reproduced with +proj=lcc +a=1 +lat_2=.0000001
- return pj_default_destructor(P, PJD_ERR_CONIC_LAT_EQUAL);
+ proj_log_error(P, _("Invalid value for lat_1 and lat_2: |lat_1 + lat_2| should be > 0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
Q->c = cosphi * pow(tan(M_FORTPI + .5 * Q->phi1), Q->n) / Q->n;
Q->rho0 = (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) ? 0. :
diff --git a/src/projections/lcca.cpp b/src/projections/lcca.cpp
index 53646fc6..9de4d5dc 100644
--- a/src/projections/lcca.cpp
+++ b/src/projections/lcca.cpp
@@ -112,7 +112,7 @@ static PJ_LP lcca_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse
if (fabs(dif) < DEL_TOL) break;
}
if (!i) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
lp.phi = pj_inv_mlfn(P->ctx, S + Q->M0, P->es, Q->en);
@@ -137,15 +137,16 @@ PJ *PROJECTION(lcca) {
double s2p0, N0, R0, tan0;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
(Q->en = pj_enfn(P->es));
if (!Q->en)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
if (P->phi0 == 0.) {
- return destructor(P, PJD_ERR_LAT_0_IS_ZERO);
+ proj_log_error(P, _("Invalid value for lat_0: it should be different from 0."));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
Q->l = sin(P->phi0);
Q->M0 = pj_mlfn(P->phi0, Q->l, cos(P->phi0), Q->en);
diff --git a/src/projections/loxim.cpp b/src/projections/loxim.cpp
index 64124bdd..00f43da4 100644
--- a/src/projections/loxim.cpp
+++ b/src/projections/loxim.cpp
@@ -58,14 +58,16 @@ static PJ_LP loxim_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
PJ *PROJECTION(loxim) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f;
Q->cosphi1 = cos(Q->phi1);
if (Q->cosphi1 < EPS)
- return pj_default_destructor(P, PJD_ERR_LAT_LARGER_THAN_90);
-
+ {
+ proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->tanphi1 = tan(M_FORTPI + 0.5 * Q->phi1);
diff --git a/src/projections/lsat.cpp b/src/projections/lsat.cpp
index a811a3a6..a7f386f7 100644
--- a/src/projections/lsat.cpp
+++ b/src/projections/lsat.cpp
@@ -136,7 +136,7 @@ static PJ_LP lsat_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse
sppsq = spp * spp;
const double denom = 1. - sppsq * (1. + Q->u);
if( denom == 0.0 ) {
- proj_errno_set(P, PJD_ERR_INVALID_X_OR_Y);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().lp;
}
lamt = atan(((1. - sppsq * P->rone_es) * tan(lamdp) *
@@ -160,16 +160,23 @@ PJ *PROJECTION(lsat) {
double lam, alf, esc, ess;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
land = pj_param(P->ctx, P->params, "ilsat").i;
if (land <= 0 || land > 5)
- return pj_default_destructor(P, PJD_ERR_LSAT_NOT_IN_RANGE);
+ {
+ proj_log_error(P, _("Invalid value for lsat: lsat should be in [1, 5] range"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
path = pj_param(P->ctx, P->params, "ipath").i;
- if (path <= 0 || path > (land <= 3 ? 251 : 233))
- return pj_default_destructor(P, PJD_ERR_PATH_NOT_IN_RANGE);
+ const int maxPathVal = (land <= 3 ? 251 : 233);
+ if (path <= 0 || path > maxPathVal)
+ {
+ proj_log_error(P, _("Invalid value for path: path should be in [1, %d] range"), maxPathVal);
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (land <= 3) {
P->lam0 = DEG_TO_RAD * 128.87 - M_TWOPI / 251. * path;
diff --git a/src/projections/mbtfpp.cpp b/src/projections/mbtfpp.cpp
index cc01cb40..bfa869f9 100644
--- a/src/projections/mbtfpp.cpp
+++ b/src/projections/mbtfpp.cpp
@@ -32,7 +32,7 @@ static PJ_LP mbtfpp_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
lp.phi = xy.y / FYC;
if (fabs(lp.phi) >= 1.) {
if (fabs(lp.phi) > ONEEPS) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
} else {
lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI;
@@ -45,7 +45,7 @@ static PJ_LP mbtfpp_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
lp.phi = sin(lp.phi) / CSy;
if (fabs(lp.phi) >= 1.) {
if (fabs(lp.phi) > ONEEPS) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
} else {
lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI;
diff --git a/src/projections/mbtfpq.cpp b/src/projections/mbtfpq.cpp
index 5c7f8ca6..7d44b5ba 100644
--- a/src/projections/mbtfpq.cpp
+++ b/src/projections/mbtfpq.cpp
@@ -42,7 +42,7 @@ static PJ_LP mbtfpq_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
lp.phi = RYC * xy.y;
if (fabs(lp.phi) > 1.) {
if (fabs(lp.phi) > ONETOL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
else if (lp.phi < 0.) {
@@ -61,7 +61,7 @@ static PJ_LP mbtfpq_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
lp.phi = RC * (t + sin(lp.phi));
if (fabs(lp.phi) > 1.)
if (fabs(lp.phi) > ONETOL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
else lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI;
diff --git a/src/projections/merc.cpp b/src/projections/merc.cpp
index 3a0ed7b4..7fd3dff9 100644
--- a/src/projections/merc.cpp
+++ b/src/projections/merc.cpp
@@ -53,7 +53,10 @@ PJ *PROJECTION(merc) {
if( (is_phits = pj_param(P->ctx, P->params, "tlat_ts").i) ) {
phits = fabs(pj_param(P->ctx, P->params, "rlat_ts").f);
if (phits >= M_HALFPI)
- return pj_default_destructor(P, PJD_ERR_LAT_TS_LARGER_THAN_90);
+ {
+ proj_log_error(P, _("Invalid value for lat_ts: |lat_ts| should be <= 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
}
if (P->es != 0.0) { /* ellipsoid */
diff --git a/src/projections/misrsom.cpp b/src/projections/misrsom.cpp
index d7e199f2..07c9961e 100644
--- a/src/projections/misrsom.cpp
+++ b/src/projections/misrsom.cpp
@@ -155,7 +155,7 @@ static PJ_LP misrsom_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inve
sppsq = spp * spp;
const double denom = 1. - sppsq * (1. + Q->u);
if( denom == 0.0 ) {
- proj_errno_set(P, PJD_ERR_NON_CONVERGENT);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().lp;
}
lamt = atan(((1. - sppsq * P->rone_es) * tan(lamdp) *
@@ -180,12 +180,15 @@ PJ *PROJECTION(misrsom) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
path = pj_param(P->ctx, P->params, "ipath").i;
if (path <= 0 || path > 233)
- return pj_default_destructor(P, PJD_ERR_PATH_NOT_IN_RANGE);
+ {
+ proj_log_error(P, _("Invalid value for path: path should be in [1, 233] range"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
P->lam0 = DEG_TO_RAD * 129.3056 - M_TWOPI / 233. * path;
alf = 98.30382 * DEG_TO_RAD;
diff --git a/src/projections/mod_ster.cpp b/src/projections/mod_ster.cpp
index 0c30b7b6..ae64c494 100644
--- a/src/projections/mod_ster.cpp
+++ b/src/projections/mod_ster.cpp
@@ -37,7 +37,7 @@ static PJ_XY mod_ster_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, for
cchi = cos(chi);
const double denom = 1. + Q->schio * schi + Q->cchio * cchi * coslon;
if( denom == 0 ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
s = 2. / denom;
@@ -136,7 +136,7 @@ PJ *PROJECTION(mil_os) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->n = 2;
@@ -159,7 +159,7 @@ PJ *PROJECTION(lee_os) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->n = 2;
@@ -184,7 +184,7 @@ PJ *PROJECTION(gs48) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->n = 4;
@@ -219,7 +219,7 @@ PJ *PROJECTION(alsk) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->n = 5;
@@ -267,7 +267,7 @@ PJ *PROJECTION(gs50) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->n = 9;
diff --git a/src/projections/moll.cpp b/src/projections/moll.cpp
index 4864c8e1..87b38336 100644
--- a/src/projections/moll.cpp
+++ b/src/projections/moll.cpp
@@ -79,7 +79,7 @@ static PJ * setup(PJ *P, double p) {
PJ *PROJECTION(moll) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
return setup(P, M_HALFPI);
@@ -89,7 +89,7 @@ PJ *PROJECTION(moll) {
PJ *PROJECTION(wag4) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
return setup(P, M_PI/3.);
@@ -98,7 +98,7 @@ PJ *PROJECTION(wag4) {
PJ *PROJECTION(wag5) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->es = 0;
diff --git a/src/projections/natearth.cpp b/src/projections/natearth.cpp
index e1f71089..f83c8467 100644
--- a/src/projections/natearth.cpp
+++ b/src/projections/natearth.cpp
@@ -82,7 +82,7 @@ static PJ_LP natearth_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inv
}
}
if( i == 0 )
- proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT );
+ proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
lp.phi = yc;
/* longitude */
diff --git a/src/projections/natearth2.cpp b/src/projections/natearth2.cpp
index e4516a0a..8bef168d 100644
--- a/src/projections/natearth2.cpp
+++ b/src/projections/natearth2.cpp
@@ -76,7 +76,7 @@ static PJ_LP natearth2_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, in
}
}
if( i == 0 )
- proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT );
+ proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
lp.phi = yc;
/* longitude */
diff --git a/src/projections/nsper.cpp b/src/projections/nsper.cpp
index 951111ac..9e67388e 100644
--- a/src/projections/nsper.cpp
+++ b/src/projections/nsper.cpp
@@ -66,7 +66,7 @@ static PJ_XY nsper_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar
break;
}
if (xy.y < Q->rp) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
xy.y = Q->pn1 / (Q->p - xy.y);
@@ -120,7 +120,7 @@ static PJ_LP nsper_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
double cosz, sinz;
sinz = 1. - rh * rh * Q->pfact;
if (sinz < 0.) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
sinz = (Q->p - sqrt(sinz)) / (Q->pn1 / rh + rh / Q->pn1);
@@ -166,7 +166,10 @@ static PJ *setup(PJ *P) {
}
Q->pn1 = Q->height / P->a; /* normalize by radius */
if ( Q->pn1 <= 0 || Q->pn1 > 1e10 )
- return pj_default_destructor (P, PJD_ERR_INVALID_H);
+ {
+ proj_log_error(P, _("Invalid value for h"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->p = 1. + Q->pn1;
Q->rp = 1. / Q->p;
Q->h = 1. / Q->pn1;
@@ -182,7 +185,7 @@ static PJ *setup(PJ *P) {
PJ *PROJECTION(nsper) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->tilt = 0;
@@ -196,7 +199,7 @@ PJ *PROJECTION(tpers) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
omega = pj_param(P->ctx, P->params, "rtilt").f;
diff --git a/src/projections/ob_tran.cpp b/src/projections/ob_tran.cpp
index 86798e0a..78c57521 100644
--- a/src/projections/ob_tran.cpp
+++ b/src/projections/ob_tran.cpp
@@ -180,26 +180,33 @@ PJ *PROJECTION(ob_tran) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
/* get name of projection to be translated */
if (pj_param(P->ctx, P->params, "so_proj").s == nullptr)
- return destructor(P, PJD_ERR_NO_ROTATION_PROJ);
+ {
+ proj_log_error(P, _("Missing parameter: o_proj"));
+ return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
/* Create the target projection object to rotate */
args = ob_tran_target_params (P->params);
/* avoid endless recursion */
if (args.argv == nullptr ) {
- return destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ);
+ proj_log_error(P, _("Failed to find projection to be rotated"));
+ return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
R = proj_create_argv (P->ctx, args.argc, args.argv);
free (args.argv);
if (nullptr==R)
- return destructor (P, PJD_ERR_UNKNOWN_PROJECTION_ID);
+ {
+ proj_log_error(P, _("Projection to be rotated is unknown"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->link = R;
if (pj_param(P->ctx, P->params, "to_alpha").i) {
@@ -210,7 +217,10 @@ PJ *PROJECTION(ob_tran) {
alpha = pj_param(P->ctx, P->params, "ro_alpha").f;
if (fabs(fabs(phic) - M_HALFPI) <= TOL)
- return destructor(P, PJD_ERR_LAT_0_OR_ALPHA_EQ_90);
+ {
+ proj_log_error(P, _("Invalid value for lat_c: |lat_c| should be < 90°"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->lamp = lamc + aatan2(-cos(alpha), -sin(alpha) * sin(phic));
phip = aasin(P->ctx,cos(phic) * sin(alpha));
@@ -225,9 +235,27 @@ PJ *PROJECTION(ob_tran) {
lam2 = pj_param(P->ctx, P->params, "ro_lon_2").f;
phi2 = pj_param(P->ctx, P->params, "ro_lat_2").f;
con = fabs(phi1);
- if (fabs(phi1 - phi2) <= TOL || con <= TOL ||
- fabs(con - M_HALFPI) <= TOL || fabs(fabs(phi2) - M_HALFPI) <= TOL)
- return destructor(P, PJD_ERR_LAT_1_OR_2_ZERO_OR_90);
+
+ if (fabs(phi1) > M_HALFPI - TOL)
+ {
+ proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+ if (fabs(phi2) > M_HALFPI - TOL)
+ {
+ proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be < 90°"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+ if (fabs(phi1 - phi2) < TOL)
+ {
+ proj_log_error(P, _("Invalid value for lat_1 and lat_2: lat_1 should be different from lat_2"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+ if (con < TOL)
+ {
+ proj_log_error(P, _("Invalid value for lat_1: lat_1 should be different from zero"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->lamp = atan2(cos(phi1) * sin(phi2) * cos(lam1) -
sin(phi1) * cos(phi2) * cos(lam2),
diff --git a/src/projections/ocea.cpp b/src/projections/ocea.cpp
index c78e1ebc..33f8d1f3 100644
--- a/src/projections/ocea.cpp
+++ b/src/projections/ocea.cpp
@@ -54,7 +54,7 @@ PJ *PROJECTION(ocea) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->rok = 1. / P->k0;
diff --git a/src/projections/oea.cpp b/src/projections/oea.cpp
index 46c00d16..2f13ae98 100644
--- a/src/projections/oea.cpp
+++ b/src/projections/oea.cpp
@@ -60,27 +60,34 @@ static PJ_LP oea_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
PJ *PROJECTION(oea) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
- if (((Q->n = pj_param(P->ctx, P->params, "dn").f) <= 0.) ||
- ((Q->m = pj_param(P->ctx, P->params, "dm").f) <= 0.)) {
- return pj_default_destructor(P, PJD_ERR_INVALID_M_OR_N);
- } else {
- Q->theta = pj_param(P->ctx, P->params, "rtheta").f;
- Q->sp0 = sin(P->phi0);
- Q->cp0 = cos(P->phi0);
- Q->rn = 1./ Q->n;
- Q->rm = 1./ Q->m;
- Q->two_r_n = 2. * Q->rn;
- Q->two_r_m = 2. * Q->rm;
- Q->hm = 0.5 * Q->m;
- Q->hn = 0.5 * Q->n;
- P->fwd = oea_s_forward;
- P->inv = oea_s_inverse;
- P->es = 0.;
+ if (((Q->n = pj_param(P->ctx, P->params, "dn").f) <= 0.) )
+ {
+ proj_log_error(P, _("Invalid value for n: it should be > 0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
+ if (((Q->m = pj_param(P->ctx, P->params, "dm").f) <= 0.) )
+ {
+ proj_log_error(P, _("Invalid value for m: it should be > 0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+
+ Q->theta = pj_param(P->ctx, P->params, "rtheta").f;
+ Q->sp0 = sin(P->phi0);
+ Q->cp0 = cos(P->phi0);
+ Q->rn = 1./ Q->n;
+ Q->rm = 1./ Q->m;
+ Q->two_r_n = 2. * Q->rn;
+ Q->two_r_m = 2. * Q->rm;
+ Q->hm = 0.5 * Q->m;
+ Q->hn = 0.5 * Q->n;
+ P->fwd = oea_s_forward;
+ P->inv = oea_s_inverse;
+ P->es = 0.;
+
return P;
}
diff --git a/src/projections/omerc.cpp b/src/projections/omerc.cpp
index 90067cc3..48943972 100644
--- a/src/projections/omerc.cpp
+++ b/src/projections/omerc.cpp
@@ -58,7 +58,7 @@ static PJ_XY omerc_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forwar
const double V = sin(Q->B * lp.lam);
const double U = (S * Q->singam - V * Q->cosgam) / T;
if (fabs(fabs(U) - 1.0) < EPS) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
v = 0.5 * Q->ArB * log((1. - U)/(1. + U));
@@ -98,7 +98,7 @@ static PJ_LP omerc_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers
}
Qp = exp(- Q->BrA * v);
if( Qp == 0 ) {
- proj_errno_set(P, PJD_ERR_INVALID_X_OR_Y);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().lp;
}
Sp = .5 * (Qp - 1. / Qp);
@@ -111,7 +111,7 @@ static PJ_LP omerc_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers
} else {
lp.phi = Q->E / sqrt((1. + Up) / (1. - Up));
if ((lp.phi = pj_phi2(P->ctx, pow(lp.phi, 1. / Q->B), P->e)) == HUGE_VAL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
lp.lam = - Q->rB * atan2((Sp * Q->cosgam -
@@ -128,7 +128,7 @@ PJ *PROJECTION(omerc) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->no_rot = pj_param(P->ctx, P->params, "bno_rot").i;
@@ -154,14 +154,36 @@ PJ *PROJECTION(omerc) {
phi1 = pj_param(P->ctx, P->params, "rlat_1").f;
lam2 = pj_param(P->ctx, P->params, "rlon_2").f;
phi2 = pj_param(P->ctx, P->params, "rlat_2").f;
- if (fabs(phi1) > M_HALFPI || fabs(phi2) > M_HALFPI)
- return pj_default_destructor(P, PJD_ERR_LAT_LARGER_THAN_90);
- if (fabs(phi1 - phi2) <= TOL ||
- (con = fabs(phi1)) <= TOL ||
- fabs(con - M_HALFPI) <= TOL ||
- fabs(fabs(P->phi0) - M_HALFPI) <= TOL ||
- fabs(fabs(phi2) - M_HALFPI) <= TOL)
- return pj_default_destructor(P, PJD_ERR_LAT_0_OR_ALPHA_EQ_90);
+ con = fabs(phi1);
+
+ if (fabs(phi1) > M_HALFPI - TOL)
+ {
+ proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+ if (fabs(phi2) > M_HALFPI - TOL)
+ {
+ proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be < 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+
+ if (fabs(phi1 - phi2) <= TOL )
+ {
+ proj_log_error(P, _("Invalid value for lat_1/lat_2: lat_1 should be different from lat_2"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+
+ if (con <= TOL )
+ {
+ proj_log_error(P, _("Invalid value for lat_1: lat_1 should be different from 0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+
+ if (fabs(fabs(P->phi0) - M_HALFPI) <= TOL)
+ {
+ proj_log_error(P, _("Invalid value for lat_01: |lat_0| should be < 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
}
com = sqrt(P->one_es);
if (fabs(P->phi0) > EPS) {
@@ -193,9 +215,13 @@ PJ *PROJECTION(omerc) {
gamma = alpha_c;
} else
alpha_c = aasin(P->ctx, D*sin(gamma0 = gamma));
- if( fabs(fabs(P->phi0) - M_HALFPI) <= TOL ) {
- return pj_default_destructor(P, PJD_ERR_LAT_0_OR_ALPHA_EQ_90);
+
+ if (fabs(fabs(P->phi0) - M_HALFPI) <= TOL)
+ {
+ proj_log_error(P, _("Invalid value for lat_01: |lat_0| should be < 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
+
P->lam0 = lamc - aasin(P->ctx, .5 * (F - 1. / F) *
tan(gamma0)) / Q->B;
} else {
@@ -205,7 +231,8 @@ PJ *PROJECTION(omerc) {
p = (L - H) / (L + H);
if( p == 0 ) {
// Not quite, but es is very close to 1...
- return pj_default_destructor(P, PJD_ERR_INVALID_ECCENTRICITY);
+ proj_log_error(P, _("Invalid value for eccentricity"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
J = Q->E * Q->E;
J = (J - L * H) / (J + L * H);
@@ -217,7 +244,8 @@ PJ *PROJECTION(omerc) {
J * tan(.5 * Q->B * (lam1 - lam2)) / p) / Q->B);
const double denom = F - 1. / F;
if( denom == 0 ) {
- return pj_default_destructor(P, PJD_ERR_INVALID_ECCENTRICITY);
+ proj_log_error(P, _("Invalid value for eccentricity"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
gamma0 = atan(2. * sin(Q->B * adjlon(lam1 - P->lam0)) / denom);
gamma = alpha_c = aasin(P->ctx, D * sin(gamma0));
diff --git a/src/projections/ortho.cpp b/src/projections/ortho.cpp
index 4417dac7..9e0d9bba 100644
--- a/src/projections/ortho.cpp
+++ b/src/projections/ortho.cpp
@@ -29,7 +29,7 @@ struct pj_opaque {
#define EPS10 1.e-10
static PJ_XY forward_error(PJ *P, PJ_LP lp, PJ_XY xy) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
proj_log_trace(P, "Coordinate (%.3f, %.3f) is on the unprojected hemisphere",
proj_todeg(lp.lam), proj_todeg(lp.phi));
return xy;
@@ -88,7 +88,7 @@ static PJ_LP ortho_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
sinc = rh;
if (sinc > 1.) {
if ((sinc - 1.) > EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
proj_log_trace(P, "Point (%.3f, %.3f) is outside the projection boundary");
return lp;
}
@@ -176,7 +176,7 @@ static PJ_LP ortho_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver
const double rh2 = SQ(xy.x) + SQ(xy.y);
if (rh2 >= 1. - 1e-15) {
if ((rh2 - 1.) > EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
proj_log_trace(P, "Point (%.3f, %.3f) is outside the projection boundary");
lp.lam = HUGE_VAL; lp.phi = HUGE_VAL;
return lp;
@@ -200,7 +200,7 @@ static PJ_LP ortho_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver
// Equation of the ellipse
if( SQ(xy.x) + SQ(xy.y * (P->a / P->b)) > 1 + 1e-11 ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
proj_log_trace(P, "Point (%.3f, %.3f) is outside the projection boundary");
lp.lam = HUGE_VAL; lp.phi = HUGE_VAL;
return lp;
@@ -228,7 +228,7 @@ static PJ_LP ortho_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver
xy_recentered.x = xy.x;
xy_recentered.y = (xy.y - Q->y_shift) / Q->y_scale;
if( SQ(xy.x) + SQ(xy_recentered.y) > 1 + 1e-11 ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
proj_log_trace(P, "Point (%.3f, %.3f) is outside the projection boundary");
lp.lam = HUGE_VAL; lp.phi = HUGE_VAL;
return lp;
@@ -273,7 +273,7 @@ static PJ_LP ortho_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver
return lp;
}
}
- proj_context_errno_set(P->ctx, PJD_ERR_NON_CONVERGENT);
+ proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
@@ -281,7 +281,7 @@ static PJ_LP ortho_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver
PJ *PROJECTION(ortho) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->sinph0 = sin(P->phi0);
diff --git a/src/projections/patterson.cpp b/src/projections/patterson.cpp
index 32544580..d24ee98d 100644
--- a/src/projections/patterson.cpp
+++ b/src/projections/patterson.cpp
@@ -100,7 +100,7 @@ static PJ_LP patterson_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, in
}
}
if( i == 0 )
- proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT );
+ proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
lp.phi = yc;
/* longitude */
diff --git a/src/projections/poly.cpp b/src/projections/poly.cpp
index 4ea95cc7..7b0b7717 100644
--- a/src/projections/poly.cpp
+++ b/src/projections/poly.cpp
@@ -80,7 +80,7 @@ static PJ_LP poly_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse
const double cp = cos(lp.phi);
const double s2ph = sp * cp;
if (fabs(cp) < ITOL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
double mlp = sqrt(1. - P->es * sp * sp);
@@ -97,7 +97,7 @@ static PJ_LP poly_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse
break;
}
if (!i) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
const double c = sin(lp.phi);
@@ -128,7 +128,7 @@ static PJ_LP poly_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse
break;
--i;
if( i == 0 ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
}
@@ -156,14 +156,14 @@ static PJ *destructor(PJ *P, int errlev) {
PJ *PROJECTION(poly) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
if (P->es != 0.0) {
if (!(Q->en = pj_enfn(P->es)))
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
Q->ml0 = pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en);
P->inv = poly_e_inverse;
P->fwd = poly_e_forward;
diff --git a/src/projections/putp3.cpp b/src/projections/putp3.cpp
index 09763851..3d72fdce 100644
--- a/src/projections/putp3.cpp
+++ b/src/projections/putp3.cpp
@@ -40,7 +40,7 @@ static PJ_LP putp3_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
PJ *PROJECTION(putp3) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->A = 4. * RPISQ;
@@ -55,7 +55,7 @@ PJ *PROJECTION(putp3) {
PJ *PROJECTION(putp3p) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->A = 2. * RPISQ;
diff --git a/src/projections/putp4p.cpp b/src/projections/putp4p.cpp
index 8df18972..d17dbfda 100644
--- a/src/projections/putp4p.cpp
+++ b/src/projections/putp4p.cpp
@@ -47,7 +47,7 @@ static PJ_LP putp4p_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver
PJ *PROJECTION(putp4p) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->C_x = 0.874038744;
@@ -64,7 +64,7 @@ PJ *PROJECTION(putp4p) {
PJ *PROJECTION(weren) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->C_x = 1.;
diff --git a/src/projections/putp5.cpp b/src/projections/putp5.cpp
index 5e70382d..d2e55625 100644
--- a/src/projections/putp5.cpp
+++ b/src/projections/putp5.cpp
@@ -45,7 +45,7 @@ static PJ_LP putp5_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
PJ *PROJECTION(putp5) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->A = 2.;
@@ -62,7 +62,7 @@ PJ *PROJECTION(putp5) {
PJ *PROJECTION(putp5p) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->A = 1.5;
diff --git a/src/projections/putp6.cpp b/src/projections/putp6.cpp
index da8c0a7c..a5d51020 100644
--- a/src/projections/putp6.cpp
+++ b/src/projections/putp6.cpp
@@ -61,7 +61,7 @@ static PJ_LP putp6_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
PJ *PROJECTION(putp6) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->C_x = 1.01346;
@@ -81,7 +81,7 @@ PJ *PROJECTION(putp6) {
PJ *PROJECTION(putp6p) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->C_x = 0.44329;
diff --git a/src/projections/qsc.cpp b/src/projections/qsc.cpp
index dd9ce965..ea35eb8c 100644
--- a/src/projections/qsc.cpp
+++ b/src/projections/qsc.cpp
@@ -379,7 +379,7 @@ static PJ_LP qsc_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse
PJ *PROJECTION(qsc) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->inv = qsc_e_inverse;
diff --git a/src/projections/robin.cpp b/src/projections/robin.cpp
index 6a1405b6..4dfc306c 100644
--- a/src/projections/robin.cpp
+++ b/src/projections/robin.cpp
@@ -86,7 +86,7 @@ static PJ_XY robin_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar
dphi = fabs(lp.phi);
i = isnan(lp.phi) ? -1 : lround(floor(dphi * C1 + 1e-15));
if( i < 0 ){
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
if (i >= NODES) i = NODES;
@@ -109,7 +109,7 @@ static PJ_LP robin_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
lp.phi = fabs(xy.y / FYC);
if (lp.phi >= 1.) { /* simple pathologic cases */
if (lp.phi > ONEEPS) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
else {
@@ -120,7 +120,7 @@ static PJ_LP robin_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
/* in Y space, reduce to table interval */
long i = isnan(lp.phi) ? -1 : lround(floor(lp.phi * NODES));
if( i < 0 || i >= NODES ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
for (;;) {
@@ -138,12 +138,12 @@ static PJ_LP robin_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
break;
}
if( iters == 0 )
- proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT );
+ proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
lp.phi = (5 * i + t) * DEG_TO_RAD;
if (xy.y < 0.) lp.phi = -lp.phi;
lp.lam /= V(X[i], t);
if( fabs(lp.lam) > M_PI ) {
- proj_errno_set(P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
lp = proj_coord_error().lp;
}
}
diff --git a/src/projections/rouss.cpp b/src/projections/rouss.cpp
index 2eb13b3d..7b513fdb 100644
--- a/src/projections/rouss.cpp
+++ b/src/projections/rouss.cpp
@@ -95,7 +95,7 @@ static PJ *destructor (PJ *P, int errlev) {
if (static_cast<struct pj_opaque*>(P->opaque)->en)
free (static_cast<struct pj_opaque*>(P->opaque)->en);
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
}
@@ -104,11 +104,11 @@ PJ *PROJECTION(rouss) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
if (!((Q->en = proj_mdist_ini(P->es))))
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
es2 = sin(P->phi0);
Q->s0 = proj_mdist(P->phi0, es2, cos(P->phi0), Q->en);
diff --git a/src/projections/rpoly.cpp b/src/projections/rpoly.cpp
index e3f09c59..f2e45f5e 100644
--- a/src/projections/rpoly.cpp
+++ b/src/projections/rpoly.cpp
@@ -46,7 +46,7 @@ static PJ_XY rpoly_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar
PJ *PROJECTION(rpoly) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->phi1 = fabs(pj_param(P->ctx, P->params, "rlat_ts").f);
diff --git a/src/projections/sch.cpp b/src/projections/sch.cpp
index 359e8efc..6e7641ff 100644
--- a/src/projections/sch.cpp
+++ b/src/projections/sch.cpp
@@ -131,7 +131,7 @@ static PJ *setup(PJ *P) { /* general initialization */
// Pass a dummy ellipsoid definition that will be overridden just afterwards
Q->cart = proj_create(P->ctx, "+proj=cart +a=1");
if (Q->cart == nullptr)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
/* inherit ellipsoid definition from P to Q->cart */
pj_inherit_ellipsoid_def (P, Q->cart);
@@ -154,7 +154,7 @@ static PJ *setup(PJ *P) { /* general initialization */
/* Set up local sphere at the given peg point */
Q->cart_sph = proj_create(P->ctx, "+proj=cart +a=1");
if (Q->cart_sph == nullptr)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
pj_calc_ellipsoid_params(Q->cart_sph, Q->rcurv, 0);
/* Set up the transformation matrices */
@@ -186,7 +186,7 @@ static PJ *setup(PJ *P) { /* general initialization */
PJ *PROJECTION(sch) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
P->destructor = destructor;
@@ -196,21 +196,24 @@ PJ *PROJECTION(sch) {
if (pj_param(P->ctx, P->params, "tplat_0").i)
Q->plat = pj_param(P->ctx, P->params, "rplat_0").f;
else {
- return pj_default_destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ);
+ proj_log_error(P, _("Missing parameter plat_0."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
/* Check if peg longitude was defined */
if (pj_param(P->ctx, P->params, "tplon_0").i)
Q->plon = pj_param(P->ctx, P->params, "rplon_0").f;
else {
- return pj_default_destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ);
+ proj_log_error(P, _("Missing parameter plon_0."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
/* Check if peg heading is defined */
if (pj_param(P->ctx, P->params, "tphdg_0").i)
Q->phdg = pj_param(P->ctx, P->params, "rphdg_0").f;
else {
- return pj_default_destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ);
+ proj_log_error(P, _("Missing parameter phdg_0."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
diff --git a/src/projections/sconics.cpp b/src/projections/sconics.cpp
index c12b05a2..8e977028 100644
--- a/src/projections/sconics.cpp
+++ b/src/projections/sconics.cpp
@@ -48,15 +48,28 @@ static int phi12(PJ *P, double *del) {
double p1, p2;
int err = 0;
- if (!pj_param(P->ctx, P->params, "tlat_1").i ||
- !pj_param(P->ctx, P->params, "tlat_2").i) {
- err = -41;
- } else {
+ if (!pj_param(P->ctx, P->params, "tlat_1").i )
+ {
+ proj_log_error(P, _("Missing parameter: lat_1 should be specified"));
+ err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE;
+ }
+ else if ( !pj_param(P->ctx, P->params, "tlat_2").i)
+ {
+ proj_log_error(P, _("Missing parameter: lat_2 should be specified"));
+ err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE;
+ }
+ else
+ {
p1 = pj_param(P->ctx, P->params, "rlat_1").f;
p2 = pj_param(P->ctx, P->params, "rlat_2").f;
*del = 0.5 * (p2 - p1);
- static_cast<struct pj_opaque*>(P->opaque)->sig = 0.5 * (p2 + p1);
- err = (fabs(*del) < EPS || fabs(static_cast<struct pj_opaque*>(P->opaque)->sig) < EPS) ? PJD_ERR_ABS_LAT1_EQ_ABS_LAT2 : 0;
+ const double sig = 0.5 * (p2 + p1);
+ static_cast<struct pj_opaque*>(P->opaque)->sig = sig;
+ err = (fabs(*del) < EPS || fabs(sig) < EPS) ? PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE : 0;
+ if( err )
+ {
+ proj_log_error(P, _("Illegal value for lat_1 and lat_2: |lat_1 - lat_2| and |lat_1 + lat_2| should be > 0"));
+ }
}
return err;
}
@@ -119,7 +132,7 @@ static PJ *setup(PJ *P, enum Type type) {
int err;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->type = type;
@@ -167,8 +180,10 @@ static PJ *setup(PJ *P, enum Type type) {
Q->c1 = 1./tan (Q->sig);
del = P->phi0 - Q->sig;
if (fabs (del) - EPS10 >= M_HALFPI)
- return pj_default_destructor(P, PJD_ERR_LAT_0_HALF_PI_FROM_MEAN);
-
+ {
+ proj_log_error(P, _("Invalid value for lat_0/lat_1/lat_2: |lat_0 - 0.5 * (lat_1 + lat_2)| should be < 90°"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
Q->rho_0 = Q->c2 * (Q->c1 - tan (del));
break;
diff --git a/src/projections/somerc.cpp b/src/projections/somerc.cpp
index a184500c..6d6885e8 100644
--- a/src/projections/somerc.cpp
+++ b/src/projections/somerc.cpp
@@ -62,7 +62,7 @@ static PJ_LP somerc_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver
lp.phi = phip;
lp.lam = lamp / Q->c;
} else {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
return (lp);
@@ -73,7 +73,7 @@ PJ *PROJECTION(somerc) {
double cp, phip0, sp;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
diff --git a/src/projections/stere.cpp b/src/projections/stere.cpp
index ad1caae2..31b0fead 100644
--- a/src/projections/stere.cpp
+++ b/src/projections/stere.cpp
@@ -60,7 +60,7 @@ static PJ_XY stere_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forwar
const double denom = Q->cosX1 * (1. + Q->sinX1 * sinX +
Q->cosX1 * cosX * coslam);
if( denom == 0 ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().xy;
}
A = Q->akm1 / denom;
@@ -117,7 +117,7 @@ static PJ_XY stere_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar
xy.y = 1. + sinph0 * sinphi + cosph0 * cosphi * coslam;
oblcon:
if (xy.y <= EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
xy.y = Q->akm1 / xy.y;
@@ -131,7 +131,7 @@ oblcon:
/*-fallthrough*/
case S_POLE:
if (fabs (lp.phi - M_HALFPI) < TOL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
xy.y = Q->akm1 * tan (M_FORTPI + .5 * lp.phi);
@@ -190,7 +190,7 @@ static PJ_LP stere_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers
phi_l = lp.phi;
}
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
@@ -304,7 +304,7 @@ static PJ *setup(PJ *P) { /* general initialization */
PJ *PROJECTION(stere) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->phits = pj_param (P->ctx, P->params, "tlat_ts").i ?
@@ -317,13 +317,14 @@ PJ *PROJECTION(stere) {
PJ *PROJECTION(ups) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
/* International Ellipsoid */
P->phi0 = pj_param(P->ctx, P->params, "bsouth").i ? - M_HALFPI: M_HALFPI;
if (P->es == 0.0) {
- return pj_default_destructor (P, PJD_ERR_ELLIPSOID_USE_REQUIRED);
+ proj_log_error(P, _("Invalid value for es: only ellipsoidal formulation supported"));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
P->k0 = .994;
P->x0 = 2000000.;
diff --git a/src/projections/sterea.cpp b/src/projections/sterea.cpp
index 4dd22d2f..19f2b778 100644
--- a/src/projections/sterea.cpp
+++ b/src/projections/sterea.cpp
@@ -55,7 +55,7 @@ static PJ_XY sterea_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forwa
cosl = cos(lp.lam);
const double denom = 1. + Q->sinc0 * sinc + Q->cosc0 * cosc * cosl;
if( denom == 0.0 ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().xy;
}
k = P->k0 * Q->R2 / denom;
@@ -103,12 +103,12 @@ PJ *PROJECTION(sterea) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->en = pj_gauss_ini(P->e, P->phi0, &(Q->phic0), &R);
if (nullptr==Q->en)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
Q->sinc0 = sin (Q->phic0);
Q->cosc0 = cos (Q->phic0);
diff --git a/src/projections/sts.cpp b/src/projections/sts.cpp
index 75190e85..1042e38b 100644
--- a/src/projections/sts.cpp
+++ b/src/projections/sts.cpp
@@ -72,7 +72,7 @@ static PJ *setup(PJ *P, double p, double q, int mode) {
PJ *PROJECTION(fouc) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
return setup(P, 2., 2., 1);
}
@@ -82,7 +82,7 @@ PJ *PROJECTION(fouc) {
PJ *PROJECTION(kav5) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
return setup(P, 1.50488, 1.35439, 0);
@@ -93,7 +93,7 @@ PJ *PROJECTION(kav5) {
PJ *PROJECTION(qua_aut) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
return setup(P, 2., 2., 0);
}
@@ -103,7 +103,7 @@ PJ *PROJECTION(qua_aut) {
PJ *PROJECTION(mbt_s) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
return setup(P, 1.48875, 1.36509, 0);
}
diff --git a/src/projections/tcc.cpp b/src/projections/tcc.cpp
index 9413b567..c53bed05 100644
--- a/src/projections/tcc.cpp
+++ b/src/projections/tcc.cpp
@@ -16,7 +16,7 @@ static PJ_XY tcc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward
const double b = cos (lp.phi) * sin (lp.lam);
const double bt = 1. - b * b;
if (bt < EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
xy.x = b / sqrt(bt);
diff --git a/src/projections/tmerc.cpp b/src/projections/tmerc.cpp
index 8f897061..68411829 100644
--- a/src/projections/tmerc.cpp
+++ b/src/projections/tmerc.cpp
@@ -89,7 +89,7 @@ static PJ_XY approx_e_fwd (PJ_LP lp, PJ *P)
if( lp.lam < -M_HALFPI || lp.lam > M_HALFPI ) {
xy.x = HUGE_VAL;
xy.y = HUGE_VAL;
- proj_context_errno_set( P->ctx, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT );
+ proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN );
return xy;
}
@@ -123,7 +123,7 @@ static PJ_XY tmerc_spherical_fwd (PJ_LP lp, PJ *P) {
cosphi = cos(lp.phi);
b = cosphi * sin (lp.lam);
if (fabs (fabs (b) - 1.) <= EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
@@ -138,7 +138,7 @@ static PJ_XY tmerc_spherical_fwd (PJ_LP lp, PJ *P) {
}
else if (b >= 1.) {
if ((b - 1.) > EPS10) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
else xy.y = 0.;
@@ -190,7 +190,7 @@ static PJ_LP tmerc_spherical_inv (PJ_XY xy, PJ *P) {
h = exp(xy.x / Q->esp);
if( h == 0 ) {
- proj_errno_set(P, PJD_ERR_INVALID_X_OR_Y);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().lp;
}
g = .5 * (h - 1. / h);
@@ -224,7 +224,7 @@ static PJ *setup_approx(PJ *P) {
if (P->es != 0.0) {
if (!(Q->en = pj_enfn(P->es)))
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
Q->ml0 = pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en);
Q->esp = P->es / (1. - P->es);
@@ -402,8 +402,10 @@ static PJ_XY exact_e_fwd (PJ_LP lp, PJ *P) {
if (fabs (Ce) <= 2.623395162778) {
xy.y = Q->Qn * Cn + Q->Zb; /* Northing */
xy.x = Q->Qn * Ce; /* Easting */
- } else
+ } else {
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
xy.x = xy.y = HUGE_VAL;
+ }
return xy;
}
@@ -476,8 +478,10 @@ static PJ_LP exact_e_inv (PJ_XY xy, PJ *P) {
lp.phi = gatg (Q->cgb, PROJ_ETMERC_ORDER, Cn, cos_2_Cn, sin_2_Cn);
lp.lam = Ce;
}
- else
+ else {
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
lp.phi = lp.lam = HUGE_VAL;
+ }
return lp;
}
@@ -587,7 +591,7 @@ static PJ *setup(PJ *P, TMercAlgo eAlg) {
struct tmerc_data *Q = static_cast<struct tmerc_data*>(calloc (1, sizeof (struct tmerc_data)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
if( P->es == 0 )
@@ -701,14 +705,18 @@ PJ *PROJECTION(tmerc) {
TMercAlgo algo;
if( !getAlgoFromParams(P, algo) )
- return pj_default_destructor(P, PJD_ERR_INVALID_ARG);
+ {
+ proj_log_error(P, _("Invalid value for algo"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
return setup(P, algo);
}
PJ *PROJECTION(etmerc) {
if (P->es == 0.0) {
- return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED);
+ proj_log_error(P, _("Invalid value for eccentricity: it should not be zero"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
return setup (P, TMercAlgo::PODER_ENGSAGER);
@@ -720,10 +728,12 @@ PJ *PROJECTION(etmerc) {
PJ *PROJECTION(utm) {
long zone;
if (P->es == 0.0) {
- return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED);
+ proj_log_error(P, _("Invalid value for eccentricity: it should not be zero"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
if (P->lam0 < -1000.0 || P->lam0 > 1000.0) {
- return pj_default_destructor(P, PJD_ERR_INVALID_UTM_ZONE);
+ proj_log_error(P, _("Invalid value for lon_0"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
P->y0 = pj_param (P->ctx, P->params, "bsouth").i ? 10000000. : 0.;
@@ -734,7 +744,8 @@ PJ *PROJECTION(utm) {
if (zone > 0 && zone <= 60)
--zone;
else {
- return pj_default_destructor(P, PJD_ERR_INVALID_UTM_ZONE);
+ proj_log_error(P, _("Invalid value for zone"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
}
else /* nearest central meridian input */
@@ -751,6 +762,9 @@ PJ *PROJECTION(utm) {
TMercAlgo algo;
if( !getAlgoFromParams(P, algo) )
- return pj_default_destructor(P, PJD_ERR_INVALID_ARG);
+ {
+ proj_log_error(P, _("Invalid value for algo"));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
return setup(P, algo);
}
diff --git a/src/projections/tobmerc.cpp b/src/projections/tobmerc.cpp
index f05a9b6b..13633a91 100644
--- a/src/projections/tobmerc.cpp
+++ b/src/projections/tobmerc.cpp
@@ -20,7 +20,7 @@ static PJ_XY tobmerc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forw
// it's not even that large, merely 38.025...). Even if the logic was
// such that phi was strictly equal to pi/2, allowing xy.y = inf would be
// a reasonable result.
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
diff --git a/src/projections/tpeqd.cpp b/src/projections/tpeqd.cpp
index 90efb395..5d3c7144 100644
--- a/src/projections/tpeqd.cpp
+++ b/src/projections/tpeqd.cpp
@@ -66,7 +66,7 @@ PJ *PROJECTION(tpeqd) {
double lam_1, lam_2, phi_1, phi_2, A12;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
@@ -77,7 +77,10 @@ PJ *PROJECTION(tpeqd) {
lam_2 = pj_param(P->ctx, P->params, "rlon_2").f;
if (phi_1 == phi_2 && lam_1 == lam_2)
- return pj_default_destructor(P, PJD_ERR_CONTROL_POINT_NO_DIST);
+ {
+ proj_log_error(P, _("Invalid value for lat_1/lon_1/lat_2/lon_2: the 2 points should be distinct."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
P->lam0 = adjlon (0.5 * (lam_1 + lam_2));
Q->dlam2 = adjlon (lam_2 - lam_1);
@@ -92,7 +95,8 @@ PJ *PROJECTION(tpeqd) {
Q->z02 = aacos(P->ctx, Q->sp1 * Q->sp2 + Q->cp1 * Q->cp2 * cos (Q->dlam2));
if( Q->z02 == 0.0 ) {
// Actually happens when both lat_1 = lat_2 and |lat_1| = 90
- return pj_default_destructor(P, PJD_ERR_LAT_1_OR_2_ZERO_OR_90);
+ proj_log_error(P, _("Invalid value for lat_1 and lat_2: their absolute value should be < 90°."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
Q->hz0 = .5 * Q->z02;
A12 = atan2(Q->cp2 * sin (Q->dlam2),
diff --git a/src/projections/urm5.cpp b/src/projections/urm5.cpp
index c3021841..965c9458 100644
--- a/src/projections/urm5.cpp
+++ b/src/projections/urm5.cpp
@@ -32,22 +32,29 @@ PJ *PROJECTION(urm5) {
double alpha, t;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
- if (pj_param(P->ctx, P->params, "tn").i) {
- Q->n = pj_param(P->ctx, P->params, "dn").f;
- if (Q->n <= 0. || Q->n > 1.)
- return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE);
- } else {
- return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE);
+ if (!pj_param(P->ctx, P->params, "tn").i )
+ {
+ proj_log_error(P, _("Missing parameter n."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
+
+ Q->n = pj_param(P->ctx, P->params, "dn").f;
+ if (Q->n <= 0. || Q->n > 1.)
+ {
+ proj_log_error(P, _("Invalid value for n: it should be in ]0,1] range."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
+
Q->q3 = pj_param(P->ctx, P->params, "dq").f / 3.;
alpha = pj_param(P->ctx, P->params, "ralpha").f;
t = Q->n * sin (alpha);
const double denom = sqrt (1. - t * t);
if( denom == 0 ) {
- return pj_default_destructor(P, PJD_ERR_LAT_0_OR_ALPHA_EQ_90);
+ proj_log_error(P, _("Invalid value for n / alpha: n * sin(|alpha|) should be < 1."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
Q->m = cos (alpha) / denom;
Q->rmn = 1. / (Q->m * Q->n);
diff --git a/src/projections/urmfps.cpp b/src/projections/urmfps.cpp
index 5d689f9f..6df9ba57 100644
--- a/src/projections/urmfps.cpp
+++ b/src/projections/urmfps.cpp
@@ -49,16 +49,21 @@ static PJ *setup(PJ *P) {
PJ *PROJECTION(urmfps) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
- if (pj_param(P->ctx, P->params, "tn").i) {
- static_cast<struct pj_opaque*>(P->opaque)->n = pj_param(P->ctx, P->params, "dn").f;
- if (static_cast<struct pj_opaque*>(P->opaque)->n <= 0. || static_cast<struct pj_opaque*>(P->opaque)->n > 1.)
- return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE);
- } else {
- return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE);
+ if (!pj_param(P->ctx, P->params, "tn").i )
+ {
+ proj_log_error(P, _("Missing parameter n."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
+
+ Q->n = pj_param(P->ctx, P->params, "dn").f;
+ if (Q->n <= 0. || Q->n > 1.)
+ {
+ proj_log_error(P, _("Invalid value for n: it should be in ]0,1] range."));
+ return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
return setup(P);
@@ -68,7 +73,7 @@ PJ *PROJECTION(urmfps) {
PJ *PROJECTION(wag1) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
static_cast<struct pj_opaque*>(P->opaque)->n = 0.8660254037844386467637231707;
diff --git a/src/projections/vandg.cpp b/src/projections/vandg.cpp
index 107fc3b9..603aa35c 100644
--- a/src/projections/vandg.cpp
+++ b/src/projections/vandg.cpp
@@ -19,7 +19,7 @@ static PJ_XY vandg_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar
// Comments tie this formulation to Snyder (1987), p. 241.
p2 = fabs(lp.phi / M_HALFPI); // sin(theta) from (29-6)
if ((p2 - TOL) > 1.) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
if (p2 > 1.)
@@ -56,7 +56,7 @@ static PJ_XY vandg_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar
// y from (29-2) has been expressed in terms of x here
xy.y = 1. - xy.y * (xy.y + 2. * al);
if (xy.y < -TOL) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return xy;
}
if (xy.y < 0.)
@@ -95,7 +95,7 @@ static PJ_LP vandg_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
d = C2_27 * c2 * c2 * c2 + (c0 * c0 - THIRD * c2 * c1) / c3; // d (29-14)
const double al_mul_m = al * m; // a1*m1
if( fabs(al_mul_m) < 1e-16 ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().lp;
}
d = 3. * d /al_mul_m; // cos(3*theta1) (29-17)
@@ -109,7 +109,7 @@ static PJ_LP vandg_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
lp.lam = fabs(xy.x) <= TOL ? 0. :
.5 * (r - PISQ + (t <= 0. ? 0. : sqrt(t))) / xy.x;
} else {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return lp;
}
diff --git a/src/projections/vandg2.cpp b/src/projections/vandg2.cpp
index cd7e7b6c..b25c677a 100644
--- a/src/projections/vandg2.cpp
+++ b/src/projections/vandg2.cpp
@@ -55,7 +55,7 @@ static PJ_XY vandg2_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwa
PJ *PROJECTION(vandg2) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->vdg3 = 0;
@@ -67,7 +67,7 @@ PJ *PROJECTION(vandg2) {
PJ *PROJECTION(vandg3) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
Q->vdg3 = 1;
diff --git a/src/projections/wag3.cpp b/src/projections/wag3.cpp
index ed3250ef..4d61d803 100644
--- a/src/projections/wag3.cpp
+++ b/src/projections/wag3.cpp
@@ -37,7 +37,7 @@ PJ *PROJECTION(wag3) {
double ts;
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
diff --git a/src/projections/wink1.cpp b/src/projections/wink1.cpp
index f4ffafe3..0fbd6a77 100644
--- a/src/projections/wink1.cpp
+++ b/src/projections/wink1.cpp
@@ -35,7 +35,7 @@ static PJ_LP wink1_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers
PJ *PROJECTION(wink1) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
static_cast<struct pj_opaque*>(P->opaque)->cosphi1 = cos (pj_param(P->ctx, P->params, "rlat_ts").f);
diff --git a/src/projections/wink2.cpp b/src/projections/wink2.cpp
index b5b1e812..bd46930f 100644
--- a/src/projections/wink2.cpp
+++ b/src/projections/wink2.cpp
@@ -55,7 +55,7 @@ static PJ_LP wink2_s_inverse(PJ_XY xy, PJ *P)
PJ *PROJECTION(wink2) {
struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
static_cast<struct pj_opaque*>(P->opaque)->cosphi1 = cos(pj_param(P->ctx, P->params, "rlat_1").f);
diff --git a/src/strerrno.cpp b/src/strerrno.cpp
index 3d0131c6..f94de55d 100644
--- a/src/strerrno.cpp
+++ b/src/strerrno.cpp
@@ -8,103 +8,68 @@
#include "proj_config.h"
#include "proj_internal.h"
-static const char * const
-pj_err_list[] = {
- "no arguments in initialization list", /* -1 */
- "no options found in 'init' file", /* -2 */
- "no colon in init= string", /* -3 */
- "projection not named", /* -4 */
- "unknown projection id", /* -5 */
- "effective eccentricity < 0 or >= 1.", /* -6 */
- "unknown unit conversion id", /* -7 */
- "invalid boolean param argument", /* -8 */
- "unknown elliptical parameter name", /* -9 */
- "reciprocal flattening (1/f) = 0", /* -10 */
- "|radius reference latitude| > 90", /* -11 */
- "squared eccentricity < 0", /* -12 */
- "major axis or radius = 0 or not given", /* -13 */
- "latitude or longitude exceeded limits", /* -14 */
- "invalid x or y", /* -15 */
- "improperly formed DMS value", /* -16 */
- "non-convergent inverse meridional dist", /* -17 */
- "non-convergent sinh(psi) to tan(phi)", /* -18 */
- "acos/asin: |arg| >1.+1e-14", /* -19 */
- "tolerance condition error", /* -20 */
- "conic lat_1 = -lat_2", /* -21 */
- "lat_0, lat_1 or lat_2 >= 90", /* -22 */
- "lat_1 = 0", /* -23 */
- "lat_ts >= 90", /* -24 */
- "no distance between control points", /* -25 */
- "projection not selected to be rotated", /* -26 */
- "W <= 0 or M <= 0", /* -27 */
- "lsat not in 1-5 range", /* -28 */
- "path not in range", /* -29 */
- "h <= 0 or h > 1e10 * a", /* -30 */
- "k <= 0", /* -31 */
- "lat_1=lat_2 or lat_1=0 or lat_2=90", /* -32 */
- "lat_0 = 0 or 90 or alpha = 90", /* -33 */
- "elliptical usage required", /* -34 */
- "invalid UTM zone number", /* -35 */
- "", /* no longer used */ /* -36 */
- "failed to find projection to be rotated", /* -37 */
- "failed to load datum shift file", /* -38 */
- "both n & m must be spec'd and > 0", /* -39 */
- "n <= 0, n > 1 or not specified", /* -40 */
- "lat_1 or lat_2 not specified", /* -41 */
- "|lat_1| == |lat_2|", /* -42 */
- "lat_0 is pi/2 from mean lat", /* -43 */
- "unparseable coordinate system definition", /* -44 */
- "geocentric transformation missing z or ellps", /* -45 */
- "unknown prime meridian conversion id", /* -46 */
- "illegal axis orientation combination", /* -47 */
- "point not within available datum shift grids", /* -48 */
- "invalid sweep axis, choose x or y", /* -49 */
- "malformed pipeline", /* -50 */
- "unit conversion factor must be > 0", /* -51 */
- "invalid scale", /* -52 */
- "non-convergent computation", /* -53 */
- "missing required arguments", /* -54 */
- "lat_0 = 0", /* -55 */
- "ellipsoidal usage unsupported", /* -56 */
- "only one +init allowed for non-pipeline operations", /* -57 */
- "argument not numerical or out of range", /* -58 */
- "inconsistent unit type between input and output", /* -59 */
- "arguments are mutually exclusive", /* -60 */
- "generic error of unknown origin", /* -61 */
- "network error", /* -62 */
- /* When adding error messages, remember to update ID defines in
- src/proj_internal.h and src/apps/gie.cpp */
-};
+const char* proj_errno_string(int err) {
+ return proj_context_errno_string(pj_get_default_ctx(), err);
+}
+static const struct
+{
+ int num;
+ const char *str;
+} error_strings[] = {
-const char* proj_errno_string(int err) {
- const int max_error = 9999;
- static char note[50];
- size_t adjusted_err;
+ { PROJ_ERR_INVALID_OP_WRONG_SYNTAX, _("Invalid PROJ string syntax") },
+ { PROJ_ERR_INVALID_OP_MISSING_ARG, _("Missing argument") },
+ { PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE, _("Invalid value for an argument") },
+ { PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS, _("Mutually exclusive arguments") },
+ { PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID, _("File not found or invalid") },
+ { PROJ_ERR_COORD_TRANSFM_INVALID_COORD, _("Invalid coordinate") },
+ { PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN, _("Point outside of projection domain") },
+ { PROJ_ERR_COORD_TRANSFM_NO_OPERATION, _("No operation matching criteria found for coordinate") },
+ { PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID, _("Coordinate to transform falls outside grid") },
+ { PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA, _("Coordinate to transform falls into a grid cell that evaluates to nodata") },
+ { PROJ_ERR_OTHER_API_MISUSE, _("API misuse") },
+ { PROJ_ERR_OTHER_NO_INVERSE_OP, _("No inverse operation") },
+ { PROJ_ERR_OTHER_NETWORK_ERROR, _("Network error when accessing a remote resource") },
+};
+
+const char PROJ_DLL * proj_context_errno_string(PJ_CONTEXT* ctx, int err)
+{
+ if( ctx == nullptr )
+ ctx = pj_get_default_ctx();
if (0==err)
return nullptr;
- /* System error codes are positive */
- if (err > 0) {
-#ifdef HAVE_STRERROR
- return strerror(err);
-#else
- /* Defend string boundary against exorbitantly large err values */
- /* which may occur on platforms with 64-bit ints */
- sprintf(note, "no system list, errno: %d\n",
- (err < max_error) ? err: max_error);
- return note;
-#endif
+ const char* str = nullptr;
+ for( const auto& num_str_pair: error_strings )
+ {
+ if( err == num_str_pair.num )
+ {
+ str = num_str_pair.str;
+ break;
+ }
}
- /* PROJ error codes are negative: -1 to -9999 */
- adjusted_err = err < -max_error ? max_error : -err - 1;
- if (adjusted_err < (sizeof(pj_err_list) / sizeof(char *)))
- return (char *)pj_err_list[adjusted_err];
+ if( str == nullptr && err > 0 && (err & PROJ_ERR_INVALID_OP) != 0 )
+ {
+ str = _("Unspecified error related to coordinate operation initialization");
+ }
+ if( str == nullptr && err > 0 && (err & PROJ_ERR_COORD_TRANSFM) != 0 )
+ {
+ str = _("Unspecified error related to coordinate transformation");
+ }
- sprintf(note, "invalid projection system error (%d)",
- (err > -max_error) ? err: -max_error);
- return note;
+ if (str) {
+ ctx->lastFullErrorMessage = str;
+ }
+ else
+ {
+ ctx->lastFullErrorMessage.resize(50);
+ snprintf(&ctx->lastFullErrorMessage[0], ctx->lastFullErrorMessage.size(),
+ _("Unknown error (code %d)"), err);
+ ctx->lastFullErrorMessage.resize(strlen(ctx->lastFullErrorMessage.data()));
+ }
+ return ctx->lastFullErrorMessage.c_str();
}
diff --git a/src/transformations/affine.cpp b/src/transformations/affine.cpp
index 43fd8642..e6a94f1e 100644
--- a/src/transformations/affine.cpp
+++ b/src/transformations/affine.cpp
@@ -154,7 +154,7 @@ static void computeReverseParameters(PJ* P)
const double det = a * A + b * B + c * C;
if( det == 0.0 || Q->forward.tscale == 0.0 ) {
if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) {
- proj_log_debug(P, "Affine: matrix non invertible");
+ proj_log_debug(P, "matrix non invertible");
}
P->inv4d = nullptr;
P->inv3d = nullptr;
@@ -176,7 +176,7 @@ static void computeReverseParameters(PJ* P)
PJ *TRANSFORMATION(affine,0 /* no need for ellipsoid */) {
struct pj_opaque_affine *Q = initQ();
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = (void *) Q;
P->fwd4d = forward_4d;
@@ -227,7 +227,7 @@ PJ *TRANSFORMATION(affine,0 /* no need for ellipsoid */) {
PJ *TRANSFORMATION(geogoffset,0 /* no need for ellipsoid */) {
struct pj_opaque_affine *Q = initQ();
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = (void *) Q;
P->fwd4d = forward_4d;
diff --git a/src/transformations/defmodel.cpp b/src/transformations/defmodel.cpp
index 3d0f2a58..0d9f6690 100644
--- a/src/transformations/defmodel.cpp
+++ b/src/transformations/defmodel.cpp
@@ -68,8 +68,7 @@ struct Grid : public GridPrototype {
if (!checkedHorizontal) {
const auto samplesPerPixel = realGrid->samplesPerPixel();
if (samplesPerPixel < 2) {
- pj_log(ctx, PJ_LOG_ERROR,
- "defmodel: grid %s has not enough samples",
+ pj_log(ctx, PJ_LOG_ERROR, "grid %s has not enough samples",
realGrid->name().c_str());
return false;
}
@@ -90,15 +89,14 @@ struct Grid : public GridPrototype {
}
}
if (foundDesc && (!foundDescX || !foundDescY)) {
- pj_log(ctx, PJ_LOG_ERROR,
- "defmodel: grid %s : Found band description, "
- "but not the ones expected",
+ pj_log(ctx, PJ_LOG_ERROR, "grid %s : Found band description, "
+ "but not the ones expected",
realGrid->name().c_str());
return false;
}
const auto unit = realGrid->unit(sampleX);
if (!unit.empty() && unit != expectedUnit) {
- pj_log(ctx, PJ_LOG_ERROR, "defmodel: grid %s : Only unit=%s "
+ pj_log(ctx, PJ_LOG_ERROR, "grid %s : Only unit=%s "
"currently handled for this mode",
realGrid->name().c_str(), expectedUnit.c_str());
return false;
@@ -130,8 +128,7 @@ struct Grid : public GridPrototype {
if (samplesPerPixel == 1) {
sampleZ = 0;
} else if (samplesPerPixel < 3) {
- pj_log(ctx, PJ_LOG_ERROR,
- "defmodel: grid %s has not enough samples",
+ pj_log(ctx, PJ_LOG_ERROR, "grid %s has not enough samples",
realGrid->name().c_str());
return false;
}
@@ -148,17 +145,15 @@ struct Grid : public GridPrototype {
}
}
if (foundDesc && !foundDescZ) {
- pj_log(ctx, PJ_LOG_ERROR,
- "defmodel: grid %s : Found band description, "
- "but not the ones expected",
+ pj_log(ctx, PJ_LOG_ERROR, "grid %s : Found band description, "
+ "but not the ones expected",
realGrid->name().c_str());
return false;
}
const auto unit = realGrid->unit(sampleZ);
if (!unit.empty() && unit != STR_METRE) {
- pj_log(ctx, PJ_LOG_ERROR,
- "defmodel: grid %s : Only unit=metre currently "
- "handled for this mode",
+ pj_log(ctx, PJ_LOG_ERROR, "grid %s : Only unit=metre currently "
+ "handled for this mode",
realGrid->name().c_str());
return false;
}
@@ -256,8 +251,7 @@ struct EvaluatorIface : public EvaluatorIfacePrototype<Grid, GridSet> {
std::unique_ptr<GridSet> open(const std::string &filename) {
auto realGridSet = NS_PROJ::GenericShiftGridSet::open(ctx, filename);
if (!realGridSet) {
- pj_log(ctx, PJ_LOG_ERROR, "defmodel: cannot open %s",
- filename.c_str());
+ pj_log(ctx, PJ_LOG_ERROR, "cannot open %s", filename.c_str());
return nullptr;
}
return std::unique_ptr<GridSet>(
@@ -390,7 +384,7 @@ PJ *TRANSFORMATION(defmodel, 1) {
// Pass a dummy ellipsoid definition that will be overridden just afterwards
auto cart = proj_create(P->ctx, "+proj=cart +a=1");
if (cart == nullptr)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
/* inherit ellipsoid definition from P to Q->cart */
pj_inherit_ellipsoid_def(P, cart);
@@ -402,14 +396,14 @@ PJ *TRANSFORMATION(defmodel, 1) {
const char *model = pj_param(P->ctx, P->params, "smodel").s;
if (!model) {
- proj_log_error(P, "defmodel: +model= should be specified.");
- return destructor(P, PJD_ERR_NO_ARGS);
+ proj_log_error(P, _("+model= should be specified."));
+ return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
auto file = NS_PROJ::FileManager::open_resource_file(P->ctx, model);
if (nullptr == file) {
- proj_log_error(P, "defmodel: Cannot open %s", model);
- return destructor(P, PJD_ERR_INVALID_ARG);
+ proj_log_error(P, _("Cannot open %s"), model);
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
file->seek(0, SEEK_END);
unsigned long long size = file->tell();
@@ -417,23 +411,23 @@ PJ *TRANSFORMATION(defmodel, 1) {
// that could be a denial of service risk. 10 MB should be sufficiently
// large for any valid use !
if (size > 10 * 1024 * 1024) {
- proj_log_error(P, "defmodel: File %s too large", model);
- return destructor(P, PJD_ERR_INVALID_ARG);
+ proj_log_error(P, _("File %s too large"), model);
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
file->seek(0);
std::string jsonStr;
jsonStr.resize(static_cast<size_t>(size));
if (file->read(&jsonStr[0], jsonStr.size()) != jsonStr.size()) {
- proj_log_error(P, "defmodel: Cannot read %s", model);
- return destructor(P, PJD_ERR_INVALID_ARG);
+ proj_log_error(P, _("Cannot read %s"), model);
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
try {
Q->evaluator.reset(new Evaluator<Grid, GridSet, EvaluatorIface>(
MasterFile::parse(jsonStr), Q->evaluatorIface, P->a, P->b));
} catch (const std::exception &e) {
- proj_log_error(P, "defmodel: invalid model: %s", e.what());
- return destructor(P, PJD_ERR_INVALID_ARG);
+ proj_log_error(P, _("invalid model: %s"), e.what());
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
P->fwd4d = forward_4d;
diff --git a/src/transformations/deformation.cpp b/src/transformations/deformation.cpp
index 8ce02bee..1a04d0f5 100644
--- a/src/transformations/deformation.cpp
+++ b/src/transformations/deformation.cpp
@@ -100,7 +100,7 @@ static bool get_grid_values(PJ* P,
}
const auto samplesPerPixel = grid->samplesPerPixel();
if( samplesPerPixel < 3 ) {
- proj_log_error(P, "deformation: grid has not enough samples");
+ proj_log_error(P, "grid has not enough samples");
return false;
}
int sampleE = 0;
@@ -119,7 +119,7 @@ static bool get_grid_values(PJ* P,
}
const auto unit = grid->unit(sampleE);
if( !unit.empty() && unit != "millimetres per year" ) {
- proj_log_error(P, "deformation: Only unit=millimetres per year currently handled");
+ proj_log_error(P, "Only unit=millimetres per year currently handled");
return false;
}
@@ -179,8 +179,8 @@ static PJ_XYZ get_grid_shift(PJ* P, const PJ_XYZ& cartesian) {
shift.lp = pj_hgrid_value(P, Q->hgrids, geodetic.lp);
shift.enu.u = pj_vgrid_value(P, Q->vgrids, geodetic.lp, 1.0);
- if (proj_errno(P) == PJD_ERR_GRID_AREA)
- proj_log_debug(P, "deformation: coordinate (%.3f, %.3f) outside deformation model",
+ if (proj_errno(P) == PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID)
+ proj_log_debug(P, "coordinate (%.3f, %.3f) outside deformation model",
proj_todeg(geodetic.lpz.lam), proj_todeg(geodetic.lpz.phi));
/* grid values are stored as mm/yr, we need m/yr */
@@ -261,7 +261,7 @@ static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
if (Q->dt == HUGE_VAL) {
out = proj_coord_error(); /* in the 3D case +t_obs must be specified */
- proj_log_debug(P, "deformation: +dt must be specified");
+ proj_log_debug(P, "+dt must be specified");
return out.xyz;
}
@@ -308,7 +308,7 @@ static PJ_LPZ reverse_3d(PJ_XYZ in, PJ *P) {
if (Q->dt == HUGE_VAL) {
out = proj_coord_error(); /* in the 3D case +t_obs must be specified */
- proj_log_debug(P, "deformation: +dt must be specified");
+ proj_log_debug(P, "+dt must be specified");
return out.lpz;
}
@@ -358,7 +358,7 @@ PJ *TRANSFORMATION(deformation,1) {
// Pass a dummy ellipsoid definition that will be overridden just afterwards
Q->cart = proj_create(P->ctx, "+proj=cart +a=1");
if (Q->cart == nullptr)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
/* inherit ellipsoid definition from P to Q->cart */
pj_inherit_ellipsoid_def (P, Q->cart);
@@ -369,8 +369,8 @@ PJ *TRANSFORMATION(deformation,1) {
/* Build gridlists. Both horizontal and vertical grids are mandatory. */
if ( !has_grids && (!has_xy_grids || !has_z_grids)) {
- proj_log_error(P, "deformation: Either +grids or (+xy_grids and +z_grids) should be specified.");
- return destructor(P, PJD_ERR_NO_ARGS );
+ proj_log_error(P, _("Either +grids or (+xy_grids and +z_grids) should be specified."));
+ return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG );
}
if( has_grids )
@@ -378,22 +378,22 @@ PJ *TRANSFORMATION(deformation,1) {
Q->grids = pj_generic_grid_init(P, "grids");
/* Was gridlist compiled properly? */
if ( proj_errno(P) ) {
- proj_log_error(P, "deformation: could not find required grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_log_error(P, _("could not find required grid(s).)"));
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
}
else
{
Q->hgrids = pj_hgrid_init(P, "xy_grids");
if (proj_errno(P)) {
- proj_log_error(P, "deformation: could not find requested xy_grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_log_error(P, _("could not find requested xy_grid(s)."));
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
Q->vgrids = pj_vgrid_init(P, "z_grids");
if (proj_errno(P)) {
- proj_log_error(P, "deformation: could not find requested z_grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_log_error(P, _("could not find requested z_grid(s)."));
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
}
@@ -403,8 +403,8 @@ PJ *TRANSFORMATION(deformation,1) {
}
if (pj_param_exists(P->params, "t_obs")) {
- proj_log_error(P, "deformation: +t_obs parameter is deprecated. Use +dt instead.");
- return destructor(P, PJD_ERR_MISSING_ARGS);
+ proj_log_error(P, _("+t_obs parameter is deprecated. Use +dt instead."));
+ return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
Q->t_epoch = HUGE_VAL;
@@ -413,13 +413,13 @@ PJ *TRANSFORMATION(deformation,1) {
}
if (Q->dt == HUGE_VAL && Q->t_epoch == HUGE_VAL) {
- proj_log_error(P, "deformation: either +dt or +t_epoch needs to be set.");
- return destructor(P, PJD_ERR_MISSING_ARGS);
+ proj_log_error(P, _("either +dt or +t_epoch needs to be set."));
+ return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
if (Q->dt != HUGE_VALL && Q->t_epoch != HUGE_VALL) {
- proj_log_error(P, "deformation: +dt or +t_epoch are mutually exclusive.");
- return destructor(P, PJD_ERR_MUTUALLY_EXCLUSIVE_ARGS);
+ proj_log_error(P, _("+dt or +t_epoch are mutually exclusive."));
+ return destructor(P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS);
}
P->fwd4d = forward_4d;
diff --git a/src/transformations/helmert.cpp b/src/transformations/helmert.cpp
index 99aa74a4..f9bb45e0 100644
--- a/src/transformations/helmert.cpp
+++ b/src/transformations/helmert.cpp
@@ -478,7 +478,7 @@ static PJ_COORD helmert_reverse_4d (PJ_COORD point, PJ *P) {
static PJ* init_helmert_six_parameters(PJ* P) {
struct pj_opaque_helmert *Q = static_cast<struct pj_opaque_helmert*>(calloc (1, sizeof (struct pj_opaque_helmert)));
if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = (void *) Q;
/* In most cases, we work on 3D cartesian coordinates */
@@ -522,8 +522,8 @@ static PJ* read_convention(PJ* P) {
if (!Q->no_rotation) {
const char* convention = pj_param (P->ctx, P->params, "sconvention").s;
if( !convention ) {
- proj_log_error (P, "helmert: missing 'convention' argument");
- return pj_default_destructor (P, PJD_ERR_MISSING_ARGS);
+ proj_log_error (P, _("helmert: missing 'convention' argument"));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
if( strcmp(convention, "position_vector") == 0 ) {
Q->is_position_vector = 1;
@@ -532,17 +532,17 @@ static PJ* read_convention(PJ* P) {
Q->is_position_vector = 0;
}
else {
- proj_log_error (P, "helmert: invalid value for 'convention' argument");
- return pj_default_destructor (P, PJD_ERR_INVALID_ARG);
+ proj_log_error (P, _("helmert: invalid value for 'convention' argument"));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
/* historically towgs84 in PROJ has always been using position_vector
* convention. Accepting coordinate_frame would be confusing. */
if (pj_param_exists (P->params, "towgs84")) {
if( !Q->is_position_vector ) {
- proj_log_error (P, "helmert: towgs84 should only be used with "
- "convention=position_vector");
- return pj_default_destructor (P, PJD_ERR_INVALID_ARG);
+ proj_log_error (P, _("helmert: towgs84 should only be used with "
+ "convention=position_vector"));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
}
}
@@ -578,9 +578,9 @@ PJ *TRANSFORMATION(helmert, 0) {
/* Detect obsolete transpose flag and error out if found */
if (pj_param (P->ctx, P->params, "ttranspose").i) {
- proj_log_error (P, "helmert: 'transpose' argument is no longer valid. "
- "Use convention=position_vector/coordinate_frame");
- return pj_default_destructor (P, PJD_ERR_INVALID_ARG);
+ proj_log_error (P, _("helmert: 'transpose' argument is no longer valid. "
+ "Use convention=position_vector/coordinate_frame"));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
/* Support the classic PROJ towgs84 parameter, but allow later overrides.*/
@@ -612,9 +612,15 @@ PJ *TRANSFORMATION(helmert, 0) {
if (pj_param (P->ctx, P->params, "ts").i) {
Q->scale_0 = pj_param (P->ctx, P->params, "ds").f;
if( Q->scale_0 <= -1.0e6 )
- return pj_default_destructor (P, PJD_ERR_INVALID_SCALE);
+ {
+ proj_log_error (P, _("helmert: invalid value for s."));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
if (pj_param (P->ctx, P->params, "ttheta").i && Q->scale_0 == 0.0)
- return pj_default_destructor (P, PJD_ERR_INVALID_SCALE);
+ {
+ proj_log_error (P, _("helmert: invalid value for s."));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
+ }
}
/* Translation rates */
diff --git a/src/transformations/hgridshift.cpp b/src/transformations/hgridshift.cpp
index b28eaf48..326bbb13 100644
--- a/src/transformations/hgridshift.cpp
+++ b/src/transformations/hgridshift.cpp
@@ -155,8 +155,8 @@ PJ *TRANSFORMATION(hgridshift,0) {
P->right = PJ_IO_UNITS_RADIANS;
if (0==pj_param(P->ctx, P->params, "tgrids").i) {
- proj_log_error(P, "hgridshift: +grids parameter missing.");
- return destructor (P, PJD_ERR_NO_ARGS);
+ proj_log_error(P, _("+grids parameter missing."));
+ return destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
/* TODO: Refactor into shared function that can be used */
@@ -194,8 +194,8 @@ PJ *TRANSFORMATION(hgridshift,0) {
Q->grids = pj_hgrid_init(P, "grids");
/* Was gridlist compiled properly? */
if ( proj_errno(P) ) {
- proj_log_error(P, "hgridshift: could not find required grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_log_error(P, _("could not find required grid(s)."));
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
gMutex.lock();
diff --git a/src/transformations/horner.cpp b/src/transformations/horner.cpp
index 2c049186..7c8ad192 100644
--- a/src/transformations/horner.cpp
+++ b/src/transformations/horner.cpp
@@ -115,7 +115,7 @@ struct horner {
} // anonymous namespace
typedef struct horner HORNER;
-static PJ_UV horner_func (const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position);
+static PJ_UV horner_func (PJ* P, const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position);
static HORNER *horner_alloc (size_t order, int complex_polynomia);
static void horner_free (HORNER *h);
@@ -181,7 +181,7 @@ static HORNER *horner_alloc (size_t order, int complex_polynomia) {
/**********************************************************************/
-static PJ_UV horner_func (const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position) {
+static PJ_UV horner_func (PJ* P, const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position) {
/***********************************************************************
A reimplementation of the classic Engsager/Poder 2D Horner polynomial
@@ -237,7 +237,6 @@ summing the tiny high order elements first.
case PJ_INV: /* inverse */
break;
default: /* invalid */
- errno = EINVAL;
return uv_error;
}
@@ -259,7 +258,7 @@ summing the tiny high order elements first.
}
if ((fabs(n) > range) || (fabs(e) > range)) {
- errno = EDOM;
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return uv_error;
}
@@ -297,12 +296,12 @@ summing the tiny high order elements first.
static PJ_COORD horner_forward_4d (PJ_COORD point, PJ *P) {
- point.uv = horner_func ((HORNER *) P->opaque, PJ_FWD, point.uv);
+ point.uv = horner_func (P, (HORNER *) P->opaque, PJ_FWD, point.uv);
return point;
}
static PJ_COORD horner_reverse_4d (PJ_COORD point, PJ *P) {
- point.uv = horner_func ((HORNER *) P->opaque, PJ_INV, point.uv);
+ point.uv = horner_func (P, (HORNER *) P->opaque, PJ_INV, point.uv);
return point;
}
@@ -310,7 +309,7 @@ static PJ_COORD horner_reverse_4d (PJ_COORD point, PJ *P) {
/**********************************************************************/
-static PJ_UV complex_horner (const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position) {
+static PJ_UV complex_horner (PJ *P, const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position) {
/***********************************************************************
A reimplementation of a classic Engsager/Poder Horner complex
@@ -337,7 +336,6 @@ polynomial evaluation engine.
case PJ_INV: /* inverse */
break;
default: /* invalid */
- errno = EINVAL;
return uv_error;
}
@@ -366,7 +364,7 @@ polynomial evaluation engine.
}
if ((fabs(n) > range) || (fabs(e) > range)) {
- errno = EDOM;
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return uv_error;
}
@@ -387,12 +385,12 @@ polynomial evaluation engine.
static PJ_COORD complex_horner_forward_4d (PJ_COORD point, PJ *P) {
- point.uv = complex_horner ((HORNER *) P->opaque, PJ_FWD, point.uv);
+ point.uv = complex_horner (P, (HORNER *) P->opaque, PJ_FWD, point.uv);
return point;
}
static PJ_COORD complex_horner_reverse_4d (PJ_COORD point, PJ *P) {
- point.uv = complex_horner ((HORNER *) P->opaque, PJ_INV, point.uv);
+ point.uv = complex_horner (P, (HORNER *) P->opaque, PJ_INV, point.uv);
return point;
}
@@ -414,7 +412,7 @@ static int parse_coefs (PJ *P, double *coefs, const char *param, int ncoefs) {
buf = static_cast<char*>(calloc (strlen (param) + 2, sizeof(char)));
if (nullptr==buf) {
- proj_log_error (P, "Horner: No memory left");
+ proj_log_error (P, "No memory left");
return 0;
}
@@ -430,7 +428,7 @@ static int parse_coefs (PJ *P, double *coefs, const char *param, int ncoefs) {
for (i = 0; i < ncoefs; i++) {
if (i > 0) {
if ( next == nullptr || ','!=*next) {
- proj_log_error (P, "Horner: Malformed polynomium set %s. need %d coefs", param, ncoefs);
+ proj_log_error (P, "Malformed polynomium set %s. need %d coefs", param, ncoefs);
return 0;
}
init = ++next;
@@ -460,12 +458,12 @@ PJ *PROJECTION(horner) {
degree = pj_param(P->ctx, P->params, "ideg").i;
if (degree < 0 || degree > 10000) {
/* What are reasonable minimum and maximums for degree? */
- proj_log_debug (P, "Horner: Degree is unreasonable: %d", degree);
- return horner_freeup (P, PJD_ERR_INVALID_ARG);
+ proj_log_error (P, _("Degree is unreasonable: %d"), degree);
+ return horner_freeup (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
} else {
- proj_log_debug (P, "Horner: Must specify polynomial degree, (+deg=n)");
- return horner_freeup (P, PJD_ERR_MISSING_ARGS);
+ proj_log_error (P, _("Must specify polynomial degree, (+deg=n)"));
+ return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
if (pj_param (P->ctx, P->params, "tfwd_c").i || pj_param (P->ctx, P->params, "tinv_c").i) /* complex polynomium? */
@@ -473,7 +471,7 @@ PJ *PROJECTION(horner) {
Q = horner_alloc (degree, complex_polynomia);
if (Q == nullptr)
- return horner_freeup (P, ENOMEM);
+ return horner_freeup (P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = Q;
if (complex_polynomia) {
@@ -483,9 +481,15 @@ PJ *PROJECTION(horner) {
n = 2*degree + 2;
if (0==parse_coefs (P, Q->fwd_c, "fwd_c", n))
- return horner_freeup (P, PJD_ERR_MISSING_ARGS);
+ {
+ proj_log_error (P, _("missing fwd_c"));
+ return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
if (0==parse_coefs (P, Q->inv_c, "inv_c", n))
- return horner_freeup (P, PJD_ERR_MISSING_ARGS);
+ {
+ proj_log_error (P, _("missing inv_c"));
+ return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
P->fwd4d = complex_horner_forward_4d;
P->inv4d = complex_horner_reverse_4d;
}
@@ -493,19 +497,37 @@ PJ *PROJECTION(horner) {
else {
n = horner_number_of_coefficients (degree);
if (0==parse_coefs (P, Q->fwd_u, "fwd_u", n))
- return horner_freeup (P, PJD_ERR_MISSING_ARGS);
+ {
+ proj_log_error (P, _("missing fwd_u"));
+ return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
if (0==parse_coefs (P, Q->fwd_v, "fwd_v", n))
- return horner_freeup (P, PJD_ERR_MISSING_ARGS);
+ {
+ proj_log_error (P, _("missing fwd_v"));
+ return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
if (0==parse_coefs (P, Q->inv_u, "inv_u", n))
- return horner_freeup (P, PJD_ERR_MISSING_ARGS);
+ {
+ proj_log_error (P, _("missing inv_u"));
+ return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
if (0==parse_coefs (P, Q->inv_v, "inv_v", n))
- return horner_freeup (P, PJD_ERR_MISSING_ARGS);
+ {
+ proj_log_error (P, _("missing inv_v"));
+ return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
}
if (0==parse_coefs (P, (double *)(Q->fwd_origin), "fwd_origin", 2))
- return horner_freeup (P, PJD_ERR_MISSING_ARGS);
+ {
+ proj_log_error (P, _("missing fwd_origin"));
+ return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
if (0==parse_coefs (P, (double *)(Q->inv_origin), "inv_origin", 2))
- return horner_freeup (P, PJD_ERR_MISSING_ARGS);
+ {
+ proj_log_error (P, _("missing inv_origin"));
+ return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
+ }
if (0==parse_coefs (P, &Q->range, "range", 1))
Q->range = 500000;
diff --git a/src/transformations/molodensky.cpp b/src/transformations/molodensky.cpp
index bf5960d2..70cf987b 100644
--- a/src/transformations/molodensky.cpp
+++ b/src/transformations/molodensky.cpp
@@ -245,7 +245,7 @@ static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
lpz = calc_standard_params(lpz, P);
}
if( lpz.lam == HUGE_VAL ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().xyz;
}
@@ -277,7 +277,7 @@ static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) {
lpz = calc_standard_params(point.lpz, P);
if( lpz.lam == HUGE_VAL ) {
- proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION);
+ proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
return proj_coord_error().lpz;
}
@@ -297,10 +297,9 @@ static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) {
PJ *TRANSFORMATION(molodensky,1) {
- int count_required_params = 0;
struct pj_opaque_molodensky *Q = static_cast<struct pj_opaque_molodensky*>(calloc(1, sizeof(struct pj_opaque_molodensky)));
if (nullptr==Q)
- return pj_default_destructor(P, ENOMEM);
+ return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
P->opaque = (void *) Q;
P->fwd4d = forward_4d;
@@ -314,39 +313,42 @@ PJ *TRANSFORMATION(molodensky,1) {
P->right = PJ_IO_UNITS_RADIANS;
/* read args */
- if (pj_param(P->ctx, P->params, "tdx").i) {
- count_required_params ++;
- Q->dx = pj_param(P->ctx, P->params, "ddx").f;
+ if (!pj_param(P->ctx, P->params, "tdx").i)
+ {
+ proj_log_error (P, _("missing dx"));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
+ Q->dx = pj_param(P->ctx, P->params, "ddx").f;
- if (pj_param(P->ctx, P->params, "tdy").i) {
- count_required_params ++;
- Q->dy = pj_param(P->ctx, P->params, "ddy").f;
+ if (!pj_param(P->ctx, P->params, "tdy").i)
+ {
+ proj_log_error (P, _("missing dy"));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
+ Q->dy = pj_param(P->ctx, P->params, "ddy").f;
- if (pj_param(P->ctx, P->params, "tdz").i) {
- count_required_params ++;
- Q->dz = pj_param(P->ctx, P->params, "ddz").f;
+ if (!pj_param(P->ctx, P->params, "tdz").i)
+ {
+ proj_log_error (P, _("missing dz"));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
+ Q->dz = pj_param(P->ctx, P->params, "ddz").f;
- if (pj_param(P->ctx, P->params, "tda").i) {
- count_required_params ++;
- Q->da = pj_param(P->ctx, P->params, "dda").f;
+ if (!pj_param(P->ctx, P->params, "tda").i)
+ {
+ proj_log_error (P, _("missing da"));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
+ Q->da = pj_param(P->ctx, P->params, "dda").f;
- if (pj_param(P->ctx, P->params, "tdf").i) {
- count_required_params ++;
- Q->df = pj_param(P->ctx, P->params, "ddf").f;
+ if (!pj_param(P->ctx, P->params, "tdf").i)
+ {
+ proj_log_error (P, _("missing df"));
+ return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
+ Q->df = pj_param(P->ctx, P->params, "ddf").f;
Q->abridged = pj_param(P->ctx, P->params, "tabridged").i;
- /* We want all parameters (except +abridged) to be set */
- if (count_required_params == 0)
- return pj_default_destructor(P, PJD_ERR_NO_ARGS);
-
- if (count_required_params != 5)
- return pj_default_destructor(P, PJD_ERR_MISSING_ARGS);
-
return P;
}
diff --git a/src/transformations/tinshift.cpp b/src/transformations/tinshift.cpp
index 96e0ea4f..51e063eb 100644
--- a/src/transformations/tinshift.cpp
+++ b/src/transformations/tinshift.cpp
@@ -86,14 +86,14 @@ PJ *TRANSFORMATION(tinshift, 1) {
const char *filename = pj_param(P->ctx, P->params, "sfile").s;
if (!filename) {
- proj_log_error(P, "tinshift: +file= should be specified.");
- return destructor(P, PJD_ERR_NO_ARGS);
+ proj_log_error(P, _("+file= should be specified."));
+ return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
auto file = NS_PROJ::FileManager::open_resource_file(P->ctx, filename);
if (nullptr == file) {
- proj_log_error(P, "tinshift: Cannot open %s", filename);
- return destructor(P, PJD_ERR_INVALID_ARG);
+ proj_log_error(P, _("Cannot open %s"), filename);
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
file->seek(0, SEEK_END);
unsigned long long size = file->tell();
@@ -101,15 +101,15 @@ PJ *TRANSFORMATION(tinshift, 1) {
// that could be a denial of service risk. 10 MB should be sufficiently
// large for any valid use !
if (size > 10 * 1024 * 1024) {
- proj_log_error(P, "tinshift: File %s too large", filename);
- return destructor(P, PJD_ERR_INVALID_ARG);
+ proj_log_error(P, _("File %s too large"), filename);
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
file->seek(0);
std::string jsonStr;
jsonStr.resize(static_cast<size_t>(size));
if (file->read(&jsonStr[0], jsonStr.size()) != jsonStr.size()) {
- proj_log_error(P, "tinshift: Cannot read %s", filename);
- return destructor(P, PJD_ERR_INVALID_ARG);
+ proj_log_error(P, _("Cannot read %s"), filename);
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
auto Q = new tinshiftData();
@@ -119,8 +119,8 @@ PJ *TRANSFORMATION(tinshift, 1) {
try {
Q->evaluator.reset(new Evaluator(TINShiftFile::parse(jsonStr)));
} catch (const std::exception &e) {
- proj_log_error(P, "tinshift: invalid model: %s", e.what());
- return destructor(P, PJD_ERR_INVALID_ARG);
+ proj_log_error(P, _("invalid model: %s"), e.what());
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
P->destructor = destructor;
diff --git a/src/transformations/vgridshift.cpp b/src/transformations/vgridshift.cpp
index 3d9f046a..7b234517 100644
--- a/src/transformations/vgridshift.cpp
+++ b/src/transformations/vgridshift.cpp
@@ -179,8 +179,8 @@ PJ *TRANSFORMATION(vgridshift,0) {
P->reassign_context = reassign_context;
if (!pj_param(P->ctx, P->params, "tgrids").i) {
- proj_log_error(P, "vgridshift: +grids parameter missing.");
- return destructor(P, PJD_ERR_NO_ARGS);
+ proj_log_error(P, _("+grids parameter missing."));
+ return destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
/* TODO: Refactor into shared function that can be used */
@@ -227,8 +227,8 @@ PJ *TRANSFORMATION(vgridshift,0) {
/* Was gridlist compiled properly? */
if ( proj_errno(P) ) {
- proj_log_error(P, "vgridshift: could not find required grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_log_error(P, _("could not find required grid(s)."));
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
gMutex.lock();
diff --git a/src/transformations/xyzgridshift.cpp b/src/transformations/xyzgridshift.cpp
index e37e874d..c75944ba 100644
--- a/src/transformations/xyzgridshift.cpp
+++ b/src/transformations/xyzgridshift.cpp
@@ -257,7 +257,7 @@ PJ *TRANSFORMATION(xyzgridshift,0) {
// Pass a dummy ellipsoid definition that will be overridden just afterwards
Q->cart = proj_create(P->ctx, "+proj=cart +a=1");
if (Q->cart == nullptr)
- return destructor(P, ENOMEM);
+ return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
/* inherit ellipsoid definition from P to Q->cart */
pj_inherit_ellipsoid_def (P, Q->cart);
@@ -272,14 +272,14 @@ PJ *TRANSFORMATION(xyzgridshift,0) {
// in RGF93
Q->grid_ref_is_input = false;
} else {
- proj_log_error(P, "xyzgridshift: unusupported value for grid_ref");
- return destructor (P, PJD_ERR_NO_ARGS);
+ proj_log_error(P, _("unusupported value for grid_ref"));
+ return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
}
}
if (0==pj_param(P->ctx, P->params, "tgrids").i) {
- proj_log_error(P, "xyzgridshift: +grids parameter missing.");
- return destructor (P, PJD_ERR_NO_ARGS);
+ proj_log_error(P, _("+grids parameter missing."));
+ return destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG);
}
/* multiplier for delta x,y,z */
@@ -294,8 +294,8 @@ PJ *TRANSFORMATION(xyzgridshift,0) {
Q->grids = pj_generic_grid_init(P, "grids");
/* Was gridlist compiled properly? */
if ( proj_errno(P) ) {
- proj_log_error(P, "xyzgridshift: could not find required grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ proj_log_error(P, _("could not find required grid(s)."));
+ return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
}
}
diff --git a/test/cli/testprojinfo b/test/cli/testprojinfo
index c31cfef0..ee1b27f5 100755
--- a/test/cli/testprojinfo
+++ b/test/cli/testprojinfo
@@ -213,6 +213,10 @@ fi
rm testprojinfo_out_remotedata.txt
unset PROJ_NETWORK
+echo 'Testing --accuracy 0.05 -s EPSG:4326 -t EPSG:4258' >> ${OUT}
+$EXE --accuracy 0.05 -s EPSG:4326 -t EPSG:4258 >>${OUT} 2>&1
+echo "" >>${OUT}
+
######################
# NZGD2000 -> ITRFxx #
######################
diff --git a/test/cli/testprojinfo_out.dist b/test/cli/testprojinfo_out.dist
index 8e8ef294..829c914c 100644
--- a/test/cli/testprojinfo_out.dist
+++ b/test/cli/testprojinfo_out.dist
@@ -1384,6 +1384,9 @@ DATUM["World Geodetic System 1984",
LENGTHUNIT["metre",1]],
ID["EPSG",6326]]
+Testing --accuracy 0.05 -s EPSG:4326 -t EPSG:4258
+Candidate operations found: 0
+
Testing -s NZGD2000 -t ITRF96 -o PROJ -q
+proj=pipeline
+step +proj=unitconvert +xy_in=deg +xy_out=rad
diff --git a/test/cli/testvarious b/test/cli/testvarious
index 82be4992..a121393c 100755
--- a/test/cli/testvarious
+++ b/test/cli/testvarious
@@ -1021,6 +1021,18 @@ $EXE --authority EPSG -E +proj=latlong +datum=WGS84 +no_defs +to +init=epsg:6342
-105 40
EOF
+echo "##############################################################" >> ${OUT}
+echo "Test effect of --accuracy" >> ${OUT}
+$EXE -E --accuracy 0.05 EPSG:4326 EPSG:4258 >> ${OUT} <<EOF
+49 2
+EOF
+
+echo "##############################################################" >> ${OUT}
+echo "Test effect of --no-ballpark" >> ${OUT}
+$EXE -E --no-ballpark EPSG:4267 EPSG:4258 >> ${OUT} <<EOF
+49 2
+EOF
+
# Done!
# do 'diff' with distribution results
diff --git a/test/cli/tv_out.dist b/test/cli/tv_out.dist
index fe1aa452..59129d99 100644
--- a/test/cli/tv_out.dist
+++ b/test/cli/tv_out.dist
@@ -492,3 +492,7 @@ The first result should use the 'WGS_1984_(ITRF08)_To_NAD_1983_2011' (ESRI:10836
and the second one a no-op
-105 40 500000.86 4427756.50 0.00
-105 40 500000.00 4427757.22 0.00
+##############################################################
+Test effect of --accuracy
+##############################################################
+Test effect of --no-ballpark
diff --git a/test/gie/4D-API_cs2cs-style.gie b/test/gie/4D-API_cs2cs-style.gie
index ade75907..b4ceb4f6 100644
--- a/test/gie/4D-API_cs2cs-style.gie
+++ b/test/gie/4D-API_cs2cs-style.gie
@@ -495,7 +495,7 @@ expect 0 0 1
roundtrip 1
operation +proj=longlat +a=1 +b=1 +vto_meter=1/0
-expect failure errno unit_factor_less_than_0
+expect failure errno invalid_op_illegal_arg_value
operation +proj=longlat +a=1 +b=1 +vto_meter=1000 +geoc
accept 0 0 1000
@@ -522,7 +522,7 @@ roundtrip 1
# in the creation of a PJ object.
-------------------------------------------------------------------------------
operation this is a bogus CRS meant to trigger a generic error in proj_create()
-expect failure errno generic error
+expect failure errno other
-------------------------------------------------------------------------------
# Test proj=set
diff --git a/test/gie/adams_hemi.gie b/test/gie/adams_hemi.gie
index b31c3581..9d23b939 100644
--- a/test/gie/adams_hemi.gie
+++ b/test/gie/adams_hemi.gie
@@ -9,31 +9,31 @@ operation +proj=adams_hemi +R=6370997
tolerance 1 mm
------------------------------------------------------------
accept -179.0512914938 -90.1445918836
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -169.5842217825 -89.1738195765
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.8126151474 -88.9303357409
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.8486678837 -88.7598088570
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.3978823413 -88.5424937255
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.4584139907 -88.3017941113
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.6382190434 -88.1579367749
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.1571755829 -87.4911657719
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.4449870732 -87.1707570236
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.9433443609 -87.0825895518
expect -2032451.307 -14670658.595
@@ -90,61 +90,61 @@ accept 80.5231675121 -77.0660588617
expect 3923735.969 -12068158.388
accept 90.0066580543 -76.6161050638
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.5350349572 -76.3746207928
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.7544963160 -76.2761103137
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.0734970456 -75.8866598636
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.5824490023 -75.8761529489
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.8041765005 -75.3058724059
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.3183273801 -74.4580538960
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.0892519416 -73.7178034782
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.3366715442 -72.7342346131
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.6302993811 -72.6551561090
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.1498353863 -79.8617775679
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.6126454375 -78.9973036997
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.5376706591 -78.3099466224
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.7206409942 -77.9762123582
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.5308439004 -77.1568269429
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.5655598613 -76.3111768828
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.7726048543 -75.3290637518
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.6962230001 -74.3833782562
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.6722260401 -73.9288741111
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.0043085338 -73.3335214021
expect -4844180.674 -11775270.656
@@ -201,61 +201,61 @@ accept 80.1994904845 -64.1621304539
expect 5574892.739 -10059995.184
accept 90.4107676644 -63.6583206132
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.7435829266 -62.9292855324
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.6722999581 -62.8950940661
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.8104324458 -62.4946649855
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.0315355771 -61.8657176607
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.7842333846 -61.7278379595
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.5202047210 -61.0467237730
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.7596965102 -60.2885251988
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.1658638654 -60.0997063537
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.2502374139 -59.7318603713
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.9256771826 -69.2161805164
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.2003519382 -68.5724302106
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.5787597594 -67.9857475830
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.8429607974 -67.2794853256
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.5406690392 -67.1131557084
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.7848559822 -66.2388824806
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.1305416029 -65.8739932573
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.5840686978 -65.1388713461
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.0639263051 -65.0873010186
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.1823111373 -64.6956687932
expect -6023804.986 -10592902.952
@@ -312,61 +312,61 @@ accept 80.4614530617 -58.9769415123
expect 6157836.688 -9386832.221
accept 90.1145386062 -58.4070158299
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.7207579758 -58.0174867644
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.5210303471 -57.2190055708
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.6967482820 -56.4709263993
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.0815389060 -55.4932687347
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.1058922487 -54.7233777967
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.7972249220 -54.0392541094
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.4001197642 -53.4894321537
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.7522870277 -52.8073510172
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.9552557119 -51.9628267761
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.8457680307 -59.7627027888
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.9866964666 -59.3961476674
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.7470584020 -58.4027994525
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.6934351691 -57.7899236032
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.8251656458 -57.1675573730
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.7548669173 -56.7627741202
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.8194582121 -56.1851853006
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.9910713909 -55.2204505294
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.5100607596 -54.5311736441
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.8644038896 -53.9245170355
expect -7342928.224 -9343992.584
@@ -423,61 +423,61 @@ accept 80.9191136756 -48.5703968561
expect 7251379.374 -8116860.291
accept 90.3784337844 -48.2622816670
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.0644440138 -47.5531771971
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.4395693884 -47.1020565364
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.8787420308 -46.5648775386
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.8192016133 -45.7254461234
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.6056889736 -45.1737240013
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.1175388463 -44.6690953878
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.6179845473 -44.6641530409
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.3909883495 -43.8609446159
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.3536945715 -43.4346766235
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.6728016921 -49.7927741248
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.8899091213 -49.5948095572
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.7193834386 -48.9692766397
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.3161804706 -48.3900540417
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.3059860307 -47.5443099911
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.2607005772 -47.3426945690
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.0464442962 -46.3637609990
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.3006137776 -46.2470536112
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.1086587357 -45.3788313212
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.6418391342 -44.9558227898
expect -8329227.919 -8319528.025
@@ -534,61 +534,61 @@ accept 80.6995146991 -37.6252342058
expect 8295075.544 -6755019.382
accept 90.0888221682 -36.9847575823
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.7780137471 -36.6711774624
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.5677826175 -36.0072187827
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.4571639125 -35.5987514678
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.4994689409 -35.0078880309
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.4973246226 -34.7603698284
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.2185553402 -33.7827115000
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.0049830768 -33.6576606654
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.1113538332 -32.7218296414
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.0413323536 -32.6458132046
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.7614880705 -39.3083971924
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.2195161001 -38.8120777840
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.8162149104 -38.0579151517
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.1975646236 -38.0219346503
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.4875552064 -37.4333640809
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.3174577073 -37.3101077795
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.3659376731 -37.0018039210
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.4951561921 -36.7266176768
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.0146344791 -36.1766584469
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.4535006021 -35.4158065520
expect -9377248.574 -7225961.706
@@ -645,61 +645,61 @@ accept 80.7902728211 -26.9566814760
expect 9340735.895 -5359874.908
accept 90.7774660604 -26.3781900961
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.9677349413 -25.6947295847
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.4045415377 -24.8858012124
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.9106581244 -24.6760450197
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.1070741983 -24.3713625355
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.5983717849 -23.4696540641
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.9695044815 -23.2859623288
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.2904693231 -22.4177486612
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.9786886104 -22.3889418243
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.4135817841 -21.4141834167
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.1529257481 -29.4728615966
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.2570543721 -28.7440325834
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.4124846191 -27.8743740104
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.9984258738 -27.2290510016
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.4767743694 -27.0546870326
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.5641039139 -26.6837015309
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.3705764793 -25.8611684419
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.5378587803 -25.4624789903
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.3734787689 -24.9304945684
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.3465946697 -24.1700440971
expect -10704395.428 -5847361.236
@@ -756,61 +756,61 @@ accept 80.8671865869 -16.2424776439
expect 10422654.067 -3712280.432
accept 90.2461291404 -15.5645924718
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.9711143384 -14.9057684714
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.5061775944 -13.9999376124
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.6646869394 -13.8762395278
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.4786384854 -13.6641917166
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.4992250881 -12.7990197875
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.0366235813 -12.4696803942
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.5278707110 -12.2720940753
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.3484529579 -12.2138862792
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.2280212650 -11.3213090122
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.9887698836 -19.8434403334
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.6164294546 -19.0221825561
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.5308654294 -18.1879041212
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.8050966059 -17.4019446265
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.1612469827 -16.6625595025
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.4619391187 -16.4594418864
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.8383311004 -16.1675065524
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.3335459376 -15.6868347454
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.7976668059 -15.6854972046
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.7267294244 -14.8632370454
expect -12053401.390 -4568307.176
@@ -867,61 +867,61 @@ accept 80.6138637845 -4.9732504580
expect 11379290.973 -1327935.989
accept 90.6032354861 -4.1370495786
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.1644532030 -3.7428216159
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.6488410247 -3.6483998900
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.4200133401 -3.5679026480
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.4611248753 -2.8417810196
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.5117693002 -2.1994039429
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.8812291858 -1.4289441365
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.7607399916 -1.4071810906
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.8773135555 -1.0552787291
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.0261501820 -0.4477561568
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.6651296552 -9.8752237332
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.8040567331 -9.5090347854
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.2369236977 -8.7812050100
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.4668694945 -8.7411685014
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.4921425087 -8.4838585832
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.0489643890 -8.3165131291
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.2267713387 -8.2419396541
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.8259504273 -7.2718622518
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.9614377002 -7.1037710855
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.8090238071 -6.4232987368
expect -13640751.484 -2974873.944
@@ -978,61 +978,61 @@ accept 80.6476764259 3.6310845229
expect 11462634.636 984489.812
accept 90.5849650699 4.1063917523
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.6238525596 4.1230600723
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.6594697851 5.0495381196
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.0930076215 5.2661796574
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.5000547754 5.3939702157
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.4751452989 5.8876297478
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.7452375246 6.3021488806
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.1359677924 6.9173640629
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.0302177328 7.6378853558
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.8170157951 8.3472735471
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.1943150364 0.7596010223
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.6391212860 1.0795717122
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.2152700578 1.5596096157
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.3566080854 2.3935395760
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.6411690265 2.8792042895
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.0884027419 3.3736512451
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.6412398776 3.5609941446
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.8410023851 4.3613065976
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.5554211656 5.1657533479
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.3773565828 5.9157121060
expect -13652078.995 2749284.275
@@ -1089,61 +1089,61 @@ accept 80.2140199400 13.5624272700
expect 10571534.837 3165721.762
accept 90.4576635954 13.9718076506
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.6449925924 14.8800104945
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.2075367326 15.0521133300
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.4913041985 15.0889224537
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.1088876084 15.6744717063
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.6091054692 15.9789322360
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.8827038395 16.1118935785
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.8584892232 16.9655055987
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.7081605352 17.1424915281
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.4875952646 17.7286920734
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.2192819040 10.8569245931
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.0657035958 11.2213297367
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.1659774291 11.5431930234
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.9545173238 12.5029285949
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.3117413904 12.6753804804
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.3194639426 13.3306225335
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.8562785026 13.8682936931
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.1061461002 14.0130001331
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.6079217864 14.7921669533
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.3223757109 14.9467935082
expect -11977848.908 4521084.682
@@ -1200,61 +1200,61 @@ accept 80.7948402736 23.3693033859
expect 9696270.644 4844753.993
accept 90.9423588456 23.9305098127
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.5427479545 24.6568371578
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.6483359564 25.2988879331
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.6254581560 25.7897168715
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.6198305384 26.5579713234
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.5223099495 27.4665102983
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.1770507578 27.9150551786
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.8757513817 28.5236724280
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.8877826460 29.0924417375
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.6425321330 29.5764205068
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.9444701529 20.8756448666
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.6232517758 21.6942653703
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.5830169036 22.4130383475
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.4619550763 22.6339632787
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.7058201004 23.2553831478
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.7766004320 23.8189681109
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.3228272091 24.7032636217
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.5551540716 24.7745254301
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.9135718461 25.5385802763
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.1841069049 26.2083957447
expect -10431143.612 6091127.422
@@ -1311,61 +1311,61 @@ accept 80.9991967787 34.8943272959
expect 8589604.803 6432883.803
accept 90.5925677009 35.3510670805
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.0148954666 36.2711932097
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.4283207815 36.6017613088
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.6777960225 37.3666811945
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.2891249681 37.8530786142
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.8212942434 38.5395736955
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.7430926180 38.7113483726
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.9509577553 39.6821505727
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.3061167023 40.4192356875
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.9886935244 41.3233715669
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.3343647471 30.6447124879
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.9318029695 30.8088173910
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.9351347433 31.4030041187
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.1603401895 31.7370508292
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.8411358400 31.9883214220
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.6487283950 32.2540792459
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.3377596938 32.5474903700
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.4052121056 32.6499740509
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.9645719240 33.2634048792
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.3430796801 33.3255757200
expect -9605176.890 6972552.471
@@ -1422,61 +1422,61 @@ accept 80.6903986951 43.0741360152
expect 7768806.954 7429525.503
accept 90.6634321726 43.5837411887
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.8304968356 44.3478015738
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.2734219112 44.4218804789
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.7981801963 44.9674252843
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.6206535666 45.4286113875
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.3730391283 46.0714239353
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.9426457881 46.5091534230
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.6115790352 47.4101077572
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.3277640796 48.0638373590
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.8477425796 48.8442357472
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.2598149320 40.1705017627
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.6056153255 40.1727881259
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.3396275493 41.0355246248
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.3430287138 41.0573932198
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.3972998819 41.0866734660
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.2347342883 41.3220478306
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.9384666450 41.9087201484
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.1765297705 42.5137977627
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.7425660165 43.1240737964
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.3631524858 43.6013437171
expect -8456598.089 8145902.918
@@ -1533,61 +1533,61 @@ accept 80.1015409967 53.6234824949
expect 6684866.077 8688618.853
accept 90.9390534980 54.4032676618
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.2275975816 54.6540829630
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.2799875321 55.5050863665
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.5746083149 55.8365526658
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.5493283922 55.8603600954
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.1036826064 56.0703761781
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.2683487285 56.7530037053
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.2871251380 57.2145257183
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.1375706366 57.2304506879
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.2551410057 57.5449642224
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.4163113130 50.3425538005
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.9733110986 50.4458299488
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.7576358296 50.9581504113
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.3417213155 51.6042289093
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.0195163151 51.8452436385
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.9037457234 52.0925641350
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.8773360504 52.9710431510
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.3390467545 53.2106639106
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.5444768748 53.6966176349
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.1508332331 54.2933793206
expect -7253089.374 9338825.070
@@ -1644,61 +1644,61 @@ accept 80.2667195466 63.4462194528
expect 5659364.075 9965995.331
accept 90.1958404963 64.0831209279
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.5787966151 64.7750993019
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.5253826702 65.0311314831
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.1812962078 65.3475412110
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.6287227669 65.6970336106
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.0181654960 66.0623612621
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.7856466511 66.3591210923
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.9724593003 66.6162174492
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.0946608724 66.6386212957
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.4742461481 67.2758505348
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.3409209668 60.7971917835
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.8189576120 61.1637771517
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.5389911401 61.2887975834
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.8226015684 61.6437737052
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.2361655032 62.2457658007
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.4677006422 63.0421916813
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.2765495979 63.0575080285
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.6674914716 63.2917945558
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.7670971881 63.3834861911
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.8162159435 64.1409584458
expect -6128005.443 10556995.120
@@ -1755,61 +1755,61 @@ accept 80.1791569395 72.7183017021
expect 4529430.842 11315638.668
accept 90.8791736411 72.9059905421
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.0478827489 73.6259975169
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.4087249466 74.2803681973
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.3401612468 74.8365621394
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.1826062634 75.5694271641
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.9483568214 75.9187393924
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.4892277622 76.6692051191
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.6382868956 77.4573927636
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.7717121538 77.8061856572
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.3573673565 78.0660690876
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.6675977383 70.0957203052
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.7405185971 70.8584256587
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.5167125707 71.8322204655
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.0042143904 72.1158106531
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.0329602286 72.2542294354
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.7801336775 73.2077231869
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.3119539671 73.8550759705
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.4758176356 74.3266149256
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.6607202273 75.0169362990
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.4753530769 75.9617981968
expect -4457168.999 12206697.987
@@ -1866,61 +1866,61 @@ accept 80.9787521274 84.9230149078
expect 2463890.570 13818835.453
accept 90.9143604315 85.3871192550
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.4734880731 86.0042464514
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.9680822795 86.3859461851
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.8622100732 87.2340544489
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.2558456040 87.3620502284
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.2081210889 87.9608446770
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.4963196965 88.3752399281
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.1141812758 89.1315428504
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.8037412086 89.6150717843
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.1602592915 90.5979873573
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -179.4988010038 80.2382292021
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.7323895837 80.2920174872
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.9503807693 80.8196927273
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.0925061081 81.4663152862
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.5716369758 82.4286410234
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.8787197288 82.8456226975
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.4793423714 83.5516191256
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.2664761985 84.4756117750
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.3415573570 85.3222226381
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.5621686999 86.1477384313
expect -2328002.776 14359252.241
@@ -1950,171 +1950,171 @@ accept -9.7223365758 89.3832521756
expect -112026.906 15387878.248
accept 0.6641448256 90.0106423220
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 10.1860723801 90.3688642972
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 20.9490167192 90.7173958262
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 30.5649867370 90.9925163187
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 40.4458702150 91.4734308311
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 50.5856921606 91.9158720484
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 60.1363202035 92.2767051552
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 70.0710227099 93.2758942081
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 80.2222434482 93.6733349750
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 90.0483434140 94.4740404396
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 100.6823676393 95.0641687155
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 110.6403276588 95.6156935259
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 120.2581625576 96.0431766104
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 130.4609264126 96.4854267472
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 140.6441294534 96.7262713213
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 150.3252472008 97.1420609214
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 160.7668616164 97.4790143988
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 170.7465062128 97.6550567817
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.7542323137 98.2872938097
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -179.4933532172 89.0462961156
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.8505428520 89.8903183246
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.6997771019 89.9583403191
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.2613507188 90.8395350848
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -139.1842997548 91.1573946186
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -129.6568991775 91.9446769249
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -119.3115606256 92.6597043587
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -109.6274703609 93.6500222571
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -99.3955363318 93.6790731121
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -89.4902277654 93.7101118679
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -79.2055095189 93.8973426839
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -69.2694919752 94.5715889948
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -59.2096313695 94.8767204910
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -49.3346426547 95.7619370286
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -39.2940192313 95.9177686800
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -29.1265263906 96.4025342806
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -19.8149677195 96.5067828223
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -9.2806942519 96.8402148749
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 0.9337491530 97.5569468760
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 10.2800898790 97.9514714385
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 20.9290209494 98.9284832763
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 30.3939457169 99.5719752144
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 40.6958590705 100.0328567981
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 50.1473239826 100.6776574030
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 60.6429472168 100.7044060498
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 70.8017862719 101.2635238404
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 80.3144473172 101.9663622692
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 90.7459660200 102.3134423218
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 100.1161002435 103.1947683448
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 110.1928396624 104.0579352787
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 120.2928981698 104.8100792607
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 130.4257286255 105.4176918707
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 140.8110132830 105.4248870814
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 150.8802025406 106.2350153626
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 160.3540927190 106.6211948814
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 170.1006211763 106.7429949781
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.9080349563 107.0582862622
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
</gie-strict>
diff --git a/test/gie/adams_ws1.gie b/test/gie/adams_ws1.gie
index fa9da8c6..3593fe12 100644
--- a/test/gie/adams_ws1.gie
+++ b/test/gie/adams_ws1.gie
@@ -10,10 +10,10 @@ operation +proj=adams_ws1 +R=6370997
tolerance 1 mm
------------------------------------------------------------
accept -179.5170670673 -90.3642618405
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -169.6193301609 -90.0089826784
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -159.5146913398 -89.9552061084
expect -350717.162 -11748881.092
@@ -1891,10 +1891,10 @@ accept 160.6971562328 89.4208328233
expect 1268680.941 11594325.039
accept 170.3991577317 90.3170479552
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.5908903286 90.7796418583
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -179.6672737749 80.9670130376
expect -5579975.368 11792148.700
@@ -1957,165 +1957,165 @@ accept 10.1878191712 89.8306694493
expect 61235.456 11123325.873
accept 20.3202271141 90.0891258483
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 30.4076531874 90.1655009485
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 40.4697816170 90.3073014721
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 50.0046280736 90.9081150272
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 60.2882853272 91.3234973005
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 70.1983790734 92.0487871293
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 80.2016298087 92.7883781050
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 90.1380649347 93.4193661081
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 100.9727810821 94.0036131508
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 110.2124518442 94.5588879261
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 120.9027437830 95.4070516655
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 130.4370128971 95.6659394457
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 140.5025123065 95.8655824422
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 150.4051276696 96.5597126498
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 160.6794810904 97.5509003421
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 170.0804614782 97.7962770171
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.7350988655 97.9297507566
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -179.6509243766 89.9594690425
expect -339016.824 11811270.987
accept -169.1462360158 90.2259103522
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -159.1237371257 91.0966186475
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -149.3864893992 92.0018593591
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -139.8836795793 92.7310580140
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -129.2447556879 93.0874941458
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -119.5691328385 94.0016439421
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -109.5375733271 94.6275672079
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -99.6767551820 94.9362396518
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -89.0765267674 95.5300020136
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -79.7404375474 95.8979843707
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -69.2186423862 96.1670313079
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -59.3116550382 97.0176191327
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -49.2194140278 97.9598960425
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -39.4423952109 97.9675789614
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -29.2846350035 98.7361321642
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -19.5599253582 98.8248133304
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -9.7751102831 99.2905137713
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 0.4459912613 99.9348079001
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 10.2556901904 100.0679369696
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 20.2129499464 100.4339484435
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 30.5120716507 100.8173987820
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 40.9186217475 101.3362169174
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 50.4499309674 101.7519777648
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 60.9618169609 101.9681616248
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 70.5136938969 102.7263743079
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 80.1112365376 103.4930742925
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 90.0656487088 104.2687818917
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 100.4737302350 105.1672304167
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 110.0826941374 105.9578331609
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 120.5038465053 106.4653112353
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 130.1946022249 107.2836000330
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 140.1612060920 108.2561565502
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 150.8381796528 108.3785139275
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 160.5344804952 109.2381208530
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 170.6816130052 110.0255190870
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.9197969760 110.0988929845
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
</gie-strict>
diff --git a/test/gie/adams_ws2.gie b/test/gie/adams_ws2.gie
index c2ff193f..06df3696 100644
--- a/test/gie/adams_ws2.gie
+++ b/test/gie/adams_ws2.gie
@@ -11,7 +11,7 @@ operation +proj=adams_ws2 +R=6370997
tolerance 1 mm
------------------------------------------------------------
accept -179.7092450238 -90.0290393775
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -169.9316998581 -89.6983443874
expect -2757243.603 -13694037.516
@@ -1886,16 +1886,16 @@ accept 140.8963110473 89.7228156694
expect 2304894.417 13439579.058
accept 150.4318097094 90.4391164019
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 160.7127363327 90.6605815936
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 170.7492804294 90.8176634524
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.4583398460 90.9109976584
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -179.7591323884 80.4424115493
expect -6903393.952 9786682.886
@@ -1964,160 +1964,160 @@ accept 30.8762585995 89.9894216623
expect 237234.260 14954822.615
accept 40.2272544657 90.2854737833
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 50.3342208278 90.6201781373
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 60.0620171885 91.1323497706
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 70.7871678571 91.2021231110
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 80.7237355733 91.8207335323
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 90.5359055804 91.8495346522
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 100.2370378259 92.3201685216
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 110.7018248262 92.7082390660
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 120.4791498907 92.8320642395
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 130.7292413039 93.7863129954
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 140.1002623482 94.2304861566
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 150.7401582820 94.4002034978
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 160.9690930362 95.0432445572
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 170.5238000008 95.9332496636
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.5593997844 96.9295538910
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -179.2788799656 89.7588830795
expect -2720982.745 13966946.704
accept -169.2920835775 90.4241930348
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -159.9895526197 91.4107597532
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -149.2463523987 92.1662912669
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -139.5441662785 93.0270602663
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -129.2489121030 93.8575161591
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -119.2218964968 94.3245277139
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -109.6391371233 94.8605218216
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -99.7022656135 95.4188372117
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -89.9236110047 96.1459344102
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -79.3053114467 96.4284727639
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -69.9403123372 97.1204524330
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -59.8919954903 98.0976036625
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -49.4360361183 99.0132534146
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -39.1296215520 99.7082663882
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -29.7429317093 100.3804719805
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -19.9518617483 100.7090523427
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -9.8094666546 100.7731576636
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 0.6789399156 101.1890038152
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 10.3002789517 101.6303167682
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 20.3122591998 101.9746447499
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 30.8403181671 102.1868812356
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 40.5913833272 102.9224326125
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 50.4094599185 103.4226263618
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 60.2807542048 103.6337815648
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 70.1179652096 103.9375646519
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 80.0709906538 104.9002368302
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 90.4905546467 104.9051484801
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 100.8677613905 105.1778053719
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 110.7875504667 106.1651152124
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 120.3531685812 106.2777498204
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 130.5786379336 106.8735960149
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 140.2390732913 107.1035922681
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 150.2230766093 107.9665006223
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 160.0106199737 108.8731447468
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 170.2666681817 109.4591245928
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.2003062924 109.9750225424
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
-------------------------------------------------------------------------------
# Test inverse
@@ -2171,6 +2171,6 @@ roundtrip 1
direction inverse
accept 0.000005801264 16722285.492330472916
-expect failure errno non_convergent
+expect failure errno coord_transfm_outside_projection_domain
</gie-strict>
diff --git a/test/gie/builtins.gie b/test/gie/builtins.gie
index 813b67bb..b3c84f74 100644
--- a/test/gie/builtins.gie
+++ b/test/gie/builtins.gie
@@ -79,18 +79,18 @@ accept -200 -100
expect -0.001790493 -0.000895247
operation +proj=aea +ellps=GRS80 +lat_1=900
-expect failure errno lat_larger_than_90
+expect failure errno invalid_op_illegal_arg_value
operation +proj=aea +ellps=GRS80 +lat_2=900
-expect failure errno lat_larger_than_90
+expect failure errno invalid_op_illegal_arg_value
operation +proj=aea +R=6400000 +lat_1=1 +lat_2=-1
-expect failure errno conic_lat_equal
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=aea +a=9999999 +b=.9 +lat_2=1
-------------------------------------------------------------------------
-expect failure errno invalid_eccentricity
+expect failure errno invalid_op_illegal_arg_value
===============================================================================
# Azimuthal Equidistant
@@ -126,7 +126,7 @@ roundtrip 100
# point opposite projection center is undefined
accept 180 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
# Test equatorial aspect of the ellipsoidal azimuthal equidistant. Test data from
@@ -269,11 +269,11 @@ roundtrip 100
#point opposite of projection center is undefined
accept 0 -90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
direction inverse
accept 0 5
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
accept 0 3.14159265359
expect 180 -90
@@ -302,7 +302,7 @@ roundtrip 100
#point opposite of projection center is undefined
accept 0 90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
@@ -385,7 +385,7 @@ expect 0 -1.3863
accept 0 90
expect 0 0
accept 0 -90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
@@ -399,7 +399,7 @@ expect 0 1.3863
accept 0 -90
expect 0 0
accept 0 90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
# Test oblique aspect
@@ -412,7 +412,7 @@ expect 0 0
accept 0 0
expect -0.7336 -0.5187
accept -45 -45
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
# Test that coordinates on the opposing hemisphere are projected when using
@@ -446,7 +446,7 @@ expect 0.3821 0.4216
operation +proj=airy +R=1 +no_cut
-------------------------------------------------------------------------------
accept -180 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
===============================================================================
# Aitoff
@@ -1351,16 +1351,16 @@ operation +proj=eqdc +a=9999999 +b=.9 +lat_2=1
expect failure
operation +proj=eqdc +R=6400000 +lat_1=1 +lat_2=-1
-expect failure errno conic_lat_equal
+expect failure errno invalid_op_illegal_arg_value
operation +proj=eqdc +R=6400000 +lat_1=91
-expect failure errno lat_larger_than_90
+expect failure errno invalid_op_illegal_arg_value
operation +proj=eqdc +R=6400000 +lat_2=91
-expect failure errno lat_larger_than_90
+expect failure errno invalid_op_illegal_arg_value
operation +proj=eqdc +R=1 +lat_1=1e-9
-expect failure errno conic_lat_equal
+expect failure errno invalid_op_illegal_arg_value
===============================================================================
# Euler
@@ -1679,12 +1679,12 @@ expect -0.001790493 -0.000895247
-------------------------------------------------------------------------------
operation +proj=geos +R=1 +h=0
-------------------------------------------------------------------------------
-expect failure errno invalid_h
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=geos +R=1 +h=1e11
-------------------------------------------------------------------------------
-expect failure errno invalid_h
+expect failure errno invalid_op_illegal_arg_value
@@ -1780,7 +1780,7 @@ accept 80 80
expect 5.6713 32.6596
roundtrip 100
accept 0 90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
# test that extreme northings are mapped to the sphere
direction inverse
@@ -1801,9 +1801,9 @@ accept 45 45
expect 0.7071 -0.7071
roundtrip 100
accept 0 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
accept 90 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
# Test the southern polar aspect of the gnonomic projection
@@ -1818,9 +1818,9 @@ accept 45 -45
expect 0.7071 0.7071
roundtrip 100
accept 0 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
accept 90 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
# Test the oblique aspect of the gnonomic projection
@@ -1838,7 +1838,7 @@ accept 0 90
expect 0 1
roundtrip 100
accept 0 -45
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
===============================================================================
# Goode Homolosine
@@ -1918,7 +1918,7 @@ expect 3450764.261536101 -175619.041820732
# For some reason, does not fail on MacOSX
#accept 60 -45
-#expect failure errno tolerance_condition
+#expect failure errno coord_transfm_outside_projection_domain
direction inverse
accept -1800000.000000000 2600000.000000000
@@ -1988,7 +1988,7 @@ expect -0.001790493 -0.000895247
operation +proj=hammer +a=6400000 +W=1
-------------------------------------------------------------------------------
accept -180 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
===============================================================================
# Hatano Asymmetrical Equal Area
@@ -2502,7 +2502,7 @@ expect 24.833333333333 59.757598563058
-------------------------------------------------------------------------------
operation +proj=krovak +lat_0=-90
-------------------------------------------------------------------------------
-expect failure errno invalid_arg
+expect failure errno invalid_op_illegal_arg_value
===============================================================================
# Laborde
@@ -2536,7 +2536,7 @@ expect 0.498202283 1.999095641
-------------------------------------------------------------------------------
operation +proj=labrd +ellps=GRS80 +lat_0=0
accept 0 0
-expect failure errno lat_0_is_zero
+expect failure errno invalid_op_illegal_arg_value
===============================================================================
# Lambert Azimuthal Equal Area
@@ -2557,7 +2557,7 @@ accept -2 -1
expect -222602.471450095 -110589.827224409
accept 180 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
direction inverse
accept 200 100
@@ -2583,7 +2583,7 @@ accept -2 -1
expect -223365.281370125 -111716.668072916
accept 180 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
direction inverse
accept 200 100
@@ -2619,7 +2619,7 @@ roundtrip 100
# error when waaay outside the sphere
direction inverse
accept 0 10
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
# Test oblique aspect of the ellipsoidal form
@@ -2662,7 +2662,7 @@ expect 0 0.7654
accept 0 45
expect 0 1.8478
accept 0 90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
tolerance 0.1 mm
accept 45 45
@@ -2683,7 +2683,7 @@ expect 0 4889334.8030
accept 0 45
expect 0 11766619.5307
accept 0 90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
tolerance 10 cm
accept 45 45
@@ -2709,7 +2709,7 @@ expect 0 -0.7654
accept 0 -45
expect 0 -1.8478
accept 0 -90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
tolerance 0.1 mm
accept 45 45
@@ -2730,7 +2730,7 @@ expect 0 -4889334.8030
accept 0 -45
expect 0 -11766619.5307
accept 0 -90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
tolerance 10 cm
accept 45 45
@@ -2740,7 +2740,7 @@ roundtrip 100
# Test error in projection setup
-------------------------------------------------------------------------------
operation +proj=laea +ellps=GRS80 +lat_0=91
-expect failure errno lat_larger_than_90
+expect failure errno invalid_op_illegal_arg_value
===============================================================================
# Lagrange
@@ -2782,22 +2782,22 @@ expect 0.10 0.0
-------------------------------------------------------------------------------
operation +proj=lagrng +R=1 +W=-1
-------------------------------------------------------------------------------
-expect failure errno w_or_m_zero_or_less
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=lagrng +R=1 +lat_1=90.00001
-------------------------------------------------------------------------------
-expect failure errno lat_larger_than_90
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=lagrng +R=1 +W=0.5
-------------------------------------------------------------------------------
accept 90 0 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
direction inverse
accept 2 0 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
operation +proj=lagrng +R=1
@@ -2966,7 +2966,7 @@ expect 1 2
-------------------------------------------------------------------------------
operation +proj=lcc +a=9999999 +b=.9 +lat_2=1
-------------------------------------------------------------------------------
-expect failure errno invalid_eccentricity
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
# This case is incredible. ossfuzz has found the exact value of lat_1 that
@@ -2975,42 +2975,42 @@ operation +proj=lcc +lat_1=2D32 +lat_2=0 +a=6378137 +b=0.2
-------------------------------------------------------------------------------
expect failure
# For some reason fails on MacOSX with a different error
-# errno invalid_eccentricity
+# errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=lcc +ellps=GRS80 +lat_1=0 +lat_2=90
-------------------------------------------------------------------------------
-expect failure errno lat_1_or_2_zero_or_90
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=lcc +ellps=GRS80 +lat_1=90 +lat_2=0
-------------------------------------------------------------------------------
-expect failure errno lat_1_or_2_zero_or_90
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=lcc +ellps=GRS80 +lat_1=90 +lat_2=90
-------------------------------------------------------------------------------
-expect failure errno lat_1_or_2_zero_or_90
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=lcc +ellps=sphere +lat_1=0 +lat_2=90
-------------------------------------------------------------------------------
-expect failure errno lat_1_or_2_zero_or_90
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=lcc +ellps=sphere +lat_1=90 +lat_2=0
-------------------------------------------------------------------------------
-expect failure errno lat_1_or_2_zero_or_90
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=lcc +ellps=sphere +lat_1=91
-------------------------------------------------------------------------------
-expect failure errno lat_larger_than_90
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=lcc +ellps=sphere +lat_2=91
-------------------------------------------------------------------------------
-expect failure errno lat_larger_than_90
+expect failure errno invalid_op_illegal_arg_value
===============================================================================
# Lambert Conformal Conic Alternative
@@ -3187,7 +3187,7 @@ operation +proj=lsat +path=1 +lsat=5 +ellps=sphere
-------------------------------------------------------------------------------
direction inverse
accept 0 1e10
-expect failure errno invalid_x_or_y
+expect failure errno coord_transfm_outside_projection_domain
===============================================================================
# McBryde-Thomas Flat-Polar Sine (No. 1)
@@ -3930,13 +3930,13 @@ accept 45 45
expect 0.4555 -0.4555
roundtrip 100
accept 0 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
direction inverse
accept 0 0
expect 0 90
accept 0 2 # projected coordinate is outside the sphere
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
# Test south polar aspect
@@ -3950,7 +3950,7 @@ accept -45 -45
expect -0.4555 0.4555
roundtrip 100
accept 0 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
operation +proj=nsper +R=1 +h=3 +lat_0=45
@@ -3970,12 +3970,12 @@ roundtrip 100
-------------------------------------------------------------------------------
operation +proj=nsper +R=1 +h=0
-------------------------------------------------------------------------------
-expect failure errno invalid_h
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=nsper +R=1 +h=1e11
-------------------------------------------------------------------------------
-expect failure errno invalid_h
+expect failure errno invalid_op_illegal_arg_value
===============================================================================
@@ -4042,7 +4042,7 @@ expect -65.862385599 51.830295078
-------------------------------------------------------------------------------
operation +proj=ob_tran +R=6400000 +o_proj +o_proj=ob_tran
-------------------------------------------------------------------------------
-expect failure errno pjd_err_failed_to_find_proj
+expect failure errno invalid_op_missing_arg
===============================================================================
# Oblique Cylindrical Equal Area
@@ -4241,7 +4241,7 @@ operation +proj=omerc +R=1 +lat_0=1 +lat_1=2 +no_rot
-------------------------------------------------------------------------------
direction inverse
accept 0 1e200
-expect failure errno invalid_x_or_y
+expect failure errno coord_transfm_outside_projection_domain
@@ -4263,28 +4263,28 @@ expect -3569.825230822232 -5093592.310871849768
-------------------------------------------------------------------------------
operation +proj=omerc +R=1 +alpha=0 +lat_0=90
-------------------------------------------------------------------------------
-expect failure errno lat_0_or_alpha_eq_90
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=omerc +lat_1=0.1 +a=6400000 +b=1
-------------------------------------------------------------------------------
# Disabled since fails on i386. Not so important. Edge condition found by ossfuzz
-#expect failure errno invalid_eccentricity
+#expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=omerc +lat_1=0.8 +a=6400000 +b=.4
-------------------------------------------------------------------------------
-expect failure errno invalid_eccentricity
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=omerc +lat_1=91
-------------------------------------------------------------------------------
-expect failure errno lat_larger_than_90
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
operation +proj=omerc +lat_2=91
-------------------------------------------------------------------------------
-expect failure errno lat_larger_than_90
+expect failure errno invalid_op_illegal_arg_value
===============================================================================
@@ -4356,11 +4356,11 @@ accept 90 90
expect 0 1
roundtrip 100
accept 120 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
direction inverse
accept 2 2
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
@@ -4390,11 +4390,11 @@ accept 170 60
expect 0.0868 0.9799
roundtrip 100
accept 140 20
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
direction inverse
accept 2 2
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
@@ -4419,13 +4419,13 @@ accept 90 0
expect 1 0
roundtrip 100
accept 180 -90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
accept 0 -45
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
direction inverse
accept 2 2
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
# Test the south polar aspect of the Orthographic projection.
@@ -4449,13 +4449,13 @@ accept 90 0
expect 1 0
roundtrip 100
accept 180 90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
accept 0 45
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
direction inverse
accept 2 2
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
# Put a point a tiny tiny bit outside the radius of the sphere.
# Since we are right at the numerical limit of floating point representation
@@ -4496,7 +4496,7 @@ expect -6378137 18504.1253
# Slightly outside
direction inverse
accept -6378137.001 18504.1253
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
# On boundary of visibility domain
direction forward
@@ -4507,7 +4507,7 @@ expect 0 -6343601.0991
# Just on it, but fails to converge. This test might be fragile
direction inverse
accept 0 -6343601.099075031466782093
-expect failure errno non_convergent
+expect failure errno coord_transfm_outside_projection_domain
# Slightly inside
direction inverse
@@ -4559,11 +4559,11 @@ roundtrip 1
# Point not visible from the projection plane
accept 90.00001 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
# Point not visible from the projection plane
accept -90.00001 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
# Consistent with WGS84 semi-major axis
accept 90 0
@@ -4586,17 +4586,17 @@ roundtrip 1
# Point not visible from the projection plane
direction inverse
accept 0 6356752.3143
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
# Point not visible from the projection plane
direction inverse
accept 1000 6356752.314
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
# Point not visible from the projection plane
direction inverse
accept 6378137.0001 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
# North pole tests
@@ -4617,7 +4617,7 @@ roundtrip 1
# Point not visible from the projection plane
accept 0 -0.0000001
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
# Consistent with WGS84 semi-major axis
accept 0 0
@@ -4627,7 +4627,7 @@ roundtrip 1
# Point not visible from the projection plane
direction inverse
accept 0 -6378137.1
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
-------------------------------------------------------------------------------
# South pole tests
@@ -4644,7 +4644,7 @@ roundtrip 1
# Point not visible from the projection plane
accept 0 0.0000001
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
# Consistent with WGS84 semi-major axis
accept 0 0
@@ -4654,7 +4654,7 @@ roundtrip 1
# Point not visible from the projection plane
direction inverse
accept 0 6378137.1
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
===============================================================================
@@ -5209,7 +5209,7 @@ accept 0.000000000000 -8654720.000000000000
expect 0 -90
accept 17250000 100000
-expect failure errno lat_or_lon_exceed_limit
+expect failure errno coord_transfm_outside_projection_domain
===============================================================================
@@ -5486,7 +5486,7 @@ expect -223407.810259507 111737.938996443
accept -2 -1
expect -223407.810259507 -111737.938996443
accept 180 0
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
direction inverse
accept 200 100
@@ -5978,7 +5978,7 @@ operation +proj=tmerc +R=1
-------------------------------------------------------------------------------
direction inverse
accept -1e200 0
-expect failure errno invalid_x_or_y
+expect failure errno coord_transfm_outside_projection_domain
===============================================================================
@@ -6137,10 +6137,10 @@ operation +proj=tobmerc +ellps=sphere
# Test expected failure at the poles:
-------------------------------------------------------------------------------
accept 0 90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
accept 0 -90
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
===============================================================================
@@ -6287,7 +6287,8 @@ expect -45.001432287 64.914588378
-------------------------------------------------------------------------------
operation +proj=ups +a=6400000
-------------------------------------------------------------------------------
-expect failure errno ellipsoid_use_required
+# ups not possible on sphere
+expect failure errno invalid_op_illegal_arg_value
===============================================================================
# Urmaev V
@@ -6309,7 +6310,7 @@ accept -2 -1
expect -223393.638433964 -111696.818785117
operation +proj=urm5 +a=6400000 +n=1 +alpha=90
-expect failure errno lat_0_or_alpha_eq_90
+expect failure errno invalid_op_illegal_arg_value
===============================================================================
# Urmaev Flat-Polar Sinusoidal
@@ -6385,7 +6386,8 @@ expect 687071.43911000 6210141.32675053 0.00000000 2000.0000
-------------------------------------------------------------------------------
operation +proj=utm +a=6400000 +zone=30
-------------------------------------------------------------------------------
-expect failure errno ellipsoid_use_required
+# utm not possible on sphere
+expect failure errno invalid_op_illegal_arg_value
===============================================================================
# van der Grinten (I)
@@ -6421,7 +6423,7 @@ operation +proj=vandg +R=1
-------------------------------------------------------------------------------
direction inverse
accept 0 -1e100
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
===============================================================================
@@ -6901,18 +6903,18 @@ roundtrip 1
# missing X_0,Y_0,Z_0 or lon_0,lat_0
operation +proj=topocentric +ellps=WGS84
-expect failure errno missing_args
+expect failure errno invalid_op_missing_arg
# missing Z_0
operation +proj=topocentric +ellps=WGS84 +X_0=0 +Y_0=0
-expect failure errno missing_args
+expect failure errno invalid_op_missing_arg
# missing lat_0
operation +proj=topocentric +ellps=WGS84 +lon_0=0
-expect failure errno missing_args
+expect failure errno invalid_op_missing_arg
# X_0 and lon_0 are mutually exclusive
operation +proj=topocentric +ellps=WGS84 +X_0=0 +lon_0=0
-expect failure errno mutually_exclusive_args
+expect failure errno invalid_op_mutually_exclusive_args
</gie-strict>
diff --git a/test/gie/defmodel.gie b/test/gie/defmodel.gie
index 94dd5784..95264c82 100644
--- a/test/gie/defmodel.gie
+++ b/test/gie/defmodel.gie
@@ -8,15 +8,15 @@ Test +proj=defmodel
# Missing +model
operation +proj=defmodel
-expect failure errno no_args
+expect failure errno invalid_op_missing_arg
# +model doesn't point to an existing file
operation +proj=defmodel +model=i_do_not_exist
-expect failure errno invalid_arg
+expect failure errno invalid_op_file_not_found_or_invalid
# Not a JSON file
operation +proj=defmodel +model=proj.ini
-expect failure errno invalid_arg
+expect failure errno invalid_op_file_not_found_or_invalid
# Horizontal deformation with horizontal unit = degree
operation +proj=defmodel +model=tests/simple_model_degree_horizontal.json
diff --git a/test/gie/deformation.gie b/test/gie/deformation.gie
index cda2e5ac..086c1ebe 100644
--- a/test/gie/deformation.gie
+++ b/test/gie/deformation.gie
@@ -47,7 +47,6 @@ operation +proj=deformation +xy_grids=alaska +z_grids=egm96_15.gtx \
+ellps=GRS80 +dt=16.0 # 2016.0 - 2000.0
-------------------------------------------------------------------------------
tolerance 0.1 mm
-ignore pjd_err_failed_to_load_grid
accept -3004295.5882503074 -1093474.1690603832 5500477.1338251457
expect -3004295.7000 -1093474.2097 5500477.3397
roundtrip 5
@@ -55,9 +54,9 @@ roundtrip 5
# Test that errors are reported for coordinates outside the grid.
# Here we test 120W 40N which is well outside the alaska grid.
accept -2446353.8001 -4237209.0750 4077985.572
-expect failure errno grid_area
+expect failure errno coord_transfm_outside_grid
accept -2446353.8001 -4237209.0750 4077985.572
-expect failure errno grid_area
+expect failure errno coord_transfm_outside_grid
-------------------------------------------------------------------------------
@@ -68,34 +67,32 @@ operation +proj=deformation \
-------------------------------------------------------------------------------
tolerance 0.1 mm
direction inverse
-ignore pjd_err_failed_to_load_grid
accept -3004295.5882503074 -1093474.1690603832 5500477.1338251457 2000.0
expect -3004295.7000 -1093474.2097 5500477.3397 2000.0
roundtrip 5
-------------------------------------------------------------------------------
operation proj=deformation xy_grids=alaska +dt=1.0 ellps=GRS80
-expect failure pjd_err_no_args
+expect failure errno invalid_op_missing_arg
operation proj=deformation z_grids=egm96_15.gtx +dt=1.0 ellps=GRS80
-expect failure pjd_err_no_args
+expect failure errno invalid_op_missing_arg
operation proj=deformation xy_grids=nonexisting z_grids=egm96_15.gtx \
+dt=1.0 ellps=GRS80
-expect failure pjd_err_failed_to_load_grid
+expect failure errno invalid_op_file_not_found_or_invalid
operation proj=deformation xy_grids=alaska z_grids=nonexisting \
+dt=1.0 ellps=GRS80
-expect failure pjd_err_failed_to_load_grid
+expect failure errno invalid_op_file_not_found_or_invalid
operation proj=deformation xy_grids=alaska z_grids=nonexisting ellps=GRS80
-expect failure pjd_err_missing_args
+expect failure errno invalid_op_file_not_found_or_invalid
-------------------------------------------------------------------------------
operation +proj=vgridshift +grids=egm96_15.gtx +t_epoch=2010.0 +t_final=2018.0
-------------------------------------------------------------------------------
tolerance 0.1 mm
-ignore pjd_err_failed_to_load_grid
accept 12 56 0.0 2000.0
expect 12 56 -36.9960 2000.0
@@ -118,7 +115,6 @@ roundtrip 100
operation +proj=vgridshift +grids=egm96_15.gtx +t_epoch=2010.0 +t_final=now
-------------------------------------------------------------------------------
tolerance 0.1 mm
-ignore pjd_err_failed_to_load_grid
accept 12 56 0.0 2000.0
expect 12 56 -36.9960 2000.0
@@ -137,7 +133,6 @@ roundtrip 100
operation +proj=hgridshift +grids=alaska +t_epoch=2010.0 +t_final=2018.0
-------------------------------------------------------------------------------
tolerance 0.1 mm
-ignore pjd_err_failed_to_load_grid
accept -147.0 64.0 0.0 2000.0
expect -147.0023233121 63.9995792119 0.0 2000.0
@@ -155,7 +150,6 @@ roundtrip 100
operation +proj=hgridshift +grids=alaska +t_epoch=2010.0 +t_final=now
-------------------------------------------------------------------------------
tolerance 0.1 mm
-ignore pjd_err_failed_to_load_grid
accept -147.0 64.0 0.0 2000.0
expect -147.0023233121 63.9995792119 0.0 2000.0
diff --git a/test/gie/ellipsoid.gie b/test/gie/ellipsoid.gie
index 5e0049f7..ea0e326a 100644
--- a/test/gie/ellipsoid.gie
+++ b/test/gie/ellipsoid.gie
@@ -53,22 +53,22 @@ expect 1335833.8895192828 7326837.7148738774
# Then try to fail deliberately
-------------------------------------------------------------------------------
operation proj=merc ellps=GRS80000000000
-expect failure errno unknown_ellp_param
+expect failure errno invalid_op_illegal_arg_value
operation proj=merc +a=-1
-expect failure errno major_axis_not_given
+expect failure errno invalid_op_illegal_arg_value
operation proj=merc
accept 0 0
expect 0 0
-operation proj=merc +es=-1
-expect failure errno major_axis_not_given
+operation proj=merc +a=1 +es=-1
+expect failure errno invalid_op_illegal_arg_value
operation proj=merc +R=0
-expect failure errno major_axis_not_given
+expect failure errno invalid_op_illegal_arg_value
operation +proj=merc +R_a +a=2 +f=2
-expect failure errno major_axis_not_given
+expect failure errno invalid_op_illegal_arg_value
operation
expect failure
@@ -117,8 +117,6 @@ expect 1338073.2696101593 7374207.4801437631
-------------------------------------------------------------------------------
operation proj=merc a=1E77 R_lat_a=90 b=1
-# errno invalid_eccentricity on x86_64
-# errno pjd_err_ref_rad_larger_than_90 on i386
expect failure
-------------------------------------------------------------------------------
@@ -136,25 +134,25 @@ expect -1.56904 0
# Shape parameters
-------------------------------------------------------------------------------
operation proj=utm zone=32 ellps=GRS80 rf=0
-expect failure errno rev_flattening_is_zero
+expect failure errno invalid_op_illegal_arg_value
operation proj=utm zone=32 ellps=GRS80 e=-0.5
-expect failure errno invalid_eccentricity
+expect failure errno invalid_op_illegal_arg_value
operation proj=utm zone=32 ellps=GRS80 e=1
-expect failure errno invalid_eccentricity
+expect failure errno invalid_op_illegal_arg_value
operation proj=utm zone=32 ellps=GRS80 es=1
-expect failure errno invalid_eccentricity
+expect failure errno invalid_op_illegal_arg_value
operation proj=utm zone=32 a=1 es=1.1
-expect failure errno invalid_eccentricity
+expect failure errno invalid_op_illegal_arg_value
operation proj=utm zone=32 ellps=GRS80 b=0
-expect failure errno invalid_eccentricity
+expect failure errno invalid_op_illegal_arg_value
operation proj=utm zone=32 ellps=GRS80 f=1
-expect failure errno invalid_eccentricity
+expect failure errno invalid_op_illegal_arg_value
operation proj=utm zone=32 ellps=GRS80 b=6000000
accept 12 55
diff --git a/test/gie/geotiff_grids.gie b/test/gie/geotiff_grids.gie
index b71ed774..7e03d20a 100644
--- a/test/gie/geotiff_grids.gie
+++ b/test/gie/geotiff_grids.gie
@@ -151,13 +151,13 @@ expect 4.05 52.1 10
-------------------------------------------------------------------------------
operation +proj=vgridshift +grids=tests/test_vgrid_invalid_channel_type.tif +multiplier=1
-------------------------------------------------------------------------------
-expect failure errno failed_to_load_grid
+expect failure errno invalid_op_file_not_found_or_invalid
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
operation +proj=vgridshift +grids=tests/test_vgrid_unsupported_byte.tif +multiplier=1
-------------------------------------------------------------------------------
-expect failure errno failed_to_load_grid
+expect failure errno invalid_op_file_not_found_or_invalid
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
@@ -181,7 +181,7 @@ accept 179.8 54.5 0
expect 179.8 54.5 -0.7011
accept 179.799 54.5 0
-expect failure errno grid_area
+expect failure errno coord_transfm_outside_grid
accept 180.1833333 54.5 0
expect -179.8166667 54.5 -3.1933
@@ -190,10 +190,10 @@ accept -179.8166667 54.5 0
expect -179.8166667 54.5 -3.1933
accept 180.184 54.5 0
-expect failure errno grid_area
+expect failure errno coord_transfm_outside_grid
accept -179.816 54.5 0
-expect failure errno grid_area
+expect failure errno coord_transfm_outside_grid
-------------------------------------------------------------------------------
operation +proj=hgridshift +grids=tests/test_hgrid.tif
@@ -327,7 +327,7 @@ accept -45.0 22.5 5
-------------------------------------------------------------------------------
operation +proj=hgridshift +grids=tests/test_vgrid.tif
-------------------------------------------------------------------------------
-expect failure errno failed_to_load_grid
+expect failure errno invalid_op_file_not_found_or_invalid
-------------------------------------------------------------------------------
diff --git a/test/gie/guyou.gie b/test/gie/guyou.gie
index f311c173..2ed2ab49 100644
--- a/test/gie/guyou.gie
+++ b/test/gie/guyou.gie
@@ -9,31 +9,31 @@ operation +proj=guyou +R=6370997
tolerance 1 mm
------------------------------------------------------------
accept -179.2338274749 -90.7265739758
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -169.3015609686 -90.0683270041
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -159.4420546811 -89.5695551279
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.0045856345 -89.3536369188
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.7153960960 -88.6283945950
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.2286632319 -88.1551228787
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.6286953558 -87.5083524092
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.3328522812 -86.7510648640
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.1598524965 -86.5788079857
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.3858632536 -85.7390309668
expect -671252.534 -11805089.168
@@ -90,61 +90,61 @@ accept 80.2085188693 -77.6809008485
expect 1936586.409 -11469925.164
accept 90.8009658259 -76.9680414794
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.6742326194 -76.5942100817
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.5209403479 -75.7585741711
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.5896919383 -74.7649093703
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.7851397036 -73.8582467239
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.7197125638 -72.9106110624
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.4163065521 -72.4059990981
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.4383759680 -71.9757412863
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.5708534042 -71.9525642223
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.2751947006 -71.5923171899
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.7095201992 -79.9687467646
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.0436424710 -79.2424043855
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.2911675882 -78.8917009107
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.2815042820 -78.0224816760
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.5027225646 -77.9987560452
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.2026665027 -77.1138023157
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.6161746714 -76.2954327600
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.9384914753 -75.7674050764
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.2183181307 -75.3730011624
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.2190150125 -74.7701795551
expect -2454071.146 -11777557.324
@@ -201,61 +201,61 @@ accept 80.8997122704 -66.2858473379
expect 3898590.190 -11120572.688
accept 90.1834036836 -65.9440561722
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.1984006355 -65.8229326488
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.0486236854 -65.7960345782
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.0510835725 -65.5190096678
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.0896340570 -64.7428784330
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.3678752942 -64.2229928140
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.9150615805 -63.6990548356
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.7280406636 -63.2384418688
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.9183869916 -62.3774990742
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.5619551786 -61.8063907044
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.5943789042 -69.9992085183
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.2134281484 -69.9776667645
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.4518981501 -69.3934026425
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.3420413989 -68.4537518490
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.1131932532 -67.9826718761
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.7785737569 -67.0804523760
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.8160590971 -66.1963102135
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.4603476745 -65.8550220266
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.0748524068 -64.9756976432
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.9206313411 -64.7601702734
expect -4268828.098 -11805632.912
@@ -312,61 +312,61 @@ accept 80.0904535261 -56.6354279372
expect 5822685.735 -10504356.087
accept 90.6634211024 -56.5251359813
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.1452342443 -55.8401169432
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.6864799856 -55.5116401637
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.3347823427 -55.2601958124
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.5134926951 -55.0238926067
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.3265242174 -54.4313834318
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.3685976599 -54.1552793246
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.9436711470 -53.1842316256
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.4034003030 -52.8621841373
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.0812683568 -52.6818511960
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.2224188803 -59.7295306861
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.4132895133 -59.2803345438
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.2336212919 -58.3674875157
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.6086565801 -57.6885943847
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.4537382278 -57.2767531329
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.8015642819 -57.0799116685
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.7728590554 -56.2047588377
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.7735055519 -55.4019628150
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.4371631073 -55.2867551270
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.8854290226 -55.2220077227
expect -6418775.726 -11794916.199
@@ -423,61 +423,61 @@ accept 80.4369996020 -46.9794332723
expect 8182405.160 -9269149.720
accept 90.9151039880 -46.9029866463
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.4373534616 -46.3943619602
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.2842129880 -46.0308507793
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.6486866778 -45.8054277747
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.9364857762 -44.8554259969
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.6165699073 -43.9676693909
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.9528142413 -43.4405627423
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.5641245537 -42.8137396224
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.0938980656 -42.4864250646
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.3769971687 -41.5989802375
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.6808058361 -49.7903016746
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.7237623059 -49.1268921477
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.4102418582 -48.8849591656
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.2254953136 -48.1844242863
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.6041758915 -48.1179744801
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.3754143228 -47.1336832541
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.9746970079 -46.6736994965
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.7581919902 -46.0366124472
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.3823846098 -45.2381613350
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.0277852820 -44.5174266340
expect -11094288.600 -10444365.724
@@ -534,61 +534,61 @@ accept 80.3791490145 -38.1923095026
expect 9574134.022 -7007709.059
accept 90.5297378114 -37.5687159826
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.5373276614 -37.4573903187
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.0551604465 -36.8785291472
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.5920897044 -36.3504262236
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.1692367892 -36.3071095311
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.0034604349 -35.8053875550
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.9162515055 -35.0369229256
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.4006102901 -34.7824559736
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.8020614665 -34.6367632672
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.1238278697 -34.5735242626
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.3392011550 -39.0089519711
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.9896135260 -38.7758352491
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.9464206150 -38.3643075290
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.8580885141 -38.2565849818
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.0005407033 -37.8175552179
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.9744744916 -36.9749101428
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.4953757022 -36.8251466679
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.8732055905 -36.2644266473
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.2563126423 -35.6283177384
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.2720021301 -34.8337636531
expect -11652944.582 -6432012.406
@@ -645,61 +645,61 @@ accept 80.2959404087 -25.8737294352
expect 10093881.725 -4297066.952
accept 90.1903848581 -25.5035070096
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.5725497895 -24.6749860350
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.0757522922 -23.8621044004
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.4910038636 -23.3154304481
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.4609905705 -22.3944602016
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.6308884892 -22.1941484220
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.0358167607 -21.7789499055
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.5878069076 -21.7481812197
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.4050593367 -20.8432353205
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.6001699300 -20.8118177919
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.3361000782 -29.8463222037
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.8999068245 -28.9429338477
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.7592377898 -28.1615925042
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.9913638701 -28.0372332017
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.2301202473 -27.0565876723
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.1268966632 -26.9865340393
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.2460222852 -26.3197565889
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.2062914741 -26.1682287226
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.9045914125 -25.7799438486
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.4037956256 -25.6991927518
expect -11705355.483 -4359448.179
@@ -756,61 +756,61 @@ accept 80.3956836685 -17.4271102390
expect 10234009.562 -2784279.451
accept 90.6808556357 -16.9481935094
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.3669156655 -16.2433923188
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.3790328662 -15.6932515137
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.0882522480 -15.6441029970
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.9326190128 -15.1072978015
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.4297399110 -14.7647959439
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.6670621082 -14.1800360544
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.3083279810 -13.5438931052
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.8801051896 -12.6909069291
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.9127621133 -11.7358777998
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.9634704329 -19.2242789077
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.1616383718 -18.5021923570
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.5989242161 -17.7569846767
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.7658699571 -17.6838689314
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.2283007229 -17.1203530837
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.9856069274 -16.5121558117
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.7066160149 -15.8271303941
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.0808307002 -15.7236132624
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.7051569128 -15.2351555878
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.3984343202 -14.2830109769
expect -11714485.527 -2294583.435
@@ -867,61 +867,61 @@ accept 80.1896127919 -6.1835665729
expect 10268415.013 -962009.778
accept 90.2901075635 -5.6342271039
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.1017071129 -4.7732910406
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.6453720127 -4.6247740358
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.7065181706 -4.2131506322
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.0549243427 -3.6680122287
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.9588650575 -3.4693258990
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.3128054514 -2.8622073994
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.6250791828 -2.1132082532
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.1249865639 -1.7779699685
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.4636820369 -1.0447468723
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.0903238876 -9.3809572676
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.8021514554 -8.8200596604
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.6897632216 -8.8151211116
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.3642571872 -7.9578518334
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.1869613409 -7.1376473645
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.8103143949 -6.6533639455
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.0042762894 -6.5424897944
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.3222469320 -6.2626405858
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.2447484897 -5.4322064514
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.6583934020 -4.8520491055
expect -11758391.270 -764820.107
@@ -978,61 +978,61 @@ accept 80.4040420231 2.1516254056
expect 10309233.052 333889.458
accept 90.0745563464 2.5008083079
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.1323994743 3.4798778581
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.7633392793 3.6524418056
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.4935453458 3.8138602324
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.1832453461 4.1827817395
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.6879708211 4.3228601350
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.2872780471 5.0096390922
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.4567844437 5.3289862640
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.1798195714 5.7690110660
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.6237066153 6.6873727446
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.1879408995 0.2925675717
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.8193242429 0.5456299185
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.6143094365 0.8491614798
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.1327424918 1.5360082778
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.9118046279 2.2304783506
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.0707848713 3.1774300866
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.0155367869 3.7108516861
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.7138958240 4.2367325538
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.7626123411 4.9489248449
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.0617378460 5.0261998535
expect -11664192.229 792317.110
@@ -1089,61 +1089,61 @@ accept 80.6785108917 11.6138802990
expect 10322728.590 1826238.813
accept 90.9684446145 11.7088334392
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.8216260873 12.6522648050
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.9341322494 12.8091489452
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.9598573820 12.9840580805
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.1838852888 13.9674113487
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.0163435591 14.2583869011
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.3440941987 15.1977215509
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.6373902313 15.9779535702
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.7489315029 16.0224553269
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.4315909707 16.2341190017
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.9613846400 10.9181102314
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.2464750707 11.2011582425
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.3961353214 12.0814824633
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.9176675016 12.1332225594
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.0300120770 12.7448426145
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.1479377942 13.2115900349
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.6509465671 13.5121287506
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.3485953585 13.9838806488
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.8025484668 14.3000196702
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.6183447418 14.3419323455
expect -11750225.007 2304558.947
@@ -1200,61 +1200,61 @@ accept 80.2433369822 23.0164275848
expect 10137749.549 3763259.969
accept 90.9377687408 23.1511733038
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.5407405945 23.7761284488
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.0059145808 24.0087396721
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.0839708022 24.8505061022
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.5684827033 25.5457759639
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.2735017629 25.7841586919
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.4960718541 26.4511385776
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.5086557650 26.5248940712
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.1010951379 26.6437577891
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.4667032708 27.5171288037
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.2332673731 20.5208357556
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.9852102849 20.6884242583
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.0918834805 20.7807287727
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.3554284700 21.5195488802
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.0517203599 22.3228377599
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.7889045882 22.9579802676
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.2864956877 23.1541372478
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.1249371549 23.9554652672
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.7886236408 24.2156608317
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.8158531726 24.4254617574
expect -11779801.464 4109615.321
@@ -1311,61 +1311,61 @@ accept 80.8754092571 33.4499285913
expect 9965465.329 5886025.407
accept 90.9417476435 34.2241428842
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.8763555867 34.9730810876
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.3824336404 35.8924849218
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.3907138338 36.0772654252
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.2213236652 36.3411661168
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.6643127929 37.0777875387
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.5696707234 38.0028546554
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.7330142926 38.8399381962
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.7575452122 39.6058463190
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.9074447347 39.7506383769
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.8967233384 30.5573004598
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.4186924041 30.7925389719
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.3550554816 31.7173298465
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.3340973772 32.5661389704
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.8793305042 33.1451975000
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.0718020283 34.1077930148
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.3765659597 34.6652275838
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.7130644703 34.6829381126
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.5574458139 35.6024191831
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.7509673047 36.5904991465
expect -11753853.356 6923417.921
@@ -1422,61 +1422,61 @@ accept 80.5212319721 43.4298814980
expect 8945156.756 8415826.102
accept 90.6029060873 44.3367368198
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.2968163089 44.7347899226
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.5453757907 45.2325884903
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.7132187645 46.1810408682
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.6036848279 46.5953112427
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.8567165951 47.1100600825
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.2452206431 47.5590007593
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.4264735249 47.6243197261
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.1158223427 47.9911994181
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.2100320112 48.9423016681
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.5621188253 40.3822966969
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.0785389235 40.7397870392
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.9453288766 41.0637989828
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.0072837113 41.2595851413
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.3677527689 41.6743754043
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.1116359654 41.9054883189
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.2367048985 42.7084205060
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.9662473469 42.7707453425
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.5437449083 43.0239640060
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.4757325990 43.2756199739
expect -11568961.170 9589089.555
@@ -1533,61 +1533,61 @@ accept 80.7661690288 51.9879378395
expect 6957594.499 10145750.592
accept 90.2760527960 52.6279152662
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.4470132582 53.2519801847
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.1282829759 53.4607562483
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.5033443580 53.9989335755
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.6561885293 54.6823982531
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.8127091472 54.7255186711
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.0054753699 55.4459165788
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.8312787949 56.2470115579
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.4614439985 57.0241073917
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.7047485406 57.6022388310
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.1586219943 50.1365490236
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.1490260788 50.5023613434
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.9535088204 50.9035428509
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.0803324654 51.7753015653
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.7669273536 52.0799612604
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.3599142098 52.6974765371
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.6940989810 52.7976100393
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.7805542206 53.6394720461
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.9951721199 54.3490528856
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.4965032257 55.2735286995
expect -6404341.484 11736182.738
@@ -1644,61 +1644,61 @@ accept 80.0566574740 65.2466871256
expect 4076741.126 11011949.460
accept 90.0598623933 65.7380193724
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.2499385302 65.8274446207
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.0091445253 66.5841928232
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.1938557094 67.3045114589
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.1756983494 67.6825233842
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.0774508004 68.5260112188
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.3642244888 69.0087889372
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.0390197585 69.5064813197
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.9882511230 69.9663419458
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.7855662108 70.7558001565
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.8630282894 60.2684905661
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.8774561096 60.7063957678
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.1639611249 61.1269337981
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.0814548820 61.9063878006
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.0476545160 62.7911475081
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.8276916999 62.8515546512
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.9464541970 63.7277849393
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.6864148265 63.9317823343
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.4184495170 64.2249537529
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.6146334566 64.3967878406
expect -4340586.273 11779220.401
@@ -1755,61 +1755,61 @@ accept 80.5645741906 73.7542661119
expect 2585249.402 11363593.076
accept 90.6177784757 74.6865930919
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.2695381559 74.9397012116
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.3724185381 75.3340827987
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.0440856622 75.6509550283
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.4765915697 75.8794035471
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.8273607225 76.0863350217
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.7446122566 76.4130503173
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.9066279973 77.2046671411
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.7039755420 77.5254695021
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.7301487821 78.3643690894
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.8424303971 70.7640164273
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.3071682617 71.3534426809
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.9256766432 72.0788134529
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.4389257633 72.9048071827
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.4466193836 73.7144086150
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.4240513471 74.3140345936
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.2324314226 75.1789245938
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.4080456874 75.9740320316
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.9624349535 76.7254677659
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.8454838458 77.0914030368
expect -2065538.883 11806583.302
@@ -1866,61 +1866,61 @@ accept 80.6482540792 83.6156947131
expect 994412.198 11647508.801
accept 90.5789951029 84.1124942551
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 100.9443622402 84.2131541284
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 110.5781783058 84.7127066970
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 120.6025664533 85.5661224284
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 130.0976840056 86.3318417998
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 140.6428111911 86.3329325553
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 150.5251209004 86.5841388479
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 160.4225603060 86.8828039057
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 170.2581319411 87.4171568183
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept 180.2641439484 88.1608036446
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -179.8401984185 80.4164439367
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.6918872432 81.0641431423
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.9211815920 81.7949103274
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.6075138921 82.1028453149
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -139.7372920424 82.6385276754
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -129.7998353504 83.1090136759
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -119.2572393635 83.4744235117
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -109.3246360912 84.2608342272
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -99.9601976700 84.4677069243
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -89.7505869053 85.0897168462
expect -774050.983 11808922.435
@@ -1956,166 +1956,166 @@ accept 10.6007672189 89.4603893825
expect 15609.535 11728897.894
accept 20.8972133561 90.3008662748
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 30.3236847010 90.9360697392
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 40.5211596030 91.7425546179
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 50.9530657025 92.7167872262
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 60.2966089164 93.5518747322
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 70.7058277145 93.9478756274
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 80.2842419408 94.5029189079
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 90.7046514943 94.5870657217
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 100.5757828394 94.7544968060
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 110.0784957493 95.6326996288
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 120.4420696450 95.7426558832
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 130.0586467430 96.7026620164
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 140.4982918026 97.2453640881
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 150.5137836610 97.2825819689
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 160.8117951323 97.8473653755
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 170.3402740510 98.3587698302
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.9378053935 98.8743871260
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -179.1153600127 89.6136396944
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -169.2261536485 89.9072554901
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -159.1164133392 89.9075872145
-expect failure errno -20
+expect failure errno coord_transfm_outside_projection_domain
accept -149.3960866375 90.6652890542
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -139.1720109722 91.2726639403
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -129.0729383513 91.6124307897
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -119.9896731679 92.3534538297
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -109.8793121665 92.7446796075
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -99.4084806305 92.8846710223
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -89.0674703286 93.0281709121
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -79.1149414160 93.4632295476
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -69.5261517902 94.2811379653
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -59.0417407292 95.2403005497
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -49.3488539326 95.3977809221
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -39.5548627592 96.1026759897
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -29.0476034815 96.4820442045
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -19.8278954645 96.7461121139
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -9.5488366916 97.4860281314
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 0.2307496667 97.5054708483
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 10.6032378382 97.7247157965
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 20.8959156966 98.0573474237
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 30.7104889319 98.5114024172
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 40.1762654017 98.8114138429
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 50.8420835837 99.5737438963
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 60.4391516649 99.6023781174
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 70.3833096116 99.8129052911
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 80.6796681432 100.7784977140
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 90.8893235311 100.9639616716
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 100.8499388037 101.2529844461
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 110.5488903609 101.9706798836
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 120.1208651509 102.2817021553
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 130.1003249085 102.6010355936
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 140.8624898562 102.6311753718
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 150.3852952852 103.0749738073
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 160.7214099599 103.8391207142
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 170.6439119665 104.6357199780
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.2173889904 105.2754168153
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
------------------------------------------------------------
------------------------------------------------------------
diff --git a/test/gie/more_builtins.gie b/test/gie/more_builtins.gie
index 41d0ac52..67635642 100644
--- a/test/gie/more_builtins.gie
+++ b/test/gie/more_builtins.gie
@@ -75,11 +75,11 @@ roundtrip 1
-------------------------------------------------------------------------------
# No arguments
operation proj=molodensky a=6378160 rf=298.25
-expect failure errno no_args
+expect failure errno invalid_op_missing_arg
# Missing arguments
operation proj=molodensky a=6378160 rf=298.25 dx=0
-expect failure errno missing_arg
+expect failure errno invalid_op_missing_arg
-------------------------------------------------------------------------------
@@ -267,11 +267,11 @@ roundtrip 100 1 nm
-------------------------------------------------------------------------------
# Fail on purpose: +grids parameter is mandatory
operation proj=vgridshift
-expect failure errno no_args
+expect failure errno invalid_op_missing_arg
# Fail on purpose: open non-existing grid
operation proj=vgridshift grids=nonexistinggrid.gtx
-expect failure errno failed_to_load_grid
+expect failure errno invalid_op_file_not_found_or_invalid
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
@@ -298,11 +298,11 @@ expect 2.250704350387 46.500051597273
-------------------------------------------------------------------------------
# Fail on purpose: open non-existing grid:
operation proj=hgridshift grids=@nonexistinggrid.gsb,anothernonexistinggrid.gsb
-expect failure errno failed_to_load_grid
+expect failure errno invalid_op_file_not_found_or_invalid
# Fail on purpose: +grids parameter is mandatory:
operation proj=hgridshift
-expect failure errno no_args
+expect failure errno invalid_op_missing_arg
-------------------------------------------------------------------------------
@@ -434,27 +434,27 @@ expect 3513638.1999 778956.4533 5248216.4535 2008.75
-------------------------------------------------------------------------------
# A rotational term implies an explicit convention to be specified
operation proj=helmert rx=1
-expect failure errno missing_arg
+expect failure errno invalid_op_missing_arg
operation proj=helmert rx=1 convention=foo
-expect failure errno invalid_arg
+expect failure errno invalid_op_illegal_arg_value
operation proj=helmert rx=1 convention=1
-expect failure errno invalid_arg
+expect failure errno invalid_op_illegal_arg_value
# towgs84 in helmert context should always be position_vector
operation proj=helmert towgs84=1,2,3,4,5,6,7 convention=coordinate_frame
-expect failure errno invalid_arg
+expect failure errno invalid_op_illegal_arg_value
# Transpose no longer accepted
operation proj=helmert transpose
-expect failure errno invalid_arg
+expect failure errno invalid_op_illegal_arg_value
# Use of 2D Helmert interface with 3D Helmert setup
operation +proj=ob_tran +o_proj=helmert +o_lat_p=0
direction inverse
accept 0 0
-expect failure errno 22
+expect failure errno no_inverse_op
-------------------------------------------------------------------------------
# Molodensky-Badekas from IOGP Guidance 7.2, Transformation from La Canoa to REGVEN
@@ -477,7 +477,7 @@ expect 2550138.45 -5749799.87 1054530.82
# Missing convention
operation proj=molobadekas
-expect failure errno missing_arg
+expect failure errno invalid_op_missing_arg
-------------------------------------------------------------------------------
@@ -513,14 +513,14 @@ roundtrip 1
# some less used options
-------------------------------------------------------------------------------
operation proj=utm ellps=GRS80 zone=32 to_meter=0
-expect failure errno unit_factor_less_than_0
+expect failure errno invalid_op_illegal_arg_value
operation proj=utm ellps=GRS80 zone=32 to_meter=10
accept 12 55
expect 69187.5632 609890.7825
operation proj=utm ellps=GRS80 zone=32 to_meter=1/0
-expect failure errno unit_factor_less_than_0
+expect failure errno invalid_op_illegal_arg_value
operation proj=utm ellps=GRS80 zone=32 to_meter=2.0/0.2
accept 12 55
@@ -791,7 +791,7 @@ expect 25 25 25 25
# Test invalid lat_0
operation +proj=aeqd +R=1 +lat_0=91
-expect failure errno lat_larger_than_90
+expect failure errno invalid_op_illegal_arg_value
-------------------------------------------------------------------------------
# cart
diff --git a/test/gie/nkg.gie b/test/gie/nkg.gie
new file mode 100644
index 00000000..7414914e
--- /dev/null
+++ b/test/gie/nkg.gie
@@ -0,0 +1,240 @@
+<gie-strict>
+
+# -------------------------------------------------------------------------------
+# NKG
+# -------------------------------------------------------------------------------
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_NKG_ETRF00
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.9311 948983.7980 5201383.2227 2020.5
+
+
+#-------------------------------------------------------------------------------
+# DENMARK
+#-------------------------------------------------------------------------------
+
+# 2008 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_DK
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.9362 948983.7825 5201383.2292 2020.5
+
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_DK
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.3829 948984.2188 5201383.5296 2020.5
+
+
+# 2020 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_DK
+tolerance 0.1 mm
+
+# BUDD
+accept 3513638.0964 778956.5470 5248216.5248 2015.0
+expect 3513638.5607 778956.1875 5248216.2477 2015.0
+
+#ESBC
+accept 3582104.8458 532590.0946 5232755.0863 2015.0
+expect 3582105.2916 532589.7310 5232754.8057 2015.0
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_NKG_ETRF14
+tolerance 0.1 mm
+
+# BUDD
+accept 3513638.0964 778956.5470 5248216.5248 2015.0
+expect 3513638.5071 778956.1528 5248216.2870 2015.0
+
+# ESBC
+accept 3582104.8458 532590.0946 5232755.0863 2015.0
+expect 3582105.2401 532589.6950 5232754.8507 2015.0
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ETRF14_TO_DK
+tolerance 0.1 mm
+
+# BUDD
+accept 3513638.5071 778956.1528 5248216.2870 2015.0
+expect 3513638.5607 778956.1875 5248216.2477 2015.0
+
+# ESBC
+accept 3582105.2401 532589.6950 5232754.8507 2015.0
+expect 3582105.2916 532589.7310 5232754.8057 2015.0
+
+# -------------------------------------------------------------------------------
+# ESTONIA
+# -------------------------------------------------------------------------------
+
+# 2008 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_EE
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.9395 948983.8006 5201383.2242 2020.5
+
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_EE
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.3862 948984.2370 5201383.5246 2020.5
+
+
+# 2020 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_EE
+tolerance 0.1 mm
+
+# AJOE
+accept 2922027.7409 1516183.8589 5444680.6502 2015.0
+expect 2922028.2730 1516183.5457 5444680.4094 2015.0
+
+# -------------------------------------------------------------------------------
+# FINLAND
+# -------------------------------------------------------------------------------
+
+# 2008 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_FI
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.9522 948983.7911 5201383.2230 2020.5
+
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_FI
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.3989 948984.2274 5201383.5235 2020.5
+
+
+# 2020 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_FI
+tolerance 0.1 mm
+
+# DEGE
+accept 2994012.0569 1112559.9272 5502272.0863 2015.0
+expect 2994012.5170 1112559.5902 5502271.7683 2015.0
+
+
+# -------------------------------------------------------------------------------
+# LATVIA
+# -------------------------------------------------------------------------------
+
+# 2008 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_LV
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.9806 948983.8606 5201383.3118 2020.5
+
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_LV
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.4273 948984.2970 5201383.6122 2020.5
+
+# 2020 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_LV
+tolerance 0.1 mm
+
+# BAUS
+accept 3226814.4746 1449250.4615 5289639.6134 2015.0
+expect 3226814.9950 1449250.1841 5289639.3779 2015.0
+
+
+# -------------------------------------------------------------------------------
+# LITHUANIA
+# -------------------------------------------------------------------------------
+
+# 2008 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_LT
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.9358 948983.8042 5201383.2294 2020.5
+
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_LT
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.3826 948984.2405 5201383.5299 2020.5
+
+# 2020 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_LT
+tolerance 0.1 mm
+
+# VLNS
+accept 3343600.4221 1580417.8797 5179337.3696 2015.0
+expect 3343600.9945 1580417.5661 5179337.1637 2015.0
+
+
+# -------------------------------------------------------------------------------
+# NORWAY
+# -------------------------------------------------------------------------------
+
+# 2008 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_NO
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.9204 948983.8049 5201383.2054 2020.5
+
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_NO
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.3671 948984.2412 5201383.5058 2020.5
+
+
+# -------------------------------------------------------------------------------
+# SWEDEN
+# -------------------------------------------------------------------------------
+
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_SE
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.9223 948983.7912 5201383.2025 2020.5
+
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_SE
+tolerance 1 mm
+
+accept 3541657.3778 948984.2343 5201383.5231 2020.5
+expect 3541657.3690 948984.2275 5201383.5030 2020.5
+
+# 2020 Transformations
+
+operation operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_SE
+tolerance 0.1 mm
+
+# ARJ0
+accept 2441774.9791 799268.3078 5818729.4941 2015.0
+expect 2441775.4338 799268.0336 5818729.1635 2015.0
+
+# BOD3
+accept 2391774.0738 615615.1324 5860966.0796 2015.0
+expect 2391774.5409 615614.8770 5860965.8078 2015.0
+
+# KIR0
+accept 2248123.0276 865686.7906 5886425.8928 2015.0
+expect 2248123.5028 865686.5301 5886425.5928 2015.0
+
+</gie-strict>
diff --git a/test/gie/peirce_q.gie b/test/gie/peirce_q.gie
index 3706e15a..6e3e5aa8 100644
--- a/test/gie/peirce_q.gie
+++ b/test/gie/peirce_q.gie
@@ -9,10 +9,10 @@ operation +proj=peirce_q +R=6370997
tolerance 1 mm
------------------------------------------------------------
accept -179.6126302052 -90.2440064745
-expect failure errno lat_or_lon_exceed_limit
+expect failure errno coord_transfm_invalid_coord
accept -169.8486692749 -89.5742075028
-expect failure errno tolerance_condition
+expect failure errno coord_transfm_outside_projection_domain
accept 0.8029310185 0.0770204080
expect 126271.599 -11800190.331
@@ -1026,159 +1026,159 @@ accept 30.5303411995 89.7622494101
expect 13429.693 -22771.512
accept 40.0593735791 90.6542378643
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 50.0020623503 91.1469954761
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 60.8522213793 91.6903429214
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 70.7535252886 92.4836618501
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 80.1051275124 92.9389937571
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 90.4124853570 93.5341249190
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 100.7024119502 93.5550590372
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 110.8115837786 93.7909664255
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 120.6013207533 93.8492870154
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 130.7349056594 94.3767135242
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 140.9588399299 94.8671896725
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 150.0262851842 95.3534968784
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 160.4280397748 95.5380581127
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 170.7651411492 95.6370520364
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.0448345846 96.2732937318
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -179.8257499571 89.6174371357
expect -129.372 42538.994
accept -169.2875892301 90.5843674190
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -159.8023916584 91.1545744228
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -149.1639625275 92.0870993705
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -139.8013904331 92.8786158281
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -129.1637359401 92.9919538772
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -119.8393027005 93.1739711428
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -109.3406602631 94.1639584700
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -99.0695417625 95.1551429476
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -89.0610247556 95.6178466298
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -79.4516859556 96.0205106534
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -69.8082104896 96.1879870093
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -59.8296737152 96.7698954045
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -49.8769972300 97.2097042960
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -39.9274760034 97.5295561660
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -29.8470989427 97.6781116960
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -19.4625703588 98.0419802761
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept -9.0210837473 98.0868081244
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 0.9799321756 98.5464017880
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 10.0429624590 98.7459055217
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 20.2792585617 99.0223555196
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 30.0602725080 100.0053623620
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 40.2383571525 100.9390147017
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 50.7977119707 101.0770157329
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 60.1464549470 101.4743886273
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 70.3854313326 101.6912083253
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 80.4259639014 102.1177916319
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 90.8059360471 102.5951916979
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 100.0616865688 102.7748252495
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 110.4583416539 103.6248728355
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 120.9751205255 103.7983620507
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 130.7919815186 104.0148850515
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 140.6325380833 104.1792511999
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 150.5737064232 105.0050682525
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 160.8571448065 105.0125232729
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 170.6832502669 105.0174505020
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
accept 180.7137917600 105.8174218935
-expect failure errno -14
+expect failure errno coord_transfm_invalid_coord
</gie-strict>
diff --git a/test/gie/tinshift.gie b/test/gie/tinshift.gie
index dff62693..f16c16bb 100644
--- a/test/gie/tinshift.gie
+++ b/test/gie/tinshift.gie
@@ -8,15 +8,15 @@ Test +proj=tinshift
# Missing +file
operation +proj=tinshift
-expect failure errno no_args
+expect failure errno invalid_op_missing_arg
# +file doesn't point to an existing file
operation +proj=tinshift +file=i_do_not_exist
-expect failure errno invalid_arg
+expect failure errno invalid_op_file_not_found_or_invalid
# Not a JSON file
operation +proj=tinshift +file=proj.ini
-expect failure errno invalid_arg
+expect failure errno invalid_op_file_not_found_or_invalid
# Tests on a file without explicit CRS
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 3924f47d..7c4c124a 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -121,6 +121,7 @@ add_executable(proj_test_cpp_api
test_metadata.cpp
test_io.cpp
test_operation.cpp
+ test_operationfactory.cpp
test_datum.cpp
test_factory.cpp
test_c_api.cpp
diff --git a/test/unit/Makefile.am b/test/unit/Makefile.am
index 4e931c2b..b073fcf2 100644
--- a/test/unit/Makefile.am
+++ b/test/unit/Makefile.am
@@ -45,7 +45,18 @@ proj_context_test_LDADD = ../../src/libproj.la @GTEST_LIBS@
proj_context_test-check: proj_context_test
PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES ./proj_context_test
-test_cpp_api_SOURCES = test_util.cpp test_common.cpp test_crs.cpp test_metadata.cpp test_io.cpp test_operation.cpp test_datum.cpp test_factory.cpp test_c_api.cpp test_grids.cpp main.cpp
+test_cpp_api_SOURCES = test_util.cpp \
+ test_common.cpp \
+ test_crs.cpp \
+ test_metadata.cpp \
+ test_io.cpp \
+ test_operation.cpp \
+ test_operationfactory.cpp \
+ test_datum.cpp \
+ test_factory.cpp \
+ test_c_api.cpp \
+ test_grids.cpp \
+ main.cpp
test_cpp_api_LDADD = ../../src/libproj.la @GTEST_LIBS@ @SQLITE3_LIBS@
test_cpp_api-check: test_cpp_api
diff --git a/test/unit/gie_self_tests.cpp b/test/unit/gie_self_tests.cpp
index f9252137..4244766e 100644
--- a/test/unit/gie_self_tests.cpp
+++ b/test/unit/gie_self_tests.cpp
@@ -46,9 +46,8 @@ TEST(gie, cart_selftest) {
PJ_CONTEXT *ctx;
PJ *P;
PJ_COORD a, b, obs[2];
- PJ_COORD coord[2];
+ PJ_COORD coord[3];
- int err;
size_t n, sz;
double dist, h, t;
const char *const args[3] = {"proj=utm", "zone=32", "ellps=GRS80"};
@@ -89,16 +88,6 @@ TEST(gie, cart_selftest) {
/* Clear any previous error */
proj_errno_reset(P);
- /* Invalid projection */
- a = proj_trans(P, static_cast<PJ_DIRECTION>(42), a);
- ASSERT_EQ(a.lpz.lam, HUGE_VAL);
-
- err = proj_errno(P);
- ASSERT_NE(err, 0);
-
- /* Clear error again */
- proj_errno_reset(P);
-
/* Clean up */
proj_destroy(P);
@@ -211,7 +200,7 @@ TEST(gie, cart_selftest) {
coord[0] = proj_coord(proj_torad(12), proj_torad(55), 45, 0);
coord[1] = proj_coord(proj_torad(12), proj_torad(56), 50, 0);
- ASSERT_FALSE(proj_trans_array(P, PJ_FWD, 2, coord));
+ ASSERT_EQ(proj_trans_array(P, PJ_FWD, 2, coord), 0);
ASSERT_EQ(a.lpz.lam, coord[0].lpz.lam);
ASSERT_EQ(a.lpz.phi, coord[0].lpz.phi);
@@ -220,6 +209,35 @@ TEST(gie, cart_selftest) {
ASSERT_EQ(b.lpz.phi, coord[1].lpz.phi);
ASSERT_EQ(b.lpz.z, coord[1].lpz.z);
+ /* test proj_trans_array () with two failed points for the same reason */
+
+ coord[0] =
+ proj_coord(proj_torad(12), proj_torad(95), 45, 0); // invalid latitude
+ coord[1] = proj_coord(proj_torad(12), proj_torad(56), 50, 0);
+ coord[2] =
+ proj_coord(proj_torad(12), proj_torad(95), 45, 0); // invalid latitude
+ ASSERT_EQ(proj_trans_array(P, PJ_FWD, 3, coord),
+ PROJ_ERR_COORD_TRANSFM_INVALID_COORD);
+
+ ASSERT_EQ(HUGE_VAL, coord[0].lpz.lam);
+ ASSERT_EQ(HUGE_VAL, coord[0].lpz.phi);
+ ASSERT_EQ(HUGE_VAL, coord[0].lpz.z);
+ ASSERT_EQ(b.lpz.lam, coord[1].lpz.lam);
+ ASSERT_EQ(b.lpz.phi, coord[1].lpz.phi);
+ ASSERT_EQ(b.lpz.z, coord[1].lpz.z);
+ ASSERT_EQ(HUGE_VAL, coord[2].lpz.lam);
+ ASSERT_EQ(HUGE_VAL, coord[2].lpz.phi);
+ ASSERT_EQ(HUGE_VAL, coord[2].lpz.z);
+
+ /* test proj_trans_array () with two failed points for different reasons */
+
+ coord[0] =
+ proj_coord(proj_torad(12), proj_torad(95), 45, 0); // invalid latitude
+ coord[1] =
+ proj_coord(proj_torad(105), proj_torad(0), 45,
+ 0); // in the equatorial axis, at 90° of the central meridian
+ ASSERT_EQ(proj_trans_array(P, PJ_FWD, 2, coord), PROJ_ERR_COORD_TRANSFM);
+
/* Clean up after proj_trans_* tests */
proj_destroy(P);
}
diff --git a/test/unit/proj_errno_string_test.cpp b/test/unit/proj_errno_string_test.cpp
index a592b31f..d58c5e3f 100644
--- a/test/unit/proj_errno_string_test.cpp
+++ b/test/unit/proj_errno_string_test.cpp
@@ -35,37 +35,26 @@
namespace {
-TEST(ProjErrnoStringTest, NoError) { EXPECT_EQ(0, proj_errno_string(0)); }
+TEST(ProjErrnoStringTest, NoError) { EXPECT_EQ(nullptr, proj_errno_string(0)); }
TEST(ProjErrnoStringTest, ProjErrnos) {
- EXPECT_STREQ("no arguments in initialization list", proj_errno_string(-1));
- EXPECT_STREQ("invalid projection system error (-1000)",
- proj_errno_string(-1000));
- EXPECT_STREQ("invalid projection system error (-9999)",
- proj_errno_string(-9999));
- // for errnos < -9999, -9999 is always returned
- const int min = std::numeric_limits<int>::min();
- EXPECT_STREQ("invalid projection system error (-9999)",
- proj_errno_string(min));
- EXPECT_STREQ("invalid projection system error (-9999)",
- proj_errno_string(-10000));
+ EXPECT_STREQ("Unknown error (code -1)", proj_errno_string(-1));
+ EXPECT_STREQ("Invalid PROJ string syntax",
+ proj_errno_string(PROJ_ERR_INVALID_OP_WRONG_SYNTAX));
+ EXPECT_STREQ(
+ "Unspecified error related to coordinate operation initialization",
+ proj_errno_string(PROJ_ERR_INVALID_OP));
+ EXPECT_STREQ("Unspecified error related to coordinate transformation",
+ proj_errno_string(PROJ_ERR_COORD_TRANSFM));
}
-TEST(ProjErrnoStringTest, SystemErrnos) {
- const int max = std::numeric_limits<int>::max();
-
-#ifdef HAVE_STRERROR
- EXPECT_STREQ(strerror(5), proj_errno_string(5));
- EXPECT_STREQ(strerror(9999), proj_errno_string(9999));
- EXPECT_STREQ(strerror(10000), proj_errno_string(10000));
- EXPECT_STREQ(strerror(max), proj_errno_string(max));
-#else
- EXPECT_STREQ("no system list, errno: 5\n", proj_errno_string(5));
- EXPECT_STREQ("no system list, errno: 9999\n", proj_errno_string(9999));
- // for errnos > 9999, 9999 is always returned
- EXPECT_STREQ("no system list, errno: 9999\n", proj_errno_string(10000));
- EXPECT_STREQ("no system list, errno: 9999\n", proj_errno_string(max));
-#endif
+TEST(ProjErrnoStringTest, proj_context_errno_string) {
+ EXPECT_STREQ("Unknown error (code -1)",
+ proj_context_errno_string(nullptr, -1));
+ PJ_CONTEXT *ctx = proj_context_create();
+ EXPECT_STREQ("Unknown error (code -999)",
+ proj_context_errno_string(ctx, -999));
+ proj_context_destroy(ctx);
}
} // namespace
diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp
index c417371d..d43d68ce 100644
--- a/test/unit/test_c_api.cpp
+++ b/test/unit/test_c_api.cpp
@@ -1183,10 +1183,12 @@ TEST_F(CApi, proj_get_authorities_from_database) {
ASSERT_TRUE(list[2] != nullptr);
EXPECT_EQ(list[2], std::string("IGNF"));
ASSERT_TRUE(list[3] != nullptr);
- EXPECT_EQ(list[3], std::string("OGC"));
+ EXPECT_EQ(list[3], std::string("NKG"));
ASSERT_TRUE(list[4] != nullptr);
- EXPECT_EQ(list[4], std::string("PROJ"));
- EXPECT_EQ(list[5], nullptr);
+ EXPECT_EQ(list[4], std::string("OGC"));
+ ASSERT_TRUE(list[5] != nullptr);
+ EXPECT_EQ(list[5], std::string("PROJ"));
+ EXPECT_EQ(list[6], nullptr);
}
// ---------------------------------------------------------------------------
@@ -4196,6 +4198,64 @@ TEST_F(CApi, proj_create_crs_to_crs_from_pj) {
// ---------------------------------------------------------------------------
+TEST_F(CApi, proj_create_crs_to_crs_from_pj_accuracy_filter) {
+
+ auto src = proj_create(m_ctxt, "EPSG:4326"); // WGS 84
+ ObjectKeeper keeper_src(src);
+ ASSERT_NE(src, nullptr);
+
+ auto dst = proj_create(m_ctxt, "EPSG:4258"); // ETRS89
+ ObjectKeeper keeper_dst(dst);
+ ASSERT_NE(dst, nullptr);
+
+ // No options
+ {
+ auto P =
+ proj_create_crs_to_crs_from_pj(m_ctxt, src, dst, nullptr, nullptr);
+ ObjectKeeper keeper_P(P);
+ ASSERT_NE(P, nullptr);
+ }
+
+ {
+ const char *const options[] = {"ACCURACY=0.05", nullptr};
+ auto P =
+ proj_create_crs_to_crs_from_pj(m_ctxt, src, dst, nullptr, options);
+ ObjectKeeper keeper_P(P);
+ ASSERT_EQ(P, nullptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_create_crs_to_crs_from_pj_ballpark_filter) {
+
+ auto src = proj_create(m_ctxt, "EPSG:4267"); // NAD 27
+ ObjectKeeper keeper_src(src);
+ ASSERT_NE(src, nullptr);
+
+ auto dst = proj_create(m_ctxt, "EPSG:4258"); // ETRS89
+ ObjectKeeper keeper_dst(dst);
+ ASSERT_NE(dst, nullptr);
+
+ // No options
+ {
+ auto P =
+ proj_create_crs_to_crs_from_pj(m_ctxt, src, dst, nullptr, nullptr);
+ ObjectKeeper keeper_P(P);
+ ASSERT_NE(P, nullptr);
+ }
+
+ {
+ const char *const options[] = {"ALLOW_BALLPARK=NO", nullptr};
+ auto P =
+ proj_create_crs_to_crs_from_pj(m_ctxt, src, dst, nullptr, options);
+ ObjectKeeper keeper_P(P);
+ ASSERT_EQ(P, nullptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
static void
check_axis_is_latitude(PJ_CONTEXT *ctx, PJ *cs, int axis_number,
const char *unit_name = "degree",
diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp
index ff86c4d3..7f474dcf 100644
--- a/test/unit/test_factory.cpp
+++ b/test/unit/test_factory.cpp
@@ -2806,6 +2806,7 @@ TEST(factory, attachExtraDatabases_none) {
auto factory = AuthorityFactory::create(ctxt, "EPSG");
auto crs = factory->createGeodeticCRS("4979");
auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ EXPECT_TRUE(gcrs != nullptr);
}
// ---------------------------------------------------------------------------
@@ -2877,12 +2878,14 @@ TEST(factory, attachExtraDatabases_auxiliary) {
auto factory = AuthorityFactory::create(ctxt, "EPSG");
auto crs = factory->createGeodeticCRS("4326");
auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ EXPECT_TRUE(gcrs != nullptr);
}
// Look for object located in auxiliary DB
{
auto factory = AuthorityFactory::create(ctxt, "OTHER");
auto crs = factory->createGeodeticCRS("OTHER_4326");
auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ EXPECT_TRUE(gcrs != nullptr);
}
}
@@ -2894,12 +2897,14 @@ TEST(factory, attachExtraDatabases_auxiliary) {
auto factory = AuthorityFactory::create(ctxt, "EPSG");
auto crs = factory->createGeodeticCRS("4326");
auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ EXPECT_TRUE(gcrs != nullptr);
}
// Look for object located in auxiliary DB
{
auto factory = AuthorityFactory::create(ctxt, "OTHER");
auto crs = factory->createGeodeticCRS("OTHER_4326");
auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ EXPECT_TRUE(gcrs != nullptr);
}
}
@@ -2910,6 +2915,7 @@ TEST(factory, attachExtraDatabases_auxiliary) {
auto factory = AuthorityFactory::create(ctxt, "EPSG");
auto crs = factory->createGeodeticCRS("4326");
auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ EXPECT_TRUE(gcrs != nullptr);
}
// Look for object located in auxiliary DB
{
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
index d0085004..422ab97d 100644
--- a/test/unit/test_operation.cpp
+++ b/test/unit/test_operation.cpp
@@ -28,8 +28,6 @@
#include "gtest_include.h"
-#include "test_primitives.hpp"
-
// to be able to use internal::replaceAll
#ifndef FROM_PROJ_CPP
#define FROM_PROJ_CPP
@@ -4285,5819 +4283,6 @@ TEST(operation, PROJ_based_with_global_parameters) {
// ---------------------------------------------------------------------------
-TEST(operation, geogCRS_to_geogCRS) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(
- op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
- "+ellps=clrk80ign +pm=paris +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_default) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- ctxt->setAllowUseIntermediateCRS(
- CoordinateOperationContext::IntermediateCRSUse::NEVER);
-
- // Directly found in database
- {
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4179"), // Pulkovo 42
- authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
- ctxt);
- ASSERT_EQ(list.size(), 3U);
- // Romania has a larger area than Poland (given our approx formula)
- EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
- EXPECT_EQ(list[1]->getEPSGCode(), 1644); // Poland - 1m
- EXPECT_EQ(list[2]->nameStr(),
- "Ballpark geographic offset from Pulkovo 1942(58) to ETRS89");
-
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
- "+step +proj=cart +ellps=krass +step +proj=helmert +x=2.3287 "
- "+y=-147.0425 +z=-92.0802 +rx=0.3092483 +ry=-0.32482185 "
- "+rz=-0.49729934 +s=5.68906266 +convention=coordinate_frame +step "
- "+inv +proj=cart +ellps=GRS80 +step +proj=pop +v_3 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
- }
-
- // Reverse case
- {
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4258"),
- authFactory->createCoordinateReferenceSystem("4179"), ctxt);
- ASSERT_EQ(list.size(), 3U);
- // Romania has a larger area than Poland (given our approx formula)
- EXPECT_EQ(list[0]->nameStr(),
- "Inverse of Pulkovo 1942(58) to ETRS89 (4)"); // Romania - 3m
-
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
- "+step +proj=cart +ellps=GRS80 +step +inv +proj=helmert +x=2.3287 "
- "+y=-147.0425 +z=-92.0802 +rx=0.3092483 +ry=-0.32482185 "
- "+rz=-0.49729934 +s=5.68906266 +convention=coordinate_frame +step "
- "+inv +proj=cart +ellps=krass +step +proj=pop +v_3 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_match_by_name) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- ctxt->setAllowUseIntermediateCRS(
- CoordinateOperationContext::IntermediateCRSUse::NEVER);
- auto NAD27 = GeographicCRS::create(
- PropertyMap().set(IdentifiedObject::NAME_KEY,
- GeographicCRS::EPSG_4267->nameStr()),
- GeographicCRS::EPSG_4267->datum(),
- GeographicCRS::EPSG_4267->datumEnsemble(),
- GeographicCRS::EPSG_4267->coordinateSystem());
- auto list = CoordinateOperationFactory::create()->createOperations(
- NAD27, GeographicCRS::EPSG_4326, ctxt);
- auto listInv = CoordinateOperationFactory::create()->createOperations(
- GeographicCRS::EPSG_4326, NAD27, ctxt);
- auto listRef = CoordinateOperationFactory::create()->createOperations(
- GeographicCRS::EPSG_4267, GeographicCRS::EPSG_4326, ctxt);
- EXPECT_EQ(list.size(), listRef.size());
- EXPECT_EQ(listInv.size(), listRef.size());
- EXPECT_GE(listRef.size(), 2U);
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_filter_accuracy) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 1.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4179"),
- authFactory->createCoordinateReferenceSystem("4258"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->getEPSGCode(), 1644); // Poland - 1m
- }
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.9);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4179"),
- authFactory->createCoordinateReferenceSystem("4258"), ctxt);
- ASSERT_EQ(list.size(), 0U);
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_filter_bbox) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- // INSERT INTO "area" VALUES('EPSG','1197','Romania','Romania - onshore and
- // offshore.',43.44,48.27,20.26,31.41,0);
- {
- auto ctxt = CoordinateOperationContext::create(
- authFactory, Extent::createFromBBOX(20.26, 43.44, 31.41, 48.27),
- 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4179"),
- authFactory->createCoordinateReferenceSystem("4258"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
- }
- {
- auto ctxt = CoordinateOperationContext::create(
- authFactory, Extent::createFromBBOX(20.26 + .1, 43.44 + .1,
- 31.41 - .1, 48.27 - .1),
- 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4179"),
- authFactory->createCoordinateReferenceSystem("4258"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
- }
- {
- auto ctxt = CoordinateOperationContext::create(
- authFactory, Extent::createFromBBOX(20.26 - .1, 43.44 - .1,
- 31.41 + .1, 48.27 + .1),
- 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4179"),
- authFactory->createCoordinateReferenceSystem("4258"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=noop");
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_incompatible_area) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4267"), // NAD27
- authFactory->createCoordinateReferenceSystem("4258"), // ETRS 89
- ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=noop");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_inverse_needed) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setUsePROJAlternativeGridNames(false);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4275"), // NTF
- authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
- ctxt);
- ASSERT_EQ(list.size(), 2U);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
- "+step +proj=cart +ellps=clrk80ign +step +proj=helmert +x=-168 "
- "+y=-60 +z=320 +step +inv +proj=cart +ellps=GRS80 +step +proj=pop "
- "+v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step "
- "+proj=axisswap +order=2,1");
- EXPECT_EQ(list[1]->exportToPROJString(
- PROJStringFormatter::create(
- PROJStringFormatter::Convention::PROJ_5,
- authFactory->databaseContext())
- .get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step "
- "+proj=hgridshift +grids=fr_ign_ntf_r93.tif +step "
- "+proj=unitconvert "
- "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
- }
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4275"), // NTF
- authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
- ctxt);
- ASSERT_EQ(list.size(), 2U);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step "
- "+proj=hgridshift +grids=fr_ign_ntf_r93.tif +step "
- "+proj=unitconvert "
- "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
- }
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
- authFactory->createCoordinateReferenceSystem("4275"), // NTF
- ctxt);
- ASSERT_EQ(list.size(), 2U);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv "
- "+proj=hgridshift +grids=fr_ign_ntf_r93.tif +step "
- "+proj=unitconvert "
- "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_ntv1_ntv2_ctable2) {
- 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 list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4267"), // NAD27
- authFactory->createCoordinateReferenceSystem("4269"), // NAD83
- ctxt);
- ASSERT_EQ(list.size(), 10U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
- "+grids=ca_nrc_ntv1_can.tif +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
- EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
- "+grids=ca_nrc_ntv2_0.tif +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
- EXPECT_EQ(list[2]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
- "+grids=us_noaa_conus.tif +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_NAD27_to_WGS84) {
- 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 list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4267"), // NAD27
- authFactory->createCoordinateReferenceSystem("4326"), // WGS84
- ctxt);
- ASSERT_EQ(list.size(), 79U);
- EXPECT_EQ(list[0]->nameStr(),
- "NAD27 to WGS 84 (33)"); // 1.0 m, Canada - NAD27
- EXPECT_EQ(list[1]->nameStr(),
- "NAD27 to WGS 84 (3)"); // 20.0 m, Canada - NAD27
- EXPECT_EQ(list[2]->nameStr(),
- "NAD27 to WGS 84 (79)"); // 5.0 m, USA - CONUS including EEZ
- EXPECT_EQ(list[3]->nameStr(),
- "NAD27 to WGS 84 (4)"); // 10.0 m, USA - CONUS - onshore
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_NAD27_to_WGS84_G1762) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), std::string());
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
-
- auto authFactoryEPSG =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto list = CoordinateOperationFactory::create()->createOperations(
- // NAD27
- authFactoryEPSG->createCoordinateReferenceSystem("4267"),
- // WGS84 (G1762)
- authFactoryEPSG->createCoordinateReferenceSystem("9057"), ctxt);
- ASSERT_GE(list.size(), 78U);
- EXPECT_EQ(list[0]->nameStr(),
- "NAD27 to WGS 84 (33) + WGS 84 to WGS 84 (G1762)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=hgridshift +grids=ca_nrc_ntv2_0.tif "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
- EXPECT_EQ(list[1]->nameStr(),
- "NAD27 to WGS 84 (3) + WGS 84 to WGS 84 (G1762)");
- EXPECT_EQ(list[2]->nameStr(),
- "NAD27 to WGS 84 (79) + WGS 84 to WGS 84 (G1762)");
- EXPECT_EQ(list[3]->nameStr(),
- "NAD27 to WGS 84 (4) + WGS 84 to WGS 84 (G1762)");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_WGS84_G1674_to_WGS84_G1762) {
- // Check that particular behavior with WGS 84 (Gxxx) related to
- // 'geodetic_datum_preferred_hub' table and custom no-op transformations
- // between WGS 84 and WGS 84 (Gxxx) doesn't affect direct transformations
- // to those realizations.
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), std::string());
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
-
- auto authFactoryEPSG =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto list = CoordinateOperationFactory::create()->createOperations(
- // WGS84 (G1674)
- authFactoryEPSG->createCoordinateReferenceSystem("9056"),
- // WGS84 (G1762)
- authFactoryEPSG->createCoordinateReferenceSystem("9057"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=cart +ellps=WGS84 "
- "+step +proj=helmert +x=-0.004 +y=0.003 +z=0.004 +rx=0.00027 "
- "+ry=-0.00027 +rz=0.00038 +s=-0.0069 "
- "+convention=coordinate_frame "
- "+step +inv +proj=cart +ellps=WGS84 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_EPSG_4240_Indian1975_to_EPSG_4326) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4240"), // Indian 1975
- authFactory->createCoordinateReferenceSystem("4326"), ctxt);
- ASSERT_EQ(list.size(), 3U);
-
- // Indian 1975 to WGS 84 (4), 3.0 m, Thailand - onshore
- EXPECT_EQ(list[0]->getEPSGCode(), 1812);
-
- // The following is the one we want to see. It has a lesser accuracy than
- // the above one and the same bbox, but the name of its area of use is
- // slightly different
- // Indian 1975 to WGS 84 (2), 5.0 m, Thailand - onshore and Gulf of Thailand
- EXPECT_EQ(list[1]->getEPSGCode(), 1304);
-
- // Indian 1975 to WGS 84 (3), 1.0 m, Thailand - Bongkot field
- EXPECT_EQ(list[2]->getEPSGCode(), 1537);
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_helmert_geog3D_crs) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4939"), // GDA94 3D
- authFactory->createCoordinateReferenceSystem("7843"), // GDA2020 3D
- ctxt);
- ASSERT_EQ(list.size(), 1U);
-
- // Check there is no push / pop of v_3
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
- "+step +proj=cart +ellps=GRS80 "
- "+step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 "
- "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 "
- "+convention=coordinate_frame "
- "+step +inv +proj=cart +ellps=GRS80 "
- "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_helmert_geocentric_3D) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- // GDA94 geocentric
- authFactory->createCoordinateReferenceSystem("4348"),
- // GDA2020 geocentric
- authFactory->createCoordinateReferenceSystem("7842"), ctxt);
- ASSERT_EQ(list.size(), 1U);
-
- // Check there is no push / pop of v_3
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 "
- "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 "
- "+convention=coordinate_frame");
- EXPECT_EQ(list[0]->inverse()->exportToPROJString(
- PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +inv +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 "
- "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 "
- "+convention=coordinate_frame");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_helmert_geog3D_to_geocentirc) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- // GDA94 3D
- authFactory->createCoordinateReferenceSystem("4939"),
- // GDA2020 geocentric
- authFactory->createCoordinateReferenceSystem("7842"), ctxt);
- ASSERT_EQ(list.size(), 1U);
-
- // Check there is no push / pop of v_3
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
- "+step +proj=cart +ellps=GRS80 "
- "+step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 "
- "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 "
- "+convention=coordinate_frame");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_invalid_EPSG_ID) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
- // EPSG:4656 is incorrect. Should be EPSG:8997
- auto obj = WKTParser().createFromWKT(
- "GEOGCS[\"ITRF2000\","
- "DATUM[\"International_Terrestrial_Reference_Frame_2000\","
- "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
- "AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6656\"]],"
- "PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.0174532925199433],"
- "AUTHORITY[\"EPSG\",\"4656\"]]");
- auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
- ASSERT_TRUE(crs != nullptr);
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(crs), GeographicCRS::EPSG_4326, ctxt);
- ASSERT_EQ(list.size(), 1U);
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_datum_ensemble) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
-
- auto dst_wkt =
- "GEOGCRS[\"unknown\","
- " ENSEMBLE[\"World Geodetic System 1984 ensemble\","
- " MEMBER[\"World Geodetic System 1984 (Transit)\","
- " ID[\"EPSG\",1166]],"
- " MEMBER[\"World Geodetic System 1984 (G730)\","
- " ID[\"EPSG\",1152]],"
- " MEMBER[\"World Geodetic System 1984 (G873)\","
- " ID[\"EPSG\",1153]],"
- " MEMBER[\"World Geodetic System 1984 (G1150)\","
- " ID[\"EPSG\",1154]],"
- " MEMBER[\"World Geodetic System 1984 (G1674)\","
- " ID[\"EPSG\",1155]],"
- " MEMBER[\"World Geodetic System 1984 (G1762)\","
- " ID[\"EPSG\",1156]],"
- " ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
- " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],"
- " ID[\"EPSG\",7030]],"
- " ENSEMBLEACCURACY[2]],"
- " PRIMEM[\"Greenwich\",0,"
- " ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],"
- " ID[\"EPSG\",8901]],"
- " CS[ellipsoidal,2,"
- " ID[\"EPSG\",6422]],"
- " AXIS[\"Geodetic latitude (Lat)\",north,"
- " ORDER[1]],"
- " AXIS[\"Geodetic longitude (Lon)\",east,"
- " ORDER[2]],"
- " ANGLEUNIT[\"degree (supplier to define representation)\","
- "0.0174532925199433,ID[\"EPSG\",9122]]]";
- auto dstObj = WKTParser().createFromWKT(dst_wkt);
- auto dstCRS = nn_dynamic_pointer_cast<CRS>(dstObj);
- ASSERT_TRUE(dstCRS != nullptr);
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
- NN_NO_CHECK(dstCRS), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(), "ETRS89 to WGS 84 (1)");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, vertCRS_to_geogCRS_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setUsePROJAlternativeGridNames(false);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem(
- "3855"), // EGM2008 height
- authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
- ctxt);
- ASSERT_EQ(list.size(), 3U);
- EXPECT_EQ(
- list[1]->exportToPROJString(
- PROJStringFormatter::create(
- PROJStringFormatter::Convention::PROJ_5,
- authFactory->databaseContext())
- .get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
- }
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem(
- "3855"), // EGM2008 height
- authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
- ctxt);
- ASSERT_EQ(list.size(), 3U);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
- }
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
- authFactory->createCoordinateReferenceSystem(
- "3855"), // EGM2008 height
- ctxt);
- ASSERT_EQ(list.size(), 2U);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +inv +proj=vgridshift +grids=us_nga_egm08_25.tif "
- "+multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
- }
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // NGVD29 depth (ftUS)
- authFactory->createCoordinateReferenceSystem("6359"),
- authFactory->createCoordinateReferenceSystem("4326"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=affine +s33=-0.304800609601219");
- }
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // NZVD2016 height
- authFactory->createCoordinateReferenceSystem("7839"),
- // NZGD2000
- authFactory->createCoordinateReferenceSystem("4959"), ctxt);
- ASSERT_EQ(list.size(), 2U);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=nz_linz_nzgeoid2016.tif "
- "+multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
- }
- {
- // Test actually the database where we derive records using the more
- // classic 'Geographic3D to GravityRelatedHeight' method from
- // records using EPSG:9635
- //'Geog3D to Geog2D+GravityRelatedHeight (US .gtx)' method
- auto ctxt = CoordinateOperationContext::create(
- AuthorityFactory::create(DatabaseContext::create(), std::string()),
- nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // Baltic 1957 height
- authFactory->createCoordinateReferenceSystem("8357"),
- // ETRS89
- authFactory->createCoordinateReferenceSystem("4937"), ctxt);
- ASSERT_EQ(list.size(), 2U);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift "
- "+grids=sk_gku_Slovakia_ETRS89h_to_Baltic1957.tif "
- "+multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geog3DCRS_to_geog2DCRS_plus_vertCRS_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // ETRS89 (3D)
- authFactory->createCoordinateReferenceSystem("4937"),
- // ETRS89 + Baltic 1957 height
- authFactory->createCoordinateReferenceSystem("8360"), ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +inv +proj=vgridshift "
- "+grids=sk_gku_Slovakia_ETRS89h_to_Baltic1957.tif +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-
- EXPECT_EQ(list[0]->inverse()->nameStr(),
- "Inverse of 'ETRS89 to ETRS89 + Baltic 1957 height (1)'");
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_noop) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->nameStr(), "Null geographic offset from WGS 84 to WGS 84");
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=noop");
- EXPECT_EQ(op->inverse()->nameStr(), op->nameStr());
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_longitude_rotation) {
-
- auto src = GeographicCRS::create(
- PropertyMap().set(IdentifiedObject::NAME_KEY, "A"),
- GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84,
- optional<std::string>(),
- PrimeMeridian::GREENWICH),
- EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
- auto dest = GeographicCRS::create(
- PropertyMap().set(IdentifiedObject::NAME_KEY, "B"),
- GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84,
- optional<std::string>(),
- PrimeMeridian::PARIS),
- EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
-
- auto op = CoordinateOperationFactory::create()->createOperation(src, dest);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=longlat "
- "+ellps=WGS84 +pm=paris +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
- EXPECT_EQ(op->inverse()->exportToWKT(WKTFormatter::create().get()),
- CoordinateOperationFactory::create()
- ->createOperation(dest, src)
- ->exportToWKT(WKTFormatter::create().get()));
- EXPECT_TRUE(
- op->inverse()->isEquivalentTo(CoordinateOperationFactory::create()
- ->createOperation(dest, src)
- .get()));
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_longitude_rotation_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris)
- authFactory->createCoordinateReferenceSystem("4275"), // NTF
- ctxt);
- ASSERT_EQ(list.size(), 2U);
- EXPECT_EQ(list[0]->nameStr(), "NTF (Paris) to NTF (1)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
- "+proj=longlat +ellps=clrk80ign +pm=paris +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
- EXPECT_EQ(list[1]->nameStr(), "NTF (Paris) to NTF (2)");
- EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
- "+proj=longlat +ellps=clrk80ign +pm=2.33720833333333 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_concatenated_operation) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setAllowUseIntermediateCRS(
- CoordinateOperationContext::IntermediateCRSUse::ALWAYS);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris)
- authFactory->createCoordinateReferenceSystem("4171"), // RGF93
- ctxt);
- ASSERT_EQ(list.size(), 4U);
-
- EXPECT_EQ(list[0]->nameStr(), "NTF (Paris) to RGF93 (1)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=grad +xy_out=rad "
- "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris "
- "+step +proj=push +v_3 "
- "+step +proj=cart +ellps=clrk80ign "
- "+step +proj=xyzgridshift +grids=fr_ign_gr3df97a.tif "
- "+grid_ref=output_crs +ellps=GRS80 "
- "+step +inv +proj=cart +ellps=GRS80 "
- "+step +proj=pop +v_3 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-
- EXPECT_EQ(list[1]->nameStr(), "NTF (Paris) to RGF93 (2)");
- EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
- "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=hgridshift "
- "+grids=fr_ign_ntf_r93.tif +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
-
- EXPECT_TRUE(nn_dynamic_pointer_cast<ConcatenatedOperation>(list[0]) !=
- nullptr);
- auto grids = list[0]->gridsNeeded(DatabaseContext::create(), false);
- EXPECT_EQ(grids.size(), 1U);
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_ED50_to_WGS72_no_NTF_intermediate) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4230"), // ED50
- authFactory->createCoordinateReferenceSystem("4322"), // WGS 72
- ctxt);
- ASSERT_GE(list.size(), 2U);
- // We should not use the ancient NTF as an intermediate when looking for
- // ED50 -> WGS 72 operations.
- for (const auto &op : list) {
- EXPECT_TRUE(op->nameStr().find("NTF") == std::string::npos)
- << op->nameStr();
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_context_same_grid_name) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4314"), // DHDN
- authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
- ctxt);
- ASSERT_TRUE(!list.empty());
- EXPECT_EQ(list[0]->nameStr(), "DHDN to ETRS89 (8)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
- "+grids=de_adv_BETA2007.tif +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_geographic_offset_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4120"), // NTF(Paris)
- authFactory->createCoordinateReferenceSystem("4121"), // NTF
- ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(), "Greek to GGRS87 (1)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
- "+dlat=-5.86 +dlon=0.28 +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_CH1903_to_CH1903plus_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setAllowUseIntermediateCRS(
- CoordinateOperationContext::IntermediateCRSUse::ALWAYS);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4149"), // CH1903
- authFactory->createCoordinateReferenceSystem("4150"), // CH1903+
- ctxt);
- ASSERT_TRUE(list.size() == 1U);
-
- EXPECT_EQ(list[0]->nameStr(), "CH1903 to CH1903+ (1)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=hgridshift +grids=ch_swisstopo_CHENyx06a.tif "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_init_IGNF_to_init_IGNF_context) {
-
- auto dbContext = DatabaseContext::create();
-
- auto sourceCRS_obj = PROJStringParser()
- .attachDatabaseContext(dbContext)
- .setUsePROJ4InitRules(true)
- .createFromPROJString("+init=IGNF:NTFG");
- auto sourceCRS = nn_dynamic_pointer_cast<CRS>(sourceCRS_obj);
- ASSERT_TRUE(sourceCRS != nullptr);
-
- auto targetCRS_obj = PROJStringParser()
- .attachDatabaseContext(dbContext)
- .setUsePROJ4InitRules(true)
- .createFromPROJString("+init=IGNF:RGF93G");
- auto targetCRS = nn_dynamic_pointer_cast<CRS>(targetCRS_obj);
- ASSERT_TRUE(targetCRS != nullptr);
-
- auto authFactory = AuthorityFactory::create(dbContext, std::string());
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_CHECK_ASSERT(sourceCRS), NN_CHECK_ASSERT(targetCRS), ctxt);
- ASSERT_EQ(list.size(), 2U);
-
- EXPECT_EQ(list[0]->nameStr(),
- "NOUVELLE TRIANGULATION DE LA FRANCE (NTF) vers RGF93 (ETRS89)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=hgridshift +grids=fr_ign_ntf_r93.tif +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geogCRS_3D) {
-
- auto geogcrs_m_obj = PROJStringParser().createFromPROJString(
- "+proj=longlat +vunits=m +type=crs");
- auto geogcrs_m = nn_dynamic_pointer_cast<CRS>(geogcrs_m_obj);
- ASSERT_TRUE(geogcrs_m != nullptr);
-
- auto geogcrs_ft_obj = PROJStringParser().createFromPROJString(
- "+proj=longlat +vunits=ft +type=crs");
- auto geogcrs_ft = nn_dynamic_pointer_cast<CRS>(geogcrs_ft_obj);
- ASSERT_TRUE(geogcrs_ft != nullptr);
-
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(geogcrs_m), NN_CHECK_ASSERT(geogcrs_ft));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=unitconvert +z_in=m +z_out=ft");
- }
-
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(geogcrs_ft), NN_CHECK_ASSERT(geogcrs_m));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=unitconvert +z_in=ft +z_out=m");
- }
-
- auto geogcrs_m_with_pm_obj = PROJStringParser().createFromPROJString(
- "+proj=longlat +pm=paris +vunits=m +type=crs");
- auto geogcrs_m_with_pm =
- nn_dynamic_pointer_cast<CRS>(geogcrs_m_with_pm_obj);
- ASSERT_TRUE(geogcrs_m_with_pm != nullptr);
-
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(geogcrs_m_with_pm), NN_CHECK_ASSERT(geogcrs_ft));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=unitconvert +xy_in=deg +z_in=m "
- "+xy_out=rad +z_out=m +step +inv +proj=longlat +ellps=WGS84 "
- "+pm=paris +step +proj=unitconvert +xy_in=rad +z_in=m "
- "+xy_out=deg +z_out=ft");
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_3D_lat_long_non_metre_to_geogCRS_longlat) {
-
- auto wkt = "GEOGCRS[\"my CRS\",\n"
- " DATUM[\"World Geodetic System 1984\",\n"
- " ELLIPSOID[\"WGS 84\",6378137,298.257223563],\n"
- " ID[\"EPSG\",6326]],\n"
- " CS[ellipsoidal,3],\n"
- " AXIS[\"latitude\",north,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
- " AXIS[\"longitude\",east,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
- " AXIS[\"ellipsoidal height\",up,\n"
- " LENGTHUNIT[\"my_vunit\",0.3]]]";
- auto srcCRS_obj = WKTParser().createFromWKT(wkt);
- auto srcCRS = nn_dynamic_pointer_cast<CRS>(srcCRS_obj);
- ASSERT_TRUE(srcCRS != nullptr);
-
- auto dstCRS_obj = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=WGS84 +type=crs");
- auto dstCRS = nn_dynamic_pointer_cast<CRS>(dstCRS_obj);
- ASSERT_TRUE(dstCRS != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(srcCRS), NN_CHECK_ASSERT(dstCRS));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +z_in=0.3 +z_out=m");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_without_id_to_geogCRS_3D_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto src =
- authFactory->createCoordinateReferenceSystem("4289"); // Amersfoort
- auto dst =
- authFactory->createCoordinateReferenceSystem("4937"); // ETRS89 3D
- auto list =
- CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
- ASSERT_GE(list.size(), 1U);
- auto wkt2 = "GEOGCRS[\"unnamed\",\n"
- " DATUM[\"Amersfoort\",\n"
- " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\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]],"
- " USAGE[\n"
- " SCOPE[\"unknown\"],\n"
- " AREA[\"Netherlands - onshore\"],\n"
- " BBOX[50.75,3.2,53.7,7.22]]]\n";
-
- auto obj = WKTParser().createFromWKT(wkt2);
- auto src_from_wkt2 = nn_dynamic_pointer_cast<CRS>(obj);
- ASSERT_TRUE(src_from_wkt2 != nullptr);
- auto list2 = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(src_from_wkt2), dst, ctxt);
- ASSERT_GE(list.size(), list2.size());
- for (size_t i = 0; i < list.size(); i++) {
- const auto &op = list[i];
- const auto &op2 = list2[i];
- EXPECT_TRUE(
- op->isEquivalentTo(op2.get(), IComparable::Criterion::EQUIVALENT))
- << op->nameStr() << " " << op2->nameStr();
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geocentricCRS_to_geogCRS_same_datum) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- createGeocentricDatumWGS84(), GeographicCRS::EPSG_4326);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geocentricCRS_to_geogCRS_different_datum) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- createGeocentricDatumWGS84(), GeographicCRS::EPSG_4269);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->nameStr(),
- "Ballpark geocentric translation from WGS 84 to NAD83 "
- "(geocentric) + Conversion from NAD83 "
- "(geocentric) to NAD83");
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=cart +ellps=GRS80 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geocentricCRS_different_datum) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- GeographicCRS::EPSG_4269, createGeocentricDatumWGS84());
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->nameStr(), "Conversion from NAD83 to NAD83 (geocentric) + "
- "Ballpark geocentric translation from NAD83 "
- "(geocentric) to WGS 84");
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
- "+ellps=GRS80");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geocentricCRS_to_geocentricCRS_same_noop) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- createGeocentricDatumWGS84(), createGeocentricDatumWGS84());
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->nameStr(),
- "Null geocentric translation from WGS 84 to WGS 84");
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=noop");
- EXPECT_EQ(op->inverse()->nameStr(), op->nameStr());
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geocentricCRS_to_geocentricCRS_different_ballpark) {
-
- PropertyMap propertiesCRS;
- propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
- .set(Identifier::CODE_KEY, 4328)
- .set(IdentifiedObject::NAME_KEY, "unknown");
- auto otherGeocentricCRS = GeodeticCRS::create(
- propertiesCRS, GeodeticReferenceFrame::EPSG_6269,
- CartesianCS::createGeocentric(UnitOfMeasure::METRE));
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- createGeocentricKM(), otherGeocentricCRS);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(
- op->nameStr(),
- "Ballpark geocentric translation from Based on WGS 84 to unknown");
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=unitconvert +xy_in=km +z_in=km +xy_out=m +z_out=m");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geocentricCRS_to_geogCRS_same_datum_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4326"),
- // WGS84 geocentric
- authFactory->createCoordinateReferenceSystem("4978"), ctxt);
- ASSERT_EQ(list.size(), 1U);
-
- EXPECT_EQ(list[0]->nameStr(),
- "Conversion from WGS 84 (geog2D) to WGS 84 (geocentric)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
- "+ellps=WGS84");
-
- EXPECT_EQ(list[0]->inverse()->nameStr(),
- "Conversion from WGS 84 (geocentric) to WGS 84 (geog2D)");
- EXPECT_EQ(list[0]->inverse()->exportToPROJString(
- PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geocentricCRS_to_geogCRS_same_datum_context_all_auth) {
- // This is to check we don't use OGC:CRS84 as a pivot
- auto authFactoryEPSG =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto authFactoryAll =
- AuthorityFactory::create(DatabaseContext::create(), std::string());
- auto ctxt =
- CoordinateOperationContext::create(authFactoryAll, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactoryEPSG->createCoordinateReferenceSystem("4326"),
- // WGS84 geocentric
- authFactoryEPSG->createCoordinateReferenceSystem("4978"), ctxt);
- ASSERT_EQ(list.size(), 1U);
-
- EXPECT_EQ(list[0]->nameStr(),
- "Conversion from WGS 84 (geog2D) to WGS 84 (geocentric)");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geocentricCRS_to_geocentricCRS_different_datum_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // ITRF2000 (geocentric)
- authFactory->createCoordinateReferenceSystem("4919"),
- // ITRF2005 (geocentric)
- authFactory->createCoordinateReferenceSystem("4896"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(), "ITRF2000 to ITRF2005 (1)");
- EXPECT_PRED_FORMAT2(
- ComparePROJString,
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=helmert +x=-0.0001 "
- "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
- "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
- "+t_epoch=2000 +convention=position_vector");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_geocentricCRS_same_datum_to_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // WGS84 geocentric
- authFactory->createCoordinateReferenceSystem("4978"),
- authFactory->createCoordinateReferenceSystem("4326"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(),
- "Conversion from WGS 84 (geocentric) to WGS 84 (geog2D)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- geog2D_to_geog3D_same_datum_but_with_potential_other_pivot_context) {
- // Check that when going from geog2D to geog3D of same datum, we don't
- // try to go through a WGS84 pivot...
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("5365"), // CR 05 2D
- authFactory->createCoordinateReferenceSystem("5364"), // CR 05 3D
- ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=noop");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- geogCRS_to_geogCRS_different_datum_though_geocentric_transform_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // ITRF2000 (geog3D)
- authFactory->createCoordinateReferenceSystem("7909"),
- // ITRF2005 (geog3D)
- authFactory->createCoordinateReferenceSystem("7910"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(),
- "Conversion from ITRF2000 (geog3D) to ITRF2000 (geocentric) + "
- "ITRF2000 to ITRF2005 (1) + "
- "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)");
- EXPECT_PRED_FORMAT2(
- ComparePROJString,
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
- "+step +proj=cart +ellps=GRS80 +step +proj=helmert +x=-0.0001 "
- "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
- "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
- "+t_epoch=2000 +convention=position_vector +step +inv "
- "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad "
- "+z_in=m +xy_out=deg +z_out=m +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_geocentricCRS_different_datum_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // ITRF2000 (geog3D)
- authFactory->createCoordinateReferenceSystem("7909"),
- // ITRF2005 (geocentric)
- authFactory->createCoordinateReferenceSystem("4896"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(),
- "Conversion from ITRF2000 (geog3D) to ITRF2000 (geocentric) + "
- "ITRF2000 to ITRF2005 (1)");
- EXPECT_PRED_FORMAT2(
- ComparePROJString,
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
- "+step +proj=cart +ellps=GRS80 +step +proj=helmert +x=-0.0001 "
- "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
- "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
- "+t_epoch=2000 +convention=position_vector");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geocentricCRS_to_geogCRS_different_datum_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // ITRF2000 (geocentric)
- authFactory->createCoordinateReferenceSystem("4919"),
- // ITRF2005 (geog3D)
- authFactory->createCoordinateReferenceSystem("7910"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(),
- "ITRF2000 to ITRF2005 (1) + "
- "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)");
- EXPECT_PRED_FORMAT2(
- ComparePROJString,
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=helmert +x=-0.0001 "
- "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
- "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
- "+t_epoch=2000 +convention=position_vector +step +inv "
- "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad "
- "+z_in=m +xy_out=deg +z_out=m +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, esri_projectedCRS_to_geogCRS_with_ITRF_intermediate_context) {
- auto dbContext = DatabaseContext::create();
- auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG");
- auto authFactoryESRI = AuthorityFactory::create(dbContext, "ESRI");
- auto ctxt =
- CoordinateOperationContext::create(authFactoryEPSG, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // NAD_1983_CORS96_StatePlane_North_Carolina_FIPS_3200_Ft_US (projected)
- authFactoryESRI->createCoordinateReferenceSystem("103501"),
- // ITRF2005 (geog3D)
- authFactoryEPSG->createCoordinateReferenceSystem("7910"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(),
- "Inverse of NAD_1983_CORS96_StatePlane_North_Carolina_"
- "FIPS_3200_Ft_US + "
- "Conversion from NAD83(CORS96) (geog2D) to NAD83(CORS96) "
- "(geocentric) + Inverse of ITRF2000 to NAD83(CORS96) (1) + "
- "ITRF2000 to ITRF2005 (1) + "
- "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)");
- EXPECT_PRED_FORMAT2(
- ComparePROJString,
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft "
- "+xy_out=m +step +inv +proj=lcc +lat_0=33.75 +lon_0=-79 "
- "+lat_1=34.3333333333333 +lat_2=36.1666666666667 "
- "+x_0=609601.219202438 +y_0=0 +ellps=GRS80 +step +proj=cart "
- "+ellps=GRS80 +step +inv +proj=helmert +x=0.9956 +y=-1.9013 "
- "+z=-0.5215 +rx=0.025915 +ry=0.009426 +rz=0.011599 +s=0.00062 "
- "+dx=0.0007 +dy=-0.0007 +dz=0.0005 +drx=6.7e-05 +dry=-0.000757 "
- "+drz=-5.1e-05 +ds=-0.00018 +t_epoch=1997 "
- "+convention=coordinate_frame +step +proj=helmert +x=-0.0001 "
- "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
- "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
- "+t_epoch=2000 +convention=position_vector +step +inv +proj=cart "
- "+ellps=GRS80 +step +proj=unitconvert +xy_in=rad +z_in=m "
- "+xy_out=deg +z_out=m +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-static ProjectedCRSNNPtr createUTM31_WGS84() {
- return ProjectedCRS::create(
- PropertyMap(), GeographicCRS::EPSG_4326,
- Conversion::createUTM(PropertyMap(), 31, true),
- CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
-}
-
-// ---------------------------------------------------------------------------
-
-static ProjectedCRSNNPtr createUTM32_WGS84() {
- return ProjectedCRS::create(
- PropertyMap(), GeographicCRS::EPSG_4326,
- Conversion::createUTM(PropertyMap(), 32, true),
- CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_projCRS) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- GeographicCRS::EPSG_4326, createUTM31_WGS84());
- ASSERT_TRUE(op != nullptr);
- EXPECT_TRUE(std::dynamic_pointer_cast<Conversion>(op) != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm "
- "+zone=31 +ellps=WGS84");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_longlat_to_geogCS_latlong) {
-
- auto sourceCRS = GeographicCRS::OGC_CRS84;
- auto targetCRS = GeographicCRS::EPSG_4326;
- auto op = CoordinateOperationFactory::create()->createOperation(sourceCRS,
- targetCRS);
- ASSERT_TRUE(op != nullptr);
- auto conv = std::dynamic_pointer_cast<Conversion>(op);
- ASSERT_TRUE(conv != nullptr);
- EXPECT_TRUE(op->sourceCRS() &&
- op->sourceCRS()->isEquivalentTo(sourceCRS.get()));
- EXPECT_TRUE(op->targetCRS() &&
- op->targetCRS()->isEquivalentTo(targetCRS.get()));
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=axisswap +order=2,1");
- auto convInverse = nn_dynamic_pointer_cast<Conversion>(conv->inverse());
- ASSERT_TRUE(convInverse != nullptr);
- EXPECT_TRUE(convInverse->sourceCRS() &&
- convInverse->sourceCRS()->isEquivalentTo(targetCRS.get()));
- EXPECT_TRUE(convInverse->targetCRS() &&
- convInverse->targetCRS()->isEquivalentTo(sourceCRS.get()));
- EXPECT_EQ(conv->method()->exportToWKT(WKTFormatter::create().get()),
- convInverse->method()->exportToWKT(WKTFormatter::create().get()));
- EXPECT_TRUE(conv->method()->isEquivalentTo(convInverse->method().get()));
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_longlat_to_geogCS_latlong_database) {
-
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), std::string());
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- AuthorityFactory::create(DatabaseContext::create(), "OGC")
- ->createCoordinateReferenceSystem("CRS84"),
- AuthorityFactory::create(DatabaseContext::create(), "EPSG")
- ->createCoordinateReferenceSystem("4326"),
- ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_longlat_to_projCRS) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- GeographicCRS::OGC_CRS84, createUTM31_WGS84());
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=utm +zone=31 +ellps=WGS84");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_different_from_baseCRS_to_projCRS) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- GeographicCRS::EPSG_4807, createUTM31_WGS84());
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(
- op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
- "+ellps=clrk80ign +pm=paris +step +proj=utm +zone=31 "
- "+ellps=WGS84");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- geogCRS_different_from_baseCRS_to_projCRS_context_compatible_area) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setAllowUseIntermediateCRS(
- CoordinateOperationContext::IntermediateCRSUse::ALWAYS);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris)
- authFactory->createCoordinateReferenceSystem("32631"), // UTM31 WGS84
- ctxt);
- ASSERT_EQ(list.size(), 4U);
- EXPECT_EQ(
- list[0]->nameStr(),
- "NTF (Paris) to NTF (1) + Inverse of WGS 84 to NTF (3) + UTM zone 31N");
- ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U);
- EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1");
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
- "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=hgridshift "
- "+grids=fr_ign_ntf_r93.tif +step +proj=utm +zone=31 +ellps=WGS84");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geocentricCRS_to_projCRS) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- createGeocentricDatumWGS84(), createUTM31_WGS84());
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
- "+proj=utm +zone=31 +ellps=WGS84");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_to_geogCRS) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- createUTM31_WGS84(), GeographicCRS::EPSG_4326);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_no_id_to_geogCRS_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto src = authFactory->createCoordinateReferenceSystem(
- "28992"); // Amersfoort / RD New
- auto dst =
- authFactory->createCoordinateReferenceSystem("4258"); // ETRS89 2D
- auto list =
- CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
- ASSERT_GE(list.size(), 1U);
- auto wkt2 =
- "PROJCRS[\"unknown\",\n"
- " BASEGEOGCRS[\"Amersfoort\",\n"
- " DATUM[\"Amersfoort\",\n"
- " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128]]],\n"
- " CONVERSION[\"unknown\",\n"
- " METHOD[\"Oblique Stereographic\"],\n"
- " PARAMETER[\"Latitude of natural origin\",52.1561605555556],\n"
- " PARAMETER[\"Longitude of natural origin\",5.38763888888889],\n"
- " PARAMETER[\"Scale factor at natural origin\",0.9999079],\n"
- " PARAMETER[\"False easting\",155000],\n"
- " PARAMETER[\"False northing\",463000]],\n"
- " CS[Cartesian,2],\n"
- " AXIS[\"(E)\",east],\n"
- " AXIS[\"(N)\",north],\n"
- " LENGTHUNIT[\"metre\",1],\n"
- " ID[\"EPSG\",28992]]";
- auto obj = WKTParser().createFromWKT(wkt2);
- auto src_from_wkt2 = nn_dynamic_pointer_cast<CRS>(obj);
- ASSERT_TRUE(src_from_wkt2 != nullptr);
- auto list2 = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(src_from_wkt2), dst, ctxt);
- ASSERT_GE(list.size(), list2.size() - 1);
- for (size_t i = 0; i < list.size(); i++) {
- const auto &op = list[i];
- const auto &op2 = list2[i];
- EXPECT_TRUE(
- op->isEquivalentTo(op2.get(), IComparable::Criterion::EQUIVALENT));
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_3D_to_geogCRS_3D_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto wkt = "PROJCRS[\"NAD83(HARN) / Oregon GIC Lambert (ft)\",\n"
- " BASEGEOGCRS[\"NAD83(HARN)\",\n"
- " DATUM[\"NAD83 (High Accuracy Reference Network)\",\n"
- " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
- " LENGTHUNIT[\"metre\",1]]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
- " ID[\"EPSG\",4957]],\n"
- " CONVERSION[\"unnamed\",\n"
- " METHOD[\"Lambert Conic Conformal (2SP)\",\n"
- " ID[\"EPSG\",9802]],\n"
- " PARAMETER[\"Latitude of false origin\",41.75,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8821]],\n"
- " PARAMETER[\"Longitude of false origin\",-120.5,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8822]],\n"
- " PARAMETER[\"Latitude of 1st standard parallel\",43,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8823]],\n"
- " PARAMETER[\"Latitude of 2nd standard parallel\",45.5,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8824]],\n"
- " PARAMETER[\"Easting at false origin\",1312335.958,\n"
- " LENGTHUNIT[\"foot\",0.3048],\n"
- " ID[\"EPSG\",8826]],\n"
- " PARAMETER[\"Northing at false origin\",0,\n"
- " LENGTHUNIT[\"foot\",0.3048],\n"
- " ID[\"EPSG\",8827]]],\n"
- " CS[Cartesian,3],\n"
- " AXIS[\"easting\",east,\n"
- " ORDER[1],\n"
- " LENGTHUNIT[\"foot\",0.3048]],\n"
- " AXIS[\"northing\",north,\n"
- " ORDER[2],\n"
- " LENGTHUNIT[\"foot\",0.3048]],\n"
- " AXIS[\"ellipsoidal height (h)\",up,\n"
- " ORDER[3],\n"
- " LENGTHUNIT[\"foot\",0.3048]]]";
- auto obj = WKTParser().createFromWKT(wkt);
- auto src = NN_CHECK_ASSERT(nn_dynamic_pointer_cast<CRS>(obj));
- auto dst = authFactory->createCoordinateReferenceSystem(
- "4957"); // NAD83(HARN) (3D)
- auto list =
- CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- // Check that z ft->m conversion is done (and just once)
- "+step +proj=unitconvert +xy_in=ft +z_in=ft +xy_out=m +z_out=m "
- "+step +inv +proj=lcc +lat_0=41.75 +lon_0=-120.5 +lat_1=43 "
- "+lat_2=45.5 +x_0=399999.9999984 +y_0=0 +ellps=GRS80 "
- "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m "
- "+step +proj=axisswap +order=2,1");
-}
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_3D_to_projCRS_2D_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto wkt =
- "PROJCRS[\"Projected 3d CRS\",\n"
- " BASEGEOGCRS[\"JGD2000\",\n"
- " DATUM[\"Japanese Geodetic Datum 2000\",\n"
- " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
- " LENGTHUNIT[\"metre\",1]]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
- " ID[\"EPSG\",4947]],\n" // the code is what triggered the bug
- " CONVERSION[\"Japan Plane Rectangular CS zone VII\",\n"
- " METHOD[\"Transverse Mercator\",\n"
- " ID[\"EPSG\",9807]],\n"
- " PARAMETER[\"Latitude of natural origin\",36,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8801]],\n"
- " PARAMETER[\"Longitude of natural origin\",137.166666666667,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8802]],\n"
- " PARAMETER[\"Scale factor at natural origin\",0.9999,\n"
- " SCALEUNIT[\"unity\",1],\n"
- " ID[\"EPSG\",8805]],\n"
- " PARAMETER[\"False easting\",0,\n"
- " LENGTHUNIT[\"metre\",1],\n"
- " ID[\"EPSG\",8806]],\n"
- " PARAMETER[\"False northing\",0,\n"
- " LENGTHUNIT[\"metre\",1],\n"
- " ID[\"EPSG\",8807]],\n"
- " ID[\"EPSG\",17807]],\n"
- " CS[Cartesian,3],\n"
- " AXIS[\"northing (X)\",north,\n"
- " ORDER[1],\n"
- " LENGTHUNIT[\"metre\",1,\n"
- " ID[\"EPSG\",9001]]],\n"
- " AXIS[\"easting (Y)\",east,\n"
- " ORDER[2],\n"
- " LENGTHUNIT[\"metre\",1,\n"
- " ID[\"EPSG\",9001]]],\n"
- " AXIS[\"ellipsoidal height (h)\",up,\n"
- " ORDER[3],\n"
- " LENGTHUNIT[\"metre\",1,\n"
- " ID[\"EPSG\",9001]]]]";
- auto obj = WKTParser().createFromWKT(wkt);
- auto src = NN_CHECK_ASSERT(nn_dynamic_pointer_cast<CRS>(obj));
- auto dst =
- authFactory->createCoordinateReferenceSystem("32653"); // WGS 84 UTM 53
- // We just want to check that we don't get inconsistent chaining exception
- auto list =
- CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
- ASSERT_GE(list.size(), 1U);
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_3D_to_projCRS_with_2D_geocentric_translation) {
-
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto src =
- authFactory->createCoordinateReferenceSystem("4979"); // WGS 84 3D
-
- // Azores Central 1948 / UTM zone 26N
- auto dst = authFactory->createCoordinateReferenceSystem("2189");
-
- auto list =
- CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
- "+step +proj=push +v_3 " // this is what we check. Due to the
- // target system being 2D only
- "+step +proj=cart +ellps=WGS84 "
- "+step +proj=helmert +x=104 +y=-167 +z=38 "
- "+step +inv +proj=cart +ellps=intl "
- "+step +proj=pop +v_3 " // this is what we check
- "+step +proj=utm +zone=26 +ellps=intl");
-
- auto listReverse =
- CoordinateOperationFactory::create()->createOperations(dst, src, ctxt);
- ASSERT_GE(listReverse.size(), 1U);
- EXPECT_EQ(
- listReverse[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +inv +proj=utm +zone=26 +ellps=intl "
- "+step +proj=push +v_3 " // this is what we check
- "+step +proj=cart +ellps=intl "
- "+step +proj=helmert +x=-104 +y=167 +z=-38 "
- "+step +inv +proj=cart +ellps=WGS84 "
- "+step +proj=pop +v_3 " // this is what we check
- "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_to_projCRS) {
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- createUTM31_WGS84(), createUTM32_WGS84());
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step "
- "+proj=utm +zone=32 +ellps=WGS84");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_to_projCRS_different_baseCRS) {
-
- auto utm32 = ProjectedCRS::create(
- PropertyMap(), GeographicCRS::EPSG_4807,
- Conversion::createUTM(PropertyMap(), 32, true),
- CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- createUTM31_WGS84(), utm32);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step "
- "+proj=utm +zone=32 +ellps=clrk80ign +pm=paris");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_to_projCRS_context_compatible_area) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("32634"), // UTM 34
- authFactory->createCoordinateReferenceSystem(
- "2171"), // Pulkovo 42 Poland I
- ctxt);
- ASSERT_EQ(list.size(), 2U);
- EXPECT_EQ(list[0]->nameStr(),
- "Inverse of UTM zone 34N + Inverse of Pulkovo 1942(58) to WGS 84 "
- "(1) + Poland zone I");
- ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U);
- EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_to_projCRS_context_compatible_area_bis) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem(
- "3844"), // Pulkovo 42 Stereo 70 (Romania)
- authFactory->createCoordinateReferenceSystem("32634"), // UTM 34
- ctxt);
- ASSERT_EQ(list.size(), 3U);
- EXPECT_EQ(list[0]->nameStr(), "Inverse of Stereo 70 + "
- "Pulkovo 1942(58) to WGS 84 "
- "(19) + UTM zone 34N");
- ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U);
- EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "3");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_to_projCRS_context_one_incompatible_area) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("32631"), // UTM 31
- authFactory->createCoordinateReferenceSystem(
- "2171"), // Pulkovo 42 Poland I
- ctxt);
- ASSERT_EQ(list.size(), 2U);
- EXPECT_EQ(list[0]->nameStr(),
- "Inverse of UTM zone 31N + Inverse of Pulkovo 1942(58) to WGS 84 "
- "(1) + Poland zone I");
- ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U);
- EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_to_projCRS_context_incompatible_areas) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("32631"), // UTM 31
- authFactory->createCoordinateReferenceSystem("32633"), // UTM 33
- ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(), "Inverse of UTM zone 31N + UTM zone 33N");
- ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U);
- EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "0");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_to_projCRS_context_incompatible_areas_ballpark) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("26711"), // UTM 11 NAD27
- authFactory->createCoordinateReferenceSystem(
- "3034"), // ETRS89 / LCC Europe
- ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_TRUE(list[0]->hasBallparkTransformation());
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- projCRS_to_projCRS_context_incompatible_areas_crs_extent_use_intersection) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSourceAndTargetCRSExtentUse(
- CoordinateOperationContext::SourceTargetCRSExtentUse::INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("26711"), // UTM 11 NAD27
- authFactory->createCoordinateReferenceSystem(
- "3034"), // ETRS89 / LCC Europe
- ctxt);
- ASSERT_GE(list.size(), 0U);
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_to_projCRS_north_pole_inverted_axis) {
-
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), std::string());
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- AuthorityFactory::create(DatabaseContext::create(), "EPSG")
- ->createCoordinateReferenceSystem("32661"),
- AuthorityFactory::create(DatabaseContext::create(), "EPSG")
- ->createCoordinateReferenceSystem("5041"),
- ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_to_projCRS_south_pole_inverted_axis) {
-
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), std::string());
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- AuthorityFactory::create(DatabaseContext::create(), "EPSG")
- ->createCoordinateReferenceSystem("32761"),
- AuthorityFactory::create(DatabaseContext::create(), "EPSG")
- ->createCoordinateReferenceSystem("5042"),
- ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_to_projCRS_through_geog3D) {
- // Check that when going from projCRS to projCRS, using
- // geog2D-->geog3D-->geog3D-->geog2D we do not have issues with
- // inconsistent CRS chaining, due to how we 'hack' a bit some intermediate
- // steps
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("5367"), // CR05 / CRTM05
- authFactory->createCoordinateReferenceSystem(
- "8908"), // CR-SIRGAS / CRTM05
- ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 "
- "+step +inv +proj=tmerc +lat_0=0 +lon_0=-84 +k=0.9999 "
- "+x_0=500000 +y_0=0 +ellps=WGS84 "
- "+step +proj=push +v_3 "
- "+step +proj=cart +ellps=WGS84 "
- "+step +proj=helmert +x=-0.16959 +y=0.35312 +z=0.51846 "
- "+rx=-0.03385 +ry=0.16325 +rz=-0.03446 +s=0.03693 "
- "+convention=position_vector "
- "+step +inv +proj=cart +ellps=GRS80 "
- "+step +proj=pop +v_3 "
- "+step +proj=tmerc +lat_0=0 +lon_0=-84 +k=0.9999 +x_0=500000 "
- "+y_0=0 +ellps=GRS80");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, transform_from_amersfoort_rd_new_to_epsg_4326) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem("28992"),
- authFactory->createCoordinateReferenceSystem("4326"), ctxt);
- ASSERT_EQ(list.size(), 2U);
- // The order matters: "Amersfoort to WGS 84 (4)" replaces "Amersfoort to WGS
- // 84 (3)"
- EXPECT_EQ(list[0]->nameStr(),
- "Inverse of RD New + Amersfoort to WGS 84 (4)");
- EXPECT_EQ(list[1]->nameStr(),
- "Inverse of RD New + Amersfoort to WGS 84 (3)");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_of_geogCRS_to_geogCRS) {
- auto boundCRS = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto op = CoordinateOperationFactory::create()->createOperation(
- boundCRS, GeographicCRS::EPSG_4326);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
- "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=push +v_3 "
- "+step +proj=cart +ellps=clrk80ign +step +proj=helmert +x=1 +y=2 "
- "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step "
- "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_of_geogCRS_to_geodCRS) {
- auto boundCRS = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto op = CoordinateOperationFactory::create()->createOperation(
- boundCRS, GeodeticCRS::EPSG_4978);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step "
- "+proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=grad +xy_out=rad "
- "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris "
- "+step +proj=cart +ellps=clrk80ign "
- "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
- "+convention=position_vector");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_of_geogCRS_to_geodCRS_not_related_to_hub) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto boundCRS = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- boundCRS,
- // ETRS89 geocentric
- authFactory->createCoordinateReferenceSystem("4936"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step "
- "+proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=grad +xy_out=rad "
- "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris "
- "+step +proj=push +v_3 "
- "+step +proj=cart +ellps=clrk80ign "
- "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
- "+convention=position_vector "
- "+step +inv +proj=cart +ellps=GRS80 "
- "+step +proj=pop +v_3 "
- "+step +proj=cart +ellps=GRS80");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_of_geogCRS_to_geogCRS_with_area) {
- auto boundCRS = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4267, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto op = CoordinateOperationFactory::create()->createOperation(
- boundCRS, authFactory->createCoordinateReferenceSystem("4326"));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
- "+step +proj=cart +ellps=clrk66 +step +proj=helmert +x=1 +y=2 "
- "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step "
- "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_of_geogCRS_to_unrelated_geogCRS) {
- auto boundCRS = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto op = CoordinateOperationFactory::create()->createOperation(
- boundCRS, GeographicCRS::EPSG_4269);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- CoordinateOperationFactory::create()
- ->createOperation(GeographicCRS::EPSG_4807,
- GeographicCRS::EPSG_4269)
- ->exportToPROJString(PROJStringFormatter::create().get()));
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, createOperation_boundCRS_identified_by_datum) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=WGS84 +type=crs");
- auto src = nn_dynamic_pointer_cast<GeographicCRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- auto objDest = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=32 +a=6378249.2 +b=6356515 "
- "+towgs84=-263.0,6.0,431.0 +no_defs +type=crs");
- auto dest = nn_dynamic_pointer_cast<BoundCRS>(objDest);
- ASSERT_TRUE(dest != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=push +v_3 +step +proj=cart +ellps=WGS84 +step "
- "+proj=helmert +x=263 +y=-6 +z=-431 +step +inv +proj=cart "
- "+ellps=clrk80ign +step +proj=pop +v_3 +step +proj=utm +zone=32 "
- "+ellps=clrk80ign");
-
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), std::string());
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_TRUE(list[0]->isEquivalentTo(op.get()));
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_of_clrk_66_geogCRS_to_nad83_geogCRS) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=latlong +ellps=clrk66 +nadgrids=ntv1_can.dat,conus +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- auto objDest = PROJStringParser().createFromPROJString(
- "+proj=latlong +datum=NAD83 +type=crs");
- auto dest = nn_dynamic_pointer_cast<CRS>(objDest);
- ASSERT_TRUE(dest != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=hgridshift +grids=ntv1_can.dat,conus "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_of_clrk_66_projCRS_to_nad83_geogCRS) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=17 +ellps=clrk66 +nadgrids=ntv1_can.dat,conus "
- "+type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- auto objDest = PROJStringParser().createFromPROJString(
- "+proj=latlong +datum=NAD83 +type=crs");
- auto dest = nn_dynamic_pointer_cast<CRS>(objDest);
- ASSERT_TRUE(dest != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=utm +zone=17 +ellps=clrk66 "
- "+step +proj=hgridshift +grids=ntv1_can.dat,conus "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_of_projCRS_to_geogCRS) {
- auto utm31 = ProjectedCRS::create(
- PropertyMap(), GeographicCRS::EPSG_4807,
- Conversion::createUTM(PropertyMap(), 31, true),
- CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
- auto boundCRS = BoundCRS::createFromTOWGS84(
- utm31, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto op = CoordinateOperationFactory::create()->createOperation(
- boundCRS, GeographicCRS::EPSG_4326);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=clrk80ign "
- "+pm=paris +step +proj=push +v_3 +step +proj=cart "
- "+ellps=clrk80ign +step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 "
- "+rz=6 +s=7 +convention=position_vector +step +inv +proj=cart "
- "+ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert "
- "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_of_geogCRS_to_projCRS) {
- auto boundCRS = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto utm31 = ProjectedCRS::create(
- PropertyMap(), GeographicCRS::EPSG_4326,
- Conversion::createUTM(PropertyMap(), 31, true),
- CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
- auto op =
- CoordinateOperationFactory::create()->createOperation(boundCRS, utm31);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
- "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=push +v_3 "
- "+step +proj=cart +ellps=clrk80ign +step +proj=helmert +x=1 +y=2 "
- "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step "
- "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step "
- "+proj=utm +zone=31 +ellps=WGS84");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_of_geogCRS_to_unrelated_geogCRS_context) {
- auto src = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- // ETRS89
- auto dst = authFactory->createCoordinateReferenceSystem("4258");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list =
- CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
- ASSERT_EQ(list.size(), 1U);
- // Check with it is a concatenated operation, since it doesn't particularly
- // show up in the PROJ string
- EXPECT_TRUE(dynamic_cast<ConcatenatedOperation *>(list[0].get()) !=
- nullptr);
- EXPECT_EQ(list[0]->nameStr(), "Transformation from NTF (Paris) to WGS84 + "
- "Inverse of ETRS89 to WGS 84 (1)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=grad +xy_out=rad "
- "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris "
- "+step +proj=push +v_3 +step +proj=cart +ellps=clrk80ign "
- "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
- "+convention=position_vector "
- "+step +inv +proj=cart +ellps=GRS80 +step +proj=pop +v_3 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geogCRS_to_boundCRS_of_geogCRS) {
- auto boundCRS = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto op = CoordinateOperationFactory::create()->createOperation(
- GeographicCRS::EPSG_4326, boundCRS);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
- "+step +proj=cart +ellps=WGS84 +step +inv +proj=helmert +x=1 "
- "+y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector "
- "+step +inv +proj=cart +ellps=clrk80ign +step +proj=pop +v_3 "
- "+step +proj=longlat +ellps=clrk80ign +pm=paris +step "
- "+proj=unitconvert +xy_in=rad +xy_out=grad +step +proj=axisswap "
- "+order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_to_geogCRS_same_datum_context) {
- auto boundCRS = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4269, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto dbContext = DatabaseContext::create();
- auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- boundCRS, GeographicCRS::EPSG_4269, ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=noop");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_to_geogCRS_hubCRS_and_targetCRS_same_but_baseCRS_not) {
- const char *wkt =
- "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " TOWGS84[0,0,0,0,0,0,0],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AUTHORITY[\"EPSG\",\"4269\"]],\n"
- " VERT_CS[\"Ellipsoid (US Feet)\",\n"
- " VERT_DATUM[\"Ellipsoid\",2002],\n"
- " UNIT[\"US survey foot\",0.304800609601219,\n"
- " AUTHORITY[\"EPSG\",\"9003\"]],\n"
- " AXIS[\"Up\",UP]]]";
-
- auto dbContext = DatabaseContext::create();
- auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt);
- auto boundCRS = nn_dynamic_pointer_cast<BoundCRS>(obj);
- ASSERT_TRUE(boundCRS != nullptr);
- auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(boundCRS), GeographicCRS::EPSG_4979, ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=unitconvert +z_in=us-ft +z_out=m");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_to_boundCRS) {
- auto utm31 = ProjectedCRS::create(
- PropertyMap(), GeographicCRS::EPSG_4807,
- Conversion::createUTM(PropertyMap(), 31, true),
- CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
- auto utm32 = ProjectedCRS::create(
- PropertyMap(), GeographicCRS::EPSG_4269,
- Conversion::createUTM(PropertyMap(), 32, true),
- CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
- auto boundCRS1 = BoundCRS::createFromTOWGS84(
- utm31, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto boundCRS2 = BoundCRS::createFromTOWGS84(
- utm32, std::vector<double>{8, 9, 10, 11, 12, 13, 14});
- auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1,
- boundCRS2);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=clrk80ign "
- "+pm=paris +step +proj=push +v_3 +step +proj=cart "
- "+ellps=clrk80ign +step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 "
- "+rz=6 +s=7 +convention=position_vector +step +inv +proj=helmert "
- "+x=8 +y=9 +z=10 +rx=11 +ry=12 +rz=13 +s=14 "
- "+convention=position_vector +step +inv +proj=cart +ellps=GRS80 "
- "+step +proj=pop +v_3 +step +proj=utm +zone=32 +ellps=GRS80");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_to_boundCRS_noop_for_TOWGS84) {
- auto boundCRS1 = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto boundCRS2 = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4269, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1,
- boundCRS2);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
- "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=push +v_3 "
- "+step +proj=cart +ellps=clrk80ign +step +inv +proj=cart "
- "+ellps=GRS80 +step +proj=pop +v_3 +step +proj=unitconvert "
- "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_to_boundCRS_unralated_hub) {
- auto boundCRS1 = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto boundCRS2 = BoundCRS::create(
- GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4979,
- Transformation::createGeocentricTranslations(
- PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4979,
- 1.0, 2.0, 3.0, std::vector<PositionalAccuracyNNPtr>()));
- auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1,
- boundCRS2);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- CoordinateOperationFactory::create()
- ->createOperation(boundCRS1->baseCRS(), boundCRS2->baseCRS())
- ->exportToPROJString(PROJStringFormatter::create().get()));
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_of_projCRS_towgs84_to_boundCRS_of_projCRS_nadgrids) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=15 +datum=NAD83 +units=m +no_defs +ellps=GRS80 "
- "+towgs84=0,0,0 +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=15 +datum=NAD27 +units=m +no_defs +ellps=clrk66 "
- "+nadgrids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat +type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=utm +zone=15 +ellps=GRS80 +step "
- "+inv +proj=hgridshift "
- "+grids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat +step +proj=utm "
- "+zone=15 +ellps=clrk66");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_with_basecrs_with_extent_to_geogCRS) {
-
- auto wkt =
- "BOUNDCRS[\n"
- " SOURCECRS[\n"
- " PROJCRS[\"NAD83 / California zone 3 (ftUS)\",\n"
- " BASEGEODCRS[\"NAD83\",\n"
- " DATUM[\"North American Datum 1983\",\n"
- " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
- " LENGTHUNIT[\"metre\",1]]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
- " CONVERSION[\"SPCS83 California zone 3 (US Survey "
- "feet)\",\n"
- " METHOD[\"Lambert Conic Conformal (2SP)\",\n"
- " ID[\"EPSG\",9802]],\n"
- " PARAMETER[\"Latitude of false origin\",36.5,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8821]],\n"
- " PARAMETER[\"Longitude of false origin\",-120.5,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8822]],\n"
- " PARAMETER[\"Latitude of 1st standard parallel\","
- " 38.4333333333333,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8823]],\n"
- " PARAMETER[\"Latitude of 2nd standard parallel\","
- " 37.0666666666667,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8824]],\n"
- " PARAMETER[\"Easting at false origin\",6561666.667,\n"
- " LENGTHUNIT[\"US survey foot\","
- " 0.304800609601219],\n"
- " ID[\"EPSG\",8826]],\n"
- " PARAMETER[\"Northing at false origin\",1640416.667,\n"
- " LENGTHUNIT[\"US survey foot\","
- " 0.304800609601219],\n"
- " ID[\"EPSG\",8827]]],\n"
- " CS[Cartesian,2],\n"
- " AXIS[\"easting (X)\",east,\n"
- " ORDER[1],\n"
- " LENGTHUNIT[\"US survey foot\","
- " 0.304800609601219]],\n"
- " AXIS[\"northing (Y)\",north,\n"
- " ORDER[2],\n"
- " LENGTHUNIT[\"US survey foot\","
- " 0.304800609601219]],\n"
- " SCOPE[\"unknown\"],\n"
- " AREA[\"USA - California - SPCS - 3\"],\n"
- " BBOX[36.73,-123.02,38.71,-117.83],\n"
- " ID[\"EPSG\",2227]]],\n"
- " TARGETCRS[\n"
- " GEODCRS[\"WGS 84\",\n"
- " DATUM[\"World Geodetic System 1984\",\n"
- " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
- " LENGTHUNIT[\"metre\",1]]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
- " CS[ellipsoidal,2],\n"
- " AXIS[\"latitude\",north,\n"
- " ORDER[1],\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
- " AXIS[\"longitude\",east,\n"
- " ORDER[2],\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
- " ID[\"EPSG\",4326]]],\n"
- " ABRIDGEDTRANSFORMATION[\"NAD83 to WGS 84 (1)\",\n"
- " METHOD[\"Geocentric translations (geog2D domain)\",\n"
- " ID[\"EPSG\",9603]],\n"
- " PARAMETER[\"X-axis translation\",0,\n"
- " ID[\"EPSG\",8605]],\n"
- " PARAMETER[\"Y-axis translation\",0,\n"
- " ID[\"EPSG\",8606]],\n"
- " PARAMETER[\"Z-axis translation\",0,\n"
- " ID[\"EPSG\",8607]],\n"
- " SCOPE[\"unknown\"],\n"
- " AREA[\"North America - Canada and USA (CONUS, Alaska "
- "mainland)\"],\n"
- " BBOX[23.81,-172.54,86.46,-47.74],\n"
- " ID[\"EPSG\",1188]]]";
- auto obj = WKTParser().createFromWKT(wkt);
- auto boundCRS = nn_dynamic_pointer_cast<BoundCRS>(obj);
- ASSERT_TRUE(boundCRS != nullptr);
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(boundCRS), GeographicCRS::EPSG_4326);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->nameStr(), "Inverse of SPCS83 California zone 3 (US Survey "
- "feet) + NAD83 to WGS 84 (1)");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, ETRS89_3D_to_proj_string_with_geoidgrids_nadgrids) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- // ETRS89 3D
- auto src = authFactory->createCoordinateReferenceSystem("4937");
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 "
- "+k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel "
- "+nadgrids=rdtrans2008.gsb +geoidgrids=naptrans2008.gtx +units=m "
- "+type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- src, NN_NO_CHECK(dst), ctxt);
- ASSERT_EQ(list.size(), 2U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +inv +proj=vgridshift +grids=naptrans2008.gtx "
- "+multiplier=1 "
- "+step +inv +proj=hgridshift +grids=rdtrans2008.gsb "
- "+step +proj=sterea +lat_0=52.1561605555556 "
- "+lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 "
- "+y_0=463000 +ellps=bessel");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, nadgrids_with_pm) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=tmerc +lat_0=39.66666666666666 +lon_0=1 +k=1 +x_0=200000 "
- "+y_0=300000 +ellps=intl +nadgrids=foo.gsb +pm=lisbon "
- "+units=m +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto dst = authFactory->createCoordinateReferenceSystem("4326");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(src), dst, ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +inv +proj=tmerc +lat_0=39.6666666666667 +lon_0=1 "
- "+k=1 +x_0=200000 +y_0=300000 +ellps=intl +pm=lisbon "
- // Check that there is no extra +step +proj=longlat +pm=lisbon
- "+step +proj=hgridshift +grids=foo.gsb "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-
- // ETRS89
- dst = authFactory->createCoordinateReferenceSystem("4258");
- list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(src), dst, ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +inv +proj=tmerc +lat_0=39.6666666666667 +lon_0=1 "
- "+k=1 +x_0=200000 +y_0=300000 +ellps=intl +pm=lisbon "
- // Check that there is no extra +step +proj=longlat +pm=lisbon
- "+step +proj=hgridshift +grids=foo.gsb "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-
- // From WKT BOUNDCRS
- auto formatter = WKTFormatter::create(WKTFormatter::Convention::WKT2_2019);
- auto src_wkt = src->exportToWKT(formatter.get());
- auto objFromWkt = WKTParser().createFromWKT(src_wkt);
- auto crsFromWkt = nn_dynamic_pointer_cast<BoundCRS>(objFromWkt);
- ASSERT_TRUE(crsFromWkt);
- list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(crsFromWkt), dst, ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +inv +proj=tmerc +lat_0=39.6666666666667 +lon_0=1 "
- "+k=1 +x_0=200000 +y_0=300000 +ellps=intl +pm=lisbon "
- // Check that there is no extra +step +proj=longlat +pm=lisbon
- "+step +proj=hgridshift +grids=foo.gsb "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, WGS84_G1762_to_compoundCRS_with_bound_vertCRS) {
- auto authFactoryEPSG =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- // WGS 84 (G1762) 3D
- auto src = authFactoryEPSG->createCoordinateReferenceSystem("7665");
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=NAD83 +geoidgrids=@foo.gtx +type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), std::string());
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- auto list = CoordinateOperationFactory::create()->createOperations(
- src, NN_NO_CHECK(dst), ctxt);
- ASSERT_GE(list.size(), 53U);
- EXPECT_EQ(list[0]->nameStr(),
- "Inverse of WGS 84 to WGS 84 (G1762) + "
- "Inverse of unknown to WGS84 ellipsoidal height + "
- "Inverse of NAD83 to WGS 84 (1) + "
- "axis order change (2D)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +inv +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_to_geogCRS) {
-
- auto compound = CompoundCRS::create(
- PropertyMap(),
- std::vector<CRSNNPtr>{GeographicCRS::EPSG_4326, createVerticalCRS()});
- auto op = CoordinateOperationFactory::create()->createOperation(
- compound, GeographicCRS::EPSG_4807);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- CoordinateOperationFactory::create()
- ->createOperation(GeographicCRS::EPSG_4326,
- GeographicCRS::EPSG_4807)
- ->exportToPROJString(PROJStringFormatter::create().get()));
-}
-
-// ---------------------------------------------------------------------------
-
-static BoundCRSNNPtr createBoundVerticalCRS() {
- auto vertCRS = createVerticalCRS();
- auto transformation =
- Transformation::createGravityRelatedHeightToGeographic3D(
- PropertyMap(), vertCRS, GeographicCRS::EPSG_4979, nullptr,
- "us_nga_egm08_25.tif", std::vector<PositionalAccuracyNNPtr>());
- return BoundCRS::create(vertCRS, GeographicCRS::EPSG_4979, transformation);
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, transformation_height_to_PROJ_string) {
- auto transf = createBoundVerticalCRS()->transformation();
- EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=us_nga_egm08_25.tif "
- "+multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-
- auto grids = transf->gridsNeeded(DatabaseContext::create(), false);
- ASSERT_EQ(grids.size(), 1U);
- auto gridDesc = *(grids.begin());
- EXPECT_EQ(gridDesc.shortName, "us_nga_egm08_25.tif");
- EXPECT_TRUE(gridDesc.packageName.empty());
- EXPECT_EQ(gridDesc.url, "https://cdn.proj.org/us_nga_egm08_25.tif");
- if (gridDesc.available) {
- EXPECT_TRUE(!gridDesc.fullName.empty()) << gridDesc.fullName;
- EXPECT_TRUE(gridDesc.fullName.find(gridDesc.shortName) !=
- std::string::npos)
- << gridDesc.fullName;
- } else {
- EXPECT_TRUE(gridDesc.fullName.empty()) << gridDesc.fullName;
- }
- EXPECT_EQ(gridDesc.directDownload, true);
- EXPECT_EQ(gridDesc.openLicense, true);
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, transformation_Geographic3D_to_GravityRelatedHeight_gtx) {
- auto wkt =
- "COORDINATEOPERATION[\"ETRS89 to NAP height (1)\",\n"
- " VERSION[\"RDNAP-Nld 2008\"],\n"
- " SOURCECRS[\n"
- " GEOGCRS[\"ETRS89\",\n"
- " DATUM[\"European Terrestrial Reference System 1989\",\n"
- " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
- " LENGTHUNIT[\"metre\",1]]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
- " CS[ellipsoidal,3],\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"
- " AXIS[\"ellipsoidal height (h)\",up,\n"
- " ORDER[3],\n"
- " LENGTHUNIT[\"metre\",1]],\n"
- " ID[\"EPSG\",4937]]],\n"
- " TARGETCRS[\n"
- " VERTCRS[\"NAP height\",\n"
- " VDATUM[\"Normaal Amsterdams Peil\"],\n"
- " CS[vertical,1],\n"
- " AXIS[\"gravity-related height (H)\",up,\n"
- " LENGTHUNIT[\"metre\",1]],\n"
- " ID[\"EPSG\",5709]]],\n"
- " METHOD[\"Geographic3D to GravityRelatedHeight (US .gtx)\",\n"
- " ID[\"EPSG\",9665]],\n"
- " PARAMETERFILE[\"Geoid (height correction) model "
- "file\",\"naptrans2008.gtx\"],\n"
- " OPERATIONACCURACY[0.01],\n"
- " USAGE[\n"
- " SCOPE[\"unknown\"],\n"
- " AREA[\"Netherlands - onshore\"],\n"
- " BBOX[50.75,3.2,53.7,7.22]],\n"
- " ID[\"EPSG\",7001]]";
- ;
- auto obj = WKTParser().createFromWKT(wkt);
- auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
- ASSERT_TRUE(transf != nullptr);
-
- // Check that we correctly inverse files in the case of
- // "Geographic3D to GravityRelatedHeight (US .gtx)"
- EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +inv +proj=vgridshift "
- "+grids=naptrans2008.gtx +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, transformation_ntv2_to_PROJ_string) {
- auto transformation = Transformation::createNTv2(
- PropertyMap(), GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326,
- "foo.gsb", std::vector<PositionalAccuracyNNPtr>());
- EXPECT_EQ(
- transformation->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=grad +xy_out=rad +step "
- "+proj=hgridshift +grids=foo.gsb +step +proj=unitconvert "
- "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, transformation_VERTCON_to_PROJ_string) {
- auto verticalCRS1 = createVerticalCRS();
-
- auto verticalCRS2 = VerticalCRS::create(
- PropertyMap(), VerticalReferenceFrame::create(PropertyMap()),
- VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
-
- // Use of this type of transformation is a bit of non-sense here
- // since it should normally be used with NGVD29 and NAVD88 for VerticalCRS,
- // and NAD27/NAD83 as horizontal CRS...
- auto vtransformation = Transformation::createVERTCON(
- PropertyMap(), verticalCRS1, verticalCRS2, "bla.gtx",
- std::vector<PositionalAccuracyNNPtr>());
- EXPECT_EQ(vtransformation->exportToPROJString(
- PROJStringFormatter::create().get()),
- "+proj=vgridshift +grids=bla.gtx +multiplier=0.001");
-}
-// ---------------------------------------------------------------------------
-
-TEST(operation, transformation_NZLVD_to_PROJ_string) {
- auto dbContext = DatabaseContext::create();
- auto factory = AuthorityFactory::create(dbContext, "EPSG");
- auto op = factory->createCoordinateOperation("7860", false);
- EXPECT_EQ(op->exportToPROJString(
- PROJStringFormatter::create(
- PROJStringFormatter::Convention::PROJ_5, dbContext)
- .get()),
- "+proj=vgridshift +grids=nz_linz_auckht1946-nzvd2016.tif "
- "+multiplier=1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, transformation_BEV_AT_to_PROJ_string) {
- auto dbContext = DatabaseContext::create();
- auto factory = AuthorityFactory::create(dbContext, "EPSG");
- auto op = factory->createCoordinateOperation("9275", false);
- EXPECT_EQ(op->exportToPROJString(
- PROJStringFormatter::create(
- PROJStringFormatter::Convention::PROJ_5, dbContext)
- .get()),
- "+proj=vgridshift +grids=at_bev_GV_Hoehengrid_V1.tif "
- "+multiplier=1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, transformation_longitude_rotation_to_PROJ_string) {
-
- auto src = GeographicCRS::create(
- PropertyMap(), GeodeticReferenceFrame::create(
- PropertyMap(), Ellipsoid::WGS84,
- optional<std::string>(), PrimeMeridian::GREENWICH),
- EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
- auto dest = GeographicCRS::create(
- PropertyMap(), GeodeticReferenceFrame::create(
- PropertyMap(), Ellipsoid::WGS84,
- optional<std::string>(), PrimeMeridian::PARIS),
- EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
- auto transformation = Transformation::createLongitudeRotation(
- PropertyMap(), src, dest, Angle(10));
- EXPECT_TRUE(transformation->validateParameters().empty());
- EXPECT_EQ(
- transformation->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv "
- "+proj=longlat +ellps=WGS84 +pm=10 +step +proj=unitconvert "
- "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
- EXPECT_EQ(transformation->inverse()->exportToPROJString(
- PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv "
- "+proj=longlat +ellps=WGS84 +pm=-10 +step +proj=unitconvert "
- "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, transformation_Geographic2D_offsets_to_PROJ_string) {
-
- auto transformation = Transformation::createGeographic2DOffsets(
- PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326,
- Angle(0.5), Angle(-1), {});
- EXPECT_TRUE(transformation->validateParameters().empty());
-
- EXPECT_EQ(
- transformation->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
- "+dlat=1800 +dlon=-3600 +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
- EXPECT_EQ(transformation->inverse()->exportToPROJString(
- PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
- "+dlat=-1800 +dlon=3600 +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, transformation_Geographic3D_offsets_to_PROJ_string) {
-
- auto transformation = Transformation::createGeographic3DOffsets(
- PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326,
- Angle(0.5), Angle(-1), Length(2), {});
- EXPECT_TRUE(transformation->validateParameters().empty());
-
- EXPECT_EQ(
- transformation->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
- "+dlat=1800 +dlon=-3600 +dh=2 +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
- EXPECT_EQ(transformation->inverse()->exportToPROJString(
- PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
- "+dlat=-1800 +dlon=3600 +dh=-2 +step +proj=unitconvert "
- "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- transformation_Geographic2D_with_height_offsets_to_PROJ_string) {
-
- auto transformation = Transformation::createGeographic2DWithHeightOffsets(
- PropertyMap(),
- CompoundCRS::create(PropertyMap(),
- {GeographicCRS::EPSG_4326, createVerticalCRS()}),
- GeographicCRS::EPSG_4326, Angle(0.5), Angle(-1), Length(2), {});
- EXPECT_TRUE(transformation->validateParameters().empty());
-
- EXPECT_EQ(
- transformation->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
- "+dlat=1800 +dlon=-3600 +dh=2 +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
- EXPECT_EQ(transformation->inverse()->exportToPROJString(
- PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
- "+dlat=-1800 +dlon=3600 +dh=-2 +step +proj=unitconvert "
- "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, transformation_vertical_offset_to_PROJ_string) {
-
- auto transformation = Transformation::createVerticalOffset(
- PropertyMap(), createVerticalCRS(), createVerticalCRS(), Length(1), {});
- EXPECT_TRUE(transformation->validateParameters().empty());
-
- EXPECT_EQ(
- transformation->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=geogoffset +dh=1");
- EXPECT_EQ(transformation->inverse()->exportToPROJString(
- PROJStringFormatter::create().get()),
- "+proj=geogoffset +dh=-1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_with_boundVerticalCRS_to_geogCRS) {
-
- auto compound = CompoundCRS::create(
- PropertyMap(), std::vector<CRSNNPtr>{GeographicCRS::EPSG_4326,
- createBoundVerticalCRS()});
- auto op = CoordinateOperationFactory::create()->createOperation(
- compound, GeographicCRS::EPSG_4979);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(
- op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift "
- "+grids=us_nga_egm08_25.tif +multiplier=1 +step +proj=unitconvert "
- "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_with_boundGeogCRS_to_geogCRS) {
-
- auto geogCRS = GeographicCRS::create(
- PropertyMap(), GeodeticReferenceFrame::create(
- PropertyMap(), Ellipsoid::WGS84,
- optional<std::string>(), PrimeMeridian::GREENWICH),
- EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
- auto horizBoundCRS = BoundCRS::createFromTOWGS84(
- geogCRS, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto compound = CompoundCRS::create(
- PropertyMap(),
- std::vector<CRSNNPtr>{horizBoundCRS, createVerticalCRS()});
- auto op = CoordinateOperationFactory::create()->createOperation(
- compound, GeographicCRS::EPSG_4979);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=push +v_3 "
- "+step +proj=cart +ellps=WGS84 "
- "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
- "+convention=position_vector "
- "+step +inv +proj=cart +ellps=WGS84 "
- "+step +proj=pop +v_3 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_with_boundGeogCRS_and_boundVerticalCRS_to_geogCRS) {
-
- auto horizBoundCRS = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto compound = CompoundCRS::create(
- PropertyMap(),
- std::vector<CRSNNPtr>{horizBoundCRS, createBoundVerticalCRS()});
- auto op = CoordinateOperationFactory::create()->createOperation(
- compound, GeographicCRS::EPSG_4979);
- ASSERT_TRUE(op != nullptr);
- // Not completely sure the order of horizontal and vertical operations
- // makes sense
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=grad +xy_out=rad "
- "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris "
- "+step +proj=push +v_3 "
- "+step +proj=cart +ellps=clrk80ign "
- "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
- "+convention=position_vector "
- "+step +inv +proj=cart +ellps=WGS84 "
- "+step +proj=pop +v_3 "
- "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-
- auto grids = op->gridsNeeded(DatabaseContext::create(), false);
- EXPECT_EQ(grids.size(), 1U);
-
- auto opInverse = CoordinateOperationFactory::create()->createOperation(
- GeographicCRS::EPSG_4979, compound);
- ASSERT_TRUE(opInverse != nullptr);
- EXPECT_TRUE(opInverse->inverse()->isEquivalentTo(op.get()));
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_with_boundProjCRS_and_boundVerticalCRS_to_geogCRS) {
-
- auto horizBoundCRS = BoundCRS::createFromTOWGS84(
- ProjectedCRS::create(
- PropertyMap(), GeographicCRS::EPSG_4807,
- Conversion::createUTM(PropertyMap(), 31, true),
- CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)),
- std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto compound = CompoundCRS::create(
- PropertyMap(),
- std::vector<CRSNNPtr>{horizBoundCRS, createBoundVerticalCRS()});
- auto op = CoordinateOperationFactory::create()->createOperation(
- compound, GeographicCRS::EPSG_4979);
- ASSERT_TRUE(op != nullptr);
- // Not completely sure the order of horizontal and vertical operations
- // makes sense
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +inv +proj=utm +zone=31 +ellps=clrk80ign +pm=paris "
- "+step +proj=push +v_3 "
- "+step +proj=cart +ellps=clrk80ign "
- "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
- "+convention=position_vector "
- "+step +inv +proj=cart +ellps=WGS84 "
- "+step +proj=pop +v_3 "
- "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-
- auto opInverse = CoordinateOperationFactory::create()->createOperation(
- GeographicCRS::EPSG_4979, compound);
- ASSERT_TRUE(opInverse != nullptr);
- EXPECT_TRUE(opInverse->inverse()->isEquivalentTo(op.get()));
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- compoundCRS_with_boundVerticalCRS_from_geoidgrids_with_m_to_geogCRS) {
-
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=WGS84 +geoidgrids=@foo.gtx +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_NO_CHECK(src), GeographicCRS::EPSG_4979);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->nameStr(), "axis order change (2D) + "
- "unknown to WGS84 ellipsoidal height");
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- compoundCRS_with_boundVerticalCRS_from_geoidgrids_with_ftus_to_geogCRS) {
-
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=WGS84 +geoidgrids=@foo.gtx +vunits=us-ft "
- "+type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_NO_CHECK(src), GeographicCRS::EPSG_4979);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->nameStr(), "axis order change (2D) + "
- "Transformation from unknown to unknown + "
- "unknown to WGS84 ellipsoidal height");
- EXPECT_EQ(
- op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad +z_out=m "
- "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- compoundCRS_with_boundProjCRS_with_ftus_and_boundVerticalCRS_to_geogCRS) {
-
- auto wkt =
- "COMPD_CS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet + "
- "NAVD88 height - Geoid12B (US Feet)\",\n"
- " PROJCS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " TOWGS84[0,0,0,0,0,0,0],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0],\n"
- " UNIT[\"Degree\",0.0174532925199433]],\n"
- " PROJECTION[\"Transverse_Mercator\"],\n"
- " PARAMETER[\"latitude_of_origin\",30],\n"
- " PARAMETER[\"central_meridian\",-87.5],\n"
- " PARAMETER[\"scale_factor\",0.999933333333333],\n"
- " PARAMETER[\"false_easting\",1968500],\n"
- " PARAMETER[\"false_northing\",0],\n"
- " UNIT[\"US survey foot\",0.304800609601219,\n"
- " AUTHORITY[\"EPSG\",\"9003\"]],\n"
- " AXIS[\"Easting\",EAST],\n"
- " AXIS[\"Northing\",NORTH],\n"
- " AUTHORITY[\"ESRI\",\"102630\"]],\n"
- " VERT_CS[\"NAVD88 height (ftUS)\",\n"
- " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
- " EXTENSION[\"PROJ4_GRIDS\",\"foo.gtx\"],\n"
- " AUTHORITY[\"EPSG\",\"5103\"]],\n"
- " UNIT[\"US survey foot\",0.304800609601219,\n"
- " AUTHORITY[\"EPSG\",\"9003\"]],\n"
- " AXIS[\"Gravity-related height\",UP],\n"
- " AUTHORITY[\"EPSG\",\"6360\"]]]";
-
- auto obj = WKTParser().createFromWKT(wkt);
- auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
- ASSERT_TRUE(crs != nullptr);
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_NO_CHECK(crs), GeographicCRS::EPSG_4979);
- ASSERT_TRUE(op != nullptr);
-
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=us-ft +xy_out=m "
- "+step +inv +proj=tmerc +lat_0=30 +lon_0=-87.5 "
- "+k=0.999933333333333 +x_0=600000 +y_0=0 +ellps=GRS80 "
- "+step +proj=unitconvert +z_in=us-ft +z_out=m "
- "+step +proj=vgridshift +grids=foo.gtx +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- compoundCRS_with_boundVerticalCRS_from_grids_to_geogCRS_with_ftus_ctxt) {
-
- auto dbContext = DatabaseContext::create();
-
- const char *wktSrc =
- "COMPD_CS[\"NAD83 + NAVD88 height - Geoid12B (Meters)\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AUTHORITY[\"EPSG\",\"4269\"]],\n"
- " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n"
- " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
- " EXTENSION[\"PROJ4_GRIDS\",\"@foo.gtx\"],\n"
- " AUTHORITY[\"EPSG\",\"5103\"]],\n"
- " UNIT[\"metre\",1.0,\n"
- " AUTHORITY[\"EPSG\",\"9001\"]],\n"
- " AXIS[\"Gravity-related height\",UP],\n"
- " AUTHORITY[\"EPSG\",\"5703\"]]]";
- auto objSrc =
- WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc);
- auto srcCRS = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
- ASSERT_TRUE(srcCRS != nullptr);
-
- const char *wktDst =
- "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AUTHORITY[\"EPSG\",\"4269\"]],\n"
- " VERT_CS[\"Ellipsoid (US Feet)\",\n"
- " VERT_DATUM[\"Ellipsoid\",2002],\n"
- " UNIT[\"US survey foot\",0.304800609601219,\n"
- " AUTHORITY[\"EPSG\",\"9003\"]],\n"
- " AXIS[\"Up\",UP]]]";
- auto objDst =
- WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktDst);
- auto dstCRS = nn_dynamic_pointer_cast<GeographicCRS>(objDst);
- ASSERT_TRUE(dstCRS != nullptr);
-
- auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +z_in=m "
- "+xy_out=deg +z_out=us-ft "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- compoundCRS_with_boundGeogCRS_boundVerticalCRS_from_grids_to_boundGeogCRS_with_ftus_ctxt) {
-
- // Variant of above but with TOWGS84 in source & target CRS
-
- auto dbContext = DatabaseContext::create();
-
- const char *wktSrc =
- "COMPD_CS[\"NAD83 + NAVD88 height - Geoid12B (Meters)\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " TOWGS84[0,0,0,0,0,0,0],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AUTHORITY[\"EPSG\",\"4269\"]],\n"
- " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n"
- " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
- " EXTENSION[\"PROJ4_GRIDS\",\"@foo.gtx\"],\n"
- " AUTHORITY[\"EPSG\",\"5103\"]],\n"
- " UNIT[\"metre\",1.0,\n"
- " AUTHORITY[\"EPSG\",\"9001\"]],\n"
- " AXIS[\"Gravity-related height\",UP],\n"
- " AUTHORITY[\"EPSG\",\"5703\"]]]";
- auto objSrc =
- WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc);
- auto srcCRS = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
- ASSERT_TRUE(srcCRS != nullptr);
-
- const char *wktDst =
- "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " TOWGS84[0,0,0,0,0,0,0],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AUTHORITY[\"EPSG\",\"4269\"]],\n"
- " VERT_CS[\"Ellipsoid (US Feet)\",\n"
- " VERT_DATUM[\"Ellipsoid\",2002],\n"
- " UNIT[\"US survey foot\",0.304800609601219,\n"
- " AUTHORITY[\"EPSG\",\"9003\"]],\n"
- " AXIS[\"Up\",UP]]]";
- auto objDst =
- WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktDst);
- auto dstCRS = nn_dynamic_pointer_cast<BoundCRS>(objDst);
- ASSERT_TRUE(dstCRS != nullptr);
-
- auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=rad +z_in=m "
- "+xy_out=deg +z_out=us-ft");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- compoundCRS_with_boundVerticalCRS_from_grids_to_boundGeogCRS_with_ftus_ctxt) {
-
- // Variant of above but with TOWGS84 in target CRS only
-
- auto dbContext = DatabaseContext::create();
-
- const char *wktSrc =
- "COMPD_CS[\"NAD83 + NAVD88 height - Geoid12B (Meters)\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AUTHORITY[\"EPSG\",\"4269\"]],\n"
- " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n"
- " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
- " EXTENSION[\"PROJ4_GRIDS\",\"@foo.gtx\"],\n"
- " AUTHORITY[\"EPSG\",\"5103\"]],\n"
- " UNIT[\"metre\",1.0,\n"
- " AUTHORITY[\"EPSG\",\"9001\"]],\n"
- " AXIS[\"Gravity-related height\",UP],\n"
- " AUTHORITY[\"EPSG\",\"5703\"]]]";
- auto objSrc =
- WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc);
- auto srcCRS = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
- ASSERT_TRUE(srcCRS != nullptr);
-
- const char *wktDst =
- "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " TOWGS84[0,0,0,0,0,0,0],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AUTHORITY[\"EPSG\",\"4269\"]],\n"
- " VERT_CS[\"Ellipsoid (US Feet)\",\n"
- " VERT_DATUM[\"Ellipsoid\",2002],\n"
- " UNIT[\"US survey foot\",0.304800609601219,\n"
- " AUTHORITY[\"EPSG\",\"9003\"]],\n"
- " AXIS[\"Up\",UP]]]";
- auto objDst =
- WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktDst);
- auto dstCRS = nn_dynamic_pointer_cast<BoundCRS>(objDst);
- ASSERT_TRUE(dstCRS != nullptr);
-
- auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +z_in=m "
- "+xy_out=deg +z_out=us-ft "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- compoundCRS_with_boundGeogCRS_and_geoid_to_geodCRS_NAD2011_ctxt) {
-
- auto dbContext = DatabaseContext::create();
-
- const char *wktSrc =
- "COMPD_CS[\"NAD83 / California zone 5 (ftUS) + "
- "NAVD88 height - Geoid12B (ftUS)\","
- " PROJCS[\"NAD83 / California zone 5 (ftUS)\","
- " GEOGCS[\"NAD83\","
- " DATUM[\"North_American_Datum_1983\","
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,"
- " AUTHORITY[\"EPSG\",\"7019\"]],"
- " TOWGS84[0,0,0,0,0,0,0],"
- " AUTHORITY[\"EPSG\",\"6269\"]],"
- " PRIMEM[\"Greenwich\",0,"
- " AUTHORITY[\"EPSG\",\"8901\"]],"
- " UNIT[\"degree\",0.0174532925199433,"
- " AUTHORITY[\"EPSG\",\"9122\"]],"
- " AUTHORITY[\"EPSG\",\"4269\"]],"
- " PROJECTION[\"Lambert_Conformal_Conic_2SP\"],"
- " PARAMETER[\"standard_parallel_1\",35.46666666666667],"
- " PARAMETER[\"standard_parallel_2\",34.03333333333333],"
- " PARAMETER[\"latitude_of_origin\",33.5],"
- " PARAMETER[\"central_meridian\",-118],"
- " PARAMETER[\"false_easting\",6561666.667],"
- " PARAMETER[\"false_northing\",1640416.667],"
- " UNIT[\"US survey foot\",0.3048006096012192,"
- " AUTHORITY[\"EPSG\",\"9003\"]],"
- " AXIS[\"X\",EAST],"
- " AXIS[\"Y\",NORTH],"
- " AUTHORITY[\"EPSG\",\"2229\"]],"
- "VERT_CS[\"NAVD88 height - Geoid12B (ftUS)\","
- " VERT_DATUM[\"North American Vertical Datum 1988\",2005,"
- " AUTHORITY[\"EPSG\",\"5103\"]],"
- " UNIT[\"US survey foot\",0.3048006096012192,"
- " AUTHORITY[\"EPSG\",\"9003\"]],"
- " AXIS[\"Gravity-related height\",UP],"
- " AUTHORITY[\"EPSG\",\"6360\"]]]";
- auto objSrc =
- WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc);
- auto srcCRS = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
- ASSERT_TRUE(srcCRS != nullptr);
-
- auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG");
- // NAD83(2011) geocentric
- auto dstCRS = authFactoryEPSG->createCoordinateReferenceSystem("6317");
-
- auto authFactory = AuthorityFactory::create(dbContext, std::string());
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(srcCRS), dstCRS, ctxt);
- bool found = false;
- for (const auto &op : list) {
- if (op->nameStr() ==
- "Inverse of unnamed + "
- "Transformation from NAD83 to WGS84 + "
- "Ballpark geographic offset from WGS 84 to NAD83(2011) + "
- "Transformation from NAVD88 height (ftUS) to NAVD88 height + "
- "Inverse of NAD83(2011) to NAVD88 height (1) + "
- "Conversion from NAD83(2011) (geog3D) to NAD83(2011) "
- "(geocentric)") {
- found = true;
- EXPECT_EQ(
- op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=us-ft +xy_out=m "
- "+step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 "
- "+lat_1=35.4666666666667 +lat_2=34.0333333333333 "
- "+x_0=2000000.0001016 +y_0=500000.0001016 +ellps=GRS80 "
- "+step +proj=unitconvert +z_in=us-ft +z_out=m "
- "+step +proj=vgridshift +grids=us_noaa_g2012bu0.tif "
- "+multiplier=1 "
- "+step +proj=cart +ellps=GRS80");
- }
- }
- EXPECT_TRUE(found);
- if (!found) {
- for (const auto &op : list) {
- std::cerr << op->nameStr() << std::endl;
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geocent_to_compoundCRS) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=geocent +datum=WGS84 +units=m +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
- "+type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step +inv "
- "+proj=vgridshift +grids=@foo.gtx +multiplier=1 +step +inv "
- "+proj=hgridshift +grids=@foo.gsb +step +proj=unitconvert "
- "+xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, geocent_to_compoundCRS_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- // WGS84 geocentric
- auto src = authFactory->createCoordinateReferenceSystem("4978");
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
- "+type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- src, NN_CHECK_ASSERT(dst), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step +inv "
- "+proj=vgridshift +grids=@foo.gtx +multiplier=1 +step +inv "
- "+proj=hgridshift +grids=@foo.gsb +step +proj=unitconvert "
- "+xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_to_compoundCRS) {
- auto compound1 = CompoundCRS::create(
- PropertyMap(),
- std::vector<CRSNNPtr>{createUTM31_WGS84(), createVerticalCRS()});
- auto compound2 = CompoundCRS::create(
- PropertyMap(),
- std::vector<CRSNNPtr>{createUTM32_WGS84(), createVerticalCRS()});
- auto op = CoordinateOperationFactory::create()->createOperation(compound1,
- compound2);
- ASSERT_TRUE(op != nullptr);
- auto opRef = CoordinateOperationFactory::create()->createOperation(
- createUTM31_WGS84(), createUTM32_WGS84());
- ASSERT_TRUE(opRef != nullptr);
- EXPECT_TRUE(op->isEquivalentTo(opRef.get()));
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_to_compoundCRS_with_vertical_transform) {
- auto verticalCRS1 = createVerticalCRS();
-
- auto verticalCRS2 = VerticalCRS::create(
- PropertyMap(), VerticalReferenceFrame::create(PropertyMap()),
- VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
-
- // Use of this type of transformation is a bit of non-sense here
- // since it should normally be used with NGVD29 and NAVD88 for VerticalCRS,
- // and NAD27/NAD83 as horizontal CRS...
- auto vtransformation = Transformation::createVERTCON(
- PropertyMap(), verticalCRS1, verticalCRS2, "bla.gtx",
- std::vector<PositionalAccuracyNNPtr>());
-
- auto compound1 = CompoundCRS::create(
- PropertyMap(),
- std::vector<CRSNNPtr>{
- ProjectedCRS::create(
- PropertyMap(), GeographicCRS::EPSG_4326,
- Conversion::createTransverseMercator(PropertyMap(), Angle(1),
- Angle(2), Scale(3),
- Length(4), Length(5)),
- CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)),
- BoundCRS::create(verticalCRS1, verticalCRS2, vtransformation)});
- auto compound2 = CompoundCRS::create(
- PropertyMap(),
- std::vector<CRSNNPtr>{createUTM32_WGS84(), verticalCRS2});
-
- auto op = CoordinateOperationFactory::create()->createOperation(compound1,
- compound2);
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=tmerc +lat_0=1 +lon_0=2 +k=3 "
- "+x_0=4 +y_0=5 +ellps=WGS84 +step "
- "+proj=vgridshift +grids=bla.gtx +multiplier=0.001 +step "
- "+proj=utm +zone=32 "
- "+ellps=WGS84");
- {
- auto formatter = PROJStringFormatter::create();
- formatter->setUseApproxTMerc(true);
- EXPECT_EQ(
- op->exportToPROJString(formatter.get()),
- "+proj=pipeline +step +inv +proj=tmerc +approx +lat_0=1 +lon_0=2 "
- "+k=3 +x_0=4 +y_0=5 +ellps=WGS84 +step "
- "+proj=vgridshift +grids=bla.gtx +multiplier=0.001 +step "
- "+proj=utm +approx +zone=32 "
- "+ellps=WGS84");
- }
- {
- auto formatter = PROJStringFormatter::create();
- formatter->setUseApproxTMerc(true);
- EXPECT_EQ(
- op->inverse()->exportToPROJString(formatter.get()),
- "+proj=pipeline +step +inv +proj=utm +approx +zone=32 +ellps=WGS84 "
- "+step +inv +proj=vgridshift +grids=bla.gtx "
- "+multiplier=0.001 +step +proj=tmerc +approx +lat_0=1 +lon_0=2 "
- "+k=3 +x_0=4 +y_0=5 +ellps=WGS84");
- }
-
- auto opInverse = CoordinateOperationFactory::create()->createOperation(
- compound2, compound1);
- ASSERT_TRUE(opInverse != nullptr);
- {
- auto formatter = PROJStringFormatter::create();
- auto formatter2 = PROJStringFormatter::create();
- EXPECT_EQ(opInverse->inverse()->exportToPROJString(formatter.get()),
- op->exportToPROJString(formatter2.get()));
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
- "+type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx "
- "+type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=hgridshift +grids=@foo.gsb "
- "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
- "+step +inv +proj=vgridshift +grids=@bar.gtx +multiplier=1 "
- "+step +inv +proj=hgridshift +grids=@bar.gsb "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_geoidgrids) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
- "+type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@foo.gtx "
- "+type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=hgridshift +grids=@foo.gsb "
- "+step +inv +proj=hgridshift +grids=@bar.gsb "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_geoidgrids_different_vunits) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
- "+type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@foo.gtx "
- "+vunits=us-ft +type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=hgridshift +grids=@foo.gsb "
- "+step +proj=unitconvert +z_in=m +z_out=us-ft "
- "+step +inv +proj=hgridshift +grids=@bar.gsb "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_nadgrids_same_geoidgrids) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
- "+type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS80 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
- "+type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=noop");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_towgs84_same_geoidgrids) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS67 +towgs84=0,0,0 +geoidgrids=@foo.gtx "
- "+type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS80 +towgs84=0,0,0 +geoidgrids=@foo.gtx "
- "+type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=push +v_3 "
- "+step +proj=cart +ellps=GRS67 "
- "+step +inv +proj=cart +ellps=GRS80 "
- "+step +proj=pop +v_3 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_WKT1_same_geoidgrids_context) {
- auto objSrc = WKTParser().createFromWKT(
- "COMPD_CS[\"NAD83 / Alabama West + NAVD88 height - Geoid12B "
- "(Meters)\",\n"
- " PROJCS[\"NAD83 / Alabama West\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " TOWGS84[0,0,0,0,0,0,0],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AUTHORITY[\"EPSG\",\"4269\"]],\n"
- " PROJECTION[\"Transverse_Mercator\"],\n"
- " PARAMETER[\"latitude_of_origin\",30],\n"
- " PARAMETER[\"central_meridian\",-87.5],\n"
- " PARAMETER[\"scale_factor\",0.999933333],\n"
- " PARAMETER[\"false_easting\",600000],\n"
- " PARAMETER[\"false_northing\",0],\n"
- " UNIT[\"metre\",1,\n"
- " AUTHORITY[\"EPSG\",\"9001\"]],\n"
- " AXIS[\"X\",EAST],\n"
- " AXIS[\"Y\",NORTH],\n"
- " AUTHORITY[\"EPSG\",\"26930\"]],\n"
- " VERT_CS[\"NAVD88 height\",\n"
- " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
- " "
- "EXTENSION[\"PROJ4_GRIDS\",\"g2012a_alaska.gtx,g2012a_hawaii.gtx,"
- "g2012a_conus.gtx\"],\n"
- " AUTHORITY[\"EPSG\",\"5103\"]],\n"
- " UNIT[\"metre\",1,\n"
- " AUTHORITY[\"EPSG\",\"9001\"]],\n"
- " AXIS[\"Gravity-related height\",UP],\n"
- " AUTHORITY[\"EPSG\",\"5703\"]]]");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto objDst = WKTParser().createFromWKT(
- "COMPD_CS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet + NAVD88 "
- "height - Geoid12B (US Feet)\",\n"
- " PROJCS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " TOWGS84[0,0,0,0,0,0,0],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0],\n"
- " UNIT[\"Degree\",0.0174532925199433]],\n"
- " PROJECTION[\"Transverse_Mercator\"],\n"
- " PARAMETER[\"latitude_of_origin\",30],\n"
- " PARAMETER[\"central_meridian\",-87.5],\n"
- " PARAMETER[\"scale_factor\",0.999933333333333],\n"
- " PARAMETER[\"false_easting\",1968500],\n"
- " PARAMETER[\"false_northing\",0],\n"
- " UNIT[\"US survey foot\",0.304800609601219,\n"
- " AUTHORITY[\"EPSG\",\"9003\"]],\n"
- " AXIS[\"Easting\",EAST],\n"
- " AXIS[\"Northing\",NORTH],\n"
- " AUTHORITY[\"ESRI\",\"102630\"]],\n"
- " VERT_CS[\"NAVD88 height (ftUS)\",\n"
- " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
- " "
- "EXTENSION[\"PROJ4_GRIDS\",\"g2012a_alaska.gtx,g2012a_hawaii.gtx,"
- "g2012a_conus.gtx\"],\n"
- " AUTHORITY[\"EPSG\",\"5103\"]],\n"
- " UNIT[\"US survey foot\",0.304800609601219,\n"
- " AUTHORITY[\"EPSG\",\"9003\"]],\n"
- " AXIS[\"Gravity-related height\",UP],\n"
- " AUTHORITY[\"EPSG\",\"6360\"]]]");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- auto dbContext = DatabaseContext::create();
- auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(),
- "Inverse of unnamed + "
- "Transformation from NAD83 to WGS84 + "
- "NAVD88 height to NAVD88 height (ftUS) + "
- "Inverse of Transformation from NAD83 to WGS84 + "
- "unnamed");
- auto grids = list[0]->gridsNeeded(dbContext, false);
- EXPECT_TRUE(grids.empty());
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +inv +proj=tmerc +lat_0=30 +lon_0=-87.5 +k=0.999933333 "
- "+x_0=600000 +y_0=0 +ellps=GRS80 "
- "+step +proj=unitconvert +z_in=m +z_out=us-ft "
- "+step +proj=tmerc +lat_0=30 +lon_0=-87.5 +k=0.999933333333333 "
- "+x_0=600000 +y_0=0 +ellps=GRS80 "
- "+step +proj=unitconvert +xy_in=m +xy_out=us-ft");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_to_compoundCRS_issue_2232) {
- auto objSrc = WKTParser().createFromWKT(
- "COMPD_CS[\"NAD83 / Alabama West + NAVD88 height - Geoid12B "
- "(Meters)\",\n"
- " PROJCS[\"NAD83 / Alabama West\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " TOWGS84[0,0,0,0,0,0,0],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AUTHORITY[\"EPSG\",\"4269\"]],\n"
- " PROJECTION[\"Transverse_Mercator\"],\n"
- " PARAMETER[\"latitude_of_origin\",30],\n"
- " PARAMETER[\"central_meridian\",-87.5],\n"
- " PARAMETER[\"scale_factor\",0.999933333],\n"
- " PARAMETER[\"false_easting\",600000],\n"
- " PARAMETER[\"false_northing\",0],\n"
- " UNIT[\"metre\",1,\n"
- " AUTHORITY[\"EPSG\",\"9001\"]],\n"
- " AXIS[\"X\",EAST],\n"
- " AXIS[\"Y\",NORTH],\n"
- " AUTHORITY[\"EPSG\",\"26930\"]],\n"
- " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n"
- " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
- " EXTENSION[\"PROJ4_GRIDS\",\"foo.gtx\"],\n"
- " AUTHORITY[\"EPSG\",\"5103\"]],\n"
- " UNIT[\"metre\",1.0,\n"
- " AUTHORITY[\"EPSG\",\"9001\"]],\n"
- " AXIS[\"Gravity-related height\",UP],\n"
- " AUTHORITY[\"EPSG\",\"5703\"]]]");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- auto objDst = WKTParser().createFromWKT(
- "COMPD_CS[\"NAD83 + some CRS (US Feet)\",\n"
- " GEOGCS[\"NAD83\",\n"
- " DATUM[\"North_American_Datum_1983\",\n"
- " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
- " AUTHORITY[\"EPSG\",\"7019\"]],\n"
- " TOWGS84[0,0,0,0,0,0,0],\n"
- " AUTHORITY[\"EPSG\",\"6269\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AUTHORITY[\"EPSG\",\"4269\"]],\n"
- " VERT_CS[\"some CRS (US Feet)\",\n"
- " VERT_DATUM[\"some datum\",2005],\n"
- " UNIT[\"US survey foot\",0.3048006096012192,\n"
- " AUTHORITY[\"EPSG\",\"9003\"]],\n"
- " AXIS[\"Up\",UP]]]");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- auto dbContext = DatabaseContext::create();
- auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst), ctxt);
- EXPECT_GE(list.size(), 1U);
-
- auto list2 = CoordinateOperationFactory::create()->createOperations(
- NN_CHECK_ASSERT(dst), NN_CHECK_ASSERT(src), ctxt);
- EXPECT_EQ(list2.size(), list.size());
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_to_compoundCRS_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // NAD27 + NGVD29 height (ftUS)
- authFactory->createCoordinateReferenceSystem("7406"),
- // NAD83(NSRS2007) + NAVD88 height
- authFactory->createCoordinateReferenceSystem("5500"), ctxt);
- // 152 or 155 depending if the VERTCON grids are there
- ASSERT_GE(list.size(), 152U);
- EXPECT_FALSE(list[0]->hasBallparkTransformation());
- EXPECT_EQ(list[0]->nameStr(), "NGVD29 height (ftUS) to NAVD88 height (3) + "
- "NAD27 to WGS 84 (79) + Inverse of "
- "NAD83(NSRS2007) to WGS 84 (1)");
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad +z_out=m "
- "+step +proj=vgridshift +grids=us_noaa_vertcone.tif +multiplier=1 "
- "+step +proj=hgridshift +grids=us_noaa_conus.tif +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
- {
- // Test that we can round-trip this through WKT and still get the same
- // PROJ string.
- auto wkt = list[0]->exportToWKT(
- WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get());
- auto obj = WKTParser().createFromWKT(wkt);
- auto co = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
- ASSERT_TRUE(co != nullptr);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- co->exportToPROJString(PROJStringFormatter::create().get()));
- }
-
- bool foundApprox = false;
- for (size_t i = 0; i < list.size(); i++) {
- auto projString =
- list[i]->exportToPROJString(PROJStringFormatter::create().get());
- EXPECT_TRUE(
- projString.find("+proj=pipeline +step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +z_in=us-ft "
- "+xy_out=rad +z_out=m") == 0)
- << list[i]->nameStr();
- if (list[i]->nameStr().find("Transformation from NGVD29 height (ftUS) "
- "to NAVD88 height (ballpark vertical "
- "transformation)") == 0) {
- EXPECT_TRUE(list[i]->hasBallparkTransformation());
- EXPECT_EQ(list[i]->nameStr(),
- "Transformation from NGVD29 height (ftUS) to NAVD88 "
- "height (ballpark vertical transformation) + NAD27 to "
- "WGS 84 (79) + Inverse of NAD83(NSRS2007) to WGS 84 (1)");
- EXPECT_EQ(
- projString,
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad "
- "+z_out=m +step +proj=hgridshift +grids=us_noaa_conus.tif "
- "+step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
- foundApprox = true;
- break;
- }
- }
- EXPECT_TRUE(foundApprox);
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_to_compoundCRS_context_helmert_noop) {
- auto dbContext = DatabaseContext::create();
- auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- // WGS84 + EGM96
- auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
- auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
- ASSERT_TRUE(srcCrs != nullptr);
- // ETRS89 + EGM96
- auto objDest = createFromUserInput("EPSG:4258+5773", dbContext);
- auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
- ASSERT_TRUE(destCrs != nullptr);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=noop");
-}
-
-// ---------------------------------------------------------------------------
-
-// EGM96 has a geoid model referenced to WGS84, and Belfast height has a
-// geoid model referenced to ETRS89
-TEST(operation, compoundCRS_to_compoundCRS_WGS84_EGM96_to_ETRS89_Belfast) {
- auto dbContext = DatabaseContext::create();
- auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- // WGS84 + EGM96
- auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
- auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
- ASSERT_TRUE(srcCrs != nullptr);
- // ETRS89 + Belfast height
- auto objDest = createFromUserInput("EPSG:4258+5732", dbContext);
- auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
- ASSERT_TRUE(destCrs != nullptr);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
- "Inverse of ETRS89 to WGS 84 (1) + "
- "ETRS89 to Belfast height (2)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 "
- "+step +inv +proj=vgridshift +grids=uk_os_OSGM15_Belfast.tif "
- "+multiplier=1 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-// Variant of above where source intermediate geog3D CRS == target intermediate
-// geog3D CRS
-TEST(operation, compoundCRS_to_compoundCRS_WGS84_EGM96_to_WGS84_Belfast) {
- auto dbContext = DatabaseContext::create();
- auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- // WGS84 + EGM96
- auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
- auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
- ASSERT_TRUE(srcCrs != nullptr);
- // WGS84 + Belfast height
- auto objDest = createFromUserInput("EPSG:4326+5732", dbContext);
- auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
- ASSERT_TRUE(destCrs != nullptr);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
- "Inverse of ETRS89 to WGS 84 (1) + "
- "ETRS89 to Belfast height (2) + "
- "ETRS89 to WGS 84 (1)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 "
- "+step +inv +proj=vgridshift +grids=uk_os_OSGM15_Belfast.tif "
- "+multiplier=1 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- compoundCRS_to_compoundCRS_concatenated_operation_with_two_vert_transformation) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // ETRS89 + Baltic 1957 height
- authFactory->createCoordinateReferenceSystem("8360"),
- // ETRS89 + EVRF2007 height
- authFactory->createCoordinateReferenceSystem("7423"), ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(
- list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift "
- "+grids=sk_gku_Slovakia_ETRS89h_to_Baltic1957.tif +multiplier=1 "
- "+step +inv +proj=vgridshift "
- "+grids=sk_gku_Slovakia_ETRS89h_to_EVRF2007.tif +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
- EXPECT_EQ(
- list[0]->nameStr(),
- "ETRS89 + Baltic 1957 height to ETRS89 + EVRF2007 height (1)");
- EXPECT_EQ(list[0]->inverse()->nameStr(), "Inverse of 'ETRS89 + Baltic "
- "1957 height to ETRS89 + "
- "EVRF2007 height (1)'");
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, vertCRS_to_vertCRS) {
-
- auto vertcrs_m_obj = PROJStringParser().createFromPROJString("+vunits=m");
- auto vertcrs_m = nn_dynamic_pointer_cast<VerticalCRS>(vertcrs_m_obj);
- ASSERT_TRUE(vertcrs_m != nullptr);
-
- auto vertcrs_ft_obj = PROJStringParser().createFromPROJString("+vunits=ft");
- auto vertcrs_ft = nn_dynamic_pointer_cast<VerticalCRS>(vertcrs_ft_obj);
- ASSERT_TRUE(vertcrs_ft != nullptr);
-
- auto vertcrs_us_ft_obj =
- PROJStringParser().createFromPROJString("+vunits=us-ft");
- auto vertcrs_us_ft =
- nn_dynamic_pointer_cast<VerticalCRS>(vertcrs_us_ft_obj);
- ASSERT_TRUE(vertcrs_us_ft != nullptr);
-
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(vertcrs_m), NN_CHECK_ASSERT(vertcrs_ft));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=unitconvert +z_in=m +z_out=ft");
- }
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(vertcrs_m), NN_CHECK_ASSERT(vertcrs_ft));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->inverse()->exportToPROJString(
- PROJStringFormatter::create().get()),
- "+proj=unitconvert +z_in=ft +z_out=m");
- }
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(vertcrs_ft), NN_CHECK_ASSERT(vertcrs_m));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=unitconvert +z_in=ft +z_out=m");
- }
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(vertcrs_ft), NN_CHECK_ASSERT(vertcrs_us_ft));
- ASSERT_TRUE(op != nullptr);
- 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");
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, vertCRS_to_vertCRS_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // NGVD29 height (m)
- authFactory->createCoordinateReferenceSystem("7968"),
- // NAVD88 height (1)
- authFactory->createCoordinateReferenceSystem("5703"), ctxt);
- ASSERT_EQ(list.size(), 3U);
- EXPECT_EQ(list[0]->nameStr(), "NGVD29 height (m) to NAVD88 height (3)");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=vgridshift +grids=us_noaa_vertcone.tif +multiplier=1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, vertCRS_to_vertCRS_New_Zealand_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // NZVD2016 height
- authFactory->createCoordinateReferenceSystem("7839"),
- // Auckland 1946 height
- authFactory->createCoordinateReferenceSystem("5759"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=vgridshift +grids=nz_linz_auckht1946-nzvd2016.tif "
- "+multiplier=1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, projCRS_3D_to_geogCRS_3D) {
-
- auto compoundcrs_ft_obj = PROJStringParser().createFromPROJString(
- "+proj=merc +vunits=ft +type=crs");
- auto proj3DCRS_ft = nn_dynamic_pointer_cast<CRS>(compoundcrs_ft_obj);
- ASSERT_TRUE(proj3DCRS_ft != nullptr);
-
- auto geogcrs_m_obj = PROJStringParser().createFromPROJString(
- "+proj=longlat +vunits=m +type=crs");
- auto geogcrs_m = nn_dynamic_pointer_cast<CRS>(geogcrs_m_obj);
- ASSERT_TRUE(geogcrs_m != nullptr);
-
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(proj3DCRS_ft), NN_CHECK_ASSERT(geogcrs_m));
- ASSERT_TRUE(op != nullptr);
- EXPECT_FALSE(op->hasBallparkTransformation());
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=m +z_in=ft "
- "+xy_out=m +z_out=m "
- "+step +inv +proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 "
- "+ellps=WGS84 "
- "+step +proj=unitconvert +xy_in=rad +z_in=m "
- "+xy_out=deg +z_out=m");
- }
-
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(geogcrs_m), NN_CHECK_ASSERT(proj3DCRS_ft));
- ASSERT_TRUE(op != nullptr);
- EXPECT_FALSE(op->hasBallparkTransformation());
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +z_in=m +z_out=ft "
- "+step +proj=unitconvert +xy_in=deg +z_in=ft "
- "+xy_out=rad +z_out=m "
- "+step +proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 "
- "+step +proj=unitconvert +xy_in=m +z_in=m "
- "+xy_out=m +z_out=ft");
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_to_geogCRS_3D) {
-
- auto compoundcrs_ft_obj = WKTParser().createFromWKT(
- "COMPOUNDCRS[\"unknown\",\n"
- " PROJCRS[\"unknown\",\n"
- " BASEGEOGCRS[\"unknown\",\n"
- " DATUM[\"World Geodetic System 1984\",\n"
- " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
- " LENGTHUNIT[\"metre\",1]],\n"
- " ID[\"EPSG\",6326]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8901]]],\n"
- " CONVERSION[\"unknown\",\n"
- " METHOD[\"Mercator (variant A)\",\n"
- " ID[\"EPSG\",9804]],\n"
- " PARAMETER[\"Latitude of natural origin\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8801]],\n"
- " PARAMETER[\"Longitude of natural origin\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8802]],\n"
- " PARAMETER[\"Scale factor at natural origin\",1,\n"
- " SCALEUNIT[\"unity\",1],\n"
- " ID[\"EPSG\",8805]],\n"
- " PARAMETER[\"False easting\",0,\n"
- " LENGTHUNIT[\"metre\",1],\n"
- " ID[\"EPSG\",8806]],\n"
- " PARAMETER[\"False northing\",0,\n"
- " LENGTHUNIT[\"metre\",1],\n"
- " ID[\"EPSG\",8807]]],\n"
- " CS[Cartesian,2],\n"
- " AXIS[\"(E)\",east,\n"
- " ORDER[1],\n"
- " LENGTHUNIT[\"metre\",1,\n"
- " ID[\"EPSG\",9001]]],\n"
- " AXIS[\"(N)\",north,\n"
- " ORDER[2],\n"
- " LENGTHUNIT[\"metre\",1,\n"
- " ID[\"EPSG\",9001]]]],\n"
- " VERTCRS[\"unknown\",\n"
- " VDATUM[\"unknown\"],\n"
- " CS[vertical,1],\n"
- " AXIS[\"gravity-related height (H)\",up,\n"
- " LENGTHUNIT[\"foot\",0.3048,\n"
- " ID[\"EPSG\",9002]]]]]");
- auto compoundcrs_ft = nn_dynamic_pointer_cast<CRS>(compoundcrs_ft_obj);
- ASSERT_TRUE(compoundcrs_ft != nullptr);
-
- auto geogcrs_m_obj = PROJStringParser().createFromPROJString(
- "+proj=longlat +vunits=m +type=crs");
- auto geogcrs_m = nn_dynamic_pointer_cast<CRS>(geogcrs_m_obj);
- ASSERT_TRUE(geogcrs_m != nullptr);
-
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(compoundcrs_ft), NN_CHECK_ASSERT(geogcrs_m));
- ASSERT_TRUE(op != nullptr);
- EXPECT_TRUE(op->hasBallparkTransformation());
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=merc +lon_0=0 +k=1 +x_0=0 "
- "+y_0=0 +ellps=WGS84 +step +proj=unitconvert +xy_in=rad "
- "+z_in=ft +xy_out=deg +z_out=m");
- }
-
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(geogcrs_m), NN_CHECK_ASSERT(compoundcrs_ft));
- ASSERT_TRUE(op != nullptr);
- EXPECT_TRUE(op->hasBallparkTransformation());
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=unitconvert +xy_in=deg +z_in=m "
- "+xy_out=rad +z_out=ft +step +proj=merc +lon_0=0 +k=1 +x_0=0 "
- "+y_0=0 +ellps=WGS84");
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_to_geogCRS_3D_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- // CompoundCRS to Geog3DCRS, with vertical unit change, but without
- // ellipsoid height <--> vertical height correction
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem(
- "7406"), // NAD27 + NGVD29 height (ftUS)
- authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
- ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_TRUE(list[0]->hasBallparkTransformation());
- EXPECT_EQ(list[0]->nameStr(),
- "NAD27 to WGS 84 (79) + Transformation from NGVD29 height "
- "(ftUS) to WGS 84 (ballpark vertical transformation, without "
- "ellipsoid height to vertical height correction)");
- EXPECT_EQ(list[0]->exportToPROJString(
- PROJStringFormatter::create(
- PROJStringFormatter::Convention::PROJ_5,
- authFactory->databaseContext())
- .get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step "
- "+proj=hgridshift +grids=us_noaa_conus.tif "
- "+step +proj=unitconvert "
- "+xy_in=rad +z_in=us-ft +xy_out=deg +z_out=m +step "
- "+proj=axisswap +order=2,1");
- }
-
- // CompoundCRS to Geog3DCRS, with same vertical unit, and with
- // direct ellipsoid height <--> vertical height correction and
- // direct horizontal transform (no-op here)
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- auto list = CoordinateOperationFactory::create()->createOperations(
- authFactory->createCoordinateReferenceSystem(
- "5500"), // NAD83(NSRS2007) + NAVD88 height
- authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
- ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(),
- "Inverse of NAD83(NSRS2007) to NAVD88 height (1) + "
- "NAD83(NSRS2007) to WGS 84 (1)");
- EXPECT_EQ(list[0]->exportToPROJString(
- PROJStringFormatter::create(
- PROJStringFormatter::Convention::PROJ_5,
- authFactory->databaseContext())
- .get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=us_noaa_geoid09_conus.tif "
- "+multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
- EXPECT_EQ(list[0]->remarks(),
- "For NAD83(NSRS2007) to NAVD88 height (1) (EPSG:9173): Uses "
- "Geoid09 hybrid model. Replaced by 2012 model (CT code 6326)."
- "\n"
- "For NAD83(NSRS2007) to WGS 84 (1) (EPSG:15931): "
- "Approximation at the +/- 1m level assuming that "
- "NAD83(NSRS2007) is equivalent to WGS 84 within the accuracy "
- "of the transformation.");
- }
-
- // NAD83 + NAVD88 height --> WGS 84
- {
- auto ctxt =
- CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- // NAD83 + NAVD88 height
- auto srcObj = createFromUserInput(
- "EPSG:4269+5703", authFactory->databaseContext(), false);
- auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
- ASSERT_TRUE(src != nullptr);
- auto nnSrc = NN_NO_CHECK(src);
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- nnSrc,
- authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
- ctxt);
- ASSERT_GE(list.size(), 2U);
-
- EXPECT_EQ(list[0]->nameStr(),
- "NAD83 to WGS 84 (1) + "
- "Inverse of NAD83(NSRS2007) to WGS 84 (1) + "
- "Inverse of NAD83(NSRS2007) to NAVD88 height (1) + "
- "NAD83(NSRS2007) to WGS 84 (1)");
- EXPECT_EQ(list[0]->exportToPROJString(
- PROJStringFormatter::create(
- PROJStringFormatter::Convention::PROJ_5,
- authFactory->databaseContext())
- .get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=us_noaa_geoid09_conus.tif "
- "+multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
- }
-
- // Another variation, but post horizontal adjustment is in two steps
- {
- 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
- auto srcObj = createFromUserInput(
- "EPSG:6318+5703", authFactory->databaseContext(), false);
- auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
- ASSERT_TRUE(src != nullptr);
- auto nnSrc = NN_NO_CHECK(src);
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- nnSrc,
- authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
- ctxt);
- ASSERT_GE(list.size(), 2U);
-
- EXPECT_EQ(list[0]->nameStr(),
- "Inverse of NAD83(2011) to NAVD88 height (3) + "
- "Inverse of NAD83 to NAD83(2011) (1) + "
- "NAD83 to WGS 84 (1)");
- EXPECT_EQ(list[0]->exportToPROJString(
- PROJStringFormatter::create(
- PROJStringFormatter::Convention::PROJ_5,
- authFactory->databaseContext())
- .get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=us_noaa_g2018u0.tif "
- "+multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-
- // Shows vertical step, and then horizontal step
- EXPECT_EQ(list[1]->nameStr(),
- "Inverse of NAD83(2011) to NAVD88 height (3) + "
- "Inverse of NAD83 to NAD83(2011) (1) + "
- "NAD83 to WGS 84 (18)");
- EXPECT_EQ(list[1]->exportToPROJString(
- PROJStringFormatter::create(
- PROJStringFormatter::Convention::PROJ_5,
- authFactory->databaseContext())
- .get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=us_noaa_g2018u0.tif "
- "+multiplier=1 "
- "+step +proj=hgridshift +grids=us_noaa_FL.tif "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_to_geogCRS_3D_with_3D_helmert_context) {
- // Use case of https://github.com/OSGeo/PROJ/issues/2225
- auto dbContext = DatabaseContext::create();
- auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- // WGS84 + EGM96 height
- auto srcObj = createFromUserInput("EPSG:4326+5773", dbContext, false);
- auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
- ASSERT_TRUE(src != nullptr);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(src),
- // CH1903+
- authFactory->createCoordinateReferenceSystem("4150")->promoteTo3D(
- std::string(), dbContext),
- ctxt);
- ASSERT_GE(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
- "Inverse of CH1903+ to WGS 84 (1)");
- // Check that there is no push v_3 / pop v_3
- const char *expected_proj =
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 "
- "+step +proj=cart +ellps=WGS84 "
- "+step +proj=helmert +x=-674.374 +y=-15.056 +z=-405.346 "
- "+step +inv +proj=cart +ellps=bessel "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1";
- EXPECT_EQ(list[0]->exportToPROJString(
- PROJStringFormatter::create(
- PROJStringFormatter::Convention::PROJ_5, dbContext)
- .get()),
- expected_proj);
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_to_geogCRS_2D_promote_to_3D_context) {
- 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 + NAVD88 height
- auto srcObj = createFromUserInput("EPSG:4269+5703",
- 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("4269"); // NAD83
-
- auto listCompoundToGeog2D =
- CoordinateOperationFactory::create()->createOperations(nnSrc, dst,
- ctxt);
- // The checked value is not that important, but in case this changes,
- // likely due to a EPSG upgrade, worth checking
- EXPECT_EQ(listCompoundToGeog2D.size(), 142U);
-
- auto listGeog2DToCompound =
- CoordinateOperationFactory::create()->createOperations(dst, nnSrc,
- ctxt);
- EXPECT_EQ(listGeog2DToCompound.size(), listCompoundToGeog2D.size());
-
- auto listCompoundToGeog3D =
- CoordinateOperationFactory::create()->createOperations(
- nnSrc,
- dst->promoteTo3D(std::string(), authFactory->databaseContext()),
- ctxt);
- EXPECT_EQ(listCompoundToGeog3D.size(), listCompoundToGeog2D.size());
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_of_projCRS_to_geogCRS_2D_context) {
- 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);
- // SPCS83 California zone 1 (US Survey feet) + NAVD88 height (ftUS)
- auto srcObj = createFromUserInput("EPSG:2225+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("4269"); // NAD83
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- nnSrc, dst, ctxt);
- // The checked value is not that important, but in case this changes,
- // likely due to a EPSG upgrade, worth checking
- // We want to make sure that the horizontal adjustments before and after
- // the vertical transformation are the reverse of each other, and there are
- // not mixes with different alternative operations (like California grid
- // forward and Nevada grid reverse)
- ASSERT_EQ(list.size(), 14U);
-
- // Check that unit conversion is OK
- auto op_proj =
- list[0]->exportToPROJString(PROJStringFormatter::create().get());
- EXPECT_EQ(op_proj,
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=us-ft +xy_out=m "
- "+step +inv +proj=lcc +lat_0=39.3333333333333 +lon_0=-122 "
- "+lat_1=41.6666666666667 +lat_2=40 +x_0=2000000.0001016 "
- "+y_0=500000.0001016 +ellps=GRS80 "
- "+step +proj=unitconvert +z_in=us-ft +z_out=m "
- "+step +proj=vgridshift +grids=us_noaa_geoid09_conus.tif "
- "+multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-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_of_projCRS_from_wkt_without_id_or_extent_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 / Pennsylvania South + NAVD88 height\",\n"
- " PROJCRS[\"NAD83 / Pennsylvania South\",\n"
- " BASEGEOGCRS[\"NAD83\",\n"
- " DATUM[\"North American Datum 1983\",\n"
- " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
- " LENGTHUNIT[\"metre\",1]]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
- " CONVERSION[\"SPCS83 Pennsylvania South zone (meters)\",\n"
- " METHOD[\"Lambert Conic Conformal (2SP)\",\n"
- " ID[\"EPSG\",9802]],\n"
- " PARAMETER[\"Latitude of false origin\",39.3333333333333,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8821]],\n"
- " PARAMETER[\"Longitude of false origin\",-77.75,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8822]],\n"
- " PARAMETER[\"Latitude of 1st standard "
- "parallel\",40.9666666666667,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8823]],\n"
- " PARAMETER[\"Latitude of 2nd standard "
- "parallel\",39.9333333333333,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8824]],\n"
- " PARAMETER[\"Easting at false origin\",600000,\n"
- " LENGTHUNIT[\"metre\",1],\n"
- " ID[\"EPSG\",8826]],\n"
- " PARAMETER[\"Northing at false origin\",0,\n"
- " LENGTHUNIT[\"metre\",1],\n"
- " ID[\"EPSG\",8827]]],\n"
- " CS[Cartesian,2],\n"
- " AXIS[\"easting (X)\",east,\n"
- " ORDER[1],\n"
- " LENGTHUNIT[\"metre\",1]],\n"
- " AXIS[\"northing (Y)\",north,\n"
- " ORDER[2],\n"
- " LENGTHUNIT[\"metre\",1]]],\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("4269"); // NAD83
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(src), dst, ctxt);
- // NAD83 / Pennsylvania South + NAVD88 height
- auto srcRefObj = createFromUserInput("EPSG:32129+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_of_vertCRS_with_geoid_model_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 / Pennsylvania South + NAVD88 height\",\n"
- " PROJCRS[\"NAD83 / Pennsylvania South\",\n"
- " BASEGEOGCRS[\"NAD83\",\n"
- " DATUM[\"North American Datum 1983\",\n"
- " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
- " LENGTHUNIT[\"metre\",1]]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
- " CONVERSION[\"SPCS83 Pennsylvania South zone (meters)\",\n"
- " METHOD[\"Lambert Conic Conformal (2SP)\",\n"
- " ID[\"EPSG\",9802]],\n"
- " PARAMETER[\"Latitude of false origin\",39.3333333333333,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8821]],\n"
- " PARAMETER[\"Longitude of false origin\",-77.75,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8822]],\n"
- " PARAMETER[\"Latitude of 1st standard "
- "parallel\",40.9666666666667,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8823]],\n"
- " PARAMETER[\"Latitude of 2nd standard "
- "parallel\",39.9333333333333,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8824]],\n"
- " PARAMETER[\"Easting at false origin\",600000,\n"
- " LENGTHUNIT[\"metre\",1],\n"
- " ID[\"EPSG\",8826]],\n"
- " PARAMETER[\"Northing at false origin\",0,\n"
- " LENGTHUNIT[\"metre\",1],\n"
- " ID[\"EPSG\",8827]]],\n"
- " CS[Cartesian,2],\n"
- " AXIS[\"easting (X)\",east,\n"
- " ORDER[1],\n"
- " LENGTHUNIT[\"metre\",1]],\n"
- " AXIS[\"northing (Y)\",north,\n"
- " ORDER[2],\n"
- " LENGTHUNIT[\"metre\",1]]],\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]],\n"
- " GEOIDMODEL[\"GEOID12B\"]]]";
- auto srcObj =
- createFromUserInput(wkt, authFactory->databaseContext(), false);
- auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
- ASSERT_TRUE(src != nullptr);
- auto dst = authFactory->createCoordinateReferenceSystem("4269"); // NAD83
-
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(src), dst, ctxt);
- ASSERT_TRUE(!list.empty());
- EXPECT_EQ(list[0]->nameStr(),
- "Inverse of SPCS83 Pennsylvania South zone (meters) + "
- "Ballpark geographic offset from NAD83 to NAD83(2011) + "
- "Inverse of NAD83(2011) to NAVD88 height (1) + "
- "Ballpark geographic offset from NAD83(2011) to NAD83");
- auto op_proj =
- list[0]->exportToPROJString(PROJStringFormatter::create().get());
- EXPECT_EQ(
- op_proj,
- "+proj=pipeline "
- "+step +inv +proj=lcc +lat_0=39.3333333333333 +lon_0=-77.75 "
- "+lat_1=40.9666666666667 +lat_2=39.9333333333333 +x_0=600000 "
- "+y_0=0 +ellps=GRS80 "
- "+step +proj=vgridshift +grids=us_noaa_g2012bu0.tif +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_from_WKT2_to_geogCRS_3D_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto src = authFactory->createCoordinateReferenceSystem(
- "7415"); // Amersfoort / RD New + NAP height
- auto dst =
- authFactory->createCoordinateReferenceSystem("4937"); // ETRS89 3D
- auto list =
- CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
- ASSERT_GE(list.size(), 1U);
- auto wkt2 = src->exportToWKT(
- WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get());
- auto obj = WKTParser().createFromWKT(wkt2);
- auto src_from_wkt2 = nn_dynamic_pointer_cast<CRS>(obj);
- ASSERT_TRUE(src_from_wkt2 != nullptr);
- auto list2 = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(src_from_wkt2), dst, ctxt);
- ASSERT_GE(list.size(), list2.size());
- for (size_t i = 0; i < list.size(); i++) {
- const auto &op = list[i];
- const auto &op2 = list2[i];
- EXPECT_TRUE(
- op->isEquivalentTo(op2.get(), IComparable::Criterion::EQUIVALENT));
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_from_WKT2_no_id_to_geogCRS_3D_context) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto src = authFactory->createCoordinateReferenceSystem(
- "7415"); // Amersfoort / RD New + NAP height
- auto dst =
- authFactory->createCoordinateReferenceSystem("4937"); // ETRS89 3D
- auto list =
- CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
- ASSERT_GE(list.size(), 1U);
-
- {
- auto op_proj =
- list[0]->exportToPROJString(PROJStringFormatter::create().get());
- EXPECT_EQ(
- op_proj,
- "+proj=pipeline +step +inv +proj=sterea +lat_0=52.1561605555556 "
- "+lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 "
- "+ellps=bessel "
- "+step +proj=hgridshift +grids=nl_nsgi_rdtrans2018.tif "
- "+step +proj=vgridshift +grids=nl_nsgi_nlgeo2018.tif +multiplier=1 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
- }
-
- auto wkt2 =
- "COMPOUNDCRS[\"unknown\",\n"
- " PROJCRS[\"unknown\",\n"
- " BASEGEOGCRS[\"Amersfoort\",\n"
- " DATUM[\"Amersfoort\",\n"
- " ELLIPSOID[\"Bessel "
- "1841\",6377397.155,299.1528128]]],\n"
- " CONVERSION[\"unknown\",\n"
- " METHOD[\"Oblique Stereographic\"],\n"
- " PARAMETER[\"Latitude of natural origin\",52.1561605555556],\n"
- " PARAMETER[\"Longitude of natural origin\",5.38763888888889],\n"
- " PARAMETER[\"Scale factor at natural origin\",0.9999079],\n"
- " PARAMETER[\"False easting\",155000],\n"
- " PARAMETER[\"False northing\",463000]],\n"
- " CS[Cartesian,2],\n"
- " AXIS[\"(E)\",east],\n"
- " AXIS[\"(N)\",north],\n"
- " LENGTHUNIT[\"metre\",1]],\n"
- " VERTCRS[\"NAP height\",\n"
- " VDATUM[\"Normaal Amsterdams Peil\"],\n"
- " CS[vertical,1],\n"
- " AXIS[\"gravity-related height (H)\",up,\n"
- " LENGTHUNIT[\"metre\",1]]],\n"
- " USAGE[\n"
- " SCOPE[\"unknown\"],\n"
- " AREA[\"Netherlands - onshore\"],\n"
- " BBOX[50.75,3.2,53.7,7.22]]]";
-
- auto obj = WKTParser().createFromWKT(wkt2);
- auto src_from_wkt2 = nn_dynamic_pointer_cast<CRS>(obj);
- ASSERT_TRUE(src_from_wkt2 != nullptr);
- auto list2 = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(src_from_wkt2), dst, ctxt);
- ASSERT_EQ(list.size(), list2.size());
- for (size_t i = 0; i < list.size(); i++) {
- const auto &op = list[i];
- const auto &op2 = list2[i];
- auto op_proj =
- op->exportToPROJString(PROJStringFormatter::create().get());
- auto op2_proj =
- op2->exportToPROJString(PROJStringFormatter::create().get());
- EXPECT_EQ(op_proj, op2_proj) << "op=" << op->nameStr()
- << " op2=" << op2->nameStr();
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, proj3DCRS_with_non_meter_horiz_and_vertical_to_geog) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=31 +datum=WGS84 +units=us-ft +vunits=us-ft +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(src), authFactory->createCoordinateReferenceSystem("4326"),
- ctxt);
- ASSERT_EQ(list.size(), 1U);
- // Check that vertical unit conversion is done just once
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=us-ft +z_in=us-ft "
- "+xy_out=m +z_out=m "
- "+step +inv +proj=utm +zone=31 +ellps=WGS84 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, compoundCRS_with_non_meter_horiz_and_vertical_to_geog) {
- auto objSrc = WKTParser().createFromWKT(
- "COMPOUNDCRS[\"unknown\",\n"
- " PROJCRS[\"unknown\",\n"
- " BASEGEOGCRS[\"unknown\",\n"
- " DATUM[\"World Geodetic System 1984\",\n"
- " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
- " LENGTHUNIT[\"metre\",1]],\n"
- " ID[\"EPSG\",6326]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8901]]],\n"
- " CONVERSION[\"UTM zone 31N\",\n"
- " METHOD[\"Transverse Mercator\",\n"
- " ID[\"EPSG\",9807]],\n"
- " PARAMETER[\"Latitude of natural origin\",0,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8801]],\n"
- " PARAMETER[\"Longitude of natural origin\",3,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8802]],\n"
- " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
- " SCALEUNIT[\"unity\",1],\n"
- " ID[\"EPSG\",8805]],\n"
- " PARAMETER[\"False easting\",500000,\n"
- " LENGTHUNIT[\"metre\",1],\n"
- " ID[\"EPSG\",8806]],\n"
- " PARAMETER[\"False northing\",0,\n"
- " LENGTHUNIT[\"metre\",1],\n"
- " ID[\"EPSG\",8807]],\n"
- " ID[\"EPSG\",16031]],\n"
- " CS[Cartesian,2],\n"
- " AXIS[\"(E)\",east,\n"
- " ORDER[1],\n"
- " LENGTHUNIT[\"US survey foot\",0.304800609601219,\n"
- " ID[\"EPSG\",9003]]],\n"
- " AXIS[\"(N)\",north,\n"
- " ORDER[2],\n"
- " LENGTHUNIT[\"US survey foot\",0.304800609601219,\n"
- " ID[\"EPSG\",9003]]]],\n"
- " VERTCRS[\"unknown\",\n"
- " VDATUM[\"unknown\"],\n"
- " CS[vertical,1],\n"
- " AXIS[\"gravity-related height (H)\",up,\n"
- " LENGTHUNIT[\"US survey foot\",0.304800609601219,\n"
- " ID[\"EPSG\",9003]]]]]"
-
- );
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- auto list = CoordinateOperationFactory::create()->createOperations(
- NN_NO_CHECK(src), authFactory->createCoordinateReferenceSystem("4326"),
- ctxt);
- ASSERT_EQ(list.size(), 1U);
- // Check that vertical unit conversion is done just once
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=us-ft +xy_out=m "
- "+step +inv +proj=utm +zone=31 +ellps=WGS84 "
- "+step +proj=unitconvert +xy_in=rad +z_in=us-ft "
- "+xy_out=deg +z_out=m "
- "+step +proj=axisswap +order=2,1");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, boundCRS_to_compoundCRS) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx "
- "+type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=hgridshift +grids=@foo.gsb "
- "+step +inv +proj=vgridshift +grids=@bar.gtx +multiplier=1 "
- "+step +inv +proj=hgridshift +grids=@bar.gsb "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-
- auto opInverse = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(dst), NN_CHECK_ASSERT(src));
- ASSERT_TRUE(opInverse != nullptr);
- EXPECT_TRUE(opInverse->inverse()->_isEquivalentTo(op.get()));
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, IGNF_LAMB1_TO_EPSG_4326) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), std::string());
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setGridAvailabilityUse(
- CoordinateOperationContext::GridAvailabilityUse::
- IGNORE_GRID_AVAILABILITY);
- ctxt->setAllowUseIntermediateCRS(
- CoordinateOperationContext::IntermediateCRSUse::ALWAYS);
- auto list = CoordinateOperationFactory::create()->createOperations(
- AuthorityFactory::create(DatabaseContext::create(), "IGNF")
- ->createCoordinateReferenceSystem("LAMB1"),
- AuthorityFactory::create(DatabaseContext::create(), "EPSG")
- ->createCoordinateReferenceSystem("4326"),
- ctxt);
- ASSERT_EQ(list.size(), 2U);
-
- EXPECT_FALSE(list[0]->hasBallparkTransformation());
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=lcc +lat_1=49.5 +lat_0=49.5 "
- "+lon_0=0 +k_0=0.99987734 +x_0=600000 +y_0=200000 "
- "+ellps=clrk80ign +pm=paris +step +proj=hgridshift "
- "+grids=fr_ign_ntf_r93.tif +step +proj=unitconvert +xy_in=rad "
- "+xy_out=deg +step +proj=axisswap +order=2,1");
-
- EXPECT_FALSE(list[1]->hasBallparkTransformation());
- EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=lcc +lat_1=49.5 +lat_0=49.5 "
- "+lon_0=0 +k_0=0.99987734 +x_0=600000 +y_0=200000 "
- "+ellps=clrk80ign +pm=paris +step +proj=push +v_3 +step "
- "+proj=cart +ellps=clrk80ign +step +proj=helmert +x=-168 +y=-60 "
- "+z=320 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step "
- "+proj=axisswap +order=2,1");
-
- auto list2 = CoordinateOperationFactory::create()->createOperations(
- AuthorityFactory::create(DatabaseContext::create(), "EPSG")
- // NTF (Paris) / Lambert Nord France equivalent to IGNF:LAMB1
- ->createCoordinateReferenceSystem("27561"),
- AuthorityFactory::create(DatabaseContext::create(), "EPSG")
- ->createCoordinateReferenceSystem("4326"),
- ctxt);
- ASSERT_GE(list2.size(), 3U);
-
- EXPECT_EQ(replaceAll(list2[0]->exportToPROJString(
- PROJStringFormatter::create().get()),
- "0.999877341", "0.99987734"),
- list[0]->exportToPROJString(PROJStringFormatter::create().get()));
-
- // The second entry in list2 (list2[1]) uses the
- // weird +pm=2.33720833333333 from "NTF (Paris) to NTF (2)"
- // so skip to the 3th method
- EXPECT_EQ(replaceAll(list2[2]->exportToPROJString(
- PROJStringFormatter::create().get()),
- "0.999877341", "0.99987734"),
- list[1]->exportToPROJString(PROJStringFormatter::create().get()));
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, NAD83_to_projeted_CRS_based_on_NAD83_2011) {
- auto authFactory =
- AuthorityFactory::create(DatabaseContext::create(), "EPSG");
- auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctxt->setSpatialCriterion(
- CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
- auto list = CoordinateOperationFactory::create()->createOperations(
- // NAD83
- authFactory->createCoordinateReferenceSystem("4269"),
- // NAD83(2011) / California Albers
- authFactory->createCoordinateReferenceSystem("6414"), ctxt);
- ASSERT_EQ(list.size(), 1U);
- EXPECT_EQ(list[0]->nameStr(), "Ballpark geographic offset from NAD83 to "
- "NAD83(2011) + California Albers");
- EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=aea +lat_0=0 +lon_0=-120 +lat_1=34 "
- "+lat_2=40.5 +x_0=0 +y_0=-4000000 +ellps=GRS80");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, isPROJInstantiable) {
-
- {
- auto transformation = Transformation::createGeocentricTranslations(
- PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326,
- 1.0, 2.0, 3.0, {});
- EXPECT_TRUE(transformation->isPROJInstantiable(
- DatabaseContext::create(), false));
- }
-
- // Missing grid
- {
- auto transformation = Transformation::createNTv2(
- PropertyMap(), GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326,
- "foo.gsb", std::vector<PositionalAccuracyNNPtr>());
- EXPECT_FALSE(transformation->isPROJInstantiable(
- DatabaseContext::create(), false));
- }
-
- // Unsupported method
- {
- auto transformation = Transformation::create(
- PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326,
- nullptr, OperationMethod::create(
- PropertyMap(), std::vector<OperationParameterNNPtr>{}),
- std::vector<GeneralParameterValueNNPtr>{},
- std::vector<PositionalAccuracyNNPtr>{});
- EXPECT_FALSE(transformation->isPROJInstantiable(
- DatabaseContext::create(), false));
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, createOperation_on_crs_with_canonical_bound_crs) {
- auto boundCRS = BoundCRS::createFromTOWGS84(
- GeographicCRS::EPSG_4267, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
- auto crs = boundCRS->baseCRSWithCanonicalBoundCRS();
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- crs, GeographicCRS::EPSG_4326);
- ASSERT_TRUE(op != nullptr);
- EXPECT_TRUE(op->isEquivalentTo(boundCRS->transformation().get()));
- {
- auto wkt1 = op->exportToWKT(
- WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)
- .get());
- auto wkt2 = boundCRS->transformation()->exportToWKT(
- WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)
- .get());
- EXPECT_EQ(wkt1, wkt2);
- }
- }
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- GeographicCRS::EPSG_4326, crs);
- ASSERT_TRUE(op != nullptr);
- EXPECT_TRUE(
- op->isEquivalentTo(boundCRS->transformation()->inverse().get()));
- {
- auto wkt1 = op->exportToWKT(
- WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)
- .get());
- auto wkt2 = boundCRS->transformation()->inverse()->exportToWKT(
- WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)
- .get());
- EXPECT_EQ(wkt1, wkt2);
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, createOperation_fallback_to_proj4_strings) {
- auto objDest = PROJStringParser().createFromPROJString(
- "+proj=longlat +geoc +datum=WGS84 +type=crs");
- auto dest = nn_dynamic_pointer_cast<GeographicCRS>(objDest);
- ASSERT_TRUE(dest != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- GeographicCRS::EPSG_4326, NN_CHECK_ASSERT(dest));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=longlat +geoc +datum=WGS84 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- createOperation_fallback_to_proj4_strings_regular_with_datum_to_projliteral) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=11 +datum=NAD27 +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=WGS84 +over +type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +inv +proj=utm +zone=11 +datum=NAD27 "
- "+step +proj=longlat +datum=WGS84 +over "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- createOperation_fallback_to_proj4_strings_proj_NAD83_to_projliteral) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=11 +datum=NAD83 +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=WGS84 +over +type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +inv +proj=utm +zone=11 +ellps=GRS80 "
- "+step +proj=longlat +datum=WGS84 +over "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- createOperation_fallback_to_proj4_strings_geog_NAD83_to_projliteral) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=NAD83 +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=WGS84 +over +type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=longlat +datum=WGS84 +over "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- createOperation_fallback_to_proj4_strings_regular_with_nadgrids_to_projliteral) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=11 +ellps=clrk66 +nadgrids=@conus +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=WGS84 +over +type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +inv +proj=utm +zone=11 +ellps=clrk66 +nadgrids=@conus "
- "+step +proj=longlat +datum=WGS84 +over "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation,
- createOperation_fallback_to_proj4_strings_projliteral_to_projliteral) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=11 +datum=NAD27 +over +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=WGS84 +over +type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline "
- "+step +inv +proj=utm +zone=11 +datum=NAD27 +over "
- "+step +proj=longlat +datum=WGS84 +over "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(
- operation,
- createOperation_fallback_to_proj4_strings_regular_to_projliteral_with_towgs84) {
- auto objSrc =
- createFromUserInput("EPSG:4326", DatabaseContext::create(), false);
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=31 +ellps=GRS80 +towgs84=1,2,3 +over +type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=utm +zone=31 +ellps=GRS80 +towgs84=1,2,3 +over");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, createOperation_on_crs_with_bound_crs_and_wktext) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=55 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 "
- "+units=m +no_defs +nadgrids=@GDA94_GDA2020_conformal.gsb +ignored1 "
- "+ignored2=val +wktext +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=utm +zone=55 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 "
- "+units=m +no_defs +type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=utm +zone=55 +south "
- "+ellps=GRS80 +step +proj=hgridshift "
- "+grids=@GDA94_GDA2020_conformal.gsb +step +proj=utm +zone=55 "
- "+south +ellps=GRS80");
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, createOperation_ossfuzz_18587) {
- auto objSrc =
- createFromUserInput("EPSG:4326", DatabaseContext::create(), false);
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
-
- // Extremely weird string ! We should likely reject it
- auto objDst = PROJStringParser().createFromPROJString(
- "type=crs proj=pipeline step proj=merc vunits=m nadgrids=@x "
- "proj=\"\nproj=pipeline step\n\"");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- // Just check that we don't go into an infinite recursion
- try {
- CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- } catch (const std::exception &) {
- }
-}
-
-// ---------------------------------------------------------------------------
-
-TEST(operation, derivedGeographicCRS_with_to_wgs84_to_geographicCRS) {
- auto objSrc = PROJStringParser().createFromPROJString(
- "+proj=ob_tran +o_proj=latlon +lat_0=0 +lon_0=180 +o_lat_p=18.0 "
- "+o_lon_p=-200.0 +ellps=WGS84 +towgs84=1,2,3 +type=crs");
- auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
- ASSERT_TRUE(src != nullptr);
- auto objDst = PROJStringParser().createFromPROJString(
- "+proj=longlat +datum=WGS84 +type=crs");
- auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
- ASSERT_TRUE(dst != nullptr);
-
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
- ASSERT_TRUE(op != nullptr);
- std::string pipeline(
- "+proj=pipeline "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +inv +proj=ob_tran +o_proj=latlon +lat_0=0 +lon_0=180 "
- "+o_lat_p=18 +o_lon_p=-200 +ellps=WGS84 "
- "+step +proj=push +v_3 "
- "+step +proj=cart +ellps=WGS84 "
- "+step +proj=helmert +x=1 +y=2 +z=3 "
- "+step +inv +proj=cart +ellps=WGS84 "
- "+step +proj=pop +v_3 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- pipeline);
-
- auto op2 = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(src),
- nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326));
- ASSERT_TRUE(op2 != nullptr);
- EXPECT_EQ(op2->exportToPROJString(PROJStringFormatter::create().get()),
- pipeline + " +step +proj=axisswap +order=2,1");
- }
-
- {
- auto op = CoordinateOperationFactory::create()->createOperation(
- NN_CHECK_ASSERT(dst), NN_CHECK_ASSERT(src));
- ASSERT_TRUE(op != nullptr);
- std::string pipeline(
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=push +v_3 "
- "+step +proj=cart +ellps=WGS84 "
- "+step +proj=helmert +x=-1 +y=-2 +z=-3 "
- "+step +inv +proj=cart +ellps=WGS84 "
- "+step +proj=pop +v_3 "
- "+step +proj=ob_tran +o_proj=latlon +lat_0=0 +lon_0=180 "
- "+o_lat_p=18 +o_lon_p=-200 +ellps=WGS84 "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
- EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline " + pipeline);
-
- auto op2 = CoordinateOperationFactory::create()->createOperation(
- nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
- NN_CHECK_ASSERT(src));
- ASSERT_TRUE(op2 != nullptr);
- EXPECT_EQ(op2->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 " + pipeline);
- }
-}
-
-// ---------------------------------------------------------------------------
-
TEST(operation, geographic_topocentric) {
auto wkt =
"PROJCRS[\"EPSG topocentric example A\",\n"
diff --git a/test/unit/test_operationfactory.cpp b/test/unit/test_operationfactory.cpp
new file mode 100644
index 00000000..b71c3d66
--- /dev/null
+++ b/test/unit/test_operationfactory.cpp
@@ -0,0 +1,5954 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2019 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+#include "test_primitives.hpp"
+
+// to be able to use internal::replaceAll
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/coordinatesystem.hpp"
+#include "proj/crs.hpp"
+#include "proj/datum.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+
+#include "proj_constants.h"
+
+#include <string>
+#include <vector>
+
+using namespace osgeo::proj::common;
+using namespace osgeo::proj::crs;
+using namespace osgeo::proj::cs;
+using namespace osgeo::proj::datum;
+using namespace osgeo::proj::io;
+using namespace osgeo::proj::internal;
+using namespace osgeo::proj::metadata;
+using namespace osgeo::proj::operation;
+using namespace osgeo::proj::util;
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(
+ op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_default) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setAllowUseIntermediateCRS(
+ CoordinateOperationContext::IntermediateCRSUse::NEVER);
+
+ // Directly found in database
+ {
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"), // Pulkovo 42
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
+ ctxt);
+ ASSERT_EQ(list.size(), 3U);
+ // Romania has a larger area than Poland (given our approx formula)
+ EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
+ EXPECT_EQ(list[1]->getEPSGCode(), 1644); // Poland - 1m
+ EXPECT_EQ(list[2]->nameStr(),
+ "Ballpark geographic offset from Pulkovo 1942(58) to ETRS89");
+
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
+ "+step +proj=cart +ellps=krass +step +proj=helmert +x=2.3287 "
+ "+y=-147.0425 +z=-92.0802 +rx=0.3092483 +ry=-0.32482185 "
+ "+rz=-0.49729934 +s=5.68906266 +convention=coordinate_frame +step "
+ "+inv +proj=cart +ellps=GRS80 +step +proj=pop +v_3 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+ }
+
+ // Reverse case
+ {
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4258"),
+ authFactory->createCoordinateReferenceSystem("4179"), ctxt);
+ ASSERT_EQ(list.size(), 3U);
+ // Romania has a larger area than Poland (given our approx formula)
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of Pulkovo 1942(58) to ETRS89 (4)"); // Romania - 3m
+
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
+ "+step +proj=cart +ellps=GRS80 +step +inv +proj=helmert +x=2.3287 "
+ "+y=-147.0425 +z=-92.0802 +rx=0.3092483 +ry=-0.32482185 "
+ "+rz=-0.49729934 +s=5.68906266 +convention=coordinate_frame +step "
+ "+inv +proj=cart +ellps=krass +step +proj=pop +v_3 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_match_by_name) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setAllowUseIntermediateCRS(
+ CoordinateOperationContext::IntermediateCRSUse::NEVER);
+ auto NAD27 = GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ GeographicCRS::EPSG_4267->nameStr()),
+ GeographicCRS::EPSG_4267->datum(),
+ GeographicCRS::EPSG_4267->datumEnsemble(),
+ GeographicCRS::EPSG_4267->coordinateSystem());
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NAD27, GeographicCRS::EPSG_4326, ctxt);
+ auto listInv = CoordinateOperationFactory::create()->createOperations(
+ GeographicCRS::EPSG_4326, NAD27, ctxt);
+ auto listRef = CoordinateOperationFactory::create()->createOperations(
+ GeographicCRS::EPSG_4267, GeographicCRS::EPSG_4326, ctxt);
+ EXPECT_EQ(list.size(), listRef.size());
+ EXPECT_EQ(listInv.size(), listRef.size());
+ EXPECT_GE(listRef.size(), 2U);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_filter_accuracy) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 1.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"),
+ authFactory->createCoordinateReferenceSystem("4258"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->getEPSGCode(), 1644); // Poland - 1m
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.9);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"),
+ authFactory->createCoordinateReferenceSystem("4258"), ctxt);
+ ASSERT_EQ(list.size(), 0U);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_filter_bbox) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ // INSERT INTO "area" VALUES('EPSG','1197','Romania','Romania - onshore and
+ // offshore.',43.44,48.27,20.26,31.41,0);
+ {
+ auto ctxt = CoordinateOperationContext::create(
+ authFactory, Extent::createFromBBOX(20.26, 43.44, 31.41, 48.27),
+ 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"),
+ authFactory->createCoordinateReferenceSystem("4258"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
+ }
+ {
+ auto ctxt = CoordinateOperationContext::create(
+ authFactory, Extent::createFromBBOX(20.26 + .1, 43.44 + .1,
+ 31.41 - .1, 48.27 - .1),
+ 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"),
+ authFactory->createCoordinateReferenceSystem("4258"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
+ }
+ {
+ auto ctxt = CoordinateOperationContext::create(
+ authFactory, Extent::createFromBBOX(20.26 - .1, 43.44 - .1,
+ 31.41 + .1, 48.27 + .1),
+ 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"),
+ authFactory->createCoordinateReferenceSystem("4258"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=noop");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_incompatible_area) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4267"), // NAD27
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS 89
+ ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=noop");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_inverse_needed) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setUsePROJAlternativeGridNames(false);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4275"), // NTF
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
+ ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
+ "+step +proj=cart +ellps=clrk80ign +step +proj=helmert +x=-168 "
+ "+y=-60 +z=320 +step +inv +proj=cart +ellps=GRS80 +step +proj=pop "
+ "+v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=2,1");
+ EXPECT_EQ(list[1]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step "
+ "+proj=hgridshift +grids=fr_ign_ntf_r93.tif +step "
+ "+proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4275"), // NTF
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
+ ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step "
+ "+proj=hgridshift +grids=fr_ign_ntf_r93.tif +step "
+ "+proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
+ authFactory->createCoordinateReferenceSystem("4275"), // NTF
+ ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv "
+ "+proj=hgridshift +grids=fr_ign_ntf_r93.tif +step "
+ "+proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_ntv1_ntv2_ctable2) {
+ 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 list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4267"), // NAD27
+ authFactory->createCoordinateReferenceSystem("4269"), // NAD83
+ ctxt);
+ ASSERT_EQ(list.size(), 10U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
+ "+grids=ca_nrc_ntv1_can.tif +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
+ "+grids=ca_nrc_ntv2_0.tif +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(list[2]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
+ "+grids=us_noaa_conus.tif +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_NAD27_to_WGS84) {
+ 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 list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4267"), // NAD27
+ authFactory->createCoordinateReferenceSystem("4326"), // WGS84
+ ctxt);
+ ASSERT_EQ(list.size(), 79U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "NAD27 to WGS 84 (33)"); // 1.0 m, Canada - NAD27
+ EXPECT_EQ(list[1]->nameStr(),
+ "NAD27 to WGS 84 (3)"); // 20.0 m, Canada - NAD27
+ EXPECT_EQ(list[2]->nameStr(),
+ "NAD27 to WGS 84 (79)"); // 5.0 m, USA - CONUS including EEZ
+ EXPECT_EQ(list[3]->nameStr(),
+ "NAD27 to WGS 84 (4)"); // 10.0 m, USA - CONUS - onshore
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_NAD27_to_WGS84_G1762) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+
+ auto authFactoryEPSG =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // NAD27
+ authFactoryEPSG->createCoordinateReferenceSystem("4267"),
+ // WGS84 (G1762)
+ authFactoryEPSG->createCoordinateReferenceSystem("9057"), ctxt);
+ ASSERT_GE(list.size(), 78U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "NAD27 to WGS 84 (33) + WGS 84 to WGS 84 (G1762)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=ca_nrc_ntv2_0.tif "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+ EXPECT_EQ(list[1]->nameStr(),
+ "NAD27 to WGS 84 (3) + WGS 84 to WGS 84 (G1762)");
+ EXPECT_EQ(list[2]->nameStr(),
+ "NAD27 to WGS 84 (79) + WGS 84 to WGS 84 (G1762)");
+ EXPECT_EQ(list[3]->nameStr(),
+ "NAD27 to WGS 84 (4) + WGS 84 to WGS 84 (G1762)");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_WGS84_G1674_to_WGS84_G1762) {
+ // Check that particular behavior with WGS 84 (Gxxx) related to
+ // 'geodetic_datum_preferred_hub' table and custom no-op transformations
+ // between WGS 84 and WGS 84 (Gxxx) doesn't affect direct transformations
+ // to those realizations.
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+
+ auto authFactoryEPSG =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // WGS84 (G1674)
+ authFactoryEPSG->createCoordinateReferenceSystem("9056"),
+ // WGS84 (G1762)
+ authFactoryEPSG->createCoordinateReferenceSystem("9057"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=-0.004 +y=0.003 +z=0.004 +rx=0.00027 "
+ "+ry=-0.00027 +rz=0.00038 +s=-0.0069 "
+ "+convention=coordinate_frame "
+ "+step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_EPSG_4240_Indian1975_to_EPSG_4326) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4240"), // Indian 1975
+ authFactory->createCoordinateReferenceSystem("4326"), ctxt);
+ ASSERT_EQ(list.size(), 3U);
+
+ // Indian 1975 to WGS 84 (4), 3.0 m, Thailand - onshore
+ EXPECT_EQ(list[0]->getEPSGCode(), 1812);
+
+ // The following is the one we want to see. It has a lesser accuracy than
+ // the above one and the same bbox, but the name of its area of use is
+ // slightly different
+ // Indian 1975 to WGS 84 (2), 5.0 m, Thailand - onshore and Gulf of Thailand
+ EXPECT_EQ(list[1]->getEPSGCode(), 1304);
+
+ // Indian 1975 to WGS 84 (3), 1.0 m, Thailand - Bongkot field
+ EXPECT_EQ(list[2]->getEPSGCode(), 1537);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_helmert_geog3D_crs) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4939"), // GDA94 3D
+ authFactory->createCoordinateReferenceSystem("7843"), // GDA2020 3D
+ ctxt);
+ ASSERT_EQ(list.size(), 1U);
+
+ // Check there is no push / pop of v_3
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
+ "+step +proj=cart +ellps=GRS80 "
+ "+step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 "
+ "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 "
+ "+convention=coordinate_frame "
+ "+step +inv +proj=cart +ellps=GRS80 "
+ "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_helmert_geocentric_3D) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // GDA94 geocentric
+ authFactory->createCoordinateReferenceSystem("4348"),
+ // GDA2020 geocentric
+ authFactory->createCoordinateReferenceSystem("7842"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+
+ // Check there is no push / pop of v_3
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 "
+ "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 "
+ "+convention=coordinate_frame");
+ EXPECT_EQ(list[0]->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 "
+ "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 "
+ "+convention=coordinate_frame");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_helmert_geog3D_to_geocentirc) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // GDA94 3D
+ authFactory->createCoordinateReferenceSystem("4939"),
+ // GDA2020 geocentric
+ authFactory->createCoordinateReferenceSystem("7842"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+
+ // Check there is no push / pop of v_3
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
+ "+step +proj=cart +ellps=GRS80 "
+ "+step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 "
+ "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 "
+ "+convention=coordinate_frame");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_invalid_EPSG_ID) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
+ // EPSG:4656 is incorrect. Should be EPSG:8997
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCS[\"ITRF2000\","
+ "DATUM[\"International_Terrestrial_Reference_Frame_2000\","
+ "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
+ "AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6656\"]],"
+ "PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.0174532925199433],"
+ "AUTHORITY[\"EPSG\",\"4656\"]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(crs), GeographicCRS::EPSG_4326, ctxt);
+ ASSERT_EQ(list.size(), 1U);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_datum_ensemble) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
+
+ auto dst_wkt =
+ "GEOGCRS[\"unknown\","
+ " ENSEMBLE[\"World Geodetic System 1984 ensemble\","
+ " MEMBER[\"World Geodetic System 1984 (Transit)\","
+ " ID[\"EPSG\",1166]],"
+ " MEMBER[\"World Geodetic System 1984 (G730)\","
+ " ID[\"EPSG\",1152]],"
+ " MEMBER[\"World Geodetic System 1984 (G873)\","
+ " ID[\"EPSG\",1153]],"
+ " MEMBER[\"World Geodetic System 1984 (G1150)\","
+ " ID[\"EPSG\",1154]],"
+ " MEMBER[\"World Geodetic System 1984 (G1674)\","
+ " ID[\"EPSG\",1155]],"
+ " MEMBER[\"World Geodetic System 1984 (G1762)\","
+ " ID[\"EPSG\",1156]],"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
+ " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],"
+ " ID[\"EPSG\",7030]],"
+ " ENSEMBLEACCURACY[2]],"
+ " PRIMEM[\"Greenwich\",0,"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],"
+ " ID[\"EPSG\",8901]],"
+ " CS[ellipsoidal,2,"
+ " ID[\"EPSG\",6422]],"
+ " AXIS[\"Geodetic latitude (Lat)\",north,"
+ " ORDER[1]],"
+ " AXIS[\"Geodetic longitude (Lon)\",east,"
+ " ORDER[2]],"
+ " ANGLEUNIT[\"degree (supplier to define representation)\","
+ "0.0174532925199433,ID[\"EPSG\",9122]]]";
+ auto dstObj = WKTParser().createFromWKT(dst_wkt);
+ auto dstCRS = nn_dynamic_pointer_cast<CRS>(dstObj);
+ ASSERT_TRUE(dstCRS != nullptr);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
+ NN_NO_CHECK(dstCRS), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "ETRS89 to WGS 84 (1)");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, vertCRS_to_geogCRS_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setUsePROJAlternativeGridNames(false);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem(
+ "3855"), // EGM2008 height
+ authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
+ ctxt);
+ ASSERT_EQ(list.size(), 3U);
+ EXPECT_EQ(
+ list[1]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem(
+ "3855"), // EGM2008 height
+ authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
+ ctxt);
+ ASSERT_EQ(list.size(), 3U);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
+ authFactory->createCoordinateReferenceSystem(
+ "3855"), // EGM2008 height
+ ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +inv +proj=vgridshift +grids=us_nga_egm08_25.tif "
+ "+multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // NGVD29 depth (ftUS)
+ authFactory->createCoordinateReferenceSystem("6359"),
+ authFactory->createCoordinateReferenceSystem("4326"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=affine +s33=-0.304800609601219");
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // NZVD2016 height
+ authFactory->createCoordinateReferenceSystem("7839"),
+ // NZGD2000
+ authFactory->createCoordinateReferenceSystem("4959"), ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=nz_linz_nzgeoid2016.tif "
+ "+multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+ }
+ {
+ // Test actually the database where we derive records using the more
+ // classic 'Geographic3D to GravityRelatedHeight' method from
+ // records using EPSG:9635
+ //'Geog3D to Geog2D+GravityRelatedHeight (US .gtx)' method
+ auto ctxt = CoordinateOperationContext::create(
+ AuthorityFactory::create(DatabaseContext::create(), std::string()),
+ nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // Baltic 1957 height
+ authFactory->createCoordinateReferenceSystem("8357"),
+ // ETRS89
+ authFactory->createCoordinateReferenceSystem("4937"), ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift "
+ "+grids=sk_gku_Slovakia_ETRS89h_to_Baltic1957.tif "
+ "+multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geog3DCRS_to_geog2DCRS_plus_vertCRS_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // ETRS89 (3D)
+ authFactory->createCoordinateReferenceSystem("4937"),
+ // ETRS89 + Baltic 1957 height
+ authFactory->createCoordinateReferenceSystem("8360"), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +inv +proj=vgridshift "
+ "+grids=sk_gku_Slovakia_ETRS89h_to_Baltic1957.tif +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+
+ EXPECT_EQ(list[0]->inverse()->nameStr(),
+ "Inverse of 'ETRS89 to ETRS89 + Baltic 1957 height (1)'");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_noop) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->nameStr(), "Null geographic offset from WGS 84 to WGS 84");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=noop");
+ EXPECT_EQ(op->inverse()->nameStr(), op->nameStr());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_longitude_rotation) {
+
+ auto src = GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "A"),
+ GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(),
+ PrimeMeridian::GREENWICH),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+ auto dest = GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "B"),
+ GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(),
+ PrimeMeridian::PARIS),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+
+ auto op = CoordinateOperationFactory::create()->createOperation(src, dest);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=longlat "
+ "+ellps=WGS84 +pm=paris +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(op->inverse()->exportToWKT(WKTFormatter::create().get()),
+ CoordinateOperationFactory::create()
+ ->createOperation(dest, src)
+ ->exportToWKT(WKTFormatter::create().get()));
+ EXPECT_TRUE(
+ op->inverse()->isEquivalentTo(CoordinateOperationFactory::create()
+ ->createOperation(dest, src)
+ .get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_longitude_rotation_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris)
+ authFactory->createCoordinateReferenceSystem("4275"), // NTF
+ ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ EXPECT_EQ(list[0]->nameStr(), "NTF (Paris) to NTF (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+ EXPECT_EQ(list[1]->nameStr(), "NTF (Paris) to NTF (2)");
+ EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=2.33720833333333 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_concatenated_operation) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setAllowUseIntermediateCRS(
+ CoordinateOperationContext::IntermediateCRSUse::ALWAYS);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris)
+ authFactory->createCoordinateReferenceSystem("4171"), // RGF93
+ ctxt);
+ ASSERT_EQ(list.size(), 4U);
+
+ EXPECT_EQ(list[0]->nameStr(), "NTF (Paris) to RGF93 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=grad +xy_out=rad "
+ "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=clrk80ign "
+ "+step +proj=xyzgridshift +grids=fr_ign_gr3df97a.tif "
+ "+grid_ref=output_crs +ellps=GRS80 "
+ "+step +inv +proj=cart +ellps=GRS80 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+
+ EXPECT_EQ(list[1]->nameStr(), "NTF (Paris) to RGF93 (2)");
+ EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=hgridshift "
+ "+grids=fr_ign_ntf_r93.tif +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<ConcatenatedOperation>(list[0]) !=
+ nullptr);
+ auto grids = list[0]->gridsNeeded(DatabaseContext::create(), false);
+ EXPECT_EQ(grids.size(), 1U);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_ED50_to_WGS72_no_NTF_intermediate) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4230"), // ED50
+ authFactory->createCoordinateReferenceSystem("4322"), // WGS 72
+ ctxt);
+ ASSERT_GE(list.size(), 2U);
+ // We should not use the ancient NTF as an intermediate when looking for
+ // ED50 -> WGS 72 operations.
+ for (const auto &op : list) {
+ EXPECT_TRUE(op->nameStr().find("NTF") == std::string::npos)
+ << op->nameStr();
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_same_grid_name) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4314"), // DHDN
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
+ ctxt);
+ ASSERT_TRUE(!list.empty());
+ EXPECT_EQ(list[0]->nameStr(), "DHDN to ETRS89 (8)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
+ "+grids=de_adv_BETA2007.tif +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_geographic_offset_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4120"), // NTF(Paris)
+ authFactory->createCoordinateReferenceSystem("4121"), // NTF
+ ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Greek to GGRS87 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=-5.86 +dlon=0.28 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_CH1903_to_CH1903plus_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setAllowUseIntermediateCRS(
+ CoordinateOperationContext::IntermediateCRSUse::ALWAYS);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4149"), // CH1903
+ authFactory->createCoordinateReferenceSystem("4150"), // CH1903+
+ ctxt);
+ ASSERT_TRUE(list.size() == 1U);
+
+ EXPECT_EQ(list[0]->nameStr(), "CH1903 to CH1903+ (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=ch_swisstopo_CHENyx06a.tif "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_init_IGNF_to_init_IGNF_context) {
+
+ auto dbContext = DatabaseContext::create();
+
+ auto sourceCRS_obj = PROJStringParser()
+ .attachDatabaseContext(dbContext)
+ .setUsePROJ4InitRules(true)
+ .createFromPROJString("+init=IGNF:NTFG");
+ auto sourceCRS = nn_dynamic_pointer_cast<CRS>(sourceCRS_obj);
+ ASSERT_TRUE(sourceCRS != nullptr);
+
+ auto targetCRS_obj = PROJStringParser()
+ .attachDatabaseContext(dbContext)
+ .setUsePROJ4InitRules(true)
+ .createFromPROJString("+init=IGNF:RGF93G");
+ auto targetCRS = nn_dynamic_pointer_cast<CRS>(targetCRS_obj);
+ ASSERT_TRUE(targetCRS != nullptr);
+
+ auto authFactory = AuthorityFactory::create(dbContext, std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_CHECK_ASSERT(sourceCRS), NN_CHECK_ASSERT(targetCRS), ctxt);
+ ASSERT_EQ(list.size(), 2U);
+
+ EXPECT_EQ(list[0]->nameStr(),
+ "NOUVELLE TRIANGULATION DE LA FRANCE (NTF) vers RGF93 (ETRS89)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=fr_ign_ntf_r93.tif +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_3D) {
+
+ auto geogcrs_m_obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +vunits=m +type=crs");
+ auto geogcrs_m = nn_dynamic_pointer_cast<CRS>(geogcrs_m_obj);
+ ASSERT_TRUE(geogcrs_m != nullptr);
+
+ auto geogcrs_ft_obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +vunits=ft +type=crs");
+ auto geogcrs_ft = nn_dynamic_pointer_cast<CRS>(geogcrs_ft_obj);
+ ASSERT_TRUE(geogcrs_ft != nullptr);
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(geogcrs_m), NN_CHECK_ASSERT(geogcrs_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=unitconvert +z_in=m +z_out=ft");
+ }
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(geogcrs_ft), NN_CHECK_ASSERT(geogcrs_m));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=unitconvert +z_in=ft +z_out=m");
+ }
+
+ auto geogcrs_m_with_pm_obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +pm=paris +vunits=m +type=crs");
+ auto geogcrs_m_with_pm =
+ nn_dynamic_pointer_cast<CRS>(geogcrs_m_with_pm_obj);
+ ASSERT_TRUE(geogcrs_m_with_pm != nullptr);
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(geogcrs_m_with_pm), NN_CHECK_ASSERT(geogcrs_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +z_in=m "
+ "+xy_out=rad +z_out=m +step +inv +proj=longlat +ellps=WGS84 "
+ "+pm=paris +step +proj=unitconvert +xy_in=rad +z_in=m "
+ "+xy_out=deg +z_out=ft");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_3D_lat_long_non_metre_to_geogCRS_longlat) {
+
+ auto wkt = "GEOGCRS[\"my CRS\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " CS[ellipsoidal,3],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"ellipsoidal height\",up,\n"
+ " LENGTHUNIT[\"my_vunit\",0.3]]]";
+ auto srcCRS_obj = WKTParser().createFromWKT(wkt);
+ auto srcCRS = nn_dynamic_pointer_cast<CRS>(srcCRS_obj);
+ ASSERT_TRUE(srcCRS != nullptr);
+
+ auto dstCRS_obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84 +type=crs");
+ auto dstCRS = nn_dynamic_pointer_cast<CRS>(dstCRS_obj);
+ ASSERT_TRUE(dstCRS != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(srcCRS), NN_CHECK_ASSERT(dstCRS));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +z_in=0.3 +z_out=m");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_without_id_to_geogCRS_3D_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto src =
+ authFactory->createCoordinateReferenceSystem("4289"); // Amersfoort
+ auto dst =
+ authFactory->createCoordinateReferenceSystem("4937"); // ETRS89 3D
+ auto list =
+ CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
+ ASSERT_GE(list.size(), 1U);
+ auto wkt2 = "GEOGCRS[\"unnamed\",\n"
+ " DATUM[\"Amersfoort\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\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]],"
+ " USAGE[\n"
+ " SCOPE[\"unknown\"],\n"
+ " AREA[\"Netherlands - onshore\"],\n"
+ " BBOX[50.75,3.2,53.7,7.22]]]\n";
+
+ auto obj = WKTParser().createFromWKT(wkt2);
+ auto src_from_wkt2 = nn_dynamic_pointer_cast<CRS>(obj);
+ ASSERT_TRUE(src_from_wkt2 != nullptr);
+ auto list2 = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src_from_wkt2), dst, ctxt);
+ ASSERT_GE(list.size(), list2.size());
+ for (size_t i = 0; i < list.size(); i++) {
+ const auto &op = list[i];
+ const auto &op2 = list2[i];
+ EXPECT_TRUE(
+ op->isEquivalentTo(op2.get(), IComparable::Criterion::EQUIVALENT))
+ << op->nameStr() << " " << op2->nameStr();
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static GeodeticCRSNNPtr createGeocentricDatumWGS84() {
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 4328)
+ .set(IdentifiedObject::NAME_KEY, "WGS 84");
+ return GeodeticCRS::create(
+ propertiesCRS, GeodeticReferenceFrame::EPSG_6326,
+ CartesianCS::createGeocentric(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+static GeodeticCRSNNPtr createGeocentricKM() {
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(IdentifiedObject::NAME_KEY, "Based on WGS 84");
+ return GeodeticCRS::create(
+ propertiesCRS, GeodeticReferenceFrame::EPSG_6326,
+ CartesianCS::createGeocentric(
+ UnitOfMeasure("kilometre", 1000.0, UnitOfMeasure::Type::LINEAR)));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geogCRS_same_datum) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createGeocentricDatumWGS84(), GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geogCRS_different_datum) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createGeocentricDatumWGS84(), GeographicCRS::EPSG_4269);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->nameStr(),
+ "Ballpark geocentric translation from WGS 84 to NAD83 "
+ "(geocentric) + Conversion from NAD83 "
+ "(geocentric) to NAD83");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geocentricCRS_different_datum) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4269, createGeocentricDatumWGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->nameStr(), "Conversion from NAD83 to NAD83 (geocentric) + "
+ "Ballpark geocentric translation from NAD83 "
+ "(geocentric) to WGS 84");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=GRS80");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geocentricCRS_same_noop) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createGeocentricDatumWGS84(), createGeocentricDatumWGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->nameStr(),
+ "Null geocentric translation from WGS 84 to WGS 84");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=noop");
+ EXPECT_EQ(op->inverse()->nameStr(), op->nameStr());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geocentricCRS_different_ballpark) {
+
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 4328)
+ .set(IdentifiedObject::NAME_KEY, "unknown");
+ auto otherGeocentricCRS = GeodeticCRS::create(
+ propertiesCRS, GeodeticReferenceFrame::EPSG_6269,
+ CartesianCS::createGeocentric(UnitOfMeasure::METRE));
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createGeocentricKM(), otherGeocentricCRS);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(
+ op->nameStr(),
+ "Ballpark geocentric translation from Based on WGS 84 to unknown");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=unitconvert +xy_in=km +z_in=km +xy_out=m +z_out=m");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geogCRS_same_datum_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4326"),
+ // WGS84 geocentric
+ authFactory->createCoordinateReferenceSystem("4978"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+
+ EXPECT_EQ(list[0]->nameStr(),
+ "Conversion from WGS 84 (geog2D) to WGS 84 (geocentric)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=WGS84");
+
+ EXPECT_EQ(list[0]->inverse()->nameStr(),
+ "Conversion from WGS 84 (geocentric) to WGS 84 (geog2D)");
+ EXPECT_EQ(list[0]->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geogCRS_same_datum_context_all_auth) {
+ // This is to check we don't use OGC:CRS84 as a pivot
+ auto authFactoryEPSG =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto authFactoryAll =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt =
+ CoordinateOperationContext::create(authFactoryAll, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactoryEPSG->createCoordinateReferenceSystem("4326"),
+ // WGS84 geocentric
+ authFactoryEPSG->createCoordinateReferenceSystem("4978"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+
+ EXPECT_EQ(list[0]->nameStr(),
+ "Conversion from WGS 84 (geog2D) to WGS 84 (geocentric)");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geocentricCRS_different_datum_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // ITRF2000 (geocentric)
+ authFactory->createCoordinateReferenceSystem("4919"),
+ // ITRF2005 (geocentric)
+ authFactory->createCoordinateReferenceSystem("4896"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "ITRF2000 to ITRF2005 (1)");
+ EXPECT_PRED_FORMAT2(
+ ComparePROJString,
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=helmert +x=-0.0001 "
+ "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
+ "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
+ "+t_epoch=2000 +convention=position_vector");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_geocentricCRS_same_datum_to_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // WGS84 geocentric
+ authFactory->createCoordinateReferenceSystem("4978"),
+ authFactory->createCoordinateReferenceSystem("4326"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Conversion from WGS 84 (geocentric) to WGS 84 (geog2D)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ geog2D_to_geog3D_same_datum_but_with_potential_other_pivot_context) {
+ // Check that when going from geog2D to geog3D of same datum, we don't
+ // try to go through a WGS84 pivot...
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("5365"), // CR 05 2D
+ authFactory->createCoordinateReferenceSystem("5364"), // CR 05 3D
+ ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=noop");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ geogCRS_to_geogCRS_different_datum_though_geocentric_transform_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // ITRF2000 (geog3D)
+ authFactory->createCoordinateReferenceSystem("7909"),
+ // ITRF2005 (geog3D)
+ authFactory->createCoordinateReferenceSystem("7910"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Conversion from ITRF2000 (geog3D) to ITRF2000 (geocentric) + "
+ "ITRF2000 to ITRF2005 (1) + "
+ "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)");
+ EXPECT_PRED_FORMAT2(
+ ComparePROJString,
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
+ "+step +proj=cart +ellps=GRS80 +step +proj=helmert +x=-0.0001 "
+ "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
+ "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
+ "+t_epoch=2000 +convention=position_vector +step +inv "
+ "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad "
+ "+z_in=m +xy_out=deg +z_out=m +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geocentricCRS_different_datum_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // ITRF2000 (geog3D)
+ authFactory->createCoordinateReferenceSystem("7909"),
+ // ITRF2005 (geocentric)
+ authFactory->createCoordinateReferenceSystem("4896"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Conversion from ITRF2000 (geog3D) to ITRF2000 (geocentric) + "
+ "ITRF2000 to ITRF2005 (1)");
+ EXPECT_PRED_FORMAT2(
+ ComparePROJString,
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
+ "+step +proj=cart +ellps=GRS80 +step +proj=helmert +x=-0.0001 "
+ "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
+ "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
+ "+t_epoch=2000 +convention=position_vector");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geogCRS_different_datum_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // ITRF2000 (geocentric)
+ authFactory->createCoordinateReferenceSystem("4919"),
+ // ITRF2005 (geog3D)
+ authFactory->createCoordinateReferenceSystem("7910"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "ITRF2000 to ITRF2005 (1) + "
+ "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)");
+ EXPECT_PRED_FORMAT2(
+ ComparePROJString,
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=helmert +x=-0.0001 "
+ "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
+ "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
+ "+t_epoch=2000 +convention=position_vector +step +inv "
+ "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad "
+ "+z_in=m +xy_out=deg +z_out=m +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, esri_projectedCRS_to_geogCRS_with_ITRF_intermediate_context) {
+ auto dbContext = DatabaseContext::create();
+ auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG");
+ auto authFactoryESRI = AuthorityFactory::create(dbContext, "ESRI");
+ auto ctxt =
+ CoordinateOperationContext::create(authFactoryEPSG, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // NAD_1983_CORS96_StatePlane_North_Carolina_FIPS_3200_Ft_US (projected)
+ authFactoryESRI->createCoordinateReferenceSystem("103501"),
+ // ITRF2005 (geog3D)
+ authFactoryEPSG->createCoordinateReferenceSystem("7910"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of NAD_1983_CORS96_StatePlane_North_Carolina_"
+ "FIPS_3200_Ft_US + "
+ "Conversion from NAD83(CORS96) (geog2D) to NAD83(CORS96) "
+ "(geocentric) + Inverse of ITRF2000 to NAD83(CORS96) (1) + "
+ "ITRF2000 to ITRF2005 (1) + "
+ "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)");
+ EXPECT_PRED_FORMAT2(
+ ComparePROJString,
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft "
+ "+xy_out=m +step +inv +proj=lcc +lat_0=33.75 +lon_0=-79 "
+ "+lat_1=34.3333333333333 +lat_2=36.1666666666667 "
+ "+x_0=609601.219202438 +y_0=0 +ellps=GRS80 +step +proj=cart "
+ "+ellps=GRS80 +step +inv +proj=helmert +x=0.9956 +y=-1.9013 "
+ "+z=-0.5215 +rx=0.025915 +ry=0.009426 +rz=0.011599 +s=0.00062 "
+ "+dx=0.0007 +dy=-0.0007 +dz=0.0005 +drx=6.7e-05 +dry=-0.000757 "
+ "+drz=-5.1e-05 +ds=-0.00018 +t_epoch=1997 "
+ "+convention=coordinate_frame +step +proj=helmert +x=-0.0001 "
+ "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
+ "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
+ "+t_epoch=2000 +convention=position_vector +step +inv +proj=cart "
+ "+ellps=GRS80 +step +proj=unitconvert +xy_in=rad +z_in=m "
+ "+xy_out=deg +z_out=m +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+static ProjectedCRSNNPtr createUTM31_WGS84() {
+ return ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+static ProjectedCRSNNPtr createUTM32_WGS84() {
+ return ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createUTM(PropertyMap(), 32, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_projCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4326, createUTM31_WGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_TRUE(std::dynamic_pointer_cast<Conversion>(op) != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm "
+ "+zone=31 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_longlat_to_geogCS_latlong) {
+
+ auto sourceCRS = GeographicCRS::OGC_CRS84;
+ auto targetCRS = GeographicCRS::EPSG_4326;
+ auto op = CoordinateOperationFactory::create()->createOperation(sourceCRS,
+ targetCRS);
+ ASSERT_TRUE(op != nullptr);
+ auto conv = std::dynamic_pointer_cast<Conversion>(op);
+ ASSERT_TRUE(conv != nullptr);
+ EXPECT_TRUE(op->sourceCRS() &&
+ op->sourceCRS()->isEquivalentTo(sourceCRS.get()));
+ EXPECT_TRUE(op->targetCRS() &&
+ op->targetCRS()->isEquivalentTo(targetCRS.get()));
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=axisswap +order=2,1");
+ auto convInverse = nn_dynamic_pointer_cast<Conversion>(conv->inverse());
+ ASSERT_TRUE(convInverse != nullptr);
+ EXPECT_TRUE(convInverse->sourceCRS() &&
+ convInverse->sourceCRS()->isEquivalentTo(targetCRS.get()));
+ EXPECT_TRUE(convInverse->targetCRS() &&
+ convInverse->targetCRS()->isEquivalentTo(sourceCRS.get()));
+ EXPECT_EQ(conv->method()->exportToWKT(WKTFormatter::create().get()),
+ convInverse->method()->exportToWKT(WKTFormatter::create().get()));
+ EXPECT_TRUE(conv->method()->isEquivalentTo(convInverse->method().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_longlat_to_geogCS_latlong_database) {
+
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ AuthorityFactory::create(DatabaseContext::create(), "OGC")
+ ->createCoordinateReferenceSystem("CRS84"),
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("4326"),
+ ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_longlat_to_projCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::OGC_CRS84, createUTM31_WGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=utm +zone=31 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_different_from_baseCRS_to_projCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4807, createUTM31_WGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(
+ op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=utm +zone=31 "
+ "+ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ geogCRS_different_from_baseCRS_to_projCRS_context_compatible_area) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setAllowUseIntermediateCRS(
+ CoordinateOperationContext::IntermediateCRSUse::ALWAYS);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris)
+ authFactory->createCoordinateReferenceSystem("32631"), // UTM31 WGS84
+ ctxt);
+ ASSERT_EQ(list.size(), 4U);
+ EXPECT_EQ(
+ list[0]->nameStr(),
+ "NTF (Paris) to NTF (1) + Inverse of WGS 84 to NTF (3) + UTM zone 31N");
+ ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U);
+ EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1");
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=hgridshift "
+ "+grids=fr_ign_ntf_r93.tif +step +proj=utm +zone=31 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_projCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createGeocentricDatumWGS84(), createUTM31_WGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
+ "+proj=utm +zone=31 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_geogCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createUTM31_WGS84(), GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_no_id_to_geogCRS_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto src = authFactory->createCoordinateReferenceSystem(
+ "28992"); // Amersfoort / RD New
+ auto dst =
+ authFactory->createCoordinateReferenceSystem("4258"); // ETRS89 2D
+ auto list =
+ CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
+ ASSERT_GE(list.size(), 1U);
+ auto wkt2 =
+ "PROJCRS[\"unknown\",\n"
+ " BASEGEOGCRS[\"Amersfoort\",\n"
+ " DATUM[\"Amersfoort\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128]]],\n"
+ " CONVERSION[\"unknown\",\n"
+ " METHOD[\"Oblique Stereographic\"],\n"
+ " PARAMETER[\"Latitude of natural origin\",52.1561605555556],\n"
+ " PARAMETER[\"Longitude of natural origin\",5.38763888888889],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9999079],\n"
+ " PARAMETER[\"False easting\",155000],\n"
+ " PARAMETER[\"False northing\",463000]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north],\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",28992]]";
+ auto obj = WKTParser().createFromWKT(wkt2);
+ auto src_from_wkt2 = nn_dynamic_pointer_cast<CRS>(obj);
+ ASSERT_TRUE(src_from_wkt2 != nullptr);
+ auto list2 = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src_from_wkt2), dst, ctxt);
+ ASSERT_GE(list.size(), list2.size() - 1);
+ for (size_t i = 0; i < list.size(); i++) {
+ const auto &op = list[i];
+ const auto &op2 = list2[i];
+ EXPECT_TRUE(
+ op->isEquivalentTo(op2.get(), IComparable::Criterion::EQUIVALENT));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_3D_to_geogCRS_3D_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto wkt = "PROJCRS[\"NAD83(HARN) / Oregon GIC Lambert (ft)\",\n"
+ " BASEGEOGCRS[\"NAD83(HARN)\",\n"
+ " DATUM[\"NAD83 (High Accuracy Reference Network)\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " ID[\"EPSG\",4957]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"Lambert Conic Conformal (2SP)\",\n"
+ " ID[\"EPSG\",9802]],\n"
+ " PARAMETER[\"Latitude of false origin\",41.75,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8821]],\n"
+ " PARAMETER[\"Longitude of false origin\",-120.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8822]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",43,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Latitude of 2nd standard parallel\",45.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"Easting at false origin\",1312335.958,\n"
+ " LENGTHUNIT[\"foot\",0.3048],\n"
+ " ID[\"EPSG\",8826]],\n"
+ " PARAMETER[\"Northing at false origin\",0,\n"
+ " LENGTHUNIT[\"foot\",0.3048],\n"
+ " ID[\"EPSG\",8827]]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"easting\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"foot\",0.3048]],\n"
+ " AXIS[\"northing\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"foot\",0.3048]],\n"
+ " AXIS[\"ellipsoidal height (h)\",up,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"foot\",0.3048]]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto src = NN_CHECK_ASSERT(nn_dynamic_pointer_cast<CRS>(obj));
+ auto dst = authFactory->createCoordinateReferenceSystem(
+ "4957"); // NAD83(HARN) (3D)
+ auto list =
+ CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ // Check that z ft->m conversion is done (and just once)
+ "+step +proj=unitconvert +xy_in=ft +z_in=ft +xy_out=m +z_out=m "
+ "+step +inv +proj=lcc +lat_0=41.75 +lon_0=-120.5 +lat_1=43 "
+ "+lat_2=45.5 +x_0=399999.9999984 +y_0=0 +ellps=GRS80 "
+ "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m "
+ "+step +proj=axisswap +order=2,1");
+}
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_3D_to_projCRS_2D_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto wkt =
+ "PROJCRS[\"Projected 3d CRS\",\n"
+ " BASEGEOGCRS[\"JGD2000\",\n"
+ " DATUM[\"Japanese Geodetic Datum 2000\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " ID[\"EPSG\",4947]],\n" // the code is what triggered the bug
+ " CONVERSION[\"Japan Plane Rectangular CS zone VII\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",36,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",137.166666666667,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9999,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]],\n"
+ " ID[\"EPSG\",17807]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"northing (X)\",north,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"easting (Y)\",east,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"ellipsoidal height (h)\",up,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto src = NN_CHECK_ASSERT(nn_dynamic_pointer_cast<CRS>(obj));
+ auto dst =
+ authFactory->createCoordinateReferenceSystem("32653"); // WGS 84 UTM 53
+ // We just want to check that we don't get inconsistent chaining exception
+ auto list =
+ CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
+ ASSERT_GE(list.size(), 1U);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_3D_to_projCRS_with_2D_geocentric_translation) {
+
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto src =
+ authFactory->createCoordinateReferenceSystem("4979"); // WGS 84 3D
+
+ // Azores Central 1948 / UTM zone 26N
+ auto dst = authFactory->createCoordinateReferenceSystem("2189");
+
+ auto list =
+ CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
+ "+step +proj=push +v_3 " // this is what we check. Due to the
+ // target system being 2D only
+ "+step +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=104 +y=-167 +z=38 "
+ "+step +inv +proj=cart +ellps=intl "
+ "+step +proj=pop +v_3 " // this is what we check
+ "+step +proj=utm +zone=26 +ellps=intl");
+
+ auto listReverse =
+ CoordinateOperationFactory::create()->createOperations(dst, src, ctxt);
+ ASSERT_GE(listReverse.size(), 1U);
+ EXPECT_EQ(
+ listReverse[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=utm +zone=26 +ellps=intl "
+ "+step +proj=push +v_3 " // this is what we check
+ "+step +proj=cart +ellps=intl "
+ "+step +proj=helmert +x=-104 +y=167 +z=-38 "
+ "+step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=pop +v_3 " // this is what we check
+ "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createUTM31_WGS84(), createUTM32_WGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step "
+ "+proj=utm +zone=32 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_different_baseCRS) {
+
+ auto utm32 = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4807,
+ Conversion::createUTM(PropertyMap(), 32, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createUTM31_WGS84(), utm32);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step "
+ "+proj=utm +zone=32 +ellps=clrk80ign +pm=paris");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_context_compatible_area) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("32634"), // UTM 34
+ authFactory->createCoordinateReferenceSystem(
+ "2171"), // Pulkovo 42 Poland I
+ ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of UTM zone 34N + Inverse of Pulkovo 1942(58) to WGS 84 "
+ "(1) + Poland zone I");
+ ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U);
+ EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_context_compatible_area_bis) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem(
+ "3844"), // Pulkovo 42 Stereo 70 (Romania)
+ authFactory->createCoordinateReferenceSystem("32634"), // UTM 34
+ ctxt);
+ ASSERT_EQ(list.size(), 3U);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of Stereo 70 + "
+ "Pulkovo 1942(58) to WGS 84 "
+ "(19) + UTM zone 34N");
+ ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U);
+ EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "3");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_context_one_incompatible_area) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("32631"), // UTM 31
+ authFactory->createCoordinateReferenceSystem(
+ "2171"), // Pulkovo 42 Poland I
+ ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of UTM zone 31N + Inverse of Pulkovo 1942(58) to WGS 84 "
+ "(1) + Poland zone I");
+ ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U);
+ EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_context_incompatible_areas) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("32631"), // UTM 31
+ authFactory->createCoordinateReferenceSystem("32633"), // UTM 33
+ ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of UTM zone 31N + UTM zone 33N");
+ ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U);
+ EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "0");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_context_incompatible_areas_ballpark) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("26711"), // UTM 11 NAD27
+ authFactory->createCoordinateReferenceSystem(
+ "3034"), // ETRS89 / LCC Europe
+ ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_TRUE(list[0]->hasBallparkTransformation());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ projCRS_to_projCRS_context_incompatible_areas_crs_extent_use_intersection) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSourceAndTargetCRSExtentUse(
+ CoordinateOperationContext::SourceTargetCRSExtentUse::INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("26711"), // UTM 11 NAD27
+ authFactory->createCoordinateReferenceSystem(
+ "3034"), // ETRS89 / LCC Europe
+ ctxt);
+ ASSERT_GE(list.size(), 0U);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_north_pole_inverted_axis) {
+
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("32661"),
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("5041"),
+ ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_south_pole_inverted_axis) {
+
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("32761"),
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("5042"),
+ ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_through_geog3D) {
+ // Check that when going from projCRS to projCRS, using
+ // geog2D-->geog3D-->geog3D-->geog2D we do not have issues with
+ // inconsistent CRS chaining, due to how we 'hack' a bit some intermediate
+ // steps
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("5367"), // CR05 / CRTM05
+ authFactory->createCoordinateReferenceSystem(
+ "8908"), // CR-SIRGAS / CRTM05
+ ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +inv +proj=tmerc +lat_0=0 +lon_0=-84 +k=0.9999 "
+ "+x_0=500000 +y_0=0 +ellps=WGS84 "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=-0.16959 +y=0.35312 +z=0.51846 "
+ "+rx=-0.03385 +ry=0.16325 +rz=-0.03446 +s=0.03693 "
+ "+convention=position_vector "
+ "+step +inv +proj=cart +ellps=GRS80 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=tmerc +lat_0=0 +lon_0=-84 +k=0.9999 +x_0=500000 "
+ "+y_0=0 +ellps=GRS80");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transform_from_amersfoort_rd_new_to_epsg_4326) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("28992"),
+ authFactory->createCoordinateReferenceSystem("4326"), ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ // The order matters: "Amersfoort to WGS 84 (4)" replaces "Amersfoort to WGS
+ // 84 (3)"
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of RD New + Amersfoort to WGS 84 (4)");
+ EXPECT_EQ(list[1]->nameStr(),
+ "Inverse of RD New + Amersfoort to WGS 84 (3)");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_geogCRS_to_geogCRS) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ boundCRS, GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=push +v_3 "
+ "+step +proj=cart +ellps=clrk80ign +step +proj=helmert +x=1 +y=2 "
+ "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step "
+ "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_geogCRS_to_geodCRS) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ boundCRS, GeodeticCRS::EPSG_4978);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step "
+ "+proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=grad +xy_out=rad "
+ "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris "
+ "+step +proj=cart +ellps=clrk80ign "
+ "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_geogCRS_to_geodCRS_not_related_to_hub) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ boundCRS,
+ // ETRS89 geocentric
+ authFactory->createCoordinateReferenceSystem("4936"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step "
+ "+proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=grad +xy_out=rad "
+ "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=clrk80ign "
+ "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector "
+ "+step +inv +proj=cart +ellps=GRS80 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=cart +ellps=GRS80");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_geogCRS_to_geogCRS_with_area) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4267, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ boundCRS, authFactory->createCoordinateReferenceSystem("4326"));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
+ "+step +proj=cart +ellps=clrk66 +step +proj=helmert +x=1 +y=2 "
+ "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step "
+ "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_geogCRS_to_unrelated_geogCRS) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ boundCRS, GeographicCRS::EPSG_4269);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ CoordinateOperationFactory::create()
+ ->createOperation(GeographicCRS::EPSG_4807,
+ GeographicCRS::EPSG_4269)
+ ->exportToPROJString(PROJStringFormatter::create().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, createOperation_boundCRS_identified_by_datum) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84 +type=crs");
+ auto src = nn_dynamic_pointer_cast<GeographicCRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ auto objDest = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=32 +a=6378249.2 +b=6356515 "
+ "+towgs84=-263.0,6.0,431.0 +no_defs +type=crs");
+ auto dest = nn_dynamic_pointer_cast<BoundCRS>(objDest);
+ ASSERT_TRUE(dest != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=push +v_3 +step +proj=cart +ellps=WGS84 +step "
+ "+proj=helmert +x=263 +y=-6 +z=-431 +step +inv +proj=cart "
+ "+ellps=clrk80ign +step +proj=pop +v_3 +step +proj=utm +zone=32 "
+ "+ellps=clrk80ign");
+
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_TRUE(list[0]->isEquivalentTo(op.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_clrk_66_geogCRS_to_nad83_geogCRS) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=latlong +ellps=clrk66 +nadgrids=ntv1_can.dat,conus +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ auto objDest = PROJStringParser().createFromPROJString(
+ "+proj=latlong +datum=NAD83 +type=crs");
+ auto dest = nn_dynamic_pointer_cast<CRS>(objDest);
+ ASSERT_TRUE(dest != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=ntv1_can.dat,conus "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_clrk_66_projCRS_to_nad83_geogCRS) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=17 +ellps=clrk66 +nadgrids=ntv1_can.dat,conus "
+ "+type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ auto objDest = PROJStringParser().createFromPROJString(
+ "+proj=latlong +datum=NAD83 +type=crs");
+ auto dest = nn_dynamic_pointer_cast<CRS>(objDest);
+ ASSERT_TRUE(dest != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=17 +ellps=clrk66 "
+ "+step +proj=hgridshift +grids=ntv1_can.dat,conus "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_projCRS_to_geogCRS) {
+ auto utm31 = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4807,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ utm31, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ boundCRS, GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=clrk80ign "
+ "+pm=paris +step +proj=push +v_3 +step +proj=cart "
+ "+ellps=clrk80ign +step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 "
+ "+rz=6 +s=7 +convention=position_vector +step +inv +proj=cart "
+ "+ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_geogCRS_to_projCRS) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto utm31 = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto op =
+ CoordinateOperationFactory::create()->createOperation(boundCRS, utm31);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=push +v_3 "
+ "+step +proj=cart +ellps=clrk80ign +step +proj=helmert +x=1 +y=2 "
+ "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step "
+ "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step "
+ "+proj=utm +zone=31 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_geogCRS_to_unrelated_geogCRS_context) {
+ auto src = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ // ETRS89
+ auto dst = authFactory->createCoordinateReferenceSystem("4258");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list =
+ CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ // Check with it is a concatenated operation, since it doesn't particularly
+ // show up in the PROJ string
+ EXPECT_TRUE(dynamic_cast<ConcatenatedOperation *>(list[0].get()) !=
+ nullptr);
+ EXPECT_EQ(list[0]->nameStr(), "Transformation from NTF (Paris) to WGS84 + "
+ "Inverse of ETRS89 to WGS 84 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=grad +xy_out=rad "
+ "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris "
+ "+step +proj=push +v_3 +step +proj=cart +ellps=clrk80ign "
+ "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector "
+ "+step +inv +proj=cart +ellps=GRS80 +step +proj=pop +v_3 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_boundCRS_of_geogCRS) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4326, boundCRS);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
+ "+step +proj=cart +ellps=WGS84 +step +inv +proj=helmert +x=1 "
+ "+y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector "
+ "+step +inv +proj=cart +ellps=clrk80ign +step +proj=pop +v_3 "
+ "+step +proj=longlat +ellps=clrk80ign +pm=paris +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=grad +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_to_geogCRS_same_datum_context) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4269, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ boundCRS, GeographicCRS::EPSG_4269, ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=noop");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_to_geogCRS_hubCRS_and_targetCRS_same_but_baseCRS_not) {
+ const char *wkt =
+ "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " VERT_CS[\"Ellipsoid (US Feet)\",\n"
+ " VERT_DATUM[\"Ellipsoid\",2002],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Up\",UP]]]";
+
+ auto dbContext = DatabaseContext::create();
+ auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt);
+ auto boundCRS = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(boundCRS != nullptr);
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(boundCRS), GeographicCRS::EPSG_4979, ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=unitconvert +z_in=us-ft +z_out=m");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_to_boundCRS) {
+ auto utm31 = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4807,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto utm32 = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4269,
+ Conversion::createUTM(PropertyMap(), 32, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto boundCRS1 = BoundCRS::createFromTOWGS84(
+ utm31, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto boundCRS2 = BoundCRS::createFromTOWGS84(
+ utm32, std::vector<double>{8, 9, 10, 11, 12, 13, 14});
+ auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1,
+ boundCRS2);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=clrk80ign "
+ "+pm=paris +step +proj=push +v_3 +step +proj=cart "
+ "+ellps=clrk80ign +step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 "
+ "+rz=6 +s=7 +convention=position_vector +step +inv +proj=helmert "
+ "+x=8 +y=9 +z=10 +rx=11 +ry=12 +rz=13 +s=14 "
+ "+convention=position_vector +step +inv +proj=cart +ellps=GRS80 "
+ "+step +proj=pop +v_3 +step +proj=utm +zone=32 +ellps=GRS80");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_to_boundCRS_noop_for_TOWGS84) {
+ auto boundCRS1 = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto boundCRS2 = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4269, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1,
+ boundCRS2);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=push +v_3 "
+ "+step +proj=cart +ellps=clrk80ign +step +inv +proj=cart "
+ "+ellps=GRS80 +step +proj=pop +v_3 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_to_boundCRS_unralated_hub) {
+ auto boundCRS1 = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto boundCRS2 = BoundCRS::create(
+ GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4979,
+ Transformation::createGeocentricTranslations(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4979,
+ 1.0, 2.0, 3.0, std::vector<PositionalAccuracyNNPtr>()));
+ auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1,
+ boundCRS2);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ CoordinateOperationFactory::create()
+ ->createOperation(boundCRS1->baseCRS(), boundCRS2->baseCRS())
+ ->exportToPROJString(PROJStringFormatter::create().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_projCRS_towgs84_to_boundCRS_of_projCRS_nadgrids) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=15 +datum=NAD83 +units=m +no_defs +ellps=GRS80 "
+ "+towgs84=0,0,0 +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=15 +datum=NAD27 +units=m +no_defs +ellps=clrk66 "
+ "+nadgrids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=15 +ellps=GRS80 +step "
+ "+inv +proj=hgridshift "
+ "+grids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat +step +proj=utm "
+ "+zone=15 +ellps=clrk66");
+}
+
+// ---------------------------------------------------------------------------
+
+static CRSNNPtr buildCRSFromProjStrThroughWKT(const std::string &projStr) {
+ auto crsFromProj = nn_dynamic_pointer_cast<CRS>(
+ PROJStringParser().createFromPROJString(projStr));
+ if (crsFromProj == nullptr) {
+ throw "crsFromProj == nullptr";
+ }
+ auto crsFromWkt = nn_dynamic_pointer_cast<CRS>(
+ WKTParser().createFromWKT(crsFromProj->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get())));
+ if (crsFromWkt == nullptr) {
+ throw "crsFromWkt == nullptr";
+ }
+ return NN_NO_CHECK(crsFromWkt);
+}
+
+TEST(operation,
+ boundCRS_to_boundCRS_with_base_geog_crs_different_from_source_of_transf) {
+
+ auto src = buildCRSFromProjStrThroughWKT(
+ "+proj=lcc +lat_1=49 +lat_0=49 +lon_0=0 +k_0=0.999877499 +x_0=600000 "
+ "+y_0=200000 +ellps=clrk80ign +pm=paris +towgs84=-168,-60,320,0,0,0,0 "
+ "+units=m +no_defs +type=crs");
+ auto dst = buildCRSFromProjStrThroughWKT(
+ "+proj=longlat +ellps=clrk80ign +pm=paris "
+ "+towgs84=-168,-60,320,0,0,0,0 +no_defs +type=crs");
+
+ auto op = CoordinateOperationFactory::create()->createOperation(src, dst);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=lcc +lat_1=49 +lat_0=49 +lon_0=0 "
+ "+k_0=0.999877499 +x_0=600000 +y_0=200000 +ellps=clrk80ign "
+ "+pm=paris "
+ "+step +proj=longlat +ellps=clrk80ign +pm=paris "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_with_basecrs_with_extent_to_geogCRS) {
+
+ auto wkt =
+ "BOUNDCRS[\n"
+ " SOURCECRS[\n"
+ " PROJCRS[\"NAD83 / California zone 3 (ftUS)\",\n"
+ " BASEGEODCRS[\"NAD83\",\n"
+ " DATUM[\"North American Datum 1983\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"SPCS83 California zone 3 (US Survey "
+ "feet)\",\n"
+ " METHOD[\"Lambert Conic Conformal (2SP)\",\n"
+ " ID[\"EPSG\",9802]],\n"
+ " PARAMETER[\"Latitude of false origin\",36.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8821]],\n"
+ " PARAMETER[\"Longitude of false origin\",-120.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8822]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\","
+ " 38.4333333333333,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Latitude of 2nd standard parallel\","
+ " 37.0666666666667,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"Easting at false origin\",6561666.667,\n"
+ " LENGTHUNIT[\"US survey foot\","
+ " 0.304800609601219],\n"
+ " ID[\"EPSG\",8826]],\n"
+ " PARAMETER[\"Northing at false origin\",1640416.667,\n"
+ " LENGTHUNIT[\"US survey foot\","
+ " 0.304800609601219],\n"
+ " ID[\"EPSG\",8827]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"easting (X)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"US survey foot\","
+ " 0.304800609601219]],\n"
+ " AXIS[\"northing (Y)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"US survey foot\","
+ " 0.304800609601219]],\n"
+ " SCOPE[\"unknown\"],\n"
+ " AREA[\"USA - California - SPCS - 3\"],\n"
+ " BBOX[36.73,-123.02,38.71,-117.83],\n"
+ " ID[\"EPSG\",2227]]],\n"
+ " TARGETCRS[\n"
+ " GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " ID[\"EPSG\",4326]]],\n"
+ " ABRIDGEDTRANSFORMATION[\"NAD83 to WGS 84 (1)\",\n"
+ " METHOD[\"Geocentric translations (geog2D domain)\",\n"
+ " ID[\"EPSG\",9603]],\n"
+ " PARAMETER[\"X-axis translation\",0,\n"
+ " ID[\"EPSG\",8605]],\n"
+ " PARAMETER[\"Y-axis translation\",0,\n"
+ " ID[\"EPSG\",8606]],\n"
+ " PARAMETER[\"Z-axis translation\",0,\n"
+ " ID[\"EPSG\",8607]],\n"
+ " SCOPE[\"unknown\"],\n"
+ " AREA[\"North America - Canada and USA (CONUS, Alaska "
+ "mainland)\"],\n"
+ " BBOX[23.81,-172.54,86.46,-47.74],\n"
+ " ID[\"EPSG\",1188]]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto boundCRS = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(boundCRS != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(boundCRS), GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->nameStr(), "Inverse of SPCS83 California zone 3 (US Survey "
+ "feet) + NAD83 to WGS 84 (1)");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, ETRS89_3D_to_proj_string_with_geoidgrids_nadgrids) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ // ETRS89 3D
+ auto src = authFactory->createCoordinateReferenceSystem("4937");
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 "
+ "+k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel "
+ "+nadgrids=rdtrans2008.gsb +geoidgrids=naptrans2008.gtx +units=m "
+ "+type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ src, NN_NO_CHECK(dst), ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +inv +proj=vgridshift +grids=naptrans2008.gtx "
+ "+multiplier=1 "
+ "+step +inv +proj=hgridshift +grids=rdtrans2008.gsb "
+ "+step +proj=sterea +lat_0=52.1561605555556 "
+ "+lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 "
+ "+y_0=463000 +ellps=bessel");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, nadgrids_with_pm) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=tmerc +lat_0=39.66666666666666 +lon_0=1 +k=1 +x_0=200000 "
+ "+y_0=300000 +ellps=intl +nadgrids=foo.gsb +pm=lisbon "
+ "+units=m +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto dst = authFactory->createCoordinateReferenceSystem("4326");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src), dst, ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=tmerc +lat_0=39.6666666666667 +lon_0=1 "
+ "+k=1 +x_0=200000 +y_0=300000 +ellps=intl +pm=lisbon "
+ // Check that there is no extra +step +proj=longlat +pm=lisbon
+ "+step +proj=hgridshift +grids=foo.gsb "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+
+ // ETRS89
+ dst = authFactory->createCoordinateReferenceSystem("4258");
+ list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src), dst, ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=tmerc +lat_0=39.6666666666667 +lon_0=1 "
+ "+k=1 +x_0=200000 +y_0=300000 +ellps=intl +pm=lisbon "
+ // Check that there is no extra +step +proj=longlat +pm=lisbon
+ "+step +proj=hgridshift +grids=foo.gsb "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+
+ // From WKT BOUNDCRS
+ auto formatter = WKTFormatter::create(WKTFormatter::Convention::WKT2_2019);
+ auto src_wkt = src->exportToWKT(formatter.get());
+ auto objFromWkt = WKTParser().createFromWKT(src_wkt);
+ auto crsFromWkt = nn_dynamic_pointer_cast<BoundCRS>(objFromWkt);
+ ASSERT_TRUE(crsFromWkt);
+ list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(crsFromWkt), dst, ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=tmerc +lat_0=39.6666666666667 +lon_0=1 "
+ "+k=1 +x_0=200000 +y_0=300000 +ellps=intl +pm=lisbon "
+ // Check that there is no extra +step +proj=longlat +pm=lisbon
+ "+step +proj=hgridshift +grids=foo.gsb "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, WGS84_G1762_to_compoundCRS_with_bound_vertCRS) {
+ auto authFactoryEPSG =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ // WGS 84 (G1762) 3D
+ auto src = authFactoryEPSG->createCoordinateReferenceSystem("7665");
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=NAD83 +geoidgrids=@foo.gtx +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ src, NN_NO_CHECK(dst), ctxt);
+ ASSERT_GE(list.size(), 53U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of WGS 84 to WGS 84 (G1762) + "
+ "Inverse of unknown to WGS84 ellipsoidal height + "
+ "Inverse of NAD83 to WGS 84 (1) + "
+ "axis order change (2D)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +inv +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+static VerticalCRSNNPtr createVerticalCRS() {
+ PropertyMap propertiesVDatum;
+ propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 5101)
+ .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn");
+ auto vdatum = VerticalReferenceFrame::create(propertiesVDatum);
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 5701)
+ .set(IdentifiedObject::NAME_KEY, "ODN height");
+ return VerticalCRS::create(
+ propertiesCRS, vdatum,
+ VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_geogCRS) {
+
+ auto compound = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{GeographicCRS::EPSG_4326, createVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ compound, GeographicCRS::EPSG_4807);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ CoordinateOperationFactory::create()
+ ->createOperation(GeographicCRS::EPSG_4326,
+ GeographicCRS::EPSG_4807)
+ ->exportToPROJString(PROJStringFormatter::create().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+static BoundCRSNNPtr createBoundVerticalCRS() {
+ auto vertCRS = createVerticalCRS();
+ auto transformation =
+ Transformation::createGravityRelatedHeightToGeographic3D(
+ PropertyMap(), vertCRS, GeographicCRS::EPSG_4979, nullptr,
+ "us_nga_egm08_25.tif", std::vector<PositionalAccuracyNNPtr>());
+ return BoundCRS::create(vertCRS, GeographicCRS::EPSG_4979, transformation);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_height_to_PROJ_string) {
+ auto transf = createBoundVerticalCRS()->transformation();
+ EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_nga_egm08_25.tif "
+ "+multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+
+ auto grids = transf->gridsNeeded(DatabaseContext::create(), false);
+ ASSERT_EQ(grids.size(), 1U);
+ auto gridDesc = *(grids.begin());
+ EXPECT_EQ(gridDesc.shortName, "us_nga_egm08_25.tif");
+ EXPECT_TRUE(gridDesc.packageName.empty());
+ EXPECT_EQ(gridDesc.url, "https://cdn.proj.org/us_nga_egm08_25.tif");
+ if (gridDesc.available) {
+ EXPECT_TRUE(!gridDesc.fullName.empty()) << gridDesc.fullName;
+ EXPECT_TRUE(gridDesc.fullName.find(gridDesc.shortName) !=
+ std::string::npos)
+ << gridDesc.fullName;
+ } else {
+ EXPECT_TRUE(gridDesc.fullName.empty()) << gridDesc.fullName;
+ }
+ EXPECT_EQ(gridDesc.directDownload, true);
+ EXPECT_EQ(gridDesc.openLicense, true);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_Geographic3D_to_GravityRelatedHeight_gtx) {
+ auto wkt =
+ "COORDINATEOPERATION[\"ETRS89 to NAP height (1)\",\n"
+ " VERSION[\"RDNAP-Nld 2008\"],\n"
+ " SOURCECRS[\n"
+ " GEOGCRS[\"ETRS89\",\n"
+ " DATUM[\"European Terrestrial Reference System 1989\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,3],\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"
+ " AXIS[\"ellipsoidal height (h)\",up,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",4937]]],\n"
+ " TARGETCRS[\n"
+ " VERTCRS[\"NAP height\",\n"
+ " VDATUM[\"Normaal Amsterdams Peil\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",5709]]],\n"
+ " METHOD[\"Geographic3D to GravityRelatedHeight (US .gtx)\",\n"
+ " ID[\"EPSG\",9665]],\n"
+ " PARAMETERFILE[\"Geoid (height correction) model "
+ "file\",\"naptrans2008.gtx\"],\n"
+ " OPERATIONACCURACY[0.01],\n"
+ " USAGE[\n"
+ " SCOPE[\"unknown\"],\n"
+ " AREA[\"Netherlands - onshore\"],\n"
+ " BBOX[50.75,3.2,53.7,7.22]],\n"
+ " ID[\"EPSG\",7001]]";
+ ;
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+
+ // Check that we correctly inverse files in the case of
+ // "Geographic3D to GravityRelatedHeight (US .gtx)"
+ EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +inv +proj=vgridshift "
+ "+grids=naptrans2008.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_ntv2_to_PROJ_string) {
+ auto transformation = Transformation::createNTv2(
+ PropertyMap(), GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326,
+ "foo.gsb", std::vector<PositionalAccuracyNNPtr>());
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step "
+ "+proj=hgridshift +grids=foo.gsb +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_VERTCON_to_PROJ_string) {
+ auto verticalCRS1 = createVerticalCRS();
+
+ auto verticalCRS2 = VerticalCRS::create(
+ PropertyMap(), VerticalReferenceFrame::create(PropertyMap()),
+ VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
+
+ // Use of this type of transformation is a bit of non-sense here
+ // since it should normally be used with NGVD29 and NAVD88 for VerticalCRS,
+ // and NAD27/NAD83 as horizontal CRS...
+ auto vtransformation = Transformation::createVERTCON(
+ PropertyMap(), verticalCRS1, verticalCRS2, "bla.gtx",
+ std::vector<PositionalAccuracyNNPtr>());
+ EXPECT_EQ(vtransformation->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=vgridshift +grids=bla.gtx +multiplier=0.001");
+}
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_NZLVD_to_PROJ_string) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ auto op = factory->createCoordinateOperation("7860", false);
+ EXPECT_EQ(op->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5, dbContext)
+ .get()),
+ "+proj=vgridshift +grids=nz_linz_auckht1946-nzvd2016.tif "
+ "+multiplier=1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_BEV_AT_to_PROJ_string) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ auto op = factory->createCoordinateOperation("9275", false);
+ EXPECT_EQ(op->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5, dbContext)
+ .get()),
+ "+proj=vgridshift +grids=at_bev_GV_Hoehengrid_V1.tif "
+ "+multiplier=1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_longitude_rotation_to_PROJ_string) {
+
+ auto src = GeographicCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::create(
+ PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(), PrimeMeridian::GREENWICH),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+ auto dest = GeographicCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::create(
+ PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(), PrimeMeridian::PARIS),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+ auto transformation = Transformation::createLongitudeRotation(
+ PropertyMap(), src, dest, Angle(10));
+ EXPECT_TRUE(transformation->validateParameters().empty());
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=WGS84 +pm=10 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(transformation->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=WGS84 +pm=-10 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_Geographic2D_offsets_to_PROJ_string) {
+
+ auto transformation = Transformation::createGeographic2DOffsets(
+ PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326,
+ Angle(0.5), Angle(-1), {});
+ EXPECT_TRUE(transformation->validateParameters().empty());
+
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=1800 +dlon=-3600 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(transformation->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=-1800 +dlon=3600 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_Geographic3D_offsets_to_PROJ_string) {
+
+ auto transformation = Transformation::createGeographic3DOffsets(
+ PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326,
+ Angle(0.5), Angle(-1), Length(2), {});
+ EXPECT_TRUE(transformation->validateParameters().empty());
+
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=1800 +dlon=-3600 +dh=2 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(transformation->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=-1800 +dlon=3600 +dh=-2 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ transformation_Geographic2D_with_height_offsets_to_PROJ_string) {
+
+ auto transformation = Transformation::createGeographic2DWithHeightOffsets(
+ PropertyMap(),
+ CompoundCRS::create(PropertyMap(),
+ {GeographicCRS::EPSG_4326, createVerticalCRS()}),
+ GeographicCRS::EPSG_4326, Angle(0.5), Angle(-1), Length(2), {});
+ EXPECT_TRUE(transformation->validateParameters().empty());
+
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=1800 +dlon=-3600 +dh=2 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(transformation->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=-1800 +dlon=3600 +dh=-2 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_vertical_offset_to_PROJ_string) {
+
+ auto transformation = Transformation::createVerticalOffset(
+ PropertyMap(), createVerticalCRS(), createVerticalCRS(), Length(1), {});
+ EXPECT_TRUE(transformation->validateParameters().empty());
+
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=geogoffset +dh=1");
+ EXPECT_EQ(transformation->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=geogoffset +dh=-1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_with_boundVerticalCRS_to_geogCRS) {
+
+ auto compound = CompoundCRS::create(
+ PropertyMap(), std::vector<CRSNNPtr>{GeographicCRS::EPSG_4326,
+ createBoundVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ compound, GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(
+ op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift "
+ "+grids=us_nga_egm08_25.tif +multiplier=1 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_with_boundGeogCRS_to_geogCRS) {
+
+ auto geogCRS = GeographicCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::create(
+ PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(), PrimeMeridian::GREENWICH),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+ auto horizBoundCRS = BoundCRS::createFromTOWGS84(
+ geogCRS, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto compound = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{horizBoundCRS, createVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ compound, GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector "
+ "+step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_with_boundGeogCRS_and_boundVerticalCRS_to_geogCRS) {
+
+ auto horizBoundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto compound = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{horizBoundCRS, createBoundVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ compound, GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+ // Not completely sure the order of horizontal and vertical operations
+ // makes sense
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=grad +xy_out=rad "
+ "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=clrk80ign "
+ "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector "
+ "+step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+
+ auto grids = op->gridsNeeded(DatabaseContext::create(), false);
+ EXPECT_EQ(grids.size(), 1U);
+
+ auto opInverse = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4979, compound);
+ ASSERT_TRUE(opInverse != nullptr);
+ EXPECT_TRUE(opInverse->inverse()->isEquivalentTo(op.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_with_boundProjCRS_and_boundVerticalCRS_to_geogCRS) {
+
+ auto horizBoundCRS = BoundCRS::createFromTOWGS84(
+ ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4807,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)),
+ std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto compound = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{horizBoundCRS, createBoundVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ compound, GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+ // Not completely sure the order of horizontal and vertical operations
+ // makes sense
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=utm +zone=31 +ellps=clrk80ign +pm=paris "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=clrk80ign "
+ "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector "
+ "+step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+
+ auto opInverse = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4979, compound);
+ ASSERT_TRUE(opInverse != nullptr);
+ EXPECT_TRUE(opInverse->inverse()->isEquivalentTo(op.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ compoundCRS_with_boundVerticalCRS_from_geoidgrids_with_m_to_geogCRS) {
+
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84 +geoidgrids=@foo.gtx +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_NO_CHECK(src), GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->nameStr(), "axis order change (2D) + "
+ "unknown to WGS84 ellipsoidal height");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ compoundCRS_with_boundVerticalCRS_from_geoidgrids_with_ftus_to_geogCRS) {
+
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84 +geoidgrids=@foo.gtx +vunits=us-ft "
+ "+type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_NO_CHECK(src), GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->nameStr(), "axis order change (2D) + "
+ "Transformation from unknown to unknown + "
+ "unknown to WGS84 ellipsoidal height");
+ EXPECT_EQ(
+ op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad +z_out=m "
+ "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ compoundCRS_with_boundProjCRS_with_ftus_and_boundVerticalCRS_to_geogCRS) {
+
+ auto wkt =
+ "COMPD_CS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet + "
+ "NAVD88 height - Geoid12B (US Feet)\",\n"
+ " PROJCS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"Degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",30],\n"
+ " PARAMETER[\"central_meridian\",-87.5],\n"
+ " PARAMETER[\"scale_factor\",0.999933333333333],\n"
+ " PARAMETER[\"false_easting\",1968500],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " AUTHORITY[\"ESRI\",\"102630\"]],\n"
+ " VERT_CS[\"NAVD88 height (ftUS)\",\n"
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"foo.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"5103\"]],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"6360\"]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_NO_CHECK(crs), GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=us-ft +xy_out=m "
+ "+step +inv +proj=tmerc +lat_0=30 +lon_0=-87.5 "
+ "+k=0.999933333333333 +x_0=600000 +y_0=0 +ellps=GRS80 "
+ "+step +proj=unitconvert +z_in=us-ft +z_out=m "
+ "+step +proj=vgridshift +grids=foo.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ compoundCRS_with_boundVerticalCRS_from_grids_to_geogCRS_with_ftus_ctxt) {
+
+ auto dbContext = DatabaseContext::create();
+
+ const char *wktSrc =
+ "COMPD_CS[\"NAD83 + NAVD88 height - Geoid12B (Meters)\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n"
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"@foo.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"5103\"]],\n"
+ " UNIT[\"metre\",1.0,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5703\"]]]";
+ auto objSrc =
+ WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc);
+ auto srcCRS = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
+ ASSERT_TRUE(srcCRS != nullptr);
+
+ const char *wktDst =
+ "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " VERT_CS[\"Ellipsoid (US Feet)\",\n"
+ " VERT_DATUM[\"Ellipsoid\",2002],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Up\",UP]]]";
+ auto objDst =
+ WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktDst);
+ auto dstCRS = nn_dynamic_pointer_cast<GeographicCRS>(objDst);
+ ASSERT_TRUE(dstCRS != nullptr);
+
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +z_in=m "
+ "+xy_out=deg +z_out=us-ft "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_with_boundGeogCRS_boundVerticalCRS_from_grids_to_boundGeogCRS_with_ftus_ctxt) {
+
+ // Variant of above but with TOWGS84 in source & target CRS
+
+ auto dbContext = DatabaseContext::create();
+
+ const char *wktSrc =
+ "COMPD_CS[\"NAD83 + NAVD88 height - Geoid12B (Meters)\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n"
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"@foo.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"5103\"]],\n"
+ " UNIT[\"metre\",1.0,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5703\"]]]";
+ auto objSrc =
+ WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc);
+ auto srcCRS = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
+ ASSERT_TRUE(srcCRS != nullptr);
+
+ const char *wktDst =
+ "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " VERT_CS[\"Ellipsoid (US Feet)\",\n"
+ " VERT_DATUM[\"Ellipsoid\",2002],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Up\",UP]]]";
+ auto objDst =
+ WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktDst);
+ auto dstCRS = nn_dynamic_pointer_cast<BoundCRS>(objDst);
+ ASSERT_TRUE(dstCRS != nullptr);
+
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=rad +z_in=m "
+ "+xy_out=deg +z_out=us-ft");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_with_boundVerticalCRS_from_grids_to_boundGeogCRS_with_ftus_ctxt) {
+
+ // Variant of above but with TOWGS84 in target CRS only
+
+ auto dbContext = DatabaseContext::create();
+
+ const char *wktSrc =
+ "COMPD_CS[\"NAD83 + NAVD88 height - Geoid12B (Meters)\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n"
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"@foo.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"5103\"]],\n"
+ " UNIT[\"metre\",1.0,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5703\"]]]";
+ auto objSrc =
+ WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc);
+ auto srcCRS = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
+ ASSERT_TRUE(srcCRS != nullptr);
+
+ const char *wktDst =
+ "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " VERT_CS[\"Ellipsoid (US Feet)\",\n"
+ " VERT_DATUM[\"Ellipsoid\",2002],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Up\",UP]]]";
+ auto objDst =
+ WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktDst);
+ auto dstCRS = nn_dynamic_pointer_cast<BoundCRS>(objDst);
+ ASSERT_TRUE(dstCRS != nullptr);
+
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +z_in=m "
+ "+xy_out=deg +z_out=us-ft "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ compoundCRS_with_boundGeogCRS_and_geoid_to_geodCRS_NAD2011_ctxt) {
+
+ auto dbContext = DatabaseContext::create();
+
+ const char *wktSrc =
+ "COMPD_CS[\"NAD83 / California zone 5 (ftUS) + "
+ "NAVD88 height - Geoid12B (ftUS)\","
+ " PROJCS[\"NAD83 / California zone 5 (ftUS)\","
+ " GEOGCS[\"NAD83\","
+ " DATUM[\"North_American_Datum_1983\","
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,"
+ " AUTHORITY[\"EPSG\",\"7019\"]],"
+ " TOWGS84[0,0,0,0,0,0,0],"
+ " AUTHORITY[\"EPSG\",\"6269\"]],"
+ " PRIMEM[\"Greenwich\",0,"
+ " AUTHORITY[\"EPSG\",\"8901\"]],"
+ " UNIT[\"degree\",0.0174532925199433,"
+ " AUTHORITY[\"EPSG\",\"9122\"]],"
+ " AUTHORITY[\"EPSG\",\"4269\"]],"
+ " PROJECTION[\"Lambert_Conformal_Conic_2SP\"],"
+ " PARAMETER[\"standard_parallel_1\",35.46666666666667],"
+ " PARAMETER[\"standard_parallel_2\",34.03333333333333],"
+ " PARAMETER[\"latitude_of_origin\",33.5],"
+ " PARAMETER[\"central_meridian\",-118],"
+ " PARAMETER[\"false_easting\",6561666.667],"
+ " PARAMETER[\"false_northing\",1640416.667],"
+ " UNIT[\"US survey foot\",0.3048006096012192,"
+ " AUTHORITY[\"EPSG\",\"9003\"]],"
+ " AXIS[\"X\",EAST],"
+ " AXIS[\"Y\",NORTH],"
+ " AUTHORITY[\"EPSG\",\"2229\"]],"
+ "VERT_CS[\"NAVD88 height - Geoid12B (ftUS)\","
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,"
+ " AUTHORITY[\"EPSG\",\"5103\"]],"
+ " UNIT[\"US survey foot\",0.3048006096012192,"
+ " AUTHORITY[\"EPSG\",\"9003\"]],"
+ " AXIS[\"Gravity-related height\",UP],"
+ " AUTHORITY[\"EPSG\",\"6360\"]]]";
+ auto objSrc =
+ WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc);
+ auto srcCRS = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
+ ASSERT_TRUE(srcCRS != nullptr);
+
+ auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG");
+ // NAD83(2011) geocentric
+ auto dstCRS = authFactoryEPSG->createCoordinateReferenceSystem("6317");
+
+ auto authFactory = AuthorityFactory::create(dbContext, std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCRS), dstCRS, ctxt);
+ bool found = false;
+ for (const auto &op : list) {
+ if (op->nameStr() ==
+ "Inverse of unnamed + "
+ "Transformation from NAD83 to WGS84 + "
+ "Ballpark geographic offset from WGS 84 to NAD83(2011) + "
+ "Transformation from NAVD88 height (ftUS) to NAVD88 height + "
+ "Inverse of NAD83(2011) to NAVD88 height (1) + "
+ "Conversion from NAD83(2011) (geog3D) to NAD83(2011) "
+ "(geocentric)") {
+ found = true;
+ EXPECT_EQ(
+ op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=us-ft +xy_out=m "
+ "+step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 "
+ "+lat_1=35.4666666666667 +lat_2=34.0333333333333 "
+ "+x_0=2000000.0001016 +y_0=500000.0001016 +ellps=GRS80 "
+ "+step +proj=unitconvert +z_in=us-ft +z_out=m "
+ "+step +proj=vgridshift +grids=us_noaa_g2012bu0.tif "
+ "+multiplier=1 "
+ "+step +proj=cart +ellps=GRS80");
+ }
+ }
+ EXPECT_TRUE(found);
+ if (!found) {
+ for (const auto &op : list) {
+ std::cerr << op->nameStr() << std::endl;
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocent_to_compoundCRS) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=geocent +datum=WGS84 +units=m +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step +inv "
+ "+proj=vgridshift +grids=@foo.gtx +multiplier=1 +step +inv "
+ "+proj=hgridshift +grids=@foo.gsb +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocent_to_compoundCRS_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ // WGS84 geocentric
+ auto src = authFactory->createCoordinateReferenceSystem("4978");
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ src, NN_CHECK_ASSERT(dst), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step +inv "
+ "+proj=vgridshift +grids=@foo.gtx +multiplier=1 +step +inv "
+ "+proj=hgridshift +grids=@foo.gsb +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_compoundCRS) {
+ auto compound1 = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{createUTM31_WGS84(), createVerticalCRS()});
+ auto compound2 = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{createUTM32_WGS84(), createVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(compound1,
+ compound2);
+ ASSERT_TRUE(op != nullptr);
+ auto opRef = CoordinateOperationFactory::create()->createOperation(
+ createUTM31_WGS84(), createUTM32_WGS84());
+ ASSERT_TRUE(opRef != nullptr);
+ EXPECT_TRUE(op->isEquivalentTo(opRef.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_compoundCRS_with_vertical_transform) {
+ auto verticalCRS1 = createVerticalCRS();
+
+ auto verticalCRS2 = VerticalCRS::create(
+ PropertyMap(), VerticalReferenceFrame::create(PropertyMap()),
+ VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
+
+ // Use of this type of transformation is a bit of non-sense here
+ // since it should normally be used with NGVD29 and NAVD88 for VerticalCRS,
+ // and NAD27/NAD83 as horizontal CRS...
+ auto vtransformation = Transformation::createVERTCON(
+ PropertyMap(), verticalCRS1, verticalCRS2, "bla.gtx",
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto compound1 = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{
+ ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createTransverseMercator(PropertyMap(), Angle(1),
+ Angle(2), Scale(3),
+ Length(4), Length(5)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)),
+ BoundCRS::create(verticalCRS1, verticalCRS2, vtransformation)});
+ auto compound2 = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{createUTM32_WGS84(), verticalCRS2});
+
+ auto op = CoordinateOperationFactory::create()->createOperation(compound1,
+ compound2);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=tmerc +lat_0=1 +lon_0=2 +k=3 "
+ "+x_0=4 +y_0=5 +ellps=WGS84 +step "
+ "+proj=vgridshift +grids=bla.gtx +multiplier=0.001 +step "
+ "+proj=utm +zone=32 "
+ "+ellps=WGS84");
+ {
+ auto formatter = PROJStringFormatter::create();
+ formatter->setUseApproxTMerc(true);
+ EXPECT_EQ(
+ op->exportToPROJString(formatter.get()),
+ "+proj=pipeline +step +inv +proj=tmerc +approx +lat_0=1 +lon_0=2 "
+ "+k=3 +x_0=4 +y_0=5 +ellps=WGS84 +step "
+ "+proj=vgridshift +grids=bla.gtx +multiplier=0.001 +step "
+ "+proj=utm +approx +zone=32 "
+ "+ellps=WGS84");
+ }
+ {
+ auto formatter = PROJStringFormatter::create();
+ formatter->setUseApproxTMerc(true);
+ EXPECT_EQ(
+ op->inverse()->exportToPROJString(formatter.get()),
+ "+proj=pipeline +step +inv +proj=utm +approx +zone=32 +ellps=WGS84 "
+ "+step +inv +proj=vgridshift +grids=bla.gtx "
+ "+multiplier=0.001 +step +proj=tmerc +approx +lat_0=1 +lon_0=2 "
+ "+k=3 +x_0=4 +y_0=5 +ellps=WGS84");
+ }
+
+ auto opInverse = CoordinateOperationFactory::create()->createOperation(
+ compound2, compound1);
+ ASSERT_TRUE(opInverse != nullptr);
+ {
+ auto formatter = PROJStringFormatter::create();
+ auto formatter2 = PROJStringFormatter::create();
+ EXPECT_EQ(opInverse->inverse()->exportToPROJString(formatter.get()),
+ op->exportToPROJString(formatter2.get()));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx "
+ "+type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=@foo.gsb "
+ "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
+ "+step +inv +proj=vgridshift +grids=@bar.gtx +multiplier=1 "
+ "+step +inv +proj=hgridshift +grids=@bar.gsb "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_geoidgrids) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=@foo.gsb "
+ "+step +inv +proj=hgridshift +grids=@bar.gsb "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_geoidgrids_different_vunits) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@foo.gtx "
+ "+vunits=us-ft +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=@foo.gsb "
+ "+step +proj=unitconvert +z_in=m +z_out=us-ft "
+ "+step +inv +proj=hgridshift +grids=@bar.gsb "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_nadgrids_same_geoidgrids) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=noop");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_towgs84_same_geoidgrids) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +towgs84=0,0,0 +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +towgs84=0,0,0 +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=GRS67 "
+ "+step +inv +proj=cart +ellps=GRS80 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_WKT1_same_geoidgrids_context) {
+ auto objSrc = WKTParser().createFromWKT(
+ "COMPD_CS[\"NAD83 / Alabama West + NAVD88 height - Geoid12B "
+ "(Meters)\",\n"
+ " PROJCS[\"NAD83 / Alabama West\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",30],\n"
+ " PARAMETER[\"central_meridian\",-87.5],\n"
+ " PARAMETER[\"scale_factor\",0.999933333],\n"
+ " PARAMETER[\"false_easting\",600000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"X\",EAST],\n"
+ " AXIS[\"Y\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"26930\"]],\n"
+ " VERT_CS[\"NAVD88 height\",\n"
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
+ " "
+ "EXTENSION[\"PROJ4_GRIDS\",\"g2012a_alaska.gtx,g2012a_hawaii.gtx,"
+ "g2012a_conus.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"5103\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5703\"]]]");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = WKTParser().createFromWKT(
+ "COMPD_CS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet + NAVD88 "
+ "height - Geoid12B (US Feet)\",\n"
+ " PROJCS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"Degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",30],\n"
+ " PARAMETER[\"central_meridian\",-87.5],\n"
+ " PARAMETER[\"scale_factor\",0.999933333333333],\n"
+ " PARAMETER[\"false_easting\",1968500],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " AUTHORITY[\"ESRI\",\"102630\"]],\n"
+ " VERT_CS[\"NAVD88 height (ftUS)\",\n"
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
+ " "
+ "EXTENSION[\"PROJ4_GRIDS\",\"g2012a_alaska.gtx,g2012a_hawaii.gtx,"
+ "g2012a_conus.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"5103\"]],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"6360\"]]]");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of unnamed + "
+ "Transformation from NAD83 to WGS84 + "
+ "NAVD88 height to NAVD88 height (ftUS) + "
+ "Inverse of Transformation from NAD83 to WGS84 + "
+ "unnamed");
+ auto grids = list[0]->gridsNeeded(dbContext, false);
+ EXPECT_TRUE(grids.empty());
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=tmerc +lat_0=30 +lon_0=-87.5 +k=0.999933333 "
+ "+x_0=600000 +y_0=0 +ellps=GRS80 "
+ "+step +proj=unitconvert +z_in=m +z_out=us-ft "
+ "+step +proj=tmerc +lat_0=30 +lon_0=-87.5 +k=0.999933333333333 "
+ "+x_0=600000 +y_0=0 +ellps=GRS80 "
+ "+step +proj=unitconvert +xy_in=m +xy_out=us-ft");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_compoundCRS_issue_2232) {
+ auto objSrc = WKTParser().createFromWKT(
+ "COMPD_CS[\"NAD83 / Alabama West + NAVD88 height - Geoid12B "
+ "(Meters)\",\n"
+ " PROJCS[\"NAD83 / Alabama West\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",30],\n"
+ " PARAMETER[\"central_meridian\",-87.5],\n"
+ " PARAMETER[\"scale_factor\",0.999933333],\n"
+ " PARAMETER[\"false_easting\",600000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"X\",EAST],\n"
+ " AXIS[\"Y\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"26930\"]],\n"
+ " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n"
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"foo.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"5103\"]],\n"
+ " UNIT[\"metre\",1.0,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5703\"]]]");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ auto objDst = WKTParser().createFromWKT(
+ "COMPD_CS[\"NAD83 + some CRS (US Feet)\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " VERT_CS[\"some CRS (US Feet)\",\n"
+ " VERT_DATUM[\"some datum\",2005],\n"
+ " UNIT[\"US survey foot\",0.3048006096012192,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Up\",UP]]]");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst), ctxt);
+ EXPECT_GE(list.size(), 1U);
+
+ auto list2 = CoordinateOperationFactory::create()->createOperations(
+ NN_CHECK_ASSERT(dst), NN_CHECK_ASSERT(src), ctxt);
+ EXPECT_EQ(list2.size(), list.size());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_compoundCRS_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // NAD27 + NGVD29 height (ftUS)
+ authFactory->createCoordinateReferenceSystem("7406"),
+ // NAD83(NSRS2007) + NAVD88 height
+ authFactory->createCoordinateReferenceSystem("5500"), ctxt);
+ // 152 or 155 depending if the VERTCON grids are there
+ ASSERT_GE(list.size(), 152U);
+ EXPECT_FALSE(list[0]->hasBallparkTransformation());
+ EXPECT_EQ(list[0]->nameStr(), "NGVD29 height (ftUS) to NAVD88 height (3) + "
+ "NAD27 to WGS 84 (79) + Inverse of "
+ "NAD83(NSRS2007) to WGS 84 (1)");
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad +z_out=m "
+ "+step +proj=vgridshift +grids=us_noaa_vertcone.tif +multiplier=1 "
+ "+step +proj=hgridshift +grids=us_noaa_conus.tif +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+ {
+ // Test that we can round-trip this through WKT and still get the same
+ // PROJ string.
+ auto wkt = list[0]->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get());
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto co = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
+ ASSERT_TRUE(co != nullptr);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ co->exportToPROJString(PROJStringFormatter::create().get()));
+ }
+
+ bool foundApprox = false;
+ for (size_t i = 0; i < list.size(); i++) {
+ auto projString =
+ list[i]->exportToPROJString(PROJStringFormatter::create().get());
+ EXPECT_TRUE(
+ projString.find("+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +z_in=us-ft "
+ "+xy_out=rad +z_out=m") == 0)
+ << list[i]->nameStr();
+ if (list[i]->nameStr().find("Transformation from NGVD29 height (ftUS) "
+ "to NAVD88 height (ballpark vertical "
+ "transformation)") == 0) {
+ EXPECT_TRUE(list[i]->hasBallparkTransformation());
+ EXPECT_EQ(list[i]->nameStr(),
+ "Transformation from NGVD29 height (ftUS) to NAVD88 "
+ "height (ballpark vertical transformation) + NAD27 to "
+ "WGS 84 (79) + Inverse of NAD83(NSRS2007) to WGS 84 (1)");
+ EXPECT_EQ(
+ projString,
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad "
+ "+z_out=m +step +proj=hgridshift +grids=us_noaa_conus.tif "
+ "+step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ foundApprox = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(foundApprox);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_compoundCRS_context_helmert_noop) {
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ // WGS84 + EGM96
+ auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
+ auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
+ ASSERT_TRUE(srcCrs != nullptr);
+ // ETRS89 + EGM96
+ auto objDest = createFromUserInput("EPSG:4258+5773", dbContext);
+ auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
+ ASSERT_TRUE(destCrs != nullptr);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=noop");
+}
+
+// ---------------------------------------------------------------------------
+
+// EGM96 has a geoid model referenced to WGS84, and Belfast height has a
+// geoid model referenced to ETRS89
+TEST(operation, compoundCRS_to_compoundCRS_WGS84_EGM96_to_ETRS89_Belfast) {
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ // WGS84 + EGM96
+ auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
+ auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
+ ASSERT_TRUE(srcCrs != nullptr);
+ // ETRS89 + Belfast height
+ auto objDest = createFromUserInput("EPSG:4258+5732", dbContext);
+ auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
+ ASSERT_TRUE(destCrs != nullptr);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
+ "Inverse of ETRS89 to WGS 84 (1) + "
+ "ETRS89 to Belfast height (2)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 "
+ "+step +inv +proj=vgridshift +grids=uk_os_OSGM15_Belfast.tif "
+ "+multiplier=1 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+// Variant of above where source intermediate geog3D CRS == target intermediate
+// geog3D CRS
+TEST(operation, compoundCRS_to_compoundCRS_WGS84_EGM96_to_WGS84_Belfast) {
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ // WGS84 + EGM96
+ auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
+ auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
+ ASSERT_TRUE(srcCrs != nullptr);
+ // WGS84 + Belfast height
+ auto objDest = createFromUserInput("EPSG:4326+5732", dbContext);
+ auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
+ ASSERT_TRUE(destCrs != nullptr);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
+ "Inverse of ETRS89 to WGS 84 (1) + "
+ "ETRS89 to Belfast height (2) + "
+ "ETRS89 to WGS 84 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 "
+ "+step +inv +proj=vgridshift +grids=uk_os_OSGM15_Belfast.tif "
+ "+multiplier=1 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_compoundCRS_concatenated_operation_with_two_vert_transformation) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // ETRS89 + Baltic 1957 height
+ authFactory->createCoordinateReferenceSystem("8360"),
+ // ETRS89 + EVRF2007 height
+ authFactory->createCoordinateReferenceSystem("7423"), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift "
+ "+grids=sk_gku_Slovakia_ETRS89h_to_Baltic1957.tif +multiplier=1 "
+ "+step +inv +proj=vgridshift "
+ "+grids=sk_gku_Slovakia_ETRS89h_to_EVRF2007.tif +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+ EXPECT_EQ(
+ list[0]->nameStr(),
+ "ETRS89 + Baltic 1957 height to ETRS89 + EVRF2007 height (1)");
+ EXPECT_EQ(list[0]->inverse()->nameStr(), "Inverse of 'ETRS89 + Baltic "
+ "1957 height to ETRS89 + "
+ "EVRF2007 height (1)'");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, vertCRS_to_vertCRS) {
+
+ auto vertcrs_m_obj = PROJStringParser().createFromPROJString("+vunits=m");
+ auto vertcrs_m = nn_dynamic_pointer_cast<VerticalCRS>(vertcrs_m_obj);
+ ASSERT_TRUE(vertcrs_m != nullptr);
+
+ auto vertcrs_ft_obj = PROJStringParser().createFromPROJString("+vunits=ft");
+ auto vertcrs_ft = nn_dynamic_pointer_cast<VerticalCRS>(vertcrs_ft_obj);
+ ASSERT_TRUE(vertcrs_ft != nullptr);
+
+ auto vertcrs_us_ft_obj =
+ PROJStringParser().createFromPROJString("+vunits=us-ft");
+ auto vertcrs_us_ft =
+ nn_dynamic_pointer_cast<VerticalCRS>(vertcrs_us_ft_obj);
+ ASSERT_TRUE(vertcrs_us_ft != nullptr);
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(vertcrs_m), NN_CHECK_ASSERT(vertcrs_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=unitconvert +z_in=m +z_out=ft");
+ }
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(vertcrs_m), NN_CHECK_ASSERT(vertcrs_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=unitconvert +z_in=ft +z_out=m");
+ }
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(vertcrs_ft), NN_CHECK_ASSERT(vertcrs_m));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=unitconvert +z_in=ft +z_out=m");
+ }
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(vertcrs_ft), NN_CHECK_ASSERT(vertcrs_us_ft));
+ ASSERT_TRUE(op != nullptr);
+ 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");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, vertCRS_to_vertCRS_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // NGVD29 height (m)
+ authFactory->createCoordinateReferenceSystem("7968"),
+ // NAVD88 height (1)
+ authFactory->createCoordinateReferenceSystem("5703"), ctxt);
+ ASSERT_EQ(list.size(), 3U);
+ EXPECT_EQ(list[0]->nameStr(), "NGVD29 height (m) to NAVD88 height (3)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=vgridshift +grids=us_noaa_vertcone.tif +multiplier=1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, vertCRS_to_vertCRS_New_Zealand_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // NZVD2016 height
+ authFactory->createCoordinateReferenceSystem("7839"),
+ // Auckland 1946 height
+ authFactory->createCoordinateReferenceSystem("5759"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=vgridshift +grids=nz_linz_auckht1946-nzvd2016.tif "
+ "+multiplier=1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_3D_to_geogCRS_3D) {
+
+ auto compoundcrs_ft_obj = PROJStringParser().createFromPROJString(
+ "+proj=merc +vunits=ft +type=crs");
+ auto proj3DCRS_ft = nn_dynamic_pointer_cast<CRS>(compoundcrs_ft_obj);
+ ASSERT_TRUE(proj3DCRS_ft != nullptr);
+
+ auto geogcrs_m_obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +vunits=m +type=crs");
+ auto geogcrs_m = nn_dynamic_pointer_cast<CRS>(geogcrs_m_obj);
+ ASSERT_TRUE(geogcrs_m != nullptr);
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(proj3DCRS_ft), NN_CHECK_ASSERT(geogcrs_m));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_FALSE(op->hasBallparkTransformation());
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=m +z_in=ft "
+ "+xy_out=m +z_out=m "
+ "+step +inv +proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 "
+ "+ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +z_in=m "
+ "+xy_out=deg +z_out=m");
+ }
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(geogcrs_m), NN_CHECK_ASSERT(proj3DCRS_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_FALSE(op->hasBallparkTransformation());
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +z_in=m +z_out=ft "
+ "+step +proj=unitconvert +xy_in=deg +z_in=ft "
+ "+xy_out=rad +z_out=m "
+ "+step +proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=m +z_in=m "
+ "+xy_out=m +z_out=ft");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_geogCRS_3D) {
+
+ auto compoundcrs_ft_obj = WKTParser().createFromWKT(
+ "COMPOUNDCRS[\"unknown\",\n"
+ " PROJCRS[\"unknown\",\n"
+ " BASEGEOGCRS[\"unknown\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]]],\n"
+ " CONVERSION[\"unknown\",\n"
+ " METHOD[\"Mercator (variant A)\",\n"
+ " ID[\"EPSG\",9804]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",1,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]],\n"
+ " VERTCRS[\"unknown\",\n"
+ " VDATUM[\"unknown\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"foot\",0.3048,\n"
+ " ID[\"EPSG\",9002]]]]]");
+ auto compoundcrs_ft = nn_dynamic_pointer_cast<CRS>(compoundcrs_ft_obj);
+ ASSERT_TRUE(compoundcrs_ft != nullptr);
+
+ auto geogcrs_m_obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +vunits=m +type=crs");
+ auto geogcrs_m = nn_dynamic_pointer_cast<CRS>(geogcrs_m_obj);
+ ASSERT_TRUE(geogcrs_m != nullptr);
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(compoundcrs_ft), NN_CHECK_ASSERT(geogcrs_m));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_TRUE(op->hasBallparkTransformation());
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=merc +lon_0=0 +k=1 +x_0=0 "
+ "+y_0=0 +ellps=WGS84 +step +proj=unitconvert +xy_in=rad "
+ "+z_in=ft +xy_out=deg +z_out=m");
+ }
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(geogcrs_m), NN_CHECK_ASSERT(compoundcrs_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_TRUE(op->hasBallparkTransformation());
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +z_in=m "
+ "+xy_out=rad +z_out=ft +step +proj=merc +lon_0=0 +k=1 +x_0=0 "
+ "+y_0=0 +ellps=WGS84");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_geogCRS_3D_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ // CompoundCRS to Geog3DCRS, with vertical unit change, but without
+ // ellipsoid height <--> vertical height correction
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem(
+ "7406"), // NAD27 + NGVD29 height (ftUS)
+ authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
+ ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_TRUE(list[0]->hasBallparkTransformation());
+ EXPECT_EQ(list[0]->nameStr(),
+ "NAD27 to WGS 84 (79) + Transformation from NGVD29 height "
+ "(ftUS) to WGS 84 (ballpark vertical transformation, without "
+ "ellipsoid height to vertical height correction)");
+ EXPECT_EQ(list[0]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step "
+ "+proj=hgridshift +grids=us_noaa_conus.tif "
+ "+step +proj=unitconvert "
+ "+xy_in=rad +z_in=us-ft +xy_out=deg +z_out=m +step "
+ "+proj=axisswap +order=2,1");
+ }
+
+ // CompoundCRS to Geog3DCRS, with same vertical unit, and with
+ // direct ellipsoid height <--> vertical height correction and
+ // direct horizontal transform (no-op here)
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem(
+ "5500"), // NAD83(NSRS2007) + NAVD88 height
+ authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
+ ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of NAD83(NSRS2007) to NAVD88 height (1) + "
+ "NAD83(NSRS2007) to WGS 84 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_noaa_geoid09_conus.tif "
+ "+multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+ EXPECT_EQ(list[0]->remarks(),
+ "For NAD83(NSRS2007) to NAVD88 height (1) (EPSG:9173): Uses "
+ "Geoid09 hybrid model. Replaced by 2012 model (CT code 6326)."
+ "\n"
+ "For NAD83(NSRS2007) to WGS 84 (1) (EPSG:15931): "
+ "Approximation at the +/- 1m level assuming that "
+ "NAD83(NSRS2007) is equivalent to WGS 84 within the accuracy "
+ "of the transformation.");
+ }
+
+ // NAD83 + NAVD88 height --> WGS 84
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ // NAD83 + NAVD88 height
+ auto srcObj = createFromUserInput(
+ "EPSG:4269+5703", authFactory->databaseContext(), false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto nnSrc = NN_NO_CHECK(src);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ nnSrc,
+ authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
+ ctxt);
+ ASSERT_GE(list.size(), 2U);
+
+ EXPECT_EQ(list[0]->nameStr(),
+ "NAD83 to WGS 84 (1) + "
+ "Inverse of NAD83(NSRS2007) to WGS 84 (1) + "
+ "Inverse of NAD83(NSRS2007) to NAVD88 height (1) + "
+ "NAD83(NSRS2007) to WGS 84 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_noaa_geoid09_conus.tif "
+ "+multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+ }
+
+ // Another variation, but post horizontal adjustment is in two steps
+ {
+ 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
+ auto srcObj = createFromUserInput(
+ "EPSG:6318+5703", authFactory->databaseContext(), false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto nnSrc = NN_NO_CHECK(src);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ nnSrc,
+ authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
+ ctxt);
+ ASSERT_GE(list.size(), 2U);
+
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of NAD83(2011) to NAVD88 height (3) + "
+ "Inverse of NAD83 to NAD83(2011) (1) + "
+ "NAD83 to WGS 84 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_noaa_g2018u0.tif "
+ "+multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+
+ // Shows vertical step, and then horizontal step
+ EXPECT_EQ(list[1]->nameStr(),
+ "Inverse of NAD83(2011) to NAVD88 height (3) + "
+ "Inverse of NAD83 to NAD83(2011) (1) + "
+ "NAD83 to WGS 84 (18)");
+ EXPECT_EQ(list[1]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_noaa_g2018u0.tif "
+ "+multiplier=1 "
+ "+step +proj=hgridshift +grids=us_noaa_FL.tif "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_geogCRS_3D_with_3D_helmert_context) {
+ // Use case of https://github.com/OSGeo/PROJ/issues/2225
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ // WGS84 + EGM96 height
+ auto srcObj = createFromUserInput("EPSG:4326+5773", dbContext, false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src),
+ // CH1903+
+ authFactory->createCoordinateReferenceSystem("4150")->promoteTo3D(
+ std::string(), dbContext),
+ ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
+ "Inverse of CH1903+ to WGS 84 (1)");
+ // Check that there is no push v_3 / pop v_3
+ const char *expected_proj =
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 "
+ "+step +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=-674.374 +y=-15.056 +z=-405.346 "
+ "+step +inv +proj=cart +ellps=bessel "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1";
+ EXPECT_EQ(list[0]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5, dbContext)
+ .get()),
+ expected_proj);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_geogCRS_2D_promote_to_3D_context) {
+ 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 + NAVD88 height
+ auto srcObj = createFromUserInput("EPSG:4269+5703",
+ 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("4269"); // NAD83
+
+ auto listCompoundToGeog2D =
+ CoordinateOperationFactory::create()->createOperations(nnSrc, dst,
+ ctxt);
+ // The checked value is not that important, but in case this changes,
+ // likely due to a EPSG upgrade, worth checking
+ EXPECT_EQ(listCompoundToGeog2D.size(), 142U);
+
+ auto listGeog2DToCompound =
+ CoordinateOperationFactory::create()->createOperations(dst, nnSrc,
+ ctxt);
+ EXPECT_EQ(listGeog2DToCompound.size(), listCompoundToGeog2D.size());
+
+ auto listCompoundToGeog3D =
+ CoordinateOperationFactory::create()->createOperations(
+ nnSrc,
+ dst->promoteTo3D(std::string(), authFactory->databaseContext()),
+ ctxt);
+ EXPECT_EQ(listCompoundToGeog3D.size(), listCompoundToGeog2D.size());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_of_projCRS_to_geogCRS_2D_context) {
+ 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);
+ // SPCS83 California zone 1 (US Survey feet) + NAVD88 height (ftUS)
+ auto srcObj = createFromUserInput("EPSG:2225+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("4269"); // NAD83
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ nnSrc, dst, ctxt);
+ // The checked value is not that important, but in case this changes,
+ // likely due to a EPSG upgrade, worth checking
+ // We want to make sure that the horizontal adjustments before and after
+ // the vertical transformation are the reverse of each other, and there are
+ // not mixes with different alternative operations (like California grid
+ // forward and Nevada grid reverse)
+ ASSERT_EQ(list.size(), 14U);
+
+ // Check that unit conversion is OK
+ auto op_proj =
+ list[0]->exportToPROJString(PROJStringFormatter::create().get());
+ EXPECT_EQ(op_proj,
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=us-ft +xy_out=m "
+ "+step +inv +proj=lcc +lat_0=39.3333333333333 +lon_0=-122 "
+ "+lat_1=41.6666666666667 +lat_2=40 +x_0=2000000.0001016 "
+ "+y_0=500000.0001016 +ellps=GRS80 "
+ "+step +proj=unitconvert +z_in=us-ft +z_out=m "
+ "+step +proj=vgridshift +grids=us_noaa_geoid09_conus.tif "
+ "+multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+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_of_projCRS_from_wkt_without_id_or_extent_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 / Pennsylvania South + NAVD88 height\",\n"
+ " PROJCRS[\"NAD83 / Pennsylvania South\",\n"
+ " BASEGEOGCRS[\"NAD83\",\n"
+ " DATUM[\"North American Datum 1983\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"SPCS83 Pennsylvania South zone (meters)\",\n"
+ " METHOD[\"Lambert Conic Conformal (2SP)\",\n"
+ " ID[\"EPSG\",9802]],\n"
+ " PARAMETER[\"Latitude of false origin\",39.3333333333333,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8821]],\n"
+ " PARAMETER[\"Longitude of false origin\",-77.75,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8822]],\n"
+ " PARAMETER[\"Latitude of 1st standard "
+ "parallel\",40.9666666666667,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Latitude of 2nd standard "
+ "parallel\",39.9333333333333,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"Easting at false origin\",600000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8826]],\n"
+ " PARAMETER[\"Northing at false origin\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8827]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"easting (X)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"northing (Y)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]],\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("4269"); // NAD83
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src), dst, ctxt);
+ // NAD83 / Pennsylvania South + NAVD88 height
+ auto srcRefObj = createFromUserInput("EPSG:32129+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_of_vertCRS_with_geoid_model_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 / Pennsylvania South + NAVD88 height\",\n"
+ " PROJCRS[\"NAD83 / Pennsylvania South\",\n"
+ " BASEGEOGCRS[\"NAD83\",\n"
+ " DATUM[\"North American Datum 1983\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"SPCS83 Pennsylvania South zone (meters)\",\n"
+ " METHOD[\"Lambert Conic Conformal (2SP)\",\n"
+ " ID[\"EPSG\",9802]],\n"
+ " PARAMETER[\"Latitude of false origin\",39.3333333333333,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8821]],\n"
+ " PARAMETER[\"Longitude of false origin\",-77.75,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8822]],\n"
+ " PARAMETER[\"Latitude of 1st standard "
+ "parallel\",40.9666666666667,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Latitude of 2nd standard "
+ "parallel\",39.9333333333333,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"Easting at false origin\",600000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8826]],\n"
+ " PARAMETER[\"Northing at false origin\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8827]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"easting (X)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"northing (Y)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]],\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]],\n"
+ " GEOIDMODEL[\"GEOID12B\"]]]";
+ auto srcObj =
+ createFromUserInput(wkt, authFactory->databaseContext(), false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto dst = authFactory->createCoordinateReferenceSystem("4269"); // NAD83
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src), dst, ctxt);
+ ASSERT_TRUE(!list.empty());
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of SPCS83 Pennsylvania South zone (meters) + "
+ "Ballpark geographic offset from NAD83 to NAD83(2011) + "
+ "Inverse of NAD83(2011) to NAVD88 height (1) + "
+ "Ballpark geographic offset from NAD83(2011) to NAD83");
+ auto op_proj =
+ list[0]->exportToPROJString(PROJStringFormatter::create().get());
+ EXPECT_EQ(
+ op_proj,
+ "+proj=pipeline "
+ "+step +inv +proj=lcc +lat_0=39.3333333333333 +lon_0=-77.75 "
+ "+lat_1=40.9666666666667 +lat_2=39.9333333333333 +x_0=600000 "
+ "+y_0=0 +ellps=GRS80 "
+ "+step +proj=vgridshift +grids=us_noaa_g2012bu0.tif +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_from_WKT2_to_geogCRS_3D_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto src = authFactory->createCoordinateReferenceSystem(
+ "7415"); // Amersfoort / RD New + NAP height
+ auto dst =
+ authFactory->createCoordinateReferenceSystem("4937"); // ETRS89 3D
+ auto list =
+ CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
+ ASSERT_GE(list.size(), 1U);
+ auto wkt2 = src->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get());
+ auto obj = WKTParser().createFromWKT(wkt2);
+ auto src_from_wkt2 = nn_dynamic_pointer_cast<CRS>(obj);
+ ASSERT_TRUE(src_from_wkt2 != nullptr);
+ auto list2 = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src_from_wkt2), dst, ctxt);
+ ASSERT_GE(list.size(), list2.size());
+ for (size_t i = 0; i < list.size(); i++) {
+ const auto &op = list[i];
+ const auto &op2 = list2[i];
+ EXPECT_TRUE(
+ op->isEquivalentTo(op2.get(), IComparable::Criterion::EQUIVALENT));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_from_WKT2_no_id_to_geogCRS_3D_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto src = authFactory->createCoordinateReferenceSystem(
+ "7415"); // Amersfoort / RD New + NAP height
+ auto dst =
+ authFactory->createCoordinateReferenceSystem("4937"); // ETRS89 3D
+ auto list =
+ CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
+ ASSERT_GE(list.size(), 1U);
+
+ {
+ auto op_proj =
+ list[0]->exportToPROJString(PROJStringFormatter::create().get());
+ EXPECT_EQ(
+ op_proj,
+ "+proj=pipeline +step +inv +proj=sterea +lat_0=52.1561605555556 "
+ "+lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 "
+ "+ellps=bessel "
+ "+step +proj=hgridshift +grids=nl_nsgi_rdtrans2018.tif "
+ "+step +proj=vgridshift +grids=nl_nsgi_nlgeo2018.tif +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+ }
+
+ auto wkt2 =
+ "COMPOUNDCRS[\"unknown\",\n"
+ " PROJCRS[\"unknown\",\n"
+ " BASEGEOGCRS[\"Amersfoort\",\n"
+ " DATUM[\"Amersfoort\",\n"
+ " ELLIPSOID[\"Bessel "
+ "1841\",6377397.155,299.1528128]]],\n"
+ " CONVERSION[\"unknown\",\n"
+ " METHOD[\"Oblique Stereographic\"],\n"
+ " PARAMETER[\"Latitude of natural origin\",52.1561605555556],\n"
+ " PARAMETER[\"Longitude of natural origin\",5.38763888888889],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9999079],\n"
+ " PARAMETER[\"False easting\",155000],\n"
+ " PARAMETER[\"False northing\",463000]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " VERTCRS[\"NAP height\",\n"
+ " VDATUM[\"Normaal Amsterdams Peil\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " USAGE[\n"
+ " SCOPE[\"unknown\"],\n"
+ " AREA[\"Netherlands - onshore\"],\n"
+ " BBOX[50.75,3.2,53.7,7.22]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt2);
+ auto src_from_wkt2 = nn_dynamic_pointer_cast<CRS>(obj);
+ ASSERT_TRUE(src_from_wkt2 != nullptr);
+ auto list2 = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src_from_wkt2), dst, ctxt);
+ ASSERT_EQ(list.size(), list2.size());
+ for (size_t i = 0; i < list.size(); i++) {
+ const auto &op = list[i];
+ const auto &op2 = list2[i];
+ auto op_proj =
+ op->exportToPROJString(PROJStringFormatter::create().get());
+ auto op2_proj =
+ op2->exportToPROJString(PROJStringFormatter::create().get());
+ EXPECT_EQ(op_proj, op2_proj) << "op=" << op->nameStr()
+ << " op2=" << op2->nameStr();
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, proj3DCRS_with_non_meter_horiz_and_vertical_to_geog) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=31 +datum=WGS84 +units=us-ft +vunits=us-ft +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src), authFactory->createCoordinateReferenceSystem("4326"),
+ ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ // Check that vertical unit conversion is done just once
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=us-ft +z_in=us-ft "
+ "+xy_out=m +z_out=m "
+ "+step +inv +proj=utm +zone=31 +ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_with_non_meter_horiz_and_vertical_to_geog) {
+ auto objSrc = WKTParser().createFromWKT(
+ "COMPOUNDCRS[\"unknown\",\n"
+ " PROJCRS[\"unknown\",\n"
+ " BASEGEOGCRS[\"unknown\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",500000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]],\n"
+ " ID[\"EPSG\",16031]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"US survey foot\",0.304800609601219,\n"
+ " ID[\"EPSG\",9003]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"US survey foot\",0.304800609601219,\n"
+ " ID[\"EPSG\",9003]]]],\n"
+ " VERTCRS[\"unknown\",\n"
+ " VDATUM[\"unknown\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"US survey foot\",0.304800609601219,\n"
+ " ID[\"EPSG\",9003]]]]]"
+
+ );
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src), authFactory->createCoordinateReferenceSystem("4326"),
+ ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ // Check that vertical unit conversion is done just once
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=us-ft +xy_out=m "
+ "+step +inv +proj=utm +zone=31 +ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +z_in=us-ft "
+ "+xy_out=deg +z_out=m "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_to_compoundCRS) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx "
+ "+type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=@foo.gsb "
+ "+step +inv +proj=vgridshift +grids=@bar.gtx +multiplier=1 "
+ "+step +inv +proj=hgridshift +grids=@bar.gsb "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+
+ auto opInverse = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(dst), NN_CHECK_ASSERT(src));
+ ASSERT_TRUE(opInverse != nullptr);
+ EXPECT_TRUE(opInverse->inverse()->_isEquivalentTo(op.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, IGNF_LAMB1_TO_EPSG_4326) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setAllowUseIntermediateCRS(
+ CoordinateOperationContext::IntermediateCRSUse::ALWAYS);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ AuthorityFactory::create(DatabaseContext::create(), "IGNF")
+ ->createCoordinateReferenceSystem("LAMB1"),
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("4326"),
+ ctxt);
+ ASSERT_EQ(list.size(), 2U);
+
+ EXPECT_FALSE(list[0]->hasBallparkTransformation());
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=lcc +lat_1=49.5 +lat_0=49.5 "
+ "+lon_0=0 +k_0=0.99987734 +x_0=600000 +y_0=200000 "
+ "+ellps=clrk80ign +pm=paris +step +proj=hgridshift "
+ "+grids=fr_ign_ntf_r93.tif +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+
+ EXPECT_FALSE(list[1]->hasBallparkTransformation());
+ EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=lcc +lat_1=49.5 +lat_0=49.5 "
+ "+lon_0=0 +k_0=0.99987734 +x_0=600000 +y_0=200000 "
+ "+ellps=clrk80ign +pm=paris +step +proj=push +v_3 +step "
+ "+proj=cart +ellps=clrk80ign +step +proj=helmert +x=-168 +y=-60 "
+ "+z=320 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=2,1");
+
+ auto list2 = CoordinateOperationFactory::create()->createOperations(
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ // NTF (Paris) / Lambert Nord France equivalent to IGNF:LAMB1
+ ->createCoordinateReferenceSystem("27561"),
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("4326"),
+ ctxt);
+ ASSERT_GE(list2.size(), 3U);
+
+ EXPECT_EQ(replaceAll(list2[0]->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "0.999877341", "0.99987734"),
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()));
+
+ // The second entry in list2 (list2[1]) uses the
+ // weird +pm=2.33720833333333 from "NTF (Paris) to NTF (2)"
+ // so skip to the 3th method
+ EXPECT_EQ(replaceAll(list2[2]->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "0.999877341", "0.99987734"),
+ list[1]->exportToPROJString(PROJStringFormatter::create().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, NAD83_to_projeted_CRS_based_on_NAD83_2011) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // NAD83
+ authFactory->createCoordinateReferenceSystem("4269"),
+ // NAD83(2011) / California Albers
+ authFactory->createCoordinateReferenceSystem("6414"), ctxt);
+ ASSERT_EQ(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Ballpark geographic offset from NAD83 to "
+ "NAD83(2011) + California Albers");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=aea +lat_0=0 +lon_0=-120 +lat_1=34 "
+ "+lat_2=40.5 +x_0=0 +y_0=-4000000 +ellps=GRS80");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, isPROJInstantiable) {
+
+ {
+ auto transformation = Transformation::createGeocentricTranslations(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326,
+ 1.0, 2.0, 3.0, {});
+ EXPECT_TRUE(transformation->isPROJInstantiable(
+ DatabaseContext::create(), false));
+ }
+
+ // Missing grid
+ {
+ auto transformation = Transformation::createNTv2(
+ PropertyMap(), GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326,
+ "foo.gsb", std::vector<PositionalAccuracyNNPtr>());
+ EXPECT_FALSE(transformation->isPROJInstantiable(
+ DatabaseContext::create(), false));
+ }
+
+ // Unsupported method
+ {
+ auto transformation = Transformation::create(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326,
+ nullptr, OperationMethod::create(
+ PropertyMap(), std::vector<OperationParameterNNPtr>{}),
+ std::vector<GeneralParameterValueNNPtr>{},
+ std::vector<PositionalAccuracyNNPtr>{});
+ EXPECT_FALSE(transformation->isPROJInstantiable(
+ DatabaseContext::create(), false));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, createOperation_on_crs_with_canonical_bound_crs) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4267, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto crs = boundCRS->baseCRSWithCanonicalBoundCRS();
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ crs, GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_TRUE(op->isEquivalentTo(boundCRS->transformation().get()));
+ {
+ auto wkt1 = op->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)
+ .get());
+ auto wkt2 = boundCRS->transformation()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)
+ .get());
+ EXPECT_EQ(wkt1, wkt2);
+ }
+ }
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4326, crs);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_TRUE(
+ op->isEquivalentTo(boundCRS->transformation()->inverse().get()));
+ {
+ auto wkt1 = op->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)
+ .get());
+ auto wkt2 = boundCRS->transformation()->inverse()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)
+ .get());
+ EXPECT_EQ(wkt1, wkt2);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, createOperation_fallback_to_proj4_strings) {
+ auto objDest = PROJStringParser().createFromPROJString(
+ "+proj=longlat +geoc +datum=WGS84 +type=crs");
+ auto dest = nn_dynamic_pointer_cast<GeographicCRS>(objDest);
+ ASSERT_TRUE(dest != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4326, NN_CHECK_ASSERT(dest));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=longlat +geoc +datum=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ createOperation_fallback_to_proj4_strings_regular_with_datum_to_projliteral) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=11 +datum=NAD27 +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84 +over +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=utm +zone=11 +datum=NAD27 "
+ "+step +proj=longlat +datum=WGS84 +over "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ createOperation_fallback_to_proj4_strings_proj_NAD83_to_projliteral) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=11 +datum=NAD83 +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84 +over +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=utm +zone=11 +ellps=GRS80 "
+ "+step +proj=longlat +datum=WGS84 +over "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ createOperation_fallback_to_proj4_strings_geog_NAD83_to_projliteral) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=NAD83 +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84 +over +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=longlat +datum=WGS84 +over "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ createOperation_fallback_to_proj4_strings_regular_with_nadgrids_to_projliteral) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=11 +ellps=clrk66 +nadgrids=@conus +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84 +over +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=utm +zone=11 +ellps=clrk66 +nadgrids=@conus "
+ "+step +proj=longlat +datum=WGS84 +over "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ createOperation_fallback_to_proj4_strings_projliteral_to_projliteral) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=11 +datum=NAD27 +over +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84 +over +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=utm +zone=11 +datum=NAD27 +over "
+ "+step +proj=longlat +datum=WGS84 +over "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ createOperation_fallback_to_proj4_strings_regular_to_projliteral_with_towgs84) {
+ auto objSrc =
+ createFromUserInput("EPSG:4326", DatabaseContext::create(), false);
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=31 +ellps=GRS80 +towgs84=1,2,3 +over +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=utm +zone=31 +ellps=GRS80 +towgs84=1,2,3 +over");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, createOperation_on_crs_with_bound_crs_and_wktext) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=55 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 "
+ "+units=m +no_defs +nadgrids=@GDA94_GDA2020_conformal.gsb +ignored1 "
+ "+ignored2=val +wktext +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=55 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 "
+ "+units=m +no_defs +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=55 +south "
+ "+ellps=GRS80 +step +proj=hgridshift "
+ "+grids=@GDA94_GDA2020_conformal.gsb +step +proj=utm +zone=55 "
+ "+south +ellps=GRS80");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, createOperation_ossfuzz_18587) {
+ auto objSrc =
+ createFromUserInput("EPSG:4326", DatabaseContext::create(), false);
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+
+ // Extremely weird string ! We should likely reject it
+ auto objDst = PROJStringParser().createFromPROJString(
+ "type=crs proj=pipeline step proj=merc vunits=m nadgrids=@x "
+ "proj=\"\nproj=pipeline step\n\"");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ // Just check that we don't go into an infinite recursion
+ try {
+ CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ } catch (const std::exception &) {
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, derivedGeographicCRS_with_to_wgs84_to_geographicCRS) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=ob_tran +o_proj=latlon +lat_0=0 +lon_0=180 +o_lat_p=18.0 "
+ "+o_lon_p=-200.0 +ellps=WGS84 +towgs84=1,2,3 +type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84 +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ std::string pipeline(
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +inv +proj=ob_tran +o_proj=latlon +lat_0=0 +lon_0=180 "
+ "+o_lat_p=18 +o_lon_p=-200 +ellps=WGS84 "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=1 +y=2 +z=3 "
+ "+step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ pipeline);
+
+ auto op2 = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326));
+ ASSERT_TRUE(op2 != nullptr);
+ EXPECT_EQ(op2->exportToPROJString(PROJStringFormatter::create().get()),
+ pipeline + " +step +proj=axisswap +order=2,1");
+ }
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(dst), NN_CHECK_ASSERT(src));
+ ASSERT_TRUE(op != nullptr);
+ std::string pipeline(
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=-1 +y=-2 +z=-3 "
+ "+step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=ob_tran +o_proj=latlon +lat_0=0 +lon_0=180 "
+ "+o_lat_p=18 +o_lon_p=-200 +ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline " + pipeline);
+
+ auto op2 = CoordinateOperationFactory::create()->createOperation(
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
+ NN_CHECK_ASSERT(src));
+ ASSERT_TRUE(op2 != nullptr);
+ EXPECT_EQ(op2->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 " + pipeline);
+ }
+}