diff -Nru proj-7.2.0/CITATION proj-7.2.1/CITATION --- proj-7.2.0/CITATION 2020-05-21 10:07:49.000000000 +0000 +++ proj-7.2.1/CITATION 2020-12-26 18:57:07.000000000 +0000 @@ -11,6 +11,6 @@ title = {{PROJ} coordinate transformation software library}, author = {{PROJ contributors}}, organization = {Open Source Geospatial Foundation}, - year = {2020}, + year = {2021}, url = {https://proj.org/}, } diff -Nru proj-7.2.0/CMakeLists.txt proj-7.2.1/CMakeLists.txt --- proj-7.2.0/CMakeLists.txt 2020-10-28 11:22:26.000000000 +0000 +++ proj-7.2.1/CMakeLists.txt 2020-12-26 18:57:21.000000000 +0000 @@ -112,7 +112,7 @@ include(ProjVersion) proj_version(MAJOR 7 MINOR 2 PATCH 0) set(PROJ_API_VERSION "19") -set(PROJ_BUILD_VERSION "21.0.2") +set(PROJ_BUILD_VERSION "21.1.2") ################################################################################ # Build features and variants diff -Nru proj-7.2.0/configure proj-7.2.1/configure --- proj-7.2.0/configure 2020-10-28 12:56:47.000000000 +0000 +++ proj-7.2.1/configure 2020-12-26 18:57:38.000000000 +0000 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for PROJ 7.2.0. +# Generated by GNU Autoconf 2.69 for PROJ 7.2.1. # # Report bugs to . # @@ -590,8 +590,8 @@ # Identity of this package. PACKAGE_NAME='PROJ' PACKAGE_TARNAME='proj' -PACKAGE_VERSION='7.2.0' -PACKAGE_STRING='PROJ 7.2.0' +PACKAGE_VERSION='7.2.1' +PACKAGE_STRING='PROJ 7.2.1' PACKAGE_BUGREPORT='https://github.com/OSGeo/PROJ/issues' PACKAGE_URL='https://proj.org' @@ -1359,7 +1359,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures PROJ 7.2.0 to adapt to many kinds of systems. +\`configure' configures PROJ 7.2.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1429,7 +1429,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of PROJ 7.2.0:";; + short | recursive ) echo "Configuration of PROJ 7.2.1:";; esac cat <<\_ACEOF @@ -1560,7 +1560,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -PROJ configure 7.2.0 +PROJ configure 7.2.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2005,7 +2005,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by PROJ $as_me 7.2.0, which was +It was created by PROJ $as_me 7.2.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2876,7 +2876,7 @@ # Define the identity of the package. PACKAGE='proj' - VERSION='7.2.0' + VERSION='7.2.1' cat >>confdefs.h <<_ACEOF @@ -19946,7 +19946,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by PROJ $as_me 7.2.0, which was +This file was extended by PROJ $as_me 7.2.1, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -20013,7 +20013,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -PROJ config.status 7.2.0 +PROJ config.status 7.2.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff -Nru proj-7.2.0/configure.ac proj-7.2.1/configure.ac --- proj-7.2.0/configure.ac 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/configure.ac 2020-12-26 18:57:21.000000000 +0000 @@ -1,7 +1,7 @@ dnl Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT([PROJ], [7.2.0], +AC_INIT([PROJ], [7.2.1], [https://github.com/OSGeo/PROJ/issues], proj, [https://proj.org]) AC_CONFIG_MACRO_DIR([m4]) AC_LANG(C) diff -Nru proj-7.2.0/data/CMakeLists.txt proj-7.2.1/data/CMakeLists.txt --- proj-7.2.0/data/CMakeLists.txt 2020-05-21 10:07:49.000000000 +0000 +++ proj-7.2.1/data/CMakeLists.txt 2020-12-21 16:29:10.000000000 +0000 @@ -27,6 +27,8 @@ 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 @@ ${PROJ_DICTIONARY} ${GRIDSHIFT_FILES} ${PROJ_DB} + ${SCHEMA_FILES} ) install( FILES ${ALL_DATA_FILE} diff -Nru proj-7.2.0/data/Makefile.am proj-7.2.1/data/Makefile.am --- proj-7.2.0/data/Makefile.am 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/Makefile.am 2020-12-25 15:59:31.000000000 +0000 @@ -42,6 +42,7 @@ 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 -Nru proj-7.2.0/data/Makefile.in proj-7.2.1/data/Makefile.in --- proj-7.2.0/data/Makefile.in 2020-10-28 12:56:46.000000000 +0000 +++ proj-7.2.1/data/Makefile.in 2020-12-26 18:57:37.000000000 +0000 @@ -339,6 +339,7 @@ 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 -Nru proj-7.2.0/data/sql/alias_name.sql proj-7.2.1/data/sql/alias_name.sql --- proj-7.2.0/data/sql/alias_name.sql 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/data/sql/alias_name.sql 2020-12-21 16:29:10.000000000 +0000 @@ -192,7 +192,6 @@ 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','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','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','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'); @@ -609,6 +605,13 @@ INSERT INTO "alias_name" VALUES('vertical_datum','EPSG','1129','JSLD72','EPSG'); 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'); @@ -720,7 +723,6 @@ INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5702','National Geodetic Vertical Datum of 1929 height (ftUS)','EPSG'); INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5703','North American Vertical Datum of 1988 height (m)','EPSG'); INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5709','Normaal Amsterdams Peil height','EPSG'); -INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5711','Australian Height Datum height','EPSG'); INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5712','Australian Height Datum (Tasmania) height','EPSG'); INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5713','Canadian Geodetic Vertical Datum of 1928 height','EPSG'); INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5714','mean sea level height','EPSG'); @@ -1027,7 +1029,6 @@ 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'); @@ -1713,8 +1714,6 @@ 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'); @@ -2204,10 +2203,8 @@ 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'); @@ -4244,8 +4241,6 @@ INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','9379','726','EPSG'); INSERT INTO "alias_name" VALUES('projected_crs','EPSG','3857','WGS 84 / Popular Visualisation Pseudo-Mercator','EPSG'); INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','9379','IGb14 - LatLonEHt','EPSG'); -INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','9380','725','EPSG'); -INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','9380','IGb14 - LatLon','EPSG'); INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','3886','National Elevation Network height','EPSG'); INSERT INTO "alias_name" VALUES('projected_crs','EPSG','3893','ED50 / Iraq Nat. Grid','EPSG'); INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','4743','Karbala 1979 (Polservice)','EPSG'); @@ -4763,7 +4758,6 @@ 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'); @@ -5086,8 +5080,6 @@ INSERT INTO "alias_name" VALUES('projected_crs','EPSG','8387','NCRS Las Vegas high (ftUS)','EPSG'); INSERT INTO "alias_name" VALUES('projected_crs','EPSG','8433','Macao 1920 Grid System','EPSG'); INSERT INTO "alias_name" VALUES('projected_crs','EPSG','5514','S-JTSK [JTSK] / Krovak East North','EPSG'); -INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5711','AHD71 height','EPSG'); -INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5711','AHD-TAS83 height','EPSG'); INSERT INTO "alias_name" VALUES('projected_crs','EPSG','4414','GGN93 / Guam Map Grid','EPSG'); INSERT INTO "alias_name" VALUES('projected_crs','EPSG','4414','Guam Geodetic Network 1993 / Guam Map Grid','EPSG'); INSERT INTO "alias_name" VALUES('projected_crs','EPSG','4414','NAD83 / Guam Map Grid','EPSG'); @@ -5608,8 +5600,6 @@ INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','4202','AGD66 - LatLon','EPSG'); INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','4203','350','EPSG'); INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','4203','AGD84 - LatLon','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('geodetic_crs','EPSG','7844','284','EPSG'); INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','7844','GDA2020 - LatLon','EPSG'); INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','7843','329','EPSG'); @@ -6083,7 +6073,6 @@ 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'); @@ -7189,8 +7178,34 @@ 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'); +INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','9380','IGb14 - LatLon','EPSG'); +INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5711','Australian Height Datum height','EPSG'); +INSERT INTO "alias_name" VALUES('vertical_crs','EPSG','5711','AHD71 height','EPSG'); +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 -Nru proj-7.2.0/data/sql/conversion.sql proj-7.2.1/data/sql/conversion.sql --- proj-7.2.0/data/sql/conversion.sql 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql/conversion.sql 2020-12-21 16:29:10.000000000 +0000 @@ -1734,6 +1734,10 @@ INSERT INTO "usage" VALUES('EPSG','14062','conversion','EPSG','9385','EPSG','4589','EPSG','1196'); INSERT INTO "conversion" VALUES('EPSG','9455','GBK19-TM','In conjunction with transformation ETRS89 to GBK19-IRF (1) (code 9454), emulates the TPEN11 Snake projection.','EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',55.45,'EPSG','9110','EPSG','8802','Longitude of natural origin',-4.21,'EPSG','9110','EPSG','8805','Scale factor at natural origin',1.0,'EPSG','9201','EPSG','8806','False easting',93720.394,'EPSG','9001','EPSG','8807','False northing',113870.493,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0); 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); @@ -4545,6 +4549,8 @@ 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 -Nru proj-7.2.0/data/sql/customizations.sql proj-7.2.1/data/sql/customizations.sql --- proj-7.2.0/data/sql/customizations.sql 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql/customizations.sql 2020-12-21 16:29:10.000000000 +0000 @@ -274,12 +274,6 @@ -- to the EPSG:4326 CRS, as this is a common use case (https://github.com/OSGeo/PROJ/issues/2216) INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','4326','WGS84','PROJ'); ----- Aliases from old datum names to new datum ensemble names ----- - --- Those have been reported to IOGP and will hopefully be integrated in a later EPSG release -INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6326','World Geodetic System 1984','PROJ'); -INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6258','European Terrestrial Reference System 1989','PROJ'); - ---- PROJ unit short names ----- -- Linear units diff -Nru proj-7.2.0/data/sql/extent.sql proj-7.2.1/data/sql/extent.sql --- proj-7.2.0/data/sql/extent.sql 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/data/sql/extent.sql 2020-12-21 16:29:10.000000000 +0000 @@ -1202,8 +1202,8 @@ 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','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','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','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); @@ -2442,7 +2442,7 @@ INSERT INTO "extent" VALUES('EPSG','3463','World - 86°S to 86°N','World between 86°S and 86°N.',-86.0,86.0,-180.0,180.0,0); INSERT INTO "extent" VALUES('EPSG','3464','World - N hemisphere - 12°E to 18°E - by country and WGS 72BE','Between 12°E and 18°E, northern hemisphere between equator and 84°N, onshore and offshore. Chad - west of 18°E.',0.0,84.0,12.0,18.0,0); INSERT INTO "extent" VALUES('EPSG','3465','World - N hemisphere - 18°E to 24°E - by country and WGS 72BE','Between 12°E and 18°E, northern hemisphere between equator and 84°N, onshore and offshore. Chad - east of 18°E.',0.0,84.0,18.0,24.0,0); -INSERT INTO "extent" VALUES('EPSG','3466','China - Ordos - 108°E to 108.5°E and 37.75°N to 38.25°N','China - Ordos basin.',35.0,39.0,107.0,110.01,0); +INSERT INTO "extent" VALUES('EPSG','3466','China - Ordos basin','China - Ordos basin.',35.0,39.0,107.0,110.007,0); INSERT INTO "extent" VALUES('EPSG','3467','North America - Great Lakes basin','Canada and United States (USA) - Great Lakes basin.',40.99,50.74,-93.17,-74.47,0); INSERT INTO "extent" VALUES('EPSG','3468','North America - Great Lakes basin and St Lawrence Seaway','Canada and United States (USA) - Great Lakes basin and St Lawrence Seaway.',40.99,52.22,-93.17,-54.75,0); INSERT INTO "extent" VALUES('EPSG','3469','China - offshore - Yellow Sea','China - offshore - Huang Hai (Yellow Sea).',31.23,37.4,119.23,125.06,0); @@ -3518,7 +3518,7 @@ INSERT INTO "extent" VALUES('EPSG','4540','Africa - South Africa, Lesotho and Eswatini.','Eswatini (Swaziland); Lesotho; South Africa - onshore and offshore.',-50.32,-22.13,13.33,42.85,0); INSERT INTO "extent" VALUES('EPSG','4541','Vietnam - Dien Bien and Lai Chau','Vietnam - Dien Bien and Lai Chau provinces.',20.89,22.82,102.14,103.99,0); INSERT INTO "extent" VALUES('EPSG','4542','Kosovo','Kosovo.',41.85,43.25,19.97,21.8,0); -INSERT INTO "extent" VALUES('EPSG','4543','Serbia','Serbia including Vojvodinja.',42.23,46.19,18.81,23.01,0); +INSERT INTO "extent" VALUES('EPSG','4543','Serbia','Serbia including Vojvodina.',42.232494354248,46.18111038208,18.81702041626,23.004997253418,0); INSERT INTO "extent" VALUES('EPSG','4544','North America - Canada, US (Conus+AK), PRVI','North America - onshore and offshore: Canada - Alberta; British Columbia; Manitoba; New Brunswick; Newfoundland and Labrador; Northwest Territories; Nova Scotia; Nunavut; Ontario; Prince Edward Island; Quebec; Saskatchewan; Yukon. Puerto Rico. United States (USA) - Alabama; Alaska; Arizona; Arkansas; California; Colorado; Connecticut; Delaware; Florida; Georgia; Idaho; Illinois; Indiana; Iowa; Kansas; Kentucky; Louisiana; Maine; Maryland; Massachusetts; Michigan; Minnesota; Mississippi; Missouri; Montana; Nebraska; Nevada; New Hampshire; New Jersey; New Mexico; New York; North Carolina; North Dakota; Ohio; Oklahoma; Oregon; Pennsylvania; Rhode Island; South Carolina; South Dakota; Tennessee; Texas; Utah; Vermont; Virginia; Washington; West Virginia; Wisconsin; Wyoming. US Virgin Islands.',14.92,86.46,167.65,-47.74,0); INSERT INTO "extent" VALUES('EPSG','4545','Vietnam - Ca Mau and Kien Giang','Vietnam - Ca Mau and Kien Giang provinces.',8.33,10.55,103.4,105.54,0); INSERT INTO "extent" VALUES('EPSG','4546','Vietnam - An Giang, Lao Cai, Nghe An, Phu Tho, Yen Bai','Vietnam - An Giang, Lao Cai, Nghe An, Phu Tho and Yen Bai provinces.',10.18,22.85,103.53,105.86,0); @@ -3585,3 +3585,7 @@ INSERT INTO "extent" VALUES('EPSG','4607','UK - Glasgow to Kilmarnock','UK - on or related to the rail route from Glasgow via Barrhead to Kilmarnock and the branch to East Kilbride.',55.55,55.95,-4.65,-4.05,0); 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 -Nru proj-7.2.0/data/sql/geodetic_crs.sql proj-7.2.1/data/sql/geodetic_crs.sql --- proj-7.2.0/data/sql/geodetic_crs.sql 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql/geodetic_crs.sql 2020-12-21 16:29:10.000000000 +0000 @@ -807,7 +807,7 @@ 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 "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 "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); @@ -2090,8 +2090,8 @@ INSERT INTO "usage" VALUES('EPSG','13999','geodetic_crs','EPSG','9378','EPSG','2830','EPSG','1027'); INSERT INTO "geodetic_crs" VALUES('EPSG','9379','IGb14',NULL,'geographic 3D','EPSG','6423','EPSG','1272',NULL,0); INSERT INTO "usage" VALUES('EPSG','14000','geodetic_crs','EPSG','9379','EPSG','1262','EPSG','1027'); -INSERT INTO "geodetic_crs" VALUES('EPSG','9380','IGb14',NULL,'geographic 2D','EPSG','6422','EPSG','1191',NULL,0); -INSERT INTO "usage" VALUES('EPSG','14001','geodetic_crs','EPSG','9380','EPSG','1262','EPSG','1027'); +INSERT INTO "geodetic_crs" VALUES('EPSG','9380','IGb14',NULL,'geographic 2D','EPSG','6422','EPSG','1272',NULL,0); +INSERT INTO "usage" VALUES('EPSG','14213','geodetic_crs','EPSG','9380','EPSG','1262','EPSG','1027'); INSERT INTO "geodetic_crs" VALUES('EPSG','9384','AbInvA96_2020-IRF',NULL,'geographic 2D','EPSG','6422','EPSG','1273',NULL,0); INSERT INTO "usage" VALUES('EPSG','14028','geodetic_crs','EPSG','9384','EPSG','4589','EPSG','1196'); INSERT INTO "geodetic_crs" VALUES('EPSG','9403','PN68',NULL,'geographic 2D','EPSG','6422','EPSG','1286',NULL,0); @@ -2104,3 +2104,7 @@ INSERT INTO "usage" VALUES('EPSG','14151','geodetic_crs','EPSG','9469','EPSG','1122','EPSG','1027'); INSERT INTO "geodetic_crs" VALUES('EPSG','9470','SRGI2013',NULL,'geographic 2D','EPSG','6422','EPSG','1293',NULL,0); INSERT INTO "usage" VALUES('EPSG','14152','geodetic_crs','EPSG','9470','EPSG','1122','EPSG','1183'); +INSERT INTO "geodetic_crs" VALUES('EPSG','9474','PZ-90.02',NULL,'geographic 2D','EPSG','6422','EPSG','1157',NULL,0); +INSERT INTO "usage" VALUES('EPSG','14195','geodetic_crs','EPSG','9474','EPSG','1262','EPSG','1183'); +INSERT INTO "geodetic_crs" VALUES('EPSG','9475','PZ-90.11',NULL,'geographic 2D','EPSG','6422','EPSG','1158',NULL,0); +INSERT INTO "usage" VALUES('EPSG','14194','geodetic_crs','EPSG','9475','EPSG','1262','EPSG','1183'); diff -Nru proj-7.2.0/data/sql/geodetic_datum.sql proj-7.2.1/data/sql/geodetic_datum.sql --- proj-7.2.0/data/sql/geodetic_datum.sql 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql/geodetic_datum.sql 2020-12-21 16:29:10.000000000 +0000 @@ -934,8 +934,8 @@ 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); @@ -1177,6 +1177,6 @@ INSERT INTO "geodetic_datum" VALUES('EPSG','6904','Lisbon 1890 (Lisbon)',NULL,'EPSG','7004','EPSG','8902','1937-01-01',NULL,NULL,0); INSERT INTO "usage" VALUES('EPSG','13849','geodetic_datum','EPSG','6904','EPSG','1294','EPSG','1153'); INSERT INTO "geodetic_datum" VALUES('EPSG','6258','European Terrestrial Reference System 1989 ensemble',NULL,'EPSG','7019','EPSG','8901',NULL,NULL,0.1,0); -INSERT INTO "usage" VALUES('EPSG','13599','geodetic_datum','EPSG','6258','EPSG','1298','EPSG','1026'); +INSERT INTO "usage" VALUES('EPSG','14235','geodetic_datum','EPSG','6258','EPSG','1298','EPSG','1026'); INSERT INTO "geodetic_datum" VALUES('EPSG','6326','World Geodetic System 1984 ensemble',NULL,'EPSG','7030','EPSG','8901',NULL,NULL,2.0,0); -INSERT INTO "usage" VALUES('EPSG','13661','geodetic_datum','EPSG','6326','EPSG','1262','EPSG','1245'); +INSERT INTO "usage" VALUES('EPSG','14343','geodetic_datum','EPSG','6326','EPSG','1262','EPSG','1245'); diff -Nru proj-7.2.0/data/sql/grid_alternatives.sql proj-7.2.1/data/sql/grid_alternatives.sql --- proj-7.2.0/data/sql/grid_alternatives.sql 2020-11-01 09:32:43.000000000 +0000 +++ proj-7.2.1/data/sql/grid_alternatives.sql 2020-12-21 16:29:10.000000000 +0000 @@ -21,6 +21,7 @@ ('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 -Nru proj-7.2.0/data/sql/grid_transformation.sql proj-7.2.1/data/sql/grid_transformation.sql --- proj-7.2.0/data/sql/grid_transformation.sql 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql/grid_transformation.sql 2020-12-21 16:29:10.000000000 +0000 @@ -277,7 +277,7 @@ INSERT INTO "grid_transformation" VALUES('EPSG','1803','AGD66 to GDA94 (11)','Replaces AGD66 to GDA94 variants 6, 7 and 10 (codes 1506 1507 1596). Input expects longitudes to be positive west; EPSG GeogCRS AGD66 (code 4202) and GDA94 (code 4283) both have longitudes positive east. May be used as tfm to WGS 84 - see code 15786.','EPSG','9615','NTv2','EPSG','4202','EPSG','4283',0.1,'EPSG','8656','Latitude and longitude difference file','A66 National (13.09.01).gsb',NULL,NULL,NULL,NULL,NULL,NULL,'ICSM-Aus 0.1m',0); INSERT INTO "usage" VALUES('EPSG','8724','grid_transformation','EPSG','1803','EPSG','2575','EPSG','1031'); INSERT INTO "grid_transformation" VALUES('EPSG','1804','AGD84 to GDA94 (5)','Replaces AGD84 to GDA94 (4) (code 1593) which itself replaced variant 3 (code 1559). Input expects longitudes to be + west; EPSG GeogCRS AGD84 (code 4203) and GDA94 (code 4283) both have longitudes positive east. May be used as tfm to WGS 84 - see 15785','EPSG','9615','NTv2','EPSG','4203','EPSG','4283',0.1,'EPSG','8656','Latitude and longitude difference file','National 84 (02.07.01).gsb',NULL,NULL,NULL,NULL,NULL,NULL,'Auslig-Aus 0.1m',0); -INSERT INTO "usage" VALUES('EPSG','8725','grid_transformation','EPSG','1804','EPSG','2576','EPSG','1031'); +INSERT INTO "usage" VALUES('EPSG','14199','grid_transformation','EPSG','1804','EPSG','2576','EPSG','1031'); INSERT INTO "grid_transformation" VALUES('EPSG','1841','ATS77 to NAD83(CSRS) (1)','Introduced in 1999. Can be taken as an approximate transformation ATS77 to WGS 84 - see code 1688.','EPSG','9615','NTv2','EPSG','4122','EPSG','4617',1.5,'EPSG','8656','Latitude and longitude difference file','NB7783v2.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'GIC-Can NB',1); INSERT INTO "usage" VALUES('EPSG','8762','grid_transformation','EPSG','1841','EPSG','1447','EPSG','1231'); INSERT INTO "grid_transformation" VALUES('EPSG','1843','NAD83 to NAD83(CSRS) (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','4617',1.5,'EPSG','8656','Latitude and longitude difference file','NAD83-98.gsb',NULL,NULL,NULL,NULL,NULL,NULL,'SGQ-Can QC',1); @@ -323,7 +323,7 @@ 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 "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 "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 "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'); @@ -826,6 +826,86 @@ INSERT INTO "usage" VALUES('EPSG','14146','grid_transformation','EPSG','9466','EPSG','4493','EPSG','1133'); INSERT INTO "grid_transformation" VALUES('EPSG','9467','GDA94 to GDA94 + AHD height (1)','Reversible alternative to GDA94 to AHD height (1) (code 5656). Uses AUSGeoid09 model which uses bi-cubic interpolation; bi-linear interpolation of the grid file will give results agreeing to within 1cm 99.97% of the time.','EPSG','1083','Geog3D to Geog2D+Vertical (AUSGeoid v2)','EPSG','4939','EPSG','9464',0.15,'EPSG','8666','Geoid (height correction) model file','AUSGeoid09_V1.01.gsb',NULL,NULL,NULL,NULL,'EPSG','4283','GA-Aus09',0); INSERT INTO "usage" VALUES('EPSG','14147','grid_transformation','EPSG','9467','EPSG','4493','EPSG','1133'); +INSERT INTO "grid_transformation" VALUES('EPSG','9483','Canada velocity grid v7','','EPSG','1070','Point motion by grid (Canada NTv2_Vel)','EPSG','8254','EPSG','8254',0.01,'EPSG','1050','Point motion velocity grid file','cvg70.cvb',NULL,NULL,NULL,NULL,NULL,NULL,'NRC-Can cvg7.0',0); +INSERT INTO "usage" VALUES('EPSG','14214','grid_transformation','EPSG','9483','EPSG','1061','EPSG','1131'); +INSERT INTO "grid_transformation" VALUES('EPSG','9484','ETRS89 to NN54 height (1)','','EPSG','9665','Geographic3D to GravityRelatedHeight (gtx)','EPSG','4937','EPSG','5776',0.02,'EPSG','8666','Geoid (height correction) model file','href2008a.gtx',NULL,NULL,NULL,NULL,NULL,NULL,'SK-Nor 2008',0); +INSERT INTO "usage" VALUES('EPSG','14215','grid_transformation','EPSG','9484','EPSG','1352','EPSG','1133'); +INSERT INTO "grid_transformation" VALUES('EPSG','9485','ETRS89 to NN2000 height (1)','','EPSG','9665','Geographic3D to GravityRelatedHeight (gtx)','EPSG','4937','EPSG','5941',0.02,'EPSG','8666','Geoid (height correction) model file','HREF2018B_NN2000_EUREF89.gtx',NULL,NULL,NULL,NULL,NULL,NULL,'SK-Nor 2018',0); +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 -Nru proj-7.2.0/data/sql/helmert_transformation.sql proj-7.2.1/data/sql/helmert_transformation.sql --- proj-7.2.0/data/sql/helmert_transformation.sql 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql/helmert_transformation.sql 2020-12-21 16:29:10.000000000 +0000 @@ -79,9 +79,9 @@ 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); @@ -416,7 +416,7 @@ INSERT INTO "helmert_transformation" VALUES('EPSG','1279','AGD84 to GDA94 (1)','Derived at 327 stations. May be taken as approximate transformation AGD84 to WGS 84 - see code 15789. For higher accuracy use AGD84 to GDA94 (2) (code 1280). Note: AGD84 officially adopted only in Queensland, South Australia and Western Australia.','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4203','EPSG','4283',5.0,-128.5,-53.0,153.4,'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,'Auslig-Aus 5m',0); INSERT INTO "usage" VALUES('EPSG','8200','helmert_transformation','EPSG','1279','EPSG','2576','EPSG','1045'); INSERT INTO "helmert_transformation" VALUES('EPSG','1280','AGD84 to GDA94 (2)','Replaces AGD84 to WGS 84 (2) (code 1236). May be taken as approximate transformation AGD84 to WGS 84 - see code 1669. Note: although applicable nationwide, AGD84 officially adopted only in Queensland, South Australia and Western Australia.','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','4203','EPSG','4283',1.0,-117.763,-51.51,139.061,'EPSG','9001',-0.292,-0.443,-0.277,'EPSG','9104',-0.191,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'Auslig-Aus 1m',0); -INSERT INTO "usage" VALUES('EPSG','8201','helmert_transformation','EPSG','1280','EPSG','2576','EPSG','1041'); +INSERT INTO "usage" VALUES('EPSG','14198','helmert_transformation','EPSG','1280','EPSG','2576','EPSG','1041'); INSERT INTO "helmert_transformation" VALUES('EPSG','1281','Pulkovo 1995 to WGS 84 (1)','Derived through concatenation of Pulkovo 1995 to PZ-90 (1) (tfm code 1257) and PZ-90 to WGS 84 (2) (tfm code 1244). Mandated for use in Russia by GOST R 51794-2001, but this has been superseded by GOST R 51794-2008. Replaced by tfm code 5043.','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','4200','EPSG','4326',1.0,24.82,-131.21,-82.66,'EPSG','9001',0.0,0.0,-0.16,'EPSG','9104',-0.12,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'GOST-Rus',0); INSERT INTO "usage" VALUES('EPSG','8202','helmert_transformation','EPSG','1281','EPSG','1198','EPSG','1041'); INSERT INTO "helmert_transformation" VALUES('EPSG','1282','Samboja to WGS 84 (1)','Datum shift derived through ITRF93.','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4125','EPSG','4326',NULL,-404.78,685.68,45.47,'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,'TOT-Idn Mah',1); @@ -2036,8 +2036,8 @@ INSERT INTO "usage" VALUES('EPSG','10463','helmert_transformation','EPSG','8270','EPSG','3299','EPSG','1041'); INSERT INTO "helmert_transformation" VALUES('EPSG','8365','ETRS89 to S-JTSK [JTSK03] (1)','Derived at 684 points with known S-JTSK and ETRS89 (ETRF2000 realization) coordinates. Scale parameter was constrained to be zero. UGKK consider this transformation to not be reversible at the 1mm accuracy level: for reverse see transformation code 8367.','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','4258','EPSG','8351',0.001,-485.014055,-169.473618,-483.842943,'EPSG','9001',7.78625453,4.39770887,4.10248899,'EPSG','9104',0.0,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'UGKK-Svk',0); INSERT INTO "usage" VALUES('EPSG','10512','helmert_transformation','EPSG','8365','EPSG','1211','EPSG','1115'); -INSERT INTO "helmert_transformation" VALUES('EPSG','8366','ITRF2014 to ETRF2014 (1)','Scale difference in ppb and scale difference rate in ppb/yr where 1/billion = 1E-9 or nm/m. See ITRF2014 to ETRF2014 (2) (code 8407) for an exactly equivalent transformation but with the transformation''s parameter values at epoch 2010.00.','EPSG','1053','Time-dependent Position Vector tfm (geocentric)','EPSG','7789','EPSG','8401',0.0,0.0,0.0,0.0,'EPSG','1025',0.0,0.0,0.0,'EPSG','1031',0.0,'EPSG','1028',0.0,0.0,0.0,'EPSG','1027',0.085,0.531,-0.77,'EPSG','1032',0.0,'EPSG','1030',1989.0,'EPSG','1029',NULL,NULL,NULL,NULL,NULL,'EUREF-Eur',0); -INSERT INTO "usage" VALUES('EPSG','10513','helmert_transformation','EPSG','8366','EPSG','1298','EPSG','1129'); +INSERT INTO "helmert_transformation" VALUES('EPSG','8366','ITRF2014 to ETRF2014 (1)','Scale difference in ppb and scale difference rate in ppb/yr where 1/billion = 1E-9 or nm/m. See ITRF2014 to ETRF2014 (2) (code 8880) for an exactly equivalent transformation but with the transformation''s parameter values at epoch 2010.00.','EPSG','1053','Time-dependent Position Vector tfm (geocentric)','EPSG','7789','EPSG','8401',0.0,0.0,0.0,0.0,'EPSG','1025',0.0,0.0,0.0,'EPSG','1031',0.0,'EPSG','1028',0.0,0.0,0.0,'EPSG','1027',0.085,0.531,-0.77,'EPSG','1032',0.0,'EPSG','1030',1989.0,'EPSG','1029',NULL,NULL,NULL,NULL,NULL,'EUREF-Eur',0); +INSERT INTO "usage" VALUES('EPSG','14203','helmert_transformation','EPSG','8366','EPSG','1298','EPSG','1129'); INSERT INTO "helmert_transformation" VALUES('EPSG','8367','S-JTSK [JTSK03] to ETRS89 (1)','Derived at 684 points. At the 1mm accuracy level this transformation is not reversible: for reverse see transformation code 8365. May be taken as approximate transformation to WGS 84 - see code 8368.','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','8351','EPSG','4258',0.001,485.021,169.465,483.839,'EPSG','9001',-7.786342,-4.397554,-4.102655,'EPSG','9104',0.0,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'UGKK-Svk',0); INSERT INTO "usage" VALUES('EPSG','10514','helmert_transformation','EPSG','8367','EPSG','1211','EPSG','1027'); INSERT INTO "helmert_transformation" VALUES('EPSG','8368','S-JTSK [JTSK03] to WGS 84 (1)','Parameter values taken from S-JTSK [JTSK03] to ETRS89 (1) (code 8367) assuming that ETRS89 (ETRF2000 realization) is coincident with WGS 84 within the accuracy of the transformation. Within the 1m accuracy of this transformation, it is reversible.','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','8351','EPSG','4326',1.0,485.021,169.465,483.839,'EPSG','9001',-7.786342,-4.397554,-4.102655,'EPSG','9104',0.0,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'UGKK-Svk',0); @@ -2172,8 +2172,8 @@ INSERT INTO "usage" VALUES('EPSG','10820','helmert_transformation','EPSG','8878','EPSG','1298','EPSG','1027'); INSERT INTO "helmert_transformation" VALUES('EPSG','8879','ITRF89 to ETRF2014 (1)','Scale difference in ppb and scale difference rate in ppb/yr where 1/billion = 1E-9 or nm/m.','EPSG','1053','Time-dependent Position Vector tfm (geocentric)','EPSG','4911','EPSG','8401',0.0,-30.4,-35.5,130.8,'EPSG','1025',1.785,11.151,-16.43,'EPSG','1031',-8.19,'EPSG','1028',-0.1,0.5,3.3,'EPSG','1027',0.085,0.531,-0.79,'EPSG','1032',-0.12,'EPSG','1030',2010.0,'EPSG','1029',NULL,NULL,NULL,NULL,NULL,'EUREF-Eur 2014',0); INSERT INTO "usage" VALUES('EPSG','10821','helmert_transformation','EPSG','8879','EPSG','1298','EPSG','1027'); -INSERT INTO "helmert_transformation" VALUES('EPSG','8880','ITRF2014 to ETRF2014 (2)','Scale difference in ppb and scale difference rate in ppb/yr where 1/billion = 1E-9. See ITRF2014 to ETRF2014 (1) (code 8366) for transformation which defines ETRF2014. Transformation 8407 is equivalent to 8366 but with parameter values at epoch 2010.00.','EPSG','1053','Time-dependent Position Vector tfm (geocentric)','EPSG','7789','EPSG','8401',0.0,0.0,0.0,0.0,'EPSG','1025',1.785,11.151,-16.17,'EPSG','1031',0.0,'EPSG','1028',0.0,0.0,0.0,'EPSG','1027',0.085,0.531,-0.77,'EPSG','1032',0.0,'EPSG','1030',2010.0,'EPSG','1029',NULL,NULL,NULL,NULL,NULL,'EUREF-Eur 2014',0); -INSERT INTO "usage" VALUES('EPSG','10822','helmert_transformation','EPSG','8880','EPSG','1298','EPSG','1027'); +INSERT INTO "helmert_transformation" VALUES('EPSG','8880','ITRF2014 to ETRF2014 (2)','Scale difference in ppb and scale difference rate in ppb/yr where 1/billion = 1E-9. See ITRF2014 to ETRF2014 (1) (code 8366) for transformation which defines ETRF2014. Transformation 8880 is equivalent to 8366 but with parameter values at epoch 2010.00.','EPSG','1053','Time-dependent Position Vector tfm (geocentric)','EPSG','7789','EPSG','8401',0.0,0.0,0.0,0.0,'EPSG','1025',1.785,11.151,-16.17,'EPSG','1031',0.0,'EPSG','1028',0.0,0.0,0.0,'EPSG','1027',0.085,0.531,-0.77,'EPSG','1032',0.0,'EPSG','1030',2010.0,'EPSG','1029',NULL,NULL,NULL,NULL,NULL,'EUREF-Eur 2014',0); +INSERT INTO "usage" VALUES('EPSG','14204','helmert_transformation','EPSG','8880','EPSG','1298','EPSG','1027'); INSERT INTO "helmert_transformation" VALUES('EPSG','8882','Camacupa 2015 to WGS 84 (11)','Used by CIDDEMA for delimitation of Angola''s EEZ boundary. Derived by Univ. of Lisbon using 38 REPANGOL points. Average horizontal error 1m, vertical 3m; max radial error 6m. Application offshore differs from Camacupa 1948 to WGS 84 by approx 25m.','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','8694','EPSG','4326',3.0,-93.799,-132.737,-219.073,'EPSG','9001',1.844,-0.648,6.37,'EPSG','9104',-0.169,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'CIDDEMA-Ago',0); INSERT INTO "usage" VALUES('EPSG','10823','helmert_transformation','EPSG','8882','EPSG','1029','EPSG','1053'); INSERT INTO "helmert_transformation" VALUES('EPSG','8883','Camacupa 1948 to RSAO13 (1)','Parameter values taken from Camacupa 1948 to WGS 84 (7) (code 1324) assuming that RSAO13 is coincident with WGS 84 within the accuracy of the transformation.','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4220','EPSG','8699',3.0,-48.0,-345.0,-231.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,'ELF-Ago B15',0); @@ -2406,6 +2406,10 @@ INSERT INTO "usage" VALUES('EPSG','14140','helmert_transformation','EPSG','9460','EPSG','4177','EPSG','1268'); INSERT INTO "helmert_transformation" VALUES('EPSG','9472','DGN95 to SRGI2013 (1)','Derived at 533 stations. Mean residual 0.218m. Note: information source gives rotations given in radians.','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','4755','EPSG','9470',0.2,-0.2773,0.0534,0.4819,'EPSG','9001',0.0935,-0.0286,0.00969,'EPSG','9109',-0.028,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'SRGI-Idn',0); INSERT INTO "usage" VALUES('EPSG','14154','helmert_transformation','EPSG','9472','EPSG','1122','EPSG','1026'); +INSERT INTO "helmert_transformation" VALUES('EPSG','9486','MGI 1901 to WGS 84 (15)','Parameter values from MGI 1901 to ETRS89 (8) (code 9495). Assumes SRB-ETRS89 and WGS 84 can be considered the same to within the accuracy of the transformation.','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','3906','EPSG','4326',1.0,577.84843,165.45019,390.43652,'EPSG','9001',-4.93131,0.96052,13.05072,'EPSG','9104',7.86546,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'IOGP-Srb 2020',0); +INSERT INTO "usage" VALUES('EPSG','14220','helmert_transformation','EPSG','9486','EPSG','4543','EPSG','1041'); +INSERT INTO "helmert_transformation" VALUES('EPSG','9495','MGI 1901 to SRB-ETRS89 (8)','','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','3906','EPSG','8685',0.46,577.84843,165.45019,390.43652,'EPSG','9001',-4.93131,0.96052,13.05072,'EPSG','9104',7.86546,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'RGZ-Srb 0.5m 2020',0); +INSERT INTO "usage" VALUES('EPSG','14227','helmert_transformation','EPSG','9495','EPSG','4543','EPSG','1153'); INSERT INTO "helmert_transformation" VALUES('EPSG','10085','Trinidad 1903 to WGS 84 (2)','Parameter values provided to EOG by Trinidad Ministry of Energy and Energy Industries. Used by EOG offshore Trinidad (including Pelican, Kiskadee and Ibis fields) since 1996.','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4302','EPSG','4326',3.0,-61.0,285.2,471.6,'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,'EOG-Tto Trin',0); INSERT INTO "usage" VALUES('EPSG','11086','helmert_transformation','EPSG','10085','EPSG','1339','EPSG','1136'); INSERT INTO "helmert_transformation" VALUES('EPSG','10086','JAD69 to WGS 72 (1)','Derived in 1977 through Transit observations at 2 stations by US DMA.','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4242','EPSG','4322',15.0,48.0,208.0,382.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,'SD-Jam',0); @@ -2896,7 +2900,7 @@ INSERT INTO "helmert_transformation" VALUES('EPSG','15978','NAD27 to WGS 84 (88)','','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','4267','EPSG','4326',1.0,2.478,149.752,197.726,'EPSG','9001',-0.526,-0.498,0.501,'EPSG','9104',0.685,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'ONHG-Cub',0); INSERT INTO "usage" VALUES('EPSG','11988','helmert_transformation','EPSG','15978','EPSG','1077','EPSG','1041'); INSERT INTO "helmert_transformation" VALUES('EPSG','15979','AGD66 to GDA94 (12)','Use only offshore: onshore, tfms 1458 (ACT), 1594 (Tas), 1460 (NSW and Vic) and 1595 (NT) are more accurate.May be used as a tfm to WGS 84 - see code 15980.','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','4202','EPSG','4283',3.0,-117.808,-51.536,137.784,'EPSG','9001',-0.303,-0.446,-0.234,'EPSG','9104',-0.29,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'ICSM-Aus off',0); -INSERT INTO "usage" VALUES('EPSG','11989','helmert_transformation','EPSG','15979','EPSG','3559','EPSG','1043'); +INSERT INTO "usage" VALUES('EPSG','14200','helmert_transformation','EPSG','15979','EPSG','3559','EPSG','1043'); INSERT INTO "helmert_transformation" VALUES('EPSG','15980','AGD66 to WGS 84 (18)','Parameter values from AGD66 to GDA94 (12) (code 15979). Assumes GDA94 and WGS 84 can be considered the same to within the accuracy of the transformation. Use only offshore: onshore tfms 1665-68 for ACT, NSW/Vic, Tas and NT respectively are more accurate.','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','4202','EPSG','4326',3.0,-117.808,-51.536,137.784,'EPSG','9001',-0.303,-0.446,-0.234,'EPSG','9104',-0.29,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'EPSG-Aus off',0); INSERT INTO "usage" VALUES('EPSG','11990','helmert_transformation','EPSG','15980','EPSG','3559','EPSG','1043'); INSERT INTO "helmert_transformation" VALUES('EPSG','15981','MGI to Slovenia 1996 (1)','Info source also gives a separate reverse tfm with slightly different parameter values. Given the tfm accuracy these differences are not significant and this tfm can be considered reversible. May be taken as approximate tfm MGI to WGS 84 (see code 15982)','EPSG','9607','Coordinate Frame rotation (geog2D domain)','EPSG','4312','EPSG','4765',1.0,409.545,72.164,486.872,'EPSG','9001',-3.085957,-5.46911,11.020289,'EPSG','9104',17.919665,'EPSG','9202',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'GuRS-Svn',1); diff -Nru proj-7.2.0/data/sql/metadata.sql proj-7.2.1/data/sql/metadata.sql --- proj-7.2.0/data/sql/metadata.sql 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/data/sql/metadata.sql 2020-12-21 16:29:10.000000000 +0000 @@ -1,2 +1,13 @@ -INSERT INTO "metadata" VALUES('EPSG.VERSION', 'v10.003'); -INSERT INTO "metadata" VALUES('EPSG.DATE', '2020-10-05'); +-- Version of the database structure. +-- The major number indicates an incompatible change (e.g. table or column +-- removed or renamed). +-- The minor number is incremented if a backward compatible change done, that +-- is the new database can still work with an older PROJ version. +-- When updating those numbers, the DATABASE_LAYOUT_VERSION_MAJOR and +-- DATABASE_LAYOUT_VERSION_MINOR constants in src/iso19111/factory.cpp must be +-- updated as well. +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.008'); +INSERT INTO "metadata" VALUES('EPSG.DATE', '2020-12-16'); diff -Nru proj-7.2.0/data/sql/nkg.sql proj-7.2.1/data/sql/nkg.sql --- proj-7.2.0/data/sql/nkg.sql 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/data/sql/nkg.sql 2020-12-25 15:59:31.000000000 +0000 @@ -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 -Nru proj-7.2.0/data/sql/other_transformation.sql proj-7.2.1/data/sql/other_transformation.sql --- proj-7.2.0/data/sql/other_transformation.sql 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql/other_transformation.sql 2020-12-21 16:29:10.000000000 +0000 @@ -438,12 +438,18 @@ 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 -Nru proj-7.2.0/data/sql/proj_db_table_defs.sql proj-7.2.1/data/sql/proj_db_table_defs.sql --- proj-7.2.0/data/sql/proj_db_table_defs.sql 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql/proj_db_table_defs.sql 2020-12-21 16:29:10.000000000 +0000 @@ -1460,9 +1460,9 @@ UNION ALL SELECT 'prime_meridian', auth_name, code, name, NULL, deprecated FROM prime_meridian UNION ALL - SELECT 'geodetic_datum', auth_name, code, name, CASE WHEN ensemble_accuracy IS NOT NULL THEN "ensemble" ELSE "datum" END, deprecated FROM geodetic_datum + SELECT 'geodetic_datum', auth_name, code, name, CASE WHEN ensemble_accuracy IS NOT NULL THEN 'ensemble' ELSE 'datum' END, deprecated FROM geodetic_datum UNION ALL - SELECT 'vertical_datum', auth_name, code, name, CASE WHEN ensemble_accuracy IS NOT NULL THEN "ensemble" ELSE "datum" END, deprecated FROM vertical_datum + SELECT 'vertical_datum', auth_name, code, name, CASE WHEN ensemble_accuracy IS NOT NULL THEN 'ensemble' ELSE 'datum' END, deprecated FROM vertical_datum UNION ALL SELECT 'axis', auth_name, code, name, NULL, 0 as deprecated FROM axis UNION ALL diff -Nru proj-7.2.0/data/sql/projected_crs.sql proj-7.2.1/data/sql/projected_crs.sql --- proj-7.2.0/data/sql/projected_crs.sql 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql/projected_crs.sql 2020-12-21 16:29:10.000000000 +0000 @@ -2114,9 +2114,9 @@ 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 "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 "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,6 +7302,10 @@ 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','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); @@ -8923,7 +8927,9 @@ 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 -Nru proj-7.2.0/data/sql/scope.sql proj-7.2.1/data/sql/scope.sql --- proj-7.2.0/data/sql/scope.sql 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/data/sql/scope.sql 2020-12-21 16:29:10.000000000 +0000 @@ -107,6 +107,7 @@ INSERT INTO "scope" VALUES('EPSG','1128','Geodesy. Defines ETRF2005.',0); INSERT INTO "scope" VALUES('EPSG','1129','Geodesy. Defines ETRF2014.',0); INSERT INTO "scope" VALUES('EPSG','1130','EEZ delimitation.',0); +INSERT INTO "scope" VALUES('EPSG','1131','Change of coordinate epoch for points referenced to NAD83(CSRS)v7.',0); INSERT INTO "scope" VALUES('EPSG','1132','Derivation of approximate gravity-related heights from GNSS observations.',0); INSERT INTO "scope" VALUES('EPSG','1133','Derivation of gravity-related heights from GNSS observations.',0); INSERT INTO "scope" VALUES('EPSG','1134','Description of the use or purpose of the CRS.',0); @@ -245,3 +246,4 @@ 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 -Nru proj-7.2.0/data/sql/supersession.sql proj-7.2.1/data/sql/supersession.sql --- proj-7.2.0/data/sql/supersession.sql 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/data/sql/supersession.sql 2020-12-21 16:29:10.000000000 +0000 @@ -246,3 +246,5 @@ INSERT INTO "supersession" VALUES('grid_transformation','EPSG','9169','grid_transformation','EPSG','9174','EPSG',0); INSERT INTO "supersession" VALUES('grid_transformation','EPSG','15932','grid_transformation','EPSG','9409','EPSG',1); INSERT INTO "supersession" VALUES('grid_transformation','EPSG','15932','grid_transformation','EPSG','9408','EPSG',1); +INSERT INTO "supersession" VALUES('helmert_transformation','EPSG','7675','helmert_transformation','EPSG','9495','EPSG',0); +INSERT INTO "supersession" VALUES('helmert_transformation','EPSG','7676','helmert_transformation','EPSG','9486','EPSG',1); diff -Nru proj-7.2.0/data/sql/vertical_crs.sql proj-7.2.1/data/sql/vertical_crs.sql --- proj-7.2.0/data/sql/vertical_crs.sql 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql/vertical_crs.sql 2020-12-21 16:29:10.000000000 +0000 @@ -85,7 +85,7 @@ INSERT INTO "vertical_crs" VALUES('EPSG','5710','Ostend height',NULL,'EPSG','6499','EPSG','5110',0); INSERT INTO "usage" VALUES('EPSG','4153','vertical_crs','EPSG','5710','EPSG','1347','EPSG','1179'); INSERT INTO "vertical_crs" VALUES('EPSG','5711','AHD height',NULL,'EPSG','6499','EPSG','5111',0); -INSERT INTO "usage" VALUES('EPSG','4154','vertical_crs','EPSG','5711','EPSG','4493','EPSG','1263'); +INSERT INTO "usage" VALUES('EPSG','14230','vertical_crs','EPSG','5711','EPSG','4493','EPSG','1263'); INSERT INTO "vertical_crs" VALUES('EPSG','5712','AHD (Tasmania) height',NULL,'EPSG','6499','EPSG','5112',0); INSERT INTO "usage" VALUES('EPSG','4155','vertical_crs','EPSG','5712','EPSG','2947','EPSG','1179'); INSERT INTO "vertical_crs" VALUES('EPSG','5713','CGVD28 height',NULL,'EPSG','6499','EPSG','5114',0); @@ -453,9 +453,9 @@ 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); @@ -481,6 +481,8 @@ INSERT INTO "vertical_crs" VALUES('EPSG','9451','BI height',NULL,'EPSG','6499','EPSG','1288',0); INSERT INTO "usage" VALUES('EPSG','14087','vertical_crs','EPSG','9451','EPSG','4606','EPSG','1026'); INSERT INTO "vertical_crs" VALUES('EPSG','9458','AVWS height',NULL,'EPSG','6499','EPSG','1292',0); -INSERT INTO "usage" VALUES('EPSG','14138','vertical_crs','EPSG','9458','EPSG','4177','EPSG','1264'); +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 -Nru proj-7.2.0/data/sql/vertical_datum.sql proj-7.2.1/data/sql/vertical_datum.sql --- proj-7.2.0/data/sql/vertical_datum.sql 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql/vertical_datum.sql 2020-12-21 16:29:10.000000000 +0000 @@ -176,8 +176,8 @@ 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 "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 "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 -Nru proj-7.2.0/data/sql_filelist.cmake proj-7.2.1/data/sql_filelist.cmake --- proj-7.2.0/data/sql_filelist.cmake 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/data/sql_filelist.cmake 2020-12-25 15:59:31.000000000 +0000 @@ -35,5 +35,6 @@ "${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 -Nru proj-7.2.0/debian/changelog proj-7.2.1/debian/changelog --- proj-7.2.0/debian/changelog 2020-12-17 10:00:00.000000000 +0000 +++ proj-7.2.1/debian/changelog 2021-01-04 17:00:00.000000000 +0000 @@ -1,8 +1,26 @@ -proj (7.2.0-1~focal0) focal; urgency=medium +proj (7.2.1-1~focal0) focal; urgency=medium * No change rebuild for focal. - -- Angelos Tzotsos Thu, 17 Dec 2020 12:00:00 +0200 + -- Angelos Tzotsos Mon, 04 Jan 2021 19:00:00 +0200 + +proj (7.2.1-1) unstable; urgency=medium + + * New upstream release. + * Update 7.2.1~rc1 symbols for other architectures. + * Strip pre-releases from symbols version. + * Move from experimental to unstable. + + -- Bas Couwenberg Fri, 01 Jan 2021 16:52:05 +0100 + +proj (7.2.1~rc1-1~exp1) experimental; urgency=medium + + * New upstream release candidate. + * Bump watch file version to 4. + * Bump Standards-Version to 4.5.1, no changes. + * Update symbols for amd64. + + -- Bas Couwenberg Sun, 27 Dec 2020 06:25:35 +0100 proj (7.2.0-1) unstable; urgency=medium diff -Nru proj-7.2.0/debian/control proj-7.2.1/debian/control --- proj-7.2.0/debian/control 2020-11-01 10:02:32.000000000 +0000 +++ proj-7.2.1/debian/control 2021-01-01 15:47:54.000000000 +0000 @@ -13,7 +13,7 @@ sharutils, sqlite3, xz-utils -Standards-Version: 4.5.0 +Standards-Version: 4.5.1 Vcs-Browser: https://salsa.debian.org/debian-gis-team/proj Vcs-Git: https://salsa.debian.org/debian-gis-team/proj.git Homepage: https://proj.org/ diff -Nru proj-7.2.0/debian/libproj19.symbols proj-7.2.1/debian/libproj19.symbols --- proj-7.2.0/debian/libproj19.symbols 2020-11-01 10:07:18.000000000 +0000 +++ proj-7.2.1/debian/libproj19.symbols 2021-01-01 15:51:47.000000000 +0000 @@ -1,4 +1,4 @@ -# SymbolsHelper-Confirmed: 7.2.0 amd64 +# SymbolsHelper-Confirmed: 7.2.1~rc1 amd64 arm64 armel armhf hppa hurd-i386 i386 ia64 m68k mips64el mipsel powerpc ppc64 ppc64el riscv64 s390x sh4 sparc64 x32 libproj.so.19 #PACKAGE# #MINVER# * Build-Depends-Package: libproj-dev _Z10pj_ell_setP9projCtx_tP8ARG_listPdS3_@Base 6.0.0 @@ -1179,14 +1179,10 @@ (optional=templinst|arch=armel riscv64)_ZNSt12__shared_ptrIN5osgeo4proj3crs3CRSELN9__gnu_cxx12_Lock_policyE1EEC2INS2_13GeographicCRSEvEERKS_IT_LS5_1EE@Base 6.0.0 (optional=templinst|arch=!armel !riscv64)_ZNSt12__shared_ptrIN5osgeo4proj3crs3CRSELN9__gnu_cxx12_Lock_policyE2EEC1INS2_13GeographicCRSEvEERKS_IT_LS5_2EE@Base 6.0.0 (optional=templinst|arch=!armel !riscv64)_ZNSt12__shared_ptrIN5osgeo4proj3crs3CRSELN9__gnu_cxx12_Lock_policyE2EEC2INS2_13GeographicCRSEvEERKS_IT_LS5_2EE@Base 6.0.0 - (optional=templinst|arch=armel riscv64)_ZNSt12__shared_ptrIN5osgeo4proj9operation19CoordinateOperationELN9__gnu_cxx12_Lock_policyE1EEC1INS2_10ConversionEvEERKS_IT_LS5_1EE@Base 6.0.0 (optional=templinst|arch=armel)_ZNSt12__shared_ptrIN5osgeo4proj9operation19CoordinateOperationELN9__gnu_cxx12_Lock_policyE1EEC1INS2_14TransformationEvEERKS_IT_LS5_1EE@Base 7.1.0 - (optional=templinst|arch=armel riscv64)_ZNSt12__shared_ptrIN5osgeo4proj9operation19CoordinateOperationELN9__gnu_cxx12_Lock_policyE1EEC2INS2_10ConversionEvEERKS_IT_LS5_1EE@Base 6.0.0 (optional=templinst|arch=armel)_ZNSt12__shared_ptrIN5osgeo4proj9operation19CoordinateOperationELN9__gnu_cxx12_Lock_policyE1EEC2INS2_14TransformationEvEERKS_IT_LS5_1EE@Base 7.1.0 - (optional=templinst|arch=!armel !riscv64)_ZNSt12__shared_ptrIN5osgeo4proj9operation19CoordinateOperationELN9__gnu_cxx12_Lock_policyE2EEC1INS2_10ConversionEvEERKS_IT_LS5_2EE@Base 6.0.0 - (optional=templinst|arch=!armel !riscv64)_ZNSt12__shared_ptrIN5osgeo4proj9operation19CoordinateOperationELN9__gnu_cxx12_Lock_policyE2EEC1INS2_14TransformationEvEERKS_IT_LS5_2EE@Base 7.1.0 - (optional=templinst|arch=!armel !riscv64)_ZNSt12__shared_ptrIN5osgeo4proj9operation19CoordinateOperationELN9__gnu_cxx12_Lock_policyE2EEC2INS2_10ConversionEvEERKS_IT_LS5_2EE@Base 6.0.0 - (optional=templinst|arch=!armel !riscv64)_ZNSt12__shared_ptrIN5osgeo4proj9operation19CoordinateOperationELN9__gnu_cxx12_Lock_policyE2EEC2INS2_14TransformationEvEERKS_IT_LS5_2EE@Base 7.1.0 + (optional=templinst|arch=arm64 armhf hppa ia64 m68k mips64el ppc64el sh4 sparc64)_ZNSt12__shared_ptrIN5osgeo4proj9operation19CoordinateOperationELN9__gnu_cxx12_Lock_policyE2EEC1INS2_14TransformationEvEERKS_IT_LS5_2EE@Base 7.2.1 + (optional=templinst|arch=arm64 armhf hppa ia64 m68k mips64el ppc64el sh4 sparc64)_ZNSt12__shared_ptrIN5osgeo4proj9operation19CoordinateOperationELN9__gnu_cxx12_Lock_policyE2EEC2INS2_14TransformationEvEERKS_IT_LS5_2EE@Base 7.2.1 (optional=templinst)_ZNSt13_Bvector_baseISaIbEE13_M_deallocateEv@Base 6.0.0 _ZNSt14_Function_baseD1Ev@Base 7.1.1 _ZNSt14_Function_baseD2Ev@Base 7.1.1 @@ -1893,7 +1889,7 @@ (optional=templinst|arch=amd64 arm64 hppa ia64 mips64el ppc64el riscv64 sh4 sparc64 x32)_ZNSt3setINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt4lessIS5_ESaIS5_EE6insertERKS5_@Base 7.1.1 (optional=templinst)_ZNSt3setINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt4lessIS5_ESaIS5_EED1Ev@Base 6.0.0 (optional=templinst)_ZNSt3setINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt4lessIS5_ESaIS5_EED2Ev@Base 6.0.0 - (optional=templinst|arch=amd64 arm64)_ZNSt3setISt4pairINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES6_ESt4lessIS7_ESaIS7_EE6insertEOS7_@Base 7.2.0 + (optional=templinst|arch=amd64 arm64 x32)_ZNSt3setISt4pairINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES6_ESt4lessIS7_ESaIS7_EE6insertEOS7_@Base 7.2.0 (optional=templinst|arch=armel armhf ia64)_ZNSt4pairINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES5_EC1IS5_S5_Lb1EEERKS5_S9_@Base 7.1.0 (optional=templinst|arch=armel armhf ia64)_ZNSt4pairINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES5_EC2IS5_S5_Lb1EEERKS5_S9_@Base 7.1.0 (optional=templinst)_ZNSt5dequeIdSaIdEE16_M_push_back_auxIJRKdEEEvDpOT_@Base 6.0.0 @@ -1936,6 +1932,7 @@ (optional=templinst|arch=amd64 arm64 ia64 mips64el ppc64 ppc64el riscv64 s390x sparc64)_ZNSt6vectorIhSaIhEE17_M_default_appendEm@Base 7.0.0 (optional=templinst)_ZNSt6vectorIhSaIhEEaSERKS1_@Base 7.1.1 (optional=templinst)_ZNSt6vectorIjSaIjEE17_M_realloc_insertIJRKjEEEvN9__gnu_cxx17__normal_iteratorIPjS1_EEDpOT_@Base 7.2.0 + (optional=templinst)_ZNSt6vectorImSaImEE17_M_realloc_insertIJRKmEEEvN9__gnu_cxx17__normal_iteratorIPmS1_EEDpOT_@Base 7.2.1 (optional=templinst)_ZNSt6vectorIxSaIxEE17_M_realloc_insertIJxEEEvN9__gnu_cxx17__normal_iteratorIPxS1_EEDpOT_@Base 7.0.0 (optional=templinst)_ZNSt7__cxx1110_List_baseIN5osgeo4proj2io16AuthorityFactory7CRSInfoESaIS5_EE8_M_clearEv@Base 6.0.0 (optional=templinst)_ZNSt7__cxx1110_List_baseIN5osgeo4proj2io16AuthorityFactory8UnitInfoESaIS5_EE8_M_clearEv@Base 7.1.0 @@ -1956,7 +1953,7 @@ (arch=amd64 arm64 ia64 mips64el ppc64 ppc64el riscv64 s390x sparc64)_ZNSt7__cxx119to_stringEm@Base 7.2.0 (optional=templinst)_ZNSt8_Rb_treeINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES5_St9_IdentityIS5_ESt4lessIS5_ESaIS5_EE16_M_insert_uniqueIRKS5_EESt4pairISt17_Rb_tree_iteratorIS5_EbEOT_@Base 6.0.0 (optional=templinst|arch=armel armhf hurd-i386 i386 m68k mipsel powerpc ppc64 s390x)_ZNSt8_Rb_treeINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES5_St9_IdentityIS5_ESt4lessIS5_ESaIS5_EE16_M_insert_uniqueIS5_EESt4pairISt17_Rb_tree_iteratorIS5_EbEOT_@Base 7.1.1 - (optional=templinst)_ZNSt8_Rb_treeINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES5_St9_IdentityIS5_ESt4lessIS5_ESaIS5_EE24_M_get_insert_unique_posERKS5_@Base 6.0.0 + (optional=templinst|arch=!armel !armhf !hurd-i386 !i386 !m68k !mipsel !powerpc !ppc64 !s390x)_ZNSt8_Rb_treeINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES5_St9_IdentityIS5_ESt4lessIS5_ESaIS5_EE24_M_get_insert_unique_posERKS5_@Base 6.0.0 (optional=templinst)_ZNSt8_Rb_treeINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES5_St9_IdentityIS5_ESt4lessIS5_ESaIS5_EE4findERKS5_@Base 6.3.1 (optional=templinst)_ZNSt8_Rb_treeINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES5_St9_IdentityIS5_ESt4lessIS5_ESaIS5_EE8_M_eraseEPSt13_Rb_tree_nodeIS5_E@Base 6.0.0 (optional=templinst)_ZNSt8_Rb_treeINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt4pairIKS5_NS0_4listISt6vectorIS5_SaIS5_EESaISB_EEEESt10_Select1stISE_ESt4lessIS5_ESaISE_EE24_M_get_insert_unique_posERS7_@Base 6.0.0 @@ -1994,6 +1991,7 @@ (optional=templinst)_ZSteqIcEN9__gnu_cxx11__enable_ifIXsrSt9__is_charIT_E7__valueEbE6__typeERKNSt7__cxx1112basic_stringIS3_St11char_traitsIS3_ESaIS3_EEESE_@Base 6.0.0 (optional=templinst|arch=amd64 arm64 hppa ia64 mips64el ppc64el riscv64 sh4 sparc64 x32)_ZStltINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES5_EbRKSt4pairIT_T0_ESB_@Base 7.1.1 (optional=templinst|arch=amd64 arm64 kfreebsd-amd64 x32)_ZStplIcSt11char_traitsIcESaIcEENSt7__cxx1112basic_stringIT_T0_T1_EEOS8_PKS5_@Base 6.0.0 + (optional=templinst)_ZStplIcSt11char_traitsIcESaIcEENSt7__cxx1112basic_stringIT_T0_T1_EEOS8_S9_@Base 7.2.1 (optional=templinst|arch=amd64 arm64 kfreebsd-amd64 x32)_ZStplIcSt11char_traitsIcESaIcEENSt7__cxx1112basic_stringIT_T0_T1_EEPKS5_OS8_@Base 6.0.0 (optional=templinst)_ZStplIcSt11char_traitsIcESaIcEENSt7__cxx1112basic_stringIT_T0_T1_EEPKS5_RKS8_@Base 6.0.0 (optional=templinst|arch=!mipsel)_ZStplIcSt11char_traitsIcESaIcEENSt7__cxx1112basic_stringIT_T0_T1_EERKS8_PKS5_@Base 6.0.0 diff -Nru proj-7.2.0/debian/watch proj-7.2.1/debian/watch --- proj-7.2.0/debian/watch 2020-02-18 16:51:21.000000000 +0000 +++ proj-7.2.1/debian/watch 2020-12-27 05:15:25.000000000 +0000 @@ -1,4 +1,4 @@ -version=3 +version=4 opts=\ dversionmangle=s/\+(debian|dfsg|ds|deb)\d*$//,\ uversionmangle=s/(\d)[_\.\-\+]?((RC|rc|pre|dev|b|beta|a|alpha)\d*)$/$1~$2/;s/RC/rc/ \ diff -Nru proj-7.2.0/include/proj/crs.hpp proj-7.2.1/include/proj/crs.hpp --- proj-7.2.0/include/proj/crs.hpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/include/proj/crs.hpp 2020-12-26 18:57:21.000000000 +0000 @@ -155,6 +155,11 @@ const; PROJ_INTERNAL bool hasImplicitCS() const; + + PROJ_INTERNAL static CRSNNPtr + getResolvedCRS(const CRSNNPtr &crs, + const io::AuthorityFactoryPtr &authFactory, + metadata::ExtentPtr &extentOut); //! @endcond protected: @@ -335,6 +340,11 @@ PROJ_INTERNAL std::list> _identify(const io::AuthorityFactoryPtr &authorityFactory) const override; + PROJ_INTERNAL bool + _isEquivalentToNoTypeCheck(const util::IComparable *other, + util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const; + INLINED_MAKE_SHARED private: diff -Nru proj-7.2.0/include/proj/internal/coordinateoperation_constants.hpp proj-7.2.1/include/proj/internal/coordinateoperation_constants.hpp --- proj-7.2.0/include/proj/internal/coordinateoperation_constants.hpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/include/proj/internal/coordinateoperation_constants.hpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,1371 +0,0 @@ -/****************************************************************************** - * - * Project: PROJ - * Purpose: ISO19111:2019 implementation - * Author: Even Rouault - * - ****************************************************************************** - * Copyright (c) 2018, Even Rouault - * - * 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_COORDINATE_OPERATION_CPP -#error This file should only be included from coordinateoperation.cpp -#endif - -#ifndef COORDINATEOPERATION_CONSTANTS_HH_INCLUDED -#define COORDINATEOPERATION_CONSTANTS_HH_INCLUDED - -#include "coordinateoperation_internal.hpp" -#include "proj_constants.h" - -//! @cond Doxygen_Suppress -// --------------------------------------------------------------------------- - -// anonymous namespace -namespace { - -using namespace ::NS_PROJ; -using namespace ::NS_PROJ::operation; - -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"; - -static const char *lat_0 = "lat_0"; -static const char *lat_1 = "lat_1"; -static const char *lat_2 = "lat_2"; -static const char *lat_ts = "lat_ts"; -static const char *lon_0 = "lon_0"; -static const char *lon_1 = "lon_1"; -static const char *lon_2 = "lon_2"; -static const char *lonc = "lonc"; -static const char *alpha = "alpha"; -static const char *gamma = "gamma"; -static const char *k_0 = "k_0"; -static const char *k = "k"; -static const char *x_0 = "x_0"; -static const char *y_0 = "y_0"; -static const char *h = "h"; - -static 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}; - -static const ParamMapping paramLongitudeNatOrigin = { - EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, WKT1_CENTRAL_MERIDIAN, - common::UnitOfMeasure::Type::ANGULAR, lon_0}; - -static const ParamMapping paramScaleFactor = { - EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, - common::UnitOfMeasure::Type::SCALE, k_0}; - -static const ParamMapping paramScaleFactorK = { - EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, - common::UnitOfMeasure::Type::SCALE, k}; - -static const ParamMapping paramFalseEasting = { - EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, - WKT1_FALSE_EASTING, common::UnitOfMeasure::Type::LINEAR, x_0}; - -static const ParamMapping paramFalseNorthing = { - EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, - WKT1_FALSE_NORTHING, common::UnitOfMeasure::Type::LINEAR, y_0}; - -static const ParamMapping paramLatitudeFalseOrigin = { - EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, WKT1_LATITUDE_OF_ORIGIN, - common::UnitOfMeasure::Type::ANGULAR, lat_0}; - -static const ParamMapping paramLongitudeFalseOrigin = { - EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, WKT1_CENTRAL_MERIDIAN, - common::UnitOfMeasure::Type::ANGULAR, lon_0}; - -static const ParamMapping paramFalseEastingOrigin = { - EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, WKT1_FALSE_EASTING, - common::UnitOfMeasure::Type::LINEAR, x_0}; - -static const ParamMapping paramFalseNorthingOrigin = { - EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, WKT1_FALSE_NORTHING, - common::UnitOfMeasure::Type::LINEAR, y_0}; - -static const ParamMapping paramLatitude1stStdParallel = { - EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, WKT1_STANDARD_PARALLEL_1, - common::UnitOfMeasure::Type::ANGULAR, lat_1}; - -static const ParamMapping paramLatitude2ndStdParallel = { - EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, WKT1_STANDARD_PARALLEL_2, - common::UnitOfMeasure::Type::ANGULAR, lat_2}; - -static const ParamMapping *const paramsNatOriginScale[] = { - ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactor, - ¶mFalseEasting, ¶mFalseNorthing, nullptr}; - -static const ParamMapping *const paramsNatOriginScaleK[] = { - ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactorK, - ¶mFalseEasting, ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramLatFirstPoint = { - "Latitude of 1st point", 0, "Latitude_Of_1st_Point", - common::UnitOfMeasure::Type::ANGULAR, lat_1}; -static const ParamMapping paramLongFirstPoint = { - "Longitude of 1st point", 0, "Longitude_Of_1st_Point", - common::UnitOfMeasure::Type::ANGULAR, lon_1}; -static const ParamMapping paramLatSecondPoint = { - "Latitude of 2nd point", 0, "Latitude_Of_2nd_Point", - common::UnitOfMeasure::Type::ANGULAR, lat_2}; -static const ParamMapping paramLongSecondPoint = { - "Longitude of 2nd point", 0, "Longitude_Of_2nd_Point", - common::UnitOfMeasure::Type::ANGULAR, lon_2}; - -static const ParamMapping *const paramsTPEQD[] = {¶mLatFirstPoint, - ¶mLongFirstPoint, - ¶mLatSecondPoint, - ¶mLongSecondPoint, - ¶mFalseEasting, - ¶mFalseNorthing, - nullptr}; - -static const ParamMapping *const paramsTMG[] = { - ¶mLatitudeFalseOrigin, ¶mLongitudeFalseOrigin, - ¶mFalseEastingOrigin, ¶mFalseNorthingOrigin, nullptr}; - -static const ParamMapping paramLatFalseOriginLatOfCenter = { - EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, WKT1_LATITUDE_OF_CENTER, - common::UnitOfMeasure::Type::ANGULAR, lat_0}; - -static const ParamMapping paramLongFalseOriginLongOfCenter = { - EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, WKT1_LONGITUDE_OF_CENTER, - common::UnitOfMeasure::Type::ANGULAR, lon_0}; - -static const ParamMapping *const paramsAEA[] = { - ¶mLatFalseOriginLatOfCenter, - ¶mLongFalseOriginLongOfCenter, - ¶mLatitude1stStdParallel, - ¶mLatitude2ndStdParallel, - ¶mFalseEastingOrigin, - ¶mFalseNorthingOrigin, - nullptr}; - -static const ParamMapping *const paramsLCC2SP[] = { - ¶mLatitudeFalseOrigin, - ¶mLongitudeFalseOrigin, - ¶mLatitude1stStdParallel, - ¶mLatitude2ndStdParallel, - ¶mFalseEastingOrigin, - ¶mFalseNorthingOrigin, - nullptr, -}; - -static const ParamMapping paramEllipsoidScaleFactor = { - EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR, - EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR, nullptr, - common::UnitOfMeasure::Type::SCALE, k_0}; - -static const ParamMapping *const paramsLCC2SPMichigan[] = { - ¶mLatitudeFalseOrigin, ¶mLongitudeFalseOrigin, - ¶mLatitude1stStdParallel, ¶mLatitude2ndStdParallel, - ¶mFalseEastingOrigin, ¶mFalseNorthingOrigin, - ¶mEllipsoidScaleFactor, nullptr, -}; - -static const ParamMapping paramLatNatLatCenter = { - EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_CENTER, - common::UnitOfMeasure::Type::ANGULAR, lat_0}; - -static const ParamMapping paramLonNatLonCenter = { - EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, WKT1_LONGITUDE_OF_CENTER, - common::UnitOfMeasure::Type::ANGULAR, lon_0}; - -static const ParamMapping *const paramsAEQD[]{ - ¶mLatNatLatCenter, ¶mLonNatLonCenter, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping *const paramsNatOrigin[] = { - ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramLatNatOriginLat1 = { - EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_STANDARD_PARALLEL_1, - common::UnitOfMeasure::Type::ANGULAR, lat_1}; - -static const ParamMapping *const paramsBonne[] = { - ¶mLatNatOriginLat1, ¶mLongitudeNatOrigin, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramLat1stParallelLatTs = { - EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, WKT1_STANDARD_PARALLEL_1, - common::UnitOfMeasure::Type::ANGULAR, lat_ts}; - -static const ParamMapping *const paramsCEA[] = { - ¶mLat1stParallelLatTs, ¶mLongitudeNatOrigin, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping *const paramsEQDC[] = {¶mLatNatLatCenter, - ¶mLonNatLonCenter, - ¶mLatitude1stStdParallel, - ¶mLatitude2ndStdParallel, - ¶mFalseEasting, - ¶mFalseNorthing, - nullptr}; - -static const ParamMapping *const paramsLonNatOrigin[] = { - ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; - -static const ParamMapping *const paramsEqc[] = { - ¶mLat1stParallelLatTs, - ¶mLatitudeNatOrigin, // extension of EPSG, but used by GDAL / PROJ - ¶mLongitudeNatOrigin, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramSatelliteHeight = { - "Satellite Height", 0, "satellite_height", - common::UnitOfMeasure::Type::LINEAR, h}; - -static const ParamMapping *const paramsGeos[] = { - ¶mLongitudeNatOrigin, ¶mSatelliteHeight, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramLatCentreLatCenter = { - EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, - EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, WKT1_LATITUDE_OF_CENTER, - common::UnitOfMeasure::Type::ANGULAR, lat_0}; - -static const ParamMapping paramLonCentreLonCenterLonc = { - EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, - EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, WKT1_LONGITUDE_OF_CENTER, - common::UnitOfMeasure::Type::ANGULAR, lonc}; - -static const ParamMapping paramAzimuth = { - EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, - EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, WKT1_AZIMUTH, - common::UnitOfMeasure::Type::ANGULAR, alpha}; - -static const ParamMapping paramAngleToSkewGrid = { - EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, - EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, WKT1_RECTIFIED_GRID_ANGLE, - common::UnitOfMeasure::Type::ANGULAR, gamma}; -static const ParamMapping paramScaleFactorInitialLine = { - EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, - EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, WKT1_SCALE_FACTOR, - common::UnitOfMeasure::Type::SCALE, k}; - -static const ParamMapping *const paramsHomVariantA[] = { - ¶mLatCentreLatCenter, - ¶mLonCentreLonCenterLonc, - ¶mAzimuth, - ¶mAngleToSkewGrid, - ¶mScaleFactorInitialLine, - ¶mFalseEasting, - ¶mFalseNorthing, - nullptr}; - -static const ParamMapping paramFalseEastingProjectionCentre = { - EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, - EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, WKT1_FALSE_EASTING, - common::UnitOfMeasure::Type::LINEAR, x_0}; - -static const ParamMapping paramFalseNorthingProjectionCentre = { - EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, - EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, WKT1_FALSE_NORTHING, - common::UnitOfMeasure::Type::LINEAR, y_0}; - -static const ParamMapping *const paramsHomVariantB[] = { - ¶mLatCentreLatCenter, - ¶mLonCentreLonCenterLonc, - ¶mAzimuth, - ¶mAngleToSkewGrid, - ¶mScaleFactorInitialLine, - ¶mFalseEastingProjectionCentre, - ¶mFalseNorthingProjectionCentre, - nullptr}; - -static const ParamMapping paramLatPoint1 = { - "Latitude of 1st point", 0, "latitude_of_point_1", - common::UnitOfMeasure::Type::ANGULAR, lat_1}; - -static const ParamMapping paramLonPoint1 = { - "Longitude of 1st point", 0, "longitude_of_point_1", - common::UnitOfMeasure::Type::ANGULAR, lon_1}; - -static const ParamMapping paramLatPoint2 = { - "Latitude of 2nd point", 0, "latitude_of_point_2", - common::UnitOfMeasure::Type::ANGULAR, lat_2}; - -static const ParamMapping paramLonPoint2 = { - "Longitude of 2nd point", 0, "longitude_of_point_2", - common::UnitOfMeasure::Type::ANGULAR, lon_2}; - -static const ParamMapping *const paramsHomTwoPoint[] = { - ¶mLatCentreLatCenter, - ¶mLatPoint1, - ¶mLonPoint1, - ¶mLatPoint2, - ¶mLonPoint2, - ¶mScaleFactorInitialLine, - ¶mFalseEastingProjectionCentre, - ¶mFalseNorthingProjectionCentre, - nullptr}; - -static const ParamMapping *const paramsIMWP[] = { - ¶mLongitudeNatOrigin, ¶mLatFirstPoint, ¶mLatSecondPoint, - ¶mFalseEasting, ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramLonCentreLonCenter = { - EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, WKT1_LONGITUDE_OF_CENTER, - common::UnitOfMeasure::Type::ANGULAR, lon_0}; - -static const ParamMapping paramColatitudeConeAxis = { - EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, - EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, WKT1_AZIMUTH, - common::UnitOfMeasure::Type::ANGULAR, - "alpha"}; /* ignored by PROJ currently */ - -static const ParamMapping paramLatitudePseudoStdParallel = { - EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, - "pseudo_standard_parallel_1", common::UnitOfMeasure::Type::ANGULAR, - nullptr}; /* ignored by PROJ currently */ - -static const ParamMapping paramScaleFactorPseudoStdParallel = { - EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, - EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, - WKT1_SCALE_FACTOR, common::UnitOfMeasure::Type::SCALE, - k}; /* ignored by PROJ currently */ - -static const ParamMapping *const krovakParameters[] = { - ¶mLatCentreLatCenter, - ¶mLonCentreLonCenter, - ¶mColatitudeConeAxis, - ¶mLatitudePseudoStdParallel, - ¶mScaleFactorPseudoStdParallel, - ¶mFalseEasting, - ¶mFalseNorthing, - nullptr}; - -static const ParamMapping *const paramsLaea[] = { - ¶mLatNatLatCenter, ¶mLonNatLonCenter, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping *const paramsMiller[] = { - ¶mLonNatLonCenter, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramLatMerc1SP = { - EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - nullptr, // always set to zero, not to be exported in WKT1 - common::UnitOfMeasure::Type::ANGULAR, - nullptr}; // always set to zero, not to be exported in PROJ strings - -static const ParamMapping *const paramsMerc1SP[] = { - ¶mLatMerc1SP, ¶mLongitudeNatOrigin, ¶mScaleFactorK, - ¶mFalseEasting, ¶mFalseNorthing, nullptr}; - -static const ParamMapping *const paramsMerc2SP[] = { - ¶mLat1stParallelLatTs, ¶mLongitudeNatOrigin, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping *const paramsObliqueStereo[] = { - ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactorK, - ¶mFalseEasting, ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramLatStdParallel = { - EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, WKT1_LATITUDE_OF_ORIGIN, - common::UnitOfMeasure::Type::ANGULAR, lat_ts}; - -static const ParamMapping paramsLonOrigin = { - EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, WKT1_CENTRAL_MERIDIAN, - common::UnitOfMeasure::Type::ANGULAR, lon_0}; - -static const ParamMapping *const paramsPolarStereo[] = { - ¶mLatStdParallel, ¶msLonOrigin, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping *const paramsLonNatOriginLongitudeCentre[] = { - ¶mLonNatLonCenter, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramLatTrueScaleWag3 = { - "Latitude of true scale", 0, WKT1_LATITUDE_OF_ORIGIN, - common::UnitOfMeasure::Type::ANGULAR, lat_ts}; - -static const ParamMapping *const paramsWag3[] = { - ¶mLatTrueScaleWag3, ¶mLongitudeNatOrigin, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramPegLat = { - "Peg point latitude", 0, "peg_point_latitude", - common::UnitOfMeasure::Type::ANGULAR, "plat_0"}; - -static const ParamMapping paramPegLon = { - "Peg point longitude", 0, "peg_point_longitude", - common::UnitOfMeasure::Type::ANGULAR, "plon_0"}; - -static const ParamMapping paramPegHeading = { - "Peg point heading", 0, "peg_point_heading", - common::UnitOfMeasure::Type::ANGULAR, "phdg_0"}; - -static const ParamMapping paramPegHeight = { - "Peg point height", 0, "peg_point_height", - common::UnitOfMeasure::Type::LINEAR, "h_0"}; - -static const ParamMapping *const paramsSch[] = { - ¶mPegLat, ¶mPegLon, ¶mPegHeading, ¶mPegHeight, nullptr}; - -static const ParamMapping *const paramsWink1[] = { - ¶mLongitudeNatOrigin, ¶mLat1stParallelLatTs, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping *const paramsWink2[] = { - ¶mLongitudeNatOrigin, ¶mLatitude1stStdParallel, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramLatLoxim = { - EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_ORIGIN, - common::UnitOfMeasure::Type::ANGULAR, lat_1}; - -static const ParamMapping *const paramsLoxim[] = { - ¶mLatLoxim, ¶mLongitudeNatOrigin, ¶mFalseEasting, - ¶mFalseNorthing, nullptr}; - -static const ParamMapping paramLonCentre = { - EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, - EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, WKT1_LONGITUDE_OF_CENTER, - common::UnitOfMeasure::Type::ANGULAR, lon_0}; - -static const ParamMapping paramLabordeObliqueMercatorAzimuth = { - EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, - EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, WKT1_AZIMUTH, - common::UnitOfMeasure::Type::ANGULAR, "azi"}; - -static const ParamMapping *const paramsLabordeObliqueMercator[] = { - ¶mLatCentreLatCenter, - ¶mLonCentre, - ¶mLabordeObliqueMercatorAzimuth, - ¶mScaleFactorInitialLine, - ¶mFalseEasting, - ¶mFalseNorthing, - nullptr}; - -static const ParamMapping paramLatTopoOrigin = { - EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, nullptr, - common::UnitOfMeasure::Type::ANGULAR, lat_0}; - -static const ParamMapping paramLonTopoOrigin = { - EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, nullptr, - common::UnitOfMeasure::Type::ANGULAR, lon_0}; - -static const ParamMapping paramHeightTopoOrigin = { - EPSG_NAME_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, - EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, nullptr, - common::UnitOfMeasure::Type::LINEAR, - nullptr}; // unsupported by PROJ right now - -static const ParamMapping paramViewpointHeight = { - EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT, EPSG_CODE_PARAMETER_VIEWPOINT_HEIGHT, - nullptr, common::UnitOfMeasure::Type::LINEAR, "h"}; - -static const ParamMapping *const paramsVerticalPerspective[] = { - ¶mLatTopoOrigin, - ¶mLonTopoOrigin, - ¶mHeightTopoOrigin, // unsupported by PROJ right now - ¶mViewpointHeight, - ¶mFalseEasting, // PROJ addition - ¶mFalseNorthing, // PROJ addition - nullptr}; - -static const ParamMapping paramProjectionPlaneOriginHeight = { - EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, - EPSG_CODE_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, nullptr, - common::UnitOfMeasure::Type::LINEAR, "h_0"}; - -static const ParamMapping *const paramsColombiaUrban[] = { - ¶mLatitudeNatOrigin, - ¶mLongitudeNatOrigin, - ¶mFalseEasting, - ¶mFalseNorthing, - ¶mProjectionPlaneOriginHeight, - nullptr}; - -static const MethodMapping projectionMethodMappings[] = { - {EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, - "Transverse_Mercator", "tmerc", nullptr, paramsNatOriginScaleK}, - - {EPSG_NAME_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, - EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, - "Transverse_Mercator_South_Orientated", "tmerc", "axis=wsu", - paramsNatOriginScaleK}, - - {PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, 0, "Two_Point_Equidistant", - "tpeqd", nullptr, paramsTPEQD}, - - {EPSG_NAME_METHOD_TUNISIA_MAPPING_GRID, - EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID, "Tunisia_Mapping_Grid", nullptr, - nullptr, // no proj equivalent - paramsTMG}, - - {EPSG_NAME_METHOD_ALBERS_EQUAL_AREA, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, - "Albers_Conic_Equal_Area", "aea", nullptr, paramsAEA}, - - {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, - "Lambert_Conformal_Conic_1SP", "lcc", nullptr, - []() { - static const ParamMapping paramLatLCC1SP = { - EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - WKT1_LATITUDE_OF_ORIGIN, common::UnitOfMeasure::Type::ANGULAR, - lat_1}; - - static const ParamMapping *const x[] = { - ¶mLatLCC1SP, ¶mLongitudeNatOrigin, ¶mScaleFactor, - ¶mFalseEasting, ¶mFalseNorthing, nullptr, - }; - return x; - }()}, - - {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, - "Lambert_Conformal_Conic_2SP", "lcc", nullptr, paramsLCC2SP}, - - // Oracle WKT - {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, "Lambert Conformal Conic", - "lcc", nullptr, paramsLCC2SP}, - - {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, - nullptr, // no mapping to WKT1_GDAL - "lcc", nullptr, paramsLCC2SPMichigan}, - - {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, - "Lambert_Conformal_Conic_2SP_Belgium", "lcc", - nullptr, // FIXME: this is what is done in GDAL, but the formula of - // LCC 2SP - // Belgium in the EPSG 7.2 guidance is difference from the regular - // LCC 2SP - paramsLCC2SP}, - - {EPSG_NAME_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, - EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, "Azimuthal_Equidistant", - "aeqd", nullptr, paramsAEQD}, - - {EPSG_NAME_METHOD_GUAM_PROJECTION, EPSG_CODE_METHOD_GUAM_PROJECTION, - nullptr, // no mapping to GDAL WKT1 - "aeqd", "guam", paramsNatOrigin}, - - {EPSG_NAME_METHOD_BONNE, EPSG_CODE_METHOD_BONNE, "Bonne", "bonne", nullptr, - paramsBonne}, - - {PROJ_WKT2_NAME_METHOD_COMPACT_MILLER, 0, "Compact_Miller", "comill", - nullptr, paramsLonNatOrigin}, - - {EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, - EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, - "Cylindrical_Equal_Area", "cea", nullptr, paramsCEA}, - - {EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, - EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, "Cylindrical_Equal_Area", - "cea", nullptr, paramsCEA}, - - {EPSG_NAME_METHOD_CASSINI_SOLDNER, EPSG_CODE_METHOD_CASSINI_SOLDNER, - "Cassini_Soldner", "cass", nullptr, paramsNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, 0, "Equidistant_Conic", "eqdc", - nullptr, paramsEQDC}, - - {PROJ_WKT2_NAME_METHOD_ECKERT_I, 0, "Eckert_I", "eck1", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_ECKERT_II, 0, "Eckert_II", "eck2", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_ECKERT_III, 0, "Eckert_III", "eck3", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_ECKERT_IV, 0, "Eckert_IV", "eck4", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_ECKERT_V, 0, "Eckert_V", "eck5", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_ECKERT_VI, 0, "Eckert_VI", "eck6", nullptr, - paramsLonNatOrigin}, - - {EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, "Equirectangular", "eqc", - nullptr, paramsEqc}, - - {EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, "Equirectangular", - "eqc", nullptr, paramsEqc}, - - {PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC, 0, "Flat_Polar_Quartic", - "mbtfpq", nullptr, paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, 0, "Gall_Stereographic", "gall", - nullptr, paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE, 0, "Goode_Homolosine", "goode", - nullptr, paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, 0, - "Interrupted_Goode_Homolosine", "igh", nullptr, paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN, 0, nullptr, - "igh_o", nullptr, paramsLonNatOrigin}, - - // No proper WKT1 representation fr sweep=x - {PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X, 0, nullptr, "geos", - "sweep=x", paramsGeos}, - - {PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, 0, - "Geostationary_Satellite", "geos", nullptr, paramsGeos}, - - {PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR, 0, - "Gauss_Schreiber_Transverse_Mercator", "gstmerc", nullptr, - paramsNatOriginScale}, - - {PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, "Gnomonic", "gnom", nullptr, - paramsNatOrigin}, - - {EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, - "Hotine_Oblique_Mercator", "omerc", "no_uoff", paramsHomVariantA}, - - {EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, - "Hotine_Oblique_Mercator_Azimuth_Center", "omerc", nullptr, - paramsHomVariantB}, - - {PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, - "Hotine_Oblique_Mercator_Two_Point_Natural_Origin", "omerc", nullptr, - paramsHomTwoPoint}, - - {PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC, 0, - "International_Map_of_the_World_Polyconic", "imw_p", nullptr, paramsIMWP}, - - {EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED, - EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, "Krovak", "krovak", nullptr, - krovakParameters}, - - {EPSG_NAME_METHOD_KROVAK, EPSG_CODE_METHOD_KROVAK, "Krovak", "krovak", - "axis=swu", krovakParameters}, - - {EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, - EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, - "Lambert_Azimuthal_Equal_Area", "laea", nullptr, paramsLaea}, - - {EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL, - EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL, - "Lambert_Azimuthal_Equal_Area", "laea", nullptr, paramsLaea}, - - {PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, 0, "Miller_Cylindrical", "mill", - "R_A", paramsMiller}, - - {EPSG_NAME_METHOD_MERCATOR_VARIANT_A, EPSG_CODE_METHOD_MERCATOR_VARIANT_A, - "Mercator_1SP", "merc", nullptr, paramsMerc1SP}, - - {EPSG_NAME_METHOD_MERCATOR_VARIANT_B, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, - "Mercator_2SP", "merc", nullptr, paramsMerc2SP}, - - {EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, - EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, - "Popular_Visualisation_Pseudo_Mercator", // particular case actually - // handled manually - "webmerc", nullptr, paramsNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_MOLLWEIDE, 0, "Mollweide", "moll", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_NATURAL_EARTH, 0, "Natural_Earth", "natearth", - nullptr, paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II, 0, "Natural_Earth_II", "natearth2", - nullptr, paramsLonNatOrigin}, - - {EPSG_NAME_METHOD_NZMG, EPSG_CODE_METHOD_NZMG, "New_Zealand_Map_Grid", - "nzmg", nullptr, paramsNatOrigin}, - - { - EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC, - EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, "Oblique_Stereographic", - "sterea", nullptr, paramsObliqueStereo, - }, - - {EPSG_NAME_METHOD_ORTHOGRAPHIC, EPSG_CODE_METHOD_ORTHOGRAPHIC, - "Orthographic", "ortho", nullptr, paramsNatOrigin}, - - {PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL, 0, "Orthographic", "ortho", "f=0", - paramsNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_PATTERSON, 0, "Patterson", "patterson", nullptr, - paramsLonNatOrigin}, - - {EPSG_NAME_METHOD_AMERICAN_POLYCONIC, EPSG_CODE_METHOD_AMERICAN_POLYCONIC, - "Polyconic", "poly", nullptr, paramsNatOrigin}, - - {EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, - EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, "Polar_Stereographic", - "stere", nullptr, paramsObliqueStereo}, - - {EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, - EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, "Polar_Stereographic", - "stere", nullptr, paramsPolarStereo}, - - {PROJ_WKT2_NAME_METHOD_ROBINSON, 0, "Robinson", "robin", nullptr, - paramsLonNatOriginLongitudeCentre}, - - {PROJ_WKT2_NAME_METHOD_SINUSOIDAL, 0, "Sinusoidal", "sinu", nullptr, - paramsLonNatOriginLongitudeCentre}, - - {PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, 0, "Stereographic", "stere", nullptr, - paramsObliqueStereo}, - - {PROJ_WKT2_NAME_METHOD_TIMES, 0, "Times", "times", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, 0, "VanDerGrinten", "vandg", "R_A", - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_WAGNER_I, 0, "Wagner_I", "wag1", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_WAGNER_II, 0, "Wagner_II", "wag2", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_WAGNER_III, 0, "Wagner_III", "wag3", nullptr, - paramsWag3}, - - {PROJ_WKT2_NAME_METHOD_WAGNER_IV, 0, "Wagner_IV", "wag4", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_WAGNER_V, 0, "Wagner_V", "wag5", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_WAGNER_VI, 0, "Wagner_VI", "wag6", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_WAGNER_VII, 0, "Wagner_VII", "wag7", nullptr, - paramsLonNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE, 0, - "Quadrilateralized_Spherical_Cube", "qsc", nullptr, paramsNatOrigin}, - - {PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT, 0, - "Spherical_Cross_Track_Height", "sch", nullptr, paramsSch}, - - // The following methods have just the WKT <--> PROJ string mapping, but - // no setter. Similarly to GDAL - - {"Aitoff", 0, "Aitoff", "aitoff", nullptr, paramsLonNatOrigin}, - - {"Winkel I", 0, "Winkel_I", "wink1", nullptr, paramsWink1}, - - {"Winkel II", 0, "Winkel_II", "wink2", nullptr, paramsWink2}, - - {"Winkel Tripel", 0, "Winkel_Tripel", "wintri", nullptr, paramsWink2}, - - {"Craster Parabolic", 0, "Craster_Parabolic", "crast", nullptr, - paramsLonNatOrigin}, - - {"Loximuthal", 0, "Loximuthal", "loxim", nullptr, paramsLoxim}, - - {"Quartic Authalic", 0, "Quartic_Authalic", "qua_aut", nullptr, - paramsLonNatOrigin}, - - {"Transverse Cylindrical Equal Area", 0, - "Transverse_Cylindrical_Equal_Area", "tcea", nullptr, paramsObliqueStereo}, - - {EPSG_NAME_METHOD_EQUAL_EARTH, EPSG_CODE_METHOD_EQUAL_EARTH, nullptr, - "eqearth", nullptr, paramsLonNatOrigin}, - - {EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR, - EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, "Laborde_Oblique_Mercator", - "labrd", nullptr, paramsLabordeObliqueMercator}, - - {EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE, - EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, nullptr, "nsper", nullptr, - paramsVerticalPerspective}, - - {EPSG_NAME_METHOD_COLOMBIA_URBAN, EPSG_CODE_METHOD_COLOMBIA_URBAN, nullptr, - "col_urban", nullptr, paramsColombiaUrban}, -}; - -#define METHOD_NAME_CODE(method) \ - { EPSG_NAME_METHOD_##method, EPSG_CODE_METHOD_##method } - -static const struct MethodNameCode { - const char *name; - int epsg_code; -} methodNameCodes[] = { - // Projection methods - METHOD_NAME_CODE(TRANSVERSE_MERCATOR), - METHOD_NAME_CODE(TRANSVERSE_MERCATOR_SOUTH_ORIENTATED), - METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_1SP), METHOD_NAME_CODE(NZMG), - METHOD_NAME_CODE(TUNISIA_MAPPING_GRID), METHOD_NAME_CODE(ALBERS_EQUAL_AREA), - METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP), - METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM), - METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN), - METHOD_NAME_CODE(MODIFIED_AZIMUTHAL_EQUIDISTANT), - METHOD_NAME_CODE(GUAM_PROJECTION), METHOD_NAME_CODE(BONNE), - METHOD_NAME_CODE(LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL), - METHOD_NAME_CODE(LAMBERT_CYLINDRICAL_EQUAL_AREA), - METHOD_NAME_CODE(CASSINI_SOLDNER), - METHOD_NAME_CODE(EQUIDISTANT_CYLINDRICAL), - METHOD_NAME_CODE(EQUIDISTANT_CYLINDRICAL_SPHERICAL), - METHOD_NAME_CODE(HOTINE_OBLIQUE_MERCATOR_VARIANT_A), - METHOD_NAME_CODE(HOTINE_OBLIQUE_MERCATOR_VARIANT_B), - METHOD_NAME_CODE(KROVAK_NORTH_ORIENTED), METHOD_NAME_CODE(KROVAK), - METHOD_NAME_CODE(LAMBERT_AZIMUTHAL_EQUAL_AREA), - METHOD_NAME_CODE(POPULAR_VISUALISATION_PSEUDO_MERCATOR), - METHOD_NAME_CODE(MERCATOR_VARIANT_A), METHOD_NAME_CODE(MERCATOR_VARIANT_B), - METHOD_NAME_CODE(OBLIQUE_STEREOGRAPHIC), - METHOD_NAME_CODE(AMERICAN_POLYCONIC), - METHOD_NAME_CODE(POLAR_STEREOGRAPHIC_VARIANT_A), - METHOD_NAME_CODE(POLAR_STEREOGRAPHIC_VARIANT_B), - METHOD_NAME_CODE(EQUAL_EARTH), METHOD_NAME_CODE(LABORDE_OBLIQUE_MERCATOR), - METHOD_NAME_CODE(VERTICAL_PERSPECTIVE), METHOD_NAME_CODE(COLOMBIA_URBAN), - // Other conversions - METHOD_NAME_CODE(CHANGE_VERTICAL_UNIT), - METHOD_NAME_CODE(HEIGHT_DEPTH_REVERSAL), - METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_2D), - METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_3D), - METHOD_NAME_CODE(GEOGRAPHIC_GEOCENTRIC), - // Transformations - METHOD_NAME_CODE(LONGITUDE_ROTATION), - METHOD_NAME_CODE(AFFINE_PARAMETRIC_TRANSFORMATION), - METHOD_NAME_CODE(COORDINATE_FRAME_GEOCENTRIC), - METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_2D), - METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_3D), - METHOD_NAME_CODE(POSITION_VECTOR_GEOCENTRIC), - METHOD_NAME_CODE(POSITION_VECTOR_GEOGRAPHIC_2D), - METHOD_NAME_CODE(POSITION_VECTOR_GEOGRAPHIC_3D), - METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOCENTRIC), - METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D), - METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D), - METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC), - METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D), - METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D), - METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC), - METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D), - METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D), - METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOCENTRIC), - METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D), - METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D), - METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOCENTRIC), - METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D), - METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D), - METHOD_NAME_CODE(MOLODENSKY), METHOD_NAME_CODE(ABRIDGED_MOLODENSKY), - METHOD_NAME_CODE(GEOGRAPHIC2D_OFFSETS), - METHOD_NAME_CODE(GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS), - METHOD_NAME_CODE(GEOGRAPHIC3D_OFFSETS), METHOD_NAME_CODE(VERTICAL_OFFSET), - METHOD_NAME_CODE(NTV2), METHOD_NAME_CODE(NTV1), METHOD_NAME_CODE(NADCON), - METHOD_NAME_CODE(VERTCON), - METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN), -}; - -#define PARAM_NAME_CODE(method) \ - { EPSG_NAME_PARAMETER_##method, EPSG_CODE_PARAMETER_##method } - -static const struct ParamNameCode { - const char *name; - int epsg_code; -} paramNameCodes[] = { - // Parameters of projection methods - PARAM_NAME_CODE(COLATITUDE_CONE_AXIS), - PARAM_NAME_CODE(LATITUDE_OF_NATURAL_ORIGIN), - PARAM_NAME_CODE(LONGITUDE_OF_NATURAL_ORIGIN), - PARAM_NAME_CODE(SCALE_FACTOR_AT_NATURAL_ORIGIN), - PARAM_NAME_CODE(FALSE_EASTING), PARAM_NAME_CODE(FALSE_NORTHING), - PARAM_NAME_CODE(LATITUDE_PROJECTION_CENTRE), - PARAM_NAME_CODE(LONGITUDE_PROJECTION_CENTRE), - PARAM_NAME_CODE(AZIMUTH_INITIAL_LINE), - PARAM_NAME_CODE(ANGLE_RECTIFIED_TO_SKEW_GRID), - PARAM_NAME_CODE(SCALE_FACTOR_INITIAL_LINE), - PARAM_NAME_CODE(EASTING_PROJECTION_CENTRE), - PARAM_NAME_CODE(NORTHING_PROJECTION_CENTRE), - PARAM_NAME_CODE(LATITUDE_PSEUDO_STANDARD_PARALLEL), - PARAM_NAME_CODE(SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL), - PARAM_NAME_CODE(LATITUDE_FALSE_ORIGIN), - PARAM_NAME_CODE(LONGITUDE_FALSE_ORIGIN), - PARAM_NAME_CODE(LATITUDE_1ST_STD_PARALLEL), - PARAM_NAME_CODE(LATITUDE_2ND_STD_PARALLEL), - PARAM_NAME_CODE(EASTING_FALSE_ORIGIN), - PARAM_NAME_CODE(NORTHING_FALSE_ORIGIN), - PARAM_NAME_CODE(LATITUDE_STD_PARALLEL), - PARAM_NAME_CODE(LONGITUDE_OF_ORIGIN), - PARAM_NAME_CODE(ELLIPSOID_SCALE_FACTOR), - PARAM_NAME_CODE(PROJECTION_PLANE_ORIGIN_HEIGHT), - // Parameters of transformations - PARAM_NAME_CODE(SEMI_MAJOR_AXIS_DIFFERENCE), - PARAM_NAME_CODE(FLATTENING_DIFFERENCE), - PARAM_NAME_CODE(LATITUDE_LONGITUDE_DIFFERENCE_FILE), - PARAM_NAME_CODE(GEOID_CORRECTION_FILENAME), - PARAM_NAME_CODE(VERTICAL_OFFSET_FILE), - PARAM_NAME_CODE(LATITUDE_DIFFERENCE_FILE), - PARAM_NAME_CODE(LONGITUDE_DIFFERENCE_FILE), - PARAM_NAME_CODE(UNIT_CONVERSION_SCALAR), PARAM_NAME_CODE(LATITUDE_OFFSET), - PARAM_NAME_CODE(LONGITUDE_OFFSET), PARAM_NAME_CODE(VERTICAL_OFFSET), - PARAM_NAME_CODE(GEOID_UNDULATION), PARAM_NAME_CODE(A0), PARAM_NAME_CODE(A1), - PARAM_NAME_CODE(A2), PARAM_NAME_CODE(B0), PARAM_NAME_CODE(B1), - PARAM_NAME_CODE(B2), PARAM_NAME_CODE(X_AXIS_TRANSLATION), - PARAM_NAME_CODE(Y_AXIS_TRANSLATION), PARAM_NAME_CODE(Z_AXIS_TRANSLATION), - PARAM_NAME_CODE(X_AXIS_ROTATION), PARAM_NAME_CODE(Y_AXIS_ROTATION), - PARAM_NAME_CODE(Z_AXIS_ROTATION), PARAM_NAME_CODE(SCALE_DIFFERENCE), - PARAM_NAME_CODE(RATE_X_AXIS_TRANSLATION), - PARAM_NAME_CODE(RATE_Y_AXIS_TRANSLATION), - PARAM_NAME_CODE(RATE_Z_AXIS_TRANSLATION), - PARAM_NAME_CODE(RATE_X_AXIS_ROTATION), - PARAM_NAME_CODE(RATE_Y_AXIS_ROTATION), - PARAM_NAME_CODE(RATE_Z_AXIS_ROTATION), - PARAM_NAME_CODE(RATE_SCALE_DIFFERENCE), PARAM_NAME_CODE(REFERENCE_EPOCH), - PARAM_NAME_CODE(TRANSFORMATION_REFERENCE_EPOCH), - PARAM_NAME_CODE(ORDINATE_1_EVAL_POINT), - PARAM_NAME_CODE(ORDINATE_2_EVAL_POINT), - PARAM_NAME_CODE(ORDINATE_3_EVAL_POINT), - PARAM_NAME_CODE(GEOCENTRIC_TRANSLATION_FILE), -}; - -static const ParamMapping paramUnitConversionScalar = { - EPSG_NAME_PARAMETER_UNIT_CONVERSION_SCALAR, - EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR, nullptr, - common::UnitOfMeasure::Type::SCALE, nullptr}; - -static const ParamMapping *const paramsChangeVerticalUnit[] = { - ¶mUnitConversionScalar, nullptr}; - -static const ParamMapping paramLongitudeOffset = { - EPSG_NAME_PARAMETER_LONGITUDE_OFFSET, EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, - nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; - -static const ParamMapping *const paramsLongitudeRotation[] = { - ¶mLongitudeOffset, nullptr}; - -static const ParamMapping paramA0 = { - EPSG_NAME_PARAMETER_A0, EPSG_CODE_PARAMETER_A0, nullptr, - common::UnitOfMeasure::Type::UNKNOWN, nullptr}; - -static const ParamMapping paramA1 = { - EPSG_NAME_PARAMETER_A1, EPSG_CODE_PARAMETER_A1, nullptr, - common::UnitOfMeasure::Type::UNKNOWN, nullptr}; - -static const ParamMapping paramA2 = { - EPSG_NAME_PARAMETER_A2, EPSG_CODE_PARAMETER_A2, nullptr, - common::UnitOfMeasure::Type::UNKNOWN, nullptr}; - -static const ParamMapping paramB0 = { - EPSG_NAME_PARAMETER_B0, EPSG_CODE_PARAMETER_B0, nullptr, - common::UnitOfMeasure::Type::UNKNOWN, nullptr}; - -static const ParamMapping paramB1 = { - EPSG_NAME_PARAMETER_B1, EPSG_CODE_PARAMETER_B1, nullptr, - common::UnitOfMeasure::Type::UNKNOWN, nullptr}; - -static const ParamMapping paramB2 = { - EPSG_NAME_PARAMETER_B2, EPSG_CODE_PARAMETER_B2, nullptr, - common::UnitOfMeasure::Type::UNKNOWN, nullptr}; - -static const ParamMapping *const paramsAffineParametricTransformation[] = { - ¶mA0, ¶mA1, ¶mA2, ¶mB0, ¶mB1, ¶mB2, nullptr}; - -static const ParamMapping paramXTranslation = { - EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION, - EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramYTranslation = { - EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION, - EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramZTranslation = { - EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION, - EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramXRotation = { - EPSG_NAME_PARAMETER_X_AXIS_ROTATION, EPSG_CODE_PARAMETER_X_AXIS_ROTATION, - nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramYRotation = { - EPSG_NAME_PARAMETER_Y_AXIS_ROTATION, EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, - nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramZRotation = { - EPSG_NAME_PARAMETER_Z_AXIS_ROTATION, EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, - nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramScaleDifference = { - EPSG_NAME_PARAMETER_SCALE_DIFFERENCE, EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, - nullptr, common::UnitOfMeasure::Type::SCALE, nullptr}; - -static const ParamMapping *const paramsHelmert3[] = { - ¶mXTranslation, ¶mYTranslation, ¶mZTranslation, nullptr}; - -static const ParamMapping *const paramsHelmert7[] = { - ¶mXTranslation, ¶mYTranslation, - ¶mZTranslation, ¶mXRotation, - ¶mYRotation, ¶mZRotation, - ¶mScaleDifference, nullptr}; - -static const ParamMapping paramRateXTranslation = { - EPSG_NAME_PARAMETER_RATE_X_AXIS_TRANSLATION, - EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramRateYTranslation = { - EPSG_NAME_PARAMETER_RATE_Y_AXIS_TRANSLATION, - EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramRateZTranslation = { - EPSG_NAME_PARAMETER_RATE_Z_AXIS_TRANSLATION, - EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramRateXRotation = { - EPSG_NAME_PARAMETER_RATE_X_AXIS_ROTATION, - EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramRateYRotation = { - EPSG_NAME_PARAMETER_RATE_Y_AXIS_ROTATION, - EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramRateZRotation = { - EPSG_NAME_PARAMETER_RATE_Z_AXIS_ROTATION, - EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramRateScaleDifference = { - EPSG_NAME_PARAMETER_RATE_SCALE_DIFFERENCE, - EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, nullptr, - common::UnitOfMeasure::Type::SCALE, nullptr}; - -static const ParamMapping paramReferenceEpoch = { - EPSG_NAME_PARAMETER_REFERENCE_EPOCH, EPSG_CODE_PARAMETER_REFERENCE_EPOCH, - nullptr, common::UnitOfMeasure::Type::TIME, nullptr}; - -static const ParamMapping *const paramsHelmert15[] = { - ¶mXTranslation, ¶mYTranslation, - ¶mZTranslation, ¶mXRotation, - ¶mYRotation, ¶mZRotation, - ¶mScaleDifference, ¶mRateXTranslation, - ¶mRateYTranslation, ¶mRateZTranslation, - ¶mRateXRotation, ¶mRateYRotation, - ¶mRateZRotation, ¶mRateScaleDifference, - ¶mReferenceEpoch, nullptr}; - -static const ParamMapping paramOrdinate1EvalPoint = { - EPSG_NAME_PARAMETER_ORDINATE_1_EVAL_POINT, - EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramOrdinate2EvalPoint = { - EPSG_NAME_PARAMETER_ORDINATE_2_EVAL_POINT, - EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramOrdinate3EvalPoint = { - EPSG_NAME_PARAMETER_ORDINATE_3_EVAL_POINT, - EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping *const paramsMolodenskyBadekas[] = { - ¶mXTranslation, - ¶mYTranslation, - ¶mZTranslation, - ¶mXRotation, - ¶mYRotation, - ¶mZRotation, - ¶mScaleDifference, - ¶mOrdinate1EvalPoint, - ¶mOrdinate2EvalPoint, - ¶mOrdinate3EvalPoint, - nullptr}; - -static const ParamMapping paramSemiMajorAxisDifference = { - EPSG_NAME_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE, - EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE, nullptr, - common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping paramFlatteningDifference = { - EPSG_NAME_PARAMETER_FLATTENING_DIFFERENCE, - EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE, nullptr, - common::UnitOfMeasure::Type::NONE, nullptr}; - -static const ParamMapping *const paramsMolodensky[] = { - ¶mXTranslation, ¶mYTranslation, - ¶mZTranslation, ¶mSemiMajorAxisDifference, - ¶mFlatteningDifference, nullptr}; - -static const ParamMapping paramLatitudeOffset = { - EPSG_NAME_PARAMETER_LATITUDE_OFFSET, EPSG_CODE_PARAMETER_LATITUDE_OFFSET, - nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; - -static const ParamMapping *const paramsGeographic2DOffsets[] = { - ¶mLatitudeOffset, ¶mLongitudeOffset, nullptr}; - -static const ParamMapping paramGeoidUndulation = { - EPSG_NAME_PARAMETER_GEOID_UNDULATION, EPSG_CODE_PARAMETER_GEOID_UNDULATION, - nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping *const paramsGeographic2DWithHeightOffsets[] = { - ¶mLatitudeOffset, ¶mLongitudeOffset, ¶mGeoidUndulation, - nullptr}; - -static const ParamMapping paramVerticalOffset = { - EPSG_NAME_PARAMETER_VERTICAL_OFFSET, EPSG_CODE_PARAMETER_VERTICAL_OFFSET, - nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; - -static const ParamMapping *const paramsGeographic3DOffsets[] = { - ¶mLatitudeOffset, ¶mLongitudeOffset, ¶mVerticalOffset, nullptr}; - -static const ParamMapping *const paramsVerticalOffsets[] = { - ¶mVerticalOffset, nullptr}; - -static const ParamMapping paramLatitudeLongitudeDifferenceFile = { - EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, - EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, nullptr, - common::UnitOfMeasure::Type::NONE, nullptr}; - -static const ParamMapping *const paramsNTV2[] = { - ¶mLatitudeLongitudeDifferenceFile, nullptr}; - -static const ParamMapping paramGeocentricTranslationFile = { - EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, - EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, nullptr, - common::UnitOfMeasure::Type::NONE, nullptr}; - -static const ParamMapping - *const paramsGeocentricTranslationGridInterpolationIGN[] = { - ¶mGeocentricTranslationFile, nullptr}; - -static const ParamMapping paramLatitudeDifferenceFile = { - EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE, - EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE, nullptr, - common::UnitOfMeasure::Type::NONE, nullptr}; - -static const ParamMapping paramLongitudeDifferenceFile = { - EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE, - EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE, nullptr, - common::UnitOfMeasure::Type::NONE, nullptr}; - -static const ParamMapping *const paramsNADCON[] = { - ¶mLatitudeDifferenceFile, ¶mLongitudeDifferenceFile, nullptr}; - -static const ParamMapping paramVerticalOffsetFile = { - EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, - EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE, nullptr, - common::UnitOfMeasure::Type::NONE, nullptr}; - -static const ParamMapping *const paramsVERTCON[] = {¶mVerticalOffsetFile, - nullptr}; - -static const ParamMapping paramSouthPoleLatGRIB = { - PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION, 0, nullptr, - common::UnitOfMeasure::Type::ANGULAR, nullptr}; - -static const ParamMapping paramSouthPoleLonGRIB = { - PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION, 0, nullptr, - common::UnitOfMeasure::Type::ANGULAR, nullptr}; - -static const ParamMapping paramAxisRotationGRIB = { - PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION, 0, nullptr, - common::UnitOfMeasure::Type::ANGULAR, nullptr}; - -static const ParamMapping *const paramsPoleRotationGRIBConvention[] = { - ¶mSouthPoleLatGRIB, ¶mSouthPoleLonGRIB, ¶mAxisRotationGRIB, - nullptr}; - -static const MethodMapping otherMethodMappings[] = { - {EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT, - EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT, nullptr, nullptr, nullptr, - paramsChangeVerticalUnit}, - {EPSG_NAME_METHOD_HEIGHT_DEPTH_REVERSAL, - EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL, nullptr, nullptr, nullptr, - paramsChangeVerticalUnit}, - {EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_2D, - EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D, nullptr, nullptr, nullptr, - nullptr}, - {EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_3D, - EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D, nullptr, nullptr, nullptr, - nullptr}, - {EPSG_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC, - EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC, nullptr, nullptr, nullptr, - nullptr}, - {EPSG_NAME_METHOD_LONGITUDE_ROTATION, EPSG_CODE_METHOD_LONGITUDE_ROTATION, - nullptr, nullptr, nullptr, paramsLongitudeRotation}, - {EPSG_NAME_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION, - EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION, nullptr, nullptr, - nullptr, paramsAffineParametricTransformation}, - - {PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION, 0, nullptr, nullptr, - nullptr, paramsPoleRotationGRIBConvention}, - - {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, nullptr, nullptr, - nullptr, paramsHelmert3}, - {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D, - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D, nullptr, nullptr, - nullptr, paramsHelmert3}, - {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D, - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D, nullptr, nullptr, - nullptr, paramsHelmert3}, - - {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOCENTRIC, - EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC, nullptr, nullptr, nullptr, - paramsHelmert7}, - {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D, - EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, - paramsHelmert7}, - {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D, - EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, - paramsHelmert7}, - - {EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC, - EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC, nullptr, nullptr, nullptr, - paramsHelmert7}, - {EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D, - EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, - paramsHelmert7}, - {EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D, - EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, - paramsHelmert7}, - - {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC, - EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC, nullptr, - nullptr, nullptr, paramsHelmert15}, - {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D, - EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D, nullptr, - nullptr, nullptr, paramsHelmert15}, - {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D, - EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D, nullptr, - nullptr, nullptr, paramsHelmert15}, - - {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC, - EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC, nullptr, - nullptr, nullptr, paramsHelmert15}, - {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D, - EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D, nullptr, - nullptr, nullptr, paramsHelmert15}, - {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D, - EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D, nullptr, - nullptr, nullptr, paramsHelmert15}, - - {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC, - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC, nullptr, nullptr, - nullptr, paramsMolodenskyBadekas}, - {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D, - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D, nullptr, nullptr, - nullptr, paramsMolodenskyBadekas}, - {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D, - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D, nullptr, nullptr, - nullptr, paramsMolodenskyBadekas}, - - {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC, - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC, nullptr, nullptr, - nullptr, paramsMolodenskyBadekas}, - {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D, - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D, nullptr, nullptr, - nullptr, paramsMolodenskyBadekas}, - {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D, - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D, nullptr, nullptr, - nullptr, paramsMolodenskyBadekas}, - - {EPSG_NAME_METHOD_MOLODENSKY, EPSG_CODE_METHOD_MOLODENSKY, nullptr, nullptr, - nullptr, paramsMolodensky}, - - {EPSG_NAME_METHOD_ABRIDGED_MOLODENSKY, EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY, - nullptr, nullptr, nullptr, paramsMolodensky}, - - {EPSG_NAME_METHOD_GEOGRAPHIC2D_OFFSETS, - EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS, nullptr, nullptr, nullptr, - paramsGeographic2DOffsets}, - - {EPSG_NAME_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS, - EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS, nullptr, nullptr, - nullptr, paramsGeographic2DWithHeightOffsets}, - - {EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSETS, - EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS, nullptr, nullptr, nullptr, - paramsGeographic3DOffsets}, - - {EPSG_NAME_METHOD_VERTICAL_OFFSET, EPSG_CODE_METHOD_VERTICAL_OFFSET, - nullptr, nullptr, nullptr, paramsVerticalOffsets}, - - {EPSG_NAME_METHOD_NTV2, EPSG_CODE_METHOD_NTV2, nullptr, nullptr, nullptr, - paramsNTV2}, - - {EPSG_NAME_METHOD_NTV1, EPSG_CODE_METHOD_NTV1, nullptr, nullptr, nullptr, - paramsNTV2}, - - {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN, - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN, nullptr, - nullptr, nullptr, paramsGeocentricTranslationGridInterpolationIGN}, - - {EPSG_NAME_METHOD_NADCON, EPSG_CODE_METHOD_NADCON, nullptr, nullptr, - nullptr, paramsNADCON}, - - {EPSG_NAME_METHOD_VERTCON, EPSG_CODE_METHOD_VERTCON, nullptr, nullptr, - nullptr, paramsVERTCON}, - {EPSG_NAME_METHOD_VERTCON_OLDNAME, EPSG_CODE_METHOD_VERTCON, nullptr, - nullptr, nullptr, paramsVERTCON}, -}; - -// end of anonymous namespace -} // namespace - -// --------------------------------------------------------------------------- - -//! @endcond - -#endif // COORDINATEOPERATION_CONSTANTS_HH_INCLUDED diff -Nru proj-7.2.0/include/proj/internal/coordinateoperation_internal.hpp proj-7.2.1/include/proj/internal/coordinateoperation_internal.hpp --- proj-7.2.0/include/proj/internal/coordinateoperation_internal.hpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/include/proj/internal/coordinateoperation_internal.hpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,312 +0,0 @@ -/****************************************************************************** - * - * Project: PROJ - * Purpose: ISO19111:2019 implementation - * Author: Even Rouault - * - ****************************************************************************** - * Copyright (c) 2018, Even Rouault - * - * 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 -#error This file should only be included from a PROJ cpp file -#endif - -#ifndef COORDINATEOPERATION_INTERNAL_HH_INCLUDED -#define COORDINATEOPERATION_INTERNAL_HH_INCLUDED - -#include "proj/coordinateoperation.hpp" - -#include - -//! @cond Doxygen_Suppress - -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 -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 -getMappingsFromESRI(const std::string &esri_name); - -// --------------------------------------------------------------------------- - -bool isAxisOrderReversal(int methodEPSGCode); - -// --------------------------------------------------------------------------- - -class InverseCoordinateOperation; -/** Shared pointer of InverseCoordinateOperation */ -using InverseCoordinateOperationPtr = - std::shared_ptr; -/** Non-null shared pointer of InverseCoordinateOperation */ -using InverseCoordinateOperationNNPtr = util::nn; - -/** \brief Inverse operation of a CoordinateOperation. - * - * This is used when there is no straightforward way of building another - * subclass of CoordinateOperation that models the inverse operation. - */ -class InverseCoordinateOperation : virtual public CoordinateOperation { - public: - InverseCoordinateOperation( - const CoordinateOperationNNPtr &forwardOperationIn, - bool wktSupportsInversion); - - ~InverseCoordinateOperation() override; - - void _exportToPROJString(io::PROJStringFormatter *formatter) - const override; // throw(FormattingException) - - bool _isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion = - util::IComparable::Criterion::STRICT, - const io::DatabaseContextPtr &dbContext = nullptr) const override; - - CoordinateOperationNNPtr inverse() const override; - - const CoordinateOperationNNPtr &forwardOperation() const { - return forwardOperation_; - } - - protected: - CoordinateOperationNNPtr forwardOperation_; - bool wktSupportsInversion_; - - void setPropertiesFromForward(); -}; - -// --------------------------------------------------------------------------- - -/** \brief Inverse of a conversion. */ -class InverseConversion : public Conversion, public InverseCoordinateOperation { - public: - explicit InverseConversion(const ConversionNNPtr &forward); - - ~InverseConversion() override; - - void _exportToWKT(io::WKTFormatter *formatter) const override { - Conversion::_exportToWKT(formatter); - } - - void _exportToJSON(io::JSONFormatter *formatter) const override { - Conversion::_exportToJSON(formatter); - } - - void - _exportToPROJString(io::PROJStringFormatter *formatter) const override { - InverseCoordinateOperation::_exportToPROJString(formatter); - } - - bool _isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion = - util::IComparable::Criterion::STRICT, - const io::DatabaseContextPtr &dbContext = nullptr) const override { - return InverseCoordinateOperation::_isEquivalentTo(other, criterion, - dbContext); - } - - CoordinateOperationNNPtr inverse() const override { - return InverseCoordinateOperation::inverse(); - } - - ConversionNNPtr inverseAsConversion() const; - -#ifdef _MSC_VER - // To avoid a warning C4250: 'osgeo::proj::operation::InverseConversion': - // inherits - // 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded' - // via dominance - std::set - gridsNeeded(const io::DatabaseContextPtr &databaseContext, - bool considerKnownGridsAsAvailable) const override { - return SingleOperation::gridsNeeded(databaseContext, - considerKnownGridsAsAvailable); - } -#endif - - static CoordinateOperationNNPtr create(const ConversionNNPtr &forward); - - CoordinateOperationNNPtr _shallowClone() const override; -}; - -// --------------------------------------------------------------------------- - -/** \brief Inverse of a transformation. */ -class InverseTransformation : public Transformation, - public InverseCoordinateOperation { - public: - explicit InverseTransformation(const TransformationNNPtr &forward); - - ~InverseTransformation() override; - - void _exportToWKT(io::WKTFormatter *formatter) const override; - - void - _exportToPROJString(io::PROJStringFormatter *formatter) const override { - return InverseCoordinateOperation::_exportToPROJString(formatter); - } - - void _exportToJSON(io::JSONFormatter *formatter) const override { - Transformation::_exportToJSON(formatter); - } - - bool _isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion = - util::IComparable::Criterion::STRICT, - const io::DatabaseContextPtr &dbContext = nullptr) const override { - return InverseCoordinateOperation::_isEquivalentTo(other, criterion, - dbContext); - } - - CoordinateOperationNNPtr inverse() const override { - return InverseCoordinateOperation::inverse(); - } - - TransformationNNPtr inverseAsTransformation() const; - -#ifdef _MSC_VER - // To avoid a warning C4250: - // 'osgeo::proj::operation::InverseTransformation': inherits - // 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded' - // via dominance - std::set - gridsNeeded(const io::DatabaseContextPtr &databaseContext, - bool considerKnownGridsAsAvailable) const override { - return SingleOperation::gridsNeeded(databaseContext, - considerKnownGridsAsAvailable); - } -#endif - - static TransformationNNPtr create(const TransformationNNPtr &forward); - - CoordinateOperationNNPtr _shallowClone() const override; -}; - -// --------------------------------------------------------------------------- - -class PROJBasedOperation; -/** Shared pointer of PROJBasedOperation */ -using PROJBasedOperationPtr = std::shared_ptr; -/** Non-null shared pointer of PROJBasedOperation */ -using PROJBasedOperationNNPtr = util::nn; - -/** \brief A PROJ-string based coordinate operation. - */ -class PROJBasedOperation : public SingleOperation { - public: - ~PROJBasedOperation() override; - - void _exportToWKT(io::WKTFormatter *formatter) - const override; // throw(io::FormattingException) - - CoordinateOperationNNPtr inverse() const override; - - static PROJBasedOperationNNPtr - create(const util::PropertyMap &properties, const std::string &PROJString, - const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, - const std::vector &accuracies); - - static PROJBasedOperationNNPtr - 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 &accuracies, - bool hasRoughTransformation); - - std::set - gridsNeeded(const io::DatabaseContextPtr &databaseContext, - bool considerKnownGridsAsAvailable) const override; - - protected: - PROJBasedOperation(const PROJBasedOperation &) = default; - explicit PROJBasedOperation(const OperationMethodNNPtr &methodIn); - - void _exportToPROJString(io::PROJStringFormatter *formatter) - const override; // throw(FormattingException) - - void _exportToJSON(io::JSONFormatter *formatter) - const override; // throw(FormattingException) - - CoordinateOperationNNPtr _shallowClone() const override; - - INLINED_MAKE_SHARED - - private: - std::string projString_{}; - io::IPROJStringExportablePtr projStringExportable_{}; - bool inverse_ = false; -}; - -} // namespace operation - -NS_PROJ_END - -//! @endcond - -#endif // COORDINATEOPERATION_INTERNAL_HH_INCLUDED diff -Nru proj-7.2.0/include/proj/internal/esri_projection_mappings.hpp proj-7.2.1/include/proj/internal/esri_projection_mappings.hpp --- proj-7.2.0/include/proj/internal/esri_projection_mappings.hpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/include/proj/internal/esri_projection_mappings.hpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,1108 +0,0 @@ -// This file was generated by scripts/build_esri_projection_mapping.py. DO NOT -// EDIT ! - -/****************************************************************************** - * - * Project: PROJ - * Purpose: Mappings between ESRI projection and parameters names and WKT2 - * Author: Even Rouault - * - ****************************************************************************** - * Copyright (c) 2019, Even Rouault - * - * 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_COORDINATE_OPERATION_CPP -#error This file should only be included from coordinateoperation.cpp -#endif - -#ifndef ESRI_PROJECTION_MAPPINGS_HH_INCLUDED -#define ESRI_PROJECTION_MAPPINGS_HH_INCLUDED - -#include "coordinateoperation_internal.hpp" - -//! @cond Doxygen_Suppress - -// --------------------------------------------------------------------------- - -// anonymous namespace -namespace { - -using namespace ::NS_PROJ; -using namespace ::NS_PROJ::operation; - -static 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, - EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static 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, - EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Miller_Cylindrical[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Mercator[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static 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, - EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static 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, - EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Transverse_Mercator_Complex[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Albers[] = { - {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, - {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Sinusoidal[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Mollweide[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Eckert_I[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Eckert_II[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Eckert_III[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Eckert_IV[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Eckert_V[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Eckert_VI[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Gall_Stereographic[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Behrmann[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", true}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "30.0", true}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Winkel_I[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Winkel_II[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt1[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; -static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt2[] = { - {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, - {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; -static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt3[] = { - {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, - {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, - {"Scale_Factor", nullptr, 0, "1.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; -static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt4[] = { - {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, - {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR, - EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Polyconic[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Quartic_Authalic[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Loximuthal[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Central_Parallel", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Bonne[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping - paramsESRI_Hotine_Oblique_Mercator_Two_Point_Natural_Origin[] = { - {"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}, - {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, - {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, - EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false}, - {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, - {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "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_Stereographic[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Equidistant_Conic[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Cassini[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Scale_Factor", nullptr, 0, "1.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Van_der_Grinten_I[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Robinson[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Two_Point_Equidistant[] = { - {"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}, - {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, - {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, - {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, - {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Azimuthal_Equidistant[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Lambert_Azimuthal_Equal_Area[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Cylindrical_Equal_Area[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping - paramsESRI_Hotine_Oblique_Mercator_Two_Point_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}, - {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, - {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, - EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false}, - {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, - {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping - paramsESRI_Hotine_Oblique_Mercator_Azimuth_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}, - {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}}; - -static const ESRIParamMapping paramsESRI_Double_Stereographic[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Krovak_alt1[] = { - {"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}, - {"Pseudo_Standard_Parallel_1", - EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, - EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, "0.0", false}, - {"Azimuth", EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, - EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, "0.0", false}, - {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, - {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, - EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, - {"X_Scale", nullptr, 0, "1.0", false}, - {"Y_Scale", nullptr, 0, "1.0", false}, - {"XY_Plane_Rotation", nullptr, 0, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; -static const ESRIParamMapping paramsESRI_Krovak_alt2[] = { - {"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}, - {"Pseudo_Standard_Parallel_1", - EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, - EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, "0.0", false}, - {"Azimuth", EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, - EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, "0.0", false}, - {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, - {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, - EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, - {"X_Scale", nullptr, 0, "-1.0", false}, - {"Y_Scale", nullptr, 0, "1.0", false}, - {"XY_Plane_Rotation", nullptr, 0, "90.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_New_Zealand_Map_Grid[] = { - {"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}, - {"Longitude_Of_Origin", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Orthographic[] = { - {"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}, - {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Local[] = { - {"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", nullptr, 0, "1.0", false}, - {"Azimuth", nullptr, 0, "0.0", false}, - {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Winkel_Tripel[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Aitoff[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Flat_Polar_Quartic[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Craster_Parabolic[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Gnomonic[] = { - {"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}, - {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Times[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Vertical_Near_Side_Perspective[] = { - {"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}, - {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, "0.0", false}, - {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, "0.0", false}, - {"Height", EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT, - EPSG_CODE_PARAMETER_VIEWPOINT_HEIGHT, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Stereographic_North_Pole[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Stereographic_South_Pole[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, - 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}, - {"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}, - {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, - EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Option", nullptr, 0, "1.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; -static const ESRIParamMapping paramsESRI_Goode_Homolosine_alt2[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Option", nullptr, 0, "2.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Equidistant_Cylindrical_Ellipsoidal[] = - {{"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Laborde_Oblique_Mercator[] = { - {"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}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Gnomonic_Ellipsoidal[] = { - {"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}, - {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Wagner_IV[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", nullptr, 0, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Wagner_V[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Wagner_VII[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Natural_Earth[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Natural_Earth_II[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Patterson[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Compact_Miller[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Geostationary_Satellite[] = { - {"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}, - {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Height", "Satellite Height", 0, "0.0", false}, - {"Option", nullptr, 0, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Mercator_Auxiliary_Sphere[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Auxiliary_Sphere_Type", nullptr, 0, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Mercator_Variant_A[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Mercator_Variant_C[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, - {"Latitude_Of_Origin", nullptr, 0, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_Transverse_Cylindrical_Equal_Area[] = { - {"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}, - {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIParamMapping paramsESRI_IGAC_Plano_Cartesiano[] = { - {"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}, - {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, - {"Height", EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, - EPSG_CODE_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, "0.0", false}, - {nullptr, nullptr, 0, "0.0", false}}; - -static const ESRIMethodMapping esriMappings[] = { - {"Plate_Carree", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, paramsESRI_Plate_Carree}, - {"Plate_Carree", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, - paramsESRI_Plate_Carree}, - {"Equidistant_Cylindrical", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, - paramsESRI_Equidistant_Cylindrical}, - {"Miller_Cylindrical", PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, 0, - paramsESRI_Miller_Cylindrical}, - {"Mercator", EPSG_NAME_METHOD_MERCATOR_VARIANT_B, - EPSG_CODE_METHOD_MERCATOR_VARIANT_B, paramsESRI_Mercator}, - {"Gauss_Kruger", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, - EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, paramsESRI_Gauss_Kruger}, - {"Transverse_Mercator", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, - EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, paramsESRI_Transverse_Mercator}, - {"Transverse_Mercator_Complex", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, - EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, - paramsESRI_Transverse_Mercator_Complex}, - {"Albers", EPSG_NAME_METHOD_ALBERS_EQUAL_AREA, - EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, paramsESRI_Albers}, - {"Sinusoidal", PROJ_WKT2_NAME_METHOD_SINUSOIDAL, 0, paramsESRI_Sinusoidal}, - {"Mollweide", PROJ_WKT2_NAME_METHOD_MOLLWEIDE, 0, paramsESRI_Mollweide}, - {"Eckert_I", PROJ_WKT2_NAME_METHOD_ECKERT_I, 0, paramsESRI_Eckert_I}, - {"Eckert_II", PROJ_WKT2_NAME_METHOD_ECKERT_II, 0, paramsESRI_Eckert_II}, - {"Eckert_III", PROJ_WKT2_NAME_METHOD_ECKERT_III, 0, paramsESRI_Eckert_III}, - {"Eckert_IV", PROJ_WKT2_NAME_METHOD_ECKERT_IV, 0, paramsESRI_Eckert_IV}, - {"Eckert_V", PROJ_WKT2_NAME_METHOD_ECKERT_V, 0, paramsESRI_Eckert_V}, - {"Eckert_VI", PROJ_WKT2_NAME_METHOD_ECKERT_VI, 0, paramsESRI_Eckert_VI}, - {"Gall_Stereographic", PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, 0, - paramsESRI_Gall_Stereographic}, - {"Behrmann", EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, - EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, paramsESRI_Behrmann}, - {"Winkel_I", "Winkel I", 0, paramsESRI_Winkel_I}, - {"Winkel_II", "Winkel II", 0, paramsESRI_Winkel_II}, - {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, - paramsESRI_Lambert_Conformal_Conic_alt1}, - {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, - paramsESRI_Lambert_Conformal_Conic_alt2}, - {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, - paramsESRI_Lambert_Conformal_Conic_alt3}, - {"Lambert_Conformal_Conic", - EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, - paramsESRI_Lambert_Conformal_Conic_alt4}, - {"Polyconic", EPSG_NAME_METHOD_AMERICAN_POLYCONIC, - EPSG_CODE_METHOD_AMERICAN_POLYCONIC, paramsESRI_Polyconic}, - {"Quartic_Authalic", "Quartic Authalic", 0, paramsESRI_Quartic_Authalic}, - {"Loximuthal", "Loximuthal", 0, paramsESRI_Loximuthal}, - {"Bonne", EPSG_NAME_METHOD_BONNE, EPSG_CODE_METHOD_BONNE, paramsESRI_Bonne}, - {"Hotine_Oblique_Mercator_Two_Point_Natural_Origin", - PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, - paramsESRI_Hotine_Oblique_Mercator_Two_Point_Natural_Origin}, - {"Stereographic", PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, 0, - paramsESRI_Stereographic}, - {"Equidistant_Conic", PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, 0, - paramsESRI_Equidistant_Conic}, - {"Cassini", EPSG_NAME_METHOD_CASSINI_SOLDNER, - EPSG_CODE_METHOD_CASSINI_SOLDNER, paramsESRI_Cassini}, - {"Van_der_Grinten_I", PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, 0, - paramsESRI_Van_der_Grinten_I}, - {"Robinson", PROJ_WKT2_NAME_METHOD_ROBINSON, 0, paramsESRI_Robinson}, - {"Two_Point_Equidistant", PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, 0, - paramsESRI_Two_Point_Equidistant}, - {"Azimuthal_Equidistant", EPSG_NAME_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, - EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, - paramsESRI_Azimuthal_Equidistant}, - {"Lambert_Azimuthal_Equal_Area", - EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, - EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, - paramsESRI_Lambert_Azimuthal_Equal_Area}, - {"Cylindrical_Equal_Area", EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, - EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, - paramsESRI_Cylindrical_Equal_Area}, - {"Hotine_Oblique_Mercator_Two_Point_Center", - PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, - paramsESRI_Hotine_Oblique_Mercator_Two_Point_Center}, - {"Hotine_Oblique_Mercator_Azimuth_Natural_Origin", - EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, - paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin}, - {"Hotine_Oblique_Mercator_Azimuth_Center", - EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, - paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center}, - {"Double_Stereographic", EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC, - EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, paramsESRI_Double_Stereographic}, - {"Krovak", EPSG_NAME_METHOD_KROVAK, EPSG_CODE_METHOD_KROVAK, - paramsESRI_Krovak_alt1}, - {"Krovak", EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED, - EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, paramsESRI_Krovak_alt2}, - {"New_Zealand_Map_Grid", EPSG_NAME_METHOD_NZMG, EPSG_CODE_METHOD_NZMG, - paramsESRI_New_Zealand_Map_Grid}, - {"Orthographic", PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL, 0, - paramsESRI_Orthographic}, - {"Local", EPSG_NAME_METHOD_ORTHOGRAPHIC, EPSG_CODE_METHOD_ORTHOGRAPHIC, - paramsESRI_Local}, - {"Winkel_Tripel", "Winkel Tripel", 0, paramsESRI_Winkel_Tripel}, - {"Aitoff", "Aitoff", 0, paramsESRI_Aitoff}, - {"Flat_Polar_Quartic", PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC, 0, - paramsESRI_Flat_Polar_Quartic}, - {"Craster_Parabolic", "Craster Parabolic", 0, paramsESRI_Craster_Parabolic}, - {"Gnomonic", PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, paramsESRI_Gnomonic}, - {"Times", PROJ_WKT2_NAME_METHOD_TIMES, 0, paramsESRI_Times}, - {"Vertical_Near_Side_Perspective", EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE, - EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, - paramsESRI_Vertical_Near_Side_Perspective}, - {"Stereographic_North_Pole", EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, - EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, - paramsESRI_Stereographic_North_Pole}, - {"Stereographic_South_Pole", EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, - EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, - paramsESRI_Stereographic_South_Pole}, - {"Rectified_Skew_Orthomorphic_Natural_Origin", - EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, - paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin}, - {"Rectified_Skew_Orthomorphic_Center", - EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, - paramsESRI_Rectified_Skew_Orthomorphic_Center}, - {"Goode_Homolosine", PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, 0, - paramsESRI_Goode_Homolosine_alt1}, - {"Goode_Homolosine", - PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN, 0, - paramsESRI_Goode_Homolosine_alt2}, - {"Equidistant_Cylindrical_Ellipsoidal", - EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, - paramsESRI_Equidistant_Cylindrical_Ellipsoidal}, - {"Laborde_Oblique_Mercator", EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR, - EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, - paramsESRI_Laborde_Oblique_Mercator}, - {"Gnomonic_Ellipsoidal", PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, - paramsESRI_Gnomonic_Ellipsoidal}, - {"Wagner_IV", PROJ_WKT2_NAME_METHOD_WAGNER_IV, 0, paramsESRI_Wagner_IV}, - {"Wagner_V", PROJ_WKT2_NAME_METHOD_WAGNER_V, 0, paramsESRI_Wagner_V}, - {"Wagner_VII", PROJ_WKT2_NAME_METHOD_WAGNER_VII, 0, paramsESRI_Wagner_VII}, - {"Natural_Earth", PROJ_WKT2_NAME_METHOD_NATURAL_EARTH, 0, - paramsESRI_Natural_Earth}, - {"Natural_Earth_II", PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II, 0, - paramsESRI_Natural_Earth_II}, - {"Patterson", PROJ_WKT2_NAME_METHOD_PATTERSON, 0, paramsESRI_Patterson}, - {"Compact_Miller", PROJ_WKT2_NAME_METHOD_COMPACT_MILLER, 0, - paramsESRI_Compact_Miller}, - {"Geostationary_Satellite", - PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, 0, - paramsESRI_Geostationary_Satellite}, - {"Mercator_Auxiliary_Sphere", - EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, - EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, - paramsESRI_Mercator_Auxiliary_Sphere}, - {"Mercator_Variant_A", EPSG_NAME_METHOD_MERCATOR_VARIANT_A, - EPSG_CODE_METHOD_MERCATOR_VARIANT_A, paramsESRI_Mercator_Variant_A}, - {"Mercator_Variant_C", EPSG_NAME_METHOD_MERCATOR_VARIANT_B, - EPSG_CODE_METHOD_MERCATOR_VARIANT_B, paramsESRI_Mercator_Variant_C}, - {"Transverse_Cylindrical_Equal_Area", "Transverse Cylindrical Equal Area", - 0, paramsESRI_Transverse_Cylindrical_Equal_Area}, - {"IGAC_Plano_Cartesiano", EPSG_NAME_METHOD_COLOMBIA_URBAN, - EPSG_CODE_METHOD_COLOMBIA_URBAN, paramsESRI_IGAC_Plano_Cartesiano}, -}; - -// --------------------------------------------------------------------------- - -} // namespace { - -//! @endcond - -#endif // ESRI_PROJECTION_MAPPINGS_HH_INCLUDED diff -Nru proj-7.2.0/include/proj/internal/Makefile.am proj-7.2.1/include/proj/internal/Makefile.am --- proj-7.2.0/include/proj/internal/Makefile.am 2020-01-23 08:45:49.000000000 +0000 +++ proj-7.2.1/include/proj/internal/Makefile.am 2020-12-21 16:29:10.000000000 +0000 @@ -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 -Nru proj-7.2.0/include/proj/internal/Makefile.in proj-7.2.1/include/proj/internal/Makefile.in --- proj-7.2.0/include/proj/internal/Makefile.in 2020-10-28 12:56:46.000000000 +0000 +++ proj-7.2.1/include/proj/internal/Makefile.in 2020-12-26 18:57:37.000000000 +0000 @@ -331,9 +331,6 @@ top_srcdir = @top_srcdir@ SUBDIRS = nlohmann noinst_HEADERS = \ - coordinateoperation_constants.hpp \ - coordinateoperation_internal.hpp \ - esri_projection_mappings.hpp \ coordinatesystem_internal.hpp \ internal.hpp \ io_internal.hpp \ diff -Nru proj-7.2.0/include/proj/io.hpp proj-7.2.1/include/proj/io.hpp --- proj-7.2.0/include/proj/io.hpp 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/include/proj/io.hpp 2020-12-26 18:57:21.000000000 +0000 @@ -342,6 +342,10 @@ PROJ_INTERNAL void ingestWKTNode(const WKTNodeNNPtr &node); + PROJ_INTERNAL WKTFormatter & + setAllowEllipsoidalHeightAsVerticalCRS(bool allow) noexcept; + PROJ_INTERNAL bool isAllowedEllipsoidalHeightAsVerticalCRS() const noexcept; + //! @endcond protected: diff -Nru proj-7.2.0/include/proj/util.hpp proj-7.2.1/include/proj/util.hpp --- proj-7.2.0/include/proj/util.hpp 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/include/proj/util.hpp 2020-12-21 16:29:10.000000000 +0000 @@ -213,6 +213,14 @@ // To avoid formatting differences between clang-format 3.8 and 7 #define PROJ_NOEXCEPT noexcept +//! @cond Doxygen_Suppress +// isOfExactType(*p) checks that the type of *p is exactly MyType +template +inline bool isOfExactType(const ObjectT &o) { + return typeid(TemplateT).hash_code() == typeid(o).hash_code(); +} +//! @endcond + /** \brief Loose transposition of [std::optional] * (https://en.cppreference.com/w/cpp/utility/optional) available from C++17. */ template class optional { diff -Nru proj-7.2.0/man/man1/cct.1 proj-7.2.1/man/man1/cct.1 --- proj-7.2.0/man/man1/cct.1 2020-10-26 11:00:19.000000000 +0000 +++ proj-7.2.1/man/man1/cct.1 2020-12-26 18:57:21.000000000 +0000 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "CCT" "1" "Nov 01, 2020" "7.2.0" "PROJ" +.TH "CCT" "1" "Dec 25, 2020" "7.2.1" "PROJ" .SH NAME cct \- Coordinate Conversion and Transformation . diff -Nru proj-7.2.0/man/man1/cs2cs.1 proj-7.2.1/man/man1/cs2cs.1 --- proj-7.2.0/man/man1/cs2cs.1 2020-10-26 11:00:19.000000000 +0000 +++ proj-7.2.1/man/man1/cs2cs.1 2020-12-26 18:57:21.000000000 +0000 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "CS2CS" "1" "Nov 01, 2020" "7.2.0" "PROJ" +.TH "CS2CS" "1" "Dec 25, 2020" "7.2.1" "PROJ" .SH NAME cs2cs \- Cartographic coordinate system filter . diff -Nru proj-7.2.0/man/man1/geod.1 proj-7.2.1/man/man1/geod.1 --- proj-7.2.0/man/man1/geod.1 2020-10-26 11:00:19.000000000 +0000 +++ proj-7.2.1/man/man1/geod.1 2020-12-26 18:57:21.000000000 +0000 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "GEOD" "1" "Nov 01, 2020" "7.2.0" "PROJ" +.TH "GEOD" "1" "Dec 25, 2020" "7.2.1" "PROJ" .SH NAME geod \- Geodesic computations . diff -Nru proj-7.2.0/man/man1/gie.1 proj-7.2.1/man/man1/gie.1 --- proj-7.2.0/man/man1/gie.1 2020-10-26 11:00:19.000000000 +0000 +++ proj-7.2.1/man/man1/gie.1 2020-12-26 18:57:21.000000000 +0000 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "GIE" "1" "Nov 01, 2020" "7.2.0" "PROJ" +.TH "GIE" "1" "Dec 25, 2020" "7.2.1" "PROJ" .SH NAME gie \- The Geospatial Integrity Investigation Environment . diff -Nru proj-7.2.0/man/man1/proj.1 proj-7.2.1/man/man1/proj.1 --- proj-7.2.0/man/man1/proj.1 2020-10-26 11:00:19.000000000 +0000 +++ proj-7.2.1/man/man1/proj.1 2020-12-26 18:57:21.000000000 +0000 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "PROJ" "1" "Nov 01, 2020" "7.2.0" "PROJ" +.TH "PROJ" "1" "Dec 25, 2020" "7.2.1" "PROJ" .SH NAME proj \- Cartographic projection filter . diff -Nru proj-7.2.0/man/man1/projinfo.1 proj-7.2.1/man/man1/projinfo.1 --- proj-7.2.0/man/man1/projinfo.1 2020-10-26 11:00:19.000000000 +0000 +++ proj-7.2.1/man/man1/projinfo.1 2020-12-26 18:57:21.000000000 +0000 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "PROJINFO" "1" "Nov 01, 2020" "7.2.0" "PROJ" +.TH "PROJINFO" "1" "Dec 25, 2020" "7.2.1" "PROJ" .SH NAME projinfo \- Geodetic object and coordinate operation queries . diff -Nru proj-7.2.0/man/man1/projsync.1 proj-7.2.1/man/man1/projsync.1 --- proj-7.2.0/man/man1/projsync.1 2020-10-26 11:00:19.000000000 +0000 +++ proj-7.2.1/man/man1/projsync.1 2020-12-26 18:57:21.000000000 +0000 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "PROJSYNC" "1" "Nov 01, 2020" "7.2.0" "PROJ" +.TH "PROJSYNC" "1" "Dec 25, 2020" "7.2.1" "PROJ" .SH NAME projsync \- Downloading tool of resource files . diff -Nru proj-7.2.0/NEWS proj-7.2.1/NEWS --- proj-7.2.0/NEWS 2020-10-26 11:00:19.000000000 +0000 +++ proj-7.2.1/NEWS 2020-12-26 18:42:14.000000000 +0000 @@ -1,3 +1,69 @@ +7.2.1 Release Notes +------------------- + + Updates + ------- + + o Add metadata with the version number of the database layout (#2474) + + o Split coordinateoperation.cpp and test_operation.cpp in several parts (#2484) + + o Update to EPSG v10.008 (#2490) + + o Added the NKG 2008 and 2020 transformations in proj.db (#2495) + + Bug fixes + --------- + + o Set CURL_ENABLED definition on projinfo build (#2405) + + o createBoundCRSToWGS84IfPossible(): make it return same result with a CRS + built from EPSG code or WKT1 (#2412) + + o WKT2 parsing: several fixes related to map projection parameter units (#2428) + + o createOperation(): make it work properly when one of the CRS is a BoundCRS of + a DerivedGeographicCRS (+proj=ob_tran +o_proj=lonlat +towgs84=....) (#2441) + + o WKT parsing: fix ingestion of WKT with a Geocentric CRS as the base of the + projected CRS (#2443) + + o GeographicCRS::_isEquivalentTo(EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS): + make it work when comparing easting,northing,up and northing,easting,up (#2446) + + o createOperation(): add a ballpark vertical transformation when dealing + with GEOIDMODEL[] (#2449) + + o Use same arguments to printf format string for both radians and degrees in + output by cct (#2453) + + o PRIMEM WKT handling: fixes on import for 'sexagesimal DMS' or from WKT1:GDAL/ESRI + when GEOGCS UNIT != Degree; morph to ESRI the PRIMEM name on export (#2455) + + o createObjectsFromName(): in exact match, make looking for 'ETRS89 / UTM zone 32N' + return only the exact match (#2462) + + o Inverse tmerc spherical: fix wrong sign of latitude when lat_0 is used (#2469) + + o Add option to allow export of Geographic/Projected 3D CRS in WKT1_GDAL (#2470) + + o Fix building proj.db with SQLite built with -DSQLITE_DQS=0 (#2480) + + o Include JSON Schema files in CMake builds (#2485) + + o createOperations(): fix inconsistent chaining exception when transforming from BoundCRS of projected CRS based on NTF Paris to BoundCRS of geog CRS NTF Paris (#2486) + + THANKS TO + --------- + + Zac Miller + Nomit Rawat + Guillaume Lostis + J.H. van de Water + Kristian Evers + Even Rouault + + 7.2.0 Release Notes ------------------- diff -Nru proj-7.2.0/src/apps/cct.cpp proj-7.2.1/src/apps/cct.cpp --- proj-7.2.0/src/apps/cct.cpp 2020-01-23 08:45:49.000000000 +0000 +++ proj-7.2.1/src/apps/cct.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -385,12 +385,22 @@ colmax = MAX(colmax, columns_xyzt[i]); comment = column(buf, colmax+1); } + /* remove the line feed from comment, as logger() above, invoked + by print() below (output), will add one */ + size_t len = strlen(comment); + if (len >= 1) + comment[len - 1] = '\0'; comment_delimiter = (comment && *comment) ? whitespace : blank_comment; /* Time to print the result */ - if (proj_angular_output (P, direction)) { - point.lpzt.lam = proj_todeg (point.lpzt.lam); - point.lpzt.phi = proj_todeg (point.lpzt.phi); + /* use same arguments to printf format string for both radians and + degrees; convert radians to degrees before printing */ + if (proj_angular_output (P, direction) || + proj_degree_output (P, direction)) { + if (proj_angular_output (P, direction)) { + point.lpzt.lam = proj_todeg (point.lpzt.lam); + point.lpzt.phi = proj_todeg (point.lpzt.phi); + } print (PJ_LOG_NONE, "%14.*f %14.*f %12.*f %12.4f%s%s", decimals_angles, point.xyzt.x, decimals_angles, point.xyzt.y, @@ -405,6 +415,8 @@ decimals_distances, point.xyzt.z, point.xyzt.t, comment_delimiter, comment ); + if( fout == stdout ) + fflush(stdout); } proj_destroy(P); diff -Nru proj-7.2.0/src/apps/cs2cs.cpp proj-7.2.1/src/apps/cs2cs.cpp --- proj-7.2.0/src/apps/cs2cs.cpp 2020-06-23 16:52:58.000000000 +0000 +++ proj-7.2.1/src/apps/cs2cs.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -217,6 +217,7 @@ printf("%s", s); else printf("\n"); + fflush(stdout); } } diff -Nru proj-7.2.0/src/apps/geod.cpp proj-7.2.1/src/apps/geod.cpp --- proj-7.2.0/src/apps/geod.cpp 2020-06-23 16:52:58.000000000 +0000 +++ proj-7.2.1/src/apps/geod.cpp 2020-12-21 16:29:10.000000000 +0000 @@ -124,6 +124,7 @@ (void)fputs(rtodms(pline, al21, 0, 0), stdout); } (void)fputs(s, stdout); + fflush(stdout); } } diff -Nru proj-7.2.0/src/apps/proj.cpp proj-7.2.1/src/apps/proj.cpp --- proj-7.2.0/src/apps/proj.cpp 2020-06-23 16:52:58.000000000 +0000 +++ proj-7.2.1/src/apps/proj.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -171,6 +171,7 @@ (void)fputs("\t<* * * * * *>", stdout); } (void)fputs(bin_in ? "\n" : s, stdout); + fflush(stdout); } } @@ -286,6 +287,8 @@ (void)fputs(proj_rtodms(pline, facs.meridian_convergence, 0, 0), stdout); (void)printf(" [ %.8f ]\n", facs.meridian_convergence * RAD_TO_DEG); (void)printf("Max-min (Tissot axis a-b) scale error: %.5f %.5f\n\n", facs.tissot_semimajor, facs.tissot_semiminor); + + fflush(stdout); } } diff -Nru proj-7.2.0/src/apps/projinfo.cpp proj-7.2.1/src/apps/projinfo.cpp --- proj-7.2.0/src/apps/projinfo.cpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/src/apps/projinfo.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -689,6 +689,7 @@ std::vector list; size_t spatialCriterionPartialIntersectionResultCount = 0; + bool spatialCriterionPartialIntersectionMoreRelevant = false; try { auto authFactory = dbContext @@ -714,10 +715,15 @@ ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion:: PARTIAL_INTERSECTION); - spatialCriterionPartialIntersectionResultCount = - CoordinateOperationFactory::create() - ->createOperations(nnSourceCRS, nnTargetCRS, ctxt) - .size(); + auto list2 = + CoordinateOperationFactory::create()->createOperations( + nnSourceCRS, nnTargetCRS, ctxt); + spatialCriterionPartialIntersectionResultCount = list2.size(); + if (spatialCriterionPartialIntersectionResultCount == 1 && + list.size() == 1 && + list2[0]->nameStr() != list[0]->nameStr()) { + spatialCriterionPartialIntersectionMoreRelevant = true; + } } catch (const std::exception &) { } } @@ -736,6 +742,10 @@ "more results (" << spatialCriterionPartialIntersectionResultCount << ")" << std::endl; + } else if (spatialCriterionPartialIntersectionMoreRelevant) { + std::cout << "Note: using '--spatial-test intersects' would bring " + "more relevant results." + << std::endl; } if (summary) { for (const auto &op : list) { diff -Nru proj-7.2.0/src/bin_projinfo.cmake proj-7.2.1/src/bin_projinfo.cmake --- proj-7.2.0/src/bin_projinfo.cmake 2020-05-21 10:07:50.000000000 +0000 +++ proj-7.2.1/src/bin_projinfo.cmake 2020-12-21 16:29:10.000000000 +0000 @@ -15,3 +15,7 @@ if(MSVC AND BUILD_SHARED_LIBS) target_compile_definitions(binprojinfo PRIVATE PROJ_MSVC_DLL_IMPORT=1) endif() + +if(CURL_ENABLED) + target_compile_definitions(binprojinfo PRIVATE -DCURL_ENABLED) +endif() diff -Nru proj-7.2.0/src/iso19111/c_api.cpp proj-7.2.1/src/iso19111/c_api.cpp --- proj-7.2.0/src/iso19111/c_api.cpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/src/iso19111/c_api.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -1418,6 +1418,13 @@ * variants, for WKT1_GDAL for ProjectedCRS with easting/northing ordering * (otherwise stripped), but not for WKT1_ESRI. Setting to YES will output * them unconditionally, and to NO will omit them unconditionally. + *
  • STRICT=YES/NO. Default is YES. If NO, a Geographic 3D CRS can be for + * example exported as WKT1_GDAL with 3 axes, whereas this is normally not + * allowed.
  • + *
  • ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES/NO. Default is NO. If set + * to YES and type == PJ_WKT1_GDAL, a Geographic 3D CRS or a Projected 3D CRS + * will be exported as a compound CRS whose vertical part represents an + * ellipsoidal height (for example for use with LAS 1.4 WKT1).
  • * * @return a string, or NULL in case of error. */ @@ -1468,6 +1475,11 @@ } } else if ((value = getOptionValue(*iter, "STRICT="))) { formatter->setStrict(ci_equal(value, "YES")); + } else if ((value = getOptionValue( + *iter, + "ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS="))) { + formatter->setAllowEllipsoidalHeightAsVerticalCRS( + ci_equal(value, "YES")); } else { std::string msg("Unknown option :"); msg += *iter; @@ -2050,6 +2062,8 @@ /** \brief Get the horizontal datum from a CRS * + * This function may return a Datum or DatumEnsemble object. + * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. @@ -4438,8 +4452,9 @@ * * @param ctx PROJ context, or NULL for default context * @param type Coordinate system type. - * @param unit_name Unit name. - * @param unit_conv_factor Unit conversion factor to SI. + * @param unit_name Name of the angular units. Or NULL for Degree + * @param unit_conv_factor Conversion factor from the angular unit to radian. + * Or 0 for Degree if unit_name == NULL. Otherwise should be not NULL * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. @@ -4478,13 +4493,17 @@ * * @param ctx PROJ context, or NULL for default context * @param type Coordinate system type. - * @param horizontal_angular_unit_name Horizontal angular unit name. - * @param horizontal_angular_unit_conv_factor Horizontal angular unit conversion - * factor to SI. - * @param vertical_linear_unit_name Vertical linear unit name. + * @param horizontal_angular_unit_name Name of the angular units. Or NULL for + * Degree. + * @param horizontal_angular_unit_conv_factor Conversion factor from the angular + * unit to radian. Or 0 for Degree if horizontal_angular_unit_name == NULL. + * Otherwise should be not NULL + * @param vertical_linear_unit_name Vertical linear unit name. Or NULL for + * Metre. * @param vertical_linear_unit_conv_factor Vertical linear unit conversion - * factor to SI. - * + * factor to metre. Or 0 for Metre if vertical_linear_unit_name == NULL. + * Otherwise should be not NULL + * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. * @since 6.3 @@ -7987,6 +8006,9 @@ /** \brief Returns the datum of a SingleCRS. * + * If that function returns NULL, @see proj_crs_get_datum_ensemble() to + * potentially get a DatumEnsemble instead. + * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. @@ -8018,6 +8040,9 @@ /** \brief Returns the datum ensemble of a SingleCRS. * + * If that function returns NULL, @see proj_crs_get_datum() to + * potentially get a Datum instead. + * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. diff -Nru proj-7.2.0/src/iso19111/coordinateoperation.cpp proj-7.2.1/src/iso19111/coordinateoperation.cpp --- proj-7.2.0/src/iso19111/coordinateoperation.cpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/src/iso19111/coordinateoperation.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,15989 +0,0 @@ -/****************************************************************************** - * - * Project: PROJ - * Purpose: ISO19111:2019 implementation - * Author: Even Rouault - * - ****************************************************************************** - * Copyright (c) 2018, Even Rouault - * - * 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 -#include -#include -#include -#include -#include -#include -#include - -// #define TRACE_CREATE_OPERATIONS -// #define DEBUG_SORT -// #define DEBUG_CONCATENATED_OPERATION -#if defined(DEBUG_SORT) || defined(DEBUG_CONCATENATED_OPERATION) -#include -#endif - -using namespace NS_PROJ::internal; - -#if 0 -namespace dropbox{ namespace oxygen { -template<> nn::~nn() = default; -template<> nn::~nn() = default; -template<> nn::~nn() = default; -template<> nn::~nn() = default; -template<> nn::~nn() = default; -template<> nn::~nn() = default; -template<> nn::~nn() = default; -template<> nn::~nn() = default; -template<> nn::~nn() = default; -template<> nn::~nn() = default; -template<> nn::~nn() = default; -template<> nn::~nn() = default; -template<> nn > >::~nn() = default; -template<> nn > >::~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>::~nn() = default; -template<> nn>::~nn() = default; -template<> nn>::~nn() = default; -template<> nn::~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 buildSetEquivalentParameters() { - - std::set 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 ¶mList : 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 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 -getMappingsFromPROJName(const std::string &projName) { - std::vector 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 ¶m) { - 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 -getMappingsFromESRI(const std::string &esri_name) { - std::vector 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 &ops); - -// Returns the accuracy of an operation, or -1 if unknown -static double getAccuracy(const CoordinateOperationNNPtr &op) { - - if (dynamic_cast(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(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 &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 &ops, - bool conversionExtentIsWorld, bool &emptyIntersection); - -static metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op, - bool conversionExtentIsWorld, - bool &emptyIntersection) { - auto conv = dynamic_cast(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(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(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(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 &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( - 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 operationVersion_{}; - std::vector - coordinateOperationAccuracies_{}; - std::weak_ptr sourceCRSWeak_{}; - std::weak_ptr targetCRSWeak_{}; - crs::CRSPtr interpolationCRS_{}; - util::optional sourceCoordinateEpoch_{}; - util::optional 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 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( - *(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()) {} - -// --------------------------------------------------------------------------- - -CoordinateOperation::CoordinateOperation(const CoordinateOperation &other) - : ObjectUsage(other), d(internal::make_unique(*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 & -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 & -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 & -CoordinateOperation::sourceCoordinateEpoch() const { - return d->sourceCoordinateEpoch_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the target epoch of coordinates. - * - * @return target epoch of coordinates, or empty. - */ -const util::optional & -CoordinateOperation::targetCoordinateEpoch() const { - return d->targetCoordinateEpoch_; -} - -// --------------------------------------------------------------------------- - -void CoordinateOperation::setWeakSourceTargetCRS( - std::weak_ptr sourceCRSIn, std::weak_ptr 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(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 &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( - shared_from_this().as_nullable())); - if (!swapSource && !swapTarget) { - return l_this; - } - std::vector 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( - ConcatenatedOperation::createComputeMetadata(subOps, true)); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -CoordinateOperationNNPtr CoordinateOperation::shallowClone() const { - return _shallowClone(); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct OperationMethod::Private { - util::optional formula_{}; - util::optional formulaCitation_{}; - std::vector parameters_{}; - std::string projMethodOverride_{}; -}; -//! @endcond - -// --------------------------------------------------------------------------- - -OperationMethod::OperationMethod() : d(internal::make_unique()) {} - -// --------------------------------------------------------------------------- - -OperationMethod::OperationMethod(const OperationMethod &other) - : IdentifiedObject(other), d(internal::make_unique(*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 &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 & -OperationMethod::formulaCitation() PROJ_PURE_DEFN { - return d->formulaCitation_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the parameters of this operation method. - * - * @return the parameters. - */ -const std::vector & -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 ¶meters) { - OperationMethodNNPtr method( - OperationMethod::nn_make_shared()); - 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 ¶meters) { - std::vector 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(other); - if (otherOM == nullptr || - !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { - return false; - } - // TODO test formula and formulaCitation - const auto ¶ms = 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 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 ¶meterIn, - const ParameterValueNNPtr &valueIn) - : parameter(parameterIn), parameterValue(valueIn) {} -}; -//! @endcond - -// --------------------------------------------------------------------------- - -OperationParameterValue::OperationParameterValue( - const OperationParameterValue &other) - : GeneralParameterValue(other), - d(internal::make_unique(*other.d)) {} - -// --------------------------------------------------------------------------- - -OperationParameterValue::OperationParameterValue( - const OperationParameterNNPtr ¶meterIn, - const ParameterValueNNPtr &valueIn) - : GeneralParameterValue(), - d(internal::make_unique(parameterIn, valueIn)) {} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a OperationParameterValue. - * - * @param parameterIn Parameter (definition). - * @param valueIn Parameter value. - * @return a new OperationParameterValue. - */ -OperationParameterValueNNPtr -OperationParameterValue::create(const OperationParameterNNPtr ¶meterIn, - const ParameterValueNNPtr &valueIn) { - return OperationParameterValue::nn_make_shared( - 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 ¶mName, double &val, - const common::UnitOfMeasure *&unit, int ¶mEPSGCode) { - 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(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()); - 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(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 parameterValues_{}; - OperationMethodNNPtr method_; - - explicit Private(const OperationMethodNNPtr &methodIn) - : method_(methodIn) {} -}; -//! @endcond - -// --------------------------------------------------------------------------- - -SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn) - : d(internal::make_unique(methodIn)) {} - -// --------------------------------------------------------------------------- - -SingleOperation::SingleOperation(const SingleOperation &other) - : -#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) - CoordinateOperation(other), -#endif - d(internal::make_unique(*other.d)) { -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -SingleOperation::~SingleOperation() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Return the parameter values. - * - * @return the parameter values. - */ -const std::vector & -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 &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 ¶mName, - int epsg_code) const noexcept { - if (epsg_code) { - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = opParamvalue->parameter(); - if (parameter->getEPSGCode() == epsg_code) { - return opParamvalue->parameterValue(); - } - } - } - } - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = 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( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = 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( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = 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 ¶mName, - 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 &accuracies) { - return util::nn_static_pointer_cast( - 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 &accuracies, - bool hasBallparkTransformation) { - return util::nn_static_pointer_cast( - 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(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(sourceCRS().get()); - auto otherGeodCRS = dynamic_cast( - 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(this); - auto otherTransf = static_cast(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(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 candidateIndices(otherValuesSize, true); - bool equivalent = true; - bool foundMissingArgs = valuesSize != otherValuesSize; - - for (size_t i = 0; equivalent && i < valuesSize; i++) { - auto opParamvalue = - dynamic_cast(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( - 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(this); - auto otherConv = dynamic_cast(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 -SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext, - bool considerKnownGridsAsAvailable) const { - std::set res; - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast( - 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 SingleOperation::validateParameters() const { - std::list 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( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = 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 == ¶mLatitudeNatOrigin) { - // 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 ¶meter = 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( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = 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 measure_{}; - std::unique_ptr stringValue_{}; - int integerValue_{}; - bool booleanValue_{}; - - explicit Private(const common::Measure &valueIn) - : type_(ParameterValue::Type::MEASURE), - measure_(internal::make_unique(valueIn)) {} - - Private(const std::string &stringValueIn, ParameterValue::Type typeIn) - : type_(typeIn), - stringValue_(internal::make_unique(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(measureIn)) {} - -// --------------------------------------------------------------------------- - -ParameterValue::ParameterValue(const std::string &stringValueIn, - ParameterValue::Type typeIn) - : d(internal::make_unique(stringValueIn, typeIn)) {} - -// --------------------------------------------------------------------------- - -ParameterValue::ParameterValue(int integerValueIn) - : d(internal::make_unique(integerValueIn)) {} - -// --------------------------------------------------------------------------- - -ParameterValue::ParameterValue(bool booleanValueIn) - : d(internal::make_unique(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(measureIn); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a ParameterValue from a string value. - * - * @return a new ParameterValue. - */ -ParameterValueNNPtr ParameterValue::create(const char *stringValueIn) { - return ParameterValue::nn_make_shared( - 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( - 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( - 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(integerValueIn); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a ParameterValue from a boolean value. - * - * @return a new ParameterValue. - */ -ParameterValueNNPtr ParameterValue::create(bool booleanValueIn) { - return ParameterValue::nn_make_shared(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(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 &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(*this); - conv->assignSelf(conv); - conv->setCRSs(this, false); - return conv; -} - -CoordinateOperationNNPtr Conversion::_shallowClone() const { - return util::nn_static_pointer_cast(shallowClone()); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -ConversionNNPtr -Conversion::alterParametersLinearUnit(const common::UnitOfMeasure &unit, - bool convertToNewUnit) const { - - std::vector newValues; - bool changesDone = false; - for (const auto &genOpParamvalue : parameterValues()) { - bool updated = false; - auto opParamvalue = dynamic_cast( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶mValue = 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(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 - &values) // throw InvalidOperation -{ - if (methodIn->parameters().size() != values.size()) { - throw InvalidOperation( - "Inconsistent number of parameters and parameter values"); - } - auto conv = Conversion::nn_make_shared(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 ¶meters, - const std::vector &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 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 &values) { - - std::vector 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 &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 &values) { - const MethodMapping *mapping = getMapping(method_wkt2_name); - assert(mapping); - return createConversion(properties, mapping, values); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -struct VectorOfParameters : public std::vector { - VectorOfParameters() : std::vector() {} - explicit VectorOfParameters( - std::initializer_list list) - : std::vector(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 { - VectorOfValues() : std::vector() {} - explicit VectorOfValues(std::initializer_list list) - : std::vector(list) {} - - explicit VectorOfValues(std::initializer_list list); - VectorOfValues(const VectorOfValues &) = delete; - VectorOfValues(VectorOfValues &&) = default; - - ~VectorOfValues(); -}; - -static std::vector buildParameterValueFromMeasure( - const std::initializer_list &list) { - std::vector res; - for (const auto &v : list) { - res.emplace_back(ParameterValue::create(v)); - } - return res; -} - -VectorOfValues::VectorOfValues(std::initializer_list list) - : std::vector(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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLat, - const common::Angle ¢erLong, 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 <= 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 ¢erLat, - const common::Angle ¢erLong, 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 <= 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 <= 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 ¢erLat, - const common::Angle ¢erLong, 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 - *<= 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLong, - 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 <= 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 <= 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 ¢erLong, - 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 ¢erLong, - 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 <= 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLat, - const common::Angle ¢erLong, 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 >=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 >=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 <= - *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 ¢erLong, - 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 ¢erLong, - 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLong, - 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLong, - 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLong, 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLong, - 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 ¢erLat, - const common::Angle ¢erLong, 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 ¢erLong, - 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: - *
      - *
    • it is a 2D-only method, ignoring the ellipsoidal height of the point to - * project.
    • - *
    • it has only a spherical development.
    • - *
    • the height of the topocentric origin is ignored, and thus assumed to be - * 0.
    • - *
    - * - * 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: - *
    - * +proj=ob_tran +o_proj=longlat +o_lon_p=-rotationAngle
    - *                               +o_lat_p=-southPoleLatInUnrotatedCRS
    - *                               +lon_0=southPoleLongInUnrotatedCRS
    - * 
    - * - * 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.get()); - if (geod) { - if (geod->isGeocentric()) { - return " (geocentric)"; - } - auto geog = dynamic_cast(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(forwardOperation_)); -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr -InverseConversion::create(const ConversionNNPtr &forward) { - auto conv = util::nn_make_shared(forward); - conv->assignSelf(conv); - return conv; -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr InverseConversion::_shallowClone() const { - auto op = InverseConversion::nn_make_shared( - inverseAsConversion()->shallowClone()); - op->assignSelf(op); - op->setCRSs(this, false); - return util::nn_static_pointer_cast(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(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: - *
      - *
    • EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to - * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)
    • - *
    • EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to - * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)
    • - *
    • EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to - * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP
    • - *
    • EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to - * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP
    • - *
    - * - * @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(shared_from_this()); - } - - auto geogCRS = dynamic_cast(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( - genOpParamvalue.get()); - if (opParamvalue && - opParamvalue->parameter()->getEPSGCode() == - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) { - const auto ¶mValue = 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( - 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 ¶mValue = 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(sourceCRS.get()); - if (!geogCRS) { - return false; - } - - std::string units("m"); - auto targetCRS = conv->targetCRS(); - auto targetProjCRS = - dynamic_cast(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(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( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶mName = opParamvalue->parameter()->nameStr(); - const auto ¶mValue = 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; - - auto l_sourceCRS = sourceCRS(); - if (!formatter->getCRSExport() && l_sourceCRS && applySourceCRSModifiers) { - - crs::CRS *horiz = l_sourceCRS.get(); - const auto compound = dynamic_cast(horiz); - if (compound) { - const auto &components = compound->componentReferenceSystems(); - if (!components.empty()) { - horiz = components.front().get(); - } - } - - auto geogCRS = dynamic_cast(horiz); - if (geogCRS) { - formatter->setOmitProjLongLatIfPossible(true); - formatter->startInversion(); - geogCRS->_exportToPROJString(formatter); - formatter->stopInversion(); - formatter->setOmitProjLongLatIfPossible(false); - } - - auto projCRS = dynamic_cast(horiz); - 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; - } - - 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( - 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(horiz); - if (compound) { - const auto &components = - compound->componentReferenceSystems(); - if (!components.empty()) { - horiz = components.front().get(); - } - } - - auto geogCRS = - dynamic_cast(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(horiz); - if (compound) { - const auto &components = compound->componentReferenceSystems(); - if (!components.empty()) { - horiz = components.front().get(); - } - } - - if (!bEllipsoidParametersDone) { - auto targetGeogCRS = horiz->extractGeographicCRS(); - if (targetGeogCRS) { - if (formatter->getCRSExport()) { - targetGeogCRS->addDatumInfoToPROJString(formatter); - } else { - targetGeogCRS->ellipsoid()->_exportToPROJString(formatter); - targetGeogCRS->primeMeridian()->_exportToPROJString( - formatter); - } - } - } - - auto projCRS = dynamic_cast(horiz); - if (projCRS) { - formatter->pushOmitZUnitConversion(); - projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound); - formatter->popOmitZUnitConversion(); - } - - auto derivedGeographicCRS = - dynamic_cast(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( - 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(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(*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 &values, - const std::vector &accuracies) - : SingleOperation(methodIn), d(internal::make_unique()) { - 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(*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(*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(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 -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 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( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = 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 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 &values, - const std::vector &accuracies) { - if (methodIn->parameters().size() != values.size()) { - throw InvalidOperation( - "Inconsistent number of parameters and parameter values"); - } - auto transf = Transformation::nn_make_shared( - 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 ¶meters, - const std::vector &values, - const std::vector - &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 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 &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(sourceCRSIn.get()); - auto targetCRSGeod = - dynamic_cast(targetCRSIn.get()); - isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() && - targetCRSGeod && targetCRSGeod->isGeocentric(); - if (isGeocentric) { - isGeog2D = false; - isGeog3D = false; - return; - } - isGeocentric = false; - - auto sourceCRSGeog = - dynamic_cast(sourceCRSIn.get()); - auto targetCRSGeog = - dynamic_cast(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( - (*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 &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 &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 &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 &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 &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 &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 &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 &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 &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 &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(transformSourceCRS.get()) - ? util::nn_static_pointer_cast( - crs::GeographicCRS::EPSG_4326) - : util::nn_static_pointer_cast( - 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 &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 &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 &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 &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 -buildAccuracyZero() { - return std::vector{ - 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 &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 &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 &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 &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 &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(op) || - starts_with(forwardName, "Transformation from ")) { - opType = "Transformation"; - } else if (dynamic_cast(op)) { - opType = "Conversion"; - } else { - opType = "Operation"; - } - - auto sourceCRS = op->sourceCRS(); - auto targetCRS = op->targetCRS(); - std::string name; - if (!forwardName.empty()) { - if (dynamic_cast(op) == nullptr && - dynamic_cast(op) == nullptr && - (starts_with(forwardName, INVERSE_OF) || - forwardName.find(" + ") != std::string::npos)) { - std::vector 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(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( - 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( - 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(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(forward); - conv->assignSelf(conv); - return conv; -} - -// --------------------------------------------------------------------------- - -TransformationNNPtr InverseTransformation::inverseAsTransformation() const { - return NN_NO_CHECK( - util::nn_dynamic_pointer_cast(forwardOperation_)); -} - -// --------------------------------------------------------------------------- - -void InverseTransformation::_exportToWKT(io::WKTFormatter *formatter) const { - - auto approxInverse = createApproximateInverseIfPossible( - util::nn_dynamic_pointer_cast(forwardOperation_).get()); - if (approxInverse) { - approxInverse->_exportToWKT(formatter); - } else { - Transformation::_exportToWKT(formatter); - } -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr InverseTransformation::_shallowClone() const { - auto op = InverseTransformation::nn_make_shared( - inverseAsTransformation()->shallowClone()); - op->assignSelf(op); - op->setCRSs(this, false); - return util::nn_static_pointer_cast(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 ¶mValue : 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()); - - std::string forwardName = obj->nameStr(); - if (!forwardName.empty()) { - map.set(common::IdentifiedObject::NAME_KEY, forwardName); - } - - 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 &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( - 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{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{ - 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{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{ - 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{ - 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{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{ - 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); - if (!geogCRS) { - auto compoundCRS = util::nn_dynamic_pointer_cast(crs); - if (compoundCRS) { - const auto &components = compoundCRS->componentReferenceSystems(); - if (!components.empty()) { - geogCRS = util::nn_dynamic_pointer_cast( - components[0]); - if (!geogCRS) { - auto boundCRS = - util::nn_dynamic_pointer_cast( - components[0]); - if (boundCRS) { - geogCRS = - util::nn_dynamic_pointer_cast( - boundCRS->baseCRS()); - } - } - } - } else { - auto boundCRS = util::nn_dynamic_pointer_cast(crs); - if (boundCRS) { - geogCRS = util::nn_dynamic_pointer_cast( - 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 (addPushV3) { - formatter->addStep("push"); - formatter->addParam("v_3"); - } - - formatter->addStep("cart"); - sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); - } else { - auto sourceCRSGeod = dynamic_cast(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"); - } - - targetCRSGeog->_exportToPROJString(formatter); - } else { - auto targetCRSGeod = dynamic_cast(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(sourceCRS().get()); - auto targetCRSGeog = - dynamic_cast(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(sourceCRS().get()); - if (!sourceCRSGeog) { - throw io::FormattingException( - "Can apply Molodensky only to GeographicCRS"); - } - - auto targetCRSGeog = - dynamic_cast(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(sourceCRS().get()); - if (!sourceCRSGeog) { - throw io::FormattingException( - "Can apply Geographic 2D offsets only to GeographicCRS"); - } - - auto targetCRSGeog = - dynamic_cast(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(sourceCRS().get()); - if (!sourceCRSGeog) { - throw io::FormattingException( - "Can apply Geographic 3D offsets only to GeographicCRS"); - } - - auto targetCRSGeog = - dynamic_cast(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(sourceCRS().get()); - if (!sourceCRSGeog) { - auto sourceCRSCompound = - dynamic_cast(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(targetCRS().get()); - if (!targetCRSGeog) { - auto targetCRSCompound = - dynamic_cast(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(sourceCRS().get()); - if (!sourceCRSVert) { - throw io::FormattingException( - "Can apply Vertical offset only to VerticalCRS"); - } - - auto targetCRSVert = - dynamic_cast(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( - 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(sourceCRS().get()); - if (!sourceCRSGeog) { - throw io::FormattingException( - concat("Can apply ", methodName, " only to GeographicCRS")); - } - - auto targetCRSGeog = - dynamic_cast(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(sourceCRS().get()); - if (!sourceCRSGeog) { - throw io::FormattingException( - concat("Can apply ", methodName, " only to GeographicCRS")); - } - - auto targetCRSGeog = - dynamic_cast(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(sourceCRS().get()); - auto targetCRSGeog = - dynamic_cast(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(sourceCRS().get()); - auto targetCRSGeod = - dynamic_cast(targetCRS().get()); - if (sourceCRSGeod && targetCRSGeod) { - auto sourceCRSGeog = - dynamic_cast(sourceCRSGeod); - auto targetCRSGeog = - dynamic_cast(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 operations_{}; - bool computedName_ = false; - - explicit Private(const std::vector &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(*(other.d))) {} -//! @endcond - -// --------------------------------------------------------------------------- - -ConcatenatedOperation::ConcatenatedOperation( - const std::vector &operationsIn) - : CoordinateOperation(), d(internal::make_unique(operationsIn)) {} - -// --------------------------------------------------------------------------- - -/** \brief Return the operation steps of the concatenated operation. - * - * @return the operation steps. - */ -const std::vector & -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 &operationsIn, - const std::vector - &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( - 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 &operationsInOut) { - - // Set of heuristics to assign CRS to steps, and possibly reverse them. - - const auto isGeographic = [](const crs::CRS *crs) -> bool { - return dynamic_cast(crs) != nullptr; - }; - - const auto isGeocentric = [](const crs::CRS *crs) -> bool { - auto geodCRS = dynamic_cast(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(op.get()); - if (conv && i == 0 && !l_sourceCRS && !l_targetCRS) { - auto derivedCRS = - dynamic_cast(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(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(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( - 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 &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 &operationsIn, - bool checkExtent) // throw InvalidOperation -{ - util::PropertyMap properties; - - if (operationsIn.size() == 1) { - return operationsIn[0]; - } - - std::vector flattenOps; - bool hasBallparkTransformation = false; - for (const auto &subOp : operationsIn) { - hasBallparkTransformation |= subOp->hasBallparkTransformation(); - auto subOpConcat = - dynamic_cast(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 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 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(*this); - std::vector 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(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(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 ConcatenatedOperation::gridsNeeded( - const io::DatabaseContextPtr &databaseContext, - bool considerKnownGridsAsAvailable) const { - std::set 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> - intermediateCRSAuthCodes_{}; - bool discardSuperseded_ = true; - bool allowBallpark_ = true; -}; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -CoordinateOperationContext::~CoordinateOperationContext() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -CoordinateOperationContext::CoordinateOperationContext() - : d(internal::make_unique()) {} - -// --------------------------------------------------------------------------- - -/** \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> - &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> & -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()); - 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::list>> - cacheNameToCRS{}; - - Context(const metadata::ExtentPtr &extent1In, - const metadata::ExtentPtr &extent2In, - const CoordinateOperationContextNNPtr &contextIn) - : extent1(extent1In), extent2(extent2In), context(contextIn) {} - }; - - static std::vector - 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> &ids); - - static std::vector findOpsInRegistryDirect( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, bool &resNonEmptyBeforeFiltering); - - static std::vector - findOpsInRegistryDirectTo(const crs::CRSNNPtr &targetCRS, - Private::Context &context); - - static std::vector - 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 &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 &res); - - static std::vector - createOperationsGeogToVertFromGeoid(const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS, - const crs::VerticalCRS *vertDst, - Context &context); - - static std::vector - createOperationsGeogToVertWithIntermediateVert( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const crs::VerticalCRS *vertDst, Context &context); - - static std::vector - 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 &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 &res); - - static void createOperationsDerivedTo( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::DerivedCRS *derivedSrc, - std::vector &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 &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 &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 &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 &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 &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 &res); - - static void createOperationsToGeod( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::GeodeticCRS *geodDst, - std::vector &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 &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 &res); - - static std::vector createOperationsGeogToGeog( - std::vector &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 &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 &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 isNullTransformation_ = false; - - PrecomputedOpCharacteristics() = default; - PrecomputedOpCharacteristics(double area, double accuracy, - bool isPROJExportable, bool hasGrids, - bool gridsAvailable, bool gridsKnown, - size_t stepCount, bool isApprox, - bool isNullTransformation) - : area_(area), accuracy_(accuracy), isPROJExportable_(isPROJExportable), - hasGrids_(hasGrids), gridsAvailable_(gridsAvailable), - gridsKnown_(gridsKnown), stepCount_(stepCount), isApprox_(isApprox), - isNullTransformation_(isNullTransformation) {} -}; - -// --------------------------------------------------------------------------- - -// We could have used a lambda instead of this old-school way, but -// filterAndSort() is already huge. -struct SortFunction { - - const std::map ↦ - - explicit SortFunction(const std::map &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.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(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(op.get()); - size_t stepCount = 1; - if (concat) { - stepCount = 0; - for (const auto &subOp : concat->operations()) { - if (dynamic_cast(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 &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 &getRes() { return res; } - - // ---------------------------------------------------------------------- - private: - const std::vector &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 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 hasFoundOpWithExtent = 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) - continue; - hasFoundOpWithExtent = 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) - continue; - hasFoundOpWithExtent = 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; - } - } - } - 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() && !hasFoundOpWithExtent) { - 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 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(), - 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 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 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 setPROJPlusExtent; - std::vector 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 -filterAndSort(const std::vector &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(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(resFiltered.size()))); -#endif - return resFiltered; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -// Apply the inverse() method on all elements of the input list -static std::vector -applyInverse(const std::vector &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> &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 allowedObjects; - auto geogCRS = dynamic_cast(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.get())) { - allowedObjects.push_back( - io::AuthorityFactory::ObjectType::PROJECTED_CRS); - } else if (dynamic_cast(crs.get())) { - allowedObjects.push_back( - io::AuthorityFactory::ObjectType::VERTICAL_CRS); - } - if (!allowedObjects.empty()) { - - const std::pair 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 -getCandidateAuthorities(const io::AuthorityFactoryPtr &authFactory, - const std::string &srcAuthName, - const std::string &targetAuthName) { - const auto &authFactoryName = authFactory->getAuthority(); - std::vector 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 -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> sourceIds; - std::list> 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 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(res.size())) + " to " + - toString(static_cast(resFiltered.size()))); -#endif - return resFiltered; - } - } - } - } - return std::vector(); -} - -// --------------------------------------------------------------------------- - -// Look in the authority registry for operations to targetCRS -std::vector -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> 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(res.size())) + " to " + - toString(static_cast(resFiltered.size()))); -#endif - return resFiltered; - } - } - } - return std::vector(); -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -// Look in the authority registry for operations from sourceCRS to targetCRS -// using an intermediate pivot -std::vector -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> sourceIds; - std::list> 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 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(), - 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(sourceCRS.get()) && - dynamic_cast(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(), - 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(res.size())) + " to " + - toString(static_cast(resFiltered.size()))); -#endif - return resFiltered; - } - } - } - return std::vector(); -} -//! @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(sourceCRS.get()); - const crs::GeographicCRS *geogDst = - dynamic_cast(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 accuracies; - if (isSameDatum) { - accuracies.emplace_back(metadata::PositionalAccuracy::create("0")); - } - - if (dynamic_cast(sourceCRS.get()) - ->coordinateSystem() - ->axisList() - .size() == 3 || - dynamic_cast(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>::~nn() = default; -template<> nn>::~nn() = default; -template<> nn>::~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( - util::nn_dynamic_pointer_cast(geodSrc), - util::nn_dynamic_pointer_cast(geodDst)); - - auto properties = util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - buildTransfName(geodSrc->nameStr(), geodDst->nameStr())); - return createPROJBased(properties, exportable, geodSrc, geodDst, nullptr, - {}, false); -} - -// --------------------------------------------------------------------------- - -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(targetCRS); - assert(geogDst); - - auto exportable = util::nn_make_shared( - 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)); - } - return createPROJBased( - properties, exportable, sourceCRS, targetCRS, nullptr, - verticalTransform->coordinateOperationAccuracies(), - verticalTransform->hasBallparkTransformation()); - } else { - bool emptyIntersection = false; - auto ops = std::vector{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)); - } - - std::vector 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( - opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS, - interpolationGeogCRS); - - std::vector 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)); - } - - std::vector 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 -CoordinateOperationFactory::Private::createOperationsGeogToGeog( - std::vector &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 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(), 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::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(), 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::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(op.get()); - if (concatenated) { - for (const auto &subOp : concatenated->operations()) { - if (hasIdentifiers(subOp)) { - return true; - } - } - } - return false; -} - -// --------------------------------------------------------------------------- - -static std::vector -findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory, - const crs::GeodeticCRS *crs, - const datum::GeodeticReferenceFrame *datum) { - std::vector 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( - 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(co); - if (invCO) { - invCO->forwardOperation()->setCRSs(targetCRS, sourceCRS, nullptr); - } - - auto transf = dynamic_cast(co); - if (transf) { - transf->inverseAsTransformation()->setCRSs(targetCRS, sourceCRS, - nullptr); - } - - auto concat = dynamic_cast(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 &res) { - for (const auto &op : res) { - auto concat = dynamic_cast(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 &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(opSecond.get()); - if (so && isAxisOrderReversal(so->method()->getEPSGCode())) { - continue; - } - - std::vector 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( - 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( - 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( - 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 &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 -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 res; - - auto boundSrc = dynamic_cast(sourceCRS.get()); - auto boundDst = dynamic_cast(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(sourceCRS.get()); - auto geodDst = dynamic_cast(targetCRS.get()); - auto geogSrc = dynamic_cast(sourceCRS.get()); - auto geogDst = dynamic_cast(targetCRS.get()); - auto vertSrc = dynamic_cast(sourceCRS.get()); - auto vertDst = dynamic_cast(targetCRS.get()); - - // First look-up if the registry provide us with operations. - auto derivedSrc = dynamic_cast(sourceCRS.get()); - auto derivedDst = dynamic_cast(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(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(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 &res) { - - ENTER_FUNCTION(); - - auto sourceProjExportable = dynamic_cast( - boundSrc ? boundSrc : sourceCRS.get()); - auto targetProjExportable = dynamic_cast( - 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(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(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 &res) { - - ENTER_FUNCTION(); - - if (geogSrc && vertDst) { - res = createOperationsGeogToVertFromGeoid(sourceCRS, targetCRS, vertDst, - context); - } else if (geogDst && vertSrc) { - res = applyInverse(createOperationsGeogToVertFromGeoid( - targetCRS, sourceCRS, vertSrc, context)); - } - - 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 -findCandidateVertCRSForDatum(const io::AuthorityFactoryPtr &authFactory, - const datum::VerticalReferenceFrame *datum) { - std::vector 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( - match.get())); - } - } - } - return candidates; -} - -// --------------------------------------------------------------------------- - -std::vector -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(op->targetCRS().get()); - assert(targetOp); - if (targetOp->_isEquivalentTo( - vertDst, util::IComparable::Criterion::EQUIVALENT)) { - return op; - } - std::vector 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(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::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 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 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( - transf->sourceCRS().get()) && - dynamic_cast( - transf->targetCRS().get())) { - res.push_back(useTransf(transf)); - } else if (dynamic_cast( - transf->targetCRS().get()) && - dynamic_cast( - transf->sourceCRS().get())) { - res.push_back(useTransf(transf->inverse())); - } - } - } - } - - return res; -} - -// --------------------------------------------------------------------------- - -std::vector CoordinateOperationFactory::Private:: - createOperationsGeogToVertWithIntermediateVert( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const crs::VerticalCRS *vertDst, Private::Context &context) { - - ENTER_FUNCTION(); - - std::vector 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 CoordinateOperationFactory::Private:: - createOperationsGeogToVertWithAlternativeGeog( - const crs::CRSNNPtr & /*sourceCRS*/, // geographic CRS - const crs::CRSNNPtr &targetCRS, // vertical CRS - Private::Context &context) { - - ENTER_FUNCTION(); - - std::vector 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(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 &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( - 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 &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(geodSrc); - auto geogDst = dynamic_cast(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::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( - 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 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 &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 &res) { - - ENTER_FUNCTION(); - - const auto &hubSrc = boundSrc->hubCRS(); - auto hubSrcGeog = dynamic_cast(hubSrc.get()); - auto geogCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractGeographicCRS(); - - 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 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 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(boundSrc->baseCRS().get())) { - auto transfSrc = boundSrc->transformation()->sourceCRS(); - if (dynamic_cast(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(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 &res) { - - ENTER_FUNCTION(); - - auto baseSrcVert = - dynamic_cast(boundSrc->baseCRS().get()); - const auto &hubSrc = boundSrc->hubCRS(); - auto hubSrcVert = dynamic_cast(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 &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 &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()) { - res = createOperations( - NN_NO_CHECK( - util::nn_dynamic_pointer_cast( - match)), - targetCRS, context); - return; - } - } - } - } - - 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 = true; - 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; - auto conv = Transformation::createChangeVerticalUnit( - util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()) + - BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT), - 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 &res) { - - ENTER_FUNCTION(); - - // BoundCRS to BoundCRS of horizontal CRS using the same (geographic) hub - const auto &hubSrc = boundSrc->hubCRS(); - auto hubSrcGeog = dynamic_cast(hubSrc.get()); - const auto &hubDst = boundDst->hubCRS(); - auto hubDstGeog = dynamic_cast(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 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(boundSrc->baseCRS().get()); - auto baseOfBoundDstAsVertCRS = - dynamic_cast(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 -getOps(const CoordinateOperationNNPtr &op) { - auto concatenated = dynamic_cast(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(subOpA.get())) - continue; - if (subOpA->sourceCRS()->nameStr() == "unknown" || - subOpA->targetCRS()->nameStr() == "unknown") - continue; - for (const auto &subOpB : subOpsB) { - if (!dynamic_cast(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(verticalTransform.get()); - if (transformationVerticalTransform == nullptr) { - const auto concat = dynamic_cast( - 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(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( - 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( - 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 &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(componentsSrc[0].get()); - if (derivedHSrc) { - std::vector 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 horizTransforms; - auto srcGeogCRS = componentsSrc[0]->extractGeographicCRS(); - if (srcGeogCRS) { - horizTransforms = - createOperations(componentsSrc[0], targetCRS, context); - } - std::vector 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> - PairOfTransforms; - std::map - 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 srcToInterpOps; - std::vector 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 &res) { - - auto cs = cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( - common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE); - auto intermGeog3DCRS = - util::nn_static_pointer_cast(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 &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 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( - nn_interpTransformCRS.get())) { - interpolationGeogCRS = NN_NO_CHECK( - util::nn_dynamic_pointer_cast( - nn_interpTransformCRS)); - } - } else { - auto compSrc0BoundCrs = - dynamic_cast(componentsSrc[0].get()); - auto compDst0BoundCrs = - dynamic_cast(componentsDst[0].get()); - if (compSrc0BoundCrs && compDst0BoundCrs && - dynamic_cast( - compSrc0BoundCrs->hubCRS().get()) && - compSrc0BoundCrs->hubCRS()->_isEquivalentTo( - compDst0BoundCrs->hubCRS().get())) { - interpolationGeogCRS = NN_NO_CHECK( - util::nn_dynamic_pointer_cast( - 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 &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(componentsDst[0].get()); - if (compDst0BoundCrs) { - auto boundSrcHubAsGeogCRS = - dynamic_cast(boundSrc->hubCRS().get()); - auto compDst0BoundCrsHubAsGeogCRS = - dynamic_cast( - 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 - -// --------------------------------------------------------------------------- - -static crs::CRSNNPtr -getResolvedCRS(const crs::CRSNNPtr &crs, - const CoordinateOperationContextNNPtr &context, - metadata::ExtentPtr &extentOut) { - const auto &authFactory = context->getAuthorityFactory(); - 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(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.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(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.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(resolvedCrs); - } - } catch (const std::exception &) { - } - } else { - return tryToIdentifyByName( - io::AuthorityFactory::ObjectType::PROJECTED_CRS); - } - } - - auto compoundCrs = dynamic_cast(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(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(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, context, componentExtent); - if (extentOut && componentExtent) - extentOut = extentOut->intersection( - NN_NO_CHECK(componentExtent)); - else if (componentExtent) - extentOut = componentExtent; - } - } - } - } - return crs; -} - -// --------------------------------------------------------------------------- - -/** \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 -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; - - metadata::ExtentPtr sourceCRSExtent; - auto l_resolvedSourceCRS = - getResolvedCRS(l_sourceCRS, context, sourceCRSExtent); - metadata::ExtentPtr targetCRSExtent; - auto l_resolvedTargetCRS = - getResolvedCRS(l_targetCRS, context, 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(); - } - } - - 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()); -} - -// --------------------------------------------------------------------------- - -//! @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(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 &accuracies) { - auto method = OperationMethod::create( - util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, - "PROJ-based operation method: " + PROJString), - std::vector{}); - auto op = PROJBasedOperation::nn_make_shared(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 &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{}); - auto op = PROJBasedOperation::nn_make_shared(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( - 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(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 ¶mValue : 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(*this); - op->assignSelf(op); - op->setCRSs(this, false); - return util::nn_static_pointer_cast(op); -} - -// --------------------------------------------------------------------------- - -std::set -PROJBasedOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext, - bool considerKnownGridsAsAvailable) const { - std::set 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 -Nru proj-7.2.0/src/iso19111/crs.cpp proj-7.2.1/src/iso19111/crs.cpp --- proj-7.2.0/src/iso19111/crs.cpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/src/iso19111/crs.cpp 2020-12-21 16:29:10.000000000 +0000 @@ -533,8 +533,12 @@ auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), authority == "any" ? std::string() : authority); + metadata::ExtentPtr extentResolved(extent); + if (!extent) { + getResolvedCRS(thisAsCRS, authFactory, extentResolved); + } auto ctxt = operation::CoordinateOperationContext::create( - authFactory, extent, 0.0); + authFactory, extentResolved, 0.0); ctxt->setAllowUseIntermediateCRS(allowIntermediateCRSUse); // ctxt->setSpatialCriterion( // operation::CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); @@ -1616,6 +1620,34 @@ vertCRSList.front()->_exportToWKT(formatter); return true; } + +// --------------------------------------------------------------------------- + +// Try to format a Geographic/ProjectedCRS 3D CRS as a +// GEOGCS[]/PROJCS[],VERTCS["Ellipsoid (metre)",DATUM["Ellipsoid",2002],...] +static bool exportAsWKT1CompoundCRSWithEllipsoidalHeight( + const CRSNNPtr &base2DCRS, + const cs::CoordinateSystemAxisNNPtr &verticalAxis, + io::WKTFormatter *formatter) { + std::string verticalCRSName = "Ellipsoid ("; + verticalCRSName += verticalAxis->unit().name(); + verticalCRSName += ')'; + auto vertDatum = datum::VerticalReferenceFrame::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, "Ellipsoid") + .set("VERT_DATUM_TYPE", "2002")); + auto vertCRS = VerticalCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + verticalCRSName), + vertDatum.as_nullable(), nullptr, + cs::VerticalCS::create(util::PropertyMap(), verticalAxis)); + formatter->startNode(io::WKTConstants::COMPD_CS, false); + formatter->addQuotedString(base2DCRS->nameStr() + " + " + verticalCRSName); + base2DCRS->_exportToWKT(formatter); + vertCRS->_exportToWKT(formatter); + formatter->endNode(); + return true; +} //! @endcond // --------------------------------------------------------------------------- @@ -1683,6 +1715,13 @@ return; } + if (formatter->isAllowedEllipsoidalHeightAsVerticalCRS()) { + if (exportAsWKT1CompoundCRSWithEllipsoidalHeight( + geogCRS2D, axisList[2], formatter)) { + return; + } + } + io::FormattingException::Throw( "WKT1 does not support Geographic 3D CRS."); } @@ -1922,11 +1961,19 @@ bool GeodeticCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { + if (other == nullptr || !util::isOfExactType(*other)) { + return false; + } + return _isEquivalentToNoTypeCheck(other, criterion, dbContext); +} + +bool GeodeticCRS::_isEquivalentToNoTypeCheck( + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { const auto standardCriterion = getStandardCriterion(criterion); - auto otherGeodCRS = dynamic_cast(other); + // TODO test velocityModel - return otherGeodCRS != nullptr && - SingleCRS::baseIsEquivalentTo(other, standardCriterion, dbContext); + return SingleCRS::baseIsEquivalentTo(other, standardCriterion, dbContext); } //! @endcond @@ -2482,12 +2529,13 @@ bool GeographicCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { - auto otherGeogCRS = dynamic_cast(other); - if (otherGeogCRS == nullptr) { + if (other == nullptr || !util::isOfExactType(*other)) { return false; } + const auto standardCriterion = getStandardCriterion(criterion); - if (GeodeticCRS::_isEquivalentTo(other, standardCriterion, dbContext)) { + if (GeodeticCRS::_isEquivalentToNoTypeCheck(other, standardCriterion, + dbContext)) { return true; } if (criterion != @@ -2506,7 +2554,29 @@ cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ? cs::EllipsoidalCS::createLatitudeLongitude(unit) : cs::EllipsoidalCS::createLongitudeLatitude(unit)) - ->GeodeticCRS::_isEquivalentTo(other, standardCriterion, dbContext); + ->GeodeticCRS::_isEquivalentToNoTypeCheck(other, standardCriterion, + dbContext); + } + if (axisOrder == + cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP || + axisOrder == + cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP) { + const auto &angularUnit = coordinateSystem()->axisList()[0]->unit(); + const auto &linearUnit = coordinateSystem()->axisList()[2]->unit(); + return GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + nameStr()), + datum(), datumEnsemble(), + axisOrder == cs::EllipsoidalCS::AxisOrder:: + LONG_EAST_LAT_NORTH_HEIGHT_UP + ? cs::EllipsoidalCS:: + createLatitudeLongitudeEllipsoidalHeight( + angularUnit, linearUnit) + : cs::EllipsoidalCS:: + createLongitudeLatitudeEllipsoidalHeight( + angularUnit, linearUnit)) + ->GeodeticCRS::_isEquivalentToNoTypeCheck(other, standardCriterion, + dbContext); } return false; } @@ -3605,6 +3675,14 @@ return; } + if (!formatter->useESRIDialect() && + formatter->isAllowedEllipsoidalHeightAsVerticalCRS()) { + if (exportAsWKT1CompoundCRSWithEllipsoidalHeight( + projCRS2D, axisList[2], formatter)) { + return; + } + } + io::FormattingException::Throw( "Projected 3D CRS can only be exported since WKT2:2019"); } @@ -3885,8 +3963,7 @@ bool ProjectedCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { - auto otherProjCRS = dynamic_cast(other); - return otherProjCRS != nullptr && + return other != nullptr && util::isOfExactType(*other) && DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } @@ -5111,7 +5188,8 @@ " (with Greenwich prime meridian)"), sourceGeographicCRS->datumNonNull(nullptr)->ellipsoid(), util::optional(), datum::PrimeMeridian::GREENWICH), - sourceGeographicCRS->coordinateSystem()); + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)); } std::string transformationName = transformationSourceCRS->nameStr(); transformationName += " to WGS84"; diff -Nru proj-7.2.0/src/iso19111/datum.cpp proj-7.2.1/src/iso19111/datum.cpp --- proj-7.2.0/src/iso19111/datum.cpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/src/iso19111/datum.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -352,6 +352,23 @@ if (!(isWKT2 && formatter->primeMeridianOmittedIfGreenwich() && l_name == "Greenwich")) { formatter->startNode(io::WKTConstants::PRIMEM, !identifiers().empty()); + + if (formatter->useESRIDialect()) { + bool aliasFound = false; + const auto &dbContext = formatter->databaseContext(); + if (dbContext) { + auto l_alias = dbContext->getAliasFromOfficialName( + l_name, "prime_meridian", "ESRI"); + if (!l_alias.empty()) { + l_name = l_alias; + aliasFound = true; + } + } + if (!aliasFound) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + } + } + formatter->addQuotedString(l_name); const auto &l_long = longitude(); if (formatter->primeMeridianInDegree()) { diff -Nru proj-7.2.0/src/iso19111/factory.cpp proj-7.2.1/src/iso19111/factory.cpp --- proj-7.2.0/src/iso19111/factory.cpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/src/iso19111/factory.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -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 @@ -94,6 +96,12 @@ #define GEOG_3D_SINGLE_QUOTED "'geographic 3D'" #define GEOCENTRIC_SINGLE_QUOTED "'geocentric'" +// See data/sql/metadata.sql for the semantics of those constants +constexpr int DATABASE_LAYOUT_VERSION_MAJOR = 1; +// If the code depends on the new additions, then DATABASE_LAYOUT_VERSION_MINOR +// must be incremented. +constexpr int DATABASE_LAYOUT_VERSION_MINOR = 0; + // --------------------------------------------------------------------------- struct SQLValues { @@ -270,6 +278,8 @@ lru11::Cache> cacheAliasNames_{ CACHE_SIZE}; + void checkDatabaseLayout(); + static void insertIntoCache(LRUCacheOfObjects &cache, const std::string &code, const util::BaseObjectPtr &obj); @@ -548,6 +558,61 @@ // --------------------------------------------------------------------------- +void DatabaseContext::Private::checkDatabaseLayout() { + auto res = run("SELECT key, value FROM metadata WHERE key IN " + "('DATABASE.LAYOUT.VERSION.MAJOR', " + "'DATABASE.LAYOUT.VERSION.MINOR')"); + if (res.size() != 2) { + // The database layout of PROJ 7.2 that shipped with EPSG v10.003 is + // at the time of writing still compatible of the one we support. + static_assert( + // cppcheck-suppress knownConditionTrueFalse + DATABASE_LAYOUT_VERSION_MAJOR == 1 && + // cppcheck-suppress knownConditionTrueFalse + DATABASE_LAYOUT_VERSION_MINOR == 0, + "remove that assertion and below lines next time we upgrade " + "database structure"); + res = run("SELECT 1 FROM metadata WHERE key = 'EPSG.VERSION' AND " + "value = 'v10.003'"); + if (!res.empty()) { + return; + } + + throw FactoryException( + databasePath_ + + " lacks DATABASE.LAYOUT.VERSION.MAJOR / " + "DATABASE.LAYOUT.VERSION.MINOR " + "metadata. It comes from another PROJ installation."); + } + int nMajor = 0; + int nMinor = 0; + for (const auto &row : res) { + if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") { + nMajor = atoi(row[1].c_str()); + } else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") { + nMinor = atoi(row[1].c_str()); + } + } + if (nMajor != DATABASE_LAYOUT_VERSION_MAJOR) { + throw FactoryException(databasePath_ + + " contains DATABASE.LAYOUT.VERSION.MAJOR = " + + toString(nMajor) + " whereas " + + toString(DATABASE_LAYOUT_VERSION_MAJOR) + + " is expected. " + "It comes from another PROJ installation."); + } + if (nMinor < DATABASE_LAYOUT_VERSION_MINOR) { + throw FactoryException(databasePath_ + + " contains DATABASE.LAYOUT.VERSION.MINOR = " + + toString(nMinor) + " whereas a number >= " + + toString(DATABASE_LAYOUT_VERSION_MINOR) + + " is expected. " + "It comes from another PROJ installation."); + } +} + +// --------------------------------------------------------------------------- + void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) { assert(sqlite_handle); @@ -865,6 +930,7 @@ if (!auxiliaryDatabasePaths.empty()) { dbCtx->getPrivate()->attachExtraDatabases(auxiliaryDatabasePaths); } + dbCtx->getPrivate()->checkDatabaseLayout(); return dbCtx; } @@ -6019,6 +6085,8 @@ auto sqlRes = d->run(sql, params); bool isFirst = true; bool firstIsDeprecated = false; + bool foundExactMatch = false; + std::size_t hashCodeFirstMatch = 0; for (const auto &row : sqlRes) { const auto &name = row[3]; if (approximateMatch) { @@ -6082,11 +6150,38 @@ } throw std::runtime_error("Unsupported table_name"); }; - res.emplace_back(PairObjectName(getObject(table_name, code), name)); + const auto obj = getObject(table_name, code); + if (metadata::Identifier::canonicalizeName(obj->nameStr()) == + canonicalizedSearchedName) { + foundExactMatch = true; + } + + const auto objPtr = obj.get(); + if (res.empty()) { + hashCodeFirstMatch = typeid(*objPtr).hash_code(); + } else if (hashCodeFirstMatch != typeid(*objPtr).hash_code()) { + hashCodeFirstMatch = 0; + } + + res.emplace_back(PairObjectName(obj, name)); if (limitResultCount > 0 && res.size() == limitResultCount) { break; } } + + // If we found a name that is an exact match, and all objects have the + // same type, and we are not in approximate mode, only keep the objet(s) + // with the exact name match. + if (foundExactMatch && hashCodeFirstMatch != 0 && !approximateMatch) { + std::list resTmp; + for (const auto &pair : res) { + if (metadata::Identifier::canonicalizeName( + pair.first->nameStr()) == canonicalizedSearchedName) { + resTmp.emplace_back(pair); + } + } + res = std::move(resTmp); + } } auto sortLambda = [](const PairObjectName &a, const PairObjectName &b) { diff -Nru proj-7.2.0/src/iso19111/io.cpp proj-7.2.1/src/iso19111/io.cpp --- proj-7.2.0/src/iso19111/io.cpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/src/iso19111/io.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -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" @@ -140,6 +144,7 @@ bool primeMeridianInDegree_ = false; bool use2019Keywords_ = false; bool useESRIDialect_ = false; + bool allowEllipsoidalHeightAsVerticalCRS_ = false; OutputAxisRule outputAxis_ = WKTFormatter::OutputAxisRule::YES; }; Params params_{}; @@ -251,6 +256,8 @@ * * The default is strict mode, in which case a FormattingException can be * thrown. + * In non-strict mode, a Geographic 3D CRS can be for example exported as + * WKT1_GDAL with 3 axes, whereas this is normally not allowed. */ WKTFormatter &WKTFormatter::setStrict(bool strictIn) noexcept { d->params_.strict_ = strictIn; @@ -264,6 +271,32 @@ // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress + +/** \brief Set whether the formatter should export, in WKT1, a Geographic or + * Projected 3D CRS as a compound CRS whose vertical part represents an + * ellipsoidal height. + */ +WKTFormatter & +WKTFormatter::setAllowEllipsoidalHeightAsVerticalCRS(bool allow) noexcept { + d->params_.allowEllipsoidalHeightAsVerticalCRS_ = allow; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether the formatter should export, in WKT1, a Geographic or + * Projected 3D CRS as a compound CRS whose vertical part represents an + * ellipsoidal height. + */ +bool WKTFormatter::isAllowedEllipsoidalHeightAsVerticalCRS() const noexcept { + return d->params_.allowEllipsoidalHeightAsVerticalCRS_; +} + +//! @endcond + +// --------------------------------------------------------------------------- + /** Returns the WKT string from the formatter. */ const std::string &WKTFormatter::toString() const { if (d->indentLevel_ > 0 || d->level_ > 0) { @@ -1979,14 +2012,80 @@ try { double angleValue = asDouble(children[1]); - // Correct for GDAL WKT1 departure + // Correct for GDAL WKT1 and WKT1-ESRI departure if (name == "Paris" && std::fabs(angleValue - 2.33722917) < 1e-8 && - unit == UnitOfMeasure::GRAD) { + unit._isEquivalentTo(UnitOfMeasure::GRAD, + util::IComparable::Criterion::EQUIVALENT)) { angleValue = 2.5969213; + } else { + static const struct { + const char *name; + int deg; + int min; + double sec; + } primeMeridiansDMS[] = { + {"Lisbon", -9, 7, 54.862}, {"Bogota", -74, 4, 51.3}, + {"Madrid", -3, 41, 14.55}, {"Rome", 12, 27, 8.4}, + {"Bern", 7, 26, 22.5}, {"Jakarta", 106, 48, 27.79}, + {"Ferro", -17, 40, 0}, {"Brussels", 4, 22, 4.71}, + {"Stockholm", 18, 3, 29.8}, {"Athens", 23, 42, 58.815}, + {"Oslo", 10, 43, 22.5}, {"Paris RGS", 2, 20, 13.95}, + {"Paris_RGS", 2, 20, 13.95}}; + + // Current epsg.org output may use the EPSG:9110 "sexagesimal DMS" + // unit and a DD.MMSSsss value, but this will likely be changed to + // use decimal degree. + // Or WKT1 may for example use the Paris RGS decimal degree value + // but with a GEOGCS with UNIT["Grad"] + for (const auto &pmDef : primeMeridiansDMS) { + if (name == pmDef.name) { + double dmsAsDecimalValue = + (pmDef.deg >= 0 ? 1 : -1) * + (std::abs(pmDef.deg) + pmDef.min / 100. + + pmDef.sec / 10000.); + double dmsAsDecimalDegreeValue = + (pmDef.deg >= 0 ? 1 : -1) * + (std::abs(pmDef.deg) + pmDef.min / 60. + + pmDef.sec / 3600.); + if (std::fabs(angleValue - dmsAsDecimalValue) < 1e-8 || + std::fabs(angleValue - dmsAsDecimalDegreeValue) < + 1e-8) { + angleValue = dmsAsDecimalDegreeValue; + unit = UnitOfMeasure::DEGREE; + } + break; + } + } + } + + auto &properties = buildProperties(node); + if (dbContext_ && esriStyle_) { + std::string outTableName; + std::string codeFromAlias; + std::string authNameFromAlias; + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto officialName = authFactory->getOfficialNameFromAlias( + name, "prime_meridian", "ESRI", false, outTableName, + authNameFromAlias, codeFromAlias); + if (!officialName.empty()) { + properties.set(IdentifiedObject::NAME_KEY, officialName); + if (!authNameFromAlias.empty()) { + auto identifiers = ArrayOfBaseObject::create(); + identifiers->add(Identifier::create( + codeFromAlias, + PropertyMap() + .set(Identifier::CODESPACE_KEY, authNameFromAlias) + .set(Identifier::AUTHORITY_KEY, + authNameFromAlias))); + properties.set(IdentifiedObject::IDENTIFIERS_KEY, + identifiers); + } + } } Angle angle(angleValue, unit); - return PrimeMeridian::create(buildProperties(node), angle); + return PrimeMeridian::create(properties, angle); } catch (const std::exception &e) { throw buildRethrow(__FUNCTION__, e); } @@ -2098,9 +2197,15 @@ return false; }; - if (name == "WGS_1984") { + // Remap GDAL WGS_1984 to EPSG v9 "World Geodetic System 1984" official + // name. + // Also remap EPSG v10 datum ensemble names to non-ensemble EPSG v9 + if (name == "WGS_1984" || name == "World Geodetic System 1984 ensemble") { properties.set(IdentifiedObject::NAME_KEY, GeodeticReferenceFrame::EPSG_6326->nameStr()); + } else if (name == "European Terrestrial Reference System 1989 ensemble") { + properties.set(IdentifiedObject::NAME_KEY, + "European Terrestrial Reference System 1989"); } else if (starts_with(name, "D_")) { esriStyle_ = true; const char *tableNameForAlias = nullptr; @@ -2734,6 +2839,9 @@ throw ParsingException("Missing DATUM or ENSEMBLE node"); } + // Do that now so that esriStyle_ can be set before buildPrimeMeridian() + auto props = buildProperties(node); + auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC); auto &csNode = nodeP->lookForChild(WKTConstants::CS_); @@ -2771,7 +2879,6 @@ angularUnit = primeMeridian->longitude().unit(); } - auto props = buildProperties(node); addExtensionProj4ToProp(nodeP, props); // No explicit AXIS node ? (WKT1) @@ -2790,6 +2897,32 @@ .as_nullable() : nullptr; auto cs = buildCS(csNode, node, angularUnit); + + // If there's no CS[] node, typically for a BASEGEODCRS of a projected CRS, + // in a few rare cases, this might be a Geocentric CRS, and thus a + // Cartesian CS, and not the ellipsoidalCS we assumed above. The only way + // to figure that is to resolve the CRS from its code... + if (isNull(csNode) && dbContext_ && + ci_equal(nodeName, WKTConstants::BASEGEODCRS)) { + const auto &nodeChildren = nodeP->children(); + for (const auto &subNode : nodeChildren) { + const auto &subNodeName(subNode->GP()->value()); + if (ci_equal(subNodeName, WKTConstants::ID) || + ci_equal(subNodeName, WKTConstants::AUTHORITY)) { + auto id = buildId(subNode, true, false); + if (id) { + try { + auto authFactory = AuthorityFactory::create( + NN_NO_CHECK(dbContext_), *id->codeSpace()); + auto dbCRS = authFactory->createGeodeticCRS(id->code()); + cs = dbCRS->coordinateSystem(); + } catch (const util::Exception &) { + } + } + } + } + } + auto ellipsoidalCS = nn_dynamic_pointer_cast(cs); if (ellipsoidalCS) { if (ci_equal(nodeName, WKTConstants::GEOCCS)) { @@ -2940,7 +3073,8 @@ const UnitOfMeasure &defaultAngularUnit) { UnitOfMeasure unit; // scale must be first because of 'Scale factor on pseudo standard parallel' - if (ci_find(paramName, "scale") != std::string::npos) { + if (ci_find(paramName, "scale") != std::string::npos || + ci_find(paramName, "scaling factor") != std::string::npos) { unit = UnitOfMeasure::SCALE_UNITY; } else if (ci_find(paramName, "latitude") != std::string::npos || ci_find(paramName, "longitude") != std::string::npos || @@ -3872,8 +4006,16 @@ return createPseudoMercator(props, NN_NO_CHECK(cartesianCS)); } - auto linearUnit = buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR); - auto angularUnit = baseGeodCRS->coordinateSystem()->axisList()[0]->unit(); + // For WKT2, if there is no explicit parameter unit, use metre for linear + // units and degree for angular units + auto linearUnit = + !isNull(conversionNode) + ? UnitOfMeasure::METRE + : buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR); + auto angularUnit = + !isNull(conversionNode) + ? UnitOfMeasure::DEGREE + : baseGeodCRS->coordinateSystem()->axisList()[0]->unit(); auto conversion = !isNull(conversionNode) @@ -4097,7 +4239,8 @@ sourceGeographicCRS->datum()->ellipsoid(), util::optional(), datum::PrimeMeridian::GREENWICH), - sourceGeographicCRS->coordinateSystem()) + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)) .as_nullable(); } } else { @@ -4941,6 +5084,10 @@ TransformationNNPtr buildTransformation(const json &j); ConcatenatedOperationNNPtr buildConcatenatedOperation(const json &j); + void buildGeodeticDatumOrDatumEnsemble(const json &j, + GeodeticReferenceFramePtr &datum, + DatumEnsemblePtr &datumEnsemble); + static util::optional getAnchor(const json &j) { util::optional anchor; if (j.contains("anchor")) { @@ -5400,9 +5547,9 @@ // --------------------------------------------------------------------------- -GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) { - GeodeticReferenceFramePtr datum; - DatumEnsemblePtr datumEnsemble; +void JSONParser::buildGeodeticDatumOrDatumEnsemble( + const json &j, GeodeticReferenceFramePtr &datum, + DatumEnsemblePtr &datumEnsemble) { if (j.contains("datum")) { auto datumJ = getObject(j, "datum"); datum = util::nn_dynamic_pointer_cast( @@ -5415,6 +5562,14 @@ datumEnsemble = buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable(); } +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) { + GeodeticReferenceFramePtr datum; + DatumEnsemblePtr datumEnsemble; + buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble); auto csJ = getObject(j, "coordinate_system"); auto ellipsoidalCS = util::nn_dynamic_pointer_cast(buildCS(csJ)); @@ -5428,12 +5583,9 @@ // --------------------------------------------------------------------------- GeodeticCRSNNPtr JSONParser::buildGeodeticCRS(const json &j) { - auto datumJ = getObject(j, "datum"); - if (getType(datumJ) != "GeodeticReferenceFrame") { - throw ParsingException("Unsupported type for datum."); - } - auto datum = buildGeodeticReferenceFrame(datumJ); + GeodeticReferenceFramePtr datum; DatumEnsemblePtr datumEnsemble; + buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble); auto csJ = getObject(j, "coordinate_system"); auto cs = buildCS(csJ); auto props = buildProperties(j); @@ -5468,7 +5620,13 @@ // --------------------------------------------------------------------------- ProjectedCRSNNPtr JSONParser::buildProjectedCRS(const json &j) { - auto baseCRS = buildGeographicCRS(getObject(j, "base_crs")); + auto jBaseCRS = getObject(j, "base_crs"); + auto jBaseCS = getObject(jBaseCRS, "coordinate_system"); + auto baseCS = buildCS(jBaseCS); + auto baseCRS = dynamic_cast(baseCS.get()) != nullptr + ? util::nn_static_pointer_cast( + buildGeographicCRS(jBaseCRS)) + : buildGeodeticCRS(jBaseCRS); auto csJ = getObject(j, "coordinate_system"); auto cartesianCS = util::nn_dynamic_pointer_cast(buildCS(csJ)); if (!cartesianCS) { @@ -9460,8 +9618,8 @@ std::string methodName = "PROJ " + step.name; for (const auto ¶m : step.paramValues) { if (is_in_stringlist(param.key, - "wktext,no_defs,datum,ellps,a,b,R,towgs84," - "nadgrids,geoidgrids," + "wktext,no_defs,datum,ellps,a,b,R,f,rf," + "towgs84,nadgrids,geoidgrids," "units,to_meter,vunits,vto_meter,type")) { continue; } diff -Nru proj-7.2.0/src/iso19111/operation/concatenatedoperation.cpp proj-7.2.1/src/iso19111/operation/concatenatedoperation.cpp --- proj-7.2.0/src/iso19111/operation/concatenatedoperation.cpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/concatenatedoperation.cpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,710 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +using namespace NS_PROJ::internal; + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ConcatenatedOperation::Private { + std::vector operations_{}; + bool computedName_ = false; + + explicit Private(const std::vector &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(*(other.d))) {} +//! @endcond + +// --------------------------------------------------------------------------- + +ConcatenatedOperation::ConcatenatedOperation( + const std::vector &operationsIn) + : CoordinateOperation(), d(internal::make_unique(operationsIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Return the operation steps of the concatenated operation. + * + * @return the operation steps. + */ +const std::vector & +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 &operationsIn, + const std::vector + &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( + 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 &operationsInOut) { + + // Set of heuristics to assign CRS to steps, and possibly reverse them. + + const auto isGeographic = [](const crs::CRS *crs) -> bool { + return dynamic_cast(crs) != nullptr; + }; + + const auto isGeocentric = [](const crs::CRS *crs) -> bool { + auto geodCRS = dynamic_cast(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(op.get()); + if (conv && i == 0 && !l_sourceCRS && !l_targetCRS) { + auto derivedCRS = + dynamic_cast(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(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(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( + 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 &operationsIn, + bool checkExtent) // throw InvalidOperation +{ + util::PropertyMap properties; + + if (operationsIn.size() == 1) { + return operationsIn[0]; + } + + std::vector flattenOps; + bool hasBallparkTransformation = false; + for (const auto &subOp : operationsIn) { + hasBallparkTransformation |= subOp->hasBallparkTransformation(); + auto subOpConcat = + dynamic_cast(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 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 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 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 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(*this); + std::vector 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(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(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 ConcatenatedOperation::gridsNeeded( + const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const { + std::set 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 -Nru proj-7.2.0/src/iso19111/operation/conversion.cpp proj-7.2.1/src/iso19111/operation/conversion.cpp --- proj-7.2.0/src/iso19111/operation/conversion.cpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/conversion.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -0,0 +1,3916 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +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 &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(*this); + conv->assignSelf(conv); + conv->setCRSs(this, false); + return conv; +} + +CoordinateOperationNNPtr Conversion::_shallowClone() const { + return util::nn_static_pointer_cast(shallowClone()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ConversionNNPtr +Conversion::alterParametersLinearUnit(const common::UnitOfMeasure &unit, + bool convertToNewUnit) const { + + std::vector newValues; + bool changesDone = false; + for (const auto &genOpParamvalue : parameterValues()) { + bool updated = false; + auto opParamvalue = dynamic_cast( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶mValue = 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(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 + &values) // throw InvalidOperation +{ + if (methodIn->parameters().size() != values.size()) { + throw InvalidOperation( + "Inconsistent number of parameters and parameter values"); + } + auto conv = Conversion::nn_make_shared(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 ¶meters, + const std::vector &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 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 &values) { + + std::vector 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 &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 &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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLat, + const common::Angle ¢erLong, 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 <= 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 ¢erLat, + const common::Angle ¢erLong, 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 <= 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 <= 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 ¢erLat, + const common::Angle ¢erLong, 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 + *<= 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLong, + 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 <= 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 <= 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 ¢erLong, + 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 ¢erLong, + 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 <= 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLat, + const common::Angle ¢erLong, 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 >=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 >=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 <= + *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 ¢erLong, + 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 ¢erLong, + 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLong, + 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLong, + 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLong, 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLong, + 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 ¢erLat, + const common::Angle ¢erLong, 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 ¢erLong, + 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: + *
      + *
    • it is a 2D-only method, ignoring the ellipsoidal height of the point to + * project.
    • + *
    • it has only a spherical development.
    • + *
    • the height of the topocentric origin is ignored, and thus assumed to be + * 0.
    • + *
    + * + * 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: + *
    + * +proj=ob_tran +o_proj=longlat +o_lon_p=-rotationAngle
    + *                               +o_lat_p=-southPoleLatInUnrotatedCRS
    + *                               +lon_0=southPoleLongInUnrotatedCRS
    + * 
    + * + * 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(forwardOperation_)); +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr +InverseConversion::create(const ConversionNNPtr &forward) { + auto conv = util::nn_make_shared(forward); + conv->assignSelf(conv); + return conv; +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr InverseConversion::_shallowClone() const { + auto op = InverseConversion::nn_make_shared( + inverseAsConversion()->shallowClone()); + op->assignSelf(op); + op->setCRSs(this, false); + return util::nn_static_pointer_cast(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(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: + *
      + *
    • EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to + * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)
    • + *
    • EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to + * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)
    • + *
    • EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to + * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP
    • + *
    • EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to + * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP
    • + *
    + * + * @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(shared_from_this()); + } + + auto geogCRS = dynamic_cast(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( + genOpParamvalue.get()); + if (opParamvalue && + opParamvalue->parameter()->getEPSGCode() == + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) { + const auto ¶mValue = 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( + 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 ¶mValue = 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(sourceCRS.get()); + if (!geogCRS) { + return false; + } + + std::string units("m"); + auto targetCRS = conv->targetCRS(); + auto targetProjCRS = + dynamic_cast(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(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( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶mValue = 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; + + auto l_sourceCRS = sourceCRS(); + if (!formatter->getCRSExport() && l_sourceCRS && applySourceCRSModifiers) { + + crs::CRS *horiz = l_sourceCRS.get(); + const auto compound = dynamic_cast(horiz); + if (compound) { + const auto &components = compound->componentReferenceSystems(); + if (!components.empty()) { + horiz = components.front().get(); + } + } + + auto geogCRS = dynamic_cast(horiz); + if (geogCRS) { + formatter->setOmitProjLongLatIfPossible(true); + formatter->startInversion(); + geogCRS->_exportToPROJString(formatter); + formatter->stopInversion(); + formatter->setOmitProjLongLatIfPossible(false); + } + + auto projCRS = dynamic_cast(horiz); + 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; + } + + 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( + 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(horiz); + if (compound) { + const auto &components = + compound->componentReferenceSystems(); + if (!components.empty()) { + horiz = components.front().get(); + } + } + + auto geogCRS = + dynamic_cast(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(horiz); + if (compound) { + const auto &components = compound->componentReferenceSystems(); + if (!components.empty()) { + horiz = components.front().get(); + } + } + + if (!bEllipsoidParametersDone) { + auto targetGeogCRS = horiz->extractGeographicCRS(); + if (targetGeogCRS) { + if (formatter->getCRSExport()) { + targetGeogCRS->addDatumInfoToPROJString(formatter); + } else { + targetGeogCRS->ellipsoid()->_exportToPROJString(formatter); + targetGeogCRS->primeMeridian()->_exportToPROJString( + formatter); + } + } + } + + auto projCRS = dynamic_cast(horiz); + if (projCRS) { + formatter->pushOmitZUnitConversion(); + projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound); + formatter->popOmitZUnitConversion(); + } + + auto derivedGeographicCRS = + dynamic_cast(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( + 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(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(*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 -Nru proj-7.2.0/src/iso19111/operation/coordinateoperationfactory.cpp proj-7.2.1/src/iso19111/operation/coordinateoperationfactory.cpp --- proj-7.2.0/src/iso19111/operation/coordinateoperationfactory.cpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/coordinateoperationfactory.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -0,0 +1,5236 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +// #define TRACE_CREATE_OPERATIONS +// #define DEBUG_SORT +// #define DEBUG_CONCATENATED_OPERATION +#if defined(DEBUG_SORT) || defined(DEBUG_CONCATENATED_OPERATION) +#include + +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( + 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> + intermediateCRSAuthCodes_{}; + bool discardSuperseded_ = true; + bool allowBallpark_ = true; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateOperationContext::~CoordinateOperationContext() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateOperationContext::CoordinateOperationContext() + : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +/** \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> + &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> & +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()); + 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::list>> + cacheNameToCRS{}; + + Context(const metadata::ExtentPtr &extent1In, + const metadata::ExtentPtr &extent2In, + const CoordinateOperationContextNNPtr &contextIn) + : extent1(extent1In), extent2(extent2In), context(contextIn) {} + }; + + static std::vector + 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> &ids); + + static std::vector findOpsInRegistryDirect( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, bool &resNonEmptyBeforeFiltering); + + static std::vector + findOpsInRegistryDirectTo(const crs::CRSNNPtr &targetCRS, + Private::Context &context); + + static std::vector + 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 &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 &res); + + static std::vector + createOperationsGeogToVertFromGeoid(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, + const crs::VerticalCRS *vertDst, + Context &context); + + static std::vector + createOperationsGeogToVertWithIntermediateVert( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::VerticalCRS *vertDst, Context &context); + + static std::vector + 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 &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 &res); + + static void createOperationsDerivedTo( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::DerivedCRS *derivedSrc, + std::vector &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 &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 &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 &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 &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 &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 &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 &res); + + static void createOperationsToGeod( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeodeticCRS *geodDst, + std::vector &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 &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 &res); + + static std::vector createOperationsGeogToGeog( + std::vector &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 &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 &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 ↦ + + explicit SortFunction(const std::map &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(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(op.get()); + size_t stepCount = 1; + if (concat) { + stepCount = 0; + for (const auto &subOp : concat->operations()) { + if (dynamic_cast(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 &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 &getRes() { return res; } + + // ---------------------------------------------------------------------- + private: + const std::vector &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 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 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 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 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 setPROJPlusExtent; + std::vector 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 +filterAndSort(const std::vector &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(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(resFiltered.size()))); +#endif + return resFiltered; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +// Apply the inverse() method on all elements of the input list +static std::vector +applyInverse(const std::vector &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> &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 allowedObjects; + auto geogCRS = dynamic_cast(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.get())) { + allowedObjects.push_back( + io::AuthorityFactory::ObjectType::PROJECTED_CRS); + } else if (dynamic_cast(crs.get())) { + allowedObjects.push_back( + io::AuthorityFactory::ObjectType::VERTICAL_CRS); + } + if (!allowedObjects.empty()) { + + const std::pair 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 +getCandidateAuthorities(const io::AuthorityFactoryPtr &authFactory, + const std::string &srcAuthName, + const std::string &targetAuthName) { + const auto &authFactoryName = authFactory->getAuthority(); + std::vector 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 +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> sourceIds; + std::list> 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 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(res.size())) + " to " + + toString(static_cast(resFiltered.size()))); +#endif + return resFiltered; + } + } + } + } + return std::vector(); +} + +// --------------------------------------------------------------------------- + +// Look in the authority registry for operations to targetCRS +std::vector +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> 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(res.size())) + " to " + + toString(static_cast(resFiltered.size()))); +#endif + return resFiltered; + } + } + } + return std::vector(); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// Look in the authority registry for operations from sourceCRS to targetCRS +// using an intermediate pivot +std::vector +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> sourceIds; + std::list> 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 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(), + 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(sourceCRS.get()) && + dynamic_cast(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(), + 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(res.size())) + " to " + + toString(static_cast(resFiltered.size()))); +#endif + return resFiltered; + } + } + } + return std::vector(); +} +//! @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(sourceCRS.get()); + const crs::GeographicCRS *geogDst = + dynamic_cast(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 accuracies; + if (isSameDatum) { + accuracies.emplace_back(metadata::PositionalAccuracy::create("0")); + } + + if (dynamic_cast(sourceCRS.get()) + ->coordinateSystem() + ->axisList() + .size() == 3 || + dynamic_cast(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>::~nn() = default; +template<> nn>::~nn() = default; +template<> nn>::~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 &accuracies, + bool hasBallparkTransformation) { + return util::nn_static_pointer_cast( + 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( + util::nn_dynamic_pointer_cast(geodSrc), + util::nn_dynamic_pointer_cast(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 &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(targetCRS); + assert(geogDst); + + auto exportable = util::nn_make_shared( + 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{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 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( + opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS, + interpolationGeogCRS); + + std::vector 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 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 +CoordinateOperationFactory::Private::createOperationsGeogToGeog( + std::vector &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 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(), 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::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(), 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::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(op.get()); + if (concatenated) { + for (const auto &subOp : concatenated->operations()) { + if (hasIdentifiers(subOp)) { + return true; + } + } + } + return false; +} + +// --------------------------------------------------------------------------- + +static std::vector +findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory, + const crs::GeodeticCRS *crs, + const datum::GeodeticReferenceFrame *datum) { + std::vector 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( + 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(co); + if (invCO) { + invCO->forwardOperation()->setCRSs(targetCRS, sourceCRS, nullptr); + } + + auto transf = dynamic_cast(co); + if (transf) { + transf->inverseAsTransformation()->setCRSs(targetCRS, sourceCRS, + nullptr); + } + + auto concat = dynamic_cast(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 &res) { + for (const auto &op : res) { + auto concat = dynamic_cast(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 &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(opSecond.get()); + if (so && isAxisOrderReversal(so->method()->getEPSGCode())) { + continue; + } + + std::vector 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( + 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( + 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( + 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 &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 +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 res; + + auto boundSrc = dynamic_cast(sourceCRS.get()); + auto boundDst = dynamic_cast(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(sourceCRS.get()); + auto geodDst = dynamic_cast(targetCRS.get()); + auto geogSrc = dynamic_cast(sourceCRS.get()); + auto geogDst = dynamic_cast(targetCRS.get()); + auto vertSrc = dynamic_cast(sourceCRS.get()); + auto vertDst = dynamic_cast(targetCRS.get()); + + // First look-up if the registry provide us with operations. + auto derivedSrc = dynamic_cast(sourceCRS.get()); + auto derivedDst = dynamic_cast(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(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(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 &res) { + + ENTER_FUNCTION(); + + auto sourceProjExportable = dynamic_cast( + boundSrc ? boundSrc : sourceCRS.get()); + auto targetProjExportable = dynamic_cast( + 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(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(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 &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 +findCandidateVertCRSForDatum(const io::AuthorityFactoryPtr &authFactory, + const datum::VerticalReferenceFrame *datum) { + std::vector 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( + match.get())); + } + } + } + return candidates; +} + +// --------------------------------------------------------------------------- + +std::vector +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(op->targetCRS().get()); + assert(targetOp); + if (targetOp->_isEquivalentTo( + vertDst, util::IComparable::Criterion::EQUIVALENT)) { + return op; + } + std::vector 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(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::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 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 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( + transf->sourceCRS().get()) && + dynamic_cast( + transf->targetCRS().get())) { + res.push_back(useTransf(transf)); + } else if (dynamic_cast( + transf->targetCRS().get()) && + dynamic_cast( + transf->sourceCRS().get())) { + res.push_back(useTransf(transf->inverse())); + } + } + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +std::vector CoordinateOperationFactory::Private:: + createOperationsGeogToVertWithIntermediateVert( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::VerticalCRS *vertDst, Private::Context &context) { + + ENTER_FUNCTION(); + + std::vector 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 CoordinateOperationFactory::Private:: + createOperationsGeogToVertWithAlternativeGeog( + const crs::CRSNNPtr & /*sourceCRS*/, // geographic CRS + const crs::CRSNNPtr &targetCRS, // vertical CRS + Private::Context &context) { + + ENTER_FUNCTION(); + + std::vector 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(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 &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( + 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 &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(geodSrc); + auto geogDst = dynamic_cast(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::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( + 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 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 &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 &res) { + + ENTER_FUNCTION(); + + const auto &hubSrc = boundSrc->hubCRS(); + auto hubSrcGeog = dynamic_cast(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( + geogCRSOfBaseOfBoundSrc); + if (derivedGeogCRS) { + auto baseCRS = std::dynamic_pointer_cast( + 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 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 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(boundSrc->baseCRS().get())) { + auto transfSrc = boundSrc->transformation()->sourceCRS(); + if (dynamic_cast(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(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 &res) { + + ENTER_FUNCTION(); + + auto baseSrcVert = + dynamic_cast(boundSrc->baseCRS().get()); + const auto &hubSrc = boundSrc->hubCRS(); + auto hubSrcVert = dynamic_cast(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 &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 &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( + 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 &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 = true; + 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 &res) { + + ENTER_FUNCTION(); + + // BoundCRS to BoundCRS of horizontal CRS using the same (geographic) hub + const auto &hubSrc = boundSrc->hubCRS(); + auto hubSrcGeog = dynamic_cast(hubSrc.get()); + const auto &hubDst = boundDst->hubCRS(); + auto hubDstGeog = dynamic_cast(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 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(boundSrc->baseCRS().get()); + auto baseOfBoundDstAsVertCRS = + dynamic_cast(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 +getOps(const CoordinateOperationNNPtr &op) { + auto concatenated = dynamic_cast(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(subOpA.get())) + continue; + if (subOpA->sourceCRS()->nameStr() == "unknown" || + subOpA->targetCRS()->nameStr() == "unknown") + continue; + for (const auto &subOpB : subOpsB) { + if (!dynamic_cast(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(verticalTransform.get()); + if (transformationVerticalTransform == nullptr) { + const auto concat = dynamic_cast( + 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(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( + 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( + 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 &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(componentsSrc[0].get()); + if (derivedHSrc) { + std::vector 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 horizTransforms; + auto srcGeogCRS = componentsSrc[0]->extractGeographicCRS(); + if (srcGeogCRS) { + horizTransforms = + createOperations(componentsSrc[0], targetCRS, context); + } + std::vector 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> + PairOfTransforms; + std::map + 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 srcToInterpOps; + std::vector 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 &res) { + + auto cs = cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( + common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE); + auto intermGeog3DCRS = + util::nn_static_pointer_cast(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 &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 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( + nn_interpTransformCRS.get())) { + interpolationGeogCRS = NN_NO_CHECK( + util::nn_dynamic_pointer_cast( + nn_interpTransformCRS)); + } + } else { + auto compSrc0BoundCrs = + dynamic_cast(componentsSrc[0].get()); + auto compDst0BoundCrs = + dynamic_cast(componentsDst[0].get()); + if (compSrc0BoundCrs && compDst0BoundCrs && + dynamic_cast( + compSrc0BoundCrs->hubCRS().get()) && + compSrc0BoundCrs->hubCRS()->_isEquivalentTo( + compDst0BoundCrs->hubCRS().get())) { + interpolationGeogCRS = NN_NO_CHECK( + util::nn_dynamic_pointer_cast( + 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 &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(componentsDst[0].get()); + if (compDst0BoundCrs) { + auto boundSrcHubAsGeogCRS = + dynamic_cast(boundSrc->hubCRS().get()); + auto compDst0BoundCrsHubAsGeogCRS = + dynamic_cast( + 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 +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(); + } + } + + 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()); +} + +// --------------------------------------------------------------------------- + +} // 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(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.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(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.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(resolvedCrs); + } + } catch (const std::exception &) { + } + } else { + return tryToIdentifyByName( + io::AuthorityFactory::ObjectType::PROJECTED_CRS); + } + } + + auto compoundCrs = dynamic_cast(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(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(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 -Nru proj-7.2.0/src/iso19111/operation/coordinateoperation_internal.hpp proj-7.2.1/src/iso19111/operation/coordinateoperation_internal.hpp --- proj-7.2.0/src/iso19111/operation/coordinateoperation_internal.hpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/coordinateoperation_internal.hpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,274 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 +#error This file should only be included from a PROJ cpp file +#endif + +#ifndef COORDINATEOPERATION_INTERNAL_HH_INCLUDED +#define COORDINATEOPERATION_INTERNAL_HH_INCLUDED + +#include "proj/coordinateoperation.hpp" + +#include + +//! @cond Doxygen_Suppress + +NS_PROJ_START + +namespace operation { + +// --------------------------------------------------------------------------- + +bool isAxisOrderReversal(int methodEPSGCode); + +// --------------------------------------------------------------------------- + +class InverseCoordinateOperation; +/** Shared pointer of InverseCoordinateOperation */ +using InverseCoordinateOperationPtr = + std::shared_ptr; +/** Non-null shared pointer of InverseCoordinateOperation */ +using InverseCoordinateOperationNNPtr = util::nn; + +/** \brief Inverse operation of a CoordinateOperation. + * + * This is used when there is no straightforward way of building another + * subclass of CoordinateOperation that models the inverse operation. + */ +class InverseCoordinateOperation : virtual public CoordinateOperation { + public: + InverseCoordinateOperation( + const CoordinateOperationNNPtr &forwardOperationIn, + bool wktSupportsInversion); + + ~InverseCoordinateOperation() override; + + void _exportToPROJString(io::PROJStringFormatter *formatter) + const override; // throw(FormattingException) + + bool _isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion = + util::IComparable::Criterion::STRICT, + const io::DatabaseContextPtr &dbContext = nullptr) const override; + + CoordinateOperationNNPtr inverse() const override; + + const CoordinateOperationNNPtr &forwardOperation() const { + return forwardOperation_; + } + + protected: + CoordinateOperationNNPtr forwardOperation_; + bool wktSupportsInversion_; + + void setPropertiesFromForward(); +}; + +// --------------------------------------------------------------------------- + +/** \brief Inverse of a conversion. */ +class InverseConversion : public Conversion, public InverseCoordinateOperation { + public: + explicit InverseConversion(const ConversionNNPtr &forward); + + ~InverseConversion() override; + + void _exportToWKT(io::WKTFormatter *formatter) const override { + Conversion::_exportToWKT(formatter); + } + + void _exportToJSON(io::JSONFormatter *formatter) const override { + Conversion::_exportToJSON(formatter); + } + + void + _exportToPROJString(io::PROJStringFormatter *formatter) const override { + InverseCoordinateOperation::_exportToPROJString(formatter); + } + + bool _isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion = + util::IComparable::Criterion::STRICT, + const io::DatabaseContextPtr &dbContext = nullptr) const override { + return InverseCoordinateOperation::_isEquivalentTo(other, criterion, + dbContext); + } + + CoordinateOperationNNPtr inverse() const override { + return InverseCoordinateOperation::inverse(); + } + + ConversionNNPtr inverseAsConversion() const; + +#ifdef _MSC_VER + // To avoid a warning C4250: 'osgeo::proj::operation::InverseConversion': + // inherits + // 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded' + // via dominance + std::set + gridsNeeded(const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const override { + return SingleOperation::gridsNeeded(databaseContext, + considerKnownGridsAsAvailable); + } +#endif + + static CoordinateOperationNNPtr create(const ConversionNNPtr &forward); + + CoordinateOperationNNPtr _shallowClone() const override; +}; + +// --------------------------------------------------------------------------- + +/** \brief Inverse of a transformation. */ +class InverseTransformation : public Transformation, + public InverseCoordinateOperation { + public: + explicit InverseTransformation(const TransformationNNPtr &forward); + + ~InverseTransformation() override; + + void _exportToWKT(io::WKTFormatter *formatter) const override; + + void + _exportToPROJString(io::PROJStringFormatter *formatter) const override { + return InverseCoordinateOperation::_exportToPROJString(formatter); + } + + void _exportToJSON(io::JSONFormatter *formatter) const override { + Transformation::_exportToJSON(formatter); + } + + bool _isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion = + util::IComparable::Criterion::STRICT, + const io::DatabaseContextPtr &dbContext = nullptr) const override { + return InverseCoordinateOperation::_isEquivalentTo(other, criterion, + dbContext); + } + + CoordinateOperationNNPtr inverse() const override { + return InverseCoordinateOperation::inverse(); + } + + TransformationNNPtr inverseAsTransformation() const; + +#ifdef _MSC_VER + // To avoid a warning C4250: + // 'osgeo::proj::operation::InverseTransformation': inherits + // 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded' + // via dominance + std::set + gridsNeeded(const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const override { + return SingleOperation::gridsNeeded(databaseContext, + considerKnownGridsAsAvailable); + } +#endif + + static TransformationNNPtr create(const TransformationNNPtr &forward); + + CoordinateOperationNNPtr _shallowClone() const override; +}; + +// --------------------------------------------------------------------------- + +class PROJBasedOperation; +/** Shared pointer of PROJBasedOperation */ +using PROJBasedOperationPtr = std::shared_ptr; +/** Non-null shared pointer of PROJBasedOperation */ +using PROJBasedOperationNNPtr = util::nn; + +/** \brief A PROJ-string based coordinate operation. + */ +class PROJBasedOperation : public SingleOperation { + public: + ~PROJBasedOperation() override; + + void _exportToWKT(io::WKTFormatter *formatter) + const override; // throw(io::FormattingException) + + CoordinateOperationNNPtr inverse() const override; + + static PROJBasedOperationNNPtr + create(const util::PropertyMap &properties, const std::string &PROJString, + const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, + const std::vector &accuracies); + + static PROJBasedOperationNNPtr + 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 &accuracies, + bool hasRoughTransformation); + + std::set + gridsNeeded(const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const override; + + protected: + PROJBasedOperation(const PROJBasedOperation &) = default; + explicit PROJBasedOperation(const OperationMethodNNPtr &methodIn); + + void _exportToPROJString(io::PROJStringFormatter *formatter) + const override; // throw(FormattingException) + + void _exportToJSON(io::JSONFormatter *formatter) + const override; // throw(FormattingException) + + CoordinateOperationNNPtr _shallowClone() const override; + + INLINED_MAKE_SHARED + + private: + std::string projString_{}; + io::IPROJStringExportablePtr projStringExportable_{}; + 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 + +//! @endcond + +#endif // COORDINATEOPERATION_INTERNAL_HH_INCLUDED diff -Nru proj-7.2.0/src/iso19111/operation/coordinateoperation_private.hpp proj-7.2.1/src/iso19111/operation/coordinateoperation_private.hpp --- proj-7.2.0/src/iso19111/operation/coordinateoperation_private.hpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/coordinateoperation_private.hpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,88 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 operationVersion_{}; + std::vector + coordinateOperationAccuracies_{}; + std::weak_ptr sourceCRSWeak_{}; + std::weak_ptr targetCRSWeak_{}; + crs::CRSPtr interpolationCRS_{}; + util::optional sourceCoordinateEpoch_{}; + util::optional 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 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( + *(other.strongRef_)) + : nullptr) {} + + Private &operator=(const Private &) = delete; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // COORDINATEROPERATION_PRIVATE_HPP diff -Nru proj-7.2.0/src/iso19111/operation/esriparammappings.cpp proj-7.2.1/src/iso19111/operation/esriparammappings.cpp --- proj-7.2.0/src/iso19111/operation/esriparammappings.cpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/esriparammappings.cpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,1123 @@ +// This file was generated by scripts/build_esri_projection_mapping.py. DO NOT +// EDIT ! + +/****************************************************************************** + * + * Project: PROJ + * Purpose: Mappings between ESRI projection and parameters names and WKT2 + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2019, Even Rouault + * + * 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 "esriparammappings.hpp" +#include "proj_constants.h" + +#include "proj/internal/internal.hpp" + +NS_PROJ_START + +using namespace internal; + +namespace operation { + +//! @cond Doxygen_Suppress + +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, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +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, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Miller_Cylindrical[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Mercator[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +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, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +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, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Transverse_Mercator_Complex[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Albers[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Sinusoidal[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Mollweide[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_I[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_II[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_III[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_IV[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_V[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_VI[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Gall_Stereographic[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Behrmann[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", true}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "30.0", true}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Winkel_I[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Winkel_II[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt1[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; +static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt2[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; +static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt3[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, + {"Scale_Factor", nullptr, 0, "1.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; +static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt4[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR, + EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Polyconic[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Quartic_Authalic[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Loximuthal[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Central_Parallel", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Bonne[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping + paramsESRI_Hotine_Oblique_Mercator_Two_Point_Natural_Origin[] = { + {"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}, + {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, + {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false}, + {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, + {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "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_Stereographic[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Equidistant_Conic[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Cassini[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", nullptr, 0, "1.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Van_der_Grinten_I[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Robinson[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Two_Point_Equidistant[] = { + {"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}, + {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, + {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, + {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, + {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Azimuthal_Equidistant[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Lambert_Azimuthal_Equal_Area[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Cylindrical_Equal_Area[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping + paramsESRI_Hotine_Oblique_Mercator_Two_Point_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}, + {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, + {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false}, + {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, + {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +const ESRIParamMapping + paramsESRI_Hotine_Oblique_Mercator_Azimuth_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}, + {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, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Krovak_alt1[] = { + {"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}, + {"Pseudo_Standard_Parallel_1", + EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, "0.0", false}, + {"Azimuth", EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, + EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, + {"X_Scale", nullptr, 0, "1.0", false}, + {"Y_Scale", nullptr, 0, "1.0", false}, + {"XY_Plane_Rotation", nullptr, 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; +static const ESRIParamMapping paramsESRI_Krovak_alt2[] = { + {"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}, + {"Pseudo_Standard_Parallel_1", + EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, "0.0", false}, + {"Azimuth", EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, + EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, + {"X_Scale", nullptr, 0, "-1.0", false}, + {"Y_Scale", nullptr, 0, "1.0", false}, + {"XY_Plane_Rotation", nullptr, 0, "90.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_New_Zealand_Map_Grid[] = { + {"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}, + {"Longitude_Of_Origin", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Orthographic[] = { + {"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}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Local[] = { + {"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", nullptr, 0, "1.0", false}, + {"Azimuth", nullptr, 0, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Winkel_Tripel[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Aitoff[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Flat_Polar_Quartic[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Craster_Parabolic[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Gnomonic[] = { + {"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}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Times[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Vertical_Near_Side_Perspective[] = { + {"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}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, "0.0", false}, + {"Height", EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT, + EPSG_CODE_PARAMETER_VIEWPOINT_HEIGHT, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Stereographic_North_Pole[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Stereographic_South_Pole[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "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, + 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}}; + +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}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Option", nullptr, 0, "1.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; +static const ESRIParamMapping paramsESRI_Goode_Homolosine_alt2[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Option", nullptr, 0, "2.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Equidistant_Cylindrical_Ellipsoidal[] = + {{"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Laborde_Oblique_Mercator[] = { + {"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}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Gnomonic_Ellipsoidal[] = { + {"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}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Wagner_IV[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", nullptr, 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Wagner_V[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Wagner_VII[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Natural_Earth[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Natural_Earth_II[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Patterson[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Compact_Miller[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Geostationary_Satellite[] = { + {"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}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Height", "Satellite Height", 0, "0.0", false}, + {"Option", nullptr, 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Mercator_Auxiliary_Sphere[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Auxiliary_Sphere_Type", nullptr, 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Mercator_Variant_A[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Mercator_Variant_C[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Latitude_Of_Origin", nullptr, 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Transverse_Cylindrical_Equal_Area[] = { + {"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}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_IGAC_Plano_Cartesiano[] = { + {"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}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Height", EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, + EPSG_CODE_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIMethodMapping esriMappings[] = { + {"Plate_Carree", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, paramsESRI_Plate_Carree}, + {"Plate_Carree", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, + paramsESRI_Plate_Carree}, + {"Equidistant_Cylindrical", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, + paramsESRI_Equidistant_Cylindrical}, + {"Miller_Cylindrical", PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, 0, + paramsESRI_Miller_Cylindrical}, + {"Mercator", EPSG_NAME_METHOD_MERCATOR_VARIANT_B, + EPSG_CODE_METHOD_MERCATOR_VARIANT_B, paramsESRI_Mercator}, + {"Gauss_Kruger", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, paramsESRI_Gauss_Kruger}, + {"Transverse_Mercator", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, paramsESRI_Transverse_Mercator}, + {"Transverse_Mercator_Complex", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, + paramsESRI_Transverse_Mercator_Complex}, + {"Albers", EPSG_NAME_METHOD_ALBERS_EQUAL_AREA, + EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, paramsESRI_Albers}, + {"Sinusoidal", PROJ_WKT2_NAME_METHOD_SINUSOIDAL, 0, paramsESRI_Sinusoidal}, + {"Mollweide", PROJ_WKT2_NAME_METHOD_MOLLWEIDE, 0, paramsESRI_Mollweide}, + {"Eckert_I", PROJ_WKT2_NAME_METHOD_ECKERT_I, 0, paramsESRI_Eckert_I}, + {"Eckert_II", PROJ_WKT2_NAME_METHOD_ECKERT_II, 0, paramsESRI_Eckert_II}, + {"Eckert_III", PROJ_WKT2_NAME_METHOD_ECKERT_III, 0, paramsESRI_Eckert_III}, + {"Eckert_IV", PROJ_WKT2_NAME_METHOD_ECKERT_IV, 0, paramsESRI_Eckert_IV}, + {"Eckert_V", PROJ_WKT2_NAME_METHOD_ECKERT_V, 0, paramsESRI_Eckert_V}, + {"Eckert_VI", PROJ_WKT2_NAME_METHOD_ECKERT_VI, 0, paramsESRI_Eckert_VI}, + {"Gall_Stereographic", PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, 0, + paramsESRI_Gall_Stereographic}, + {"Behrmann", EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, paramsESRI_Behrmann}, + {"Winkel_I", "Winkel I", 0, paramsESRI_Winkel_I}, + {"Winkel_II", "Winkel II", 0, paramsESRI_Winkel_II}, + {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, + paramsESRI_Lambert_Conformal_Conic_alt1}, + {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + paramsESRI_Lambert_Conformal_Conic_alt2}, + {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + paramsESRI_Lambert_Conformal_Conic_alt3}, + {"Lambert_Conformal_Conic", + EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, + paramsESRI_Lambert_Conformal_Conic_alt4}, + {"Polyconic", EPSG_NAME_METHOD_AMERICAN_POLYCONIC, + EPSG_CODE_METHOD_AMERICAN_POLYCONIC, paramsESRI_Polyconic}, + {"Quartic_Authalic", "Quartic Authalic", 0, paramsESRI_Quartic_Authalic}, + {"Loximuthal", "Loximuthal", 0, paramsESRI_Loximuthal}, + {"Bonne", EPSG_NAME_METHOD_BONNE, EPSG_CODE_METHOD_BONNE, paramsESRI_Bonne}, + {"Hotine_Oblique_Mercator_Two_Point_Natural_Origin", + PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, + paramsESRI_Hotine_Oblique_Mercator_Two_Point_Natural_Origin}, + {"Stereographic", PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, 0, + paramsESRI_Stereographic}, + {"Equidistant_Conic", PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, 0, + paramsESRI_Equidistant_Conic}, + {"Cassini", EPSG_NAME_METHOD_CASSINI_SOLDNER, + EPSG_CODE_METHOD_CASSINI_SOLDNER, paramsESRI_Cassini}, + {"Van_der_Grinten_I", PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, 0, + paramsESRI_Van_der_Grinten_I}, + {"Robinson", PROJ_WKT2_NAME_METHOD_ROBINSON, 0, paramsESRI_Robinson}, + {"Two_Point_Equidistant", PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, 0, + paramsESRI_Two_Point_Equidistant}, + {"Azimuthal_Equidistant", EPSG_NAME_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, + EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, + paramsESRI_Azimuthal_Equidistant}, + {"Lambert_Azimuthal_Equal_Area", + EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, + EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, + paramsESRI_Lambert_Azimuthal_Equal_Area}, + {"Cylindrical_Equal_Area", EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, + paramsESRI_Cylindrical_Equal_Area}, + {"Hotine_Oblique_Mercator_Two_Point_Center", + PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, + paramsESRI_Hotine_Oblique_Mercator_Two_Point_Center}, + {"Hotine_Oblique_Mercator_Azimuth_Natural_Origin", + EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin}, + {"Hotine_Oblique_Mercator_Azimuth_Center", + EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center}, + {"Double_Stereographic", EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC, + EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, paramsESRI_Double_Stereographic}, + {"Krovak", EPSG_NAME_METHOD_KROVAK, EPSG_CODE_METHOD_KROVAK, + paramsESRI_Krovak_alt1}, + {"Krovak", EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED, + EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, paramsESRI_Krovak_alt2}, + {"New_Zealand_Map_Grid", EPSG_NAME_METHOD_NZMG, EPSG_CODE_METHOD_NZMG, + paramsESRI_New_Zealand_Map_Grid}, + {"Orthographic", PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL, 0, + paramsESRI_Orthographic}, + {"Local", EPSG_NAME_METHOD_ORTHOGRAPHIC, EPSG_CODE_METHOD_ORTHOGRAPHIC, + paramsESRI_Local}, + {"Winkel_Tripel", "Winkel Tripel", 0, paramsESRI_Winkel_Tripel}, + {"Aitoff", "Aitoff", 0, paramsESRI_Aitoff}, + {"Flat_Polar_Quartic", PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC, 0, + paramsESRI_Flat_Polar_Quartic}, + {"Craster_Parabolic", "Craster Parabolic", 0, paramsESRI_Craster_Parabolic}, + {"Gnomonic", PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, paramsESRI_Gnomonic}, + {"Times", PROJ_WKT2_NAME_METHOD_TIMES, 0, paramsESRI_Times}, + {"Vertical_Near_Side_Perspective", EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE, + EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, + paramsESRI_Vertical_Near_Side_Perspective}, + {"Stereographic_North_Pole", EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + paramsESRI_Stereographic_North_Pole}, + {"Stereographic_South_Pole", EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + paramsESRI_Stereographic_South_Pole}, + {"Rectified_Skew_Orthomorphic_Natural_Origin", + EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin}, + {"Rectified_Skew_Orthomorphic_Center", + EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + paramsESRI_Rectified_Skew_Orthomorphic_Center}, + {"Goode_Homolosine", PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, 0, + paramsESRI_Goode_Homolosine_alt1}, + {"Goode_Homolosine", + PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN, 0, + paramsESRI_Goode_Homolosine_alt2}, + {"Equidistant_Cylindrical_Ellipsoidal", + EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, + paramsESRI_Equidistant_Cylindrical_Ellipsoidal}, + {"Laborde_Oblique_Mercator", EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR, + EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, + paramsESRI_Laborde_Oblique_Mercator}, + {"Gnomonic_Ellipsoidal", PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, + paramsESRI_Gnomonic_Ellipsoidal}, + {"Wagner_IV", PROJ_WKT2_NAME_METHOD_WAGNER_IV, 0, paramsESRI_Wagner_IV}, + {"Wagner_V", PROJ_WKT2_NAME_METHOD_WAGNER_V, 0, paramsESRI_Wagner_V}, + {"Wagner_VII", PROJ_WKT2_NAME_METHOD_WAGNER_VII, 0, paramsESRI_Wagner_VII}, + {"Natural_Earth", PROJ_WKT2_NAME_METHOD_NATURAL_EARTH, 0, + paramsESRI_Natural_Earth}, + {"Natural_Earth_II", PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II, 0, + paramsESRI_Natural_Earth_II}, + {"Patterson", PROJ_WKT2_NAME_METHOD_PATTERSON, 0, paramsESRI_Patterson}, + {"Compact_Miller", PROJ_WKT2_NAME_METHOD_COMPACT_MILLER, 0, + paramsESRI_Compact_Miller}, + {"Geostationary_Satellite", + PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, 0, + paramsESRI_Geostationary_Satellite}, + {"Mercator_Auxiliary_Sphere", + EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, + paramsESRI_Mercator_Auxiliary_Sphere}, + {"Mercator_Variant_A", EPSG_NAME_METHOD_MERCATOR_VARIANT_A, + EPSG_CODE_METHOD_MERCATOR_VARIANT_A, paramsESRI_Mercator_Variant_A}, + {"Mercator_Variant_C", EPSG_NAME_METHOD_MERCATOR_VARIANT_B, + EPSG_CODE_METHOD_MERCATOR_VARIANT_B, paramsESRI_Mercator_Variant_C}, + {"Transverse_Cylindrical_Equal_Area", "Transverse Cylindrical Equal Area", + 0, paramsESRI_Transverse_Cylindrical_Equal_Area}, + {"IGAC_Plano_Cartesiano", EPSG_NAME_METHOD_COLOMBIA_URBAN, + EPSG_CODE_METHOD_COLOMBIA_URBAN, paramsESRI_IGAC_Plano_Cartesiano}, +}; + +// --------------------------------------------------------------------------- + +const ESRIMethodMapping *getEsriMappings(size_t &nElts) { + nElts = sizeof(esriMappings) / sizeof(esriMappings[0]); + return esriMappings; +} + +// --------------------------------------------------------------------------- + +std::vector +getMappingsFromESRI(const std::string &esri_name) { + std::vector res; + for (const auto &mapping : esriMappings) { + if (ci_equal(esri_name, mapping.esri_name)) { + res.push_back(&mapping); + } + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END diff -Nru proj-7.2.0/src/iso19111/operation/esriparammappings.hpp proj-7.2.1/src/iso19111/operation/esriparammappings.hpp --- proj-7.2.0/src/iso19111/operation/esriparammappings.hpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/esriparammappings.hpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,86 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 + +// --------------------------------------------------------------------------- + +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 +getMappingsFromESRI(const std::string &esri_name); + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // ESRIPARAMMAPPINGS_HPP diff -Nru proj-7.2.0/src/iso19111/operation/operationmethod_private.hpp proj-7.2.1/src/iso19111/operation/operationmethod_private.hpp --- proj-7.2.0/src/iso19111/operation/operationmethod_private.hpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/operationmethod_private.hpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,56 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 formula_{}; + util::optional formulaCitation_{}; + std::vector parameters_{}; + std::string projMethodOverride_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // OPERATIONMETHOD_PRIVATE_HPP diff -Nru proj-7.2.0/src/iso19111/operation/oputils.cpp proj-7.2.1/src/iso19111/operation/oputils.cpp --- proj-7.2.0/src/iso19111/operation/oputils.cpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/oputils.cpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,643 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 + +#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.get()); + if (geod) { + if (geod->isGeocentric()) { + return " (geocentric)"; + } + auto geog = dynamic_cast(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(op) || + starts_with(forwardName, "Transformation from ")) { + opType = "Transformation"; + } else if (dynamic_cast(op)) { + opType = "Conversion"; + } else { + opType = "Operation"; + } + + auto sourceCRS = op->sourceCRS(); + auto targetCRS = op->targetCRS(); + std::string name; + if (!forwardName.empty()) { + if (dynamic_cast(op) == nullptr && + dynamic_cast(op) == nullptr && + (starts_with(forwardName, INVERSE_OF) || + forwardName.find(" + ") != std::string::npos)) { + std::vector 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(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 buildSetEquivalentParameters() { + + std::set 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 ¶mList : 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 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 &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(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(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(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(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 &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(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(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 &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 -Nru proj-7.2.0/src/iso19111/operation/oputils.hpp proj-7.2.1/src/iso19111/operation/oputils.hpp --- proj-7.2.0/src/iso19111/operation/oputils.hpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/oputils.hpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,121 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 &flattenOps); + +metadata::ExtentPtr getExtent(const std::vector &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 &ops); + +void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co, + io::WKTFormatter *formatter); + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // OPUTILS_HPP diff -Nru proj-7.2.0/src/iso19111/operation/parammappings.cpp proj-7.2.1/src/iso19111/operation/parammappings.cpp --- proj-7.2.0/src/iso19111/operation/parammappings.cpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/parammappings.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -0,0 +1,1510 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 "parammappings.hpp" +#include "oputils.hpp" +#include "proj_constants.h" + +#include "proj/internal/internal.hpp" + +NS_PROJ_START + +using namespace internal; + +namespace operation { + +//! @cond Doxygen_Suppress + +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"; +static const char *lat_2 = "lat_2"; +static const char *lat_ts = "lat_ts"; +static const char *lon_0 = "lon_0"; +static const char *lon_1 = "lon_1"; +static const char *lon_2 = "lon_2"; +static const char *lonc = "lonc"; +static const char *alpha = "alpha"; +static const char *gamma = "gamma"; +static const char *k_0 = "k_0"; +static const char *k = "k"; +static const char *x_0 = "x_0"; +static const char *y_0 = "y_0"; +static const char *h = "h"; + +// --------------------------------------------------------------------------- + +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}; + +static const ParamMapping paramLongitudeNatOrigin = { + EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, WKT1_CENTRAL_MERIDIAN, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping paramScaleFactor = { + EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, + common::UnitOfMeasure::Type::SCALE, k_0}; + +static const ParamMapping paramScaleFactorK = { + EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, + common::UnitOfMeasure::Type::SCALE, k}; + +static const ParamMapping paramFalseEasting = { + EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, + WKT1_FALSE_EASTING, common::UnitOfMeasure::Type::LINEAR, x_0}; + +static const ParamMapping paramFalseNorthing = { + EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, + WKT1_FALSE_NORTHING, common::UnitOfMeasure::Type::LINEAR, y_0}; + +static const ParamMapping paramLatitudeFalseOrigin = { + EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, WKT1_LATITUDE_OF_ORIGIN, + common::UnitOfMeasure::Type::ANGULAR, lat_0}; + +static const ParamMapping paramLongitudeFalseOrigin = { + EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, WKT1_CENTRAL_MERIDIAN, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping paramFalseEastingOrigin = { + EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, WKT1_FALSE_EASTING, + common::UnitOfMeasure::Type::LINEAR, x_0}; + +static const ParamMapping paramFalseNorthingOrigin = { + EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, WKT1_FALSE_NORTHING, + common::UnitOfMeasure::Type::LINEAR, y_0}; + +static const ParamMapping paramLatitude1stStdParallel = { + EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, WKT1_STANDARD_PARALLEL_1, + common::UnitOfMeasure::Type::ANGULAR, lat_1}; + +static const ParamMapping paramLatitude2ndStdParallel = { + EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, WKT1_STANDARD_PARALLEL_2, + common::UnitOfMeasure::Type::ANGULAR, lat_2}; + +static const ParamMapping *const paramsNatOriginScale[] = { + ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactor, + ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsNatOriginScaleK[] = { + ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactorK, + ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatFirstPoint = { + "Latitude of 1st point", 0, "Latitude_Of_1st_Point", + common::UnitOfMeasure::Type::ANGULAR, lat_1}; +static const ParamMapping paramLongFirstPoint = { + "Longitude of 1st point", 0, "Longitude_Of_1st_Point", + common::UnitOfMeasure::Type::ANGULAR, lon_1}; +static const ParamMapping paramLatSecondPoint = { + "Latitude of 2nd point", 0, "Latitude_Of_2nd_Point", + common::UnitOfMeasure::Type::ANGULAR, lat_2}; +static const ParamMapping paramLongSecondPoint = { + "Longitude of 2nd point", 0, "Longitude_Of_2nd_Point", + common::UnitOfMeasure::Type::ANGULAR, lon_2}; + +static const ParamMapping *const paramsTPEQD[] = {¶mLatFirstPoint, + ¶mLongFirstPoint, + ¶mLatSecondPoint, + ¶mLongSecondPoint, + ¶mFalseEasting, + ¶mFalseNorthing, + nullptr}; + +static const ParamMapping *const paramsTMG[] = { + ¶mLatitudeFalseOrigin, ¶mLongitudeFalseOrigin, + ¶mFalseEastingOrigin, ¶mFalseNorthingOrigin, nullptr}; + +static const ParamMapping paramLatFalseOriginLatOfCenter = { + EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, WKT1_LATITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lat_0}; + +static const ParamMapping paramLongFalseOriginLongOfCenter = { + EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, WKT1_LONGITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping *const paramsAEA[] = { + ¶mLatFalseOriginLatOfCenter, + ¶mLongFalseOriginLongOfCenter, + ¶mLatitude1stStdParallel, + ¶mLatitude2ndStdParallel, + ¶mFalseEastingOrigin, + ¶mFalseNorthingOrigin, + nullptr}; + +static const ParamMapping *const paramsLCC2SP[] = { + ¶mLatitudeFalseOrigin, + ¶mLongitudeFalseOrigin, + ¶mLatitude1stStdParallel, + ¶mLatitude2ndStdParallel, + ¶mFalseEastingOrigin, + ¶mFalseNorthingOrigin, + nullptr, +}; + +static const ParamMapping paramEllipsoidScaleFactor = { + EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR, + EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR, nullptr, + common::UnitOfMeasure::Type::SCALE, k_0}; + +static const ParamMapping *const paramsLCC2SPMichigan[] = { + ¶mLatitudeFalseOrigin, ¶mLongitudeFalseOrigin, + ¶mLatitude1stStdParallel, ¶mLatitude2ndStdParallel, + ¶mFalseEastingOrigin, ¶mFalseNorthingOrigin, + ¶mEllipsoidScaleFactor, nullptr, +}; + +static const ParamMapping paramLatNatLatCenter = { + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lat_0}; + +static const ParamMapping paramLonNatLonCenter = { + EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, WKT1_LONGITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping *const paramsAEQD[]{ + ¶mLatNatLatCenter, ¶mLonNatLonCenter, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsNatOrigin[] = { + ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatNatOriginLat1 = { + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_STANDARD_PARALLEL_1, + common::UnitOfMeasure::Type::ANGULAR, lat_1}; + +static const ParamMapping *const paramsBonne[] = { + ¶mLatNatOriginLat1, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLat1stParallelLatTs = { + EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, WKT1_STANDARD_PARALLEL_1, + common::UnitOfMeasure::Type::ANGULAR, lat_ts}; + +static const ParamMapping *const paramsCEA[] = { + ¶mLat1stParallelLatTs, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsEQDC[] = {¶mLatNatLatCenter, + ¶mLonNatLonCenter, + ¶mLatitude1stStdParallel, + ¶mLatitude2ndStdParallel, + ¶mFalseEasting, + ¶mFalseNorthing, + nullptr}; + +static const ParamMapping *const paramsLonNatOrigin[] = { + ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsEqc[] = { + ¶mLat1stParallelLatTs, + ¶mLatitudeNatOrigin, // extension of EPSG, but used by GDAL / PROJ + ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramSatelliteHeight = { + "Satellite Height", 0, "satellite_height", + common::UnitOfMeasure::Type::LINEAR, h}; + +static const ParamMapping *const paramsGeos[] = { + ¶mLongitudeNatOrigin, ¶mSatelliteHeight, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatCentreLatCenter = { + EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, WKT1_LATITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lat_0}; + +static const ParamMapping paramLonCentreLonCenterLonc = { + EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, WKT1_LONGITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lonc}; + +static const ParamMapping paramAzimuth = { + EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, WKT1_AZIMUTH, + common::UnitOfMeasure::Type::ANGULAR, alpha}; + +static const ParamMapping paramAngleToSkewGrid = { + EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, WKT1_RECTIFIED_GRID_ANGLE, + common::UnitOfMeasure::Type::ANGULAR, gamma}; +static const ParamMapping paramScaleFactorInitialLine = { + EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, WKT1_SCALE_FACTOR, + common::UnitOfMeasure::Type::SCALE, k}; + +static const ParamMapping *const paramsHomVariantA[] = { + ¶mLatCentreLatCenter, + ¶mLonCentreLonCenterLonc, + ¶mAzimuth, + ¶mAngleToSkewGrid, + ¶mScaleFactorInitialLine, + ¶mFalseEasting, + ¶mFalseNorthing, + nullptr}; + +static const ParamMapping paramFalseEastingProjectionCentre = { + EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, WKT1_FALSE_EASTING, + common::UnitOfMeasure::Type::LINEAR, x_0}; + +static const ParamMapping paramFalseNorthingProjectionCentre = { + EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, WKT1_FALSE_NORTHING, + common::UnitOfMeasure::Type::LINEAR, y_0}; + +static const ParamMapping *const paramsHomVariantB[] = { + ¶mLatCentreLatCenter, + ¶mLonCentreLonCenterLonc, + ¶mAzimuth, + ¶mAngleToSkewGrid, + ¶mScaleFactorInitialLine, + ¶mFalseEastingProjectionCentre, + ¶mFalseNorthingProjectionCentre, + nullptr}; + +static const ParamMapping paramLatPoint1 = { + "Latitude of 1st point", 0, "latitude_of_point_1", + common::UnitOfMeasure::Type::ANGULAR, lat_1}; + +static const ParamMapping paramLonPoint1 = { + "Longitude of 1st point", 0, "longitude_of_point_1", + common::UnitOfMeasure::Type::ANGULAR, lon_1}; + +static const ParamMapping paramLatPoint2 = { + "Latitude of 2nd point", 0, "latitude_of_point_2", + common::UnitOfMeasure::Type::ANGULAR, lat_2}; + +static const ParamMapping paramLonPoint2 = { + "Longitude of 2nd point", 0, "longitude_of_point_2", + common::UnitOfMeasure::Type::ANGULAR, lon_2}; + +static const ParamMapping *const paramsHomTwoPoint[] = { + ¶mLatCentreLatCenter, + ¶mLatPoint1, + ¶mLonPoint1, + ¶mLatPoint2, + ¶mLonPoint2, + ¶mScaleFactorInitialLine, + ¶mFalseEastingProjectionCentre, + ¶mFalseNorthingProjectionCentre, + nullptr}; + +static const ParamMapping *const paramsIMWP[] = { + ¶mLongitudeNatOrigin, ¶mLatFirstPoint, ¶mLatSecondPoint, + ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLonCentreLonCenter = { + EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, WKT1_LONGITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping paramColatitudeConeAxis = { + EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, + EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, WKT1_AZIMUTH, + common::UnitOfMeasure::Type::ANGULAR, + "alpha"}; /* ignored by PROJ currently */ + +static const ParamMapping paramLatitudePseudoStdParallel = { + EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, + "pseudo_standard_parallel_1", common::UnitOfMeasure::Type::ANGULAR, + nullptr}; /* ignored by PROJ currently */ + +static const ParamMapping paramScaleFactorPseudoStdParallel = { + EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, + WKT1_SCALE_FACTOR, common::UnitOfMeasure::Type::SCALE, + k}; /* ignored by PROJ currently */ + +static const ParamMapping *const krovakParameters[] = { + ¶mLatCentreLatCenter, + ¶mLonCentreLonCenter, + ¶mColatitudeConeAxis, + ¶mLatitudePseudoStdParallel, + ¶mScaleFactorPseudoStdParallel, + ¶mFalseEasting, + ¶mFalseNorthing, + nullptr}; + +static const ParamMapping *const paramsLaea[] = { + ¶mLatNatLatCenter, ¶mLonNatLonCenter, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsMiller[] = { + ¶mLonNatLonCenter, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatMerc1SP = { + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + nullptr, // always set to zero, not to be exported in WKT1 + common::UnitOfMeasure::Type::ANGULAR, + nullptr}; // always set to zero, not to be exported in PROJ strings + +static const ParamMapping *const paramsMerc1SP[] = { + ¶mLatMerc1SP, ¶mLongitudeNatOrigin, ¶mScaleFactorK, + ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsMerc2SP[] = { + ¶mLat1stParallelLatTs, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsObliqueStereo[] = { + ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactorK, + ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatStdParallel = { + EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, WKT1_LATITUDE_OF_ORIGIN, + common::UnitOfMeasure::Type::ANGULAR, lat_ts}; + +static const ParamMapping paramsLonOrigin = { + EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, WKT1_CENTRAL_MERIDIAN, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping *const paramsPolarStereo[] = { + ¶mLatStdParallel, ¶msLonOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsLonNatOriginLongitudeCentre[] = { + ¶mLonNatLonCenter, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatTrueScaleWag3 = { + "Latitude of true scale", 0, WKT1_LATITUDE_OF_ORIGIN, + common::UnitOfMeasure::Type::ANGULAR, lat_ts}; + +static const ParamMapping *const paramsWag3[] = { + ¶mLatTrueScaleWag3, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramPegLat = { + "Peg point latitude", 0, "peg_point_latitude", + common::UnitOfMeasure::Type::ANGULAR, "plat_0"}; + +static const ParamMapping paramPegLon = { + "Peg point longitude", 0, "peg_point_longitude", + common::UnitOfMeasure::Type::ANGULAR, "plon_0"}; + +static const ParamMapping paramPegHeading = { + "Peg point heading", 0, "peg_point_heading", + common::UnitOfMeasure::Type::ANGULAR, "phdg_0"}; + +static const ParamMapping paramPegHeight = { + "Peg point height", 0, "peg_point_height", + common::UnitOfMeasure::Type::LINEAR, "h_0"}; + +static const ParamMapping *const paramsSch[] = { + ¶mPegLat, ¶mPegLon, ¶mPegHeading, ¶mPegHeight, nullptr}; + +static const ParamMapping *const paramsWink1[] = { + ¶mLongitudeNatOrigin, ¶mLat1stParallelLatTs, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsWink2[] = { + ¶mLongitudeNatOrigin, ¶mLatitude1stStdParallel, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatLoxim = { + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_ORIGIN, + common::UnitOfMeasure::Type::ANGULAR, lat_1}; + +static const ParamMapping *const paramsLoxim[] = { + ¶mLatLoxim, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLonCentre = { + EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, WKT1_LONGITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping paramLabordeObliqueMercatorAzimuth = { + EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, WKT1_AZIMUTH, + common::UnitOfMeasure::Type::ANGULAR, "azi"}; + +static const ParamMapping *const paramsLabordeObliqueMercator[] = { + ¶mLatCentreLatCenter, + ¶mLonCentre, + ¶mLabordeObliqueMercatorAzimuth, + ¶mScaleFactorInitialLine, + ¶mFalseEasting, + ¶mFalseNorthing, + nullptr}; + +static const ParamMapping paramLatTopoOrigin = { + EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, nullptr, + common::UnitOfMeasure::Type::ANGULAR, lat_0}; + +static const ParamMapping paramLonTopoOrigin = { + EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, nullptr, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping paramHeightTopoOrigin = { + EPSG_NAME_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, + EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, nullptr, + common::UnitOfMeasure::Type::LINEAR, + nullptr}; // unsupported by PROJ right now + +static const ParamMapping paramViewpointHeight = { + EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT, EPSG_CODE_PARAMETER_VIEWPOINT_HEIGHT, + nullptr, common::UnitOfMeasure::Type::LINEAR, "h"}; + +static const ParamMapping *const paramsVerticalPerspective[] = { + ¶mLatTopoOrigin, + ¶mLonTopoOrigin, + ¶mHeightTopoOrigin, // unsupported by PROJ right now + ¶mViewpointHeight, + ¶mFalseEasting, // PROJ addition + ¶mFalseNorthing, // PROJ addition + nullptr}; + +static const ParamMapping paramProjectionPlaneOriginHeight = { + EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, + EPSG_CODE_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, nullptr, + common::UnitOfMeasure::Type::LINEAR, "h_0"}; + +static const ParamMapping *const paramsColombiaUrban[] = { + ¶mLatitudeNatOrigin, + ¶mLongitudeNatOrigin, + ¶mFalseEasting, + ¶mFalseNorthing, + ¶mProjectionPlaneOriginHeight, + nullptr}; + +static const MethodMapping projectionMethodMappings[] = { + {EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, + "Transverse_Mercator", "tmerc", nullptr, paramsNatOriginScaleK}, + + {EPSG_NAME_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, + "Transverse_Mercator_South_Orientated", "tmerc", "axis=wsu", + paramsNatOriginScaleK}, + + {PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, 0, "Two_Point_Equidistant", + "tpeqd", nullptr, paramsTPEQD}, + + {EPSG_NAME_METHOD_TUNISIA_MAPPING_GRID, + EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID, "Tunisia_Mapping_Grid", nullptr, + nullptr, // no proj equivalent + paramsTMG}, + + {EPSG_NAME_METHOD_ALBERS_EQUAL_AREA, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, + "Albers_Conic_Equal_Area", "aea", nullptr, paramsAEA}, + + {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, + "Lambert_Conformal_Conic_1SP", "lcc", nullptr, + []() { + static const ParamMapping paramLatLCC1SP = { + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + WKT1_LATITUDE_OF_ORIGIN, common::UnitOfMeasure::Type::ANGULAR, + lat_1}; + + static const ParamMapping *const x[] = { + ¶mLatLCC1SP, ¶mLongitudeNatOrigin, ¶mScaleFactor, + ¶mFalseEasting, ¶mFalseNorthing, nullptr, + }; + return x; + }()}, + + {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + "Lambert_Conformal_Conic_2SP", "lcc", nullptr, paramsLCC2SP}, + + // Oracle WKT + {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, "Lambert Conformal Conic", + "lcc", nullptr, paramsLCC2SP}, + + {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, + nullptr, // no mapping to WKT1_GDAL + "lcc", nullptr, paramsLCC2SPMichigan}, + + {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, + "Lambert_Conformal_Conic_2SP_Belgium", "lcc", + nullptr, // FIXME: this is what is done in GDAL, but the formula of + // LCC 2SP + // Belgium in the EPSG 7.2 guidance is difference from the regular + // LCC 2SP + paramsLCC2SP}, + + {EPSG_NAME_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, + EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, "Azimuthal_Equidistant", + "aeqd", nullptr, paramsAEQD}, + + {EPSG_NAME_METHOD_GUAM_PROJECTION, EPSG_CODE_METHOD_GUAM_PROJECTION, + nullptr, // no mapping to GDAL WKT1 + "aeqd", "guam", paramsNatOrigin}, + + {EPSG_NAME_METHOD_BONNE, EPSG_CODE_METHOD_BONNE, "Bonne", "bonne", nullptr, + paramsBonne}, + + {PROJ_WKT2_NAME_METHOD_COMPACT_MILLER, 0, "Compact_Miller", "comill", + nullptr, paramsLonNatOrigin}, + + {EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, + "Cylindrical_Equal_Area", "cea", nullptr, paramsCEA}, + + {EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, "Cylindrical_Equal_Area", + "cea", nullptr, paramsCEA}, + + {EPSG_NAME_METHOD_CASSINI_SOLDNER, EPSG_CODE_METHOD_CASSINI_SOLDNER, + "Cassini_Soldner", "cass", nullptr, paramsNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, 0, "Equidistant_Conic", "eqdc", + nullptr, paramsEQDC}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_I, 0, "Eckert_I", "eck1", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_II, 0, "Eckert_II", "eck2", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_III, 0, "Eckert_III", "eck3", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_IV, 0, "Eckert_IV", "eck4", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_V, 0, "Eckert_V", "eck5", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_VI, 0, "Eckert_VI", "eck6", nullptr, + paramsLonNatOrigin}, + + {EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, "Equirectangular", "eqc", + nullptr, paramsEqc}, + + {EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, "Equirectangular", + "eqc", nullptr, paramsEqc}, + + {PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC, 0, "Flat_Polar_Quartic", + "mbtfpq", nullptr, paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, 0, "Gall_Stereographic", "gall", + nullptr, paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE, 0, "Goode_Homolosine", "goode", + nullptr, paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, 0, + "Interrupted_Goode_Homolosine", "igh", nullptr, paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN, 0, nullptr, + "igh_o", nullptr, paramsLonNatOrigin}, + + // No proper WKT1 representation fr sweep=x + {PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X, 0, nullptr, "geos", + "sweep=x", paramsGeos}, + + {PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, 0, + "Geostationary_Satellite", "geos", nullptr, paramsGeos}, + + {PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR, 0, + "Gauss_Schreiber_Transverse_Mercator", "gstmerc", nullptr, + paramsNatOriginScale}, + + {PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, "Gnomonic", "gnom", nullptr, + paramsNatOrigin}, + + {EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + "Hotine_Oblique_Mercator", "omerc", "no_uoff", paramsHomVariantA}, + + {EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + "Hotine_Oblique_Mercator_Azimuth_Center", "omerc", nullptr, + paramsHomVariantB}, + + {PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, + "Hotine_Oblique_Mercator_Two_Point_Natural_Origin", "omerc", nullptr, + paramsHomTwoPoint}, + + {PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC, 0, + "International_Map_of_the_World_Polyconic", "imw_p", nullptr, paramsIMWP}, + + {EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED, + EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, "Krovak", "krovak", nullptr, + krovakParameters}, + + {EPSG_NAME_METHOD_KROVAK, EPSG_CODE_METHOD_KROVAK, "Krovak", "krovak", + "axis=swu", krovakParameters}, + + {EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, + EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, + "Lambert_Azimuthal_Equal_Area", "laea", nullptr, paramsLaea}, + + {EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL, + EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL, + "Lambert_Azimuthal_Equal_Area", "laea", nullptr, paramsLaea}, + + {PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, 0, "Miller_Cylindrical", "mill", + "R_A", paramsMiller}, + + {EPSG_NAME_METHOD_MERCATOR_VARIANT_A, EPSG_CODE_METHOD_MERCATOR_VARIANT_A, + "Mercator_1SP", "merc", nullptr, paramsMerc1SP}, + + {EPSG_NAME_METHOD_MERCATOR_VARIANT_B, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, + "Mercator_2SP", "merc", nullptr, paramsMerc2SP}, + + {EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, + "Popular_Visualisation_Pseudo_Mercator", // particular case actually + // handled manually + "webmerc", nullptr, paramsNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_MOLLWEIDE, 0, "Mollweide", "moll", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_NATURAL_EARTH, 0, "Natural_Earth", "natearth", + nullptr, paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II, 0, "Natural_Earth_II", "natearth2", + nullptr, paramsLonNatOrigin}, + + {EPSG_NAME_METHOD_NZMG, EPSG_CODE_METHOD_NZMG, "New_Zealand_Map_Grid", + "nzmg", nullptr, paramsNatOrigin}, + + { + EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC, + EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, "Oblique_Stereographic", + "sterea", nullptr, paramsObliqueStereo, + }, + + {EPSG_NAME_METHOD_ORTHOGRAPHIC, EPSG_CODE_METHOD_ORTHOGRAPHIC, + "Orthographic", "ortho", nullptr, paramsNatOrigin}, + + {PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL, 0, "Orthographic", "ortho", "f=0", + paramsNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_PATTERSON, 0, "Patterson", "patterson", nullptr, + paramsLonNatOrigin}, + + {EPSG_NAME_METHOD_AMERICAN_POLYCONIC, EPSG_CODE_METHOD_AMERICAN_POLYCONIC, + "Polyconic", "poly", nullptr, paramsNatOrigin}, + + {EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, "Polar_Stereographic", + "stere", nullptr, paramsObliqueStereo}, + + {EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, "Polar_Stereographic", + "stere", nullptr, paramsPolarStereo}, + + {PROJ_WKT2_NAME_METHOD_ROBINSON, 0, "Robinson", "robin", nullptr, + paramsLonNatOriginLongitudeCentre}, + + {PROJ_WKT2_NAME_METHOD_SINUSOIDAL, 0, "Sinusoidal", "sinu", nullptr, + paramsLonNatOriginLongitudeCentre}, + + {PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, 0, "Stereographic", "stere", nullptr, + paramsObliqueStereo}, + + {PROJ_WKT2_NAME_METHOD_TIMES, 0, "Times", "times", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, 0, "VanDerGrinten", "vandg", "R_A", + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_I, 0, "Wagner_I", "wag1", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_II, 0, "Wagner_II", "wag2", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_III, 0, "Wagner_III", "wag3", nullptr, + paramsWag3}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_IV, 0, "Wagner_IV", "wag4", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_V, 0, "Wagner_V", "wag5", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_VI, 0, "Wagner_VI", "wag6", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_VII, 0, "Wagner_VII", "wag7", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE, 0, + "Quadrilateralized_Spherical_Cube", "qsc", nullptr, paramsNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT, 0, + "Spherical_Cross_Track_Height", "sch", nullptr, paramsSch}, + + // The following methods have just the WKT <--> PROJ string mapping, but + // no setter. Similarly to GDAL + + {"Aitoff", 0, "Aitoff", "aitoff", nullptr, paramsLonNatOrigin}, + + {"Winkel I", 0, "Winkel_I", "wink1", nullptr, paramsWink1}, + + {"Winkel II", 0, "Winkel_II", "wink2", nullptr, paramsWink2}, + + {"Winkel Tripel", 0, "Winkel_Tripel", "wintri", nullptr, paramsWink2}, + + {"Craster Parabolic", 0, "Craster_Parabolic", "crast", nullptr, + paramsLonNatOrigin}, + + {"Loximuthal", 0, "Loximuthal", "loxim", nullptr, paramsLoxim}, + + {"Quartic Authalic", 0, "Quartic_Authalic", "qua_aut", nullptr, + paramsLonNatOrigin}, + + {"Transverse Cylindrical Equal Area", 0, + "Transverse_Cylindrical_Equal_Area", "tcea", nullptr, paramsObliqueStereo}, + + {EPSG_NAME_METHOD_EQUAL_EARTH, EPSG_CODE_METHOD_EQUAL_EARTH, nullptr, + "eqearth", nullptr, paramsLonNatOrigin}, + + {EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR, + EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, "Laborde_Oblique_Mercator", + "labrd", nullptr, paramsLabordeObliqueMercator}, + + {EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE, + EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, nullptr, "nsper", nullptr, + paramsVerticalPerspective}, + + {EPSG_NAME_METHOD_COLOMBIA_URBAN, EPSG_CODE_METHOD_COLOMBIA_URBAN, nullptr, + "col_urban", nullptr, paramsColombiaUrban}, +}; + +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 } + +const struct MethodNameCode methodNameCodes[] = { + // Projection methods + METHOD_NAME_CODE(TRANSVERSE_MERCATOR), + METHOD_NAME_CODE(TRANSVERSE_MERCATOR_SOUTH_ORIENTATED), + METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_1SP), METHOD_NAME_CODE(NZMG), + METHOD_NAME_CODE(TUNISIA_MAPPING_GRID), METHOD_NAME_CODE(ALBERS_EQUAL_AREA), + METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP), + METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM), + METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN), + METHOD_NAME_CODE(MODIFIED_AZIMUTHAL_EQUIDISTANT), + METHOD_NAME_CODE(GUAM_PROJECTION), METHOD_NAME_CODE(BONNE), + METHOD_NAME_CODE(LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL), + METHOD_NAME_CODE(LAMBERT_CYLINDRICAL_EQUAL_AREA), + METHOD_NAME_CODE(CASSINI_SOLDNER), + METHOD_NAME_CODE(EQUIDISTANT_CYLINDRICAL), + METHOD_NAME_CODE(EQUIDISTANT_CYLINDRICAL_SPHERICAL), + METHOD_NAME_CODE(HOTINE_OBLIQUE_MERCATOR_VARIANT_A), + METHOD_NAME_CODE(HOTINE_OBLIQUE_MERCATOR_VARIANT_B), + METHOD_NAME_CODE(KROVAK_NORTH_ORIENTED), METHOD_NAME_CODE(KROVAK), + METHOD_NAME_CODE(LAMBERT_AZIMUTHAL_EQUAL_AREA), + METHOD_NAME_CODE(POPULAR_VISUALISATION_PSEUDO_MERCATOR), + METHOD_NAME_CODE(MERCATOR_VARIANT_A), METHOD_NAME_CODE(MERCATOR_VARIANT_B), + METHOD_NAME_CODE(OBLIQUE_STEREOGRAPHIC), + METHOD_NAME_CODE(AMERICAN_POLYCONIC), + METHOD_NAME_CODE(POLAR_STEREOGRAPHIC_VARIANT_A), + METHOD_NAME_CODE(POLAR_STEREOGRAPHIC_VARIANT_B), + METHOD_NAME_CODE(EQUAL_EARTH), METHOD_NAME_CODE(LABORDE_OBLIQUE_MERCATOR), + METHOD_NAME_CODE(VERTICAL_PERSPECTIVE), METHOD_NAME_CODE(COLOMBIA_URBAN), + // Other conversions + METHOD_NAME_CODE(CHANGE_VERTICAL_UNIT), + METHOD_NAME_CODE(HEIGHT_DEPTH_REVERSAL), + METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_2D), + METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_3D), + METHOD_NAME_CODE(GEOGRAPHIC_GEOCENTRIC), + // Transformations + METHOD_NAME_CODE(LONGITUDE_ROTATION), + METHOD_NAME_CODE(AFFINE_PARAMETRIC_TRANSFORMATION), + METHOD_NAME_CODE(COORDINATE_FRAME_GEOCENTRIC), + METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_2D), + METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_3D), + METHOD_NAME_CODE(POSITION_VECTOR_GEOCENTRIC), + METHOD_NAME_CODE(POSITION_VECTOR_GEOGRAPHIC_2D), + METHOD_NAME_CODE(POSITION_VECTOR_GEOGRAPHIC_3D), + METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOCENTRIC), + METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D), + METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D), + METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC), + METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D), + METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D), + METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC), + METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D), + METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOCENTRIC), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOCENTRIC), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D), + METHOD_NAME_CODE(MOLODENSKY), METHOD_NAME_CODE(ABRIDGED_MOLODENSKY), + METHOD_NAME_CODE(GEOGRAPHIC2D_OFFSETS), + METHOD_NAME_CODE(GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS), + METHOD_NAME_CODE(GEOGRAPHIC3D_OFFSETS), METHOD_NAME_CODE(VERTICAL_OFFSET), + METHOD_NAME_CODE(NTV2), METHOD_NAME_CODE(NTV1), METHOD_NAME_CODE(NADCON), + METHOD_NAME_CODE(VERTCON), + 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 } + +const struct ParamNameCode paramNameCodes[] = { + // Parameters of projection methods + PARAM_NAME_CODE(COLATITUDE_CONE_AXIS), + PARAM_NAME_CODE(LATITUDE_OF_NATURAL_ORIGIN), + PARAM_NAME_CODE(LONGITUDE_OF_NATURAL_ORIGIN), + PARAM_NAME_CODE(SCALE_FACTOR_AT_NATURAL_ORIGIN), + PARAM_NAME_CODE(FALSE_EASTING), PARAM_NAME_CODE(FALSE_NORTHING), + PARAM_NAME_CODE(LATITUDE_PROJECTION_CENTRE), + PARAM_NAME_CODE(LONGITUDE_PROJECTION_CENTRE), + PARAM_NAME_CODE(AZIMUTH_INITIAL_LINE), + PARAM_NAME_CODE(ANGLE_RECTIFIED_TO_SKEW_GRID), + PARAM_NAME_CODE(SCALE_FACTOR_INITIAL_LINE), + PARAM_NAME_CODE(EASTING_PROJECTION_CENTRE), + PARAM_NAME_CODE(NORTHING_PROJECTION_CENTRE), + PARAM_NAME_CODE(LATITUDE_PSEUDO_STANDARD_PARALLEL), + PARAM_NAME_CODE(SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL), + PARAM_NAME_CODE(LATITUDE_FALSE_ORIGIN), + PARAM_NAME_CODE(LONGITUDE_FALSE_ORIGIN), + PARAM_NAME_CODE(LATITUDE_1ST_STD_PARALLEL), + PARAM_NAME_CODE(LATITUDE_2ND_STD_PARALLEL), + PARAM_NAME_CODE(EASTING_FALSE_ORIGIN), + PARAM_NAME_CODE(NORTHING_FALSE_ORIGIN), + PARAM_NAME_CODE(LATITUDE_STD_PARALLEL), + PARAM_NAME_CODE(LONGITUDE_OF_ORIGIN), + PARAM_NAME_CODE(ELLIPSOID_SCALE_FACTOR), + PARAM_NAME_CODE(PROJECTION_PLANE_ORIGIN_HEIGHT), + // Parameters of transformations + PARAM_NAME_CODE(SEMI_MAJOR_AXIS_DIFFERENCE), + PARAM_NAME_CODE(FLATTENING_DIFFERENCE), + PARAM_NAME_CODE(LATITUDE_LONGITUDE_DIFFERENCE_FILE), + PARAM_NAME_CODE(GEOID_CORRECTION_FILENAME), + PARAM_NAME_CODE(VERTICAL_OFFSET_FILE), + PARAM_NAME_CODE(LATITUDE_DIFFERENCE_FILE), + PARAM_NAME_CODE(LONGITUDE_DIFFERENCE_FILE), + PARAM_NAME_CODE(UNIT_CONVERSION_SCALAR), PARAM_NAME_CODE(LATITUDE_OFFSET), + PARAM_NAME_CODE(LONGITUDE_OFFSET), PARAM_NAME_CODE(VERTICAL_OFFSET), + PARAM_NAME_CODE(GEOID_UNDULATION), PARAM_NAME_CODE(A0), PARAM_NAME_CODE(A1), + PARAM_NAME_CODE(A2), PARAM_NAME_CODE(B0), PARAM_NAME_CODE(B1), + PARAM_NAME_CODE(B2), PARAM_NAME_CODE(X_AXIS_TRANSLATION), + PARAM_NAME_CODE(Y_AXIS_TRANSLATION), PARAM_NAME_CODE(Z_AXIS_TRANSLATION), + PARAM_NAME_CODE(X_AXIS_ROTATION), PARAM_NAME_CODE(Y_AXIS_ROTATION), + PARAM_NAME_CODE(Z_AXIS_ROTATION), PARAM_NAME_CODE(SCALE_DIFFERENCE), + PARAM_NAME_CODE(RATE_X_AXIS_TRANSLATION), + PARAM_NAME_CODE(RATE_Y_AXIS_TRANSLATION), + PARAM_NAME_CODE(RATE_Z_AXIS_TRANSLATION), + PARAM_NAME_CODE(RATE_X_AXIS_ROTATION), + PARAM_NAME_CODE(RATE_Y_AXIS_ROTATION), + PARAM_NAME_CODE(RATE_Z_AXIS_ROTATION), + PARAM_NAME_CODE(RATE_SCALE_DIFFERENCE), PARAM_NAME_CODE(REFERENCE_EPOCH), + PARAM_NAME_CODE(TRANSFORMATION_REFERENCE_EPOCH), + PARAM_NAME_CODE(ORDINATE_1_EVAL_POINT), + PARAM_NAME_CODE(ORDINATE_2_EVAL_POINT), + PARAM_NAME_CODE(ORDINATE_3_EVAL_POINT), + 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, + common::UnitOfMeasure::Type::SCALE, nullptr}; + +static const ParamMapping *const paramsChangeVerticalUnit[] = { + ¶mUnitConversionScalar, nullptr}; + +static const ParamMapping paramLongitudeOffset = { + EPSG_NAME_PARAMETER_LONGITUDE_OFFSET, EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, + nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping *const paramsLongitudeRotation[] = { + ¶mLongitudeOffset, nullptr}; + +static const ParamMapping paramA0 = { + EPSG_NAME_PARAMETER_A0, EPSG_CODE_PARAMETER_A0, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping paramA1 = { + EPSG_NAME_PARAMETER_A1, EPSG_CODE_PARAMETER_A1, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping paramA2 = { + EPSG_NAME_PARAMETER_A2, EPSG_CODE_PARAMETER_A2, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping paramB0 = { + EPSG_NAME_PARAMETER_B0, EPSG_CODE_PARAMETER_B0, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping paramB1 = { + EPSG_NAME_PARAMETER_B1, EPSG_CODE_PARAMETER_B1, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping paramB2 = { + EPSG_NAME_PARAMETER_B2, EPSG_CODE_PARAMETER_B2, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping *const paramsAffineParametricTransformation[] = { + ¶mA0, ¶mA1, ¶mA2, ¶mB0, ¶mB1, ¶mB2, nullptr}; + +static const ParamMapping paramXTranslation = { + EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramYTranslation = { + EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramZTranslation = { + EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramXRotation = { + EPSG_NAME_PARAMETER_X_AXIS_ROTATION, EPSG_CODE_PARAMETER_X_AXIS_ROTATION, + nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramYRotation = { + EPSG_NAME_PARAMETER_Y_AXIS_ROTATION, EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, + nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramZRotation = { + EPSG_NAME_PARAMETER_Z_AXIS_ROTATION, EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, + nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramScaleDifference = { + EPSG_NAME_PARAMETER_SCALE_DIFFERENCE, EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, + nullptr, common::UnitOfMeasure::Type::SCALE, nullptr}; + +static const ParamMapping *const paramsHelmert3[] = { + ¶mXTranslation, ¶mYTranslation, ¶mZTranslation, nullptr}; + +static const ParamMapping *const paramsHelmert7[] = { + ¶mXTranslation, ¶mYTranslation, + ¶mZTranslation, ¶mXRotation, + ¶mYRotation, ¶mZRotation, + ¶mScaleDifference, nullptr}; + +static const ParamMapping paramRateXTranslation = { + EPSG_NAME_PARAMETER_RATE_X_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateYTranslation = { + EPSG_NAME_PARAMETER_RATE_Y_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateZTranslation = { + EPSG_NAME_PARAMETER_RATE_Z_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateXRotation = { + EPSG_NAME_PARAMETER_RATE_X_AXIS_ROTATION, + EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateYRotation = { + EPSG_NAME_PARAMETER_RATE_Y_AXIS_ROTATION, + EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateZRotation = { + EPSG_NAME_PARAMETER_RATE_Z_AXIS_ROTATION, + EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateScaleDifference = { + EPSG_NAME_PARAMETER_RATE_SCALE_DIFFERENCE, + EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, nullptr, + common::UnitOfMeasure::Type::SCALE, nullptr}; + +static const ParamMapping paramReferenceEpoch = { + EPSG_NAME_PARAMETER_REFERENCE_EPOCH, EPSG_CODE_PARAMETER_REFERENCE_EPOCH, + nullptr, common::UnitOfMeasure::Type::TIME, nullptr}; + +static const ParamMapping *const paramsHelmert15[] = { + ¶mXTranslation, ¶mYTranslation, + ¶mZTranslation, ¶mXRotation, + ¶mYRotation, ¶mZRotation, + ¶mScaleDifference, ¶mRateXTranslation, + ¶mRateYTranslation, ¶mRateZTranslation, + ¶mRateXRotation, ¶mRateYRotation, + ¶mRateZRotation, ¶mRateScaleDifference, + ¶mReferenceEpoch, nullptr}; + +static const ParamMapping paramOrdinate1EvalPoint = { + EPSG_NAME_PARAMETER_ORDINATE_1_EVAL_POINT, + EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramOrdinate2EvalPoint = { + EPSG_NAME_PARAMETER_ORDINATE_2_EVAL_POINT, + EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramOrdinate3EvalPoint = { + EPSG_NAME_PARAMETER_ORDINATE_3_EVAL_POINT, + EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping *const paramsMolodenskyBadekas[] = { + ¶mXTranslation, + ¶mYTranslation, + ¶mZTranslation, + ¶mXRotation, + ¶mYRotation, + ¶mZRotation, + ¶mScaleDifference, + ¶mOrdinate1EvalPoint, + ¶mOrdinate2EvalPoint, + ¶mOrdinate3EvalPoint, + nullptr}; + +static const ParamMapping paramSemiMajorAxisDifference = { + EPSG_NAME_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE, + EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramFlatteningDifference = { + EPSG_NAME_PARAMETER_FLATTENING_DIFFERENCE, + EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping *const paramsMolodensky[] = { + ¶mXTranslation, ¶mYTranslation, + ¶mZTranslation, ¶mSemiMajorAxisDifference, + ¶mFlatteningDifference, nullptr}; + +static const ParamMapping paramLatitudeOffset = { + EPSG_NAME_PARAMETER_LATITUDE_OFFSET, EPSG_CODE_PARAMETER_LATITUDE_OFFSET, + nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping *const paramsGeographic2DOffsets[] = { + ¶mLatitudeOffset, ¶mLongitudeOffset, nullptr}; + +static const ParamMapping paramGeoidUndulation = { + EPSG_NAME_PARAMETER_GEOID_UNDULATION, EPSG_CODE_PARAMETER_GEOID_UNDULATION, + nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping *const paramsGeographic2DWithHeightOffsets[] = { + ¶mLatitudeOffset, ¶mLongitudeOffset, ¶mGeoidUndulation, + nullptr}; + +static const ParamMapping paramVerticalOffset = { + EPSG_NAME_PARAMETER_VERTICAL_OFFSET, EPSG_CODE_PARAMETER_VERTICAL_OFFSET, + nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping *const paramsGeographic3DOffsets[] = { + ¶mLatitudeOffset, ¶mLongitudeOffset, ¶mVerticalOffset, nullptr}; + +static const ParamMapping *const paramsVerticalOffsets[] = { + ¶mVerticalOffset, nullptr}; + +static const ParamMapping paramLatitudeLongitudeDifferenceFile = { + EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping *const paramsNTV2[] = { + ¶mLatitudeLongitudeDifferenceFile, nullptr}; + +static const ParamMapping paramGeocentricTranslationFile = { + EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, + EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping + *const paramsGeocentricTranslationGridInterpolationIGN[] = { + ¶mGeocentricTranslationFile, nullptr}; + +static const ParamMapping paramLatitudeDifferenceFile = { + EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping paramLongitudeDifferenceFile = { + EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping *const paramsNADCON[] = { + ¶mLatitudeDifferenceFile, ¶mLongitudeDifferenceFile, nullptr}; + +static const ParamMapping paramVerticalOffsetFile = { + EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, + EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping *const paramsVERTCON[] = {¶mVerticalOffsetFile, + nullptr}; + +static const ParamMapping paramSouthPoleLatGRIB = { + PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION, 0, nullptr, + common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping paramSouthPoleLonGRIB = { + PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION, 0, nullptr, + common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping paramAxisRotationGRIB = { + PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION, 0, nullptr, + common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping *const paramsPoleRotationGRIBConvention[] = { + ¶mSouthPoleLatGRIB, ¶mSouthPoleLonGRIB, ¶mAxisRotationGRIB, + nullptr}; + +static const MethodMapping otherMethodMappings[] = { + {EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT, + EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT, nullptr, nullptr, nullptr, + paramsChangeVerticalUnit}, + {EPSG_NAME_METHOD_HEIGHT_DEPTH_REVERSAL, + EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL, nullptr, nullptr, nullptr, + paramsChangeVerticalUnit}, + {EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_2D, + EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D, nullptr, nullptr, nullptr, + nullptr}, + {EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_3D, + EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D, nullptr, nullptr, nullptr, + nullptr}, + {EPSG_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC, + EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC, nullptr, nullptr, nullptr, + nullptr}, + {EPSG_NAME_METHOD_LONGITUDE_ROTATION, EPSG_CODE_METHOD_LONGITUDE_ROTATION, + nullptr, nullptr, nullptr, paramsLongitudeRotation}, + {EPSG_NAME_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION, + EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION, nullptr, nullptr, + nullptr, paramsAffineParametricTransformation}, + + {PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION, 0, nullptr, nullptr, + nullptr, paramsPoleRotationGRIBConvention}, + + {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, nullptr, nullptr, + nullptr, paramsHelmert3}, + {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D, nullptr, nullptr, + nullptr, paramsHelmert3}, + {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D, nullptr, nullptr, + nullptr, paramsHelmert3}, + + {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOCENTRIC, + EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC, nullptr, nullptr, nullptr, + paramsHelmert7}, + {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, + paramsHelmert7}, + {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, + paramsHelmert7}, + + {EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC, + EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC, nullptr, nullptr, nullptr, + paramsHelmert7}, + {EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, + paramsHelmert7}, + {EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, + paramsHelmert7}, + + {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC, + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC, nullptr, + nullptr, nullptr, paramsHelmert15}, + {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D, nullptr, + nullptr, nullptr, paramsHelmert15}, + {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D, nullptr, + nullptr, nullptr, paramsHelmert15}, + + {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC, + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC, nullptr, + nullptr, nullptr, paramsHelmert15}, + {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D, nullptr, + nullptr, nullptr, paramsHelmert15}, + {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D, nullptr, + nullptr, nullptr, paramsHelmert15}, + + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + + {EPSG_NAME_METHOD_MOLODENSKY, EPSG_CODE_METHOD_MOLODENSKY, nullptr, nullptr, + nullptr, paramsMolodensky}, + + {EPSG_NAME_METHOD_ABRIDGED_MOLODENSKY, EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY, + nullptr, nullptr, nullptr, paramsMolodensky}, + + {EPSG_NAME_METHOD_GEOGRAPHIC2D_OFFSETS, + EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS, nullptr, nullptr, nullptr, + paramsGeographic2DOffsets}, + + {EPSG_NAME_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS, + EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS, nullptr, nullptr, + nullptr, paramsGeographic2DWithHeightOffsets}, + + {EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSETS, + EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS, nullptr, nullptr, nullptr, + paramsGeographic3DOffsets}, + + {EPSG_NAME_METHOD_VERTICAL_OFFSET, EPSG_CODE_METHOD_VERTICAL_OFFSET, + nullptr, nullptr, nullptr, paramsVerticalOffsets}, + + {EPSG_NAME_METHOD_NTV2, EPSG_CODE_METHOD_NTV2, nullptr, nullptr, nullptr, + paramsNTV2}, + + {EPSG_NAME_METHOD_NTV1, EPSG_CODE_METHOD_NTV1, nullptr, nullptr, nullptr, + paramsNTV2}, + + {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN, + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN, nullptr, + nullptr, nullptr, paramsGeocentricTranslationGridInterpolationIGN}, + + {EPSG_NAME_METHOD_NADCON, EPSG_CODE_METHOD_NADCON, nullptr, nullptr, + nullptr, paramsNADCON}, + + {EPSG_NAME_METHOD_VERTCON, EPSG_CODE_METHOD_VERTCON, nullptr, nullptr, + nullptr, paramsVERTCON}, + {EPSG_NAME_METHOD_VERTCON_OLDNAME, EPSG_CODE_METHOD_VERTCON, nullptr, + nullptr, nullptr, paramsVERTCON}, +}; + +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 +getMappingsFromPROJName(const std::string &projName) { + std::vector 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 ¶m) { + 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 + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END diff -Nru proj-7.2.0/src/iso19111/operation/parammappings.hpp proj-7.2.1/src/iso19111/operation/parammappings.hpp --- proj-7.2.0/src/iso19111/operation/parammappings.hpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/parammappings.hpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,114 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 +getMappingsFromPROJName(const std::string &projName); +const ParamMapping *getMapping(const MethodMapping *mapping, + const OperationParameterNNPtr ¶m); +const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping, + const std::string &wkt1_name); + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // PARAMMAPPINGS_HPP diff -Nru proj-7.2.0/src/iso19111/operation/projbasedoperation.cpp proj-7.2.1/src/iso19111/operation/projbasedoperation.cpp --- proj-7.2.0/src/iso19111/operation/projbasedoperation.cpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/projbasedoperation.cpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,316 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +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 &accuracies) { + auto method = OperationMethod::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + "PROJ-based operation method: " + PROJString), + std::vector{}); + auto op = PROJBasedOperation::nn_make_shared(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 &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{}); + auto op = PROJBasedOperation::nn_make_shared(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( + 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(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 ¶mValue : 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(*this); + op->assignSelf(op); + op->setCRSs(this, false); + return util::nn_static_pointer_cast(op); +} + +// --------------------------------------------------------------------------- + +std::set +PROJBasedOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const { + std::set 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 -Nru proj-7.2.0/src/iso19111/operation/singleoperation.cpp proj-7.2.1/src/iso19111/operation/singleoperation.cpp --- proj-7.2.0/src/iso19111/operation/singleoperation.cpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/singleoperation.cpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,2218 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +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()) {} + +// --------------------------------------------------------------------------- + +CoordinateOperation::CoordinateOperation(const CoordinateOperation &other) + : ObjectUsage(other), d(internal::make_unique(*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 & +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 & +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 & +CoordinateOperation::sourceCoordinateEpoch() const { + return d->sourceCoordinateEpoch_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the target epoch of coordinates. + * + * @return target epoch of coordinates, or empty. + */ +const util::optional & +CoordinateOperation::targetCoordinateEpoch() const { + return d->targetCoordinateEpoch_; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperation::setWeakSourceTargetCRS( + std::weak_ptr sourceCRSIn, std::weak_ptr 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(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 &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( + shared_from_this().as_nullable())); + if (!swapSource && !swapTarget) { + return l_this; + } + std::vector 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( + ConcatenatedOperation::createComputeMetadata(subOps, true)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateOperationNNPtr CoordinateOperation::shallowClone() const { + return _shallowClone(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +OperationMethod::OperationMethod() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +OperationMethod::OperationMethod(const OperationMethod &other) + : IdentifiedObject(other), d(internal::make_unique(*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 &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 & +OperationMethod::formulaCitation() PROJ_PURE_DEFN { + return d->formulaCitation_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the parameters of this operation method. + * + * @return the parameters. + */ +const std::vector & +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 ¶meters) { + OperationMethodNNPtr method( + OperationMethod::nn_make_shared()); + 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 ¶meters) { + std::vector 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(other); + if (otherOM == nullptr || + !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { + return false; + } + // TODO test formula and formulaCitation + const auto ¶ms = 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 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 ¶meterIn, + const ParameterValueNNPtr &valueIn) + : parameter(parameterIn), parameterValue(valueIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +OperationParameterValue::OperationParameterValue( + const OperationParameterValue &other) + : GeneralParameterValue(other), + d(internal::make_unique(*other.d)) {} + +// --------------------------------------------------------------------------- + +OperationParameterValue::OperationParameterValue( + const OperationParameterNNPtr ¶meterIn, + const ParameterValueNNPtr &valueIn) + : GeneralParameterValue(), + d(internal::make_unique(parameterIn, valueIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a OperationParameterValue. + * + * @param parameterIn Parameter (definition). + * @param valueIn Parameter value. + * @return a new OperationParameterValue. + */ +OperationParameterValueNNPtr +OperationParameterValue::create(const OperationParameterNNPtr ¶meterIn, + const ParameterValueNNPtr &valueIn) { + return OperationParameterValue::nn_make_shared( + 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 ¶mName, double &val, + const common::UnitOfMeasure *&unit, int ¶mEPSGCode) { + 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(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()); + 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(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 parameterValues_{}; + OperationMethodNNPtr method_; + + explicit Private(const OperationMethodNNPtr &methodIn) + : method_(methodIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn) + : d(internal::make_unique(methodIn)) {} + +// --------------------------------------------------------------------------- + +SingleOperation::SingleOperation(const SingleOperation &other) + : +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + CoordinateOperation(other), +#endif + d(internal::make_unique(*other.d)) { +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +SingleOperation::~SingleOperation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the parameter values. + * + * @return the parameter values. + */ +const std::vector & +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 &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 ¶mName, + int epsg_code) const noexcept { + if (epsg_code) { + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = opParamvalue->parameter(); + if (parameter->getEPSGCode() == epsg_code) { + return opParamvalue->parameterValue(); + } + } + } + } + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = 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( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = 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( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = 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 ¶mName, + 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 &accuracies) { + return util::nn_static_pointer_cast( + 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(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(sourceCRS().get()); + auto otherGeodCRS = dynamic_cast( + 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(this); + auto otherTransf = static_cast(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(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 candidateIndices(otherValuesSize, true); + bool equivalent = true; + bool foundMissingArgs = valuesSize != otherValuesSize; + + for (size_t i = 0; equivalent && i < valuesSize; i++) { + auto opParamvalue = + dynamic_cast(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( + 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(this); + auto otherConv = dynamic_cast(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 +SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const { + std::set res; + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast( + 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 SingleOperation::validateParameters() const { + std::list 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( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = 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 == ¶mLatitudeNatOrigin) { + // 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 ¶meter = 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( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = 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 measure_{}; + std::unique_ptr stringValue_{}; + int integerValue_{}; + bool booleanValue_{}; + + explicit Private(const common::Measure &valueIn) + : type_(ParameterValue::Type::MEASURE), + measure_(internal::make_unique(valueIn)) {} + + Private(const std::string &stringValueIn, ParameterValue::Type typeIn) + : type_(typeIn), + stringValue_(internal::make_unique(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(measureIn)) {} + +// --------------------------------------------------------------------------- + +ParameterValue::ParameterValue(const std::string &stringValueIn, + ParameterValue::Type typeIn) + : d(internal::make_unique(stringValueIn, typeIn)) {} + +// --------------------------------------------------------------------------- + +ParameterValue::ParameterValue(int integerValueIn) + : d(internal::make_unique(integerValueIn)) {} + +// --------------------------------------------------------------------------- + +ParameterValue::ParameterValue(bool booleanValueIn) + : d(internal::make_unique(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(measureIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a ParameterValue from a string value. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(const char *stringValueIn) { + return ParameterValue::nn_make_shared( + 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( + 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( + 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(integerValueIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a ParameterValue from a boolean value. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(bool booleanValueIn) { + return ParameterValue::nn_make_shared(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(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 ¶mValue : 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(sourceCRS().get()); + auto targetCRSGeog = + dynamic_cast(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(sourceCRS().get()); + auto targetCRSGeod = + dynamic_cast(targetCRS().get()); + if (sourceCRSGeod && targetCRSGeod) { + auto sourceCRSGeog = + dynamic_cast(sourceCRSGeod); + auto targetCRSGeog = + dynamic_cast(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(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 -Nru proj-7.2.0/src/iso19111/operation/transformation.cpp proj-7.2.1/src/iso19111/operation/transformation.cpp --- proj-7.2.0/src/iso19111/operation/transformation.cpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/transformation.cpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,3274 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +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 &values, + const std::vector &accuracies) + : SingleOperation(methodIn), d(internal::make_unique()) { + 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(*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(*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(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 +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 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( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = 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 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 &values, + const std::vector &accuracies) { + if (methodIn->parameters().size() != values.size()) { + throw InvalidOperation( + "Inconsistent number of parameters and parameter values"); + } + auto transf = Transformation::nn_make_shared( + 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 ¶meters, + const std::vector &values, + const std::vector + &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 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 &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(sourceCRSIn.get()); + auto targetCRSGeod = + dynamic_cast(targetCRSIn.get()); + isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() && + targetCRSGeod && targetCRSGeod->isGeocentric(); + if (isGeocentric) { + isGeog2D = false; + isGeog3D = false; + return; + } + isGeocentric = false; + + auto sourceCRSGeog = + dynamic_cast(sourceCRSIn.get()); + auto targetCRSGeog = + dynamic_cast(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( + (*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 &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 &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 &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 &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 &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 &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 &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 &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 &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 &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(transformSourceCRS.get()) + ? util::nn_static_pointer_cast( + crs::GeographicCRS::EPSG_4326) + : util::nn_static_pointer_cast( + 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 &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 &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 &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 &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 +buildAccuracyZero() { + return std::vector{ + 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 &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 &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 &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 &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 &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( + 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( + 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(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(forward); + conv->assignSelf(conv); + return conv; +} + +// --------------------------------------------------------------------------- + +TransformationNNPtr InverseTransformation::inverseAsTransformation() const { + return NN_NO_CHECK( + util::nn_dynamic_pointer_cast(forwardOperation_)); +} + +// --------------------------------------------------------------------------- + +void InverseTransformation::_exportToWKT(io::WKTFormatter *formatter) const { + + auto approxInverse = createApproximateInverseIfPossible( + util::nn_dynamic_pointer_cast(forwardOperation_).get()); + if (approxInverse) { + approxInverse->_exportToWKT(formatter); + } else { + Transformation::_exportToWKT(formatter); + } +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr InverseTransformation::_shallowClone() const { + auto op = InverseTransformation::nn_make_shared( + inverseAsTransformation()->shallowClone()); + op->assignSelf(op); + op->setCRSs(this, false); + return util::nn_static_pointer_cast(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 &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( + 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{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{ + 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{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{ + 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{ + 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{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{ + 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); + if (!geogCRS) { + auto compoundCRS = util::nn_dynamic_pointer_cast(crs); + if (compoundCRS) { + const auto &components = compoundCRS->componentReferenceSystems(); + if (!components.empty()) { + geogCRS = util::nn_dynamic_pointer_cast( + components[0]); + if (!geogCRS) { + auto boundCRS = + util::nn_dynamic_pointer_cast( + components[0]); + if (boundCRS) { + geogCRS = + util::nn_dynamic_pointer_cast( + boundCRS->baseCRS()); + } + } + } + } else { + auto boundCRS = util::nn_dynamic_pointer_cast(crs); + if (boundCRS) { + geogCRS = util::nn_dynamic_pointer_cast( + 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( + *(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(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( + *(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(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(sourceCRS().get()); + auto targetCRSGeog = + dynamic_cast(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(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + "Can apply Molodensky only to GeographicCRS"); + } + + auto targetCRSGeog = + dynamic_cast(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(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + "Can apply Geographic 2D offsets only to GeographicCRS"); + } + + auto targetCRSGeog = + dynamic_cast(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(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + "Can apply Geographic 3D offsets only to GeographicCRS"); + } + + auto targetCRSGeog = + dynamic_cast(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(sourceCRS().get()); + if (!sourceCRSGeog) { + auto sourceCRSCompound = + dynamic_cast(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(targetCRS().get()); + if (!targetCRSGeog) { + auto targetCRSCompound = + dynamic_cast(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(sourceCRS().get()); + if (!sourceCRSVert) { + throw io::FormattingException( + "Can apply Vertical offset only to VerticalCRS"); + } + + auto targetCRSVert = + dynamic_cast(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( + 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(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + auto targetCRSGeog = + dynamic_cast(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(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + auto targetCRSGeog = + dynamic_cast(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 -Nru proj-7.2.0/src/iso19111/operation/vectorofvaluesparams.cpp proj-7.2.1/src/iso19111/operation/vectorofvaluesparams.cpp --- proj-7.2.0/src/iso19111/operation/vectorofvaluesparams.cpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/vectorofvaluesparams.cpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,116 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 buildParameterValueFromMeasure( + const std::initializer_list &list) { + std::vector res; + for (const auto &v : list) { + res.emplace_back(ParameterValue::create(v)); + } + return res; +} + +VectorOfValues::VectorOfValues(std::initializer_list list) + : std::vector(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 -Nru proj-7.2.0/src/iso19111/operation/vectorofvaluesparams.hpp proj-7.2.1/src/iso19111/operation/vectorofvaluesparams.hpp --- proj-7.2.0/src/iso19111/operation/vectorofvaluesparams.hpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/src/iso19111/operation/vectorofvaluesparams.hpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,101 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 { + VectorOfValues() : std::vector() {} + explicit VectorOfValues(std::initializer_list list) + : std::vector(list) {} + + explicit VectorOfValues(std::initializer_list 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 { + VectorOfParameters() : std::vector() {} + explicit VectorOfParameters( + std::initializer_list list) + : std::vector(list) {} + VectorOfParameters(const VectorOfParameters &) = delete; + + ~VectorOfParameters(); +}; + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // VECTOROFVALUESPARAMS_HPP diff -Nru proj-7.2.0/src/iso19111/static.cpp proj-7.2.1/src/iso19111/static.cpp --- proj-7.2.0/src/iso19111/static.cpp 2020-06-23 16:52:58.000000000 +0000 +++ proj-7.2.1/src/iso19111/static.cpp 2020-12-21 16:29:10.000000000 +0000 @@ -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 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 -Nru proj-7.2.0/src/lib_proj.cmake proj-7.2.1/src/lib_proj.cmake --- proj-7.2.0/src/lib_proj.cmake 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/src/lib_proj.cmake 2020-12-26 18:57:21.000000000 +0000 @@ -198,11 +198,20 @@ 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 -Nru proj-7.2.0/src/Makefile.am proj-7.2.1/src/Makefile.am --- proj-7.2.0/src/Makefile.am 2020-10-26 11:00:19.000000000 +0000 +++ proj-7.2.1/src/Makefile.am 2020-12-26 18:57:21.000000000 +0000 @@ -52,7 +52,7 @@ lib_LTLIBRARIES = libproj.la -libproj_la_LDFLAGS = -no-undefined -version-info 21:0:2 +libproj_la_LDFLAGS = -no-undefined -version-info 21:1:2 libproj_la_LIBADD = @SQLITE3_LIBS@ @TIFF_LIBS@ @CURL_LIBS@ libproj_la_SOURCES = \ @@ -65,7 +65,23 @@ 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 -Nru proj-7.2.0/src/Makefile.in proj-7.2.1/src/Makefile.in --- proj-7.2.0/src/Makefile.in 2020-10-28 12:56:46.000000000 +0000 +++ proj-7.2.1/src/Makefile.in 2020-12-26 18:57:37.000000000 +0000 @@ -150,7 +150,16 @@ am_libproj_la_OBJECTS = iso19111/static.lo iso19111/util.lo \ iso19111/metadata.lo iso19111/common.lo iso19111/crs.lo \ iso19111/datum.lo iso19111/coordinatesystem.lo \ - iso19111/coordinateoperation.lo iso19111/io.lo \ + iso19111/operation/concatenatedoperation.lo \ + iso19111/operation/coordinateoperationfactory.lo \ + iso19111/operation/conversion.lo \ + iso19111/operation/esriparammappings.lo \ + iso19111/operation/oputils.lo \ + iso19111/operation/parammappings.lo \ + iso19111/operation/projbasedoperation.lo \ + iso19111/operation/singleoperation.lo \ + iso19111/operation/transformation.lo \ + iso19111/operation/vectorofvaluesparams.lo iso19111/io.lo \ iso19111/internal.lo iso19111/factory.lo iso19111/c_api.lo \ projections/aeqd.lo projections/adams.lo projections/gnom.lo \ projections/laea.lo projections/mod_ster.lo \ @@ -314,12 +323,21 @@ conversions/$(DEPDIR)/noop.Plo conversions/$(DEPDIR)/set.Plo \ conversions/$(DEPDIR)/unitconvert.Plo \ iso19111/$(DEPDIR)/c_api.Plo iso19111/$(DEPDIR)/common.Plo \ - iso19111/$(DEPDIR)/coordinateoperation.Plo \ iso19111/$(DEPDIR)/coordinatesystem.Plo \ iso19111/$(DEPDIR)/crs.Plo iso19111/$(DEPDIR)/datum.Plo \ iso19111/$(DEPDIR)/factory.Plo iso19111/$(DEPDIR)/internal.Plo \ iso19111/$(DEPDIR)/io.Plo iso19111/$(DEPDIR)/metadata.Plo \ iso19111/$(DEPDIR)/static.Plo iso19111/$(DEPDIR)/util.Plo \ + iso19111/operation/$(DEPDIR)/concatenatedoperation.Plo \ + iso19111/operation/$(DEPDIR)/conversion.Plo \ + iso19111/operation/$(DEPDIR)/coordinateoperationfactory.Plo \ + iso19111/operation/$(DEPDIR)/esriparammappings.Plo \ + iso19111/operation/$(DEPDIR)/oputils.Plo \ + iso19111/operation/$(DEPDIR)/parammappings.Plo \ + iso19111/operation/$(DEPDIR)/projbasedoperation.Plo \ + iso19111/operation/$(DEPDIR)/singleoperation.Plo \ + iso19111/operation/$(DEPDIR)/transformation.Plo \ + iso19111/operation/$(DEPDIR)/vectorofvaluesparams.Plo \ projections/$(DEPDIR)/adams.Plo projections/$(DEPDIR)/aea.Plo \ projections/$(DEPDIR)/aeqd.Plo projections/$(DEPDIR)/airy.Plo \ projections/$(DEPDIR)/aitoff.Plo \ @@ -842,7 +860,7 @@ test228_LDADD = libproj.la @THREAD_LIB@ geodtest_LDADD = libproj.la lib_LTLIBRARIES = libproj.la -libproj_la_LDFLAGS = -no-undefined -version-info 21:0:2 +libproj_la_LDFLAGS = -no-undefined -version-info 21:1:2 libproj_la_LIBADD = @SQLITE3_LIBS@ @TIFF_LIBS@ @CURL_LIBS@ libproj_la_SOURCES = \ pj_list.h proj_internal.h \ @@ -854,7 +872,23 @@ 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 \ @@ -1197,8 +1231,40 @@ iso19111/$(DEPDIR)/$(am__dirstamp) iso19111/coordinatesystem.lo: iso19111/$(am__dirstamp) \ iso19111/$(DEPDIR)/$(am__dirstamp) -iso19111/coordinateoperation.lo: iso19111/$(am__dirstamp) \ - iso19111/$(DEPDIR)/$(am__dirstamp) +iso19111/operation/$(am__dirstamp): + @$(MKDIR_P) iso19111/operation + @: > iso19111/operation/$(am__dirstamp) +iso19111/operation/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) iso19111/operation/$(DEPDIR) + @: > iso19111/operation/$(DEPDIR)/$(am__dirstamp) +iso19111/operation/concatenatedoperation.lo: \ + iso19111/operation/$(am__dirstamp) \ + iso19111/operation/$(DEPDIR)/$(am__dirstamp) +iso19111/operation/coordinateoperationfactory.lo: \ + iso19111/operation/$(am__dirstamp) \ + iso19111/operation/$(DEPDIR)/$(am__dirstamp) +iso19111/operation/conversion.lo: iso19111/operation/$(am__dirstamp) \ + iso19111/operation/$(DEPDIR)/$(am__dirstamp) +iso19111/operation/esriparammappings.lo: \ + iso19111/operation/$(am__dirstamp) \ + iso19111/operation/$(DEPDIR)/$(am__dirstamp) +iso19111/operation/oputils.lo: iso19111/operation/$(am__dirstamp) \ + iso19111/operation/$(DEPDIR)/$(am__dirstamp) +iso19111/operation/parammappings.lo: \ + iso19111/operation/$(am__dirstamp) \ + iso19111/operation/$(DEPDIR)/$(am__dirstamp) +iso19111/operation/projbasedoperation.lo: \ + iso19111/operation/$(am__dirstamp) \ + iso19111/operation/$(DEPDIR)/$(am__dirstamp) +iso19111/operation/singleoperation.lo: \ + iso19111/operation/$(am__dirstamp) \ + iso19111/operation/$(DEPDIR)/$(am__dirstamp) +iso19111/operation/transformation.lo: \ + iso19111/operation/$(am__dirstamp) \ + iso19111/operation/$(DEPDIR)/$(am__dirstamp) +iso19111/operation/vectorofvaluesparams.lo: \ + iso19111/operation/$(am__dirstamp) \ + iso19111/operation/$(DEPDIR)/$(am__dirstamp) iso19111/io.lo: iso19111/$(am__dirstamp) \ iso19111/$(DEPDIR)/$(am__dirstamp) iso19111/internal.lo: iso19111/$(am__dirstamp) \ @@ -1572,6 +1638,8 @@ -rm -f conversions/*.lo -rm -f iso19111/*.$(OBJEXT) -rm -f iso19111/*.lo + -rm -f iso19111/operation/*.$(OBJEXT) + -rm -f iso19111/operation/*.lo -rm -f projections/*.$(OBJEXT) -rm -f projections/*.lo -rm -f tests/*.$(OBJEXT) @@ -1656,7 +1724,6 @@ @AMDEP_TRUE@@am__include@ @am__quote@conversions/$(DEPDIR)/unitconvert.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@iso19111/$(DEPDIR)/c_api.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@iso19111/$(DEPDIR)/common.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@iso19111/$(DEPDIR)/coordinateoperation.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@iso19111/$(DEPDIR)/coordinatesystem.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@iso19111/$(DEPDIR)/crs.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@iso19111/$(DEPDIR)/datum.Plo@am__quote@ # am--include-marker @@ -1666,6 +1733,16 @@ @AMDEP_TRUE@@am__include@ @am__quote@iso19111/$(DEPDIR)/metadata.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@iso19111/$(DEPDIR)/static.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@iso19111/$(DEPDIR)/util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@iso19111/operation/$(DEPDIR)/concatenatedoperation.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@iso19111/operation/$(DEPDIR)/conversion.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@iso19111/operation/$(DEPDIR)/coordinateoperationfactory.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@iso19111/operation/$(DEPDIR)/esriparammappings.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@iso19111/operation/$(DEPDIR)/oputils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@iso19111/operation/$(DEPDIR)/parammappings.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@iso19111/operation/$(DEPDIR)/projbasedoperation.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@iso19111/operation/$(DEPDIR)/singleoperation.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@iso19111/operation/$(DEPDIR)/transformation.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@iso19111/operation/$(DEPDIR)/vectorofvaluesparams.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@projections/$(DEPDIR)/adams.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@projections/$(DEPDIR)/aea.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@projections/$(DEPDIR)/aeqd.Plo@am__quote@ # am--include-marker @@ -1851,6 +1928,7 @@ -rm -rf .libs _libs -rm -rf conversions/.libs conversions/_libs -rm -rf iso19111/.libs iso19111/_libs + -rm -rf iso19111/operation/.libs iso19111/operation/_libs -rm -rf projections/.libs projections/_libs -rm -rf transformations/.libs transformations/_libs install-includeHEADERS: $(include_HEADERS) @@ -2169,6 +2247,8 @@ -rm -f conversions/$(am__dirstamp) -rm -f iso19111/$(DEPDIR)/$(am__dirstamp) -rm -f iso19111/$(am__dirstamp) + -rm -f iso19111/operation/$(DEPDIR)/$(am__dirstamp) + -rm -f iso19111/operation/$(am__dirstamp) -rm -f projections/$(DEPDIR)/$(am__dirstamp) -rm -f projections/$(am__dirstamp) -rm -f tests/$(DEPDIR)/$(am__dirstamp) @@ -2260,7 +2340,6 @@ -rm -f conversions/$(DEPDIR)/unitconvert.Plo -rm -f iso19111/$(DEPDIR)/c_api.Plo -rm -f iso19111/$(DEPDIR)/common.Plo - -rm -f iso19111/$(DEPDIR)/coordinateoperation.Plo -rm -f iso19111/$(DEPDIR)/coordinatesystem.Plo -rm -f iso19111/$(DEPDIR)/crs.Plo -rm -f iso19111/$(DEPDIR)/datum.Plo @@ -2270,6 +2349,16 @@ -rm -f iso19111/$(DEPDIR)/metadata.Plo -rm -f iso19111/$(DEPDIR)/static.Plo -rm -f iso19111/$(DEPDIR)/util.Plo + -rm -f iso19111/operation/$(DEPDIR)/concatenatedoperation.Plo + -rm -f iso19111/operation/$(DEPDIR)/conversion.Plo + -rm -f iso19111/operation/$(DEPDIR)/coordinateoperationfactory.Plo + -rm -f iso19111/operation/$(DEPDIR)/esriparammappings.Plo + -rm -f iso19111/operation/$(DEPDIR)/oputils.Plo + -rm -f iso19111/operation/$(DEPDIR)/parammappings.Plo + -rm -f iso19111/operation/$(DEPDIR)/projbasedoperation.Plo + -rm -f iso19111/operation/$(DEPDIR)/singleoperation.Plo + -rm -f iso19111/operation/$(DEPDIR)/transformation.Plo + -rm -f iso19111/operation/$(DEPDIR)/vectorofvaluesparams.Plo -rm -f projections/$(DEPDIR)/adams.Plo -rm -f projections/$(DEPDIR)/aea.Plo -rm -f projections/$(DEPDIR)/aeqd.Plo @@ -2514,7 +2603,6 @@ -rm -f conversions/$(DEPDIR)/unitconvert.Plo -rm -f iso19111/$(DEPDIR)/c_api.Plo -rm -f iso19111/$(DEPDIR)/common.Plo - -rm -f iso19111/$(DEPDIR)/coordinateoperation.Plo -rm -f iso19111/$(DEPDIR)/coordinatesystem.Plo -rm -f iso19111/$(DEPDIR)/crs.Plo -rm -f iso19111/$(DEPDIR)/datum.Plo @@ -2524,6 +2612,16 @@ -rm -f iso19111/$(DEPDIR)/metadata.Plo -rm -f iso19111/$(DEPDIR)/static.Plo -rm -f iso19111/$(DEPDIR)/util.Plo + -rm -f iso19111/operation/$(DEPDIR)/concatenatedoperation.Plo + -rm -f iso19111/operation/$(DEPDIR)/conversion.Plo + -rm -f iso19111/operation/$(DEPDIR)/coordinateoperationfactory.Plo + -rm -f iso19111/operation/$(DEPDIR)/esriparammappings.Plo + -rm -f iso19111/operation/$(DEPDIR)/oputils.Plo + -rm -f iso19111/operation/$(DEPDIR)/parammappings.Plo + -rm -f iso19111/operation/$(DEPDIR)/projbasedoperation.Plo + -rm -f iso19111/operation/$(DEPDIR)/singleoperation.Plo + -rm -f iso19111/operation/$(DEPDIR)/transformation.Plo + -rm -f iso19111/operation/$(DEPDIR)/vectorofvaluesparams.Plo -rm -f projections/$(DEPDIR)/adams.Plo -rm -f projections/$(DEPDIR)/aea.Plo -rm -f projections/$(DEPDIR)/aeqd.Plo diff -Nru proj-7.2.0/src/proj_api.h proj-7.2.1/src/proj_api.h --- proj-7.2.0/src/proj_api.h 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/src/proj_api.h 2020-12-26 18:57:21.000000000 +0000 @@ -38,7 +38,7 @@ #endif #ifndef PJ_VERSION -#define PJ_VERSION 720 +#define PJ_VERSION 721 #endif #ifdef PROJ_RENAME_SYMBOLS diff -Nru proj-7.2.0/src/projections/tmerc.cpp proj-7.2.1/src/projections/tmerc.cpp --- proj-7.2.0/src/projections/tmerc.cpp 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/src/projections/tmerc.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -203,11 +203,13 @@ return proj_coord_error().lp; } g = .5 * (h - 1. / h); - h = cos (P->phi0 + xy.y / Q->esp); + /* D, as in equation 8-8 of USGS "Map Projections - A Working Manual" */ + const double D = P->phi0 + xy.y / Q->esp; + h = cos (D); lp.phi = asin(sqrt((1. - h * h) / (1. + g * g))); /* Make sure that phi is on the correct hemisphere when false northing is used */ - if (xy.y < 0. && -lp.phi+P->phi0 < 0.0) lp.phi = -lp.phi; + lp.phi = copysign(lp.phi, D); lp.lam = (g != 0.0 || h != 0.0) ? atan2 (g, h) : 0.; return lp; diff -Nru proj-7.2.0/src/proj.h proj-7.2.1/src/proj.h --- proj-7.2.0/src/proj.h 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/src/proj.h 2020-12-26 18:57:21.000000000 +0000 @@ -172,7 +172,7 @@ /* The version numbers should be updated with every release! **/ #define PROJ_VERSION_MAJOR 7 #define PROJ_VERSION_MINOR 2 -#define PROJ_VERSION_PATCH 0 +#define PROJ_VERSION_PATCH 1 extern char const PROJ_DLL pj_release[]; /* global release id string */ diff -Nru proj-7.2.0/src/release.cpp proj-7.2.1/src/release.cpp --- proj-7.2.0/src/release.cpp 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/src/release.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -11,7 +11,7 @@ STR(PROJ_VERSION_MAJOR)"." STR(PROJ_VERSION_MINOR)"." STR(PROJ_VERSION_PATCH)", " - "November 1st, 2020"; + "January 1st, 2021"; const char *pj_get_release() { return pj_release; diff -Nru proj-7.2.0/src/wkt2_generated_parser.c proj-7.2.1/src/wkt2_generated_parser.c --- proj-7.2.0/src/wkt2_generated_parser.c 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/src/wkt2_generated_parser.c 2020-12-21 16:29:10.000000000 +0000 @@ -542,16 +542,16 @@ /* YYFINAL -- State number of the termination state. */ #define YYFINAL 106 /* YYLAST -- Last index in YYTABLE. */ -#define YYLAST 3132 +#define YYLAST 3219 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 164 /* YYNNTS -- Number of nonterminals. */ #define YYNNTS 353 /* YYNRULES -- Number of rules. */ -#define YYNRULES 694 +#define YYNRULES 695 /* YYNSTATES -- Number of states. */ -#define YYNSTATES 1427 +#define YYNSTATES 1429 /* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned by yylex, with out-of-bounds checking. */ @@ -658,30 +658,30 @@ 997, 1003, 1003, 1007, 1007, 1008, 1008, 1010, 1015, 1016, 1017, 1018, 1019, 1021, 1027, 1032, 1038, 1040, 1042, 1044, 1048, 1054, 1055, 1056, 1058, 1060, 1062, 1066, 1066, 1068, - 1070, 1075, 1076, 1078, 1080, 1082, 1084, 1088, 1088, 1090, - 1096, 1103, 1103, 1106, 1113, 1114, 1115, 1116, 1117, 1119, - 1123, 1125, 1127, 1127, 1131, 1136, 1136, 1136, 1140, 1145, - 1145, 1147, 1151, 1151, 1155, 1160, 1162, 1166, 1166, 1170, - 1175, 1177, 1181, 1182, 1183, 1184, 1185, 1187, 1187, 1189, - 1192, 1194, 1194, 1196, 1198, 1200, 1204, 1210, 1211, 1212, - 1213, 1215, 1217, 1221, 1226, 1228, 1231, 1236, 1240, 1246, - 1246, 1246, 1246, 1246, 1246, 1250, 1255, 1257, 1262, 1262, - 1263, 1265, 1265, 1267, 1274, 1274, 1276, 1283, 1283, 1285, - 1292, 1299, 1304, 1305, 1306, 1308, 1314, 1319, 1327, 1333, - 1335, 1337, 1343, 1345, 1345, 1346, 1346, 1350, 1356, 1356, - 1358, 1363, 1369, 1374, 1380, 1385, 1390, 1396, 1401, 1406, - 1412, 1417, 1422, 1428, 1428, 1429, 1429, 1430, 1430, 1431, - 1431, 1432, 1432, 1433, 1433, 1436, 1436, 1438, 1439, 1440, - 1442, 1444, 1448, 1451, 1451, 1454, 1455, 1456, 1458, 1462, - 1463, 1465, 1467, 1467, 1468, 1468, 1469, 1469, 1469, 1470, - 1471, 1471, 1472, 1472, 1473, 1473, 1475, 1475, 1476, 1476, - 1477, 1478, 1478, 1482, 1486, 1487, 1490, 1495, 1496, 1497, - 1498, 1499, 1500, 1501, 1503, 1505, 1507, 1510, 1512, 1514, - 1516, 1518, 1520, 1522, 1524, 1526, 1528, 1533, 1537, 1538, - 1541, 1546, 1547, 1548, 1549, 1550, 1552, 1557, 1562, 1563, - 1566, 1572, 1572, 1572, 1572, 1574, 1575, 1576, 1577, 1579, - 1581, 1586, 1592, 1594, 1599, 1600, 1603, 1609, 1609, 1611, - 1612, 1613, 1614, 1616, 1618 + 1070, 1075, 1076, 1077, 1079, 1081, 1083, 1085, 1089, 1089, + 1091, 1097, 1104, 1104, 1107, 1114, 1115, 1116, 1117, 1118, + 1120, 1124, 1126, 1128, 1128, 1132, 1137, 1137, 1137, 1141, + 1146, 1146, 1148, 1152, 1152, 1156, 1161, 1163, 1167, 1167, + 1171, 1176, 1178, 1182, 1183, 1184, 1185, 1186, 1188, 1188, + 1190, 1193, 1195, 1195, 1197, 1199, 1201, 1205, 1211, 1212, + 1213, 1214, 1216, 1218, 1222, 1227, 1229, 1232, 1237, 1241, + 1247, 1247, 1247, 1247, 1247, 1247, 1251, 1256, 1258, 1263, + 1263, 1264, 1266, 1266, 1268, 1275, 1275, 1277, 1284, 1284, + 1286, 1293, 1300, 1305, 1306, 1307, 1309, 1315, 1320, 1328, + 1334, 1336, 1338, 1344, 1346, 1346, 1347, 1347, 1351, 1357, + 1357, 1359, 1364, 1370, 1375, 1381, 1386, 1391, 1397, 1402, + 1407, 1413, 1418, 1423, 1429, 1429, 1430, 1430, 1431, 1431, + 1432, 1432, 1433, 1433, 1434, 1434, 1437, 1437, 1439, 1440, + 1441, 1443, 1445, 1449, 1452, 1452, 1455, 1456, 1457, 1459, + 1463, 1464, 1466, 1468, 1468, 1469, 1469, 1470, 1470, 1470, + 1471, 1472, 1472, 1473, 1473, 1474, 1474, 1476, 1476, 1477, + 1477, 1478, 1479, 1479, 1483, 1487, 1488, 1491, 1496, 1497, + 1498, 1499, 1500, 1501, 1502, 1504, 1506, 1508, 1511, 1513, + 1515, 1517, 1519, 1521, 1523, 1525, 1527, 1529, 1534, 1538, + 1539, 1542, 1547, 1548, 1549, 1550, 1551, 1553, 1558, 1563, + 1564, 1567, 1573, 1573, 1573, 1573, 1575, 1576, 1577, 1578, + 1580, 1582, 1587, 1593, 1595, 1600, 1601, 1604, 1610, 1610, + 1612, 1613, 1614, 1615, 1617, 1619 }; #endif @@ -933,7 +933,7 @@ #define yypact_value_is_default(Yystate) \ (!!((Yystate) == (-1232))) -#define YYTABLE_NINF -633 +#define YYTABLE_NINF -634 #define yytable_value_is_error(Yytable_value) \ 0 @@ -942,149 +942,149 @@ STATE-NUM. */ static const yytype_int16 yypact[] = { - 2573, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, + 1574, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, - -1232, -1232, -1232, -1232, -1232, -1232, -1232, 114, -1232, -1232, - -1232, 231, -1232, -1232, -1232, 231, -1232, -1232, -1232, -1232, - -1232, -1232, 231, 231, -1232, 231, -1232, -75, 231, -1232, - 231, -1232, 231, -1232, -1232, -1232, 231, -1232, 231, -1232, - 231, -1232, 231, -1232, 231, -1232, 231, -1232, 231, -1232, - 231, -1232, -1232, -1232, -1232, -1232, -1232, -1232, 231, -1232, - -1232, -1232, -1232, -1232, -1232, 231, -1232, 231, -1232, 231, - -1232, 231, -1232, 231, -1232, 231, -1232, -1232, -1232, 33, - 33, 33, 33, 33, -1232, 81, 33, 33, 33, 33, - 33, 33, 33, 33, 33, 33, 33, 33, 33, 879, - 33, 33, 33, 117, -1232, -1232, -75, -1232, -75, -1232, - -75, -75, -1232, -75, -1232, -1232, -1232, 231, -1232, -75, - -75, -1232, -75, -75, -75, -75, -75, -75, -75, -75, - -75, -1232, -75, -1232, -75, -1232, -1232, -1232, -1232, 28, - -1232, -1232, -1232, -1232, -1232, 97, 168, 186, -1232, -1232, - -1232, -1232, 284, -1232, -75, -1232, -75, -75, -75, -1232, - -75, 231, 1038, 176, 66, 66, 713, 33, 287, 179, - 157, 766, 290, 284, 244, 284, 413, 284, 105, 286, - 284, 297, 1406, -1232, -1232, -1232, 428, 101, -1232, -1232, - 101, -1232, -1232, 101, -1232, -1232, 331, 879, -1232, -1232, - -1232, -1232, -1232, -1232, -1232, 728, -1232, -1232, -1232, -1232, - 265, 278, 293, 713, -1232, -75, -1232, -75, 231, -1232, - -1232, -1232, -1232, 231, -75, 231, -75, -1232, 231, 231, - -75, -75, -1232, -1232, -1232, -1232, -75, -75, -75, -75, - -1232, -75, -1232, -75, -75, -75, -1232, -1232, -1232, -1232, - 231, 231, -1232, -1232, -75, 231, -1232, -1232, 231, -75, - -75, -1232, -75, -1232, -1232, 231, -1232, -75, -75, 231, - -1232, -1232, -75, -75, 231, -1232, -1232, -75, -75, 231, - -1232, -1232, -75, -75, 231, -1232, -1232, -75, -75, 231, - -75, 231, -1232, -1232, -75, 231, -1232, -75, -1232, -1232, - -1232, -1232, 231, -1232, -75, 231, -75, -75, -75, -75, - -75, -1232, -75, 231, 284, -1232, 476, 728, -1232, -1232, - 387, 284, 283, 284, 284, 33, 33, 59, 404, 258, - 33, 33, 441, 441, 59, 258, 441, 441, 713, 476, - 284, 452, 33, 33, 349, 284, 33, 33, 88, 477, - 441, 33, 479, -1232, 479, 33, 477, 441, 33, 477, - 441, 33, 477, 441, 33, -1232, -1232, 682, 74, -1232, - 33, 441, 33, 1406, 728, 117, -1232, 33, 331, 117, - -1232, 487, 117, -1232, 331, 469, 879, -1232, 728, -1232, - -1232, -1232, -1232, -1232, -1232, -1232, -1232, -75, -75, 231, - -1232, 231, -1232, -1232, -75, -75, 231, -75, -1232, -1232, - -1232, -75, -75, -75, -1232, -75, 231, -1232, -1232, -1232, - -1232, -1232, -1232, 231, 284, -75, -1232, -75, -75, -1232, - -75, 231, -75, -75, 284, -75, -75, -1232, -75, -75, - 713, 284, -1232, -75, -75, -75, -1232, -75, -75, 231, - -1232, -1232, -75, -75, -75, 231, 284, -75, -75, -75, - -75, -1232, 284, 284, -75, -75, 284, -75, -75, 284, - -75, -75, -1232, -1232, 116, -1232, 284, -75, -1232, 284, - -75, -75, -75, 293, 284, -1232, 284, -75, -1232, -75, - 231, -75, -1232, -75, 231, 284, -1232, 488, 511, 33, - 33, -1232, -1232, 479, -1232, 1192, 486, 479, 284, 176, - 258, 614, 284, 728, 1482, -1232, 477, 56, 56, 477, - 33, 477, 258, -1232, 477, 477, 148, 284, 345, -1232, - -1232, -1232, 477, 56, 56, -1232, -1232, 33, 284, 176, - 477, 1503, -1232, 477, 277, -1232, -1232, -1232, -1232, 477, - -1, -1232, 477, -2, -1232, 477, 22, -1232, -1232, 728, - -1232, -1232, 728, -1232, -1232, -1232, 477, 179, 1669, 284, - 728, -1232, -1232, 487, 1210, 284, 33, 523, 1412, 284, - 33, -1232, -75, -1232, -1232, 284, -1232, 284, -1232, -75, - -1232, 284, -75, -1232, -75, -1232, -75, 284, -1232, -1232, - -1232, 231, -1232, 293, 284, -1232, -1232, -1232, -1232, -1232, - -1232, -1232, -1232, -1232, -75, -1232, -1232, -1232, -1232, -75, - -75, -75, -1232, -75, -75, -75, -75, 284, -1232, -75, - 284, 284, 284, 284, -1232, -1232, -75, -75, 231, -1232, - -1232, -1232, -75, 231, 284, -75, -75, -75, -75, -1232, - -75, -1232, -75, 284, -75, 284, -75, -75, 284, -75, - 284, -75, 284, -75, 159, 401, -1232, 767, 284, -1232, - -1232, -1232, -1232, -75, -1232, -1232, -1232, -1232, -1232, -1232, - -1232, -1232, -1232, -1232, -1232, -75, 231, -75, 231, -1232, - -75, 231, -75, 231, -75, 231, -75, 231, -75, -1232, - 231, -75, -1232, -1232, -75, -1232, -1232, -1232, 231, -75, - -75, 231, -75, 231, -1232, -1232, -75, -1232, 231, -1232, - -1232, -75, 511, -1232, -1232, -1232, -1232, -1232, -1232, 257, - -1232, 33, 728, -1232, 586, 586, 586, 586, 59, 196, - 284, 59, 284, -1232, 487, -1232, -1232, -1232, -1232, -1232, - -1232, 33, -1232, 33, -1232, 59, 141, 284, 59, 284, - 476, 631, -1232, 586, -1232, 88, 284, -1232, 284, -1232, - 284, -1232, 284, -1232, 728, -1232, -1232, 728, 728, -1232, - 415, -1232, -1232, -1232, -1232, 452, 145, 548, 528, -1232, - 33, 547, -1232, 33, 322, -1232, 1192, 325, -1232, 1192, - 267, -1232, 682, -1232, 439, -1232, 980, 284, 33, -1232, - -1232, 33, -1232, 1192, 479, 284, 282, 42, -1232, -1232, - -1232, -75, -1232, -75, -1232, -1232, -1232, -1232, -75, -75, - -75, -75, -75, -75, -75, -1232, -75, -1232, -75, -1232, - -75, -75, -75, -75, -1232, -75, -75, -1232, -75, -1232, - -1232, -75, -75, -75, -75, -1232, -1232, -1232, -1232, -1232, - 433, 415, -1232, 767, 728, -1232, -75, -1232, -75, -1232, - -75, -1232, -75, -1232, -1232, 284, -75, -75, -75, -1232, - 284, -75, -75, -1232, -75, -75, -1232, -75, -1232, -1232, - -75, -1232, 284, -1232, -1232, -75, -75, -75, 231, -75, - -1232, -75, -75, 284, -1232, -1232, -1232, -1232, -1232, -1232, - 284, -75, -75, 284, 284, 284, 284, 284, 284, -1232, - -1232, 284, 259, 284, 713, 713, 284, -1232, 345, -1232, - -1232, 284, 456, 284, 284, 284, -1232, -1232, 728, -1232, - -1232, -1232, 284, -1232, 212, -1232, -1232, 322, -1232, 325, - -1232, -1232, -1232, 325, -1232, -1232, 1192, -1232, 1192, 682, - -1232, -1232, -1232, 1052, -1232, 879, -1232, 476, 33, -1232, - -75, 167, 487, -1232, -1232, -75, -75, -75, -75, -1232, - -1232, -75, -75, -75, -1232, -1232, -75, -75, -1232, -75, - -1232, -1232, -1232, -1232, -1232, -1232, 231, -75, -1232, -75, - -1232, -1232, -1232, 1008, -1232, 284, -75, -75, -75, -1232, - -75, -75, -75, -75, -1232, -75, -1232, -75, -1232, -1232, - 284, -75, 284, -75, -1232, -75, 523, 231, -1232, -75, - -1232, 595, 595, 595, 595, -1232, -1232, -1232, 284, 284, - -1232, 33, -1232, 595, 1080, -1232, -1232, 315, 622, 553, - 325, -1232, -1232, -1232, -1232, 1192, 376, 284, -1232, -1232, - -1232, 775, 284, 231, 33, 1141, 284, -1232, -75, 231, - -75, 231, -75, -75, 231, -1232, -1232, -75, -75, 525, - 1080, -1232, -75, -75, -1232, -75, -1232, -1232, -75, -1232, - -75, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -75, - -1232, 231, -1232, 282, -75, -1232, -75, -75, -1232, 696, - -1232, 33, 688, -1232, 33, -1232, 974, -1232, 33, 713, - 1086, -1232, -1232, 622, 553, 553, -1232, 1192, 284, 33, - 284, 476, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, - -1232, -1232, -1232, -1232, -1232, 231, -1232, 231, -75, -1232, - -75, -1232, -75, -75, -75, -1232, -75, -75, -75, -1232, - -1232, -75, -75, 231, -75, -1232, -1232, -1232, -1232, 284, - -1232, -75, -75, -75, 33, 33, 1412, 2101, -1232, -1232, - 2559, -1232, 2630, 284, 478, -1232, -1232, 33, 553, -1232, - 713, 284, 1469, 284, 284, -75, -75, -1232, -1232, -1232, + -1232, -1232, -1232, -1232, -1232, -1232, -1232, 62, -1232, -1232, + -1232, 98, -1232, -1232, -1232, 98, -1232, -1232, -1232, -1232, + -1232, -1232, 98, 98, -1232, 98, -1232, -87, 98, -1232, + 98, -1232, 98, -1232, -1232, -1232, 98, -1232, 98, -1232, + 98, -1232, 98, -1232, 98, -1232, 98, -1232, 98, -1232, + 98, -1232, -1232, -1232, -1232, -1232, -1232, -1232, 98, -1232, + -1232, -1232, -1232, -1232, -1232, 98, -1232, 98, -1232, 98, + -1232, 98, -1232, 98, -1232, 98, -1232, -1232, -1232, -24, + -24, -24, -24, -24, -1232, 83, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, 810, + -24, -24, -24, 113, -1232, -1232, -87, -1232, -87, -1232, + -87, -87, -1232, -87, -1232, -1232, -1232, 98, -1232, -87, + -87, -1232, -87, -87, -87, -87, -87, -87, -87, -87, + -87, -1232, -87, -1232, -87, -1232, -1232, -1232, -1232, 26, + -1232, -1232, -1232, -1232, -1232, 37, 52, 179, -1232, -1232, + -1232, -1232, 360, -1232, -87, -1232, -87, -87, -87, -1232, + -87, 98, 1048, 212, 59, 59, 761, -24, 256, 138, + 156, 467, 304, 360, 203, 360, 322, 360, 217, 314, + 360, 275, 1439, -1232, -1232, -1232, 483, 69, -1232, -1232, + 69, -1232, -1232, 69, -1232, -1232, 335, 810, -1232, -1232, + -1232, -1232, -1232, -1232, -1232, 462, -1232, -1232, -1232, -1232, + 252, 265, 287, 761, -1232, -87, -1232, -87, 98, -1232, + -1232, -1232, -1232, 98, -87, 98, -87, -1232, 98, 98, + -87, -87, -1232, -1232, -1232, -1232, -87, -87, -87, -87, + -1232, -87, -1232, -87, -87, -87, -1232, -1232, -1232, -1232, + 98, 98, -1232, -1232, -87, 98, -1232, -1232, 98, -87, + -87, -1232, -87, -1232, -1232, 98, -1232, -87, -87, 98, + -1232, -1232, -87, -87, 98, -1232, -1232, -87, -87, 98, + -1232, -1232, -87, -87, 98, -1232, -1232, -87, -87, 98, + -87, 98, -1232, -1232, -87, 98, -1232, -87, -1232, -1232, + -1232, -1232, 98, -1232, -87, 98, -87, -87, -87, -87, + -87, -1232, -87, 98, 360, -1232, 416, 462, -1232, -1232, + 558, 360, 75, 360, 360, -24, -24, 78, 390, 102, + -24, -24, 413, 413, 78, 102, 413, 413, 761, 416, + 360, 466, -24, -24, 364, 360, -24, -24, 89, 477, + 413, -24, 488, -1232, 488, -24, 477, 413, -24, 477, + 413, -24, 477, 413, -24, -1232, -1232, 387, 104, -1232, + -24, 413, -24, 1439, 462, 113, -1232, -24, 335, 113, + -1232, 499, 113, -1232, 335, 461, 810, -1232, 462, -1232, + -1232, -1232, -1232, -1232, -1232, -1232, -1232, -87, -87, 98, + -1232, 98, -1232, -1232, -87, -87, 98, -87, -1232, -1232, + -1232, -87, -87, -87, -1232, -87, 98, -1232, -1232, -1232, + -1232, -1232, -1232, 98, 360, -87, -1232, -87, -87, -1232, + -87, 98, -87, -87, 360, -87, -87, -1232, -87, -87, + 761, 360, -1232, -87, -87, -87, -1232, -87, -87, 98, + -1232, -1232, -87, -87, -87, 98, 360, -87, -87, -87, + -87, -1232, 360, 360, -87, -87, 360, -87, -87, 360, + -87, -87, -1232, -1232, 159, -1232, 360, -87, -1232, 360, + -87, -87, -87, 287, 360, -1232, 360, -87, -1232, -87, + 98, -87, -1232, -87, 98, 360, -1232, 481, 486, -24, + -24, -1232, -1232, 488, -1232, 823, 479, 488, 360, 212, + 102, 517, 360, 462, 1699, -1232, 477, 112, 112, 477, + -24, 477, 102, -1232, 477, 477, 329, 360, 306, -1232, + -1232, -1232, 477, 112, 112, -1232, -1232, -24, 360, 212, + 477, 940, -1232, 477, 243, -1232, -1232, -1232, -1232, 477, + 108, -1232, 477, 192, -1232, 477, 25, -1232, -1232, 462, + -1232, -1232, 462, -1232, -1232, -1232, 477, 138, 1409, 360, + 462, -1232, -1232, 499, 1212, 360, -24, 498, 1047, 360, + -24, -1232, -87, -1232, -1232, 360, -1232, 360, -1232, -87, + -1232, 360, -87, -1232, -87, -1232, -87, 360, -1232, -1232, + -1232, 98, -1232, 287, 360, -1232, -1232, -1232, -1232, -1232, + -1232, -1232, -1232, -1232, -87, -1232, -1232, -1232, -1232, -87, + -87, -87, -1232, -87, -87, -87, -87, 360, -1232, -87, + 360, 360, 360, 360, -1232, -1232, -87, -87, 98, -1232, + -1232, -1232, -87, 98, 360, -87, -87, -87, -87, -1232, + -87, -1232, -87, 360, -87, 360, -87, -87, 360, -87, + 360, -87, 360, -87, 205, 427, -1232, 497, 360, -1232, + -1232, -1232, -1232, -87, -1232, -1232, -1232, -1232, -1232, -1232, + -1232, -1232, -1232, -1232, -1232, -87, 98, -87, 98, -1232, + -87, 98, -87, 98, -87, 98, -87, 98, -87, -1232, + 98, -87, -1232, -1232, -87, -1232, -1232, -1232, 98, -87, + -87, 98, -87, 98, -1232, -1232, -87, -1232, 98, -1232, + -1232, -87, 486, -1232, -1232, -1232, -1232, -1232, -1232, 117, + -1232, -24, 462, -1232, 398, 398, 398, 398, 78, 100, + 360, 78, 360, -1232, 499, -1232, -1232, -1232, -1232, -1232, + -1232, -24, -1232, -24, -1232, 78, 58, 360, 78, 360, + 416, 518, -1232, 398, -1232, 89, 360, -1232, 360, -1232, + 360, -1232, 360, -1232, 462, -1232, -1232, 462, 462, -1232, + 430, -1232, -1232, -1232, -1232, 466, 176, 566, 530, -1232, + -24, 547, -1232, -24, 425, -1232, 823, 281, -1232, 823, + 333, -1232, 387, -1232, 469, -1232, 1134, 360, -24, -1232, + -1232, -24, -1232, 823, 488, 360, 267, 47, -1232, -1232, + -1232, -87, -1232, -87, -1232, -1232, -1232, -1232, -87, -87, + -87, -87, -87, -87, -87, -1232, -87, -1232, -87, -1232, + -87, -87, -87, -87, -1232, -87, -87, -1232, -87, -1232, + -1232, -87, -87, -87, -87, -1232, -1232, -1232, -1232, -1232, + 465, 430, -1232, 497, 462, -1232, -87, -1232, -87, -1232, + -87, -1232, -87, -1232, -1232, 360, -87, -87, -87, -1232, + 360, -87, -87, -1232, -87, -87, -1232, -87, -1232, -1232, + -87, -1232, 360, -1232, -1232, -87, -87, -87, 98, -87, + -1232, -87, -87, 360, -1232, -1232, -1232, -1232, -1232, -1232, + 360, -87, -87, 360, 360, 360, 360, 360, 360, -1232, + -1232, 360, 103, 360, 761, 761, 360, -1232, 306, -1232, + -1232, 360, 746, 360, 360, 360, -1232, -1232, 462, -1232, + -1232, -1232, 360, -1232, 338, -1232, -1232, 425, -1232, 281, + -1232, -1232, -1232, 281, -1232, -1232, 823, -1232, 823, 387, + -1232, -1232, -1232, 1054, -1232, 810, -1232, 416, -24, -1232, + -87, 168, 499, -1232, -1232, -87, -87, -87, -87, -1232, + -1232, -87, -87, -87, -1232, -1232, -87, -87, -1232, -87, + -1232, -1232, -1232, -1232, -1232, -87, -1232, 98, -87, -1232, + -87, -1232, -1232, -1232, 681, -1232, 360, -87, -87, -87, + -1232, -87, -87, -87, -87, -1232, -87, -1232, -87, -1232, + -1232, 360, -87, 360, -87, -1232, -87, 498, 98, -1232, + -87, -1232, 623, 623, 623, 623, -1232, -1232, -1232, 360, + 360, -1232, -1232, -24, -1232, 623, 811, -1232, -1232, 199, + 535, 579, 281, -1232, -1232, -1232, -1232, 823, 372, 360, + -1232, -1232, -1232, 297, 360, 98, -24, 1415, 360, -1232, + -87, 98, -87, 98, -87, -87, 98, -1232, -1232, -87, + -87, 370, 811, -1232, -87, -87, -1232, -87, -1232, -1232, + -87, -1232, -87, -1232, -1232, -1232, -1232, -1232, -1232, -1232, + -1232, -87, -1232, 98, -1232, 267, -87, -1232, -87, -87, + -1232, 1348, -1232, -24, 1001, -1232, -24, -1232, 711, -1232, + -24, 761, 1104, -1232, -1232, 535, 579, 579, -1232, 823, + 360, -24, 360, 416, -1232, -1232, -1232, -1232, -1232, -1232, + -1232, -1232, -1232, -1232, -1232, -1232, -1232, 98, -1232, 98, + -87, -1232, -87, -1232, -87, -87, -87, -1232, -87, -87, + -87, -1232, -1232, -87, -87, 98, -87, -1232, -1232, -1232, + -1232, 360, -1232, -87, -87, -87, -24, -24, 1047, 2503, + -1232, -1232, 2115, -1232, 2716, 360, 1527, -1232, -1232, -24, + 579, -1232, 761, 360, 1340, 360, 360, -87, -87, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -1232, - -1232, -1232, -1232, -1232, -75, -75, -75, -75, -75, 284, - -1232, -75, -75, -75, -75, -75, 284, -1232, -75, -1232, - -75, -1232, -75, -1232, -75, -1232, -1232, -75, 231, -1232, - -1232, 713, 284, 55, 284, 563, 563, 723, 723, -1232, - 741, 457, 563, 593, 593, -1232, 530, -1232, 284, -1232, - -1232, 282, -75, -1232, -1232, -75, -75, -1232, -75, 231, - -1232, -1232, -75, -75, -1232, -75, 231, -75, -1232, -1232, - -75, -75, -1232, -75, 231, -75, -1232, -75, -75, -1232, - -75, -75, -1232, -75, -75, -1232, -75, -1232, -75, -75, - -1232, -75, -1232, -75, -1232, 284, 284, -1232, -1232, 92, - -1232, 728, -1232, -1232, 741, -1232, 1192, 763, -1232, -1232, - -1232, 741, -1232, 1192, 763, -1232, -1232, -1232, 763, -1232, - -1232, 530, -1232, -1232, -1232, 530, -1232, -1232, -1232, -1232, - -75, -1232, -75, 284, -75, -75, -75, -75, -75, -75, - 284, -75, -75, -75, -75, -1232, -1232, -1232, -1232, 763, - -1232, 577, -1232, -1232, 763, -1232, -1232, -1232, -1232, -1232, - -1232, -75, 284, -75, -1232, -1232, -1232 + -1232, -1232, -1232, -1232, -1232, -1232, -87, -87, -87, -87, + -87, 360, -1232, -87, -87, -87, -87, -87, 360, -1232, + -87, -1232, -87, -1232, -87, -1232, -87, -1232, -1232, -87, + 98, -1232, -1232, 761, 360, 272, 360, 679, 679, 710, + 710, -1232, 870, 412, 679, 494, 494, -1232, 336, -1232, + 360, -1232, -1232, 267, -87, -1232, -1232, -87, -87, -1232, + -87, 98, -1232, -1232, -87, -87, -1232, -87, 98, -87, + -1232, -1232, -87, -87, -1232, -87, 98, -87, -1232, -87, + -87, -1232, -87, -87, -1232, -87, -87, -1232, -87, -1232, + -87, -87, -1232, -87, -1232, -87, -1232, 360, 360, -1232, + -1232, 74, -1232, 462, -1232, -1232, 870, -1232, 823, 475, + -1232, -1232, -1232, 870, -1232, 823, 475, -1232, -1232, -1232, + 475, -1232, -1232, 336, -1232, -1232, -1232, 336, -1232, -1232, + -1232, -1232, -87, -1232, -87, 360, -87, -87, -87, -87, + -87, -87, 360, -87, -87, -87, -87, -1232, -1232, -1232, + -1232, 475, -1232, 438, -1232, -1232, 475, -1232, -1232, -1232, + -1232, -1232, -1232, -87, 360, -87, -1232, -1232, -1232 }; /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. @@ -1092,190 +1092,190 @@ means the default is an error. */ static const yytype_uint16 yydefact[] = { - 0, 419, 406, 395, 405, 161, 431, 454, 397, 482, - 485, 600, 644, 679, 682, 507, 500, 356, 559, 492, - 489, 497, 495, 611, 666, 396, 421, 432, 398, 420, - 483, 487, 486, 508, 493, 490, 498, 0, 4, 5, - 2, 0, 13, 346, 347, 0, 583, 385, 383, 384, - 386, 387, 0, 0, 3, 0, 12, 416, 0, 585, - 0, 11, 0, 587, 467, 468, 0, 14, 0, 589, - 0, 15, 0, 591, 0, 16, 0, 593, 0, 17, - 0, 584, 540, 538, 539, 541, 542, 586, 0, 588, - 590, 592, 594, 19, 18, 0, 7, 0, 8, 0, + 0, 419, 406, 395, 405, 161, 431, 454, 397, 483, + 486, 601, 645, 680, 683, 508, 501, 356, 560, 493, + 490, 498, 496, 612, 667, 396, 421, 432, 398, 420, + 484, 488, 487, 509, 494, 491, 499, 0, 4, 5, + 2, 0, 13, 346, 347, 0, 584, 385, 383, 384, + 386, 387, 0, 0, 3, 0, 12, 416, 0, 586, + 0, 11, 0, 588, 468, 469, 0, 14, 0, 590, + 0, 15, 0, 592, 0, 16, 0, 594, 0, 17, + 0, 585, 541, 539, 540, 542, 543, 587, 0, 589, + 591, 593, 595, 19, 18, 0, 7, 0, 8, 0, 9, 0, 10, 0, 6, 0, 1, 73, 74, 0, 0, 0, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 162, 0, 357, 0, 394, 0, 0, 407, 0, 411, 412, 417, 0, 422, 0, 0, 455, 0, 0, 423, 0, 423, 0, 423, 0, - 502, 560, 0, 601, 0, 612, 626, 613, 627, 614, - 615, 629, 616, 617, 618, 619, 620, 621, 622, 623, - 624, 625, 0, 609, 0, 645, 0, 0, 0, 650, + 503, 561, 0, 602, 0, 613, 627, 614, 628, 615, + 616, 630, 617, 618, 619, 620, 621, 622, 623, 624, + 625, 626, 0, 610, 0, 646, 0, 0, 0, 651, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 75, 76, 608, 0, 0, 633, 635, - 0, 657, 659, 0, 667, 669, 0, 0, 40, 20, + 0, 0, 0, 75, 76, 609, 0, 0, 634, 636, + 0, 658, 660, 0, 668, 670, 0, 0, 40, 20, 37, 38, 39, 41, 42, 0, 163, 21, 22, 26, 0, 25, 35, 0, 164, 154, 361, 0, 0, 446, 447, 369, 400, 0, 0, 0, 0, 399, 0, 0, - 0, 0, 544, 547, 545, 548, 0, 0, 0, 0, + 0, 0, 545, 548, 546, 549, 0, 0, 0, 0, 408, 0, 413, 0, 423, 0, 433, 434, 435, 436, - 0, 0, 458, 457, 451, 0, 572, 472, 0, 0, - 0, 471, 0, 568, 569, 0, 428, 190, 424, 0, - 484, 575, 0, 0, 0, 491, 578, 0, 0, 0, - 496, 581, 0, 0, 0, 514, 510, 190, 190, 0, - 190, 0, 501, 562, 0, 0, 595, 0, 596, 603, - 604, 610, 0, 647, 0, 0, 0, 0, 0, 0, - 0, 652, 0, 0, 0, 34, 27, 0, 33, 23, + 0, 0, 458, 457, 451, 0, 573, 473, 0, 0, + 0, 472, 0, 569, 570, 0, 428, 190, 424, 0, + 485, 576, 0, 0, 0, 492, 579, 0, 0, 0, + 497, 582, 0, 0, 0, 515, 511, 190, 190, 0, + 190, 0, 502, 563, 0, 0, 596, 0, 597, 604, + 605, 611, 0, 648, 0, 0, 0, 0, 0, 0, + 0, 653, 0, 0, 0, 34, 27, 0, 33, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 425, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 506, 505, 0, 0, 503, - 0, 0, 0, 0, 0, 0, 634, 0, 0, 0, - 658, 0, 0, 668, 0, 0, 0, 649, 0, 29, + 0, 0, 0, 0, 0, 507, 506, 0, 0, 504, + 0, 0, 0, 0, 0, 0, 635, 0, 0, 0, + 659, 0, 0, 669, 0, 0, 0, 650, 0, 29, 31, 28, 36, 168, 171, 165, 166, 155, 158, 0, 160, 0, 153, 365, 0, 351, 0, 0, 348, 353, 362, 359, 0, 0, 371, 375, 0, 223, 393, 204, - 205, 206, 207, 0, 0, 0, 448, 0, 0, 521, + 205, 206, 207, 0, 0, 0, 448, 0, 0, 522, 0, 0, 0, 0, 0, 0, 0, 409, 402, 190, - 0, 0, 418, 0, 0, 0, 463, 190, 451, 0, + 0, 0, 418, 0, 0, 0, 464, 190, 451, 0, 450, 459, 190, 0, 0, 0, 0, 0, 0, 190, 190, 429, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 50, 511, 48, 512, 0, 190, 515, 0, - 0, 0, 597, 605, 0, 648, 0, 0, 524, 661, - 0, 0, 693, 80, 0, 0, 32, 0, 0, 0, + 0, 0, 50, 512, 48, 513, 0, 190, 516, 0, + 0, 0, 598, 606, 0, 649, 0, 0, 525, 662, + 0, 0, 694, 80, 0, 0, 32, 0, 0, 0, 0, 350, 355, 0, 354, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 388, 0, 0, 0, 0, 0, 0, 0, 390, 0, 0, 0, 0, 0, 415, 24, 410, 0, 0, 0, 452, 453, 0, 0, 0, - 0, 0, 469, 0, 0, 191, 426, 427, 488, 0, - 0, 494, 0, 0, 499, 0, 0, 44, 58, 0, - 45, 49, 0, 509, 504, 513, 0, 0, 0, 0, - 606, 602, 646, 0, 0, 0, 0, 0, 0, 0, - 0, 651, 156, 159, 169, 0, 172, 0, 367, 351, + 0, 0, 470, 0, 0, 191, 426, 427, 489, 0, + 0, 495, 0, 0, 500, 0, 0, 44, 58, 0, + 45, 49, 0, 510, 505, 514, 0, 0, 0, 0, + 607, 603, 647, 0, 0, 0, 0, 0, 0, 0, + 0, 652, 156, 159, 169, 0, 172, 0, 367, 351, 366, 0, 351, 363, 359, 358, 0, 0, 380, 381, 376, 0, 368, 372, 0, 224, 225, 226, 227, 228, 229, 230, 231, 232, 0, 233, 234, 235, 236, 0, - 0, 0, 392, 0, 552, 0, 552, 0, 522, 0, + 0, 0, 392, 0, 553, 0, 553, 0, 523, 0, 0, 0, 0, 0, 199, 198, 190, 190, 0, 401, - 197, 196, 190, 0, 0, 0, 438, 0, 438, 464, + 197, 196, 190, 0, 0, 0, 438, 0, 438, 465, 0, 456, 0, 0, 0, 0, 0, 190, 0, 190, - 0, 190, 0, 190, 48, 0, 59, 0, 0, 563, - 564, 565, 566, 0, 174, 100, 133, 136, 144, 148, - 98, 599, 82, 88, 89, 93, 0, 85, 0, 92, + 0, 190, 0, 190, 48, 0, 59, 0, 0, 564, + 565, 566, 567, 0, 174, 100, 133, 136, 144, 148, + 98, 600, 82, 88, 89, 93, 0, 85, 0, 92, 85, 0, 85, 0, 85, 0, 85, 0, 85, 84, - 0, 597, 582, 607, 637, 536, 656, 665, 0, 661, - 661, 0, 80, 0, 660, 525, 378, 680, 0, 81, - 681, 0, 0, 167, 170, 352, 364, 349, 360, 0, + 0, 598, 583, 608, 638, 537, 657, 666, 0, 662, + 662, 0, 80, 0, 661, 526, 378, 681, 0, 81, + 682, 0, 0, 167, 170, 352, 364, 349, 360, 0, 389, 0, 373, 370, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 543, 0, 546, 391, 549, 550, 404, + 0, 0, 0, 544, 0, 547, 391, 550, 551, 404, 403, 0, 414, 0, 430, 0, 0, 0, 0, 0, - 27, 0, 470, 0, 567, 0, 0, 573, 0, 576, - 0, 579, 0, 46, 0, 43, 68, 0, 0, 53, - 71, 55, 66, 67, 558, 0, 0, 0, 0, 91, + 27, 0, 471, 0, 568, 0, 0, 574, 0, 577, + 0, 580, 0, 46, 0, 43, 68, 0, 0, 53, + 71, 55, 66, 67, 559, 0, 0, 0, 0, 91, 0, 0, 117, 0, 0, 118, 0, 0, 119, 0, - 0, 120, 0, 83, 0, 598, 0, 0, 0, 662, - 663, 0, 664, 0, 0, 0, 0, 0, 683, 685, + 0, 120, 0, 83, 0, 599, 0, 0, 0, 663, + 664, 0, 665, 0, 0, 0, 0, 0, 684, 686, 157, 0, 382, 378, 374, 237, 238, 239, 190, 190, - 190, 190, 552, 190, 190, 551, 552, 556, 517, 202, + 190, 190, 553, 190, 190, 552, 553, 557, 518, 202, 0, 0, 438, 190, 449, 190, 190, 437, 438, 444, - 465, 461, 0, 190, 190, 570, 574, 577, 580, 52, + 466, 461, 0, 190, 190, 571, 575, 578, 581, 52, 48, 71, 60, 0, 0, 70, 190, 96, 85, 94, 0, 90, 85, 87, 101, 0, 85, 85, 85, 134, 0, 85, 85, 137, 0, 85, 145, 0, 149, 150, - 0, 79, 0, 654, 643, 637, 637, 80, 0, 80, - 636, 0, 0, 0, 379, 523, 673, 674, 671, 672, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 554, - 553, 0, 0, 0, 0, 0, 0, 442, 0, 439, + 0, 79, 0, 655, 644, 638, 638, 80, 0, 80, + 637, 0, 0, 0, 379, 524, 674, 675, 672, 673, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 555, + 554, 0, 0, 0, 0, 0, 0, 442, 0, 439, 441, 0, 0, 0, 0, 0, 47, 69, 0, 54, 57, 72, 0, 95, 0, 86, 99, 0, 121, 0, 122, 123, 132, 0, 124, 125, 0, 126, 0, 0, - 173, 638, 639, 0, 640, 0, 642, 27, 0, 655, - 0, 0, 0, 684, 377, 0, 0, 0, 0, 555, - 557, 190, 517, 517, 516, 203, 190, 190, 443, 190, - 445, 188, 186, 185, 187, 466, 0, 190, 460, 0, - 571, 64, 56, 0, 561, 0, 102, 103, 104, 105, - 85, 85, 85, 85, 138, 0, 146, 142, 151, 152, - 0, 80, 0, 0, 537, 378, 0, 0, 688, 689, - 687, 0, 0, 0, 0, 520, 518, 519, 0, 0, - 440, 0, 462, 0, 0, 63, 97, 0, 0, 0, - 0, 127, 128, 129, 130, 0, 0, 0, 147, 641, - 653, 0, 0, 0, 0, 0, 0, 243, 214, 0, - 209, 0, 80, 220, 0, 192, 189, 0, 474, 65, - 0, 61, 106, 107, 108, 109, 110, 111, 85, 139, - 0, 143, 141, 534, 529, 530, 531, 532, 533, 378, - 527, 0, 535, 0, 0, 692, 689, 689, 686, 0, - 213, 0, 0, 208, 0, 218, 0, 219, 0, 0, - 0, 473, 62, 0, 0, 0, 131, 0, 0, 0, - 0, 27, 691, 690, 183, 180, 179, 182, 200, 181, - 201, 217, 345, 175, 177, 0, 176, 0, 215, 244, - 0, 212, 209, 80, 0, 222, 220, 0, 190, 480, - 478, 80, 80, 0, 112, 113, 114, 115, 140, 0, - 526, 194, 675, 190, 0, 0, 0, 0, 211, 210, - 0, 221, 0, 0, 0, 475, 477, 0, 0, 135, - 0, 0, 0, 0, 0, 0, 194, 216, 303, 304, - 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, - 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, - 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, - 335, 336, 337, 338, 297, 246, 248, 250, 252, 0, - 245, 270, 277, 279, 281, 283, 0, 276, 293, 184, - 80, 481, 378, 116, 190, 528, 678, 80, 0, 670, - 694, 0, 0, 0, 0, 0, 0, 0, 0, 240, - 0, 0, 0, 0, 0, 242, 0, 476, 0, 195, - 677, 0, 190, 193, 344, 190, 190, 298, 190, 0, - 241, 340, 190, 190, 247, 190, 0, 190, 249, 342, - 190, 190, 251, 190, 0, 190, 253, 190, 190, 271, - 190, 190, 278, 190, 190, 280, 190, 282, 190, 190, - 284, 190, 294, 190, 479, 0, 0, 299, 302, 0, - 300, 0, 254, 261, 0, 258, 0, 0, 260, 262, - 269, 0, 266, 0, 0, 268, 272, 275, 0, 273, - 285, 0, 287, 288, 289, 0, 291, 292, 295, 296, - 675, 178, 190, 0, 190, 190, 0, 190, 190, 190, - 0, 190, 190, 190, 190, 676, 301, 343, 257, 0, - 255, 0, 259, 265, 0, 263, 341, 267, 274, 286, - 290, 190, 0, 190, 256, 339, 264 + 173, 639, 640, 0, 641, 0, 643, 27, 0, 656, + 0, 0, 0, 685, 377, 0, 0, 0, 0, 556, + 558, 190, 518, 518, 517, 203, 190, 190, 443, 190, + 445, 188, 186, 185, 187, 190, 467, 0, 190, 460, + 0, 572, 64, 56, 0, 562, 0, 102, 103, 104, + 105, 85, 85, 85, 85, 138, 0, 146, 142, 151, + 152, 0, 80, 0, 0, 538, 378, 0, 0, 689, + 690, 688, 0, 0, 0, 0, 521, 519, 520, 0, + 0, 440, 462, 0, 463, 0, 0, 63, 97, 0, + 0, 0, 0, 127, 128, 129, 130, 0, 0, 0, + 147, 642, 654, 0, 0, 0, 0, 0, 0, 243, + 214, 0, 209, 0, 80, 220, 0, 192, 189, 0, + 475, 65, 0, 61, 106, 107, 108, 109, 110, 111, + 85, 139, 0, 143, 141, 535, 530, 531, 532, 533, + 534, 378, 528, 0, 536, 0, 0, 693, 690, 690, + 687, 0, 213, 0, 0, 208, 0, 218, 0, 219, + 0, 0, 0, 474, 62, 0, 0, 0, 131, 0, + 0, 0, 0, 27, 692, 691, 183, 180, 179, 182, + 200, 181, 201, 217, 345, 175, 177, 0, 176, 0, + 215, 244, 0, 212, 209, 80, 0, 222, 220, 0, + 190, 481, 479, 80, 80, 0, 112, 113, 114, 115, + 140, 0, 527, 194, 676, 190, 0, 0, 0, 0, + 211, 210, 0, 221, 0, 0, 0, 476, 478, 0, + 0, 135, 0, 0, 0, 0, 0, 0, 194, 216, + 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, + 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, + 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, + 333, 334, 335, 336, 337, 338, 297, 246, 248, 250, + 252, 0, 245, 270, 277, 279, 281, 283, 0, 276, + 293, 184, 80, 482, 378, 116, 190, 529, 679, 80, + 0, 671, 695, 0, 0, 0, 0, 0, 0, 0, + 0, 240, 0, 0, 0, 0, 0, 242, 0, 477, + 0, 195, 678, 0, 190, 193, 344, 190, 190, 298, + 190, 0, 241, 340, 190, 190, 247, 190, 0, 190, + 249, 342, 190, 190, 251, 190, 0, 190, 253, 190, + 190, 271, 190, 190, 278, 190, 190, 280, 190, 282, + 190, 190, 284, 190, 294, 190, 480, 0, 0, 299, + 302, 0, 300, 0, 254, 261, 0, 258, 0, 0, + 260, 262, 269, 0, 266, 0, 0, 268, 272, 275, + 0, 273, 285, 0, 287, 288, 289, 0, 291, 292, + 295, 296, 676, 178, 190, 0, 190, 190, 0, 190, + 190, 190, 0, 190, 190, 190, 190, 677, 301, 343, + 257, 0, 255, 0, 259, 265, 0, 263, 341, 267, + 274, 286, 290, 190, 0, 190, 256, 339, 264 }; /* YYPGOTO[NTERM-NUM]. */ static const yytype_int16 yypgoto[] = { - -1232, -1232, -1232, -222, -226, -189, -1232, 247, -195, 289, - -1232, -1232, -1232, -1232, -1232, -1232, -196, -342, -662, -56, - -783, -637, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -569, - -252, -1232, -1232, -1232, -849, -1232, -1232, -247, 1222, 1220, - -57, 1263, -1232, -708, -589, -607, -1232, -1232, -169, -1232, - -1232, -168, -1232, -1232, -1232, -167, -321, -1232, -1232, -791, - -1232, -1232, -1232, -1232, -1232, -788, -1232, -1232, -1232, -1232, - -655, -1232, -1232, -1232, 108, -1232, -1232, -1232, -1232, -1232, - 130, -1232, -1232, -503, -1232, -1232, -489, -1232, -1232, -1222, - -1232, -1232, -1232, -1232, -552, 1709, -427, -1231, -565, -1232, - -1232, -1232, -746, -909, -36, -1232, -516, -1232, -1232, -1232, - -1232, -518, -334, 99, -1232, -1232, -315, -1007, -385, -466, - -1008, -980, -1232, -942, -614, -1232, -1232, -1232, -1232, -600, - -1232, -1232, -1232, -1232, -606, -611, -1232, -583, -1232, -776, - -1232, -761, -1232, 711, -412, -190, 514, -417, 29, -245, - -310, 107, -1232, -1232, -1232, 193, -1232, -110, -1232, -77, - -1232, -1232, -1232, -1232, -1232, -1232, -835, -1232, -1232, -1232, - -1232, 635, 637, 638, 640, -283, 531, -1232, -1232, -91, - 41, -1232, -1232, -1232, -1232, -1232, -107, -1232, -1232, -1232, - -1232, 10, -1232, 502, -104, -1232, -1232, -1232, 642, -1232, - -1232, -1232, -620, -1232, -1232, -1232, 580, 584, 510, -174, - 2, 312, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -367, - -798, -966, -1232, -1232, 662, 671, -1232, 232, -1232, -411, - -1232, -1232, -1232, -182, -1232, 675, -1232, -178, -1232, 677, - -1232, -186, -1232, 679, -1232, -187, -1232, -1232, 414, -1232, - -1232, -1232, -1232, -1232, 969, -289, -1232, -1232, -379, -1232, - -1232, -785, -1232, -1232, -1232, -775, -1232, -1232, 687, -1232, - -1232, 625, -1232, 628, -1232, -1232, 230, -618, 234, 241, - 242, 699, -1232, -1232, -1232, -1232, -1232, 717, -1232, -1232, - -1232, -1232, 718, -1232, -1232, 719, -1232, -1232, 727, -1232, - -1232, 738, -188, -344, 109, -1232, -1232, -1232, -1232, -1232, - -1232, -1232, -1232, -1232, -1232, 842, -1232, 539, -179, -1232, - -119, -209, -1232, -1232, -86, -1232, 115, -1232, -1232, -1232, - -808, -1232, -1232, -1232, 549, 16, 887, -1232, -1232, 551, - -1083, -502, -1232, -988, 892, -1232, -1232, -1232, -49, -1232, - -375, -1232, -200 + -1232, -1232, -1232, -221, -226, -189, -1232, 266, -194, 293, + -1232, -1232, -1232, -1232, -1232, -1232, -196, -341, -654, -53, + -782, -642, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -563, + -250, -1232, -1232, -1232, -847, -1232, -1232, -237, 1251, 1249, + -57, 1948, -1232, -665, -567, -574, -1232, -1232, -157, -1232, + -1232, -156, -1232, -1232, -1232, -154, -306, -1232, -1232, -791, + -1232, -1232, -1232, -1232, -1232, -783, -1232, -1232, -1232, -1232, + -657, -1232, -1232, -1232, 110, -1232, -1232, -1232, -1232, -1232, + 143, -1232, -1232, -505, -1232, -1232, -473, -1232, -1232, -1231, + -1232, -1232, -1232, -1232, -557, 1794, -410, -1201, -544, -1232, + -1232, -1232, -748, -930, -37, -1232, -497, -1232, -1232, -1232, + -1232, -500, -334, 124, -1232, -1232, -290, -1018, -364, -446, + -1004, -578, -1232, -852, -597, -1232, -1232, -1232, -1232, -602, + -1232, -1232, -1232, -1232, -729, -588, -1232, -701, -1232, -556, + -1232, -724, -1232, 716, -416, -148, 519, -419, 36, -102, + -326, 101, -1232, -1232, -1232, 188, -1232, -117, -1232, -67, + -1232, -1232, -1232, -1232, -1232, -1232, -836, -1232, -1232, -1232, + -1232, 600, 604, 605, 607, -280, 663, -1232, -1232, -91, + 42, -1232, -1232, -1232, -1232, -1232, -107, -1232, -1232, -1232, + -1232, 10, -1232, 506, -105, -1232, -1232, -1232, 609, -1232, + -1232, -1232, -644, -1232, -1232, -1232, 546, 552, 527, -204, + 4, 277, -1232, -1232, -1232, -1232, -1232, -1232, -1232, -367, + -794, -938, -1232, -1232, 627, 633, -1232, 197, -1232, -448, + -1232, -1232, -1232, -192, -1232, 640, -1232, -185, -1232, 641, + -1232, -186, -1232, 644, -1232, -187, -1232, -1232, 378, -1232, + -1232, -1232, -1232, -1232, 540, -406, -1232, -1232, -384, -1232, + -1232, -781, -1232, -1232, -1232, -796, -1232, -1232, 648, -1232, + -1232, 588, -1232, 590, -1232, -1232, 190, -624, 193, 194, + 195, 660, -1232, -1232, -1232, -1232, -1232, 671, -1232, -1232, + -1232, -1232, 672, -1232, -1232, 673, -1232, -1232, 674, -1232, + -1232, 675, -179, -349, 76, -1232, -1232, -1232, -1232, -1232, + -1232, -1232, -1232, -1232, -1232, 806, -1232, 478, -253, -1232, + -119, -209, -1232, -1232, -109, -1232, 81, -1232, -1232, -1232, + -814, -1232, -1232, -1232, 489, -64, 816, -1232, -1232, 484, + -1087, -552, -1232, -1001, 842, -1232, -1232, -1232, -96, -1232, + -458, -1232, -249 }; /* YYDEFGOTO[NTERM-NUM]. */ @@ -1284,18 +1284,18 @@ -1, 37, 38, 39, 235, 620, 237, 880, 238, 470, 239, 240, 419, 420, 241, 348, 242, 243, 894, 589, 503, 590, 504, 695, 890, 591, 809, 969, 592, 810, - 893, 1032, 1033, 1110, 811, 812, 813, 895, 109, 215, + 893, 1033, 1034, 1112, 811, 812, 813, 895, 109, 215, 382, 456, 922, 609, 749, 819, 712, 713, 714, 715, - 716, 717, 718, 905, 1035, 719, 720, 721, 910, 722, - 723, 914, 1045, 1120, 1199, 724, 1087, 725, 917, 1047, - 726, 727, 920, 1050, 489, 351, 41, 136, 245, 427, - 428, 429, 615, 430, 431, 617, 729, 730, 1172, 1173, - 1174, 1175, 1025, 1026, 874, 383, 667, 1176, 1221, 673, - 668, 1177, 870, 1016, 448, 449, 1143, 450, 1140, 451, - 452, 1147, 453, 649, 650, 651, 858, 1100, 1098, 1103, - 1101, 1180, 1269, 1324, 1332, 1270, 1339, 1276, 1342, 1347, - 1277, 1352, 1294, 1317, 1264, 1325, 1326, 1333, 1334, 1327, - 1319, 1178, 42, 252, 353, 534, 44, 354, 253, 138, + 716, 717, 718, 905, 1036, 719, 720, 721, 910, 722, + 723, 914, 1046, 1122, 1201, 724, 1089, 725, 917, 1048, + 726, 727, 920, 1051, 489, 351, 41, 136, 245, 427, + 428, 429, 615, 430, 431, 617, 729, 730, 1174, 1175, + 1176, 1177, 1026, 1027, 874, 383, 667, 1178, 1223, 673, + 668, 1179, 870, 1016, 448, 449, 1145, 450, 1142, 451, + 452, 1149, 453, 649, 650, 651, 858, 1102, 1100, 1105, + 1103, 1182, 1271, 1326, 1334, 1272, 1341, 1278, 1344, 1349, + 1279, 1354, 1296, 1319, 1266, 1327, 1328, 1335, 1336, 1329, + 1321, 1180, 42, 252, 353, 534, 44, 354, 253, 138, 247, 538, 248, 441, 624, 435, 436, 621, 619, 254, 255, 445, 446, 634, 542, 630, 845, 631, 853, 46, 47, 48, 49, 50, 51, 454, 140, 52, 53, 256, @@ -1303,11 +1303,11 @@ 56, 257, 58, 149, 203, 298, 299, 492, 59, 60, 275, 276, 787, 277, 278, 279, 258, 259, 457, 876, 936, 375, 62, 152, 284, 285, 482, 478, 963, 738, - 680, 881, 1027, 63, 64, 65, 290, 486, 1151, 1192, - 1193, 1282, 66, 67, 68, 69, 70, 71, 72, 73, + 680, 881, 1028, 63, 64, 65, 290, 486, 1153, 1194, + 1195, 1284, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 210, 80, 318, 319, 506, 320, 321, 509, 937, 953, 461, 659, 941, 520, - 746, 739, 1129, 1130, 1131, 740, 741, 1055, 81, 82, + 746, 739, 1131, 1132, 1133, 740, 741, 1056, 81, 82, 83, 260, 84, 261, 85, 86, 262, 770, 263, 264, 265, 87, 88, 162, 324, 325, 703, 89, 292, 293, 294, 295, 90, 303, 304, 91, 308, 309, 92, 313, @@ -1315,8 +1315,8 @@ 96, 182, 97, 183, 184, 938, 218, 219, 837, 99, 186, 334, 335, 516, 336, 191, 342, 343, 927, 928, 742, 743, 100, 221, 222, 605, 939, 102, 224, 225, - 940, 1223, 103, 748, 328, 105, 523, 848, 849, 1059, - 1096, 524, 1060 + 940, 1225, 103, 748, 328, 105, 523, 848, 849, 1060, + 1098, 524, 1061 }; /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If @@ -1324,638 +1324,654 @@ number is the opposite. If YYTABLE_NINF, syntax error. */ static const yytype_int16 yytable[] = { - 115, 270, 61, 236, 421, 344, 672, 479, 146, 711, - 57, 337, 187, 188, 339, 737, 347, 532, 943, 291, - 307, 312, 531, 697, 326, 613, 302, 421, 929, 45, - 906, 1053, 519, 907, 842, 808, 911, 871, 772, 345, - 931, 54, 434, 932, 970, 485, 1017, 190, 349, 918, - 1160, 925, 205, 1099, 207, 1099, 1104, 803, 789, 512, - 1, 926, 1316, 1, 19, 21, 1108, 518, 1093, 1168, - 1, 15, 5, 1323, 1323, 1331, 1331, 1314, 1338, 192, - 1323, 193, 464, 194, 195, 249, 196, 144, 114, 34, - 36, 5, 198, 199, 250, 200, 201, 202, 204, 202, - 206, 202, 208, 209, 267, 211, 1168, 212, 40, 5, - 33, 442, 251, 822, 106, 825, 17, 828, 266, 831, - 333, 833, 251, 10, 289, 316, 17, 216, 1392, 217, - 220, 223, 483, 226, 338, 1182, 26, 340, 1104, 26, - 29, 189, 1394, 29, 1170, 1397, 26, 144, 670, 1398, - 29, 422, 1401, 671, 15, 664, 1402, 189, 5, 665, - 282, 425, 5, 145, 1184, 5, 908, 1012, 1187, 912, - 370, 704, 915, 467, 31, 32, 476, 1013, 311, 333, - 134, 1170, 283, 1037, 1075, 1051, 1038, 1421, 350, 1040, - 352, -628, 1423, 33, 525, 1203, 484, 357, 249, 359, - 710, 502, 144, 362, 363, 735, 1048, 250, 513, 364, - 365, 366, 367, 5, 368, 326, 369, 202, 371, 994, - 1092, 996, 526, 145, 734, 1111, 1058, 374, 1355, 626, - 288, 968, 378, 379, 1288, 380, 485, 246, 891, 274, - 1188, 384, 706, 707, 948, 386, 387, 924, 951, 850, - 389, 390, 956, 966, 731, 392, 393, 627, 961, 682, - -630, 1152, 2, 398, 144, 708, 709, 401, 476, 661, - 403, 587, 4, 588, 447, 560, 5, 405, 145, 408, - 409, 411, 412, 414, 5, 415, 1112, 600, 2, 1113, - 1115, 973, 2, 704, 1158, 975, 1134, 735, 4, 978, - 980, 981, 4, 5, 984, 985, 7, 5, 987, 19, - 297, 1284, 10, 296, -51, 301, 588, 317, 246, 1039, - 1136, 12, 1041, 338, 1042, 1107, 340, 899, 1043, 903, - 459, -631, 903, 251, 34, 903, 315, 316, 903, 5, - 145, 903, 5, 1089, 246, 433, 707, 633, 704, -632, - 493, 704, 670, 496, 1328, 807, 499, 671, 476, 24, - 1345, 323, 5, 31, 32, 1194, 5, 443, 708, 709, - 527, 528, 341, 473, 463, 708, 709, 352, 533, 709, - 536, 1183, 1312, 664, 537, 539, 540, 665, 541, 1191, - 107, 108, 687, 694, 1145, 868, 696, 691, 546, 693, - 547, 548, 689, 549, 733, 551, 552, 762, 554, 555, - 326, 556, 558, 1201, 1287, 346, 562, 563, 564, 423, - 424, 374, 1114, 1116, 1117, 1118, 569, 570, -30, 755, - 573, 574, 757, 1081, 1082, 1083, 1084, 579, 580, 531, - 582, 583, 229, 585, 586, 213, 214, 1308, 421, 851, - 859, 860, 861, 596, 597, 598, 654, 656, 1225, 1226, - 603, 444, 604, 1021, 607, 532, 608, 1022, 1023, 1024, - 653, 655, 676, 678, 5, 1209, 7, 1321, 883, 1314, - 21, 882, 477, 1215, 1216, 306, 675, 677, 903, 459, - 903, 751, 490, 447, 903, 5, 5, 686, 1195, 1196, - 1197, 696, 329, 330, 704, 36, 1135, 705, 706, 707, - 652, 1156, 518, 657, 1189, 660, 522, 1318, 662, 663, - 423, 1335, 1335, 517, 1340, 1344, 674, 1349, 1349, 521, - 1353, 708, 709, 710, 134, 228, 229, 685, 230, 231, - 232, 233, 234, 688, 424, 5, 690, 5, 433, 692, - 1171, 807, 1314, 1181, 704, 752, 587, 1185, 706, 707, - 698, 1190, 533, 1283, 5, 533, 854, 537, 747, 759, - 1164, 805, 1307, 704, 1165, 1166, 1167, 705, 707, 1310, - 5, 708, 709, 1321, 670, 1314, 921, 764, 1395, 671, - 588, 903, 765, 766, 767, 1399, 768, 769, 771, 769, - 708, 709, 774, 1097, 1271, 1403, 1278, 709, 889, 1404, - 5, 696, 892, 884, 1329, 1314, 469, 1227, 785, 786, - 788, 786, 154, 790, 156, 791, 158, 793, 160, 795, - 233, 234, 1169, 1286, 1057, 418, 502, 913, 942, 804, - 916, 618, 971, 141, 967, 623, 815, 897, 150, 900, - 153, 901, 155, 1036, 157, 421, 159, 612, 816, 1121, - 818, 1292, 864, 821, 666, 824, 1208, 827, 1211, 830, - 684, 830, 628, 629, 598, 708, 709, 836, 1102, 875, - 1186, -59, 604, 604, 1336, 608, -59, -59, -59, 844, - 1343, 807, 246, 433, 847, 1164, 1097, 696, 892, 1165, - 1166, 1167, 1168, 1164, 1350, 5, 728, 1165, 1166, 1167, - 1168, 43, 728, 5, 704, 287, 728, 705, 706, 707, - 1348, 1348, 704, 1066, 1067, 705, 706, 707, 1057, 622, - 1164, 758, 807, 933, 1165, 1166, 1167, 855, 856, 857, - 5, 708, 709, 710, 1329, 1314, 991, 992, 1164, 708, - 709, 710, 1165, 1166, 1167, 839, 840, 1169, 5, 1015, - 1015, 1162, 1163, 1314, 165, 1169, 166, 167, 807, 168, - 1164, 169, 1031, 1052, 1165, 1166, 1167, 1170, 862, 280, - 5, 866, 1123, 281, 1019, 1170, 1124, 1125, 1126, 1127, - 566, 170, 1169, 502, 759, 872, 844, 1044, 878, 1046, - 171, 10, 683, 1280, 172, 769, 173, 1074, 174, 769, - 1169, 952, 507, 954, 955, 786, 175, 896, 958, 421, - 268, 786, 251, 269, 962, 791, 17, 699, 176, 134, - 228, 700, 1169, 230, 231, 232, 286, 696, 701, 702, - 835, 830, 98, 974, 1128, 830, 177, 178, 179, 977, - 979, 830, 31, 32, 983, 830, 180, 986, 830, 1422, - 988, 228, 229, 989, 230, 231, 232, 181, 836, 836, - 993, 458, 608, 406, 997, 998, 228, 863, 1109, 230, - 231, 232, 474, 475, 1001, 1002, 410, 101, 1405, 3, - 413, 488, 104, 1003, 873, 1137, 1119, 6, 495, 0, - 0, 498, 0, 0, 501, 0, 8, 0, 0, 0, - 0, 0, 511, 9, 696, 228, 11, 0, 230, 231, - 232, 233, 234, 805, 898, 806, 902, 0, 0, 902, - 0, 16, 902, 0, 0, 902, 0, 0, 902, 0, - 0, 0, 18, 1056, 728, 20, 0, 22, 1061, 1062, - 1063, 1064, 934, 0, 1015, 952, 952, 0, 25, 0, - 27, 0, 28, 0, 30, 0, 0, 0, 1198, 0, - 35, 0, 1073, 0, 0, 0, 0, 0, 0, 1077, - 1078, 1079, 1097, 1080, 830, 830, 830, 0, 1085, 476, - 1086, 5, 0, 0, 608, 0, 1091, 5, 844, 0, - 704, 0, 1095, 705, 706, 707, 704, 0, 0, 705, - 706, 707, 0, 0, 0, 0, 0, 0, 735, 0, - 0, 0, 923, 736, 0, 1015, 0, 708, 709, 710, - 0, 0, 0, 708, 709, 710, 0, 0, 0, 0, - 0, 1139, 0, 1142, 0, 608, 1146, 0, 0, 0, - 1149, 1150, 0, 0, 0, 1153, 1154, 0, 1155, 0, - 1011, 830, 0, 1157, 0, 0, 0, 0, 0, 5, - 0, 0, 844, 0, 0, 0, 0, 1161, 704, 1095, - 1095, 705, 706, 707, 0, 902, 0, 902, 0, 0, - 0, 902, 0, 1164, 0, 736, 1015, 1165, 1166, 1167, - 1168, 728, 0, 5, 0, 708, 709, 710, 0, 0, - 0, 0, 704, 0, 0, 705, 706, 707, 0, 0, - 0, 1206, 1189, 1207, 0, 1142, 608, 1210, 0, 1146, - 1212, 0, 0, 0, 1214, 608, 0, 1218, 0, 708, - 709, 710, 0, 0, 1220, 1222, 0, 0, 0, 0, - 476, 0, 0, 0, 0, 1169, 228, 229, 5, 230, - 231, 232, 233, 234, 0, 1393, 806, 704, 1291, 1220, - 705, 706, 707, 0, 0, 1170, 0, 1396, 0, 735, - 0, 0, 0, 0, 1400, 134, 228, 229, 902, 230, - 231, 232, 233, 234, 708, 709, 710, 0, 0, 0, - 0, 0, 0, 728, 0, 0, 0, 1293, 1295, 1296, - 1297, 1298, 0, 0, 1300, 1301, 1302, 1303, 1304, 476, - 0, 1306, 0, 608, 0, 844, 0, 5, 228, 0, - 608, 230, 231, 232, 233, 234, 704, 0, 806, 705, - 706, 707, 0, 0, 0, 0, 0, 728, 735, 0, - 728, 0, 0, 736, 728, 0, 0, 0, 728, 0, - 0, 1359, 0, 708, 709, 710, 0, 110, 1364, 0, - 1367, 0, 0, 0, 111, 112, 1371, 113, 1374, 0, - 116, 0, 117, 1378, 118, 0, 1381, 0, 119, 0, - 120, 1385, 121, 0, 122, 0, 123, 0, 124, 0, - 125, 0, 126, 0, 0, 0, 0, 0, 0, 0, - 127, 0, 0, 0, 728, 0, 0, 128, 0, 129, - 0, 130, 728, 131, 0, 132, 0, 133, 0, 0, - 728, 460, 462, 1222, 0, 465, 466, 0, 1409, 1411, - 228, 229, 1414, 230, 231, 232, 233, 234, 0, 487, - 0, 0, 0, 0, 0, 0, 494, 0, 0, 497, - 0, 0, 500, 0, 0, 0, 0, 0, 0, 197, - 510, 0, 135, 137, 139, 139, 142, 0, 0, 148, - 139, 151, 139, 148, 139, 148, 139, 148, 139, 148, - 161, 163, 0, 185, 185, 185, 0, 0, 0, 0, - 0, 1315, 0, 1322, 1322, 1330, 1330, 0, 1337, 1341, - 1322, 1346, 1346, 227, 1351, 0, 3, 0, 0, 0, - 0, 0, 0, 300, 6, 305, 0, 310, 0, 5, - 322, 0, 0, 8, 0, 0, 0, 0, 704, 0, - 9, 705, 706, 707, 0, 0, 0, 0, 0, 0, - 0, 0, 14, 0, 0, 244, 0, 0, 16, 0, - 272, 0, 0, 0, 0, 708, 709, 710, 0, 18, - 355, 0, 20, 0, 22, 356, 0, 358, 0, 0, - 360, 361, 0, 0, 0, 25, 5, 27, 0, 28, - 0, 30, 0, 0, 0, 704, 0, 35, 705, 706, - 707, 0, 372, 373, 0, 0, 0, 376, 0, 0, - 377, 0, 736, 0, 747, 0, 0, 381, 0, 0, - 0, 385, 708, 709, 710, 0, 388, 0, 0, 0, - 0, 391, 0, 0, 0, 0, 394, 0, 0, 0, - 0, 397, 0, 400, 0, 0, 0, 402, 0, 0, - 0, 0, 0, 0, 404, 0, 0, 407, 0, 0, - 0, 0, 0, 0, 417, 416, 0, 0, 0, 0, - 0, 432, 0, 438, 439, 635, 636, 637, 638, 639, - 640, 641, 642, 643, 644, 645, 646, 647, 648, 0, - 472, 0, 0, 0, 0, 480, 635, 636, 637, 638, - 639, 640, 641, 642, 643, 0, 0, 0, 0, 0, - 0, 0, 0, 426, 0, 0, 0, 0, 440, 137, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 481, - 137, 0, 0, 0, 0, 0, 0, 0, 491, 0, - 0, 529, 0, 530, 0, 0, 0, 0, 535, 0, - 505, 0, 0, 508, 0, 0, 0, 0, 543, 0, - 515, 0, 0, 0, 545, 544, 0, 0, 0, 3, - 0, 0, 0, 550, 553, 0, 5, 6, 0, 0, - 0, 561, 0, 0, 0, 704, 8, 0, 705, 706, - 707, 567, 0, 9, 0, 0, 572, 571, 0, 0, - 0, 0, 577, 578, 0, 14, 581, 0, 0, 584, - 0, 16, 708, 709, 710, 0, 593, 0, 0, 595, - 0, 0, 18, 0, 601, 20, 602, 22, 0, 0, - 0, 0, 606, 0, 0, 611, 610, 0, 25, 0, - 27, 0, 28, 0, 30, 0, 0, 0, 625, 0, - 35, 0, 632, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 669, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 681, 0, - 0, 0, 614, 616, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 658, 0, 0, 0, 0, 0, 732, - 0, 0, 0, 0, 0, 744, 0, 0, 0, 750, - 679, 0, 0, 0, 0, 753, 0, 754, 0, 0, - 0, 756, 0, 0, 0, 0, 0, 760, 0, 0, - 0, 0, 0, 761, 763, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 745, - 0, 0, 0, 185, 0, 0, 0, 773, 0, 0, - 775, 776, 777, 778, 0, 0, 0, 0, 0, 0, - 781, 0, 0, 0, 784, 783, 0, 0, 0, 0, - 0, 0, 0, 792, 0, 794, 0, 0, 797, 0, - 799, 0, 801, 0, 0, 0, 0, 0, 814, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 817, 0, - 820, 0, 0, 823, 0, 826, 0, 829, 0, 832, - 0, 0, 834, 0, 0, 0, 0, 0, 0, 0, - 838, 0, 0, 841, 0, 843, 0, 0, 0, 0, - 846, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 865, 0, 867, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 877, 0, 879, - 0, 0, 0, 0, 0, 0, 885, 0, 886, 0, - 887, 0, 888, 0, 852, 0, 395, 396, 0, 399, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 869, 0, 869, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 930, 0, 0, - 0, 0, 0, 0, 0, 935, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 904, 0, 0, 909, 0, 0, 0, - 0, 0, 0, 0, 0, 919, 0, 0, 0, 0, - 0, 679, 0, 0, 679, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 976, 0, 0, 0, 0, - 982, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 990, 0, 0, 0, 0, 0, 0, 0, - 995, 0, 0, 999, 0, 0, 0, 0, 0, 0, - 1000, 0, 0, 1004, 1005, 1006, 1007, 1008, 1009, 0, - 0, 1010, 0, 1014, 0, 0, 1018, 0, 559, 0, - 0, 1020, 0, 1028, 1029, 1030, 565, 0, 0, 0, - 0, 568, 1034, 0, 0, 0, 0, 0, 575, 576, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1228, - 1229, 1230, 1231, 1232, 1233, 1234, 594, 1235, 1236, 1237, - 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246, 1247, - 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, - 1258, 0, 0, 1259, 1260, 1261, 1262, 1263, 1071, 0, - 0, 0, 1049, 0, 0, 1076, 0, 0, 0, 0, - 0, 1054, 0, 0, 0, 0, 0, 0, 0, 0, - 1088, 0, 1090, 0, 0, 0, 0, 0, 0, 1094, - 0, 0, 0, 0, 0, 0, 0, 0, 1105, 1106, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1122, 0, 0, - 0, 0, 1132, 0, 0, 1133, 1138, 0, 0, 0, - 0, 1141, 0, 1144, 0, 0, 1148, 0, 0, 0, - 0, 0, 0, 0, 869, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1159, 0, 0, 0, 679, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 779, 780, 0, 1200, 0, - 1202, 782, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 796, 1204, 798, 1205, - 800, 0, 802, 0, 1179, 0, 0, 1179, 0, 0, - 0, 1179, 0, 0, 0, 1217, 0, 0, 0, 1219, - 0, 0, 869, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1279, 0, 0, 0, 0, 0, 0, - 0, 1285, 0, 1289, 1290, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 869, 869, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1281, 0, 0, 0, 0, 0, 0, 0, 0, 1299, - 0, 0, 0, 0, 0, 0, 1305, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1311, 0, 1313, 0, 1320, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1354, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1361, 0, 0, 0, 0, 0, 0, 1366, 0, - 0, 0, 0, 0, 0, 0, 1373, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 944, 945, 946, - 947, 0, 949, 950, 0, 1390, 1391, 1, 2, 0, - 0, 0, 957, 3, 959, 960, 0, 0, 4, 0, - 5, 6, 964, 965, 0, 0, 0, 7, 0, 0, - 8, 0, 0, 0, 0, 972, 0, 9, 10, 0, - 11, 0, 12, 1407, 0, 0, 0, 13, 0, 14, - 1416, 0, 15, 0, 0, 16, 0, 0, 0, 0, - 0, 0, 0, 17, 0, 0, 18, 0, 19, 20, - 21, 22, 1425, 0, 0, 0, 0, 0, 0, 23, - 24, 0, 25, 26, 27, 0, 28, 29, 30, 31, - 32, 33, 0, 34, 35, 36, 1265, 1228, 1229, 1230, - 1231, 1232, 1233, 1234, 1266, 1235, 1236, 1237, 1238, 1239, - 1240, 1241, 1242, 1243, 1244, 1245, 1246, 1247, 1248, 1249, - 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258, 1267, - 1268, 1259, 1260, 1261, 1262, 1263, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1065, 0, 0, 0, 0, 1068, 1069, 0, 1070, 0, - 0, 0, 0, 0, 0, 0, 1072, 1272, 1228, 1229, - 1230, 1231, 1232, 1233, 1234, 1273, 1235, 1236, 1237, 1238, - 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246, 1247, 1248, - 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258, - 1274, 1275, 1259, 1260, 1261, 1262, 1263, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 115, 672, 270, 236, 61, 421, 344, 479, 146, 291, + 57, 337, 187, 188, 339, 532, 347, 943, 531, 302, + 307, 312, 929, 613, 190, 1017, 434, 519, 421, 697, + 906, 711, 772, 326, 789, 871, 45, 737, 907, 345, + 926, 911, 54, 808, 931, 485, 970, 932, 1162, 349, + 918, 205, 803, 207, 512, 925, 1095, 1110, 1101, 1054, + 1101, 1106, 106, 1, 144, 670, 1325, 1325, 1333, 1333, + 671, 1340, 518, 1325, 15, 5, 114, 842, 249, 192, + 2, 193, 1, 194, 195, 464, 196, 250, 1170, 144, + 4, 5, 198, 199, 1318, 200, 201, 202, 204, 202, + 206, 202, 208, 209, 267, 211, 144, 212, 144, 189, + 40, 338, 476, 33, 340, 251, 1, 5, 447, 17, + 5, 5, 2, 134, 10, 333, 1184, 216, 266, 217, + 220, 223, 4, 226, 289, 1396, 246, 433, 1399, 26, + 145, 735, 1400, 29, 1106, 1403, 822, 333, 825, 1404, + 828, 422, 831, 189, 833, 316, 1013, 249, 26, 282, + 1394, 425, 29, 1172, 908, 145, 250, 912, 251, 370, + 915, 1012, 17, 19, 467, 31, 32, 476, 246, 1052, + 1423, 283, 145, 1038, 145, 1425, 484, 1077, 350, -629, + 352, 1039, 26, 5, 1041, 525, 29, 357, 34, 359, + -631, 502, 704, 362, 363, 1059, 735, 1049, 513, 364, + 365, 366, 367, 626, 368, -632, 369, 202, 371, 734, + 1094, 1190, 526, 1290, 326, 1205, 1357, 374, 956, 1113, + 707, 710, 378, 379, 961, 380, 485, 288, 948, 968, + 274, 384, 951, 682, 891, 386, 387, 850, 966, 731, + 389, 390, 708, 709, 442, 392, 393, 107, 108, 21, + 627, 2, 994, 398, 996, 1154, 15, 401, 19, 924, + 403, 4, 661, 246, 301, 483, 560, 405, 10, 408, + 409, 411, 412, 414, 36, 415, 1170, 600, 1114, 5, + 311, 7, 1286, 34, 1316, 1160, 1115, 1117, 5, 251, + 338, 1138, 1136, 340, 1125, 33, 12, 704, 1126, 1127, + 1128, 1129, 297, 670, 587, 459, 588, 1040, 671, 317, + 1042, 5, 1043, 5, 973, 1109, 1044, 296, 975, 31, + 32, 5, 978, 980, 981, 709, 664, 984, 985, 323, + 665, 987, -633, 899, 24, 903, 5, 633, 903, 493, + 5, 903, 496, 5, 903, 499, 807, 903, 1316, 704, + -51, 1172, 588, 1314, 315, 316, 1130, 443, 706, 707, + 527, 528, 1196, 476, 463, 473, 341, 352, 533, 664, + 536, 5, 687, 665, 537, 539, 540, 1091, 541, 21, + 868, 708, 709, 694, 306, 689, 696, 691, 546, 693, + 547, 548, 346, 549, 733, 551, 552, 762, 554, 555, + 1289, 556, 558, 1203, 36, -30, 562, 563, 564, 326, + 1185, 374, 1116, 1118, 1119, 1120, 569, 570, 1193, 5, + 573, 574, 1323, 851, 1316, 531, 229, 579, 580, 1147, + 582, 583, 5, 585, 586, 670, 1330, 444, 1310, 421, + 671, 704, 1347, 596, 597, 598, 654, 656, 1227, 1228, + 603, 459, 604, 532, 607, 882, 608, 1083, 1084, 1085, + 1086, 755, 676, 678, 757, 859, 860, 861, 708, 709, + 653, 655, 1166, 1273, 477, 1280, 1167, 1168, 1169, 517, + 7, 751, 5, 447, 490, 521, 675, 677, 1197, 1198, + 1199, 696, 10, 883, 903, 5, 903, 686, 522, 652, + 903, 5, 657, 423, 660, 1331, 1316, 662, 663, 424, + 1211, 213, 214, 251, 518, 674, -59, 17, 1217, 1218, + 1137, -59, -59, -59, 134, 228, 685, 286, 230, 231, + 232, 433, 688, 747, 1171, 690, 1158, 5, 692, 855, + 856, 857, 807, 31, 32, 752, 704, 329, 330, 698, + 706, 707, 533, 1285, 5, 533, 854, 537, 1186, 759, + 233, 234, 1189, 704, 1173, 628, 629, 1183, 707, 246, + 433, 1187, 587, 708, 709, 1192, 805, 764, 708, 709, + 423, 424, 765, 766, 767, 705, 768, 769, 771, 769, + 708, 709, 774, 884, 1350, 1350, 1067, 1068, 889, 903, + 228, 696, 892, 230, 231, 232, 921, 1309, 785, 786, + 788, 786, 588, 790, 1312, 791, 154, 793, 156, 795, + 158, 1099, 160, 709, 1058, 469, 502, 913, 942, 418, + 916, 1229, 804, 618, 971, 228, 815, 623, 230, 231, + 232, 233, 234, 805, 967, 806, 421, 1288, 816, 897, + 818, 900, 864, 821, 901, 824, 666, 827, 1037, 830, + 612, 830, 991, 992, 598, 839, 840, 836, 1123, 875, + 1164, 1165, 604, 604, 1294, 608, 1166, 1210, 1213, 844, + 1167, 1168, 1169, 807, 847, 684, 5, 696, 892, 1323, + 1104, 1316, 1188, 1338, 1352, 134, 228, 229, 728, 230, + 231, 232, 233, 234, 728, 1345, 43, 1166, 728, 1099, + 287, 1167, 1168, 1169, 622, 758, 933, 5, 5, 165, + 1058, 1331, 1316, 166, 167, 807, 168, 704, 169, 1320, + 705, 706, 707, 1337, 1337, 280, 1342, 1346, 1171, 1351, + 1351, 281, 1355, 1021, 1019, 566, 170, 1022, 1023, 1024, + 1015, 1015, 171, 5, 708, 709, 710, 683, 1282, 172, + 173, 807, 1032, 174, 1053, 141, 507, 175, 862, 1171, + 150, 866, 153, 268, 155, 269, 157, 699, 159, 176, + 700, 701, 702, 502, 759, 872, 844, 1045, 878, 1047, + 177, 178, 179, 180, 181, 769, 98, 835, 1076, 769, + 1397, 952, 406, 954, 955, 786, 101, 1401, 958, 896, + 3, 786, 421, 413, 962, 791, 410, 1405, 6, 228, + 229, 1406, 230, 231, 232, 233, 234, 8, 696, 806, + 1407, 830, 104, 974, 9, 830, 1003, 11, 1139, 977, + 979, 830, 0, 0, 983, 830, 1424, 986, 830, 0, + 988, 0, 16, 989, 0, 0, 0, 0, 836, 836, + 993, 0, 608, 18, 997, 998, 20, 1166, 22, 863, + 1111, 1167, 1168, 1169, 1001, 1002, 0, 5, 458, 25, + 0, 27, 1316, 28, 0, 30, 873, 0, 1121, 474, + 475, 35, 460, 462, 0, 0, 465, 466, 488, 228, + 229, 0, 230, 231, 232, 495, 696, 0, 498, 0, + 487, 501, 0, 0, 0, 0, 898, 494, 902, 511, + 497, 902, 0, 500, 902, 0, 0, 902, 0, 1171, + 902, 510, 0, 1057, 0, 0, 728, 0, 1062, 1063, + 1064, 1065, 0, 0, 934, 952, 952, 1015, 0, 228, + 0, 0, 230, 231, 232, 233, 234, 0, 0, 806, + 1200, 228, 229, 1075, 230, 231, 232, 233, 234, 0, + 1079, 1080, 1081, 0, 1082, 830, 830, 830, 0, 1087, + 0, 1088, 0, 0, 0, 608, 0, 1093, 0, 844, + 0, 0, 0, 1097, 0, 0, 0, 0, 1166, 1099, + 0, 0, 1167, 1168, 1169, 1170, 0, 0, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 704, 1015, 0, + 705, 706, 707, 635, 636, 637, 638, 639, 640, 641, + 642, 643, 0, 1141, 0, 1144, 0, 608, 1148, 0, + 0, 0, 1151, 1152, 708, 709, 710, 1155, 1156, 0, + 1157, 0, 1011, 830, 5, 1159, 0, 0, 0, 0, + 1171, 5, 1025, 704, 844, 0, 705, 706, 707, 1163, + 704, 1097, 1097, 705, 706, 707, 0, 902, 0, 902, + 1172, 0, 0, 902, 0, 0, 0, 736, 0, 1015, + 708, 709, 710, 728, 0, 0, 0, 708, 709, 710, + 0, 1166, 0, 0, 0, 1167, 1168, 1169, 1170, 0, + 0, 5, 0, 1208, 0, 1209, 0, 1144, 608, 1212, + 704, 1148, 1214, 705, 706, 707, 1216, 608, 0, 1220, + 1191, 0, 0, 476, 0, 0, 1222, 1224, 0, 0, + 0, 5, 0, 0, 0, 0, 0, 708, 709, 710, + 704, 0, 0, 705, 706, 707, 0, 1395, 0, 0, + 1293, 1222, 735, 1171, 0, 0, 923, 736, 0, 1398, + 0, 0, 0, 0, 0, 0, 1402, 708, 709, 710, + 0, 0, 902, 1172, 0, 134, 228, 229, 0, 230, + 231, 232, 233, 234, 0, 0, 0, 728, 0, 1295, + 1297, 1298, 1299, 1300, 0, 0, 1302, 1303, 1304, 1305, + 1306, 476, 0, 1308, 0, 608, 0, 844, 0, 5, + 0, 0, 608, 0, 0, 0, 0, 0, 704, 0, + 0, 705, 706, 707, 0, 0, 0, 0, 0, 0, + 735, 728, 0, 0, 728, 736, 0, 0, 728, 0, + 0, 0, 728, 1361, 0, 708, 709, 710, 0, 0, + 1366, 0, 1369, 0, 0, 0, 0, 0, 1373, 0, + 1376, 0, 0, 0, 0, 1380, 0, 0, 1383, 0, + 0, 0, 0, 1387, 0, 0, 110, 0, 0, 0, + 0, 0, 0, 111, 112, 0, 113, 0, 0, 116, + 0, 117, 0, 118, 0, 0, 0, 119, 728, 120, + 0, 121, 0, 122, 0, 123, 728, 124, 0, 125, + 0, 126, 0, 0, 728, 1224, 0, 0, 0, 127, + 1411, 1413, 0, 0, 1416, 0, 128, 0, 129, 0, + 130, 0, 131, 0, 132, 1166, 133, 5, 0, 1167, + 1168, 1169, 1170, 0, 0, 5, 704, 0, 0, 705, + 706, 707, 0, 0, 704, 0, 0, 705, 706, 707, + 0, 0, 0, 736, 0, 747, 0, 0, 0, 0, + 0, 0, 0, 708, 709, 710, 0, 0, 197, 0, + 0, 708, 709, 710, 0, 1317, 0, 1324, 1324, 1332, + 1332, 0, 1339, 1343, 1324, 1348, 1348, 1171, 1353, 3, + 0, 0, 0, 0, 476, 0, 5, 6, 0, 0, + 0, 0, 5, 0, 0, 704, 8, 1172, 705, 706, + 707, 704, 227, 9, 705, 706, 707, 0, 0, 3, + 0, 0, 300, 735, 305, 14, 310, 6, 0, 322, + 0, 16, 708, 709, 710, 0, 8, 0, 708, 709, + 710, 0, 18, 9, 0, 20, 0, 22, 0, 0, + 0, 0, 0, 0, 0, 14, 0, 0, 25, 0, + 27, 16, 28, 0, 30, 0, 0, 0, 0, 355, + 35, 0, 18, 0, 356, 20, 358, 22, 0, 360, + 361, 0, 0, 0, 0, 0, 0, 0, 25, 0, + 27, 0, 28, 0, 30, 0, 0, 0, 0, 0, + 35, 372, 373, 0, 0, 0, 376, 0, 0, 377, + 0, 0, 0, 0, 5, 0, 381, 0, 0, 0, + 385, 0, 0, 704, 0, 388, 705, 706, 707, 0, + 391, 0, 0, 1191, 0, 394, 0, 0, 0, 0, + 397, 0, 400, 0, 0, 0, 402, 0, 1, 2, + 708, 709, 710, 404, 3, 0, 407, 0, 0, 4, + 0, 5, 6, 417, 416, 0, 0, 0, 7, 0, + 432, 8, 438, 439, 0, 0, 0, 0, 9, 10, + 0, 11, 0, 12, 0, 0, 0, 0, 13, 472, + 14, 0, 0, 15, 480, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 17, 0, 0, 18, 0, 19, + 20, 21, 22, 0, 0, 0, 0, 0, 0, 0, + 23, 24, 0, 25, 26, 27, 0, 28, 29, 30, + 31, 32, 33, 0, 34, 35, 36, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 529, 0, 530, 0, 0, 0, 0, 535, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 543, 0, 0, + 0, 0, 0, 545, 544, 0, 0, 0, 0, 0, + 0, 0, 550, 553, 0, 0, 0, 0, 0, 0, + 561, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 567, 0, 0, 0, 0, 572, 571, 0, 0, 0, + 0, 577, 578, 0, 0, 581, 0, 0, 584, 0, + 0, 0, 0, 0, 0, 593, 0, 0, 595, 0, + 0, 0, 0, 601, 0, 602, 0, 0, 0, 0, + 0, 606, 0, 0, 611, 610, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 625, 0, 0, + 0, 632, 635, 636, 637, 638, 639, 640, 641, 642, + 643, 644, 645, 646, 647, 648, 669, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 681, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 732, 0, + 0, 0, 0, 0, 744, 0, 0, 0, 750, 0, + 0, 0, 0, 0, 753, 0, 754, 0, 0, 0, + 756, 0, 0, 0, 0, 0, 760, 0, 0, 0, + 0, 0, 761, 763, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 773, 0, 0, 775, + 776, 777, 778, 0, 0, 0, 0, 0, 0, 781, + 0, 0, 0, 784, 783, 0, 0, 0, 0, 0, + 0, 0, 792, 0, 794, 0, 0, 797, 0, 799, + 0, 801, 0, 0, 0, 0, 0, 814, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 817, 0, 820, + 0, 0, 823, 0, 826, 0, 829, 0, 832, 0, + 0, 834, 0, 0, 0, 0, 0, 0, 0, 838, + 0, 0, 841, 0, 843, 0, 0, 0, 0, 846, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 865, + 0, 867, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 877, 0, 879, 0, + 0, 0, 0, 0, 0, 885, 0, 886, 0, 887, + 0, 888, 0, 0, 0, 0, 0, 135, 137, 139, + 139, 142, 0, 0, 148, 139, 151, 139, 148, 139, + 148, 139, 148, 139, 148, 161, 163, 0, 185, 185, + 185, 0, 0, 0, 0, 0, 930, 0, 0, 0, + 0, 0, 0, 0, 935, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 395, 396, 0, 399, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 244, 0, 0, 0, 0, 272, 0, 0, 0, 0, + 0, 0, 0, 0, 976, 0, 0, 0, 0, 982, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 990, 0, 0, 0, 0, 0, 0, 0, 995, + 0, 0, 999, 0, 0, 0, 0, 0, 0, 1000, + 0, 0, 1004, 1005, 1006, 1007, 1008, 1009, 0, 0, + 1010, 0, 1014, 0, 0, 1018, 0, 0, 0, 0, + 1020, 0, 1029, 1030, 1031, 0, 0, 0, 0, 0, + 0, 1035, 1267, 1230, 1231, 1232, 1233, 1234, 1235, 1236, + 1268, 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, + 1246, 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, + 1256, 1257, 1258, 1259, 1260, 1269, 1270, 1261, 1262, 1263, + 1264, 1265, 0, 559, 0, 0, 0, 0, 0, 0, + 0, 565, 0, 0, 0, 0, 568, 0, 1073, 0, + 0, 0, 0, 575, 576, 1078, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 426, 0, + 1090, 594, 1092, 440, 137, 0, 0, 0, 0, 1096, + 0, 0, 0, 0, 0, 0, 0, 0, 1107, 1108, + 0, 0, 0, 0, 481, 137, 0, 0, 0, 0, + 0, 0, 0, 491, 0, 0, 0, 0, 1124, 0, + 0, 0, 0, 1134, 0, 505, 1135, 1140, 508, 0, + 0, 0, 1143, 0, 1146, 515, 0, 1150, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1161, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1202, + 0, 1204, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1206, 0, + 1207, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1219, 0, 0, 0, + 1221, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 779, 780, 0, 0, 1281, 0, 782, 0, 0, 0, + 0, 0, 1287, 0, 1291, 1292, 0, 614, 616, 0, + 0, 796, 0, 798, 0, 800, 0, 802, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 658, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 679, 0, 0, 0, 0, + 1301, 0, 0, 0, 0, 0, 0, 1307, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1313, 0, 1315, 0, 1322, 0, 0, 0, 0, + 0, 0, 0, 0, 745, 0, 0, 0, 185, 1356, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1363, 0, 0, 0, 0, 0, 0, 1368, + 0, 0, 0, 0, 0, 0, 0, 1375, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1392, 1393, 0, 0, + 0, 1230, 1231, 1232, 1233, 1234, 1235, 1236, 0, 1237, + 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246, 1247, + 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, + 1258, 1259, 1260, 0, 1409, 1261, 1262, 1263, 1264, 1265, + 0, 1418, 944, 945, 946, 947, 0, 949, 950, 0, + 0, 0, 0, 0, 0, 0, 0, 957, 0, 959, + 960, 0, 0, 1427, 0, 0, 0, 964, 965, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 972, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 852, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 869, + 0, 869, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 904, 0, + 0, 909, 0, 0, 0, 0, 0, 0, 0, 0, + 919, 0, 0, 0, 0, 0, 679, 0, 0, 679, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1066, 0, 0, 0, 0, + 1069, 1070, 0, 1071, 0, 0, 0, 0, 0, 1072, + 0, 0, 1074, 1274, 1230, 1231, 1232, 1233, 1234, 1235, + 1236, 1275, 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, + 1245, 1246, 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, + 1255, 1256, 1257, 1258, 1259, 1260, 1276, 1277, 1261, 1262, + 1263, 1264, 1265, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1050, 0, 0, + 0, 0, 0, 0, 0, 0, 1055, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1215, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1226, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 869, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1356, 0, 0, 1357, 1358, 0, 1360, 0, 0, - 0, 1362, 1363, 0, 1365, 0, 1368, 0, 0, 1369, - 1370, 0, 1372, 0, 1375, 0, 1376, 1377, 0, 1379, - 1380, 0, 1382, 1383, 0, 1384, 0, 1386, 1387, 0, - 1388, 0, 1389, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 679, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1406, 0, 1408, 1410, 0, 1412, 1413, 1415, 0, - 1417, 1418, 1419, 1420, 0, 0, 0, 0, 0, 0, + 1311, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1181, 0, 0, 1181, 0, 0, 0, 1181, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1358, 869, + 0, 1359, 1360, 0, 1362, 0, 0, 0, 1364, 1365, + 0, 1367, 0, 1370, 0, 0, 1371, 1372, 0, 1374, + 0, 1377, 0, 1378, 1379, 0, 1381, 1382, 0, 1384, + 1385, 0, 1386, 0, 1388, 1389, 0, 1390, 0, 1391, + 0, 0, 0, 0, 869, 869, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1283, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1424, 0, 1426 + 0, 0, 0, 0, 0, 0, 0, 0, 1408, 0, + 1410, 1412, 0, 1414, 1415, 1417, 0, 1419, 1420, 1421, + 1422, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1426, 0, 1428 }; static const yytype_int16 yycheck[] = { - 57, 196, 0, 192, 346, 227, 558, 374, 115, 598, - 0, 220, 131, 132, 223, 604, 242, 434, 853, 201, - 206, 208, 434, 592, 212, 528, 204, 369, 836, 0, - 821, 997, 411, 821, 742, 697, 824, 783, 656, 235, - 838, 0, 352, 841, 893, 379, 955, 133, 243, 832, - 1133, 836, 156, 1061, 158, 1063, 1064, 694, 678, 403, - 4, 836, 1293, 4, 65, 67, 1073, 25, 1056, 14, - 4, 49, 17, 1295, 1296, 1297, 1298, 22, 1300, 136, - 1302, 138, 365, 140, 141, 19, 143, 6, 163, 90, - 92, 17, 149, 150, 28, 152, 153, 154, 155, 156, - 157, 158, 159, 160, 195, 162, 14, 164, 0, 17, - 88, 356, 56, 720, 0, 722, 60, 724, 195, 726, - 78, 728, 56, 35, 201, 51, 60, 184, 1359, 186, - 187, 188, 377, 190, 220, 1142, 80, 223, 1146, 80, - 84, 40, 1364, 84, 89, 1367, 80, 6, 7, 1371, - 84, 347, 1374, 12, 49, 7, 1378, 40, 17, 11, - 3, 350, 17, 82, 1144, 17, 821, 952, 1148, 824, - 274, 26, 827, 368, 86, 87, 9, 952, 73, 78, - 147, 89, 25, 974, 1033, 993, 974, 1409, 245, 977, - 247, 163, 1414, 88, 416, 1161, 378, 254, 19, 256, - 55, 397, 6, 260, 261, 38, 989, 28, 404, 266, - 267, 268, 269, 17, 271, 403, 273, 274, 275, 927, - 1055, 929, 418, 82, 603, 1074, 1001, 284, 1311, 539, - 201, 893, 289, 290, 1222, 292, 570, 61, 807, 198, - 1149, 298, 30, 31, 862, 302, 303, 836, 866, 752, - 307, 308, 872, 890, 598, 312, 313, 540, 878, 569, - 163, 1110, 5, 320, 6, 53, 54, 324, 9, 552, - 327, 155, 15, 157, 16, 470, 17, 334, 82, 336, - 337, 338, 339, 340, 17, 342, 1077, 513, 5, 1077, - 1078, 898, 5, 26, 1129, 902, 1094, 38, 15, 906, - 907, 908, 15, 17, 911, 912, 24, 17, 915, 65, - 202, 1220, 35, 23, 155, 71, 157, 209, 61, 974, - 1095, 39, 977, 409, 979, 1071, 412, 816, 983, 818, - 48, 163, 821, 56, 90, 824, 50, 51, 827, 17, - 82, 830, 17, 1051, 61, 62, 31, 543, 26, 163, - 386, 26, 7, 389, 1296, 697, 392, 12, 9, 77, - 1302, 64, 17, 86, 87, 1153, 17, 357, 53, 54, - 427, 428, 41, 371, 364, 53, 54, 434, 435, 54, - 437, 1142, 1291, 7, 441, 442, 443, 11, 445, 1150, - 159, 160, 574, 589, 1102, 774, 592, 583, 455, 586, - 457, 458, 580, 460, 600, 462, 463, 633, 465, 466, - 598, 468, 469, 1159, 1222, 150, 473, 474, 475, 32, - 33, 478, 1077, 1078, 1079, 1080, 483, 484, 150, 619, - 487, 488, 622, 1040, 1041, 1042, 1043, 494, 495, 851, - 497, 498, 149, 500, 501, 161, 162, 1282, 790, 759, - 765, 766, 767, 510, 511, 512, 547, 548, 1204, 1205, - 517, 57, 519, 7, 521, 882, 523, 11, 12, 13, - 547, 548, 563, 564, 17, 1183, 24, 20, 793, 22, - 67, 791, 374, 1191, 1192, 72, 563, 564, 977, 48, - 979, 610, 384, 16, 983, 17, 17, 574, 1153, 1154, - 1155, 697, 74, 75, 26, 92, 1095, 29, 30, 31, - 546, 1118, 25, 549, 36, 551, 47, 1293, 554, 555, - 32, 1297, 1298, 408, 1300, 1301, 562, 1303, 1304, 414, - 1306, 53, 54, 55, 147, 148, 149, 573, 151, 152, - 153, 154, 155, 579, 33, 17, 582, 17, 62, 585, - 1139, 893, 22, 1142, 26, 612, 155, 1146, 30, 31, - 596, 1150, 619, 1218, 17, 622, 762, 624, 45, 626, - 7, 156, 1280, 26, 11, 12, 13, 29, 31, 1287, - 17, 53, 54, 20, 7, 22, 147, 644, 1364, 12, - 157, 1080, 649, 650, 651, 1371, 653, 654, 655, 656, - 53, 54, 659, 8, 1210, 1381, 1212, 54, 804, 1385, - 17, 807, 808, 795, 21, 22, 369, 1206, 675, 676, - 677, 678, 120, 680, 122, 682, 124, 684, 126, 686, - 154, 155, 69, 1222, 1001, 346, 832, 826, 847, 695, - 829, 533, 894, 112, 891, 537, 703, 816, 117, 817, - 119, 818, 121, 974, 123, 997, 125, 527, 715, 1086, - 717, 1226, 769, 720, 556, 722, 1182, 724, 1186, 726, - 571, 728, 58, 59, 731, 53, 54, 734, 1063, 786, - 1146, 156, 739, 740, 1298, 742, 161, 162, 163, 746, - 1301, 1033, 61, 62, 751, 7, 8, 893, 894, 11, - 12, 13, 14, 7, 1304, 17, 598, 11, 12, 13, - 14, 0, 604, 17, 26, 201, 608, 29, 30, 31, - 1303, 1304, 26, 1012, 1013, 29, 30, 31, 1095, 536, - 7, 624, 1074, 843, 11, 12, 13, 151, 152, 153, - 17, 53, 54, 55, 21, 22, 925, 926, 7, 53, - 54, 55, 11, 12, 13, 739, 740, 69, 17, 954, - 955, 1136, 1137, 22, 129, 69, 129, 129, 1110, 129, - 7, 129, 968, 995, 11, 12, 13, 89, 768, 199, - 17, 771, 7, 199, 958, 89, 11, 12, 13, 14, - 478, 129, 69, 989, 851, 785, 853, 986, 788, 988, - 129, 35, 570, 1214, 129, 862, 129, 1033, 129, 866, - 69, 868, 398, 870, 871, 872, 129, 815, 875, 1161, - 195, 878, 56, 195, 881, 882, 60, 597, 129, 147, - 148, 597, 69, 151, 152, 153, 70, 1033, 597, 597, - 731, 898, 0, 900, 69, 902, 129, 129, 129, 906, - 907, 908, 86, 87, 911, 912, 129, 914, 915, 1411, - 917, 148, 149, 920, 151, 152, 153, 129, 925, 926, - 927, 361, 929, 334, 931, 932, 148, 769, 1074, 151, - 152, 153, 372, 373, 941, 942, 337, 0, 1390, 10, - 339, 381, 0, 942, 786, 1095, 1085, 18, 388, -1, - -1, 391, -1, -1, 394, -1, 27, -1, -1, -1, - -1, -1, 402, 34, 1110, 148, 37, -1, 151, 152, - 153, 154, 155, 156, 816, 158, 818, -1, -1, 821, - -1, 52, 824, -1, -1, 827, -1, -1, 830, -1, - -1, -1, 63, 1000, 836, 66, -1, 68, 1005, 1006, - 1007, 1008, 844, -1, 1149, 1012, 1013, -1, 79, -1, - 81, -1, 83, -1, 85, -1, -1, -1, 1157, -1, - 91, -1, 1029, -1, -1, -1, -1, -1, -1, 1036, - 1037, 1038, 8, 1040, 1041, 1042, 1043, -1, 1045, 9, - 1047, 17, -1, -1, 1051, -1, 1053, 17, 1055, -1, - 26, -1, 1059, 29, 30, 31, 26, -1, -1, 29, - 30, 31, -1, -1, -1, -1, -1, -1, 38, -1, - -1, -1, 42, 43, -1, 1220, -1, 53, 54, 55, - -1, -1, -1, 53, 54, 55, -1, -1, -1, -1, - -1, 1098, -1, 1100, -1, 1102, 1103, -1, -1, -1, - 1107, 1108, -1, -1, -1, 1112, 1113, -1, 1115, -1, - 952, 1118, -1, 1120, -1, -1, -1, -1, -1, 17, - -1, -1, 1129, -1, -1, -1, -1, 1134, 26, 1136, - 1137, 29, 30, 31, -1, 977, -1, 979, -1, -1, - -1, 983, -1, 7, -1, 43, 1291, 11, 12, 13, - 14, 993, -1, 17, -1, 53, 54, 55, -1, -1, - -1, -1, 26, -1, -1, 29, 30, 31, -1, -1, - -1, 1178, 36, 1180, -1, 1182, 1183, 1184, -1, 1186, - 1187, -1, -1, -1, 1191, 1192, -1, 1194, -1, 53, - 54, 55, -1, -1, 1201, 1202, -1, -1, -1, -1, - 9, -1, -1, -1, -1, 69, 148, 149, 17, 151, - 152, 153, 154, 155, -1, 1361, 158, 26, 1225, 1226, - 29, 30, 31, -1, -1, 89, -1, 1366, -1, 38, - -1, -1, -1, -1, 1373, 147, 148, 149, 1080, 151, - 152, 153, 154, 155, 53, 54, 55, -1, -1, -1, - -1, -1, -1, 1095, -1, -1, -1, 1264, 1265, 1266, - 1267, 1268, -1, -1, 1271, 1272, 1273, 1274, 1275, 9, - -1, 1278, -1, 1280, -1, 1282, -1, 17, 148, -1, - 1287, 151, 152, 153, 154, 155, 26, -1, 158, 29, - 30, 31, -1, -1, -1, -1, -1, 1139, 38, -1, - 1142, -1, -1, 43, 1146, -1, -1, -1, 1150, -1, - -1, 1318, -1, 53, 54, 55, -1, 45, 1325, -1, - 1327, -1, -1, -1, 52, 53, 1333, 55, 1335, -1, - 58, -1, 60, 1340, 62, -1, 1343, -1, 66, -1, - 68, 1348, 70, -1, 72, -1, 74, -1, 76, -1, - 78, -1, 80, -1, -1, -1, -1, -1, -1, -1, - 88, -1, -1, -1, 1206, -1, -1, 95, -1, 97, - -1, 99, 1214, 101, -1, 103, -1, 105, -1, -1, - 1222, 362, 363, 1390, -1, 366, 367, -1, 1395, 1396, - 148, 149, 1399, 151, 152, 153, 154, 155, -1, 380, - -1, -1, -1, -1, -1, -1, 387, -1, -1, 390, - -1, -1, 393, -1, -1, -1, -1, -1, -1, 147, - 401, -1, 109, 110, 111, 112, 113, -1, -1, 116, - 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, - 127, 128, -1, 130, 131, 132, -1, -1, -1, -1, - -1, 1293, -1, 1295, 1296, 1297, 1298, -1, 1300, 1301, - 1302, 1303, 1304, 191, 1306, -1, 10, -1, -1, -1, - -1, -1, -1, 203, 18, 205, -1, 207, -1, 17, - 210, -1, -1, 27, -1, -1, -1, -1, 26, -1, - 34, 29, 30, 31, -1, -1, -1, -1, -1, -1, - -1, -1, 46, -1, -1, 192, -1, -1, 52, -1, - 197, -1, -1, -1, -1, 53, 54, 55, -1, 63, - 248, -1, 66, -1, 68, 253, -1, 255, -1, -1, - 258, 259, -1, -1, -1, 79, 17, 81, -1, 83, - -1, 85, -1, -1, -1, 26, -1, 91, 29, 30, - 31, -1, 280, 281, -1, -1, -1, 285, -1, -1, - 288, -1, 43, -1, 45, -1, -1, 295, -1, -1, - -1, 299, 53, 54, 55, -1, 304, -1, -1, -1, - -1, 309, -1, -1, -1, -1, 314, -1, -1, -1, - -1, 319, -1, 321, -1, -1, -1, 325, -1, -1, - -1, -1, -1, -1, 332, -1, -1, 335, -1, -1, - -1, -1, -1, -1, 344, 343, -1, -1, -1, -1, - -1, 351, -1, 353, 354, 93, 94, 95, 96, 97, - 98, 99, 100, 101, 102, 103, 104, 105, 106, -1, - 370, -1, -1, -1, -1, 375, 93, 94, 95, 96, - 97, 98, 99, 100, 101, -1, -1, -1, -1, -1, - -1, -1, -1, 350, -1, -1, -1, -1, 355, 356, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, 376, - 377, -1, -1, -1, -1, -1, -1, -1, 385, -1, - -1, 429, -1, 431, -1, -1, -1, -1, 436, -1, - 397, -1, -1, 400, -1, -1, -1, -1, 446, -1, - 407, -1, -1, -1, 454, 453, -1, -1, -1, 10, - -1, -1, -1, 461, 464, -1, 17, 18, -1, -1, - -1, 471, -1, -1, -1, 26, 27, -1, 29, 30, - 31, 479, -1, 34, -1, -1, 486, 485, -1, -1, - -1, -1, 492, 493, -1, 46, 496, -1, -1, 499, - -1, 52, 53, 54, 55, -1, 506, -1, -1, 509, - -1, -1, 63, -1, 514, 66, 516, 68, -1, -1, - -1, -1, 520, -1, -1, 525, 524, -1, 79, -1, - 81, -1, 83, -1, 85, -1, -1, -1, 538, -1, - 91, -1, 542, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, 557, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, 568, -1, - -1, -1, 529, 530, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, 550, -1, -1, -1, -1, -1, 599, - -1, -1, -1, -1, -1, 605, -1, -1, -1, 609, - 567, -1, -1, -1, -1, 615, -1, 617, -1, -1, - -1, 621, -1, -1, -1, -1, -1, 627, -1, -1, - -1, -1, -1, 631, 634, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, 606, - -1, -1, -1, 610, -1, -1, -1, 657, -1, -1, - 660, 661, 662, 663, -1, -1, -1, -1, -1, -1, - 668, -1, -1, -1, 674, 673, -1, -1, -1, -1, - -1, -1, -1, 683, -1, 685, -1, -1, 688, -1, - 690, -1, 692, -1, -1, -1, -1, -1, 698, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, 716, -1, - 718, -1, -1, 721, -1, 723, -1, 725, -1, 727, - -1, -1, 730, -1, -1, -1, -1, -1, -1, -1, - 738, -1, -1, 741, -1, 743, -1, -1, -1, -1, - 748, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 770, -1, 772, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, 787, -1, 789, - -1, -1, -1, -1, -1, -1, 796, -1, 798, -1, - 800, -1, 802, -1, 761, -1, 317, 318, -1, 320, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, 781, -1, 783, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, 837, -1, -1, - -1, -1, -1, -1, -1, 845, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, 820, -1, -1, 823, -1, -1, -1, - -1, -1, -1, -1, -1, 832, -1, -1, -1, -1, - -1, 838, -1, -1, 841, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, 905, -1, -1, -1, -1, - 910, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, 922, -1, -1, -1, -1, -1, -1, -1, - 928, -1, -1, 933, -1, -1, -1, -1, -1, -1, - 940, -1, -1, 943, 944, 945, 946, 947, 948, -1, - -1, 951, -1, 953, -1, -1, 956, -1, 469, -1, - -1, 961, -1, 963, 964, 965, 477, -1, -1, -1, - -1, 482, 972, -1, -1, -1, -1, -1, 489, 490, - -1, -1, -1, -1, -1, -1, -1, -1, -1, 108, - 109, 110, 111, 112, 113, 114, 507, 116, 117, 118, - 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, - 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, - 139, -1, -1, 142, 143, 144, 145, 146, 1026, -1, - -1, -1, 989, -1, -1, 1035, -1, -1, -1, -1, - -1, 998, -1, -1, -1, -1, -1, -1, -1, -1, - 1050, -1, 1052, -1, -1, -1, -1, -1, -1, 1057, - -1, -1, -1, -1, -1, -1, -1, -1, 1068, 1069, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, 1087, -1, -1, - -1, -1, 1092, -1, -1, 1093, 1096, -1, -1, -1, - -1, 1099, -1, 1101, -1, -1, 1104, -1, -1, -1, - -1, -1, -1, -1, 1071, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, 1131, -1, -1, -1, 1094, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, 666, 667, -1, 1158, -1, - 1160, 672, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, 687, 1175, 689, 1177, - 691, -1, 693, -1, 1141, -1, -1, 1144, -1, -1, - -1, 1148, -1, -1, -1, 1193, -1, -1, -1, 1199, - -1, -1, 1159, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, 1213, -1, -1, -1, -1, -1, -1, - -1, 1221, -1, 1223, 1224, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, 1204, 1205, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 1217, -1, -1, -1, -1, -1, -1, -1, -1, 1269, - -1, -1, -1, -1, -1, -1, 1276, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 1288, -1, 1292, -1, 1294, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, 1308, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 1319, -1, -1, -1, -1, -1, -1, 1326, -1, - -1, -1, -1, -1, -1, -1, 1334, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, 858, 859, 860, - 861, -1, 863, 864, -1, 1355, 1356, 4, 5, -1, - -1, -1, 873, 10, 875, 876, -1, -1, 15, -1, - 17, 18, 883, 884, -1, -1, -1, 24, -1, -1, - 27, -1, -1, -1, -1, 896, -1, 34, 35, -1, - 37, -1, 39, 1393, -1, -1, -1, 44, -1, 46, - 1400, -1, 49, -1, -1, 52, -1, -1, -1, -1, - -1, -1, -1, 60, -1, -1, 63, -1, 65, 66, - 67, 68, 1422, -1, -1, -1, -1, -1, -1, 76, - 77, -1, 79, 80, 81, -1, 83, 84, 85, 86, - 87, 88, -1, 90, 91, 92, 107, 108, 109, 110, - 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, - 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, - 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, - 141, 142, 143, 144, 145, 146, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 1011, -1, -1, -1, -1, 1016, 1017, -1, 1019, -1, - -1, -1, -1, -1, -1, -1, 1027, 107, 108, 109, - 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, - 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, - 140, 141, 142, 143, 144, 145, 146, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 57, 558, 196, 192, 0, 346, 227, 374, 115, 201, + 0, 220, 131, 132, 223, 434, 242, 853, 434, 204, + 206, 208, 836, 528, 133, 955, 352, 411, 369, 592, + 821, 598, 656, 212, 678, 783, 0, 604, 821, 235, + 836, 824, 0, 697, 838, 379, 893, 841, 1135, 243, + 832, 156, 694, 158, 403, 836, 1057, 1075, 1062, 997, + 1064, 1065, 0, 4, 6, 7, 1297, 1298, 1299, 1300, + 12, 1302, 25, 1304, 49, 17, 163, 742, 19, 136, + 5, 138, 4, 140, 141, 365, 143, 28, 14, 6, + 15, 17, 149, 150, 1295, 152, 153, 154, 155, 156, + 157, 158, 159, 160, 195, 162, 6, 164, 6, 40, + 0, 220, 9, 88, 223, 56, 4, 17, 16, 60, + 17, 17, 5, 147, 35, 78, 1144, 184, 195, 186, + 187, 188, 15, 190, 201, 1366, 61, 62, 1369, 80, + 82, 38, 1373, 84, 1148, 1376, 720, 78, 722, 1380, + 724, 347, 726, 40, 728, 51, 952, 19, 80, 3, + 1361, 350, 84, 89, 821, 82, 28, 824, 56, 274, + 827, 952, 60, 65, 368, 86, 87, 9, 61, 993, + 1411, 25, 82, 974, 82, 1416, 378, 1034, 245, 163, + 247, 974, 80, 17, 977, 416, 84, 254, 90, 256, + 163, 397, 26, 260, 261, 1001, 38, 989, 404, 266, + 267, 268, 269, 539, 271, 163, 273, 274, 275, 603, + 1056, 1151, 418, 1224, 403, 1163, 1313, 284, 872, 1076, + 31, 55, 289, 290, 878, 292, 570, 201, 862, 893, + 198, 298, 866, 569, 807, 302, 303, 752, 890, 598, + 307, 308, 53, 54, 356, 312, 313, 159, 160, 67, + 540, 5, 927, 320, 929, 1112, 49, 324, 65, 836, + 327, 15, 552, 61, 71, 377, 470, 334, 35, 336, + 337, 338, 339, 340, 92, 342, 14, 513, 1079, 17, + 73, 24, 1222, 90, 22, 1131, 1079, 1080, 17, 56, + 409, 1097, 1096, 412, 7, 88, 39, 26, 11, 12, + 13, 14, 202, 7, 155, 48, 157, 974, 12, 209, + 977, 17, 979, 17, 898, 1073, 983, 23, 902, 86, + 87, 17, 906, 907, 908, 54, 7, 911, 912, 64, + 11, 915, 163, 816, 77, 818, 17, 543, 821, 386, + 17, 824, 389, 17, 827, 392, 697, 830, 22, 26, + 155, 89, 157, 1293, 50, 51, 69, 357, 30, 31, + 427, 428, 1155, 9, 364, 371, 41, 434, 435, 7, + 437, 17, 574, 11, 441, 442, 443, 1052, 445, 67, + 774, 53, 54, 589, 72, 580, 592, 583, 455, 586, + 457, 458, 150, 460, 600, 462, 463, 633, 465, 466, + 1224, 468, 469, 1161, 92, 150, 473, 474, 475, 598, + 1144, 478, 1079, 1080, 1081, 1082, 483, 484, 1152, 17, + 487, 488, 20, 759, 22, 851, 149, 494, 495, 1104, + 497, 498, 17, 500, 501, 7, 1298, 57, 1284, 790, + 12, 26, 1304, 510, 511, 512, 547, 548, 1206, 1207, + 517, 48, 519, 882, 521, 791, 523, 1041, 1042, 1043, + 1044, 619, 563, 564, 622, 765, 766, 767, 53, 54, + 547, 548, 7, 1212, 374, 1214, 11, 12, 13, 408, + 24, 610, 17, 16, 384, 414, 563, 564, 1155, 1156, + 1157, 697, 35, 793, 977, 17, 979, 574, 47, 546, + 983, 17, 549, 32, 551, 21, 22, 554, 555, 33, + 1185, 161, 162, 56, 25, 562, 156, 60, 1193, 1194, + 1097, 161, 162, 163, 147, 148, 573, 70, 151, 152, + 153, 62, 579, 45, 69, 582, 1120, 17, 585, 151, + 152, 153, 893, 86, 87, 612, 26, 74, 75, 596, + 30, 31, 619, 1220, 17, 622, 762, 624, 1146, 626, + 154, 155, 1150, 26, 1141, 58, 59, 1144, 31, 61, + 62, 1148, 155, 53, 54, 1152, 156, 644, 53, 54, + 32, 33, 649, 650, 651, 29, 653, 654, 655, 656, + 53, 54, 659, 795, 1305, 1306, 1012, 1013, 804, 1082, + 148, 807, 808, 151, 152, 153, 147, 1282, 675, 676, + 677, 678, 157, 680, 1289, 682, 120, 684, 122, 686, + 124, 8, 126, 54, 1001, 369, 832, 826, 847, 346, + 829, 1208, 695, 533, 894, 148, 703, 537, 151, 152, + 153, 154, 155, 156, 891, 158, 997, 1224, 715, 816, + 717, 817, 769, 720, 818, 722, 556, 724, 974, 726, + 527, 728, 925, 926, 731, 739, 740, 734, 1088, 786, + 1138, 1139, 739, 740, 1228, 742, 7, 1184, 1188, 746, + 11, 12, 13, 1034, 751, 571, 17, 893, 894, 20, + 1064, 22, 1148, 1300, 1306, 147, 148, 149, 598, 151, + 152, 153, 154, 155, 604, 1303, 0, 7, 608, 8, + 201, 11, 12, 13, 536, 624, 843, 17, 17, 129, + 1097, 21, 22, 129, 129, 1076, 129, 26, 129, 1295, + 29, 30, 31, 1299, 1300, 199, 1302, 1303, 69, 1305, + 1306, 199, 1308, 7, 958, 478, 129, 11, 12, 13, + 954, 955, 129, 17, 53, 54, 55, 570, 1216, 129, + 129, 1112, 968, 129, 995, 112, 398, 129, 768, 69, + 117, 771, 119, 195, 121, 195, 123, 597, 125, 129, + 597, 597, 597, 989, 851, 785, 853, 986, 788, 988, + 129, 129, 129, 129, 129, 862, 0, 731, 1034, 866, + 1366, 868, 334, 870, 871, 872, 0, 1373, 875, 815, + 10, 878, 1163, 339, 881, 882, 337, 1383, 18, 148, + 149, 1387, 151, 152, 153, 154, 155, 27, 1034, 158, + 1392, 898, 0, 900, 34, 902, 942, 37, 1097, 906, + 907, 908, -1, -1, 911, 912, 1413, 914, 915, -1, + 917, -1, 52, 920, -1, -1, -1, -1, 925, 926, + 927, -1, 929, 63, 931, 932, 66, 7, 68, 769, + 1076, 11, 12, 13, 941, 942, -1, 17, 361, 79, + -1, 81, 22, 83, -1, 85, 786, -1, 1087, 372, + 373, 91, 362, 363, -1, -1, 366, 367, 381, 148, + 149, -1, 151, 152, 153, 388, 1112, -1, 391, -1, + 380, 394, -1, -1, -1, -1, 816, 387, 818, 402, + 390, 821, -1, 393, 824, -1, -1, 827, -1, 69, + 830, 401, -1, 1000, -1, -1, 836, -1, 1005, 1006, + 1007, 1008, -1, -1, 844, 1012, 1013, 1151, -1, 148, + -1, -1, 151, 152, 153, 154, 155, -1, -1, 158, + 1159, 148, 149, 1030, 151, 152, 153, 154, 155, -1, + 1037, 1038, 1039, -1, 1041, 1042, 1043, 1044, -1, 1046, + -1, 1048, -1, -1, -1, 1052, -1, 1054, -1, 1056, + -1, -1, -1, 1060, -1, -1, -1, -1, 7, 8, + -1, -1, 11, 12, 13, 14, -1, -1, 17, -1, + -1, -1, -1, -1, -1, -1, -1, 26, 1222, -1, + 29, 30, 31, 93, 94, 95, 96, 97, 98, 99, + 100, 101, -1, 1100, -1, 1102, -1, 1104, 1105, -1, + -1, -1, 1109, 1110, 53, 54, 55, 1114, 1115, -1, + 1117, -1, 952, 1120, 17, 1122, -1, -1, -1, -1, + 69, 17, 962, 26, 1131, -1, 29, 30, 31, 1136, + 26, 1138, 1139, 29, 30, 31, -1, 977, -1, 979, + 89, -1, -1, 983, -1, -1, -1, 43, -1, 1293, + 53, 54, 55, 993, -1, -1, -1, 53, 54, 55, + -1, 7, -1, -1, -1, 11, 12, 13, 14, -1, + -1, 17, -1, 1180, -1, 1182, -1, 1184, 1185, 1186, + 26, 1188, 1189, 29, 30, 31, 1193, 1194, -1, 1196, + 36, -1, -1, 9, -1, -1, 1203, 1204, -1, -1, + -1, 17, -1, -1, -1, -1, -1, 53, 54, 55, + 26, -1, -1, 29, 30, 31, -1, 1363, -1, -1, + 1227, 1228, 38, 69, -1, -1, 42, 43, -1, 1368, + -1, -1, -1, -1, -1, -1, 1375, 53, 54, 55, + -1, -1, 1082, 89, -1, 147, 148, 149, -1, 151, + 152, 153, 154, 155, -1, -1, -1, 1097, -1, 1266, + 1267, 1268, 1269, 1270, -1, -1, 1273, 1274, 1275, 1276, + 1277, 9, -1, 1280, -1, 1282, -1, 1284, -1, 17, + -1, -1, 1289, -1, -1, -1, -1, -1, 26, -1, + -1, 29, 30, 31, -1, -1, -1, -1, -1, -1, + 38, 1141, -1, -1, 1144, 43, -1, -1, 1148, -1, + -1, -1, 1152, 1320, -1, 53, 54, 55, -1, -1, + 1327, -1, 1329, -1, -1, -1, -1, -1, 1335, -1, + 1337, -1, -1, -1, -1, 1342, -1, -1, 1345, -1, + -1, -1, -1, 1350, -1, -1, 45, -1, -1, -1, + -1, -1, -1, 52, 53, -1, 55, -1, -1, 58, + -1, 60, -1, 62, -1, -1, -1, 66, 1208, 68, + -1, 70, -1, 72, -1, 74, 1216, 76, -1, 78, + -1, 80, -1, -1, 1224, 1392, -1, -1, -1, 88, + 1397, 1398, -1, -1, 1401, -1, 95, -1, 97, -1, + 99, -1, 101, -1, 103, 7, 105, 17, -1, 11, + 12, 13, 14, -1, -1, 17, 26, -1, -1, 29, + 30, 31, -1, -1, 26, -1, -1, 29, 30, 31, + -1, -1, -1, 43, -1, 45, -1, -1, -1, -1, + -1, -1, -1, 53, 54, 55, -1, -1, 147, -1, + -1, 53, 54, 55, -1, 1295, -1, 1297, 1298, 1299, + 1300, -1, 1302, 1303, 1304, 1305, 1306, 69, 1308, 10, + -1, -1, -1, -1, 9, -1, 17, 18, -1, -1, + -1, -1, 17, -1, -1, 26, 27, 89, 29, 30, + 31, 26, 191, 34, 29, 30, 31, -1, -1, 10, + -1, -1, 203, 38, 205, 46, 207, 18, -1, 210, + -1, 52, 53, 54, 55, -1, 27, -1, 53, 54, + 55, -1, 63, 34, -1, 66, -1, 68, -1, -1, + -1, -1, -1, -1, -1, 46, -1, -1, 79, -1, + 81, 52, 83, -1, 85, -1, -1, -1, -1, 248, + 91, -1, 63, -1, 253, 66, 255, 68, -1, 258, + 259, -1, -1, -1, -1, -1, -1, -1, 79, -1, + 81, -1, 83, -1, 85, -1, -1, -1, -1, -1, + 91, 280, 281, -1, -1, -1, 285, -1, -1, 288, + -1, -1, -1, -1, 17, -1, 295, -1, -1, -1, + 299, -1, -1, 26, -1, 304, 29, 30, 31, -1, + 309, -1, -1, 36, -1, 314, -1, -1, -1, -1, + 319, -1, 321, -1, -1, -1, 325, -1, 4, 5, + 53, 54, 55, 332, 10, -1, 335, -1, -1, 15, + -1, 17, 18, 344, 343, -1, -1, -1, 24, -1, + 351, 27, 353, 354, -1, -1, -1, -1, 34, 35, + -1, 37, -1, 39, -1, -1, -1, -1, 44, 370, + 46, -1, -1, 49, 375, -1, 52, -1, -1, -1, + -1, -1, -1, -1, 60, -1, -1, 63, -1, 65, + 66, 67, 68, -1, -1, -1, -1, -1, -1, -1, + 76, 77, -1, 79, 80, 81, -1, 83, 84, 85, + 86, 87, 88, -1, 90, 91, 92, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 429, -1, 431, -1, -1, -1, -1, 436, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 446, -1, -1, + -1, -1, -1, 454, 453, -1, -1, -1, -1, -1, + -1, -1, 461, 464, -1, -1, -1, -1, -1, -1, + 471, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 479, -1, -1, -1, -1, 486, 485, -1, -1, -1, + -1, 492, 493, -1, -1, 496, -1, -1, 499, -1, + -1, -1, -1, -1, -1, 506, -1, -1, 509, -1, + -1, -1, -1, 514, -1, 516, -1, -1, -1, -1, + -1, 520, -1, -1, 525, 524, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 538, -1, -1, + -1, 542, 93, 94, 95, 96, 97, 98, 99, 100, + 101, 102, 103, 104, 105, 106, 557, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 568, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 599, -1, + -1, -1, -1, -1, 605, -1, -1, -1, 609, -1, + -1, -1, -1, -1, 615, -1, 617, -1, -1, -1, + 621, -1, -1, -1, -1, -1, 627, -1, -1, -1, + -1, -1, 631, 634, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 657, -1, -1, 660, + 661, 662, 663, -1, -1, -1, -1, -1, -1, 668, + -1, -1, -1, 674, 673, -1, -1, -1, -1, -1, + -1, -1, 683, -1, 685, -1, -1, 688, -1, 690, + -1, 692, -1, -1, -1, -1, -1, 698, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 716, -1, 718, + -1, -1, 721, -1, 723, -1, 725, -1, 727, -1, + -1, 730, -1, -1, -1, -1, -1, -1, -1, 738, + -1, -1, 741, -1, 743, -1, -1, -1, -1, 748, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 770, + -1, 772, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 787, -1, 789, -1, + -1, -1, -1, -1, -1, 796, -1, 798, -1, 800, + -1, 802, -1, -1, -1, -1, -1, 109, 110, 111, + 112, 113, -1, -1, 116, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 127, 128, -1, 130, 131, + 132, -1, -1, -1, -1, -1, 837, -1, -1, -1, + -1, -1, -1, -1, 845, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 317, 318, -1, 320, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 192, -1, -1, -1, -1, 197, -1, -1, -1, -1, + -1, -1, -1, -1, 905, -1, -1, -1, -1, 910, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 922, -1, -1, -1, -1, -1, -1, -1, 928, + -1, -1, 933, -1, -1, -1, -1, -1, -1, 940, + -1, -1, 943, 944, 945, 946, 947, 948, -1, -1, + 951, -1, 953, -1, -1, 956, -1, -1, -1, -1, + 961, -1, 963, 964, 965, -1, -1, -1, -1, -1, + -1, 972, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, + 145, 146, -1, 469, -1, -1, -1, -1, -1, -1, + -1, 477, -1, -1, -1, -1, 482, -1, 1027, -1, + -1, -1, -1, 489, 490, 1036, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 350, -1, + 1051, 507, 1053, 355, 356, -1, -1, -1, -1, 1058, + -1, -1, -1, -1, -1, -1, -1, -1, 1069, 1070, + -1, -1, -1, -1, 376, 377, -1, -1, -1, -1, + -1, -1, -1, 385, -1, -1, -1, -1, 1089, -1, + -1, -1, -1, 1094, -1, 397, 1095, 1098, 400, -1, + -1, -1, 1101, -1, 1103, 407, -1, 1106, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 1133, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 1160, + -1, 1162, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 1177, -1, + 1179, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 1195, -1, -1, -1, + 1201, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 666, 667, -1, -1, 1215, -1, 672, -1, -1, -1, + -1, -1, 1223, -1, 1225, 1226, -1, 529, 530, -1, + -1, 687, -1, 689, -1, 691, -1, 693, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 550, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 567, -1, -1, -1, -1, + 1271, -1, -1, -1, -1, -1, -1, 1278, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 1290, -1, 1294, -1, 1296, -1, -1, -1, -1, + -1, -1, -1, -1, 606, -1, -1, -1, 610, 1310, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 1321, -1, -1, -1, -1, -1, -1, 1328, + -1, -1, -1, -1, -1, -1, -1, 1336, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 1357, 1358, -1, -1, + -1, 108, 109, 110, 111, 112, 113, 114, -1, 116, + 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, + 137, 138, 139, -1, 1395, 142, 143, 144, 145, 146, + -1, 1402, 858, 859, 860, 861, -1, 863, 864, -1, + -1, -1, -1, -1, -1, -1, -1, 873, -1, 875, + 876, -1, -1, 1424, -1, -1, -1, 883, 884, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 896, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 761, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 781, + -1, 783, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 820, -1, + -1, 823, -1, -1, -1, -1, -1, -1, -1, -1, + 832, -1, -1, -1, -1, -1, 838, -1, -1, 841, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 1011, -1, -1, -1, -1, + 1016, 1017, -1, 1019, -1, -1, -1, -1, -1, 1025, + -1, -1, 1028, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, + 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, + 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, 1188, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, 1203, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 989, -1, -1, + -1, -1, -1, -1, -1, -1, 998, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 1190, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 1205, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, 1284, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 1073, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 1312, -1, -1, 1315, 1316, -1, 1318, -1, -1, - -1, 1322, 1323, -1, 1325, -1, 1327, -1, -1, 1330, - 1331, -1, 1333, -1, 1335, -1, 1337, 1338, -1, 1340, - 1341, -1, 1343, 1344, -1, 1346, -1, 1348, 1349, -1, - 1351, -1, 1353, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 1096, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 1392, -1, 1394, 1395, -1, 1397, 1398, 1399, -1, - 1401, 1402, 1403, 1404, -1, -1, -1, -1, -1, -1, + 1286, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 1143, -1, -1, 1146, -1, -1, -1, 1150, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 1314, 1161, + -1, 1317, 1318, -1, 1320, -1, -1, -1, 1324, 1325, + -1, 1327, -1, 1329, -1, -1, 1332, 1333, -1, 1335, + -1, 1337, -1, 1339, 1340, -1, 1342, 1343, -1, 1345, + 1346, -1, 1348, -1, 1350, 1351, -1, 1353, -1, 1355, + -1, -1, -1, -1, 1206, 1207, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 1219, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 1421, -1, 1423 + -1, -1, -1, -1, -1, -1, -1, -1, 1394, -1, + 1396, 1397, -1, 1399, 1400, 1401, -1, 1403, 1404, 1405, + 1406, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 1423, -1, 1425 }; /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing @@ -2064,47 +2080,47 @@ 203, 482, 482, 204, 207, 202, 207, 204, 204, 203, 203, 204, 204, 512, 203, 203, 203, 203, 203, 203, 203, 238, 425, 429, 203, 172, 267, 267, 203, 373, - 203, 7, 11, 12, 13, 256, 257, 386, 203, 203, - 203, 180, 195, 196, 203, 218, 220, 223, 229, 234, - 229, 234, 234, 234, 169, 226, 169, 233, 184, 205, - 237, 494, 167, 385, 205, 431, 204, 383, 429, 513, - 516, 204, 204, 204, 204, 259, 419, 419, 259, 259, - 259, 202, 259, 204, 168, 198, 203, 204, 204, 204, - 204, 209, 209, 209, 209, 204, 204, 230, 203, 207, - 203, 204, 330, 507, 202, 204, 514, 8, 282, 284, - 281, 284, 282, 283, 284, 203, 203, 266, 281, 180, - 197, 198, 223, 229, 234, 229, 234, 234, 234, 169, - 227, 260, 203, 7, 11, 12, 13, 14, 69, 426, - 427, 428, 203, 202, 384, 208, 429, 516, 203, 204, - 272, 202, 204, 270, 202, 207, 204, 275, 202, 204, - 204, 392, 198, 204, 204, 204, 209, 204, 330, 202, - 504, 204, 514, 514, 7, 11, 12, 13, 14, 69, - 89, 208, 252, 253, 254, 255, 261, 265, 305, 205, - 285, 208, 281, 305, 285, 208, 283, 285, 267, 36, - 208, 305, 393, 394, 229, 234, 234, 234, 169, 228, - 203, 266, 203, 385, 202, 202, 204, 204, 270, 207, - 204, 275, 204, 259, 204, 207, 207, 202, 204, 203, - 204, 262, 204, 505, 259, 266, 266, 208, 108, 109, - 110, 111, 112, 113, 114, 116, 117, 118, 119, 120, - 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, - 131, 132, 133, 134, 135, 136, 137, 138, 139, 142, - 143, 144, 145, 146, 298, 107, 115, 140, 141, 286, - 289, 298, 107, 115, 140, 141, 291, 294, 298, 203, - 393, 205, 395, 234, 267, 203, 208, 494, 507, 203, - 203, 204, 262, 204, 296, 204, 204, 204, 204, 203, - 204, 204, 204, 204, 204, 203, 204, 207, 330, 259, - 207, 202, 267, 203, 22, 238, 261, 297, 303, 304, - 203, 20, 238, 253, 287, 299, 300, 303, 287, 21, - 238, 253, 288, 301, 302, 303, 288, 238, 253, 290, - 303, 238, 292, 299, 303, 287, 238, 293, 301, 303, - 293, 238, 295, 303, 203, 504, 259, 259, 259, 204, - 259, 202, 259, 259, 204, 259, 202, 204, 259, 259, - 259, 204, 259, 202, 204, 259, 259, 259, 204, 259, - 259, 204, 259, 259, 259, 204, 259, 259, 259, 259, - 203, 203, 261, 180, 253, 303, 169, 253, 253, 303, - 169, 253, 253, 303, 303, 505, 259, 203, 259, 204, - 259, 204, 259, 259, 204, 259, 203, 259, 259, 259, - 259, 253, 258, 253, 259, 203, 259 + 203, 7, 11, 12, 13, 238, 256, 257, 386, 203, + 203, 203, 180, 195, 196, 203, 218, 220, 223, 229, + 234, 229, 234, 234, 234, 169, 226, 169, 233, 184, + 205, 237, 494, 167, 385, 205, 431, 204, 383, 429, + 513, 516, 204, 204, 204, 204, 259, 419, 419, 259, + 259, 259, 259, 202, 259, 204, 168, 198, 203, 204, + 204, 204, 204, 209, 209, 209, 209, 204, 204, 230, + 203, 207, 203, 204, 330, 507, 202, 204, 514, 8, + 282, 284, 281, 284, 282, 283, 284, 203, 203, 266, + 281, 180, 197, 198, 223, 229, 234, 229, 234, 234, + 234, 169, 227, 260, 203, 7, 11, 12, 13, 14, + 69, 426, 427, 428, 203, 202, 384, 208, 429, 516, + 203, 204, 272, 202, 204, 270, 202, 207, 204, 275, + 202, 204, 204, 392, 198, 204, 204, 204, 209, 204, + 330, 202, 504, 204, 514, 514, 7, 11, 12, 13, + 14, 69, 89, 208, 252, 253, 254, 255, 261, 265, + 305, 205, 285, 208, 281, 305, 285, 208, 283, 285, + 267, 36, 208, 305, 393, 394, 229, 234, 234, 234, + 169, 228, 203, 266, 203, 385, 202, 202, 204, 204, + 270, 207, 204, 275, 204, 259, 204, 207, 207, 202, + 204, 203, 204, 262, 204, 505, 259, 266, 266, 208, + 108, 109, 110, 111, 112, 113, 114, 116, 117, 118, + 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, + 139, 142, 143, 144, 145, 146, 298, 107, 115, 140, + 141, 286, 289, 298, 107, 115, 140, 141, 291, 294, + 298, 203, 393, 205, 395, 234, 267, 203, 208, 494, + 507, 203, 203, 204, 262, 204, 296, 204, 204, 204, + 204, 203, 204, 204, 204, 204, 204, 203, 204, 207, + 330, 259, 207, 202, 267, 203, 22, 238, 261, 297, + 303, 304, 203, 20, 238, 253, 287, 299, 300, 303, + 287, 21, 238, 253, 288, 301, 302, 303, 288, 238, + 253, 290, 303, 238, 292, 299, 303, 287, 238, 293, + 301, 303, 293, 238, 295, 303, 203, 504, 259, 259, + 259, 204, 259, 202, 259, 259, 204, 259, 202, 204, + 259, 259, 259, 204, 259, 202, 204, 259, 259, 259, + 204, 259, 259, 204, 259, 259, 259, 204, 259, 259, + 259, 259, 203, 203, 261, 180, 253, 303, 169, 253, + 253, 303, 169, 253, 253, 303, 303, 505, 259, 203, + 259, 204, 259, 204, 259, 259, 204, 259, 203, 259, + 259, 259, 259, 253, 258, 253, 259, 203, 259 }; /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ @@ -2156,30 +2172,30 @@ 362, 363, 363, 364, 364, 364, 364, 365, 366, 366, 366, 366, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 375, 375, 376, 377, 378, 379, 379, 380, - 381, 382, 382, 383, 384, 385, 386, 387, 387, 388, - 389, 390, 390, 391, 392, 392, 392, 392, 392, 393, - 394, 395, 396, 396, 397, 398, 398, 398, 399, 400, - 400, 401, 402, 402, 403, 404, 405, 406, 406, 407, - 408, 409, 410, 410, 410, 410, 410, 411, 411, 412, - 413, 414, 414, 415, 416, 417, 418, 419, 419, 419, - 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, - 428, 428, 428, 428, 428, 429, 430, 431, 432, 432, - 432, 433, 433, 434, 435, 435, 436, 437, 437, 438, - 439, 440, 441, 441, 441, 442, 443, 444, 445, 446, - 447, 448, 449, 450, 450, 450, 450, 451, 452, 452, - 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, - 463, 464, 465, 466, 466, 466, 466, 466, 466, 466, - 466, 466, 466, 466, 466, 467, 467, 468, 468, 468, - 469, 470, 471, 472, 472, 473, 473, 473, 474, 475, - 475, 476, 477, 477, 477, 477, 477, 477, 477, 477, - 477, 477, 477, 477, 477, 477, 478, 478, 478, 478, - 478, 478, 478, 479, 480, 480, 481, 482, 482, 482, - 482, 482, 482, 482, 483, 484, 485, 486, 487, 488, - 489, 490, 491, 492, 493, 494, 495, 496, 497, 497, - 498, 499, 499, 499, 499, 499, 500, 501, 502, 502, - 503, 504, 504, 504, 504, 505, 505, 505, 505, 506, - 507, 508, 509, 510, 511, 511, 512, 513, 513, 514, - 514, 514, 514, 515, 516 + 381, 382, 382, 382, 383, 384, 385, 386, 387, 387, + 388, 389, 390, 390, 391, 392, 392, 392, 392, 392, + 393, 394, 395, 396, 396, 397, 398, 398, 398, 399, + 400, 400, 401, 402, 402, 403, 404, 405, 406, 406, + 407, 408, 409, 410, 410, 410, 410, 410, 411, 411, + 412, 413, 414, 414, 415, 416, 417, 418, 419, 419, + 419, 419, 420, 421, 422, 423, 424, 425, 426, 427, + 428, 428, 428, 428, 428, 428, 429, 430, 431, 432, + 432, 432, 433, 433, 434, 435, 435, 436, 437, 437, + 438, 439, 440, 441, 441, 441, 442, 443, 444, 445, + 446, 447, 448, 449, 450, 450, 450, 450, 451, 452, + 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, + 462, 463, 464, 465, 466, 466, 466, 466, 466, 466, + 466, 466, 466, 466, 466, 466, 467, 467, 468, 468, + 468, 469, 470, 471, 472, 472, 473, 473, 473, 474, + 475, 475, 476, 477, 477, 477, 477, 477, 477, 477, + 477, 477, 477, 477, 477, 477, 477, 478, 478, 478, + 478, 478, 478, 478, 479, 480, 480, 481, 482, 482, + 482, 482, 482, 482, 482, 483, 484, 485, 486, 487, + 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, + 497, 498, 499, 499, 499, 499, 499, 500, 501, 502, + 502, 503, 504, 504, 504, 504, 505, 505, 505, 505, + 506, 507, 508, 509, 510, 511, 511, 512, 513, 513, + 514, 514, 514, 514, 515, 516 }; /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ @@ -2231,30 +2247,30 @@ 10, 1, 1, 1, 1, 1, 1, 7, 0, 3, 5, 3, 3, 9, 7, 9, 1, 1, 1, 1, 7, 0, 3, 3, 1, 1, 5, 1, 1, 1, - 7, 0, 3, 1, 1, 1, 1, 1, 1, 8, - 10, 1, 1, 10, 0, 3, 5, 3, 2, 5, - 1, 1, 1, 1, 5, 1, 1, 1, 8, 1, - 1, 5, 1, 1, 8, 1, 5, 1, 1, 8, - 1, 5, 0, 3, 5, 3, 3, 1, 1, 4, - 1, 1, 1, 4, 1, 1, 7, 0, 3, 3, - 3, 1, 1, 5, 1, 1, 9, 1, 5, 1, - 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, - 1, 1, 1, 10, 1, 1, 10, 1, 1, 10, - 10, 7, 0, 3, 3, 9, 7, 9, 10, 1, - 1, 9, 1, 1, 1, 1, 1, 10, 1, 1, - 7, 9, 1, 10, 7, 1, 10, 7, 1, 10, - 7, 1, 9, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 0, 3, 2, - 1, 1, 4, 1, 1, 1, 2, 3, 4, 1, - 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 7, 0, 3, 3, 1, 1, 1, 1, 1, 1, + 8, 10, 1, 1, 10, 0, 3, 5, 3, 2, + 5, 1, 1, 1, 1, 5, 1, 1, 1, 8, + 1, 1, 5, 1, 1, 8, 1, 5, 1, 1, + 8, 1, 5, 0, 3, 5, 3, 3, 1, 1, + 4, 1, 1, 1, 4, 1, 1, 7, 0, 3, + 3, 3, 1, 1, 5, 1, 1, 9, 1, 5, + 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, + 1, 1, 1, 1, 10, 1, 1, 10, 1, 1, + 10, 10, 7, 0, 3, 3, 9, 7, 9, 10, + 1, 1, 9, 1, 1, 1, 1, 1, 10, 1, + 1, 7, 9, 1, 10, 7, 1, 10, 7, 1, + 10, 7, 1, 9, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 0, 3, + 2, 1, 1, 4, 1, 1, 1, 2, 3, 4, + 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 4, 3, 1, 8, 0, 3, 3, - 3, 5, 3, 2, 1, 1, 4, 1, 1, 4, - 1, 4, 1, 4, 1, 4, 1, 4, 3, 1, - 6, 0, 3, 3, 3, 2, 1, 4, 3, 1, - 16, 1, 1, 1, 1, 0, 6, 3, 2, 1, - 1, 9, 1, 4, 3, 1, 6, 1, 1, 0, - 3, 3, 2, 1, 7 + 1, 1, 1, 1, 4, 3, 1, 8, 0, 3, + 3, 3, 5, 3, 2, 1, 1, 4, 1, 1, + 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, + 1, 6, 0, 3, 3, 3, 2, 1, 4, 3, + 1, 16, 1, 1, 1, 1, 0, 6, 3, 2, + 1, 1, 9, 1, 4, 3, 1, 6, 1, 1, + 0, 3, 3, 2, 1, 7 }; diff -Nru proj-7.2.0/src/wkt2_grammar.y proj-7.2.1/src/wkt2_grammar.y --- proj-7.2.0/src/wkt2_grammar.y 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/src/wkt2_grammar.y 2020-12-21 16:29:10.000000000 +0000 @@ -1073,6 +1073,7 @@ right_delimiter opt_separator_param_unit_identifier_list: + | wkt_separator identifier opt_separator_identifier_list | wkt_separator map_projection_parameter_unit opt_separator_identifier_list parameter_keyword: T_PARAMETER diff -Nru proj-7.2.0/test/cli/testcct proj-7.2.1/test/cli/testcct --- proj-7.2.0/test/cli/testcct 2019-03-06 16:11:50.000000000 +0000 +++ proj-7.2.1/test/cli/testcct 2020-12-26 18:57:21.000000000 +0000 @@ -32,6 +32,28 @@ echo "90 45" 0 | $EXE -d 8 +proj=merc +R=1 >>${OUT} echo "" >>${OUT} +# tests without specifying the number of decimals (by default: 10 for radians and degrees, 4 for meters) +echo "Testing echo 0.5 2 | cct -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad" >> ${OUT} +echo 0.5 2 | $EXE -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad >> ${OUT} + +echo "Testing echo 0.5 2 | cct -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=deg" >> ${OUT} +echo 0.5 2 | $EXE -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=deg >> ${OUT} + +echo "Testing echo 0.5 2 | cct -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=m +xy_out=km" >> ${OUT} +echo 0.5 2 | $EXE -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=m +xy_out=km >> ${OUT} +echo "" >> ${OUT} + +# tests for which the number of decimals has been specified (-d 6) +echo "Testing echo 0.5 2 | cct -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad" >> ${OUT} +echo 0.5 2 | $EXE -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad >> ${OUT} + +echo "Testing echo 0.5 2 | cct -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=deg" >> ${OUT} +echo 0.5 2 | $EXE -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=deg >> ${OUT} + +echo "Testing echo 0.5 2 | cct -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=m +xy_out=km" >> ${OUT} +echo 0.5 2 | $EXE -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=m +xy_out=km >> ${OUT} +echo "" >> ${OUT} + # do 'diff' with distribution results echo "diff ${OUT} with testcct_out.dist" diff -u ${OUT} ${TEST_CLI_DIR}/testcct_out.dist diff -Nru proj-7.2.0/test/cli/testcct_out.dist proj-7.2.1/test/cli/testcct_out.dist --- proj-7.2.0/test/cli/testcct_out.dist 2019-03-06 16:11:50.000000000 +0000 +++ proj-7.2.1/test/cli/testcct_out.dist 2020-12-26 18:57:21.000000000 +0000 @@ -1,3 +1,17 @@ Testing cct -d 8 +proj=merc +R=1 1.57079633 0.88137359 0.00000000 inf +Testing echo 0.5 2 | cct -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad + 0.5000000000 2.0000000000 0.0000 0.0000 +Testing echo 0.5 2 | cct -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=deg + 0.5000000000 2.0000000000 0.0000 0.0000 +Testing echo 0.5 2 | cct -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=m +xy_out=km + 0.0005 0.0020 0.0000 0.0000 + +Testing echo 0.5 2 | cct -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad + 0.500000 2.000000 0.000000 0.0000 +Testing echo 0.5 2 | cct -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=deg + 0.500000 2.000000 0.000000 0.0000 +Testing echo 0.5 2 | cct -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=m +xy_out=km + 0.000500 0.002000 0.000000 0.0000 + diff -Nru proj-7.2.0/test/cli/testprojinfo_out.dist proj-7.2.1/test/cli/testprojinfo_out.dist --- proj-7.2.0/test/cli/testprojinfo_out.dist 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/test/cli/testprojinfo_out.dist 2020-12-26 18:57:21.000000000 +0000 @@ -317,7 +317,8 @@ SCOPE["Historic record only - now superseded - see remarks."], AREA["Canada - onshore and offshore - Alberta; British Columbia; Manitoba; New Brunswick; Newfoundland and Labrador; Northwest Territories; Nova Scotia; Nunavut; Ontario; Prince Edward Island; Quebec; Saskatchewan; Yukon."], BBOX[40.04,-141.01,86.46,-47.74]], - ID["DERIVED_FROM(EPSG)",1312]] + ID["DERIVED_FROM(EPSG)",1312], + REMARK["Uses NTv1 method. Replaced in Quebec by code 1462 and elsewhere in 1997 by NTv2 (transformation code 1313). Input expects longitudes to be positive west; EPSG GeogCRS NAD27 (code 4267) and NAD83 (code 4269) have longitudes positive east."]] ------------------------------------- Operation No. 2: @@ -371,7 +372,8 @@ SCOPE["Transformation of coordinates at 1m to 2m level of accuracy."], AREA["Canada - onshore - Alberta; British Columbia; Manitoba; New Brunswick; Newfoundland and Labrador; Northwest Territories; Nova Scotia; Nunavut; Ontario; Prince Edward Island; Quebec; Saskatchewan; Yukon; offshore east coast."], BBOX[40.04,-141.01,83.17,-47.74]], - ID["DERIVED_FROM(EPSG)",1313]] + ID["DERIVED_FROM(EPSG)",1313], + REMARK["Uses NTv2 data files. Replaces NTv1 (transformation code 1312) except in Quebec. Input expects longitudes to be positive west; EPSG GeogCRS NAD27 (code 4267) and (code 4269) have longitudes positive east. May be used as tfm to WGS 84 - see code 1693."]] ------------------------------------- Operation No. 3: @@ -425,7 +427,8 @@ SCOPE["Transformation of coordinates at 0.2m level of accuracy."], AREA["United States (USA) - CONUS including EEZ -onshore and offshore - Alabama; Arizona; Arkansas; California; Colorado; Connecticut; Delaware; Florida; Georgia; Idaho; Illinois; Indiana; Iowa; Kansas; Kentucky; Louisiana; Maine; Maryland; Massachusetts; Michigan; Minnesota; Mississippi; Missouri; Montana; Nebraska; Nevada; New Hampshire; New Jersey; New Mexico; New York; North Carolina; North Dakota; Ohio; Oklahoma; Oregon; Pennsylvania; Rhode Island; South Carolina; South Dakota; Tennessee; Texas; Utah; Vermont; Virginia; Washington; West Virginia; Wisconsin; Wyoming. US Gulf of Mexico (GoM) OCS."], BBOX[23.81,-129.17,49.38,-65.69]], - ID["DERIVED_FROM(EPSG)",1241]] + ID["DERIVED_FROM(EPSG)",1241], + REMARK["Uses NADCON method which expects longitudes positive west; EPSG GeogCRS NAD27 (code 4267) and NAD83 (code 4269) have longitudes positive east."]] ------------------------------------- Operation No. 4: @@ -479,7 +482,8 @@ SCOPE["Geodesy."], AREA["United States (USA) - Alaska including EEZ."], BBOX[47.88,167.65,74.71,-129.99]], - ID["DERIVED_FROM(EPSG)",1243]] + ID["DERIVED_FROM(EPSG)",1243], + REMARK["Uses NADCON method which expects longitudes positive west; EPSG GeogCRS NAD27 (code 4267) and NAD83 (code 4269) have longitudes positive east. May be used as transformation to WGS 84 - see NAD27 to WGS 84 (85) (code 15864)."]] ------------------------------------- Operation No. 5: @@ -533,7 +537,8 @@ SCOPE["Transformation of coordinates at 1m to 2m level of accuracy."], AREA["Canada - Quebec."], BBOX[44.99,-79.85,62.62,-57.1]], - ID["DERIVED_FROM(EPSG)",1573]] + ID["DERIVED_FROM(EPSG)",1573], + REMARK["Also distributed with file name QUE27-83.gsb. Replaces NAD27 to NAD83 (5) (code 1462). Uses NT method which expects longitudes positive west; EPSG GeogCRSs NAD27 (code 4267) and NAD83 (code 4269) have longitudes positive east."]] ------------------------------------- Operation No. 6: @@ -916,7 +921,7 @@ +proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs Testing RH2000 height to SWEREF99: projinfo -s EPSG:5613 -t EPSG:4977 -Candidate operations found: 1 +Candidate operations found: 2 ------------------------------------- Operation No. 1: @@ -968,8 +973,55 @@ BBOX[55.28,10.93,69.07,24.17]], ID["INVERSE(DERIVED_FROM(PROJ))","EPSG_4977_TO_EPSG_5613"]] +------------------------------------- +Operation No. 2: + +unknown id, Transformation from RH2000 height to SWEREF99 (ballpark vertical transformation, without ellipsoid height to vertical height correction), unknown accuracy, World, has ballpark transformation + +PROJ string: ++proj=noop + +WKT2:2019 string: +COORDINATEOPERATION["Transformation from RH2000 height to SWEREF99 (ballpark vertical transformation, without ellipsoid height to vertical height correction)", + SOURCECRS[ + VERTCRS["RH2000 height", + DYNAMIC[ + FRAMEEPOCH[2000]], + VDATUM["Rikets hojdsystem 2000"], + CS[vertical,1], + AXIS["gravity-related height (H)",up, + LENGTHUNIT["metre",1]], + ID["EPSG",5613]]], + TARGETCRS[ + GEOGCRS["SWEREF99", + DATUM["SWEREF99", + ELLIPSOID["GRS 1980",6378137,298.257222101, + LENGTHUNIT["metre",1]]], + PRIMEM["Greenwich",0, + ANGLEUNIT["degree",0.0174532925199433]], + CS[ellipsoidal,3], + AXIS["geodetic latitude (Lat)",north, + ORDER[1], + ANGLEUNIT["degree",0.0174532925199433]], + AXIS["geodetic longitude (Lon)",east, + ORDER[2], + ANGLEUNIT["degree",0.0174532925199433]], + AXIS["ellipsoidal height (h)",up, + ORDER[3], + LENGTHUNIT["metre",1]], + ID["EPSG",4977]]], + METHOD["Change of Vertical Unit", + ID["EPSG",1069]], + PARAMETER["Unit conversion scalar",1, + SCALEUNIT["unity",1], + ID["EPSG",1051]], + USAGE[ + SCOPE["unknown"], + AREA["World"], + BBOX[-90,-180,90,180]]] + Testing NAD83(2011) + NAVD88 height -> NAD83(2011) : projinfo -s EPSG:6349 -t EPSG:6319 --spatial-test intersects -o PROJ -Candidate operations found: 2 +Candidate operations found: 3 ------------------------------------- Operation No. 1: @@ -996,8 +1048,16 @@ +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 +------------------------------------- +Operation No. 3: + +unknown id, Transformation from NAVD88 height to NAD83(2011) (ballpark vertical transformation, without ellipsoid height to vertical height correction), unknown accuracy, World, has ballpark transformation + +PROJ string: ++proj=noop + Testing NGF IGN69 height to RGF93: projinfo -s EPSG:5720 -t EPSG:4965 -o PROJ -Candidate operations found: 1 +Candidate operations found: 2 ------------------------------------- Operation No. 1: @@ -1011,6 +1071,14 @@ +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 +------------------------------------- +Operation No. 2: + +unknown id, Transformation from NGF-IGN69 height to RGF93 (ballpark vertical transformation, without ellipsoid height to vertical height correction), unknown accuracy, World, has ballpark transformation + +PROJ string: ++proj=noop + Testing EPSG:32631 --3d PROJ.4 string: +proj=utm +zone=31 +datum=WGS84 +units=m +no_defs +type=crs diff -Nru proj-7.2.0/test/gie/builtins.gie proj-7.2.1/test/gie/builtins.gie --- proj-7.2.0/test/gie/builtins.gie 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/test/gie/builtins.gie 2020-12-26 18:57:21.000000000 +0000 @@ -5702,6 +5702,130 @@ expect -0.001790493 -0.000895247 ------------------------------------------------------------------------------- +# Approx tmerc on a almost spherical ellipsoid, lat_0 north hemisphere + +operation +proj=tmerc +a=6400000 +rf=1e12 +k=0.9 +lat_0=40 +approx +------------------------------------------------------------------------------- +tolerance 0.1 mm + +accept 0 -30 +expect 0 -7037167.5440 +roundtrip 1 + +accept 1 -30 +expect 87064.5795 -7037547.4590 +roundtrip 1 + +accept -1 -30 +expect -87064.5795 -7037547.4590 +roundtrip 1 + +accept 0 30 +expect 0 -1005309.6491 +roundtrip 1 + +accept 0 40 +expect 0 0 +roundtrip 1 + +accept 1 41 +expect 75872.2182 100965.3718 +roundtrip 1 + +------------------------------------------------------------------------------- +# Approx tmerc on a sphere, lat_0 north hemisphere + +operation +proj=tmerc +R=6400000 +k=0.9 +lat_0=40 +------------------------------------------------------------------------------- +tolerance 0.1 mm + +accept 0 -30 +expect 0 -7037167.5440 +roundtrip 1 + +accept 1 -30 +expect 87064.5795 -7037547.4590 +roundtrip 1 + +accept -1 -30 +expect -87064.5795 -7037547.4590 +roundtrip 1 + +accept 0 30 +expect 0 -1005309.6491 +roundtrip 1 + +accept 0 40 +expect 0 0 +roundtrip 1 + +accept 1 41 +expect 75872.2182 100965.3718 +roundtrip 1 + +------------------------------------------------------------------------------- +# Approx tmerc on a almost spherical ellipsoid, lat_0 south hemisphere + +operation +proj=tmerc +a=6400000 +rf=1e12 +k=0.9 +lat_0=-40 +approx +------------------------------------------------------------------------------- +tolerance 0.1 mm + +accept 0 -30 +expect 0 1005309.6491 +roundtrip 1 + +accept 1 -30 +expect 87064.5795 1004929.7341 +roundtrip 1 + +accept -1 -30 +expect -87064.5795 1004929.7341 +roundtrip 1 + +accept 0 30 +expect 0 7037167.5440 +roundtrip 1 + +accept 0 -40 +expect 0 0 +roundtrip 1 + +accept 1 -41 +expect 75872.2182 -100965.3718 +roundtrip 1 + +------------------------------------------------------------------------------- +# Approx tmerc on a sphere, lat_0 south hemisphere + +operation +proj=tmerc +R=6400000 +k=0.9 +lat_0=-40 +------------------------------------------------------------------------------- +tolerance 0.1 mm + +accept 0 -30 +expect 0 1005309.6491 +roundtrip 1 + +accept 1 -30 +expect 87064.5795 1004929.7341 +roundtrip 1 + +accept -1 -30 +expect -87064.5795 1004929.7341 +roundtrip 1 + +accept 0 30 +expect 0 7037167.5440 +roundtrip 1 + +accept 0 -40 +expect 0 0 +roundtrip 1 + +accept 1 -41 +expect 75872.2182 -100965.3718 +roundtrip 1 + +------------------------------------------------------------------------------- operation +proj=tmerc +R=1 ------------------------------------------------------------------------------- direction inverse diff -Nru proj-7.2.0/test/unit/CMakeLists.txt proj-7.2.1/test/unit/CMakeLists.txt --- proj-7.2.0/test/unit/CMakeLists.txt 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/test/unit/CMakeLists.txt 2020-12-26 18:57:21.000000000 +0000 @@ -132,6 +132,7 @@ test_metadata.cpp test_io.cpp test_operation.cpp + test_operationfactory.cpp test_datum.cpp test_factory.cpp test_c_api.cpp diff -Nru proj-7.2.0/test/unit/Makefile.am proj-7.2.1/test/unit/Makefile.am --- proj-7.2.0/test/unit/Makefile.am 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/test/unit/Makefile.am 2020-12-26 18:57:21.000000000 +0000 @@ -51,7 +51,18 @@ 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 -Nru proj-7.2.0/test/unit/Makefile.in proj-7.2.1/test/unit/Makefile.in --- proj-7.2.0/test/unit/Makefile.in 2020-10-28 12:56:47.000000000 +0000 +++ proj-7.2.1/test/unit/Makefile.in 2020-12-26 18:57:38.000000000 +0000 @@ -145,9 +145,9 @@ proj_errno_string_test_DEPENDENCIES = ../../src/libproj.la am_test_cpp_api_OBJECTS = test_util.$(OBJEXT) test_common.$(OBJEXT) \ test_crs.$(OBJEXT) test_metadata.$(OBJEXT) test_io.$(OBJEXT) \ - test_operation.$(OBJEXT) test_datum.$(OBJEXT) \ - test_factory.$(OBJEXT) test_c_api.$(OBJEXT) \ - test_grids.$(OBJEXT) main.$(OBJEXT) + test_operation.$(OBJEXT) test_operationfactory.$(OBJEXT) \ + test_datum.$(OBJEXT) test_factory.$(OBJEXT) \ + test_c_api.$(OBJEXT) test_grids.$(OBJEXT) main.$(OBJEXT) test_cpp_api_OBJECTS = $(am_test_cpp_api_OBJECTS) test_cpp_api_DEPENDENCIES = ../../src/libproj.la am_test_defmodel_OBJECTS = test_defmodel.$(OBJEXT) main.$(OBJEXT) @@ -190,8 +190,9 @@ ./$(DEPDIR)/test_grids.Po ./$(DEPDIR)/test_io.Po \ ./$(DEPDIR)/test_metadata.Po ./$(DEPDIR)/test_network-main.Po \ ./$(DEPDIR)/test_network-test_network.Po \ - ./$(DEPDIR)/test_operation.Po ./$(DEPDIR)/test_tinshift.Po \ - ./$(DEPDIR)/test_util.Po + ./$(DEPDIR)/test_operation.Po \ + ./$(DEPDIR)/test_operationfactory.Po \ + ./$(DEPDIR)/test_tinshift.Po ./$(DEPDIR)/test_util.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -423,7 +424,19 @@ proj_angular_io_test_LDADD = ../../src/libproj.la @GTEST_LIBS@ proj_context_test_SOURCES = proj_context_test.cpp main.cpp proj_context_test_LDADD = ../../src/libproj.la @GTEST_LIBS@ -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@ gie_self_tests_SOURCES = gie_self_tests.cpp main.cpp gie_self_tests_LDADD = ../../src/libproj.la @GTEST_LIBS@ @SQLITE3_LIBS@ @@ -548,6 +561,7 @@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_network-main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_network-test_network.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_operation.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_operationfactory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_tinshift.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_util.Po@am__quote@ # am--include-marker @@ -785,6 +799,7 @@ -rm -f ./$(DEPDIR)/test_network-main.Po -rm -f ./$(DEPDIR)/test_network-test_network.Po -rm -f ./$(DEPDIR)/test_operation.Po + -rm -f ./$(DEPDIR)/test_operationfactory.Po -rm -f ./$(DEPDIR)/test_tinshift.Po -rm -f ./$(DEPDIR)/test_util.Po -rm -f Makefile @@ -852,6 +867,7 @@ -rm -f ./$(DEPDIR)/test_network-main.Po -rm -f ./$(DEPDIR)/test_network-test_network.Po -rm -f ./$(DEPDIR)/test_operation.Po + -rm -f ./$(DEPDIR)/test_operationfactory.Po -rm -f ./$(DEPDIR)/test_tinshift.Po -rm -f ./$(DEPDIR)/test_util.Po -rm -f Makefile diff -Nru proj-7.2.0/test/unit/test_c_api.cpp proj-7.2.1/test/unit/test_c_api.cpp --- proj-7.2.0/test/unit/test_c_api.cpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/test/unit/test_c_api.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -433,16 +433,27 @@ ObjectKeeper keeper_crs4979(crs4979); ASSERT_NE(crs4979, nullptr); + EXPECT_EQ(proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_GDAL, nullptr), nullptr); + // STRICT=NO { - EXPECT_EQ(proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_GDAL, nullptr), nullptr); - const char *const options[] = {"STRICT=NO", nullptr}; auto wkt = proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_GDAL, options); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("GEOGCS[\"WGS 84\"") == 0) << wkt; } + // ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES + { + const char *const options[] = { + "ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES", nullptr}; + auto wkt = proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_GDAL, options); + ASSERT_NE(wkt, nullptr); + EXPECT_TRUE(std::string(wkt).find( + "COMPD_CS[\"WGS 84 + Ellipsoid (metre)\"") == 0) + << wkt; + } + // unsupported option { const char *const options[] = {"unsupported=yes", nullptr}; @@ -1165,10 +1176,12 @@ 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); } // --------------------------------------------------------------------------- @@ -4643,10 +4656,21 @@ ObjectKeeper keeper_geog_crs(geog_crs); ASSERT_NE(geog_crs, nullptr); - auto P = proj_create_crs_to_crs_from_pj(m_ctxt, compound, geog_crs, nullptr, - nullptr); - ObjectKeeper keeper_P(P); - ASSERT_NE(P, nullptr); + PJ_OPERATION_FACTORY_CONTEXT *ctxt = + proj_create_operation_factory_context(m_ctxt, nullptr); + ASSERT_NE(ctxt, nullptr); + ContextKeeper keeper_ctxt(ctxt); + proj_operation_factory_context_set_grid_availability_use( + m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); + proj_operation_factory_context_set_spatial_criterion( + m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); + PJ_OBJ_LIST *operations = + proj_create_operations(m_ctxt, compound, geog_crs, ctxt); + ASSERT_NE(operations, nullptr); + ObjListKeeper keeper_operations(operations); + EXPECT_GE(proj_list_get_count(operations), 1); + auto P = proj_list_get(m_ctxt, operations, 0); + ObjectKeeper keeper_transform(P); auto name = proj_get_name(P); ASSERT_TRUE(name != nullptr); @@ -4699,10 +4723,21 @@ ObjectKeeper keeper_geog_crs(geog_crs); ASSERT_NE(geog_crs, nullptr); - auto P = proj_create_crs_to_crs_from_pj(m_ctxt, compound, geog_crs, nullptr, - nullptr); - ObjectKeeper keeper_P(P); - ASSERT_NE(P, nullptr); + PJ_OPERATION_FACTORY_CONTEXT *ctxt = + proj_create_operation_factory_context(m_ctxt, nullptr); + ASSERT_NE(ctxt, nullptr); + ContextKeeper keeper_ctxt(ctxt); + proj_operation_factory_context_set_grid_availability_use( + m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); + proj_operation_factory_context_set_spatial_criterion( + m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); + PJ_OBJ_LIST *operations = + proj_create_operations(m_ctxt, compound, geog_crs, ctxt); + ASSERT_NE(operations, nullptr); + ObjListKeeper keeper_operations(operations); + EXPECT_GE(proj_list_get_count(operations), 1); + auto P = proj_list_get(m_ctxt, operations, 0); + ObjectKeeper keeper_transform(P); auto name = proj_get_name(P); ASSERT_TRUE(name != nullptr); @@ -4732,10 +4767,13 @@ ObjectKeeper keeper_compound_from_projjson(compound_from_projjson); ASSERT_NE(compound_from_projjson, nullptr); - auto P2 = proj_create_crs_to_crs_from_pj(m_ctxt, compound_from_projjson, - geog_crs, nullptr, nullptr); - ObjectKeeper keeper_P2(P2); - ASSERT_NE(P2, nullptr); + PJ_OBJ_LIST *operations2 = + proj_create_operations(m_ctxt, compound_from_projjson, geog_crs, ctxt); + ASSERT_NE(operations2, nullptr); + ObjListKeeper keeper_operations2(operations2); + EXPECT_GE(proj_list_get_count(operations2), 1); + auto P2 = proj_list_get(m_ctxt, operations2, 0); + ObjectKeeper keeper_transform2(P2); auto name_bis = proj_get_name(P2); ASSERT_TRUE(name_bis != nullptr); diff -Nru proj-7.2.0/test/unit/test_crs.cpp proj-7.2.1/test/unit/test_crs.cpp --- proj-7.2.0/test/unit/test_crs.cpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/test/unit/test_crs.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -414,6 +414,20 @@ // --------------------------------------------------------------------------- +TEST(crs, EPSG_4901_as_WKT1_ESRI_with_PRIMEM_unit_name_morphing) { + auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); + auto crs = factory->createCoordinateReferenceSystem("4901"); + WKTFormatterNNPtr f(WKTFormatter::create( + WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create())); + EXPECT_EQ(crs->exportToWKT(f.get()), + "GEOGCS[\"GCS_ATF_Paris\",DATUM[\"D_ATF\"," + "SPHEROID[\"Plessis_1817\",6376523.0,308.64]]," + "PRIMEM[\"Paris_RGS\",2.33720833333333]," + "UNIT[\"Grad\",0.0157079632679489]]"); +} + +// --------------------------------------------------------------------------- + TEST(crs, EPSG_4326_as_WKT1_ESRI_without_database) { auto crs = GeographicCRS::EPSG_4326; WKTFormatterNNPtr f( @@ -513,6 +527,36 @@ // --------------------------------------------------------------------------- +#ifdef notavailable_since_setAllowEllipsoidalHeightAsVerticalCRS_is_internal +TEST(crs, EPSG_4979_as_WKT1_GDAL_with_ellipsoidal_height_as_vertical_crs) { + auto crs = GeographicCRS::EPSG_4979; + auto wkt = crs->exportToWKT( + &(WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, + DatabaseContext::create()) + ->setAllowEllipsoidalHeightAsVerticalCRS(true))); + + // For LAS 1.4 WKT1... + EXPECT_EQ(wkt, "COMPD_CS[\"WGS 84 + Ellipsoid (metre)\",\n" + " GEOGCS[\"WGS 84\",\n" + " DATUM[\"WGS_1984\",\n" + " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" + " AUTHORITY[\"EPSG\",\"7030\"]],\n" + " AUTHORITY[\"EPSG\",\"6326\"]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " AUTHORITY[\"EPSG\",\"8901\"]],\n" + " UNIT[\"degree\",0.0174532925199433,\n" + " AUTHORITY[\"EPSG\",\"9122\"]],\n" + " AUTHORITY[\"EPSG\",\"4326\"]],\n" + " VERT_CS[\"Ellipsoid (metre)\",\n" + " VERT_DATUM[\"Ellipsoid\",2002],\n" + " UNIT[\"metre\",1,\n" + " AUTHORITY[\"EPSG\",\"9001\"]],\n" + " AXIS[\"Ellipsoidal height\",UP]]]"); +} +#endif + +// --------------------------------------------------------------------------- + TEST(crs, EPSG_4979_as_WKT1_ESRI) { auto crs = GeographicCRS::EPSG_4979; WKTFormatterNNPtr f( @@ -2037,6 +2081,52 @@ // --------------------------------------------------------------------------- +#ifdef notavailable_since_setAllowEllipsoidalHeightAsVerticalCRS_is_internal +TEST(crs, + projectedCRS_3D_as_WKT1_GDAL_with_ellipsoidal_height_as_vertical_crs) { + auto dbContext = DatabaseContext::create(); + auto crs = AuthorityFactory::create(dbContext, "EPSG") + ->createProjectedCRS("32631") + ->promoteTo3D(std::string(), dbContext); + auto wkt = crs->exportToWKT( + &(WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, dbContext) + ->setAllowEllipsoidalHeightAsVerticalCRS(true))); + + // For LAS 1.4 WKT1... + EXPECT_EQ(wkt, + "COMPD_CS[\"WGS 84 / UTM zone 31N + Ellipsoid (metre)\",\n" + " PROJCS[\"WGS 84 / UTM zone 31N\",\n" + " GEOGCS[\"WGS 84\",\n" + " DATUM[\"WGS_1984\",\n" + " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" + " AUTHORITY[\"EPSG\",\"7030\"]],\n" + " AUTHORITY[\"EPSG\",\"6326\"]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " AUTHORITY[\"EPSG\",\"8901\"]],\n" + " UNIT[\"degree\",0.0174532925199433,\n" + " AUTHORITY[\"EPSG\",\"9122\"]],\n" + " AUTHORITY[\"EPSG\",\"4326\"]],\n" + " PROJECTION[\"Transverse_Mercator\"],\n" + " PARAMETER[\"latitude_of_origin\",0],\n" + " PARAMETER[\"central_meridian\",3],\n" + " PARAMETER[\"scale_factor\",0.9996],\n" + " PARAMETER[\"false_easting\",500000],\n" + " PARAMETER[\"false_northing\",0],\n" + " UNIT[\"metre\",1,\n" + " AUTHORITY[\"EPSG\",\"9001\"]],\n" + " AXIS[\"Easting\",EAST],\n" + " AXIS[\"Northing\",NORTH],\n" + " AUTHORITY[\"EPSG\",\"32631\"]],\n" + " VERT_CS[\"Ellipsoid (metre)\",\n" + " VERT_DATUM[\"Ellipsoid\",2002],\n" + " UNIT[\"metre\",1,\n" + " AUTHORITY[\"EPSG\",\"9001\"]],\n" + " AXIS[\"Ellipsoidal height\",UP]]]"); +} +#endif + +// --------------------------------------------------------------------------- + TEST(crs, projectedCRS_with_ESRI_code_as_WKT1_ESRI) { auto dbContext = DatabaseContext::create(); auto crs = AuthorityFactory::create(dbContext, "ESRI") @@ -2107,6 +2197,53 @@ // --------------------------------------------------------------------------- +TEST(crs, projectedCRS_3D_is_WKT1_equivalent_to_WKT2) { + auto dbContext = DatabaseContext::create(); + + // "Illegal" WKT1 with a Projected 3D CRS + auto wkt1 = "PROJCS[\"WGS 84 / UTM zone 16N [EGM08-1]\"," + "GEOGCS[\"WGS 84 / UTM zone 16N [EGM08-1]\"," + "DATUM[\"WGS84\",SPHEROID[\"WGS84\",6378137.000,298.257223563," + "AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]]," + "PRIMEM[\"Greenwich\",0.0000000000000000," + "AUTHORITY[\"EPSG\",\"8901\"]]," + "UNIT[\"Degree\",0.01745329251994329547," + "AUTHORITY[\"EPSG\",\"9102\"]],AUTHORITY[\"EPSG\",\"32616\"]]," + "UNIT[\"Meter\",1.00000000000000000000," + "AUTHORITY[\"EPSG\",\"9001\"]]," + "PROJECTION[\"Transverse_Mercator\"]," + "PARAMETER[\"latitude_of_origin\",0.0000000000000000]," + "PARAMETER[\"central_meridian\",-87.0000000002777938]," + "PARAMETER[\"scale_factor\",0.9996000000000000]," + "PARAMETER[\"false_easting\",500000.000]," + "PARAMETER[\"false_northing\",0.000]," + "AXIS[\"Easting\",EAST]," + "AXIS[\"Northing\",NORTH]," + "AXIS[\"Height\",UP]," + "AUTHORITY[\"EPSG\",\"32616\"]]"; + + auto obj = WKTParser() + .setStrict(false) + .attachDatabaseContext(dbContext) + .createFromWKT(wkt1); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + + WKTFormatterNNPtr f( + WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)); + auto wkt2 = crs->exportToWKT(f.get()); + auto obj2 = + WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt2); + auto crs2 = nn_dynamic_pointer_cast(obj2); + ASSERT_TRUE(crs2 != nullptr); + + EXPECT_TRUE(crs->isEquivalentTo( + crs2.get(), + IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)); +} + +// --------------------------------------------------------------------------- + TEST(crs, projectedCRS_Krovak_EPSG_5221_as_PROJ_string) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto crs = factory->createProjectedCRS("5221"); @@ -4750,6 +4887,18 @@ // --------------------------------------------------------------------------- +TEST(crs, derivedGeographicCRS_basic) { + + auto derivedCRS = createDerivedGeographicCRS(); + EXPECT_TRUE(derivedCRS->isEquivalentTo(derivedCRS.get())); + EXPECT_FALSE(derivedCRS->isEquivalentTo( + derivedCRS->baseCRS().get(), IComparable::Criterion::EQUIVALENT)); + EXPECT_FALSE(derivedCRS->baseCRS()->isEquivalentTo( + derivedCRS.get(), IComparable::Criterion::EQUIVALENT)); +} + +// --------------------------------------------------------------------------- + TEST(crs, derivedGeographicCRS_WKT2) { auto expected = "GEODCRS[\"WMO Atlantic Pole\",\n" @@ -4950,6 +5099,18 @@ // --------------------------------------------------------------------------- +TEST(crs, derivedGeodeticCRS_basic) { + + auto derivedCRS = createDerivedGeodeticCRS(); + EXPECT_TRUE(derivedCRS->isEquivalentTo(derivedCRS.get())); + EXPECT_FALSE(derivedCRS->isEquivalentTo( + derivedCRS->baseCRS().get(), IComparable::Criterion::EQUIVALENT)); + EXPECT_FALSE(derivedCRS->baseCRS()->isEquivalentTo( + derivedCRS.get(), IComparable::Criterion::EQUIVALENT)); +} + +// --------------------------------------------------------------------------- + TEST(crs, derivedGeodeticCRS_WKT2) { auto expected = "GEODCRS[\"Derived geodetic CRS\",\n" @@ -5796,6 +5957,31 @@ CoordinateOperationContext::IntermediateCRSUse::NEVER), crs_5340); } + + // Check that we get the same result from an EPSG code and a CRS created + // from its WKT1 representation. + { + // Pulkovo 1942 / CS63 zone A2 + auto crs = factory->createCoordinateReferenceSystem("2936"); + + // Two candidate transformations found, so not picking up any + EXPECT_EQ(crs->createBoundCRSToWGS84IfPossible( + dbContext, + CoordinateOperationContext::IntermediateCRSUse::NEVER), + crs); + + auto wkt = crs->exportToWKT( + WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, dbContext) + .get()); + auto obj = + WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); + auto crs_from_wkt = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs_from_wkt != nullptr); + EXPECT_EQ(crs_from_wkt->createBoundCRSToWGS84IfPossible( + dbContext, + CoordinateOperationContext::IntermediateCRSUse::NEVER), + crs_from_wkt); + } } // --------------------------------------------------------------------------- diff -Nru proj-7.2.0/test/unit/test_factory.cpp proj-7.2.1/test/unit/test_factory.cpp --- proj-7.2.0/test/unit/test_factory.cpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/test/unit/test_factory.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -3062,6 +3062,12 @@ .size(), 1U); + // Exact name, but with other CRS that have an aliases to it ==> should + // match only the CRS with the given name, not those other CRS. + EXPECT_EQ(factory->createObjectsFromName("ETRS89 / UTM zone 32N", {}, false) + .size(), + 1U); + // Prime meridian EXPECT_EQ(factoryEPSG->createObjectsFromName("Paris", {}, false, 2).size(), 1U); diff -Nru proj-7.2.0/test/unit/test_io.cpp proj-7.2.1/test/unit/test_io.cpp --- proj-7.2.0/test/unit/test_io.cpp 2020-10-26 09:33:16.000000000 +0000 +++ proj-7.2.1/test/unit/test_io.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -488,6 +488,62 @@ // --------------------------------------------------------------------------- +TEST(wkt_parse, wkt1_esri_EPSG_4901_grad) { + auto obj = + WKTParser() + .attachDatabaseContext(DatabaseContext::create()) + .createFromWKT("GEOGCS[\"GCS_ATF_Paris\",DATUM[\"D_ATF\"," + "SPHEROID[\"Plessis_1817\",6376523.0,308.64]]," + "PRIMEM[\"Paris_RGS\",2.33720833333333]," + "UNIT[\"Grad\",0.0157079632679489]]"); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + + auto datum = crs->datum(); + auto primem = datum->primeMeridian(); + EXPECT_EQ(primem->nameStr(), "Paris RGS"); + // The PRIMEM is really in degree + EXPECT_EQ(primem->longitude().unit(), UnitOfMeasure::DEGREE); + EXPECT_NEAR(primem->longitude().value(), 2.33720833333333, 1e-14); +} + +// --------------------------------------------------------------------------- + +TEST(wkt_parse, wkt2_epsg_org_EPSG_4901_PRIMEM_weird_sexagesimal_DMS) { + // Current epsg.org output may use the EPSG:9110 "sexagesimal DMS" + // unit and a DD.MMSSsss value, but this will likely be changed to + // use decimal degree. + auto obj = WKTParser().createFromWKT( + "GEOGCRS[\"ATF (Paris)\"," + " DATUM[\"Ancienne Triangulation Francaise (Paris)\"," + " ELLIPSOID[\"Plessis 1817\",6376523,308.64," + " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]," + " ID[\"EPSG\",7027]]," + " ID[\"EPSG\",6901]]," + " PRIMEM[\"Paris RGS\",2.201395," + " ANGLEUNIT[\"sexagesimal DMS\",1,ID[\"EPSG\",9110]]," + " ID[\"EPSG\",8914]]," + " CS[ellipsoidal,2," + " ID[\"EPSG\",6403]]," + " AXIS[\"Geodetic latitude (Lat)\",north," + " ORDER[1]]," + " AXIS[\"Geodetic longitude (Lon)\",east," + " ORDER[2]]," + " ANGLEUNIT[\"grad\",0.015707963267949,ID[\"EPSG\",9105]]," + " USAGE[SCOPE[\"Geodesy.\"],AREA[\"France - mainland onshore.\"]," + " BBOX[42.33,-4.87,51.14,8.23]]," + "ID[\"EPSG\",4901]]"); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + + auto datum = crs->datum(); + auto primem = datum->primeMeridian(); + EXPECT_EQ(primem->longitude().unit(), UnitOfMeasure::DEGREE); + EXPECT_NEAR(primem->longitude().value(), 2.33720833333333, 1e-14); +} + +// --------------------------------------------------------------------------- + TEST(wkt_parse, wkt1_geographic_old_datum_name_from_EPSG_code) { auto wkt = "GEOGCS[\"S-JTSK (Ferro)\",\n" @@ -619,6 +675,52 @@ // --------------------------------------------------------------------------- +TEST(wkt_parse, wkt1_geographic_epsg_org_api_4326) { + // Output from + // https://apps.epsg.org/api/v1/CoordRefSystem/4326/export/?format=wkt&formatVersion=1 + // using a datum ensemble name + auto wkt = + "GEOGCS[\"WGS 84\",DATUM[\"World Geodetic System 1984 ensemble\"," + "SPHEROID[\"WGS 84\",6378137,298.257223563," + "AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]]," + "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," + "UNIT[\"degree (supplier to define representation)\"," + "0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]," + "AXIS[\"Lat\",north],AXIS[\"Lon\",east]," + "AUTHORITY[\"EPSG\",\"4326\"]]"; + auto obj = WKTParser().createFromWKT(wkt); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + + auto datum = crs->datum(); + EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984"); +} + +// --------------------------------------------------------------------------- + +TEST(wkt_parse, wkt1_geographic_epsg_org_api_4258) { + // Output from + // https://apps.epsg.org/api/v1/CoordRefSystem/4258/export/?format=wkt&formatVersion=1 + // using a datum ensemble name + auto wkt = "GEOGCS[\"ETRS89\"," + "DATUM[\"European Terrestrial Reference System 1989 ensemble\"," + "SPHEROID[\"GRS 1980\",6378137,298.257222101," + "AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6258\"]]," + "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," + "UNIT[\"degree (supplier to define representation)\"," + "0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]," + "AXIS[\"Lat\",north],AXIS[\"Lon\",east]," + "AUTHORITY[\"EPSG\",\"4258\"]]"; + auto obj = WKTParser().createFromWKT(wkt); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + + auto datum = crs->datum(); + EXPECT_EQ(datum->nameStr(), "European Terrestrial Reference System 1989"); +} + +// --------------------------------------------------------------------------- + TEST(wkt_parse, wkt1_geocentric_with_PROJ4_extension) { auto wkt = "GEOCCS[\"WGS 84\",\n" " DATUM[\"unknown\",\n" @@ -1658,16 +1760,21 @@ " ID[\"EPSG\",9122]],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" - " ANGLEUNIT[\"degree\",0.0174532925199433,\n" - " ID[\"EPSG\",9122]],\n" + // Volontary omit LENGTHUNIT to check the WKT grammar accepts + // Check that we default to degree + //" ANGLEUNIT[\"degree\",0.0174532925199433,\n" + //" ID[\"EPSG\",9122]],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" - " SCALEUNIT[\"unity\",1,\n" - " ID[\"EPSG\",9201]],\n" + // Check that we default to unity + //" SCALEUNIT[\"unity\",1,\n" + //" ID[\"EPSG\",9201]],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" - " LENGTHUNIT[\"metre\",1,\n" - " ID[\"EPSG\",9001]],\n" + // Volontary omit LENGTHUNIT to check the WKT grammar accepts + // Check that we default to metre + //" LENGTHUNIT[\"metre\",1,\n" + //" ID[\"EPSG\",9001]],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1,\n" @@ -1879,6 +1986,60 @@ // --------------------------------------------------------------------------- +TEST(wkt_parse, wkt2_2019_projected_with_base_geocentric) { + auto wkt = + "PROJCRS[\"EPSG topocentric example B\",\n" + " BASEGEODCRS[\"WGS 84\",\n" + " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" + " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" + " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + " LENGTHUNIT[\"metre\",1]],\n" + " ENSEMBLEACCURACY[2.0]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + " ID[\"EPSG\",4978]],\n" + " CONVERSION[\"EPSG topocentric example B\",\n" + " METHOD[\"Geocentric/topocentric conversions\",\n" + " ID[\"EPSG\",9836]],\n" + " PARAMETER[\"Geocentric X of topocentric origin\",3771793.97,\n" + " LENGTHUNIT[\"metre\",1],\n" + " ID[\"EPSG\",8837]],\n" + " PARAMETER[\"Geocentric Y of topocentric origin\",140253.34,\n" + " LENGTHUNIT[\"metre\",1],\n" + " ID[\"EPSG\",8838]],\n" + " PARAMETER[\"Geocentric Z of topocentric origin\",5124304.35,\n" + " LENGTHUNIT[\"metre\",1],\n" + " ID[\"EPSG\",8839]]],\n" + " CS[Cartesian,3],\n" + " AXIS[\"topocentric East (U)\",east,\n" + " ORDER[1],\n" + " LENGTHUNIT[\"metre\",1]],\n" + " AXIS[\"topocentric North (V)\",north,\n" + " ORDER[2],\n" + " LENGTHUNIT[\"metre\",1]],\n" + " AXIS[\"topocentric height (W)\",up,\n" + " ORDER[3],\n" + " LENGTHUNIT[\"metre\",1]],\n" + " USAGE[\n" + " SCOPE[\"Example only (fictitious).\"],\n" + " AREA[\"Description of the extent of the CRS.\"],\n" + " BBOX[-90,-180,90,180]],\n" + " ID[\"EPSG\",5820]]"; + auto dbContext = DatabaseContext::create(); + // Need a database so that EPSG:4978 is resolved + auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_TRUE(crs->baseCRS()->isGeocentric()); +} + +// --------------------------------------------------------------------------- + TEST(crs, projected_angular_unit_from_primem) { auto obj = WKTParser().createFromWKT( "PROJCRS[\"NTF (Paris) / Lambert Nord France\",\n" @@ -11136,6 +11297,155 @@ EXPECT_EQ(pcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } + +// --------------------------------------------------------------------------- + +TEST(json_import, projected_crs_with_geocentric_base) { + auto json = "{\n" + " \"$schema\": \"foo\",\n" + " \"type\": \"ProjectedCRS\",\n" + " \"name\": \"EPSG topocentric example B\",\n" + " \"base_crs\": {\n" + " \"name\": \"WGS 84\",\n" + " \"datum_ensemble\": {\n" + " \"name\": \"World Geodetic System 1984 ensemble\",\n" + " \"members\": [\n" + " {\n" + " \"name\": \"World Geodetic System 1984 (Transit)\"\n" + " },\n" + " {\n" + " \"name\": \"World Geodetic System 1984 (G730)\"\n" + " },\n" + " {\n" + " \"name\": \"World Geodetic System 1984 (G873)\"\n" + " },\n" + " {\n" + " \"name\": \"World Geodetic System 1984 (G1150)\"\n" + " },\n" + " {\n" + " \"name\": \"World Geodetic System 1984 (G1674)\"\n" + " },\n" + " {\n" + " \"name\": \"World Geodetic System 1984 (G1762)\"\n" + " }\n" + " ],\n" + " \"ellipsoid\": {\n" + " \"name\": \"WGS 84\",\n" + " \"semi_major_axis\": 6378137,\n" + " \"inverse_flattening\": 298.257223563\n" + " },\n" + " \"accuracy\": \"2.0\"\n" + " },\n" + " \"coordinate_system\": {\n" + " \"subtype\": \"Cartesian\",\n" + " \"axis\": [\n" + " {\n" + " \"name\": \"Geocentric X\",\n" + " \"abbreviation\": \"X\",\n" + " \"direction\": \"geocentricX\",\n" + " \"unit\": \"metre\"\n" + " },\n" + " {\n" + " \"name\": \"Geocentric Y\",\n" + " \"abbreviation\": \"Y\",\n" + " \"direction\": \"geocentricY\",\n" + " \"unit\": \"metre\"\n" + " },\n" + " {\n" + " \"name\": \"Geocentric Z\",\n" + " \"abbreviation\": \"Z\",\n" + " \"direction\": \"geocentricZ\",\n" + " \"unit\": \"metre\"\n" + " }\n" + " ]\n" + " },\n" + " \"id\": {\n" + " \"authority\": \"EPSG\",\n" + " \"code\": 4978\n" + " }\n" + " },\n" + " \"conversion\": {\n" + " \"name\": \"EPSG topocentric example B\",\n" + " \"method\": {\n" + " \"name\": \"Geocentric/topocentric conversions\",\n" + " \"id\": {\n" + " \"authority\": \"EPSG\",\n" + " \"code\": 9836\n" + " }\n" + " },\n" + " \"parameters\": [\n" + " {\n" + " \"name\": \"Geocentric X of topocentric origin\",\n" + " \"value\": 3771793.97,\n" + " \"unit\": \"metre\",\n" + " \"id\": {\n" + " \"authority\": \"EPSG\",\n" + " \"code\": 8837\n" + " }\n" + " },\n" + " {\n" + " \"name\": \"Geocentric Y of topocentric origin\",\n" + " \"value\": 140253.34,\n" + " \"unit\": \"metre\",\n" + " \"id\": {\n" + " \"authority\": \"EPSG\",\n" + " \"code\": 8838\n" + " }\n" + " },\n" + " {\n" + " \"name\": \"Geocentric Z of topocentric origin\",\n" + " \"value\": 5124304.35,\n" + " \"unit\": \"metre\",\n" + " \"id\": {\n" + " \"authority\": \"EPSG\",\n" + " \"code\": 8839\n" + " }\n" + " }\n" + " ]\n" + " },\n" + " \"coordinate_system\": {\n" + " \"subtype\": \"Cartesian\",\n" + " \"axis\": [\n" + " {\n" + " \"name\": \"Topocentric East\",\n" + " \"abbreviation\": \"U\",\n" + " \"direction\": \"east\",\n" + " \"unit\": \"metre\"\n" + " },\n" + " {\n" + " \"name\": \"Topocentric North\",\n" + " \"abbreviation\": \"V\",\n" + " \"direction\": \"north\",\n" + " \"unit\": \"metre\"\n" + " },\n" + " {\n" + " \"name\": \"Topocentric height\",\n" + " \"abbreviation\": \"W\",\n" + " \"direction\": \"up\",\n" + " \"unit\": \"metre\"\n" + " }\n" + " ]\n" + " },\n" + " \"scope\": \"Example only (fictitious).\",\n" + " \"area\": \"Description of the extent of the CRS.\",\n" + " \"bbox\": {\n" + " \"south_latitude\": -90,\n" + " \"west_longitude\": -180,\n" + " \"north_latitude\": 90,\n" + " \"east_longitude\": 180\n" + " },\n" + " \"id\": {\n" + " \"authority\": \"EPSG\",\n" + " \"code\": 5820\n" + " }\n" + "}"; + auto obj = createFromUserInput(json, nullptr); + auto pcrs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(pcrs != nullptr); + EXPECT_TRUE(pcrs->baseCRS()->isGeocentric()); + EXPECT_EQ(pcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), + json); +} // --------------------------------------------------------------------------- diff -Nru proj-7.2.0/test/unit/test_operation.cpp proj-7.2.1/test/unit/test_operation.cpp --- proj-7.2.0/test/unit/test_operation.cpp 2020-10-26 09:32:20.000000000 +0000 +++ proj-7.2.1/test/unit/test_operation.cpp 2020-12-26 18:57:21.000000000 +0000 @@ -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,5950 +4283,210 @@ // --------------------------------------------------------------------------- -TEST(operation, geogCRS_to_geogCRS) { +TEST(operation, mercator_variant_A_to_variant_B) { + auto projCRS = ProjectedCRS::create( + PropertyMap(), GeographicCRS::EPSG_4326, + Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), + Scale(0.9), Length(3), Length(4)), + CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); - 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"); -} + auto conv = projCRS->derivingConversion(); + auto sameConv = + conv->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_A); + ASSERT_TRUE(sameConv); + EXPECT_TRUE(sameConv->isEquivalentTo(conv.get())); -// --------------------------------------------------------------------------- + auto targetConv = + conv->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); + ASSERT_TRUE(targetConv); -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); + auto lat_1 = targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, UnitOfMeasure::DEGREE); + EXPECT_EQ(lat_1, 25.917499691810534) << lat_1; - // 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"); - } + EXPECT_EQ(targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + UnitOfMeasure::DEGREE), + 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"); - } -} + EXPECT_EQ(targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE), + 3); -// --------------------------------------------------------------------------- + EXPECT_EQ(targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE), + 4); -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); + EXPECT_FALSE( + conv->isEquivalentTo(targetConv.get(), IComparable::Criterion::STRICT)); + EXPECT_TRUE(conv->isEquivalentTo(targetConv.get(), + IComparable::Criterion::EQUIVALENT)); + EXPECT_TRUE(targetConv->isEquivalentTo(conv.get(), + IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- -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, mercator_variant_A_to_variant_B_scale_1) { + auto projCRS = ProjectedCRS::create( + PropertyMap(), GeographicCRS::EPSG_4326, + Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), + Scale(1.0), Length(3), Length(4)), + CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); -// --------------------------------------------------------------------------- + auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( + EPSG_CODE_METHOD_MERCATOR_VARIANT_B); + ASSERT_TRUE(targetConv); -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"); - } + auto lat_1 = targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, UnitOfMeasure::DEGREE); + EXPECT_EQ(lat_1, 0.0) << lat_1; } // --------------------------------------------------------------------------- -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, mercator_variant_A_to_variant_B_no_crs) { + auto targetConv = + Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), + Scale(1.0), Length(3), Length(4)) + ->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); + EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- -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, mercator_variant_A_to_variant_B_invalid_scale) { + auto projCRS = ProjectedCRS::create( + PropertyMap(), GeographicCRS::EPSG_4326, + Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), + Scale(0.0), Length(3), Length(4)), + CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); -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"); + auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( + EPSG_CODE_METHOD_MERCATOR_VARIANT_B); + EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- -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 +static GeographicCRSNNPtr geographicCRSInvalidEccentricity() { + return GeographicCRS::create( + PropertyMap(), + GeodeticReferenceFrame::create( + PropertyMap(), Ellipsoid::createFlattenedSphere( + PropertyMap(), Length(6378137), Scale(0.1)), + optional(), PrimeMeridian::GREENWICH), + EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); } // --------------------------------------------------------------------------- -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); +TEST(operation, mercator_variant_A_to_variant_B_invalid_eccentricity) { + auto projCRS = ProjectedCRS::create( + PropertyMap(), geographicCRSInvalidEccentricity(), + Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), + Scale(1.0), Length(3), Length(4)), + CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); - 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)"); + auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( + EPSG_CODE_METHOD_MERCATOR_VARIANT_B); + EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- -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, mercator_variant_B_to_variant_A) { + auto projCRS = ProjectedCRS::create( + PropertyMap(), GeographicCRS::EPSG_4326, + Conversion::createMercatorVariantB(PropertyMap(), + Angle(25.917499691810534), Angle(1), + Length(3), Length(4)), + CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); + auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( + EPSG_CODE_METHOD_MERCATOR_VARIANT_A); + ASSERT_TRUE(targetConv); -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); + EXPECT_EQ(targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + UnitOfMeasure::DEGREE), + 0); - auto list = CoordinateOperationFactory::create()->createOperations( - authFactory->createCoordinateReferenceSystem("4240"), // Indian 1975 - authFactory->createCoordinateReferenceSystem("4326"), ctxt); - ASSERT_EQ(list.size(), 3U); + EXPECT_EQ(targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + UnitOfMeasure::DEGREE), + 1); - // Indian 1975 to WGS 84 (4), 3.0 m, Thailand - onshore - EXPECT_EQ(list[0]->getEPSGCode(), 1812); + auto k_0 = targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + UnitOfMeasure::SCALE_UNITY); + EXPECT_EQ(k_0, 0.9) << k_0; - // 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); + EXPECT_EQ(targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE), + 3); - // Indian 1975 to WGS 84 (3), 1.0 m, Thailand - Bongkot field - EXPECT_EQ(list[2]->getEPSGCode(), 1537); + EXPECT_EQ(targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE), + 4); } // --------------------------------------------------------------------------- -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, mercator_variant_B_to_variant_A_invalid_std1) { + auto projCRS = ProjectedCRS::create( + PropertyMap(), GeographicCRS::EPSG_4326, + Conversion::createMercatorVariantB(PropertyMap(), Angle(100), Angle(1), + Length(3), Length(4)), + CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); + auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( + EPSG_CODE_METHOD_MERCATOR_VARIANT_A); + EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- -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, mercator_variant_B_to_variant_A_invalid_eccentricity) { + auto projCRS = ProjectedCRS::create( + PropertyMap(), geographicCRSInvalidEccentricity(), + Conversion::createMercatorVariantB(PropertyMap(), Angle(0), Angle(1), + Length(3), Length(4)), + CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); + auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( + EPSG_CODE_METHOD_MERCATOR_VARIANT_A); + EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- -TEST(operation, geogCRS_to_geogCRS_context_helmert_geog3D_to_geocentirc) { - auto authFactory = - AuthorityFactory::create(DatabaseContext::create(), "EPSG"); - auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0); +TEST(operation, lcc2sp_to_lcc1sp) { + // equivalent to EPSG:2154 + auto projCRS = ProjectedCRS::create( + PropertyMap(), GeographicCRS::EPSG_4269, // something using GRS80 + Conversion::createLambertConicConformal_2SP( + PropertyMap(), Angle(46.5), Angle(3), Angle(49), Angle(44), + Length(700000), Length(6600000)), + CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); - auto list = CoordinateOperationFactory::create()->createOperations( - // GDA94 3D - authFactory->createCoordinateReferenceSystem("4939"), - // GDA2020 geocentric - authFactory->createCoordinateReferenceSystem("7842"), ctxt); - ASSERT_EQ(list.size(), 1U); + auto conv = projCRS->derivingConversion(); + auto targetConv = conv->convertToOtherMethod( + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); + ASSERT_TRUE(targetConv); - // 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"); -} + { + auto lat_0 = targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + UnitOfMeasure::DEGREE); + EXPECT_NEAR(lat_0, 46.519430223986866, 1e-12) << lat_0; -// --------------------------------------------------------------------------- + auto lon_0 = targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + UnitOfMeasure::DEGREE); + EXPECT_NEAR(lon_0, 3.0, 1e-15) << lon_0; -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(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(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(), 2U); - 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(), 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=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(), 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=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(), 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 +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(), - PrimeMeridian::GREENWICH), - EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); - auto dest = GeographicCRS::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, "B"), - GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84, - optional(), - 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(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(sourceCRS_obj); - ASSERT_TRUE(sourceCRS != nullptr); - - auto targetCRS_obj = PROJStringParser() - .attachDatabaseContext(dbContext) - .setUsePROJ4InitRules(true) - .createFromPROJString("+init=IGNF:RGF93G"); - auto targetCRS = nn_dynamic_pointer_cast(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(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(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(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(srcCRS_obj); - ASSERT_TRUE(srcCRS != nullptr); - - auto dstCRS_obj = PROJStringParser().createFromPROJString( - "+proj=longlat +datum=WGS84 +type=crs"); - auto dstCRS = nn_dynamic_pointer_cast(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(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(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(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(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(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(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(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{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{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{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{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{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(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(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(objSrc); - ASSERT_TRUE(src != nullptr); - - auto objDest = PROJStringParser().createFromPROJString( - "+proj=latlong +datum=NAD83 +type=crs"); - auto dest = nn_dynamic_pointer_cast(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(objSrc); - ASSERT_TRUE(src != nullptr); - - auto objDest = PROJStringParser().createFromPROJString( - "+proj=latlong +datum=NAD83 +type=crs"); - auto dest = nn_dynamic_pointer_cast(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{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{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{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(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{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{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(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{1, 2, 3, 4, 5, 6, 7}); - auto boundCRS2 = BoundCRS::createFromTOWGS84( - utm32, std::vector{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{1, 2, 3, 4, 5, 6, 7}); - auto boundCRS2 = BoundCRS::createFromTOWGS84( - GeographicCRS::EPSG_4269, std::vector{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{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())); - 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(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(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(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(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(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(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(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{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()); - 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(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()); - 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()); - 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(), PrimeMeridian::GREENWICH), - EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); - auto dest = GeographicCRS::create( - PropertyMap(), GeodeticReferenceFrame::create( - PropertyMap(), Ellipsoid::WGS84, - optional(), 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{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(), PrimeMeridian::GREENWICH), - EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); - auto horizBoundCRS = BoundCRS::createFromTOWGS84( - geogCRS, std::vector{1, 2, 3, 4, 5, 6, 7}); - auto compound = CompoundCRS::create( - PropertyMap(), - std::vector{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{1, 2, 3, 4, 5, 6, 7}); - auto compound = CompoundCRS::create( - PropertyMap(), - std::vector{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{1, 2, 3, 4, 5, 6, 7}); - auto compound = CompoundCRS::create( - PropertyMap(), - std::vector{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(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(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(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(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(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(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(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(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(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(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(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(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(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{createUTM31_WGS84(), createVerticalCRS()}); - auto compound2 = CompoundCRS::create( - PropertyMap(), - std::vector{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()); - - auto compound1 = CompoundCRS::create( - PropertyMap(), - std::vector{ - 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{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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(objSrc); - ASSERT_TRUE(srcCrs != nullptr); - // ETRS89 + EGM96 - auto objDest = createFromUserInput("EPSG:4258+5773", dbContext); - auto destCrs = nn_dynamic_pointer_cast(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(objSrc); - ASSERT_TRUE(srcCrs != nullptr); - // ETRS89 + Belfast height - auto objDest = createFromUserInput("EPSG:4258+5732", dbContext); - auto destCrs = nn_dynamic_pointer_cast(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(objSrc); - ASSERT_TRUE(srcCrs != nullptr); - // WGS84 + Belfast height - auto objDest = createFromUserInput("EPSG:4326+5732", dbContext); - auto destCrs = nn_dynamic_pointer_cast(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(vertcrs_m_obj); - ASSERT_TRUE(vertcrs_m != nullptr); - - auto vertcrs_ft_obj = PROJStringParser().createFromPROJString("+vunits=ft"); - auto vertcrs_ft = nn_dynamic_pointer_cast(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(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(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(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(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(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(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(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(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"); - } - - // 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(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(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(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(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(), 141U); - - 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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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()); - 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{}), - std::vector{}, - std::vector{}); - 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{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(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(objSrc); - ASSERT_TRUE(src != nullptr); - - auto objDst = PROJStringParser().createFromPROJString( - "+proj=longlat +datum=WGS84 +over +type=crs"); - auto dst = nn_dynamic_pointer_cast(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(objSrc); - ASSERT_TRUE(src != nullptr); - - auto objDst = PROJStringParser().createFromPROJString( - "+proj=longlat +datum=WGS84 +over +type=crs"); - auto dst = nn_dynamic_pointer_cast(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(objSrc); - ASSERT_TRUE(src != nullptr); - - auto objDst = PROJStringParser().createFromPROJString( - "+proj=longlat +datum=WGS84 +over +type=crs"); - auto dst = nn_dynamic_pointer_cast(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(objSrc); - ASSERT_TRUE(src != nullptr); - - auto objDst = PROJStringParser().createFromPROJString( - "+proj=longlat +datum=WGS84 +over +type=crs"); - auto dst = nn_dynamic_pointer_cast(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(objSrc); - ASSERT_TRUE(src != nullptr); - - auto objDst = PROJStringParser().createFromPROJString( - "+proj=longlat +datum=WGS84 +over +type=crs"); - auto dst = nn_dynamic_pointer_cast(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(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(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(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(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(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(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, mercator_variant_A_to_variant_B) { - auto projCRS = ProjectedCRS::create( - PropertyMap(), GeographicCRS::EPSG_4326, - Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), - Scale(0.9), Length(3), Length(4)), - CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); - - auto conv = projCRS->derivingConversion(); - auto sameConv = - conv->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_A); - ASSERT_TRUE(sameConv); - EXPECT_TRUE(sameConv->isEquivalentTo(conv.get())); - - auto targetConv = - conv->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); - ASSERT_TRUE(targetConv); - - auto lat_1 = targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, UnitOfMeasure::DEGREE); - EXPECT_EQ(lat_1, 25.917499691810534) << lat_1; - - EXPECT_EQ(targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - UnitOfMeasure::DEGREE), - 1); - - EXPECT_EQ(targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE), - 3); - - EXPECT_EQ(targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE), - 4); - - EXPECT_FALSE( - conv->isEquivalentTo(targetConv.get(), IComparable::Criterion::STRICT)); - EXPECT_TRUE(conv->isEquivalentTo(targetConv.get(), - IComparable::Criterion::EQUIVALENT)); - EXPECT_TRUE(targetConv->isEquivalentTo(conv.get(), - IComparable::Criterion::EQUIVALENT)); -} - -// --------------------------------------------------------------------------- - -TEST(operation, mercator_variant_A_to_variant_B_scale_1) { - auto projCRS = ProjectedCRS::create( - PropertyMap(), GeographicCRS::EPSG_4326, - Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), - Scale(1.0), Length(3), Length(4)), - CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); - - auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( - EPSG_CODE_METHOD_MERCATOR_VARIANT_B); - ASSERT_TRUE(targetConv); - - auto lat_1 = targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, UnitOfMeasure::DEGREE); - EXPECT_EQ(lat_1, 0.0) << lat_1; -} - -// --------------------------------------------------------------------------- - -TEST(operation, mercator_variant_A_to_variant_B_no_crs) { - auto targetConv = - Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), - Scale(1.0), Length(3), Length(4)) - ->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); - EXPECT_FALSE(targetConv != nullptr); -} - -// --------------------------------------------------------------------------- - -TEST(operation, mercator_variant_A_to_variant_B_invalid_scale) { - auto projCRS = ProjectedCRS::create( - PropertyMap(), GeographicCRS::EPSG_4326, - Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), - Scale(0.0), Length(3), Length(4)), - CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); - - auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( - EPSG_CODE_METHOD_MERCATOR_VARIANT_B); - EXPECT_FALSE(targetConv != nullptr); -} - -// --------------------------------------------------------------------------- - -static GeographicCRSNNPtr geographicCRSInvalidEccentricity() { - return GeographicCRS::create( - PropertyMap(), - GeodeticReferenceFrame::create( - PropertyMap(), Ellipsoid::createFlattenedSphere( - PropertyMap(), Length(6378137), Scale(0.1)), - optional(), PrimeMeridian::GREENWICH), - EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); -} - -// --------------------------------------------------------------------------- - -TEST(operation, mercator_variant_A_to_variant_B_invalid_eccentricity) { - auto projCRS = ProjectedCRS::create( - PropertyMap(), geographicCRSInvalidEccentricity(), - Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), - Scale(1.0), Length(3), Length(4)), - CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); - - auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( - EPSG_CODE_METHOD_MERCATOR_VARIANT_B); - EXPECT_FALSE(targetConv != nullptr); -} - -// --------------------------------------------------------------------------- - -TEST(operation, mercator_variant_B_to_variant_A) { - auto projCRS = ProjectedCRS::create( - PropertyMap(), GeographicCRS::EPSG_4326, - Conversion::createMercatorVariantB(PropertyMap(), - Angle(25.917499691810534), Angle(1), - Length(3), Length(4)), - CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); - auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( - EPSG_CODE_METHOD_MERCATOR_VARIANT_A); - ASSERT_TRUE(targetConv); - - EXPECT_EQ(targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - UnitOfMeasure::DEGREE), - 0); - - EXPECT_EQ(targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - UnitOfMeasure::DEGREE), - 1); - - auto k_0 = targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - UnitOfMeasure::SCALE_UNITY); - EXPECT_EQ(k_0, 0.9) << k_0; - - EXPECT_EQ(targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE), - 3); - - EXPECT_EQ(targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE), - 4); -} - -// --------------------------------------------------------------------------- - -TEST(operation, mercator_variant_B_to_variant_A_invalid_std1) { - auto projCRS = ProjectedCRS::create( - PropertyMap(), GeographicCRS::EPSG_4326, - Conversion::createMercatorVariantB(PropertyMap(), Angle(100), Angle(1), - Length(3), Length(4)), - CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); - auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( - EPSG_CODE_METHOD_MERCATOR_VARIANT_A); - EXPECT_FALSE(targetConv != nullptr); -} - -// --------------------------------------------------------------------------- - -TEST(operation, mercator_variant_B_to_variant_A_invalid_eccentricity) { - auto projCRS = ProjectedCRS::create( - PropertyMap(), geographicCRSInvalidEccentricity(), - Conversion::createMercatorVariantB(PropertyMap(), Angle(0), Angle(1), - Length(3), Length(4)), - CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); - auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( - EPSG_CODE_METHOD_MERCATOR_VARIANT_A); - EXPECT_FALSE(targetConv != nullptr); -} - -// --------------------------------------------------------------------------- - -TEST(operation, lcc2sp_to_lcc1sp) { - // equivalent to EPSG:2154 - auto projCRS = ProjectedCRS::create( - PropertyMap(), GeographicCRS::EPSG_4269, // something using GRS80 - Conversion::createLambertConicConformal_2SP( - PropertyMap(), Angle(46.5), Angle(3), Angle(49), Angle(44), - Length(700000), Length(6600000)), - CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); - - auto conv = projCRS->derivingConversion(); - auto targetConv = conv->convertToOtherMethod( - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); - ASSERT_TRUE(targetConv); - - { - auto lat_0 = targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - UnitOfMeasure::DEGREE); - EXPECT_NEAR(lat_0, 46.519430223986866, 1e-12) << lat_0; - - auto lon_0 = targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - UnitOfMeasure::DEGREE); - EXPECT_NEAR(lon_0, 3.0, 1e-15) << lon_0; - - auto k_0 = targetConv->parameterValueNumeric( - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, - UnitOfMeasure::SCALE_UNITY); - EXPECT_NEAR(k_0, 0.9990510286374692, 1e-15) << k_0; + auto k_0 = targetConv->parameterValueNumeric( + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + UnitOfMeasure::SCALE_UNITY); + EXPECT_NEAR(k_0, 0.9990510286374692, 1e-15) << k_0; auto x_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE); @@ -11096,7 +5354,7 @@ src, authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 3D ctxt); - ASSERT_EQ(list.size(), 2U); + ASSERT_EQ(list.size(), 3U); auto op = list[1]; auto opNormalized = op->normalizeForVisualization(); EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get())); diff -Nru proj-7.2.0/test/unit/test_operationfactory.cpp proj-7.2.1/test/unit/test_operationfactory.cpp --- proj-7.2.0/test/unit/test_operationfactory.cpp 1970-01-01 00:00:00.000000000 +0000 +++ proj-7.2.1/test/unit/test_operationfactory.cpp 2020-12-21 16:29:10.000000000 +0000 @@ -0,0 +1,5954 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: Test ISO19111:2019 implementation + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault + * + * 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 +#include + +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(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(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(), + PrimeMeridian::GREENWICH), + EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); + auto dest = GeographicCRS::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, "B"), + GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84, + optional(), + 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(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(sourceCRS_obj); + ASSERT_TRUE(sourceCRS != nullptr); + + auto targetCRS_obj = PROJStringParser() + .attachDatabaseContext(dbContext) + .setUsePROJ4InitRules(true) + .createFromPROJString("+init=IGNF:RGF93G"); + auto targetCRS = nn_dynamic_pointer_cast(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(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(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(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(srcCRS_obj); + ASSERT_TRUE(srcCRS != nullptr); + + auto dstCRS_obj = PROJStringParser().createFromPROJString( + "+proj=longlat +datum=WGS84 +type=crs"); + auto dstCRS = nn_dynamic_pointer_cast(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(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(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(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(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(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(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(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{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{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{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{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{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(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(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(objSrc); + ASSERT_TRUE(src != nullptr); + + auto objDest = PROJStringParser().createFromPROJString( + "+proj=latlong +datum=NAD83 +type=crs"); + auto dest = nn_dynamic_pointer_cast(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(objSrc); + ASSERT_TRUE(src != nullptr); + + auto objDest = PROJStringParser().createFromPROJString( + "+proj=latlong +datum=NAD83 +type=crs"); + auto dest = nn_dynamic_pointer_cast(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{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{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{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(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{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{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(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{1, 2, 3, 4, 5, 6, 7}); + auto boundCRS2 = BoundCRS::createFromTOWGS84( + utm32, std::vector{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{1, 2, 3, 4, 5, 6, 7}); + auto boundCRS2 = BoundCRS::createFromTOWGS84( + GeographicCRS::EPSG_4269, std::vector{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{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())); + 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(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(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( + PROJStringParser().createFromPROJString(projStr)); + if (crsFromProj == nullptr) { + throw "crsFromProj == nullptr"; + } + auto crsFromWkt = nn_dynamic_pointer_cast( + 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(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(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(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(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(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{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()); + 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(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()); + 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()); + 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(), PrimeMeridian::GREENWICH), + EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); + auto dest = GeographicCRS::create( + PropertyMap(), GeodeticReferenceFrame::create( + PropertyMap(), Ellipsoid::WGS84, + optional(), 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{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(), PrimeMeridian::GREENWICH), + EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); + auto horizBoundCRS = BoundCRS::createFromTOWGS84( + geogCRS, std::vector{1, 2, 3, 4, 5, 6, 7}); + auto compound = CompoundCRS::create( + PropertyMap(), + std::vector{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{1, 2, 3, 4, 5, 6, 7}); + auto compound = CompoundCRS::create( + PropertyMap(), + std::vector{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{1, 2, 3, 4, 5, 6, 7}); + auto compound = CompoundCRS::create( + PropertyMap(), + std::vector{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(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(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(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(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(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(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(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(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(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(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(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(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(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{createUTM31_WGS84(), createVerticalCRS()}); + auto compound2 = CompoundCRS::create( + PropertyMap(), + std::vector{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()); + + auto compound1 = CompoundCRS::create( + PropertyMap(), + std::vector{ + 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{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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(objSrc); + ASSERT_TRUE(srcCrs != nullptr); + // ETRS89 + EGM96 + auto objDest = createFromUserInput("EPSG:4258+5773", dbContext); + auto destCrs = nn_dynamic_pointer_cast(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(objSrc); + ASSERT_TRUE(srcCrs != nullptr); + // ETRS89 + Belfast height + auto objDest = createFromUserInput("EPSG:4258+5732", dbContext); + auto destCrs = nn_dynamic_pointer_cast(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(objSrc); + ASSERT_TRUE(srcCrs != nullptr); + // WGS84 + Belfast height + auto objDest = createFromUserInput("EPSG:4326+5732", dbContext); + auto destCrs = nn_dynamic_pointer_cast(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(vertcrs_m_obj); + ASSERT_TRUE(vertcrs_m != nullptr); + + auto vertcrs_ft_obj = PROJStringParser().createFromPROJString("+vunits=ft"); + auto vertcrs_ft = nn_dynamic_pointer_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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()); + 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{}), + std::vector{}, + std::vector{}); + 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{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(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(objSrc); + ASSERT_TRUE(src != nullptr); + + auto objDst = PROJStringParser().createFromPROJString( + "+proj=longlat +datum=WGS84 +over +type=crs"); + auto dst = nn_dynamic_pointer_cast(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(objSrc); + ASSERT_TRUE(src != nullptr); + + auto objDst = PROJStringParser().createFromPROJString( + "+proj=longlat +datum=WGS84 +over +type=crs"); + auto dst = nn_dynamic_pointer_cast(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(objSrc); + ASSERT_TRUE(src != nullptr); + + auto objDst = PROJStringParser().createFromPROJString( + "+proj=longlat +datum=WGS84 +over +type=crs"); + auto dst = nn_dynamic_pointer_cast(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(objSrc); + ASSERT_TRUE(src != nullptr); + + auto objDst = PROJStringParser().createFromPROJString( + "+proj=longlat +datum=WGS84 +over +type=crs"); + auto dst = nn_dynamic_pointer_cast(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(objSrc); + ASSERT_TRUE(src != nullptr); + + auto objDst = PROJStringParser().createFromPROJString( + "+proj=longlat +datum=WGS84 +over +type=crs"); + auto dst = nn_dynamic_pointer_cast(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(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(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(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(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(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(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(objSrc); + ASSERT_TRUE(src != nullptr); + auto objDst = PROJStringParser().createFromPROJString( + "+proj=longlat +datum=WGS84 +type=crs"); + auto dst = nn_dynamic_pointer_cast(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(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(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); + } +}