From e6de172371ea203f6393d745641d66c82b5b13e2 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 19 Dec 2018 12:25:33 +0100 Subject: cpp conversion: move source files in apps/ iso19111/ conversions/ projections/ transformations/ tests/ subdirectories --- src/Makefile.am | 198 +- src/PJ_aea.cpp | 224 - src/PJ_aeqd.cpp | 327 - src/PJ_affine.cpp | 250 - src/PJ_airy.cpp | 155 - src/PJ_aitoff.cpp | 201 - src/PJ_august.cpp | 34 - src/PJ_axisswap.cpp | 302 - src/PJ_bacon.cpp | 81 - src/PJ_bertin1953.cpp | 96 - src/PJ_bipc.cpp | 176 - src/PJ_boggs.cpp | 43 - src/PJ_bonne.cpp | 136 - src/PJ_calcofi.cpp | 163 - src/PJ_cart.cpp | 219 - src/PJ_cass.cpp | 123 - src/PJ_cc.cpp | 41 - src/PJ_ccon.cpp | 109 - src/PJ_cea.cpp | 103 - src/PJ_chamb.cpp | 141 - src/PJ_collg.cpp | 53 - src/PJ_comill.cpp | 84 - src/PJ_crast.cpp | 40 - src/PJ_deformation.cpp | 325 - src/PJ_denoy.cpp | 32 - src/PJ_eck1.cpp | 41 - src/PJ_eck2.cpp | 56 - src/PJ_eck3.cpp | 112 - src/PJ_eck4.cpp | 63 - src/PJ_eck5.cpp | 40 - src/PJ_eqc.cpp | 54 - src/PJ_eqdc.cpp | 122 - src/PJ_eqearth.cpp | 164 - src/PJ_fahey.cpp | 41 - src/PJ_fouc_s.cpp | 72 - src/PJ_gall.cpp | 44 - src/PJ_geoc.cpp | 59 - src/PJ_geos.cpp | 238 - src/PJ_gins8.cpp | 33 - src/PJ_gn_sinu.cpp | 189 - src/PJ_gnom.cpp | 147 - src/PJ_goode.cpp | 84 - src/PJ_gstmerc.cpp | 74 - src/PJ_hammer.cpp | 77 - src/PJ_hatano.cpp | 83 - src/PJ_healpix.cpp | 674 -- src/PJ_helmert.cpp | 755 -- src/PJ_hgridshift.cpp | 133 - src/PJ_horner.cpp | 513 -- src/PJ_igh.cpp | 227 - src/PJ_imw_p.cpp | 217 - src/PJ_isea.cpp | 1098 --- src/PJ_krovak.cpp | 222 - src/PJ_labrd.cpp | 132 - src/PJ_laea.cpp | 300 - src/PJ_lagrng.cpp | 98 - src/PJ_larr.cpp | 28 - src/PJ_lask.cpp | 39 - src/PJ_latlong.cpp | 124 - src/PJ_lcc.cpp | 130 - src/PJ_lcca.cpp | 164 - src/PJ_loxim.cpp | 77 - src/PJ_lsat.cpp | 212 - src/PJ_mbt_fps.cpp | 57 - src/PJ_mbtfpp.cpp | 65 - src/PJ_mbtfpq.cpp | 74 - src/PJ_merc.cpp | 101 - src/PJ_mill.cpp | 37 - src/PJ_misrsom.cpp | 219 - src/PJ_mod_ster.cpp | 282 - src/PJ_moll.cpp | 112 - src/PJ_molodensky.cpp | 329 - src/PJ_natearth.cpp | 100 - src/PJ_natearth2.cpp | 97 - src/PJ_nell.cpp | 51 - src/PJ_nell_h.cpp | 53 - src/PJ_nocol.cpp | 54 - src/PJ_nsper.cpp | 202 - src/PJ_nzmg.cpp | 123 - src/PJ_ob_tran.cpp | 245 - src/PJ_ocea.cpp | 102 - src/PJ_oea.cpp | 87 - src/PJ_omerc.cpp | 229 - src/PJ_ortho.cpp | 143 - src/PJ_patterson.cpp | 117 - src/PJ_poly.cpp | 171 - src/PJ_putp2.cpp | 61 - src/PJ_putp3.cpp | 67 - src/PJ_putp4p.cpp | 76 - src/PJ_putp5.cpp | 75 - src/PJ_putp6.cpp | 97 - src/PJ_qsc.cpp | 408 -- src/PJ_robin.cpp | 161 - src/PJ_rpoly.cpp | 58 - src/PJ_sch.cpp | 232 - src/PJ_sconics.cpp | 220 - src/PJ_somerc.cpp | 94 - src/PJ_stere.cpp | 320 - src/PJ_sterea.cpp | 117 - src/PJ_sts.cpp | 109 - src/PJ_tcc.cpp | 34 - src/PJ_tcea.cpp | 36 - src/PJ_times.cpp | 79 - src/PJ_tmerc.cpp | 210 - src/PJ_tobmerc.cpp | 51 - src/PJ_tpeqd.cpp | 109 - src/PJ_unitconvert.cpp | 552 -- src/PJ_urm5.cpp | 56 - src/PJ_urmfps.cpp | 76 - src/PJ_vandg.cpp | 106 - src/PJ_vandg2.cpp | 76 - src/PJ_vandg4.cpp | 55 - src/PJ_vgridshift.cpp | 144 - src/PJ_wag2.cpp | 38 - src/PJ_wag3.cpp | 50 - src/PJ_wag7.cpp | 30 - src/PJ_wink1.cpp | 46 - src/PJ_wink2.cpp | 56 - src/apps/cct.cpp | 472 ++ src/apps/cs2cs.cpp | 640 ++ src/apps/emess.cpp | 68 + src/apps/emess.h | 29 + src/apps/gen_cheb.cpp | 106 + src/apps/geod.cpp | 241 + src/apps/geod_interface.cpp | 33 + src/apps/geod_interface.h | 45 + src/apps/geod_set.cpp | 75 + src/apps/gie.cpp | 1458 ++++ src/apps/nad2bin.cpp | 382 + src/apps/optargpm.h | 635 ++ src/apps/p_series.cpp | 42 + src/apps/proj.cpp | 582 ++ src/apps/proj_strtod.cpp | 440 ++ src/apps/proj_strtod.h | 4 + src/apps/projinfo.cpp | 1067 +++ src/bin_cct.cmake | 4 +- src/bin_cs2cs.cmake | 8 +- src/bin_geod.cmake | 9 +- src/bin_geodtest.cmake | 2 +- src/bin_gie.cmake | 6 +- src/bin_nad2bin.cmake | 2 +- src/bin_proj.cmake | 8 +- src/bin_projinfo.cmake | 2 +- src/c_api.cpp | 6540 ----------------- src/cct.cpp | 472 -- src/common.cpp | 1115 --- src/conversions/PJ_axisswap.cpp | 302 + src/conversions/PJ_cart.cpp | 219 + src/conversions/PJ_geoc.cpp | 59 + src/conversions/PJ_unitconvert.cpp | 552 ++ src/conversions/pj_geocent.cpp | 62 + src/coordinateoperation.cpp | 11749 ------------------------------- src/coordinatesystem.cpp | 1279 ---- src/crs.cpp | 4971 ------------- src/cs2cs.cpp | 640 -- src/datum.cpp | 1996 ------ src/emess.cpp | 68 - src/emess.h | 29 - src/factory.cpp | 4973 ------------- src/gen_cheb.cpp | 106 - src/geod.cpp | 241 - src/geod_interface.cpp | 33 - src/geod_interface.h | 45 - src/geod_set.cpp | 75 - src/geodtest.cpp | 1069 --- src/gie.cpp | 1458 ---- src/internal.cpp | 374 - src/io.cpp | 7501 -------------------- src/iso19111/c_api.cpp | 6540 +++++++++++++++++ src/iso19111/common.cpp | 1115 +++ src/iso19111/coordinateoperation.cpp | 11749 +++++++++++++++++++++++++++++++ src/iso19111/coordinatesystem.cpp | 1279 ++++ src/iso19111/crs.cpp | 4971 +++++++++++++ src/iso19111/datum.cpp | 1996 ++++++ src/iso19111/factory.cpp | 4973 +++++++++++++ src/iso19111/internal.cpp | 374 + src/iso19111/io.cpp | 7501 ++++++++++++++++++++ src/iso19111/metadata.cpp | 1285 ++++ src/iso19111/static.cpp | 644 ++ src/iso19111/util.cpp | 689 ++ src/lib_proj.cmake | 383 +- src/metadata.cpp | 1285 ---- src/multistresstest.cpp | 488 -- src/nad2bin.cpp | 382 - src/optargpm.h | 635 -- src/p_series.cpp | 42 - src/pj_geocent.cpp | 62 - src/proj.cpp | 582 -- src/proj_etmerc.cpp | 362 - src/proj_rouss.cpp | 158 - src/proj_strtod.cpp | 440 -- src/proj_strtod.h | 4 - src/projections/PJ_aea.cpp | 224 + src/projections/PJ_aeqd.cpp | 327 + src/projections/PJ_airy.cpp | 155 + src/projections/PJ_aitoff.cpp | 201 + src/projections/PJ_august.cpp | 34 + src/projections/PJ_bacon.cpp | 81 + src/projections/PJ_bertin1953.cpp | 96 + src/projections/PJ_bipc.cpp | 176 + src/projections/PJ_boggs.cpp | 43 + src/projections/PJ_bonne.cpp | 136 + src/projections/PJ_calcofi.cpp | 163 + src/projections/PJ_cass.cpp | 123 + src/projections/PJ_cc.cpp | 41 + src/projections/PJ_ccon.cpp | 109 + src/projections/PJ_cea.cpp | 103 + src/projections/PJ_chamb.cpp | 141 + src/projections/PJ_collg.cpp | 53 + src/projections/PJ_comill.cpp | 84 + src/projections/PJ_crast.cpp | 40 + src/projections/PJ_denoy.cpp | 32 + src/projections/PJ_eck1.cpp | 41 + src/projections/PJ_eck2.cpp | 56 + src/projections/PJ_eck3.cpp | 112 + src/projections/PJ_eck4.cpp | 63 + src/projections/PJ_eck5.cpp | 40 + src/projections/PJ_eqc.cpp | 54 + src/projections/PJ_eqdc.cpp | 122 + src/projections/PJ_eqearth.cpp | 164 + src/projections/PJ_fahey.cpp | 41 + src/projections/PJ_fouc_s.cpp | 72 + src/projections/PJ_gall.cpp | 44 + src/projections/PJ_geos.cpp | 238 + src/projections/PJ_gins8.cpp | 33 + src/projections/PJ_gn_sinu.cpp | 189 + src/projections/PJ_gnom.cpp | 147 + src/projections/PJ_goode.cpp | 84 + src/projections/PJ_gstmerc.cpp | 74 + src/projections/PJ_hammer.cpp | 77 + src/projections/PJ_hatano.cpp | 83 + src/projections/PJ_healpix.cpp | 674 ++ src/projections/PJ_igh.cpp | 227 + src/projections/PJ_imw_p.cpp | 217 + src/projections/PJ_isea.cpp | 1098 +++ src/projections/PJ_krovak.cpp | 222 + src/projections/PJ_labrd.cpp | 132 + src/projections/PJ_laea.cpp | 300 + src/projections/PJ_lagrng.cpp | 98 + src/projections/PJ_larr.cpp | 28 + src/projections/PJ_lask.cpp | 39 + src/projections/PJ_latlong.cpp | 124 + src/projections/PJ_lcc.cpp | 130 + src/projections/PJ_lcca.cpp | 164 + src/projections/PJ_loxim.cpp | 77 + src/projections/PJ_lsat.cpp | 212 + src/projections/PJ_mbt_fps.cpp | 57 + src/projections/PJ_mbtfpp.cpp | 65 + src/projections/PJ_mbtfpq.cpp | 74 + src/projections/PJ_merc.cpp | 101 + src/projections/PJ_mill.cpp | 37 + src/projections/PJ_misrsom.cpp | 219 + src/projections/PJ_mod_ster.cpp | 282 + src/projections/PJ_moll.cpp | 112 + src/projections/PJ_natearth.cpp | 100 + src/projections/PJ_natearth2.cpp | 97 + src/projections/PJ_nell.cpp | 51 + src/projections/PJ_nell_h.cpp | 53 + src/projections/PJ_nocol.cpp | 54 + src/projections/PJ_nsper.cpp | 202 + src/projections/PJ_nzmg.cpp | 123 + src/projections/PJ_ob_tran.cpp | 245 + src/projections/PJ_ocea.cpp | 102 + src/projections/PJ_oea.cpp | 87 + src/projections/PJ_omerc.cpp | 229 + src/projections/PJ_ortho.cpp | 143 + src/projections/PJ_patterson.cpp | 117 + src/projections/PJ_poly.cpp | 171 + src/projections/PJ_putp2.cpp | 61 + src/projections/PJ_putp3.cpp | 67 + src/projections/PJ_putp4p.cpp | 76 + src/projections/PJ_putp5.cpp | 75 + src/projections/PJ_putp6.cpp | 97 + src/projections/PJ_qsc.cpp | 408 ++ src/projections/PJ_robin.cpp | 161 + src/projections/PJ_rpoly.cpp | 58 + src/projections/PJ_sch.cpp | 232 + src/projections/PJ_sconics.cpp | 220 + src/projections/PJ_somerc.cpp | 94 + src/projections/PJ_stere.cpp | 320 + src/projections/PJ_sterea.cpp | 117 + src/projections/PJ_sts.cpp | 109 + src/projections/PJ_tcc.cpp | 34 + src/projections/PJ_tcea.cpp | 36 + src/projections/PJ_times.cpp | 79 + src/projections/PJ_tmerc.cpp | 210 + src/projections/PJ_tobmerc.cpp | 51 + src/projections/PJ_tpeqd.cpp | 109 + src/projections/PJ_urm5.cpp | 56 + src/projections/PJ_urmfps.cpp | 76 + src/projections/PJ_vandg.cpp | 106 + src/projections/PJ_vandg2.cpp | 76 + src/projections/PJ_vandg4.cpp | 55 + src/projections/PJ_wag2.cpp | 38 + src/projections/PJ_wag3.cpp | 50 + src/projections/PJ_wag7.cpp | 30 + src/projections/PJ_wink1.cpp | 46 + src/projections/PJ_wink2.cpp | 56 + src/projections/proj_etmerc.cpp | 362 + src/projections/proj_rouss.cpp | 158 + src/projinfo.cpp | 1067 --- src/static.cpp | 644 -- src/test228.cpp | 86 - src/tests/geodtest.cpp | 1069 +++ src/tests/multistresstest.cpp | 488 ++ src/tests/test228.cpp | 86 + src/transformations/PJ_affine.cpp | 250 + src/transformations/PJ_deformation.cpp | 325 + src/transformations/PJ_helmert.cpp | 755 ++ src/transformations/PJ_hgridshift.cpp | 133 + src/transformations/PJ_horner.cpp | 513 ++ src/transformations/PJ_molodensky.cpp | 329 + src/transformations/PJ_vgridshift.cpp | 144 + src/util.cpp | 689 -- 314 files changed, 69357 insertions(+), 69271 deletions(-) delete mode 100644 src/PJ_aea.cpp delete mode 100644 src/PJ_aeqd.cpp delete mode 100644 src/PJ_affine.cpp delete mode 100644 src/PJ_airy.cpp delete mode 100644 src/PJ_aitoff.cpp delete mode 100644 src/PJ_august.cpp delete mode 100644 src/PJ_axisswap.cpp delete mode 100644 src/PJ_bacon.cpp delete mode 100644 src/PJ_bertin1953.cpp delete mode 100644 src/PJ_bipc.cpp delete mode 100644 src/PJ_boggs.cpp delete mode 100644 src/PJ_bonne.cpp delete mode 100644 src/PJ_calcofi.cpp delete mode 100644 src/PJ_cart.cpp delete mode 100644 src/PJ_cass.cpp delete mode 100644 src/PJ_cc.cpp delete mode 100644 src/PJ_ccon.cpp delete mode 100644 src/PJ_cea.cpp delete mode 100644 src/PJ_chamb.cpp delete mode 100644 src/PJ_collg.cpp delete mode 100644 src/PJ_comill.cpp delete mode 100644 src/PJ_crast.cpp delete mode 100644 src/PJ_deformation.cpp delete mode 100644 src/PJ_denoy.cpp delete mode 100644 src/PJ_eck1.cpp delete mode 100644 src/PJ_eck2.cpp delete mode 100644 src/PJ_eck3.cpp delete mode 100644 src/PJ_eck4.cpp delete mode 100644 src/PJ_eck5.cpp delete mode 100644 src/PJ_eqc.cpp delete mode 100644 src/PJ_eqdc.cpp delete mode 100644 src/PJ_eqearth.cpp delete mode 100644 src/PJ_fahey.cpp delete mode 100644 src/PJ_fouc_s.cpp delete mode 100644 src/PJ_gall.cpp delete mode 100644 src/PJ_geoc.cpp delete mode 100644 src/PJ_geos.cpp delete mode 100644 src/PJ_gins8.cpp delete mode 100644 src/PJ_gn_sinu.cpp delete mode 100644 src/PJ_gnom.cpp delete mode 100644 src/PJ_goode.cpp delete mode 100644 src/PJ_gstmerc.cpp delete mode 100644 src/PJ_hammer.cpp delete mode 100644 src/PJ_hatano.cpp delete mode 100644 src/PJ_healpix.cpp delete mode 100644 src/PJ_helmert.cpp delete mode 100644 src/PJ_hgridshift.cpp delete mode 100644 src/PJ_horner.cpp delete mode 100644 src/PJ_igh.cpp delete mode 100644 src/PJ_imw_p.cpp delete mode 100644 src/PJ_isea.cpp delete mode 100644 src/PJ_krovak.cpp delete mode 100644 src/PJ_labrd.cpp delete mode 100644 src/PJ_laea.cpp delete mode 100644 src/PJ_lagrng.cpp delete mode 100644 src/PJ_larr.cpp delete mode 100644 src/PJ_lask.cpp delete mode 100644 src/PJ_latlong.cpp delete mode 100644 src/PJ_lcc.cpp delete mode 100644 src/PJ_lcca.cpp delete mode 100644 src/PJ_loxim.cpp delete mode 100644 src/PJ_lsat.cpp delete mode 100644 src/PJ_mbt_fps.cpp delete mode 100644 src/PJ_mbtfpp.cpp delete mode 100644 src/PJ_mbtfpq.cpp delete mode 100644 src/PJ_merc.cpp delete mode 100644 src/PJ_mill.cpp delete mode 100644 src/PJ_misrsom.cpp delete mode 100644 src/PJ_mod_ster.cpp delete mode 100644 src/PJ_moll.cpp delete mode 100644 src/PJ_molodensky.cpp delete mode 100644 src/PJ_natearth.cpp delete mode 100644 src/PJ_natearth2.cpp delete mode 100644 src/PJ_nell.cpp delete mode 100644 src/PJ_nell_h.cpp delete mode 100644 src/PJ_nocol.cpp delete mode 100644 src/PJ_nsper.cpp delete mode 100644 src/PJ_nzmg.cpp delete mode 100644 src/PJ_ob_tran.cpp delete mode 100644 src/PJ_ocea.cpp delete mode 100644 src/PJ_oea.cpp delete mode 100644 src/PJ_omerc.cpp delete mode 100644 src/PJ_ortho.cpp delete mode 100644 src/PJ_patterson.cpp delete mode 100644 src/PJ_poly.cpp delete mode 100644 src/PJ_putp2.cpp delete mode 100644 src/PJ_putp3.cpp delete mode 100644 src/PJ_putp4p.cpp delete mode 100644 src/PJ_putp5.cpp delete mode 100644 src/PJ_putp6.cpp delete mode 100644 src/PJ_qsc.cpp delete mode 100644 src/PJ_robin.cpp delete mode 100644 src/PJ_rpoly.cpp delete mode 100644 src/PJ_sch.cpp delete mode 100644 src/PJ_sconics.cpp delete mode 100644 src/PJ_somerc.cpp delete mode 100644 src/PJ_stere.cpp delete mode 100644 src/PJ_sterea.cpp delete mode 100644 src/PJ_sts.cpp delete mode 100644 src/PJ_tcc.cpp delete mode 100644 src/PJ_tcea.cpp delete mode 100644 src/PJ_times.cpp delete mode 100644 src/PJ_tmerc.cpp delete mode 100644 src/PJ_tobmerc.cpp delete mode 100644 src/PJ_tpeqd.cpp delete mode 100644 src/PJ_unitconvert.cpp delete mode 100644 src/PJ_urm5.cpp delete mode 100644 src/PJ_urmfps.cpp delete mode 100644 src/PJ_vandg.cpp delete mode 100644 src/PJ_vandg2.cpp delete mode 100644 src/PJ_vandg4.cpp delete mode 100644 src/PJ_vgridshift.cpp delete mode 100644 src/PJ_wag2.cpp delete mode 100644 src/PJ_wag3.cpp delete mode 100644 src/PJ_wag7.cpp delete mode 100644 src/PJ_wink1.cpp delete mode 100644 src/PJ_wink2.cpp create mode 100644 src/apps/cct.cpp create mode 100644 src/apps/cs2cs.cpp create mode 100644 src/apps/emess.cpp create mode 100644 src/apps/emess.h create mode 100644 src/apps/gen_cheb.cpp create mode 100644 src/apps/geod.cpp create mode 100644 src/apps/geod_interface.cpp create mode 100644 src/apps/geod_interface.h create mode 100644 src/apps/geod_set.cpp create mode 100644 src/apps/gie.cpp create mode 100644 src/apps/nad2bin.cpp create mode 100644 src/apps/optargpm.h create mode 100644 src/apps/p_series.cpp create mode 100644 src/apps/proj.cpp create mode 100644 src/apps/proj_strtod.cpp create mode 100644 src/apps/proj_strtod.h create mode 100644 src/apps/projinfo.cpp delete mode 100644 src/c_api.cpp delete mode 100644 src/cct.cpp delete mode 100644 src/common.cpp create mode 100644 src/conversions/PJ_axisswap.cpp create mode 100644 src/conversions/PJ_cart.cpp create mode 100644 src/conversions/PJ_geoc.cpp create mode 100644 src/conversions/PJ_unitconvert.cpp create mode 100644 src/conversions/pj_geocent.cpp delete mode 100644 src/coordinateoperation.cpp delete mode 100644 src/coordinatesystem.cpp delete mode 100644 src/crs.cpp delete mode 100644 src/cs2cs.cpp delete mode 100644 src/datum.cpp delete mode 100644 src/emess.cpp delete mode 100644 src/emess.h delete mode 100644 src/factory.cpp delete mode 100644 src/gen_cheb.cpp delete mode 100644 src/geod.cpp delete mode 100644 src/geod_interface.cpp delete mode 100644 src/geod_interface.h delete mode 100644 src/geod_set.cpp delete mode 100644 src/geodtest.cpp delete mode 100644 src/gie.cpp delete mode 100644 src/internal.cpp delete mode 100644 src/io.cpp create mode 100644 src/iso19111/c_api.cpp create mode 100644 src/iso19111/common.cpp create mode 100644 src/iso19111/coordinateoperation.cpp create mode 100644 src/iso19111/coordinatesystem.cpp create mode 100644 src/iso19111/crs.cpp create mode 100644 src/iso19111/datum.cpp create mode 100644 src/iso19111/factory.cpp create mode 100644 src/iso19111/internal.cpp create mode 100644 src/iso19111/io.cpp create mode 100644 src/iso19111/metadata.cpp create mode 100644 src/iso19111/static.cpp create mode 100644 src/iso19111/util.cpp delete mode 100644 src/metadata.cpp delete mode 100644 src/multistresstest.cpp delete mode 100644 src/nad2bin.cpp delete mode 100644 src/optargpm.h delete mode 100644 src/p_series.cpp delete mode 100644 src/pj_geocent.cpp delete mode 100644 src/proj.cpp delete mode 100644 src/proj_etmerc.cpp delete mode 100644 src/proj_rouss.cpp delete mode 100644 src/proj_strtod.cpp delete mode 100644 src/proj_strtod.h create mode 100644 src/projections/PJ_aea.cpp create mode 100644 src/projections/PJ_aeqd.cpp create mode 100644 src/projections/PJ_airy.cpp create mode 100644 src/projections/PJ_aitoff.cpp create mode 100644 src/projections/PJ_august.cpp create mode 100644 src/projections/PJ_bacon.cpp create mode 100644 src/projections/PJ_bertin1953.cpp create mode 100644 src/projections/PJ_bipc.cpp create mode 100644 src/projections/PJ_boggs.cpp create mode 100644 src/projections/PJ_bonne.cpp create mode 100644 src/projections/PJ_calcofi.cpp create mode 100644 src/projections/PJ_cass.cpp create mode 100644 src/projections/PJ_cc.cpp create mode 100644 src/projections/PJ_ccon.cpp create mode 100644 src/projections/PJ_cea.cpp create mode 100644 src/projections/PJ_chamb.cpp create mode 100644 src/projections/PJ_collg.cpp create mode 100644 src/projections/PJ_comill.cpp create mode 100644 src/projections/PJ_crast.cpp create mode 100644 src/projections/PJ_denoy.cpp create mode 100644 src/projections/PJ_eck1.cpp create mode 100644 src/projections/PJ_eck2.cpp create mode 100644 src/projections/PJ_eck3.cpp create mode 100644 src/projections/PJ_eck4.cpp create mode 100644 src/projections/PJ_eck5.cpp create mode 100644 src/projections/PJ_eqc.cpp create mode 100644 src/projections/PJ_eqdc.cpp create mode 100644 src/projections/PJ_eqearth.cpp create mode 100644 src/projections/PJ_fahey.cpp create mode 100644 src/projections/PJ_fouc_s.cpp create mode 100644 src/projections/PJ_gall.cpp create mode 100644 src/projections/PJ_geos.cpp create mode 100644 src/projections/PJ_gins8.cpp create mode 100644 src/projections/PJ_gn_sinu.cpp create mode 100644 src/projections/PJ_gnom.cpp create mode 100644 src/projections/PJ_goode.cpp create mode 100644 src/projections/PJ_gstmerc.cpp create mode 100644 src/projections/PJ_hammer.cpp create mode 100644 src/projections/PJ_hatano.cpp create mode 100644 src/projections/PJ_healpix.cpp create mode 100644 src/projections/PJ_igh.cpp create mode 100644 src/projections/PJ_imw_p.cpp create mode 100644 src/projections/PJ_isea.cpp create mode 100644 src/projections/PJ_krovak.cpp create mode 100644 src/projections/PJ_labrd.cpp create mode 100644 src/projections/PJ_laea.cpp create mode 100644 src/projections/PJ_lagrng.cpp create mode 100644 src/projections/PJ_larr.cpp create mode 100644 src/projections/PJ_lask.cpp create mode 100644 src/projections/PJ_latlong.cpp create mode 100644 src/projections/PJ_lcc.cpp create mode 100644 src/projections/PJ_lcca.cpp create mode 100644 src/projections/PJ_loxim.cpp create mode 100644 src/projections/PJ_lsat.cpp create mode 100644 src/projections/PJ_mbt_fps.cpp create mode 100644 src/projections/PJ_mbtfpp.cpp create mode 100644 src/projections/PJ_mbtfpq.cpp create mode 100644 src/projections/PJ_merc.cpp create mode 100644 src/projections/PJ_mill.cpp create mode 100644 src/projections/PJ_misrsom.cpp create mode 100644 src/projections/PJ_mod_ster.cpp create mode 100644 src/projections/PJ_moll.cpp create mode 100644 src/projections/PJ_natearth.cpp create mode 100644 src/projections/PJ_natearth2.cpp create mode 100644 src/projections/PJ_nell.cpp create mode 100644 src/projections/PJ_nell_h.cpp create mode 100644 src/projections/PJ_nocol.cpp create mode 100644 src/projections/PJ_nsper.cpp create mode 100644 src/projections/PJ_nzmg.cpp create mode 100644 src/projections/PJ_ob_tran.cpp create mode 100644 src/projections/PJ_ocea.cpp create mode 100644 src/projections/PJ_oea.cpp create mode 100644 src/projections/PJ_omerc.cpp create mode 100644 src/projections/PJ_ortho.cpp create mode 100644 src/projections/PJ_patterson.cpp create mode 100644 src/projections/PJ_poly.cpp create mode 100644 src/projections/PJ_putp2.cpp create mode 100644 src/projections/PJ_putp3.cpp create mode 100644 src/projections/PJ_putp4p.cpp create mode 100644 src/projections/PJ_putp5.cpp create mode 100644 src/projections/PJ_putp6.cpp create mode 100644 src/projections/PJ_qsc.cpp create mode 100644 src/projections/PJ_robin.cpp create mode 100644 src/projections/PJ_rpoly.cpp create mode 100644 src/projections/PJ_sch.cpp create mode 100644 src/projections/PJ_sconics.cpp create mode 100644 src/projections/PJ_somerc.cpp create mode 100644 src/projections/PJ_stere.cpp create mode 100644 src/projections/PJ_sterea.cpp create mode 100644 src/projections/PJ_sts.cpp create mode 100644 src/projections/PJ_tcc.cpp create mode 100644 src/projections/PJ_tcea.cpp create mode 100644 src/projections/PJ_times.cpp create mode 100644 src/projections/PJ_tmerc.cpp create mode 100644 src/projections/PJ_tobmerc.cpp create mode 100644 src/projections/PJ_tpeqd.cpp create mode 100644 src/projections/PJ_urm5.cpp create mode 100644 src/projections/PJ_urmfps.cpp create mode 100644 src/projections/PJ_vandg.cpp create mode 100644 src/projections/PJ_vandg2.cpp create mode 100644 src/projections/PJ_vandg4.cpp create mode 100644 src/projections/PJ_wag2.cpp create mode 100644 src/projections/PJ_wag3.cpp create mode 100644 src/projections/PJ_wag7.cpp create mode 100644 src/projections/PJ_wink1.cpp create mode 100644 src/projections/PJ_wink2.cpp create mode 100644 src/projections/proj_etmerc.cpp create mode 100644 src/projections/proj_rouss.cpp delete mode 100644 src/projinfo.cpp delete mode 100644 src/static.cpp delete mode 100644 src/test228.cpp create mode 100644 src/tests/geodtest.cpp create mode 100644 src/tests/multistresstest.cpp create mode 100644 src/tests/test228.cpp create mode 100644 src/transformations/PJ_affine.cpp create mode 100644 src/transformations/PJ_deformation.cpp create mode 100644 src/transformations/PJ_helmert.cpp create mode 100644 src/transformations/PJ_hgridshift.cpp create mode 100644 src/transformations/PJ_horner.cpp create mode 100644 src/transformations/PJ_molodensky.cpp create mode 100644 src/transformations/PJ_vgridshift.cpp delete mode 100644 src/util.cpp (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 990ca74d..4912ae02 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,20 +15,20 @@ include_HEADERS = proj.h proj_experimental.h proj_constants.h proj_api.h geodesi EXTRA_DIST = bin_cct.cmake bin_gie.cmake bin_cs2cs.cmake \ bin_geod.cmake bin_nad2bin.cmake bin_proj.cmake bin_projinfo.cmake \ - lib_proj.cmake CMakeLists.txt bin_geodtest.cmake geodtest.cpp \ - pj_wkt1_grammar.y pj_wkt2_grammar.y + lib_proj.cmake CMakeLists.txt bin_geodtest.cmake tests/geodtest.cpp \ + pj_wkt1_grammar.y pj_wkt2_grammar.y apps/emess.h -proj_SOURCES = proj.cpp gen_cheb.cpp p_series.cpp -projinfo_SOURCES = projinfo.cpp -cs2cs_SOURCES = cs2cs.cpp gen_cheb.cpp p_series.cpp -cct_SOURCES = cct.cpp proj_strtod.cpp proj_strtod.h optargpm.h -nad2bin_SOURCES = nad2bin.cpp -geod_SOURCES = geod.cpp geod_set.cpp geod_interface.cpp geod_interface.h +proj_SOURCES = apps/proj.cpp apps/gen_cheb.cpp apps/p_series.cpp apps/emess.cpp +projinfo_SOURCES = apps/projinfo.cpp +cs2cs_SOURCES = apps/cs2cs.cpp apps/gen_cheb.cpp apps/p_series.cpp apps/emess.cpp +cct_SOURCES = apps/cct.cpp apps/proj_strtod.cpp apps/proj_strtod.h apps/optargpm.h +nad2bin_SOURCES = apps/nad2bin.cpp +geod_SOURCES = apps/geod.cpp apps/geod_set.cpp apps/geod_interface.cpp apps/geod_interface.h apps/emess.cpp -gie_SOURCES = gie.cpp proj_strtod.cpp proj_strtod.h optargpm.h -multistresstest_SOURCES = multistresstest.cpp -test228_SOURCES = test228.cpp -geodtest_SOURCES = geodtest.cpp +gie_SOURCES = apps/gie.cpp apps/proj_strtod.cpp apps/proj_strtod.h apps/optargpm.h +multistresstest_SOURCES = tests/multistresstest.cpp +test228_SOURCES = tests/test228.cpp +geodtest_SOURCES = tests/geodtest.cpp cct_LDADD = libproj.la cs2cs_LDADD = libproj.la @@ -48,32 +48,144 @@ libproj_la_LDFLAGS = -no-undefined -version-info 14:1:1 libproj_la_LIBADD = @SQLITE3_LDFLAGS@ libproj_la_SOURCES = \ - pj_list.h proj_internal.h proj_math.h projects.h\ - static.cpp util.cpp metadata.cpp common.cpp crs.cpp datum.cpp coordinatesystem.cpp coordinateoperation.cpp io.cpp \ - internal.cpp factory.cpp c_api.cpp \ - PJ_aeqd.cpp PJ_gnom.cpp PJ_laea.cpp PJ_mod_ster.cpp \ - PJ_nsper.cpp PJ_nzmg.cpp PJ_ortho.cpp PJ_stere.cpp PJ_sterea.cpp \ - PJ_aea.cpp PJ_bipc.cpp PJ_bonne.cpp PJ_eqdc.cpp PJ_isea.cpp PJ_ccon.cpp \ - PJ_imw_p.cpp PJ_krovak.cpp PJ_lcc.cpp PJ_poly.cpp \ - PJ_rpoly.cpp PJ_sconics.cpp proj_rouss.cpp \ - PJ_cass.cpp PJ_cc.cpp PJ_cea.cpp PJ_eqc.cpp PJ_gall.cpp PJ_geoc.cpp \ - PJ_labrd.cpp PJ_lsat.cpp PJ_misrsom.cpp PJ_merc.cpp \ - PJ_mill.cpp PJ_ocea.cpp PJ_omerc.cpp PJ_somerc.cpp \ - PJ_tcc.cpp PJ_tcea.cpp PJ_times.cpp PJ_tmerc.cpp PJ_tobmerc.cpp \ - PJ_airy.cpp PJ_aitoff.cpp PJ_august.cpp PJ_bacon.cpp \ - PJ_bertin1953.cpp PJ_chamb.cpp PJ_hammer.cpp PJ_lagrng.cpp PJ_larr.cpp \ - PJ_lask.cpp PJ_latlong.cpp PJ_nocol.cpp PJ_ob_tran.cpp PJ_oea.cpp \ - PJ_tpeqd.cpp PJ_vandg.cpp PJ_vandg2.cpp PJ_vandg4.cpp \ - PJ_wag7.cpp PJ_lcca.cpp PJ_geos.cpp proj_etmerc.cpp \ - PJ_boggs.cpp PJ_collg.cpp PJ_comill.cpp PJ_crast.cpp PJ_denoy.cpp \ - PJ_eck1.cpp PJ_eck2.cpp PJ_eck3.cpp PJ_eck4.cpp \ - PJ_eck5.cpp PJ_fahey.cpp PJ_fouc_s.cpp PJ_gins8.cpp PJ_gstmerc.cpp \ - PJ_gn_sinu.cpp PJ_goode.cpp PJ_igh.cpp PJ_hatano.cpp PJ_loxim.cpp \ - PJ_mbt_fps.cpp PJ_mbtfpp.cpp PJ_mbtfpq.cpp PJ_moll.cpp \ - PJ_nell.cpp PJ_nell_h.cpp PJ_patterson.cpp PJ_putp2.cpp PJ_putp3.cpp \ - PJ_putp4p.cpp PJ_putp5.cpp PJ_putp6.cpp PJ_qsc.cpp PJ_robin.cpp \ - PJ_sch.cpp PJ_sts.cpp PJ_urm5.cpp PJ_urmfps.cpp PJ_wag2.cpp \ - PJ_wag3.cpp PJ_wink1.cpp PJ_wink2.cpp pj_geocent.cpp \ + pj_list.h proj_internal.h proj_math.h projects.h \ + \ + iso19111/static.cpp \ + iso19111/util.cpp \ + iso19111/metadata.cpp \ + iso19111/common.cpp \ + 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 \ + \ + projections/PJ_aeqd.cpp \ + projections/PJ_gnom.cpp \ + projections/PJ_laea.cpp \ + projections/PJ_mod_ster.cpp \ + projections/PJ_nsper.cpp \ + projections/PJ_nzmg.cpp \ + projections/PJ_ortho.cpp \ + projections/PJ_stere.cpp \ + projections/PJ_sterea.cpp \ + projections/PJ_aea.cpp \ + projections/PJ_bipc.cpp \ + projections/PJ_bonne.cpp \ + projections/PJ_eqdc.cpp \ + projections/PJ_isea.cpp \ + projections/PJ_ccon.cpp \ + projections/PJ_imw_p.cpp \ + projections/PJ_krovak.cpp \ + projections/PJ_lcc.cpp \ + projections/PJ_poly.cpp \ + projections/PJ_rpoly.cpp \ + projections/PJ_sconics.cpp \ + projections/proj_rouss.cpp \ + projections/PJ_cass.cpp \ + projections/PJ_cc.cpp \ + projections/PJ_cea.cpp \ + projections/PJ_eqc.cpp \ + projections/PJ_gall.cpp \ + projections/PJ_labrd.cpp \ + projections/PJ_lsat.cpp \ + projections/PJ_misrsom.cpp \ + projections/PJ_merc.cpp \ + projections/PJ_mill.cpp \ + projections/PJ_ocea.cpp \ + projections/PJ_omerc.cpp \ + projections/PJ_somerc.cpp \ + projections/PJ_tcc.cpp \ + projections/PJ_tcea.cpp \ + projections/PJ_times.cpp \ + projections/PJ_tmerc.cpp \ + projections/PJ_tobmerc.cpp \ + projections/PJ_airy.cpp \ + projections/PJ_aitoff.cpp \ + projections/PJ_august.cpp \ + projections/PJ_bacon.cpp \ + projections/PJ_bertin1953.cpp \ + projections/PJ_chamb.cpp \ + projections/PJ_hammer.cpp \ + projections/PJ_lagrng.cpp \ + projections/PJ_larr.cpp \ + projections/PJ_lask.cpp \ + projections/PJ_latlong.cpp \ + projections/PJ_nocol.cpp \ + projections/PJ_ob_tran.cpp \ + projections/PJ_oea.cpp \ + projections/PJ_tpeqd.cpp \ + projections/PJ_vandg.cpp \ + projections/PJ_vandg2.cpp \ + projections/PJ_vandg4.cpp \ + projections/PJ_wag7.cpp \ + projections/PJ_lcca.cpp \ + projections/PJ_geos.cpp \ + projections/proj_etmerc.cpp \ + projections/PJ_boggs.cpp \ + projections/PJ_collg.cpp \ + projections/PJ_comill.cpp \ + projections/PJ_crast.cpp \ + projections/PJ_denoy.cpp \ + projections/PJ_eck1.cpp \ + projections/PJ_eck2.cpp \ + projections/PJ_eck3.cpp \ + projections/PJ_eck4.cpp \ + projections/PJ_eck5.cpp \ + projections/PJ_fahey.cpp \ + projections/PJ_fouc_s.cpp \ + projections/PJ_gins8.cpp \ + projections/PJ_gstmerc.cpp \ + projections/PJ_gn_sinu.cpp \ + projections/PJ_goode.cpp \ + projections/PJ_igh.cpp \ + projections/PJ_hatano.cpp \ + projections/PJ_loxim.cpp \ + projections/PJ_mbt_fps.cpp \ + projections/PJ_mbtfpp.cpp \ + projections/PJ_mbtfpq.cpp \ + projections/PJ_moll.cpp \ + projections/PJ_nell.cpp \ + projections/PJ_nell_h.cpp \ + projections/PJ_patterson.cpp \ + projections/PJ_putp2.cpp \ + projections/PJ_putp3.cpp \ + projections/PJ_putp4p.cpp \ + projections/PJ_putp5.cpp \ + projections/PJ_putp6.cpp \ + projections/PJ_qsc.cpp \ + projections/PJ_robin.cpp \ + projections/PJ_sch.cpp \ + projections/PJ_sts.cpp \ + projections/PJ_urm5.cpp \ + projections/PJ_urmfps.cpp \ + projections/PJ_wag2.cpp \ + projections/PJ_wag3.cpp \ + projections/PJ_wink1.cpp \ + projections/PJ_wink2.cpp \ + projections/PJ_healpix.cpp \ + projections/PJ_natearth.cpp \ + projections/PJ_natearth2.cpp \ + projections/PJ_calcofi.cpp \ + projections/PJ_eqearth.cpp \ + \ + conversions/PJ_axisswap.cpp \ + conversions/PJ_cart.cpp \ + conversions/PJ_geoc.cpp \ + conversions/pj_geocent.cpp \ + conversions/PJ_unitconvert.cpp \ + \ + transformations/PJ_affine.cpp \ + transformations/PJ_deformation.cpp \ + transformations/PJ_helmert.cpp \ + transformations/PJ_hgridshift.cpp \ + transformations/PJ_horner.cpp \ + transformations/PJ_molodensky.cpp \ + transformations/PJ_vgridshift.cpp \ + \ aasincos.cpp adjlon.cpp bch2bps.cpp bchgen.cpp \ biveval.cpp dmstor.cpp mk_cheby.cpp pj_auth.cpp \ pj_deriv.cpp pj_ell_set.cpp pj_ellps.cpp pj_errno.cpp \ @@ -83,19 +195,17 @@ libproj_la_SOURCES = \ pj_qsfn.cpp pj_strerrno.cpp \ pj_tsfn.cpp pj_units.cpp pj_ctx.cpp pj_log.cpp pj_zpoly1.cpp rtodms.cpp \ vector1.cpp pj_release.cpp pj_gauss.cpp \ - PJ_healpix.cpp PJ_natearth.cpp PJ_natearth2.cpp PJ_calcofi.cpp pj_fileapi.cpp \ - PJ_eqearth.cpp \ + pj_fileapi.cpp \ \ pj_gc_reader.cpp pj_gridcatalog.cpp \ - nad_cvt.cpp nad_init.cpp nad_intr.cpp emess.cpp emess.h \ + nad_cvt.cpp nad_init.cpp nad_intr.cpp \ pj_apply_gridshift.cpp pj_datums.cpp pj_datum_set.cpp pj_transform.cpp \ geocent.cpp geocent.h pj_utils.cpp pj_gridinfo.cpp pj_gridlist.cpp \ jniproj.cpp pj_mutex.cpp pj_initcache.cpp pj_apply_vgridshift.cpp geodesic.cpp \ pj_strtod.cpp pj_math.cpp \ \ - proj_4D_api.cpp PJ_cart.cpp PJ_pipeline.cpp PJ_horner.cpp PJ_helmert.cpp \ - PJ_vgridshift.cpp PJ_hgridshift.cpp PJ_unitconvert.cpp PJ_molodensky.cpp \ - PJ_deformation.cpp pj_internal.cpp PJ_axisswap.cpp PJ_affine.cpp \ + proj_4D_api.cpp PJ_pipeline.cpp \ + pj_internal.cpp \ pj_wkt_parser.hpp pj_wkt_parser.cpp \ pj_wkt1_parser.h pj_wkt1_parser.cpp \ pj_wkt1_generated_parser.h pj_wkt1_generated_parser.c \ diff --git a/src/PJ_aea.cpp b/src/PJ_aea.cpp deleted file mode 100644 index c4a4a72a..00000000 --- a/src/PJ_aea.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of the aea (Albers Equal Area) projection. - * and the leac (Lambert Equal Area Conic) projection - * Author: Gerald Evenden (1995) - * Thomas Knudsen (2016) - revise/add regression tests - * - ****************************************************************************** - * Copyright (c) 1995, Gerald Evenden - * - * 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. - *****************************************************************************/ - -#define PJ_LIB__ -#include "proj.h" -#include -#include "projects.h" -#include "proj_math.h" - - -# define EPS10 1.e-10 -# define TOL7 1.e-7 - -PROJ_HEAD(aea, "Albers Equal Area") "\n\tConic Sph&Ell\n\tlat_1= lat_2="; -PROJ_HEAD(leac, "Lambert Equal Area Conic") "\n\tConic, Sph&Ell\n\tlat_1= south"; - - -/* determine latitude angle phi-1 */ -# define N_ITER 15 -# define EPSILON 1.0e-7 -# define TOL 1.0e-10 -static double phi1_(double qs, double Te, double Tone_es) { - int i; - double Phi, sinpi, cospi, con, com, dphi; - - Phi = asin (.5 * qs); - if (Te < EPSILON) - return( Phi ); - i = N_ITER; - do { - sinpi = sin (Phi); - cospi = cos (Phi); - con = Te * sinpi; - com = 1. - con * con; - dphi = .5 * com * com / cospi * (qs / Tone_es - - sinpi / com + .5 / Te * log ((1. - con) / - (1. + con))); - Phi += dphi; - } while (fabs(dphi) > TOL && --i); - return( i ? Phi : HUGE_VAL ); -} - - -namespace { // anonymous namespace -struct pj_opaque { - double ec; - double n; - double c; - double dd; - double n2; - double rho0; - double rho; - double phi1; - double phi2; - double *en; - int ellips; -}; -} // anonymous namespace - - -static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->en); - return pj_default_destructor (P, errlev); -} - - - - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoid/spheroid, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - Q->rho = Q->c - (Q->ellips ? Q->n * pj_qsfn(sin(lp.phi), P->e, P->one_es) : Q->n2 * sin(lp.phi));; - if (Q->rho < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - Q->rho = Q->dd * sqrt(Q->rho); - xy.x = Q->rho * sin( lp.lam *= Q->n ); - xy.y = Q->rho0 - Q->rho * cos(lp.lam); - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoid/spheroid, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - if( (Q->rho = hypot(xy.x, xy.y = Q->rho0 - xy.y)) != 0.0 ) { - if (Q->n < 0.) { - Q->rho = -Q->rho; - xy.x = -xy.x; - xy.y = -xy.y; - } - lp.phi = Q->rho / Q->dd; - if (Q->ellips) { - lp.phi = (Q->c - lp.phi * lp.phi) / Q->n; - if (fabs(Q->ec - fabs(lp.phi)) > TOL7) { - if ((lp.phi = phi1_(lp.phi, P->e, P->one_es)) == HUGE_VAL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - } else - lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; - } else if (fabs(lp.phi = (Q->c - lp.phi * lp.phi) / Q->n2) <= 1.) - lp.phi = asin(lp.phi); - else - lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; - lp.lam = atan2(xy.x, xy.y) / Q->n; - } else { - lp.lam = 0.; - lp.phi = Q->n > 0. ? M_HALFPI : - M_HALFPI; - } - return lp; -} - - - -static PJ *setup(PJ *P) { - double cosphi, sinphi; - int secant; - struct pj_opaque *Q = static_cast(P->opaque); - - P->inv = e_inverse; - P->fwd = e_forward; - - if (fabs(Q->phi1 + Q->phi2) < EPS10) - return destructor(P, PJD_ERR_CONIC_LAT_EQUAL); - Q->n = sinphi = sin(Q->phi1); - cosphi = cos(Q->phi1); - secant = fabs(Q->phi1 - Q->phi2) >= EPS10; - if( (Q->ellips = (P->es > 0.))) { - double ml1, m1; - - if (!(Q->en = pj_enfn(P->es))) - return destructor(P, 0); - m1 = pj_msfn(sinphi, cosphi, P->es); - ml1 = pj_qsfn(sinphi, P->e, P->one_es); - if (secant) { /* secant cone */ - double ml2, m2; - - sinphi = sin(Q->phi2); - cosphi = cos(Q->phi2); - m2 = pj_msfn(sinphi, cosphi, P->es); - ml2 = pj_qsfn(sinphi, P->e, P->one_es); - if (ml2 == ml1) - return destructor(P, 0); - - Q->n = (m1 * m1 - m2 * m2) / (ml2 - ml1); - } - Q->ec = 1. - .5 * P->one_es * log((1. - P->e) / - (1. + P->e)) / P->e; - Q->c = m1 * m1 + Q->n * ml1; - Q->dd = 1. / Q->n; - Q->rho0 = Q->dd * sqrt(Q->c - Q->n * pj_qsfn(sin(P->phi0), - P->e, P->one_es)); - } else { - if (secant) Q->n = .5 * (Q->n + sin(Q->phi2)); - Q->n2 = Q->n + Q->n; - Q->c = cosphi * cosphi + Q->n2 * sinphi; - Q->dd = 1. / Q->n; - Q->rho0 = Q->dd * sqrt(Q->c - Q->n2 * sin(P->phi0)); - } - - return P; -} - - -PJ *PROJECTION(aea) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; - Q->phi2 = pj_param(P->ctx, P->params, "rlat_2").f; - return setup(P); -} - - -PJ *PROJECTION(leac) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - Q->phi2 = pj_param(P->ctx, P->params, "rlat_1").f; - Q->phi1 = pj_param(P->ctx, P->params, "bsouth").i ? - M_HALFPI: M_HALFPI; - return setup(P); -} - diff --git a/src/PJ_aeqd.cpp b/src/PJ_aeqd.cpp deleted file mode 100644 index 1a350d90..00000000 --- a/src/PJ_aeqd.cpp +++ /dev/null @@ -1,327 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of the aeqd (Azimuthal Equidistant) projection. - * Author: Gerald Evenden - * - ****************************************************************************** - * Copyright (c) 1995, Gerald Evenden - * - * 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. - *****************************************************************************/ - -#define PJ_LIB__ -#include "geodesic.h" -#include "proj.h" -#include -#include "projects.h" -#include "proj_math.h" - -namespace { // anonymous namespace -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque { - double sinph0; - double cosph0; - double *en; - double M1; - double N1; - double Mp; - double He; - double G; - enum Mode mode; - struct geod_geodesic g; -}; -} // anonymous namespace - -PROJ_HEAD(aeqd, "Azimuthal Equidistant") "\n\tAzi, Sph&Ell\n\tlat_0 guam"; - -#define EPS10 1.e-10 -#define TOL 1.e-14 - - -static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->en); - return pj_default_destructor (P, errlev); -} - - - -static XY e_guam_fwd(LP lp, PJ *P) { /* Guam elliptical */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double cosphi, sinphi, t; - - cosphi = cos(lp.phi); - sinphi = sin(lp.phi); - t = 1. / sqrt(1. - P->es * sinphi * sinphi); - xy.x = lp.lam * cosphi * t; - xy.y = pj_mlfn(lp.phi, sinphi, cosphi, Q->en) - Q->M1 + - .5 * lp.lam * lp.lam * cosphi * sinphi * t; - - return xy; -} - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double coslam, cosphi, sinphi, rho; - double azi1, azi2, s12; - double lam1, phi1, lam2, phi2; - - coslam = cos(lp.lam); - cosphi = cos(lp.phi); - sinphi = sin(lp.phi); - switch (Q->mode) { - case N_POLE: - coslam = - coslam; - /*-fallthrough*/ - case S_POLE: - xy.x = (rho = fabs(Q->Mp - pj_mlfn(lp.phi, sinphi, cosphi, Q->en))) * - sin(lp.lam); - xy.y = rho * coslam; - break; - case EQUIT: - case OBLIQ: - if (fabs(lp.lam) < EPS10 && fabs(lp.phi - P->phi0) < EPS10) { - xy.x = xy.y = 0.; - break; - } - - phi1 = P->phi0 / DEG_TO_RAD; lam1 = P->lam0 / DEG_TO_RAD; - phi2 = lp.phi / DEG_TO_RAD; lam2 = (lp.lam+P->lam0) / DEG_TO_RAD; - - geod_inverse(&Q->g, phi1, lam1, phi2, lam2, &s12, &azi1, &azi2); - azi1 *= DEG_TO_RAD; - xy.x = s12 * sin(azi1) / P->a; - xy.y = s12 * cos(azi1) / P->a; - break; - } - return xy; -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double coslam, cosphi, sinphi; - - sinphi = sin(lp.phi); - cosphi = cos(lp.phi); - coslam = cos(lp.lam); - switch (Q->mode) { - case EQUIT: - xy.y = cosphi * coslam; - goto oblcon; - case OBLIQ: - xy.y = Q->sinph0 * sinphi + Q->cosph0 * cosphi * coslam; -oblcon: - if (fabs(fabs(xy.y) - 1.) < TOL) - if (xy.y < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - else - xy.x = xy.y = 0.; - else { - xy.y = acos(xy.y); - xy.y /= sin(xy.y); - xy.x = xy.y * cosphi * sin(lp.lam); - xy.y *= (Q->mode == EQUIT) ? sinphi : - Q->cosph0 * sinphi - Q->sinph0 * cosphi * coslam; - } - break; - case N_POLE: - lp.phi = -lp.phi; - coslam = -coslam; - /*-fallthrough*/ - case S_POLE: - if (fabs(lp.phi - M_HALFPI) < EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - xy.x = (xy.y = (M_HALFPI + lp.phi)) * sin(lp.lam); - xy.y *= coslam; - break; - } - return xy; -} - - -static LP e_guam_inv(XY xy, PJ *P) { /* Guam elliptical */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double x2, t = 0.0; - int i; - - x2 = 0.5 * xy.x * xy.x; - lp.phi = P->phi0; - for (i = 0; i < 3; ++i) { - t = P->e * sin(lp.phi); - lp.phi = pj_inv_mlfn(P->ctx, Q->M1 + xy.y - - x2 * tan(lp.phi) * (t = sqrt(1. - t * t)), P->es, Q->en); - } - lp.lam = xy.x * t / cos(lp.phi); - return lp; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double c; - double azi1, azi2, s12, x2, y2, lat1, lon1, lat2, lon2; - - if ((c = hypot(xy.x, xy.y)) < EPS10) { - lp.phi = P->phi0; - lp.lam = 0.; - return (lp); - } - if (Q->mode == OBLIQ || Q->mode == EQUIT) { - - x2 = xy.x * P->a; - y2 = xy.y * P->a; - lat1 = P->phi0 / DEG_TO_RAD; - lon1 = P->lam0 / DEG_TO_RAD; - azi1 = atan2(x2, y2) / DEG_TO_RAD; - s12 = sqrt(x2 * x2 + y2 * y2); - geod_direct(&Q->g, lat1, lon1, azi1, s12, &lat2, &lon2, &azi2); - lp.phi = lat2 * DEG_TO_RAD; - lp.lam = lon2 * DEG_TO_RAD; - lp.lam -= P->lam0; - } else { /* Polar */ - lp.phi = pj_inv_mlfn(P->ctx, Q->mode == N_POLE ? Q->Mp - c : Q->Mp + c, - P->es, Q->en); - lp.lam = atan2(xy.x, Q->mode == N_POLE ? -xy.y : xy.y); - } - return lp; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double cosc, c_rh, sinc; - - if ((c_rh = hypot(xy.x, xy.y)) > M_PI) { - if (c_rh - EPS10 > M_PI) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - c_rh = M_PI; - } else if (c_rh < EPS10) { - lp.phi = P->phi0; - lp.lam = 0.; - return (lp); - } - if (Q->mode == OBLIQ || Q->mode == EQUIT) { - sinc = sin(c_rh); - cosc = cos(c_rh); - if (Q->mode == EQUIT) { - lp.phi = aasin(P->ctx, xy.y * sinc / c_rh); - xy.x *= sinc; - xy.y = cosc * c_rh; - } else { - lp.phi = aasin(P->ctx,cosc * Q->sinph0 + xy.y * sinc * Q->cosph0 / - c_rh); - xy.y = (cosc - Q->sinph0 * sin(lp.phi)) * c_rh; - xy.x *= sinc * Q->cosph0; - } - lp.lam = xy.y == 0. ? 0. : atan2(xy.x, xy.y); - } else if (Q->mode == N_POLE) { - lp.phi = M_HALFPI - c_rh; - lp.lam = atan2(xy.x, -xy.y); - } else { - lp.phi = c_rh - M_HALFPI; - lp.lam = atan2(xy.x, xy.y); - } - return lp; -} - - -PJ *PROJECTION(aeqd) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - geod_init(&Q->g, P->a, P->es / (1 + sqrt(P->one_es))); - - if (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) { - Q->mode = P->phi0 < 0. ? S_POLE : N_POLE; - Q->sinph0 = P->phi0 < 0. ? -1. : 1.; - Q->cosph0 = 0.; - } else if (fabs(P->phi0) < EPS10) { - Q->mode = EQUIT; - Q->sinph0 = 0.; - Q->cosph0 = 1.; - } else { - Q->mode = OBLIQ; - Q->sinph0 = sin(P->phi0); - Q->cosph0 = cos(P->phi0); - } - if (P->es == 0.0) { - P->inv = s_inverse; - P->fwd = s_forward; - } else { - if (!(Q->en = pj_enfn(P->es))) - return pj_default_destructor (P, 0); - if (pj_param(P->ctx, P->params, "bguam").i) { - Q->M1 = pj_mlfn(P->phi0, Q->sinph0, Q->cosph0, Q->en); - P->inv = e_guam_inv; - P->fwd = e_guam_fwd; - } else { - switch (Q->mode) { - case N_POLE: - Q->Mp = pj_mlfn(M_HALFPI, 1., 0., Q->en); - break; - case S_POLE: - Q->Mp = pj_mlfn(-M_HALFPI, -1., 0., Q->en); - break; - case EQUIT: - case OBLIQ: - P->inv = e_inverse; P->fwd = e_forward; - Q->N1 = 1. / sqrt(1. - P->es * Q->sinph0 * Q->sinph0); - Q->G = Q->sinph0 * (Q->He = P->e / sqrt(P->one_es)); - Q->He *= Q->cosph0; - break; - } - P->inv = e_inverse; - P->fwd = e_forward; - } - } - - return P; -} - - diff --git a/src/PJ_affine.cpp b/src/PJ_affine.cpp deleted file mode 100644 index e2b668d3..00000000 --- a/src/PJ_affine.cpp +++ /dev/null @@ -1,250 +0,0 @@ -/************************************************************************ -* 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. -* -***********************************************************************/ -#define PJ_LIB__ - -#include -#include - -#include "proj_internal.h" -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(affine, "Affine transformation"); -PROJ_HEAD(geogoffset, "Geographic Offset"); - -namespace { // anonymous namespace -struct pj_affine_coeffs { - double s11; - double s12; - double s13; - double s21; - double s22; - double s23; - double s31; - double s32; - double s33; - double tscale; -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque_affine { - double xoff; - double yoff; - double zoff; - double toff; - struct pj_affine_coeffs forward; - struct pj_affine_coeffs reverse; -}; -} // anonymous namespace - - -static PJ_COORD forward_4d(PJ_COORD obs, PJ *P) { - PJ_COORD newObs; - const struct pj_opaque_affine *Q = (const struct pj_opaque_affine *) P->opaque; - const struct pj_affine_coeffs *C = &(Q->forward); - newObs.xyzt.x = Q->xoff + C->s11 * obs.xyzt.x + C->s12 * obs.xyzt.y + C->s13 * obs.xyzt.z; - newObs.xyzt.y = Q->yoff + C->s21 * obs.xyzt.x + C->s22 * obs.xyzt.y + C->s23 * obs.xyzt.z; - newObs.xyzt.z = Q->zoff + C->s31 * obs.xyzt.x + C->s32 * obs.xyzt.y + C->s33 * obs.xyzt.z; - newObs.xyzt.t = Q->toff + C->tscale * obs.xyzt.t; - return newObs; -} - -static XYZ forward_3d(LPZ lpz, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - point.lpz = lpz; - return forward_4d(point, P).xyz; -} - - -static XY forward_2d(LP lp, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - point.lp = lp; - return forward_4d(point, P).xy; -} - - -static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) { - PJ_COORD newObs; - const struct pj_opaque_affine *Q = (const struct pj_opaque_affine *) P->opaque; - const struct pj_affine_coeffs *C = &(Q->reverse); - obs.xyzt.x -= Q->xoff; - obs.xyzt.y -= Q->yoff; - obs.xyzt.z -= Q->zoff; - newObs.xyzt.x = C->s11 * obs.xyzt.x + C->s12 * obs.xyzt.y + C->s13 * obs.xyzt.z; - newObs.xyzt.y = C->s21 * obs.xyzt.x + C->s22 * obs.xyzt.y + C->s23 * obs.xyzt.z; - newObs.xyzt.z = C->s31 * obs.xyzt.x + C->s32 * obs.xyzt.y + C->s33 * obs.xyzt.z; - newObs.xyzt.t = C->tscale * (obs.xyzt.t - Q->toff); - return newObs; -} - -static LPZ reverse_3d(XYZ xyz, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - point.xyz = xyz; - return reverse_4d(point, P).lpz; -} - -static LP reverse_2d(XY xy, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - point.xy = xy; - return reverse_4d(point, P).lp; -} - -static struct pj_opaque_affine * initQ() { - struct pj_opaque_affine *Q = static_cast(pj_calloc(1, sizeof(struct pj_opaque_affine))); - if (nullptr==Q) - return nullptr; - - /* default values */ - Q->forward.s11 = 1.0; - Q->forward.s22 = 1.0; - Q->forward.s33 = 1.0; - Q->forward.tscale = 1.0; - - Q->reverse.s11 = 1.0; - Q->reverse.s22 = 1.0; - Q->reverse.s33 = 1.0; - Q->reverse.tscale = 1.0; - - return Q; -} - -static void computeReverseParameters(PJ* P) -{ - struct pj_opaque_affine *Q = (struct pj_opaque_affine *) P->opaque; - - /* cf https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices */ - const double a = Q->forward.s11; - const double b = Q->forward.s12; - const double c = Q->forward.s13; - const double d = Q->forward.s21; - const double e = Q->forward.s22; - const double f = Q->forward.s23; - const double g = Q->forward.s31; - const double h = Q->forward.s32; - const double i = Q->forward.s33; - const double A = e * i - f * h; - const double B = -(d * i - f * g); - const double C = (d * h - e * g); - const double D = -(b * i - c * h); - const double E = (a * i - c * g); - const double F = -(a * h - b * g); - const double G = b * f - c * e; - const double H = -(a * f - c * d); - const double I = a * e - b * d; - const double det = a * A + b * B + c * C; - if( det == 0.0 || Q->forward.tscale == 0.0 ) { - if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { - proj_log_debug(P, "Affine: matrix non invertible"); - } - P->inv4d = nullptr; - P->inv3d = nullptr; - P->inv = nullptr; - } else { - Q->reverse.s11 = A / det; - Q->reverse.s12 = D / det; - Q->reverse.s13 = G / det; - Q->reverse.s21 = B / det; - Q->reverse.s22 = E / det; - Q->reverse.s23 = H / det; - Q->reverse.s31 = C / det; - Q->reverse.s32 = F / det; - Q->reverse.s33 = I / det; - Q->reverse.tscale = 1.0 / Q->forward.tscale; - } -} - -PJ *TRANSFORMATION(affine,0 /* no need for ellipsoid */) { - struct pj_opaque_affine *Q = initQ(); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = (void *) Q; - - P->fwd4d = forward_4d; - P->inv4d = reverse_4d; - P->fwd3d = forward_3d; - P->inv3d = reverse_3d; - P->fwd = forward_2d; - P->inv = reverse_2d; - - P->left = PJ_IO_UNITS_WHATEVER; - P->right = PJ_IO_UNITS_WHATEVER; - - /* read args */ - Q->xoff = pj_param(P->ctx, P->params, "dxoff").f; - Q->yoff = pj_param(P->ctx, P->params, "dyoff").f; - Q->zoff = pj_param(P->ctx, P->params, "dzoff").f; - Q->toff = pj_param(P->ctx, P->params, "dtoff").f; - - if(pj_param (P->ctx, P->params, "ts11").i) { - Q->forward.s11 = pj_param(P->ctx, P->params, "ds11").f; - } - Q->forward.s12 = pj_param(P->ctx, P->params, "ds12").f; - Q->forward.s13 = pj_param(P->ctx, P->params, "ds13").f; - Q->forward.s21 = pj_param(P->ctx, P->params, "ds21").f; - if(pj_param (P->ctx, P->params, "ts22").i) { - Q->forward.s22 = pj_param(P->ctx, P->params, "ds22").f; - } - Q->forward.s23 = pj_param(P->ctx, P->params, "ds23").f; - Q->forward.s31 = pj_param(P->ctx, P->params, "ds31").f; - Q->forward.s32 = pj_param(P->ctx, P->params, "ds32").f; - if(pj_param (P->ctx, P->params, "ts33").i) { - Q->forward.s33 = pj_param(P->ctx, P->params, "ds33").f; - } - if(pj_param (P->ctx, P->params, "ttscale").i) { - Q->forward.tscale = pj_param(P->ctx, P->params, "dtscale").f; - } - - computeReverseParameters(P); - - return P; -} - - -/* Arcsecond to radians */ -#define ARCSEC_TO_RAD (DEG_TO_RAD / 3600.0) - - -PJ *TRANSFORMATION(geogoffset,0 /* no need for ellipsoid */) { - struct pj_opaque_affine *Q = initQ(); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = (void *) Q; - - P->fwd4d = forward_4d; - P->inv4d = reverse_4d; - P->fwd3d = forward_3d; - P->inv3d = reverse_3d; - P->fwd = forward_2d; - P->inv = reverse_2d; - - P->left = PJ_IO_UNITS_ANGULAR; - P->right = PJ_IO_UNITS_ANGULAR; - - /* read args */ - Q->xoff = pj_param(P->ctx, P->params, "ddlon").f * ARCSEC_TO_RAD; - Q->yoff = pj_param(P->ctx, P->params, "ddlat").f * ARCSEC_TO_RAD; - Q->zoff = pj_param(P->ctx, P->params, "ddh").f; - - return P; -} diff --git a/src/PJ_airy.cpp b/src/PJ_airy.cpp deleted file mode 100644 index 0eb5efd7..00000000 --- a/src/PJ_airy.cpp +++ /dev/null @@ -1,155 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of the airy (Airy) projection. - * Author: Gerald Evenden (1995) - * Thomas Knudsen (2016) - revise/add regression tests - * - ****************************************************************************** - * Copyright (c) 1995, Gerald Evenden - * - * 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. - *****************************************************************************/ - -#define PJ_LIB__ -#include "proj.h" -#include -#include "projects.h" - -PROJ_HEAD(airy, "Airy") "\n\tMisc Sph, no inv\n\tno_cut lat_b="; - - -namespace { // anonymous namespace -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque { - double p_halfpi; - double sinph0; - double cosph0; - double Cb; - enum Mode mode; - int no_cut; /* do not cut at hemisphere limit */ -}; -} // anonymous namespace - - -# define EPS 1.e-10 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double sinlam, coslam, cosphi, sinphi, t, s, Krho, cosz; - - sinlam = sin(lp.lam); - coslam = cos(lp.lam); - switch (Q->mode) { - case EQUIT: - case OBLIQ: - sinphi = sin(lp.phi); - cosphi = cos(lp.phi); - cosz = cosphi * coslam; - if (Q->mode == OBLIQ) - cosz = Q->sinph0 * sinphi + Q->cosph0 * cosz; - if (!Q->no_cut && cosz < -EPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - if (fabs(s = 1. - cosz) > EPS) { - t = 0.5 * (1. + cosz); - Krho = -log(t)/s - Q->Cb / t; - } else - Krho = 0.5 - Q->Cb; - xy.x = Krho * cosphi * sinlam; - if (Q->mode == OBLIQ) - xy.y = Krho * (Q->cosph0 * sinphi - - Q->sinph0 * cosphi * coslam); - else - xy.y = Krho * sinphi; - break; - case S_POLE: - case N_POLE: - lp.phi = fabs(Q->p_halfpi - lp.phi); - if (!Q->no_cut && (lp.phi - EPS) > M_HALFPI) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - if ((lp.phi *= 0.5) > EPS) { - t = tan(lp.phi); - Krho = -2.*(log(cos(lp.phi)) / t + t * Q->Cb); - xy.x = Krho * sinlam; - xy.y = Krho * coslam; - if (Q->mode == N_POLE) - xy.y = -xy.y; - } else - xy.x = xy.y = 0.; - } - return xy; -} - - - - -PJ *PROJECTION(airy) { - double beta; - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - - P->opaque = Q; - - Q->no_cut = pj_param(P->ctx, P->params, "bno_cut").i; - beta = 0.5 * (M_HALFPI - pj_param(P->ctx, P->params, "rlat_b").f); - if (fabs(beta) < EPS) - Q->Cb = -0.5; - else { - Q->Cb = 1./tan(beta); - Q->Cb *= Q->Cb * log(cos(beta)); - } - - if (fabs(fabs(P->phi0) - M_HALFPI) < EPS) - if (P->phi0 < 0.) { - Q->p_halfpi = -M_HALFPI; - Q->mode = S_POLE; - } else { - Q->p_halfpi = M_HALFPI; - Q->mode = N_POLE; - } - else { - if (fabs(P->phi0) < EPS) - Q->mode = EQUIT; - else { - Q->mode = OBLIQ; - Q->sinph0 = sin(P->phi0); - Q->cosph0 = cos(P->phi0); - } - } - P->fwd = s_forward; - P->es = 0.; - return P; -} - - diff --git a/src/PJ_aitoff.cpp b/src/PJ_aitoff.cpp deleted file mode 100644 index effd2c29..00000000 --- a/src/PJ_aitoff.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of the aitoff (Aitoff) and wintri (Winkel Tripel) - * projections. - * Author: Gerald Evenden (1995) - * Drazen Tutic, Lovro Gradiser (2015) - add inverse - * Thomas Knudsen (2016) - revise/add regression tests - * - ****************************************************************************** - * Copyright (c) 1995, Gerald Evenden - * - * 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. - *****************************************************************************/ - -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - - -namespace { // anonymous namespace -enum Mode { - AITOFF = 0, - WINKEL_TRIPEL = 1 -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque { - double cosphi1; - enum Mode mode; -}; -} // anonymous namespace - - -PROJ_HEAD(aitoff, "Aitoff") "\n\tMisc Sph"; -PROJ_HEAD(wintri, "Winkel Tripel") "\n\tMisc Sph\n\tlat_1"; - - - -#if 0 -FORWARD(s_forward); /* spheroid */ -#endif - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double c, d; - - if((d = acos(cos(lp.phi) * cos(c = 0.5 * lp.lam))) != 0.0) {/* basic Aitoff */ - xy.x = 2. * d * cos(lp.phi) * sin(c) * (xy.y = 1. / sin(d)); - xy.y *= d * sin(lp.phi); - } else - xy.x = xy.y = 0.; - if (Q->mode == WINKEL_TRIPEL) { - xy.x = (xy.x + lp.lam * Q->cosphi1) * 0.5; - xy.y = (xy.y + lp.phi) * 0.5; - } - return (xy); -} - -/*********************************************************************************** -* -* Inverse functions added by Drazen Tutic and Lovro Gradiser based on paper: -* -* I.Özbug Biklirici and Cengizhan Ipbüker. A General Algorithm for the Inverse -* Transformation of Map Projections Using Jacobian Matrices. In Proceedings of the -* Third International Symposium Mathematical & Computational Applications, -* pages 175{182, Turkey, September 2002. -* -* Expected accuracy is defined by EPSILON = 1e-12. Should be appropriate for -* most applications of Aitoff and Winkel Tripel projections. -* -* Longitudes of 180W and 180E can be mixed in solution obtained. -* -* Inverse for Aitoff projection in poles is undefined, longitude value of 0 is assumed. -* -* Contact : dtutic@geof.hr -* Date: 2015-02-16 -* -************************************************************************************/ - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - int iter, MAXITER = 10, round = 0, MAXROUND = 20; - double EPSILON = 1e-12, D, C, f1, f2, f1p, f1l, f2p, f2l, dp, dl, sl, sp, cp, cl, x, y; - - if ((fabs(xy.x) < EPSILON) && (fabs(xy.y) < EPSILON )) { lp.phi = 0.; lp.lam = 0.; return lp; } - - /* initial values for Newton-Raphson method */ - lp.phi = xy.y; lp.lam = xy.x; - do { - iter = 0; - do { - sl = sin(lp.lam * 0.5); cl = cos(lp.lam * 0.5); - sp = sin(lp.phi); cp = cos(lp.phi); - D = cp * cl; - C = 1. - D * D; - D = acos(D) / pow(C, 1.5); - f1 = 2. * D * C * cp * sl; - f2 = D * C * sp; - f1p = 2.* (sl * cl * sp * cp / C - D * sp * sl); - f1l = cp * cp * sl * sl / C + D * cp * cl * sp * sp; - f2p = sp * sp * cl / C + D * sl * sl * cp; - f2l = 0.5 * (sp * cp * sl / C - D * sp * cp * cp * sl * cl); - if (Q->mode == WINKEL_TRIPEL) { - f1 = 0.5 * (f1 + lp.lam * Q->cosphi1); - f2 = 0.5 * (f2 + lp.phi); - f1p *= 0.5; - f1l = 0.5 * (f1l + Q->cosphi1); - f2p = 0.5 * (f2p + 1.); - f2l *= 0.5; - } - f1 -= xy.x; f2 -= xy.y; - dl = (f2 * f1p - f1 * f2p) / (dp = f1p * f2l - f2p * f1l); - dp = (f1 * f2l - f2 * f1l) / dp; - dl = fmod(dl, M_PI); /* set to interval [-M_PI, M_PI] */ - lp.phi -= dp; lp.lam -= dl; - } while ((fabs(dp) > EPSILON || fabs(dl) > EPSILON) && (iter++ < MAXITER)); - if (lp.phi > M_PI_2) lp.phi -= 2.*(lp.phi-M_PI_2); /* correct if symmetrical solution for Aitoff */ - if (lp.phi < -M_PI_2) lp.phi -= 2.*(lp.phi+M_PI_2); /* correct if symmetrical solution for Aitoff */ - if ((fabs(fabs(lp.phi) - M_PI_2) < EPSILON) && (Q->mode == AITOFF)) lp.lam = 0.; /* if pole in Aitoff, return longitude of 0 */ - - /* calculate x,y coordinates with solution obtained */ - if((D = acos(cos(lp.phi) * cos(C = 0.5 * lp.lam))) != 0.0) {/* Aitoff */ - x = 2. * D * cos(lp.phi) * sin(C) * (y = 1. / sin(D)); - y *= D * sin(lp.phi); - } else - x = y = 0.; - if (Q->mode == WINKEL_TRIPEL) { - x = (x + lp.lam * Q->cosphi1) * 0.5; - y = (y + lp.phi) * 0.5; - } - /* if too far from given values of x,y, repeat with better approximation of phi,lam */ - } while (((fabs(xy.x-x) > EPSILON) || (fabs(xy.y-y) > EPSILON)) && (round++ < MAXROUND)); - - if (iter == MAXITER && round == MAXROUND) - { - pj_ctx_set_errno( P->ctx, PJD_ERR_NON_CONVERGENT ); - /* fprintf(stderr, "Warning: Accuracy of 1e-12 not reached. Last increments: dlat=%e and dlon=%e\n", dp, dl); */ - } - - return lp; -} - - -static PJ *setup(PJ *P) { - P->inv = s_inverse; - P->fwd = s_forward; - P->es = 0.; - return P; -} - - -PJ *PROJECTION(aitoff) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - Q->mode = AITOFF; - return setup(P); -} - - -PJ *PROJECTION(wintri) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - Q->mode = WINKEL_TRIPEL; - if (pj_param(P->ctx, P->params, "tlat_1").i) { - if ((Q->cosphi1 = cos(pj_param(P->ctx, P->params, "rlat_1").f)) == 0.) - return pj_default_destructor (P, PJD_ERR_LAT_LARGER_THAN_90); - } - else /* 50d28' or acos(2/pi) */ - Q->cosphi1 = 0.636619772367581343; - return setup(P); -} diff --git a/src/PJ_august.cpp b/src/PJ_august.cpp deleted file mode 100644 index b5a21ef7..00000000 --- a/src/PJ_august.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(august, "August Epicycloidal") "\n\tMisc Sph, no inv"; -#define M 1.333333333333333 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double t, c1, c, x1, x12, y1, y12; - (void) P; - - t = tan(.5 * lp.phi); - c1 = sqrt(1. - t * t); - c = 1. + c1 * cos(lp.lam *= .5); - x1 = sin(lp.lam) * c1 / c; - y1 = t / c; - xy.x = M * x1 * (3. + (x12 = x1 * x1) - 3. * (y12 = y1 * y1)); - xy.y = M * y1 * (3. + 3. * x12 - y12); - return (xy); -} - - - - -PJ *PROJECTION(august) { - P->inv = nullptr; - P->fwd = s_forward; - P->es = 0.; - return P; -} diff --git a/src/PJ_axisswap.cpp b/src/PJ_axisswap.cpp deleted file mode 100644 index 8714ec85..00000000 --- a/src/PJ_axisswap.cpp +++ /dev/null @@ -1,302 +0,0 @@ -/*********************************************************************** - - Axis order operation for use with transformation pipelines. - - Kristian Evers, kreve@sdfe.dk, 2017-10-31 - -************************************************************************ - -Change the order and sign of 2,3 or 4 axes. Each of the possible four -axes are numbered with 1-4, such that the first input axis is 1, the -second is 2 and so on. The output ordering is controlled by a list of the -input axes re-ordered to the new mapping. Examples: - -Reversing the order of the axes: - - +proj=axisswap +order=4,3,2,1 - -Swapping the first two axes (x and y): - - +proj=axisswap +order=2,1,3,4 - -The direction, or sign, of an axis can be changed by adding a minus in -front of the axis-number: - - +proj=axisswap +order=1,-2,3,4 - -It is only necessary to specify the axes that are affected by the swap -operation: - - +proj=axisswap +order=2,1 - -************************************************************************ -* Copyright (c) 2017, Kristian Evers / SDFE -* -* 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. -* -***********************************************************************/ - -#define PJ_LIB__ -#include -#include -#include - -#include "proj_internal.h" -#include "projects.h" - -PROJ_HEAD(axisswap, "Axis ordering"); - -namespace { // anonymous namespace -struct pj_opaque { - unsigned int axis[4]; - int sign[4]; -}; -} // anonymous namespace - -static int sign(int x) { - return (x > 0) - (x < 0); -} - -static XY forward_2d(LP lp, PJ *P) { - struct pj_opaque *Q = (struct pj_opaque *) P->opaque; - unsigned int i; - PJ_COORD out, in; - - in.lp = lp; - out = proj_coord_error(); - - for (i=0; i<2; i++) - out.v[i] = in.v[Q->axis[i]] * Q->sign[i]; - - return out.xy; -} - - -static LP reverse_2d(XY xy, PJ *P) { - struct pj_opaque *Q = (struct pj_opaque *) P->opaque; - unsigned int i; - PJ_COORD out, in; - - in.xy = xy; - out = proj_coord_error(); - - for (i=0; i<2; i++) - out.v[Q->axis[i]] = in.v[i] * Q->sign[i]; - - return out.lp; -} - - -static XYZ forward_3d(LPZ lpz, PJ *P) { - struct pj_opaque *Q = (struct pj_opaque *) P->opaque; - unsigned int i; - PJ_COORD out, in; - - in.lpz = lpz; - out = proj_coord_error(); - - for (i=0; i<3; i++) - out.v[i] = in.v[Q->axis[i]] * Q->sign[i]; - - return out.xyz; -} - -static LPZ reverse_3d(XYZ xyz, PJ *P) { - struct pj_opaque *Q = (struct pj_opaque *) P->opaque; - unsigned int i; - PJ_COORD in, out; - - out = proj_coord_error(); - in.xyz = xyz; - - for (i=0; i<3; i++) - out.v[Q->axis[i]] = in.v[i] * Q->sign[i]; - - return out.lpz; -} - - -static PJ_COORD forward_4d(PJ_COORD coo, PJ *P) { - struct pj_opaque *Q = (struct pj_opaque *) P->opaque; - unsigned int i; - PJ_COORD out; - - out = proj_coord_error(); - - for (i=0; i<4; i++) - out.v[i] = coo.v[Q->axis[i]] * Q->sign[i]; - - return out; -} - - -static PJ_COORD reverse_4d(PJ_COORD coo, PJ *P) { - struct pj_opaque *Q = (struct pj_opaque *) P->opaque; - unsigned int i; - PJ_COORD out; - - out = proj_coord_error(); - - for (i=0; i<4; i++) - out.v[Q->axis[i]] = coo.v[i] * Q->sign[i]; - - return out; -} - - -/***********************************************************************/ -PJ *CONVERSION(axisswap,0) { -/***********************************************************************/ - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - char *s; - unsigned int i, j, n = 0; - - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = (void *) Q; - - - /* +order and +axis are mutually exclusive */ - if ( !pj_param_exists(P->params, "order") == !pj_param_exists(P->params, "axis") ) - return pj_default_destructor(P, PJD_ERR_AXIS); - - /* fill axis list with indices from 4-7 to simplify duplicate search further down */ - for (i=0; i<4; i++) { - Q->axis[i] = i+4; - Q->sign[i] = 1; - } - - /* if the "order" parameter is used */ - if ( pj_param_exists(P->params, "order") ) { - /* read axis order */ - char *order = pj_param(P->ctx, P->params, "sorder").s; - - /* check that all characters are valid */ - for (i=0; iaxis[n] = abs(atoi(s))-1; - if (Q->axis[n] > 3) { - proj_log_error(P, "axisswap: invalid axis '%d'", Q->axis[n]); - return pj_default_destructor(P, PJD_ERR_AXIS); - } - Q->sign[n++] = sign(atoi(s)); - while ( *s != '\0' && *s != ',' ) - s++; - if ( *s == ',' ) - s++; - } - } - - /* if the "axis" parameter is used */ - if ( pj_param_exists(P->params, "axis") ) { - /* parse the classic PROJ.4 enu axis specification */ - for (i=0; i < 3; i++) { - switch(P->axis[i]) { - case 'w': - Q->sign[i] = -1; - Q->axis[i] = 0; - break; - case 'e': - Q->sign[i] = 1; - Q->axis[i] = 0; - break; - case 's': - Q->sign[i] = -1; - Q->axis[i] = 1; - break; - case 'n': - Q->sign[i] = 1; - Q->axis[i] = 1; - break; - case 'd': - Q->sign[i] = -1; - Q->axis[i] = 2; - break; - case 'u': - Q->sign[i] = 1; - Q->axis[i] = 2; - break; - default: - proj_log_error(P, "axisswap: unknown axis '%c'", P->axis[i]); - return pj_default_destructor(P, PJD_ERR_AXIS); - } - } - n = 3; - } - - /* check for duplicate axes */ - for (i=0; i<4; i++) - for (j=0; j<4; j++) { - if (i==j) - continue; - if (Q->axis[i] == Q->axis[j]) { - proj_log_error(P, "swapaxis: duplicate axes specified"); - return pj_default_destructor(P, PJD_ERR_AXIS); - } - } - - - /* only map fwd/inv functions that are possible with the given axis setup */ - if (n == 4) { - P->fwd4d = forward_4d; - P->inv4d = reverse_4d; - } - if (n == 3 && Q->axis[0] < 3 && Q->axis[1] < 3 && Q->axis[2] < 3) { - P->fwd3d = forward_3d; - P->inv3d = reverse_3d; - } - if (n == 2 && Q->axis[0] < 2 && Q->axis[1] < 2) { - P->fwd = forward_2d; - P->inv = reverse_2d; - } - - - if (P->fwd4d == nullptr && P->fwd3d == nullptr && P->fwd == nullptr) { - proj_log_error(P, "swapaxis: bad axis order"); - return pj_default_destructor(P, PJD_ERR_AXIS); - } - - if (pj_param(P->ctx, P->params, "tangularunits").i) { - P->left = PJ_IO_UNITS_ANGULAR; - P->right = PJ_IO_UNITS_ANGULAR; - } else { - P->left = PJ_IO_UNITS_WHATEVER; - P->right = PJ_IO_UNITS_WHATEVER; - } - - - /* Preparation and finalization steps are skipped, since the raison */ - /* d'etre of axisswap is to bring input coordinates in line with the */ - /* the internally expected order (ENU), such that handling of offsets */ - /* etc. can be done correctly in a later step of a pipeline */ - P->skip_fwd_prepare = 1; - P->skip_fwd_finalize = 1; - P->skip_inv_prepare = 1; - P->skip_inv_finalize = 1; - - return P; -} diff --git a/src/PJ_bacon.cpp b/src/PJ_bacon.cpp deleted file mode 100644 index 6c6350fe..00000000 --- a/src/PJ_bacon.cpp +++ /dev/null @@ -1,81 +0,0 @@ -# define HLFPI2 2.46740110027233965467 /* (pi/2)^2 */ -# define EPS 1e-10 -#define PJ_LIB__ -#include -#include - -#include "projects.h" - - -namespace { // anonymous namespace -struct pj_opaque { - int bacn; - int ortl; -}; -} // anonymous namespace - -PROJ_HEAD(apian, "Apian Globular I") "\n\tMisc Sph, no inv"; -PROJ_HEAD(ortel, "Ortelius Oval") "\n\tMisc Sph, no inv"; -PROJ_HEAD(bacon, "Bacon Globular") "\n\tMisc Sph, no inv"; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double ax, f; - - xy.y = Q->bacn ? M_HALFPI * sin(lp.phi) : lp.phi; - if ((ax = fabs(lp.lam)) >= EPS) { - if (Q->ortl && ax >= M_HALFPI) - xy.x = sqrt(HLFPI2 - lp.phi * lp.phi + EPS) + ax - M_HALFPI; - else { - f = 0.5 * (HLFPI2 / ax + ax); - xy.x = ax - f + sqrt(f * f - xy.y * xy.y); - } - if (lp.lam < 0.) xy.x = - xy.x; - } else - xy.x = 0.; - return (xy); -} - - - -PJ *PROJECTION(bacon) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->bacn = 1; - Q->ortl = 0; - P->es = 0.; - P->fwd = s_forward; - return P; -} - - -PJ *PROJECTION(apian) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->bacn = Q->ortl = 0; - P->es = 0.; - P->fwd = s_forward; - return P; -} - - -PJ *PROJECTION(ortel) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->bacn = 0; - Q->ortl = 1; - P->es = 0.; - P->fwd = s_forward; - return P; -} diff --git a/src/PJ_bertin1953.cpp b/src/PJ_bertin1953.cpp deleted file mode 100644 index 2203d6f1..00000000 --- a/src/PJ_bertin1953.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - Created by Jacques Bertin in 1953, this projection was the go-to choice - of the French cartographic school when they wished to represent phenomena - on a global scale. - - Formula designed by Philippe Rivière, 2017. - https://visionscarto.net/bertin-projection-1953 - - Port to PROJ by Philippe Rivière, 21 September 2018 -*/ - -#define PJ_LIB__ - -#include -#include - -#include "proj_internal.h" -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(bertin1953, "Bertin 1953") - "\n\tMisc Sph no inv."; - -namespace { // anonymous namespace -struct pj_opaque { - double cos_delta_phi, sin_delta_phi, cos_delta_gamma, sin_delta_gamma, deltaLambda; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - double fu = 1.4, k = 12., w = 1.68, d; - - /* Rotate */ - double cosphi, x, y, z, z0; - lp.lam += PJ_TORAD(-16.5); - cosphi = cos(lp.phi); - x = cos(lp.lam) * cosphi; - y = sin(lp.lam) * cosphi; - z = sin(lp.phi); - z0 = z * Q->cos_delta_phi + x * Q->sin_delta_phi; - lp.lam = atan2(y * Q->cos_delta_gamma - z0 * Q->sin_delta_gamma, - x * Q->cos_delta_phi - z * Q->sin_delta_phi); - z0 = z0 * Q->cos_delta_gamma + y * Q->sin_delta_gamma; - lp.phi = asin(z0); - - lp.lam = adjlon(lp.lam); - - /* Adjust pre-projection */ - if (lp.lam + lp.phi < -fu) { - d = (lp.lam - lp.phi + 1.6) * (lp.lam + lp.phi + fu) / 8.; - lp.lam += d; - lp.phi -= 0.8 * d * sin(lp.phi + M_PI / 2.); - } - - /* Project with Hammer (1.68,2) */ - cosphi = cos(lp.phi); - d = sqrt(2./(1. + cosphi * cos(lp.lam / 2.))); - xy.x = w * d * cosphi * sin(lp.lam / 2.); - xy.y = d * sin(lp.phi); - - /* Adjust post-projection */ - d = (1. - cos(lp.lam * lp.phi)) / k; - if (xy.y < 0.) { - xy.x *= 1. + d; - } - if (xy.y > 0.) { - xy.x *= 1. + d / 1.5 * xy.x * xy.x; - } - - return xy; -} - - -PJ *PROJECTION(bertin1953) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - P->lam0 = 0; - P->phi0 = PJ_TORAD(-42.); - - Q->cos_delta_phi = cos(P->phi0); - Q->sin_delta_phi = sin(P->phi0); - Q->cos_delta_gamma = 1.; - Q->sin_delta_gamma = 0.; - - P->es = 0.; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_bipc.cpp b/src/PJ_bipc.cpp deleted file mode 100644 index 19a6bbe1..00000000 --- a/src/PJ_bipc.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#define PJ_LIB__ -#include -#include - -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -PROJ_HEAD(bipc, "Bipolar conic of western hemisphere") "\n\tConic Sph"; - -# define EPS 1e-10 -# define EPS10 1e-10 -# define ONEEPS 1.000000001 -# define NITER 10 -# define lamB -.34894976726250681539 -# define n .63055844881274687180 -# define F 1.89724742567461030582 -# define Azab .81650043674686363166 -# define Azba 1.82261843856185925133 -# define T 1.27246578267089012270 -# define rhoc 1.20709121521568721927 -# define cAzc .69691523038678375519 -# define sAzc .71715351331143607555 -# define C45 .70710678118654752469 -# define S45 .70710678118654752410 -# define C20 .93969262078590838411 -# define S20 -.34202014332566873287 -# define R110 1.91986217719376253360 -# define R104 1.81514242207410275904 - - -namespace { // anonymous namespace -struct pj_opaque { - int noskew; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double cphi, sphi, tphi, t, al, Az, z, Av, cdlam, sdlam, r; - int tag; - - cphi = cos(lp.phi); - sphi = sin(lp.phi); - cdlam = cos(sdlam = lamB - lp.lam); - sdlam = sin(sdlam); - if (fabs(fabs(lp.phi) - M_HALFPI) < EPS10) { - Az = lp.phi < 0. ? M_PI : 0.; - tphi = HUGE_VAL; - } else { - tphi = sphi / cphi; - Az = atan2(sdlam , C45 * (tphi - cdlam)); - } - if( (tag = (Az > Azba)) ) { - cdlam = cos(sdlam = lp.lam + R110); - sdlam = sin(sdlam); - z = S20 * sphi + C20 * cphi * cdlam; - if (fabs(z) > 1.) { - if (fabs(z) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - else z = z < 0. ? -1. : 1.; - } else - z = acos(z); - if (tphi != HUGE_VAL) - Az = atan2(sdlam, (C20 * tphi - S20 * cdlam)); - Av = Azab; - xy.y = rhoc; - } else { - z = S45 * (sphi + cphi * cdlam); - if (fabs(z) > 1.) { - if (fabs(z) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - else z = z < 0. ? -1. : 1.; - } else - z = acos(z); - Av = Azba; - xy.y = -rhoc; - } - if (z < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - r = F * (t = pow(tan(.5 * z), n)); - if ((al = .5 * (R104 - z)) < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - al = (t + pow(al, n)) / T; - if (fabs(al) > 1.) { - if (fabs(al) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - else al = al < 0. ? -1. : 1.; - } else - al = acos(al); - if (fabs(t = n * (Av - Az)) < al) - r /= cos(al + (tag ? t : -t)); - xy.x = r * sin(t); - xy.y += (tag ? -r : r) * cos(t); - if (Q->noskew) { - t = xy.x; - xy.x = -xy.x * cAzc - xy.y * sAzc; - xy.y = -xy.y * cAzc + t * sAzc; - } - return (xy); -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double t, r, rp, rl, al, z = 0.0, fAz, Az, s, c, Av; - int neg, i; - - if (Q->noskew) { - t = xy.x; - xy.x = -xy.x * cAzc + xy.y * sAzc; - xy.y = -xy.y * cAzc - t * sAzc; - } - if( (neg = (xy.x < 0.)) ) { - xy.y = rhoc - xy.y; - s = S20; - c = C20; - Av = Azab; - } else { - xy.y += rhoc; - s = S45; - c = C45; - Av = Azba; - } - rl = rp = r = hypot(xy.x, xy.y); - fAz = fabs(Az = atan2(xy.x, xy.y)); - for (i = NITER; i ; --i) { - z = 2. * atan(pow(r / F,1 / n)); - al = acos((pow(tan(.5 * z), n) + - pow(tan(.5 * (R104 - z)), n)) / T); - if (fAz < al) - r = rp * cos(al + (neg ? Az : -Az)); - if (fabs(rl - r) < EPS) - break; - rl = r; - } - if (! i) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - Az = Av - Az / n; - lp.phi = asin(s * cos(z) + c * sin(z) * cos(Az)); - lp.lam = atan2(sin(Az), c / tan(z) - s * cos(Az)); - if (neg) - lp.lam -= R110; - else - lp.lam = lamB - lp.lam; - return (lp); -} - - -PJ *PROJECTION(bipc) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->noskew = pj_param(P->ctx, P->params, "bns").i; - P->inv = s_inverse; - P->fwd = s_forward; - P->es = 0.; - return P; -} diff --git a/src/PJ_boggs.cpp b/src/PJ_boggs.cpp deleted file mode 100644 index 119357c0..00000000 --- a/src/PJ_boggs.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#define PJ_LIB__ -#include - -#include "projects.h" - -PROJ_HEAD(boggs, "Boggs Eumorphic") "\n\tPCyl, no inv, Sph"; -# define NITER 20 -# define EPS 1e-7 -# define FXC 2.00276 -# define FXC2 1.11072 -# define FYC 0.49931 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double theta, th1, c; - int i; - (void) P; - - theta = lp.phi; - if (fabs(fabs(lp.phi) - M_HALFPI) < EPS) - xy.x = 0.; - else { - c = sin(theta) * M_PI; - for (i = NITER; i; --i) { - theta -= th1 = (theta + sin(theta) - c) / - (1. + cos(theta)); - if (fabs(th1) < EPS) break; - } - theta *= 0.5; - xy.x = FXC * lp.lam / (1. / cos(lp.phi) + FXC2 / cos(theta)); - } - xy.y = FYC * (lp.phi + M_SQRT2 * sin(theta)); - return (xy); -} - - - -PJ *PROJECTION(boggs) { - P->es = 0.; - P->fwd = s_forward; - return P; -} diff --git a/src/PJ_bonne.cpp b/src/PJ_bonne.cpp deleted file mode 100644 index 385c1c4b..00000000 --- a/src/PJ_bonne.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#define PJ_LIB__ -#include -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - - -PROJ_HEAD(bonne, "Bonne (Werner lat_1=90)") - "\n\tConic Sph&Ell\n\tlat_1="; -#define EPS10 1e-10 - -namespace { // anonymous namespace -struct pj_opaque { - double phi1; - double cphi1; - double am1; - double m1; - double *en; -}; -} // anonymous namespace - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double rh, E, c; - - rh = Q->am1 + Q->m1 - pj_mlfn(lp.phi, E = sin(lp.phi), c = cos(lp.phi), Q->en); - E = c * lp.lam / (rh * sqrt(1. - P->es * E * E)); - xy.x = rh * sin(E); - xy.y = Q->am1 - rh * cos(E); - return xy; -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double E, rh; - - rh = Q->cphi1 + Q->phi1 - lp.phi; - if (fabs(rh) > EPS10) { - xy.x = rh * sin(E = lp.lam * cos(lp.phi) / rh); - xy.y = Q->cphi1 - rh * cos(E); - } else - xy.x = xy.y = 0.; - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double rh; - - rh = hypot(xy.x, xy.y = Q->cphi1 - xy.y); - lp.phi = Q->cphi1 + Q->phi1 - rh; - if (fabs(lp.phi) > M_HALFPI) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) - lp.lam = 0.; - else - lp.lam = rh * atan2(xy.x, xy.y) / cos(lp.phi); - return lp; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double s, rh; - - rh = hypot(xy.x, xy.y = Q->am1 - xy.y); - lp.phi = pj_inv_mlfn(P->ctx, Q->am1 + Q->m1 - rh, P->es, Q->en); - if ((s = fabs(lp.phi)) < M_HALFPI) { - s = sin(lp.phi); - lp.lam = rh * atan2(xy.x, xy.y) * - sqrt(1. - P->es * s * s) / cos(lp.phi); - } else if (fabs(s - M_HALFPI) <= EPS10) - lp.lam = 0.; - else { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - return lp; -} - - - -static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->en); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(bonne) { - double c; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; - if (fabs(Q->phi1) < EPS10) - return destructor (P, PJD_ERR_LAT1_IS_ZERO); - - if (P->es != 0.0) { - Q->en = pj_enfn(P->es); - if (nullptr==Q->en) - return destructor(P, ENOMEM); - Q->m1 = pj_mlfn(Q->phi1, Q->am1 = sin(Q->phi1), - c = cos(Q->phi1), Q->en); - Q->am1 = c / (sqrt(1. - P->es * Q->am1 * Q->am1) * Q->am1); - P->inv = e_inverse; - P->fwd = e_forward; - } else { - if (fabs(Q->phi1) + EPS10 >= M_HALFPI) - Q->cphi1 = 0.; - else - Q->cphi1 = 1. / tan(Q->phi1); - P->inv = s_inverse; - P->fwd = s_forward; - } - return P; -} - - diff --git a/src/PJ_calcofi.cpp b/src/PJ_calcofi.cpp deleted file mode 100644 index e81e4d2a..00000000 --- a/src/PJ_calcofi.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "proj.h" -#include "projects.h" -#include "proj_api.h" - -PROJ_HEAD(calcofi, - "Cal Coop Ocean Fish Invest Lines/Stations") "\n\tCyl, Sph&Ell"; - - -/* Conversions for the California Cooperative Oceanic Fisheries Investigations -Line/Station coordinate system following the algorithm of: -Eber, L.E., and R.P. Hewitt. 1979. Conversion algorithms for the CalCOFI -station grid. California Cooperative Oceanic Fisheries Investigations Reports -20:135-137. (corrected for typographical errors). -http://www.calcofi.org/publications/calcofireports/v20/Vol_20_Eber___Hewitt.pdf -They assume 1 unit of CalCOFI Line == 1/5 degree in longitude or -meridional units at reference point O, and similarly 1 unit of CalCOFI -Station == 1/15 of a degree at O. -By convention, CalCOFI Line/Station conversions use Clarke 1866 but we use -whatever ellipsoid is provided. */ - - -#define EPS10 1.e-10 -#define DEG_TO_LINE 5 -#define DEG_TO_STATION 15 -#define LINE_TO_RAD 0.0034906585039886592 -#define STATION_TO_RAD 0.0011635528346628863 -#define PT_O_LINE 80 /* reference point O is at line 80, */ -#define PT_O_STATION 60 /* station 60, */ -#define PT_O_LAMBDA -2.1144663887911301 /* lon -121.15 and */ -#define PT_O_PHI 0.59602993955606354 /* lat 34.15 */ -#define ROTATION_ANGLE 0.52359877559829882 /*CalCOFI angle of 30 deg in rad */ - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - double oy; /* pt O y value in Mercator */ - double l1; /* l1 and l2 are distances calculated using trig that sum - to the east/west distance between point O and point xy */ - double l2; - double ry; /* r is the point on the same station as o (60) and the same - line as xy xy, r, o form a right triangle */ - - if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - - xy.x = lp.lam; - xy.y = -log(pj_tsfn(lp.phi, sin(lp.phi), P->e)); /* Mercator transform xy*/ - oy = -log(pj_tsfn(PT_O_PHI, sin(PT_O_PHI), P->e)); - l1 = (xy.y - oy) * tan(ROTATION_ANGLE); - l2 = -xy.x - l1 + PT_O_LAMBDA; - ry = l2 * cos(ROTATION_ANGLE) * sin(ROTATION_ANGLE) + xy.y; - ry = pj_phi2(P->ctx, exp(-ry), P->e); /*inverse Mercator*/ - xy.x = PT_O_LINE - RAD_TO_DEG * - (ry - PT_O_PHI) * DEG_TO_LINE / cos(ROTATION_ANGLE); - xy.y = PT_O_STATION + RAD_TO_DEG * - (ry - lp.phi) * DEG_TO_STATION / sin(ROTATION_ANGLE); - /* set a = 1, x0 = 0, and y0 = 0 so that no further unit adjustments - are done */ - - return xy; -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double oy; - double l1; - double l2; - double ry; - if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - xy.x = lp.lam; - xy.y = log(tan(M_FORTPI + .5 * lp.phi)); - oy = log(tan(M_FORTPI + .5 * PT_O_PHI)); - l1 = (xy.y - oy) * tan(ROTATION_ANGLE); - l2 = -xy.x - l1 + PT_O_LAMBDA; - ry = l2 * cos(ROTATION_ANGLE) * sin(ROTATION_ANGLE) + xy.y; - ry = M_HALFPI - 2. * atan(exp(-ry)); - xy.x = PT_O_LINE - RAD_TO_DEG * - (ry - PT_O_PHI) * DEG_TO_LINE / cos(ROTATION_ANGLE); - xy.y = PT_O_STATION + RAD_TO_DEG * - (ry - lp.phi) * DEG_TO_STATION / sin(ROTATION_ANGLE); - - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - double ry; /* y value of point r */ - double oymctr; /* Mercator-transformed y value of point O */ - double rymctr; /* Mercator-transformed ry */ - double xymctr; /* Mercator-transformed xy.y */ - double l1; - double l2; - - ry = PT_O_PHI - LINE_TO_RAD * (xy.x - PT_O_LINE) * - cos(ROTATION_ANGLE); - lp.phi = ry - STATION_TO_RAD * (xy.y - PT_O_STATION) * sin(ROTATION_ANGLE); - oymctr = -log(pj_tsfn(PT_O_PHI, sin(PT_O_PHI), P->e)); - rymctr = -log(pj_tsfn(ry, sin(ry), P->e)); - xymctr = -log(pj_tsfn(lp.phi, sin(lp.phi), P->e)); - l1 = (xymctr - oymctr) * tan(ROTATION_ANGLE); - l2 = (rymctr - xymctr) / (cos(ROTATION_ANGLE) * sin(ROTATION_ANGLE)); - lp.lam = PT_O_LAMBDA - (l1 + l2); - - return lp; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double ry; - double oymctr; - double rymctr; - double xymctr; - double l1; - double l2; - (void) P; - - ry = PT_O_PHI - LINE_TO_RAD * (xy.x - PT_O_LINE) * - cos(ROTATION_ANGLE); - lp.phi = ry - STATION_TO_RAD * (xy.y - PT_O_STATION) * sin(ROTATION_ANGLE); - oymctr = log(tan(M_FORTPI + .5 * PT_O_PHI)); - rymctr = log(tan(M_FORTPI + .5 * ry)); - xymctr = log(tan(M_FORTPI + .5 * lp.phi)); - l1 = (xymctr - oymctr) * tan(ROTATION_ANGLE); - l2 = (rymctr - xymctr) / (cos(ROTATION_ANGLE) * sin(ROTATION_ANGLE)); - lp.lam = PT_O_LAMBDA - (l1 + l2); - - return lp; -} - - -PJ *PROJECTION(calcofi) { - P->opaque = nullptr; - - /* if the user has specified +lon_0 or +k0 for some reason, - we're going to ignore it so that xy is consistent with point O */ - P->lam0 = 0; - P->ra = 1; - P->a = 1; - P->x0 = 0; - P->y0 = 0; - P->over = 1; - - if (P->es != 0.0) { /* ellipsoid */ - P->inv = e_inverse; - P->fwd = e_forward; - } else { /* sphere */ - P->inv = s_inverse; - P->fwd = s_forward; - } - return P; -} diff --git a/src/PJ_cart.cpp b/src/PJ_cart.cpp deleted file mode 100644 index 6fed9985..00000000 --- a/src/PJ_cart.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Convert between ellipsoidal, geodetic coordinates and - * cartesian, geocentric coordinates. - * - * Formally, this functionality is also found in the PJ_geocent.c - * code. - * - * Actually, however, the PJ_geocent transformations are carried - * out in concert between 2D stubs in PJ_geocent.c and 3D code - * placed in pj_transform.c. - * - * For pipeline-style datum shifts, we do need direct access - * to the full 3D interface for this functionality. - * - * Hence this code, which may look like "just another PJ_geocent" - * but really is something substantially different. - * - * Author: Thomas Knudsen, thokn@sdfe.dk - * - ****************************************************************************** - * Copyright (c) 2016, Thomas Knudsen / SDFE - * - * 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. - *****************************************************************************/ - -#define PJ_LIB__ - -#include "proj_internal.h" -#include "projects.h" -#include "proj_math.h" - -PROJ_HEAD(cart, "Geodetic/cartesian conversions"); - - -/************************************************************** - CARTESIAN / GEODETIC CONVERSIONS -*************************************************************** - This material follows: - - Bernhard Hofmann-Wellenhof & Helmut Moritz: - Physical Geodesy, 2nd edition. - Springer, 2005. - - chapter 5.6: Coordinate transformations - (HM, below), - - and - - Wikipedia: Geographic Coordinate Conversion, - https://en.wikipedia.org/wiki/Geographic_coordinate_conversion - - (WP, below). - - The cartesian-to-geodetic conversion is based on Bowring's - celebrated method: - - B. R. Bowring: - Transformation from spatial to geographical coordinates - Survey Review 23(181), pp. 323-327, 1976 - - (BB, below), - - but could probably use some TLC from a newer and faster - algorithm: - - Toshio Fukushima: - Transformation from Cartesian to Geodetic Coordinates - Accelerated by Halley’s Method - Journal of Geodesy, February 2006 - - (TF, below). - - Close to the poles, we avoid singularities by switching to an - approximation requiring knowledge of the geocentric radius - at the given latitude. For this, we use an adaptation of the - formula given in: - - Wikipedia: Earth Radius - https://en.wikipedia.org/wiki/Earth_radius#Radius_at_a_given_geodetic_latitude - (Derivation and commentary at https://gis.stackexchange.com/q/20200) - - (WP2, below) - - These routines are probably not as robust at those in - geocent.c, at least thay haven't been through as heavy - use as their geocent sisters. Some care has been taken - to avoid singularities, but extreme cases (e.g. setting - es, the squared eccentricity, to 1), will cause havoc. - -**************************************************************/ - - -/*********************************************************************/ -static double normal_radius_of_curvature (double a, double es, double phi) { -/*********************************************************************/ - double s = sin(phi); - if (es==0) - return a; - /* This is from WP. HM formula 2-149 gives an a,b version */ - return a / sqrt (1 - es*s*s); -} - -/*********************************************************************/ -static double geocentric_radius (double a, double b, double phi) { -/********************************************************************* - Return the geocentric radius at latitude phi, of an ellipsoid - with semimajor axis a and semiminor axis b. - - This is from WP2, but uses hypot() for potentially better - numerical robustness -***********************************************************************/ - return hypot (a*a*cos (phi), b*b*sin(phi)) / hypot (a*cos(phi), b*sin(phi)); -} - - -/*********************************************************************/ -static XYZ cartesian (LPZ geod, PJ *P) { -/*********************************************************************/ - double N, cosphi = cos(geod.phi); - XYZ xyz; - - N = normal_radius_of_curvature(P->a, P->es, geod.phi); - - /* HM formula 5-27 (z formula follows WP) */ - xyz.x = (N + geod.z) * cosphi * cos(geod.lam); - xyz.y = (N + geod.z) * cosphi * sin(geod.lam); - xyz.z = (N * (1 - P->es) + geod.z) * sin(geod.phi); - - return xyz; -} - - -/*********************************************************************/ -static LPZ geodetic (XYZ cart, PJ *P) { -/*********************************************************************/ - double N, p, theta, c, s; - LPZ lpz; - - /* Perpendicular distance from point to Z-axis (HM eq. 5-28) */ - p = hypot (cart.x, cart.y); - - /* HM eq. (5-37) */ - theta = atan2 (cart.z * P->a, p * P->b); - - /* HM eq. (5-36) (from BB, 1976) */ - c = cos(theta); - s = sin(theta); - lpz.phi = atan2 (cart.z + P->e2s*P->b*s*s*s, p - P->es*P->a*c*c*c); - lpz.lam = atan2 (cart.y, cart.x); - N = normal_radius_of_curvature (P->a, P->es, lpz.phi); - - - c = cos(lpz.phi); - if (fabs(c) < 1e-6) { - /* poleward of 89.99994 deg, we avoid division by zero */ - /* by computing the height as the cartesian z value */ - /* minus the geocentric radius of the Earth at the given */ - /* latitude */ - double r = geocentric_radius (P->a, P->b, lpz.phi); - lpz.z = fabs (cart.z) - r; - } - else - lpz.z = p / c - N; - - return lpz; -} - - - -/* In effect, 2 cartesian coordinates of a point on the ellipsoid. Rather pointless, but... */ -static XY cart_forward (LP lp, PJ *P) { - PJ_COORD point; - point.lp = lp; - point.lpz.z = 0; - - point.xyz = cartesian (point.lpz, P); - return point.xy; -} - -/* And the other way round. Still rather pointless, but... */ -static LP cart_reverse (XY xy, PJ *P) { - PJ_COORD point; - point.xy = xy; - point.xyz.z = 0; - - point.lpz = geodetic (point.xyz, P); - return point.lp; -} - - - -/*********************************************************************/ -PJ *CONVERSION(cart,1) { -/*********************************************************************/ - P->fwd3d = cartesian; - P->inv3d = geodetic; - P->fwd = cart_forward; - P->inv = cart_reverse; - P->left = PJ_IO_UNITS_ANGULAR; - P->right = PJ_IO_UNITS_CARTESIAN; - return P; -} diff --git a/src/PJ_cass.cpp b/src/PJ_cass.cpp deleted file mode 100644 index c831558c..00000000 --- a/src/PJ_cass.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(cass, "Cassini") "\n\tCyl, Sph&Ell"; - - -# define C1 .16666666666666666666 -# define C2 .00833333333333333333 -# define C3 .04166666666666666666 -# define C4 .33333333333333333333 -# define C5 .06666666666666666666 - - -namespace { // anonymous namespace -struct pj_opaque { - double *en; - double m0; -}; -} // anonymous namespace - - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - double n, t, a1, c, a2, tn; - XY xy = {0.0, 0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - xy.y = pj_mlfn (lp.phi, n = sin (lp.phi), c = cos (lp.phi), Q->en); - - n = 1./sqrt(1. - P->es * n*n); - tn = tan(lp.phi); t = tn * tn; - a1 = lp.lam * c; - c *= P->es * c / (1 - P->es); - a2 = a1 * a1; - - xy.x = n * a1 * (1. - a2 * t * - (C1 - (8. - t + 8. * c) * a2 * C2)); - xy.y -= Q->m0 - n * tn * a2 * - (.5 + (5. - t + 6. * c) * a2 * C3); - - return xy; -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - xy.x = asin (cos (lp.phi) * sin (lp.lam)); - xy.y = atan2 (tan (lp.phi), cos (lp.lam)) - P->phi0; - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - double n, t, r, dd, d2, tn, ph1; - LP lp = {0.0, 0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - ph1 = pj_inv_mlfn (P->ctx, Q->m0 + xy.y, P->es, Q->en); - tn = tan (ph1); t = tn*tn; - n = sin (ph1); - r = 1. / (1. - P->es * n * n); - n = sqrt (r); - r *= (1. - P->es) * n; - dd = xy.x / n; - d2 = dd * dd; - lp.phi = ph1 - (n * tn / r) * d2 * - (.5 - (1. + 3. * t) * d2 * C3); - lp.lam = dd * (1. + t * d2 * - (-C4 + (1. + 3. * t) * d2 * C5)) / cos (ph1); - return lp; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double dd; - lp.phi = asin(sin(dd = xy.y + P->phi0) * cos(xy.x)); - lp.lam = atan2(tan(xy.x), cos(dd)); - return lp; -} - -static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->en); - return pj_default_destructor (P, errlev); -} - - - -PJ *PROJECTION(cass) { - - /* Spheroidal? */ - if (0==P->es) { - P->inv = s_inverse; - P->fwd = s_forward; - return P; - } - - /* otherwise it's ellipsoidal */ - P->opaque = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==P->opaque) - return pj_default_destructor (P, ENOMEM); - P->destructor = destructor; - - static_cast(P->opaque)->en = pj_enfn (P->es); - if (nullptr==static_cast(P->opaque)->en) - return pj_default_destructor (P, ENOMEM); - - static_cast(P->opaque)->m0 = pj_mlfn (P->phi0, sin (P->phi0), cos (P->phi0), static_cast(P->opaque)->en); - P->inv = e_inverse; - P->fwd = e_forward; - - return P; -} diff --git a/src/PJ_cc.cpp b/src/PJ_cc.cpp deleted file mode 100644 index 152e6e4a..00000000 --- a/src/PJ_cc.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(cc, "Central Cylindrical") "\n\tCyl, Sph"; -#define EPS10 1.e-10 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - if (fabs (fabs(lp.phi) - M_HALFPI) <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - xy.x = lp.lam; - xy.y = tan(lp.phi); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - (void) P; - lp.phi = atan(xy.y); - lp.lam = xy.x; - return lp; -} - - - -PJ *PROJECTION(cc) { - P->es = 0.; - - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_ccon.cpp b/src/PJ_ccon.cpp deleted file mode 100644 index 4f7dedb4..00000000 --- a/src/PJ_ccon.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2017, Lukasz Komsta - * - * 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. - *****************************************************************************/ - -#define PJ_LIB__ -#include -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -#define EPS10 1e-10 - -namespace { // anonymous namespace -struct pj_opaque { - double phi1; - double ctgphi1; - double sinphi1; - double cosphi1; - double *en; -}; -} // anonymous namespace - -PROJ_HEAD(ccon, "Central Conic") - "\n\tCentral Conic, Sph\n\tlat_1="; - - - -static XY forward (LP lp, PJ *P) { - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double r; - - r = Q->ctgphi1 - tan(lp.phi - Q->phi1); - xy.x = r * sin(lp.lam * Q->sinphi1); - xy.y = Q->ctgphi1 - r * cos(lp.lam * Q->sinphi1); - - return xy; -} - - -static LP inverse (XY xy, PJ *P) { - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - xy.y = Q->ctgphi1 - xy.y; - lp.phi = Q->phi1 - atan(hypot(xy.x,xy.y) - Q->ctgphi1); - lp.lam = atan2(xy.x,xy.y)/Q->sinphi1; - - return lp; -} - - -static PJ *destructor (PJ *P, int errlev) { - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->en); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(ccon) { - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; - if (fabs(Q->phi1) < EPS10) - return destructor (P, PJD_ERR_LAT1_IS_ZERO); - - if (!(Q->en = pj_enfn(P->es))) - return destructor(P, ENOMEM); - - Q->sinphi1 = sin(Q->phi1); - Q->cosphi1 = cos(Q->phi1); - Q->ctgphi1 = Q->cosphi1/Q->sinphi1; - - - P->inv = inverse; - P->fwd = forward; - - return P; -} - - diff --git a/src/PJ_cea.cpp b/src/PJ_cea.cpp deleted file mode 100644 index f8275b62..00000000 --- a/src/PJ_cea.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -namespace { // anonymous namespace -struct pj_opaque { - double qp; - double *apa; -}; -} // anonymous namespace - -PROJ_HEAD(cea, "Equal Area Cylindrical") "\n\tCyl, Sph&Ell\n\tlat_ts="; -# define EPS 1e-10 - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - xy.x = P->k0 * lp.lam; - xy.y = 0.5 * pj_qsfn (sin (lp.phi), P->e, P->one_es) / P->k0; - return xy; -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - xy.x = P->k0 * lp.lam; - xy.y = sin(lp.phi) / P->k0; - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - lp.phi = pj_authlat(asin( 2. * xy.y * P->k0 / static_cast(P->opaque)->qp), static_cast(P->opaque)->apa); - lp.lam = xy.x / P->k0; - return lp; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double t; - - if ((t = fabs(xy.y *= P->k0)) - EPS <= 1.) { - if (t >= 1.) - lp.phi = xy.y < 0. ? -M_HALFPI : M_HALFPI; - else - lp.phi = asin(xy.y); - lp.lam = xy.x / P->k0; - } else { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - return (lp); -} - -static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->apa); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(cea) { - double t = 0.0; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - - if (pj_param(P->ctx, P->params, "tlat_ts").i) { - P->k0 = cos(t = pj_param(P->ctx, P->params, "rlat_ts").f); - if (P->k0 < 0.) - return pj_default_destructor (P, PJD_ERR_LAT_TS_LARGER_THAN_90); - } - if (P->es != 0.0) { - t = sin(t); - P->k0 /= sqrt(1. - P->es * t * t); - P->e = sqrt(P->es); - if (!(Q->apa = pj_authset(P->es))) - return pj_default_destructor(P, ENOMEM); - - Q->qp = pj_qsfn(1., P->e, P->one_es); - P->inv = e_inverse; - P->fwd = e_forward; - } else { - P->inv = s_inverse; - P->fwd = s_forward; - } - - return P; -} diff --git a/src/PJ_chamb.cpp b/src/PJ_chamb.cpp deleted file mode 100644 index a490e817..00000000 --- a/src/PJ_chamb.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -typedef struct { double r, Az; } VECT; -namespace { // anonymous namespace -struct pj_opaque { - struct { /* control point data */ - double phi, lam; - double cosphi, sinphi; - VECT v; - XY p; - double Az; - } c[3]; - XY p; - double beta_0, beta_1, beta_2; -}; -} // anonymous namespace - -PROJ_HEAD(chamb, "Chamberlin Trimetric") "\n\tMisc Sph, no inv" -"\n\tlat_1= lon_1= lat_2= lon_2= lat_3= lon_3="; - -#include -#define THIRD 0.333333333333333333 -#define TOL 1e-9 - -/* distance and azimuth from point 1 to point 2 */ -static VECT vect(projCtx ctx, double dphi, double c1, double s1, double c2, double s2, double dlam) { - VECT v; - double cdl, dp, dl; - - cdl = cos(dlam); - if (fabs(dphi) > 1. || fabs(dlam) > 1.) - v.r = aacos(ctx, s1 * s2 + c1 * c2 * cdl); - else { /* more accurate for smaller distances */ - dp = sin(.5 * dphi); - dl = sin(.5 * dlam); - v.r = 2. * aasin(ctx,sqrt(dp * dp + c1 * c2 * dl * dl)); - } - if (fabs(v.r) > TOL) - v.Az = atan2(c2 * sin(dlam), c1 * s2 - s1 * c2 * cdl); - else - v.r = v.Az = 0.; - return v; -} - -/* law of cosines */ -static double lc(projCtx ctx, double b,double c,double a) { - return aacos(ctx, .5 * (b * b + c * c - a * a) / (b * c)); -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy; - struct pj_opaque *Q = static_cast(P->opaque); - double sinphi, cosphi, a; - VECT v[3]; - int i, j; - - sinphi = sin(lp.phi); - cosphi = cos(lp.phi); - for (i = 0; i < 3; ++i) { /* dist/azimiths from control */ - v[i] = vect(P->ctx, lp.phi - Q->c[i].phi, Q->c[i].cosphi, Q->c[i].sinphi, - cosphi, sinphi, lp.lam - Q->c[i].lam); - if (v[i].r == 0.0) - break; - v[i].Az = adjlon(v[i].Az - Q->c[i].v.Az); - } - if (i < 3) /* current point at control point */ - xy = Q->c[i].p; - else { /* point mean of intersepts */ - xy = Q->p; - for (i = 0; i < 3; ++i) { - j = i == 2 ? 0 : i + 1; - a = lc(P->ctx,Q->c[i].v.r, v[i].r, v[j].r); - if (v[i].Az < 0.) - a = -a; - if (! i) { /* coord comp unique to each arc */ - xy.x += v[i].r * cos(a); - xy.y -= v[i].r * sin(a); - } else if (i == 1) { - a = Q->beta_1 - a; - xy.x -= v[i].r * cos(a); - xy.y -= v[i].r * sin(a); - } else { - a = Q->beta_2 - a; - xy.x += v[i].r * cos(a); - xy.y += v[i].r * sin(a); - } - } - xy.x *= THIRD; /* mean of arc intercepts */ - xy.y *= THIRD; - } - return xy; -} - - - -PJ *PROJECTION(chamb) { - int i, j; - char line[10]; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - - for (i = 0; i < 3; ++i) { /* get control point locations */ - (void)sprintf(line, "rlat_%d", i+1); - Q->c[i].phi = pj_param(P->ctx, P->params, line).f; - (void)sprintf(line, "rlon_%d", i+1); - Q->c[i].lam = pj_param(P->ctx, P->params, line).f; - Q->c[i].lam = adjlon(Q->c[i].lam - P->lam0); - Q->c[i].cosphi = cos(Q->c[i].phi); - Q->c[i].sinphi = sin(Q->c[i].phi); - } - for (i = 0; i < 3; ++i) { /* inter ctl pt. distances and azimuths */ - j = i == 2 ? 0 : i + 1; - Q->c[i].v = vect(P->ctx,Q->c[j].phi - Q->c[i].phi, Q->c[i].cosphi, Q->c[i].sinphi, - Q->c[j].cosphi, Q->c[j].sinphi, Q->c[j].lam - Q->c[i].lam); - if (Q->c[i].v.r == 0.0) - return pj_default_destructor (P, PJD_ERR_CONTROL_POINT_NO_DIST); - /* co-linearity problem ignored for now */ - } - Q->beta_0 = lc(P->ctx,Q->c[0].v.r, Q->c[2].v.r, Q->c[1].v.r); - Q->beta_1 = lc(P->ctx,Q->c[0].v.r, Q->c[1].v.r, Q->c[2].v.r); - Q->beta_2 = M_PI - Q->beta_0; - Q->p.y = 2. * (Q->c[0].p.y = Q->c[1].p.y = Q->c[2].v.r * sin(Q->beta_0)); - Q->c[2].p.y = 0.; - Q->c[0].p.x = - (Q->c[1].p.x = 0.5 * Q->c[0].v.r); - Q->p.x = Q->c[2].p.x = Q->c[0].p.x + Q->c[2].v.r * cos(Q->beta_0); - - P->es = 0.; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_collg.cpp b/src/PJ_collg.cpp deleted file mode 100644 index 7904de29..00000000 --- a/src/PJ_collg.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(collg, "Collignon") "\n\tPCyl, Sph"; -#define FXC 1.12837916709551257390 -#define FYC 1.77245385090551602729 -#define ONEEPS 1.0000001 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - if ((xy.y = 1. - sin(lp.phi)) <= 0.) - xy.y = 0.; - else - xy.y = sqrt(xy.y); - xy.x = FXC * lp.lam * xy.y; - xy.y = FYC * (1. - xy.y); - return (xy); -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - lp.phi = xy.y / FYC - 1.; - if (fabs(lp.phi = 1. - lp.phi * lp.phi) < 1.) - lp.phi = asin(lp.phi); - else if (fabs(lp.phi) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } else { - lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; - } - - if ((lp.lam = 1. - sin(lp.phi)) <= 0.) - lp.lam = 0.; - else - lp.lam = xy.x / (FXC * sqrt(lp.lam)); - return (lp); -} - - -PJ *PROJECTION(collg) { - P->es = 0.0; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_comill.cpp b/src/PJ_comill.cpp deleted file mode 100644 index b6e0192e..00000000 --- a/src/PJ_comill.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* -The Compact Miller projection was designed by Tom Patterson, US National -Park Service, in 2014. The polynomial equation was developed by Bojan -Savric and Bernhard Jenny, College of Earth, Ocean, and Atmospheric -Sciences, Oregon State University. -Port to PROJ.4 by Bojan Savric, 4 April 2016 -*/ - -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(comill, "Compact Miller") "\n\tCyl, Sph"; - -#define K1 0.9902 -#define K2 0.1604 -#define K3 -0.03054 -#define C1 K1 -#define C2 (3 * K2) -#define C3 (5 * K3) -#define EPS 1e-11 -#define MAX_Y (0.6000207669862655 * M_PI) -/* Not sure at all of the appropriate number for MAX_ITER... */ -#define MAX_ITER 100 - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double lat_sq; - - (void) P; /* silence unused parameter warnings */ - - lat_sq = lp.phi * lp.phi; - xy.x = lp.lam; - xy.y = lp.phi * (K1 + lat_sq * (K2 + K3 * lat_sq)); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double yc, tol, y2, f, fder; - int i; - - (void) P; /* silence unused parameter warnings */ - - /* make sure y is inside valid range */ - if (xy.y > MAX_Y) { - xy.y = MAX_Y; - } else if (xy.y < -MAX_Y) { - xy.y = -MAX_Y; - } - - /* latitude */ - yc = xy.y; - for (i = MAX_ITER; i ; --i) { /* Newton-Raphson */ - y2 = yc * yc; - f = (yc * (K1 + y2 * (K2 + K3 * y2))) - xy.y; - fder = C1 + y2 * (C2 + C3 * y2); - yc -= tol = f / fder; - if (fabs(tol) < EPS) { - break; - } - } - if( i == 0 ) - pj_ctx_set_errno( P->ctx, PJD_ERR_NON_CONVERGENT ); - lp.phi = yc; - - /* longitude */ - lp.lam = xy.x; - - return lp; -} - - -PJ *PROJECTION(comill) { - P->es = 0; - - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_crast.cpp b/src/PJ_crast.cpp deleted file mode 100644 index 4e4dee8b..00000000 --- a/src/PJ_crast.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#define PJ_LIB__ -#include - -#include "projects.h" - -PROJ_HEAD(crast, "Craster Parabolic (Putnins P4)") "\n\tPCyl, Sph"; - -#define XM 0.97720502380583984317 -#define RXM 1.02332670794648848847 -#define YM 3.06998012383946546542 -#define RYM 0.32573500793527994772 -#define THIRD 0.333333333333333333 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - lp.phi *= THIRD; - xy.x = XM * lp.lam * (2. * cos(lp.phi + lp.phi) - 1.); - xy.y = YM * sin(lp.phi); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - (void) P; - lp.phi = 3. * asin(xy.y * RYM); - lp.lam = xy.x * RXM / (2. * cos((lp.phi + lp.phi) * THIRD) - 1); - return lp; -} - - -PJ *PROJECTION(crast) { - P->es = 0.0; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_deformation.cpp b/src/PJ_deformation.cpp deleted file mode 100644 index 6c30f21c..00000000 --- a/src/PJ_deformation.cpp +++ /dev/null @@ -1,325 +0,0 @@ -/*********************************************************************** - - Kinematic datum shifting utilizing a deformation model - - Kristian Evers, 2017-10-29 - -************************************************************************ - -Perform datum shifts by means of a deformation/velocity model. - - X_out = X_in + (T_ct - T_obs)*DX - Y_out = Y_in + (T_ct - T_obs)*DY - Z_out = Z_in + (T_ct - T_obs)*DZ - - -The deformation operation takes cartesian coordinates as input and -returns cartesian coordinates as well. - -Corrections in the gridded model are in east, north, up (ENU) space. -Hence the input coordinates needs to be converted to ENU-space when -searching for corrections in the grid. The corrections are then converted -to cartesian XYZ-space and applied to the input coordinates (also in -cartesian space). - -A full deformation model is described by two grids, one for the horizontal -components and one for the vertical component. The horizontal grid is -stored in CTable/CTable2 and the vertical grid is stored in the GTX -format. The NTv2 format should not be used for this purpose since grid- -values are scaled upon reading. Both grids are expected to contain -grid-values in units of mm/year in ENU-space. - -************************************************************************ -* Copyright (c) 2017, Kristian Evers -* -* 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. -* -***********************************************************************/ -#define PJ_LIB__ -#include -#include "proj.h" -#include "proj_internal.h" -#include "proj_math.h" -#include "projects.h" - -PROJ_HEAD(deformation, "Kinematic grid shift"); - -#define TOL 1e-8 -#define MAX_ITERATIONS 10 - -namespace { // anonymous namespace -struct pj_opaque { - double t_obs; - double t_epoch; - PJ *cart; -}; -} // anonymous namespace - -/********************************************************************************/ -static XYZ get_grid_shift(PJ* P, XYZ cartesian) { -/******************************************************************************** - Read correction values from grid. The cartesian input coordinates are - converted to geodetic coordinates in order look up the correction values - in the grid. Once the grid corrections are read we need to convert them - from ENU-space to cartesian XYZ-space. ENU -> XYZ formula described in: - - Nørbech, T., et al, 2003(?), "Transformation from a Common Nordic Reference - Frame to ETRS89 in Denmark, Finland, Norway, and Sweden – status report" - -********************************************************************************/ - PJ_COORD geodetic, shift, temp; - double sp, cp, sl, cl; - int previous_errno = proj_errno_reset(P); - - /* cartesian to geodetic */ - geodetic.lpz = pj_inv3d(cartesian, static_cast(P->opaque)->cart); - - /* look up correction values in grids */ - shift.lp = proj_hgrid_value(P, geodetic.lp); - shift.enu.u = proj_vgrid_value(P, geodetic.lp); - - if (proj_errno(P) == PJD_ERR_GRID_AREA) - proj_log_debug(P, "deformation: coordinate (%.3f, %.3f) outside deformation model", - proj_todeg(geodetic.lp.lam), proj_todeg(geodetic.lp.phi)); - - /* grid values are stored as mm/yr, we need m/yr */ - shift.xyz.x /= 1000; - shift.xyz.y /= 1000; - shift.xyz.z /= 1000; - - /* pre-calc cosines and sines */ - sp = sin(geodetic.lp.phi); - cp = cos(geodetic.lp.phi); - sl = sin(geodetic.lp.lam); - cl = cos(geodetic.lp.lam); - - /* ENU -> XYZ */ - temp.xyz.x = -sp*cl*shift.enu.n - sl*shift.enu.e + cp*cl*shift.enu.u; - temp.xyz.y = -sp*sl*shift.enu.n + cl*shift.enu.e + cp*sl*shift.enu.u; - temp.xyz.z = cp*shift.enu.n + sp*shift.enu.u; - - shift.xyz = temp.xyz; - - proj_errno_restore(P, previous_errno); - - return shift.xyz; -} - -/********************************************************************************/ -static XYZ reverse_shift(PJ *P, XYZ input, double dt) { -/******************************************************************************** - Iteratively determine the reverse grid shift correction values. -*********************************************************************************/ - XYZ out, delta, dif; - double z0; - int i = MAX_ITERATIONS; - - delta = get_grid_shift(P, input); - - /* Store the origial z shift for later application */ - z0 = delta.z; - - /* When iterating to find the best horizontal coordinate we also carry */ - /* along the z-component, since we need it for the cartesian -> geodetic */ - /* conversion. The z-component adjustment is overwritten with z0 after */ - /* the loop has finished. */ - out.x = input.x - dt*delta.x; - out.y = input.y - dt*delta.y; - out.z = input.z + dt*delta.z; - - do { - delta = get_grid_shift(P, out); - - if (delta.x == HUGE_VAL) - break; - - dif.x = out.x + dt*delta.x - input.x; - dif.y = out.y + dt*delta.y - input.y; - dif.z = out.z - dt*delta.z - input.z; - out.x += dif.x; - out.y += dif.y; - out.z += dif.z; - - } while ( --i && hypot(dif.x, dif.y) > TOL ); - - out.z = input.z - dt*z0; - - return out; -} - -static XYZ forward_3d(LPZ lpz, PJ *P) { - struct pj_opaque *Q = (struct pj_opaque *) P->opaque; - PJ_COORD out, in; - XYZ shift; - double dt = 0.0; - in.lpz = lpz; - out = in; - - if (Q->t_obs != HUGE_VAL) { - dt = Q->t_epoch - Q->t_obs; - } else { - out = proj_coord_error(); /* in the 3D case +t_obs must be specified */ - proj_log_debug(P, "deformation: +t_obs must be specified"); - return out.xyz; - } - - shift = get_grid_shift(P, in.xyz); - - out.xyz.x += dt * shift.x; - out.xyz.y += dt * shift.y; - out.xyz.z += dt * shift.z; - - return out.xyz; -} - - -static PJ_COORD forward_4d(PJ_COORD in, PJ *P) { - struct pj_opaque *Q = (struct pj_opaque *) P->opaque; - double dt; - XYZ shift; - PJ_COORD out = in; - - if (Q->t_obs != HUGE_VAL) { - dt = Q->t_epoch - Q->t_obs; - } else { - dt = Q->t_epoch - in.xyzt.t; - } - - shift = get_grid_shift(P, in.xyz); - - out.xyzt.x += dt*shift.x; - out.xyzt.y += dt*shift.y; - out.xyzt.z += dt*shift.z; - - - return out; -} - - -static LPZ reverse_3d(XYZ in, PJ *P) { - struct pj_opaque *Q = (struct pj_opaque *) P->opaque; - PJ_COORD out; - double dt = 0.0; - out.xyz = in; - - if (Q->t_obs != HUGE_VAL) { - dt = Q->t_epoch - Q->t_obs; - } else { - out = proj_coord_error(); /* in the 3D case +t_obs must be specified */ - proj_log_debug(P, "deformation: +t_obs must be specified"); - return out.lpz; - } - - out.xyz = reverse_shift(P, in, dt); - - return out.lpz; -} - -static PJ_COORD reverse_4d(PJ_COORD in, PJ *P) { - struct pj_opaque *Q = (struct pj_opaque *) P->opaque; - PJ_COORD out = in; - double dt; - - - if (Q->t_obs != HUGE_VAL) { - dt = Q->t_epoch - Q->t_obs; - } else { - dt = Q->t_epoch - in.xyzt.t; - } - - out.xyz = reverse_shift(P, in.xyz, dt); - return out; -} - -static PJ *destructor(PJ *P, int errlev) { - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - if (static_cast(P->opaque)->cart) - static_cast(P->opaque)->cart->destructor (static_cast(P->opaque)->cart, errlev); - - return pj_default_destructor(P, errlev); -} - - -PJ *TRANSFORMATION(deformation,1) { - int has_xy_grids = 0; - int has_z_grids = 0; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return destructor(P, ENOMEM); - P->opaque = (void *) Q; - - Q->cart = proj_create(P->ctx, "+proj=cart"); - if (Q->cart == nullptr) - return destructor(P, ENOMEM); - - /* inherit ellipsoid definition from P to Q->cart */ - pj_inherit_ellipsoid_def (P, Q->cart); - - has_xy_grids = pj_param(P->ctx, P->params, "txy_grids").i; - has_z_grids = pj_param(P->ctx, P->params, "tz_grids").i; - - /* Build gridlists. Both horizontal and vertical grids are mandatory. */ - if (!has_xy_grids || !has_z_grids) { - proj_log_error(P, "deformation: Both +xy_grids and +z_grids should be specified."); - return destructor(P, PJD_ERR_NO_ARGS ); - } - - proj_hgrid_init(P, "xy_grids"); - if (proj_errno(P)) { - proj_log_error(P, "deformation: could not find requested xy_grid(s)."); - return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID); - } - - proj_vgrid_init(P, "z_grids"); - if (proj_errno(P)) { - proj_log_error(P, "deformation: could not find requested z_grid(s)."); - return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID); - } - - Q->t_obs = HUGE_VAL; - if (pj_param(P->ctx, P->params, "tt_obs").i) { - Q->t_obs = pj_param(P->ctx, P->params, "dt_obs").f; - } - - if (pj_param(P->ctx, P->params, "tt_epoch").i) { - Q->t_epoch = pj_param(P->ctx, P->params, "dt_epoch").f; - } else { - proj_log_error(P, "deformation: +t_epoch parameter missing."); - return destructor(P, PJD_ERR_MISSING_ARGS); - } - - P->fwd4d = forward_4d; - P->inv4d = reverse_4d; - P->fwd3d = forward_3d; - P->inv3d = reverse_3d; - P->fwd = nullptr; - P->inv = nullptr; - - P->left = PJ_IO_UNITS_CARTESIAN; - P->right = PJ_IO_UNITS_CARTESIAN; - P->destructor = destructor; - - return P; -} - diff --git a/src/PJ_denoy.cpp b/src/PJ_denoy.cpp deleted file mode 100644 index 5c337c45..00000000 --- a/src/PJ_denoy.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#define PJ_LIB__ -#include - -#include "projects.h" - -PROJ_HEAD(denoy, "Denoyer Semi-Elliptical") "\n\tPCyl, no inv, Sph"; - -#define C0 0.95 -#define C1 -0.08333333333333333333 -#define C3 0.00166666666666666666 -#define D1 0.9 -#define D5 0.03 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - (void) P; - xy.y = lp.phi; - xy.x = lp.lam; - lp.lam = fabs(lp.lam); - xy.x *= cos((C0 + lp.lam * (C1 + lp.lam * lp.lam * C3)) * - (lp.phi * (D1 + D5 * lp.phi * lp.phi * lp.phi * lp.phi))); - return xy; -} - - -PJ *PROJECTION(denoy) { - P->es = 0.0; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_eck1.cpp b/src/PJ_eck1.cpp deleted file mode 100644 index 88a7430c..00000000 --- a/src/PJ_eck1.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#define PJ_LIB__ -#include - -#include "projects.h" - -PROJ_HEAD(eck1, "Eckert I") "\n\tPCyl, Sph"; -#define FC 0.92131773192356127802 -#define RP 0.31830988618379067154 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - - xy.x = FC * lp.lam * (1. - RP * fabs(lp.phi)); - xy.y = FC * lp.phi; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - (void) P; - - lp.phi = xy.y / FC; - lp.lam = xy.x / (FC * (1. - RP * fabs(lp.phi))); - - return (lp); -} - - - -PJ *PROJECTION(eck1) { - P->es = 0.0; - P->inv = s_inverse; - P->fwd = s_forward; - - return P -; -} diff --git a/src/PJ_eck2.cpp b/src/PJ_eck2.cpp deleted file mode 100644 index f76ab4ec..00000000 --- a/src/PJ_eck2.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(eck2, "Eckert II") "\n\tPCyl, Sph"; - -#define FXC 0.46065886596178063902 -#define FYC 1.44720250911653531871 -#define C13 0.33333333333333333333 -#define ONEEPS 1.0000001 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - - xy.x = FXC * lp.lam * (xy.y = sqrt(4. - 3. * sin(fabs(lp.phi)))); - xy.y = FYC * (2. - xy.y); - if ( lp.phi < 0.) xy.y = -xy.y; - - return (xy); -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - (void) P; - - lp.lam = xy.x / (FXC * ( lp.phi = 2. - fabs(xy.y) / FYC) ); - lp.phi = (4. - lp.phi * lp.phi) * C13; - if (fabs(lp.phi) >= 1.) { - if (fabs(lp.phi) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } else { - lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; - } - } else - lp.phi = asin(lp.phi); - if (xy.y < 0) - lp.phi = -lp.phi; - return (lp); -} - - - -PJ *PROJECTION(eck2) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_eck3.cpp b/src/PJ_eck3.cpp deleted file mode 100644 index 90376631..00000000 --- a/src/PJ_eck3.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(eck3, "Eckert III") "\n\tPCyl, Sph"; -PROJ_HEAD(putp1, "Putnins P1") "\n\tPCyl, Sph"; -PROJ_HEAD(wag6, "Wagner VI") "\n\tPCyl, Sph"; -PROJ_HEAD(kav7, "Kavraisky VII") "\n\tPCyl, Sph"; - -namespace { // anonymous namespace -struct pj_opaque { - double C_x, C_y, A, B; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - xy.y = Q->C_y * lp.phi; - xy.x = Q->C_x * lp.lam * (Q->A + asqrt(1. - Q->B * lp.phi * lp.phi)); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double denominator; - - lp.phi = xy.y / Q->C_y; - denominator = (Q->C_x * (Q->A + asqrt(1. - Q->B * lp.phi * lp.phi))); - if ( denominator == 0.0) - lp.lam = HUGE_VAL; - else - lp.lam = xy.x / denominator; - return lp; -} - - -static PJ *setup(PJ *P) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - return P; -} - - -PJ *PROJECTION(eck3) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->C_x = 0.42223820031577120149; - Q->C_y = 0.84447640063154240298; - Q->A = 1.0; - Q->B = 0.4052847345693510857755; - - return setup(P); -} - - -PJ *PROJECTION(kav7) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - /* Defined twice in original code - Using 0.866..., - * but leaving the other one here as a safety measure. - * Q->C_x = 0.2632401569273184856851; */ - Q->C_x = 0.8660254037844; - Q->C_y = 1.; - Q->A = 0.; - Q->B = 0.30396355092701331433; - - return setup(P); -} - - -PJ *PROJECTION(wag6) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->C_x = Q->C_y = 0.94745; - Q->A = 0.0; - Q->B = 0.30396355092701331433; - - return setup(P); -} - - -PJ *PROJECTION(putp1) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->C_x = 1.89490; - Q->C_y = 0.94745; - Q->A = -0.5; - Q->B = 0.30396355092701331433; - - return setup(P); -} diff --git a/src/PJ_eck4.cpp b/src/PJ_eck4.cpp deleted file mode 100644 index 4fa4c21f..00000000 --- a/src/PJ_eck4.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(eck4, "Eckert IV") "\n\tPCyl, Sph"; - -#define C_x .42223820031577120149 -#define C_y 1.32650042817700232218 -#define RC_y .75386330736002178205 -#define C_p 3.57079632679489661922 -#define RC_p .28004957675577868795 -#define EPS 1e-7 -#define NITER 6 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double p, V, s, c; - int i; - (void) P; - - p = C_p * sin(lp.phi); - V = lp.phi * lp.phi; - lp.phi *= 0.895168 + V * ( 0.0218849 + V * 0.00826809 ); - for (i = NITER; i ; --i) { - c = cos(lp.phi); - s = sin(lp.phi); - lp.phi -= V = (lp.phi + s * (c + 2.) - p) / - (1. + c * (c + 2.) - s * s); - if (fabs(V) < EPS) - break; - } - if (!i) { - xy.x = C_x * lp.lam; - xy.y = lp.phi < 0. ? -C_y : C_y; - } else { - xy.x = C_x * lp.lam * (1. + cos(lp.phi)); - xy.y = C_y * sin(lp.phi); - } - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double c; - - lp.phi = aasin(P->ctx,xy.y * RC_y); - lp.lam = xy.x / (C_x * (1. + (c = cos(lp.phi)))); - lp.phi = aasin(P->ctx,(lp.phi + sin(lp.phi) * (c + 2.)) * RC_p); - return lp; -} - - -PJ *PROJECTION(eck4) { - P->es = 0.0; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_eck5.cpp b/src/PJ_eck5.cpp deleted file mode 100644 index f9f28460..00000000 --- a/src/PJ_eck5.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(eck5, "Eckert V") "\n\tPCyl, Sph"; - -#define XF 0.44101277172455148219 -#define RXF 2.26750802723822639137 -#define YF 0.88202554344910296438 -#define RYF 1.13375401361911319568 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - xy.x = XF * (1. + cos(lp.phi)) * lp.lam; - xy.y = YF * lp.phi; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - (void) P; - lp.lam = RXF * xy.x / (1. + cos( lp.phi = RYF * xy.y)); - - return lp; -} - - -PJ *PROJECTION(eck5) { - P->es = 0.0; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_eqc.cpp b/src/PJ_eqc.cpp deleted file mode 100644 index 3fdb6dc0..00000000 --- a/src/PJ_eqc.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -namespace { // anonymous namespace -struct pj_opaque { - double rc; -}; -} // anonymous namespace - -PROJ_HEAD(eqc, "Equidistant Cylindrical (Plate Carree)") - "\n\tCyl, Sph\n\tlat_ts=[, lat_0=0]"; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - xy.x = Q->rc * lp.lam; - xy.y = lp.phi - P->phi0; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - lp.lam = xy.x / Q->rc; - lp.phi = xy.y + P->phi0; - - return lp; -} - - -PJ *PROJECTION(eqc) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - if ((Q->rc = cos(pj_param(P->ctx, P->params, "rlat_ts").f)) <= 0.) - return pj_default_destructor (P, PJD_ERR_LAT_TS_LARGER_THAN_90); - P->inv = s_inverse; - P->fwd = s_forward; - P->es = 0.; - - return P; -} diff --git a/src/PJ_eqdc.cpp b/src/PJ_eqdc.cpp deleted file mode 100644 index 0831fca4..00000000 --- a/src/PJ_eqdc.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -namespace { // anonymous namespace -struct pj_opaque { - double phi1; - double phi2; - double n; - double rho; - double rho0; - double c; - double *en; - int ellips; -}; -} // anonymous namespace - -PROJ_HEAD(eqdc, "Equidistant Conic") - "\n\tConic, Sph&Ell\n\tlat_1= lat_2="; -# define EPS10 1.e-10 - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - Q->rho = Q->c - (Q->ellips ? pj_mlfn(lp.phi, sin(lp.phi), - cos(lp.phi), Q->en) : lp.phi); - xy.x = Q->rho * sin( lp.lam *= Q->n ); - xy.y = Q->rho0 - Q->rho * cos(lp.lam); - - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - if ((Q->rho = hypot(xy.x, xy.y = Q->rho0 - xy.y)) != 0.0 ) { - if (Q->n < 0.) { - Q->rho = -Q->rho; - xy.x = -xy.x; - xy.y = -xy.y; - } - lp.phi = Q->c - Q->rho; - if (Q->ellips) - lp.phi = pj_inv_mlfn(P->ctx, lp.phi, P->es, Q->en); - lp.lam = atan2(xy.x, xy.y) / Q->n; - } else { - lp.lam = 0.; - lp.phi = Q->n > 0. ? M_HALFPI : -M_HALFPI; - } - return lp; -} - - -static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->en); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(eqdc) { - double cosphi, sinphi; - int secant; - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; - Q->phi2 = pj_param(P->ctx, P->params, "rlat_2").f; - - if (fabs(Q->phi1 + Q->phi2) < EPS10) - return pj_default_destructor (P, PJD_ERR_CONIC_LAT_EQUAL); - - if (!(Q->en = pj_enfn(P->es))) - return pj_default_destructor(P, ENOMEM); - - Q->n = sinphi = sin(Q->phi1); - cosphi = cos(Q->phi1); - secant = fabs(Q->phi1 - Q->phi2) >= EPS10; - if( (Q->ellips = (P->es > 0.)) ) { - double ml1, m1; - - m1 = pj_msfn(sinphi, cosphi, P->es); - ml1 = pj_mlfn(Q->phi1, sinphi, cosphi, Q->en); - if (secant) { /* secant cone */ - sinphi = sin(Q->phi2); - cosphi = cos(Q->phi2); - Q->n = (m1 - pj_msfn(sinphi, cosphi, P->es)) / - (pj_mlfn(Q->phi2, sinphi, cosphi, Q->en) - ml1); - } - Q->c = ml1 + m1 / Q->n; - Q->rho0 = Q->c - pj_mlfn(P->phi0, sin(P->phi0), - cos(P->phi0), Q->en); - } else { - if (secant) - Q->n = (cosphi - cos(Q->phi2)) / (Q->phi2 - Q->phi1); - Q->c = Q->phi1 + cos(Q->phi1) / Q->n; - Q->rho0 = Q->c - P->phi0; - } - - P->inv = e_inverse; - P->fwd = e_forward; - - return P; -} diff --git a/src/PJ_eqearth.cpp b/src/PJ_eqearth.cpp deleted file mode 100644 index e5c1f974..00000000 --- a/src/PJ_eqearth.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/* -Equal Earth is a projection inspired by the Robinson projection, but unlike -the Robinson projection retains the relative size of areas. The projection -was designed in 2018 by Bojan Savric, Tom Patterson and Bernhard Jenny. - -Publication: -Bojan Savric, Tom Patterson & Bernhard Jenny (2018). The Equal Earth map -projection, International Journal of Geographical Information Science, -DOI: 10.1080/13658816.2018.1504949 - -Port to PROJ by Juernjakob Dugge, 16 August 2018 -Added ellipsoidal equations by Bojan Savric, 22 August 2018 -*/ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(eqearth, "Equal Earth") "\n\tPCyl, Sph&Ell"; - -/* A1..A4, polynomial coefficients */ -#define A1 1.340264 -#define A2 -0.081106 -#define A3 0.000893 -#define A4 0.003796 -#define M (sqrt(3) / 2.0) - -#define MAX_Y 1.3173627591574 /* 90° latitude on a sphere with radius 1 */ -#define EPS 1e-11 -#define MAX_ITER 12 - -namespace { // anonymous namespace -struct pj_opaque { - double qp; - double rqda; - double *apa; -}; -} // anonymous namespace - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal/spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double sbeta; - double psi, psi2, psi6; - - /* Spheroidal case, using sine latitude */ - sbeta = sin(lp.phi); - - /* In the ellipsoidal case, we convert sbeta to sine of authalic latitude */ - if (P->es != 0.0) { - sbeta = pj_qsfn(sbeta, P->e, 1.0 - P->es) / Q->qp; - - /* Rounding error. */ - if (fabs(sbeta) > 1) - sbeta = sbeta > 0 ? 1 : -1; - } - - /* Equal Earth projection */ - psi = asin(M * sbeta); - psi2 = psi * psi; - psi6 = psi2 * psi2 * psi2; - - xy.x = lp.lam * cos(psi) / (M * (A1 + 3 * A2 * psi2 + psi6 * (7 * A3 + 9 * A4 * psi2))); - xy.y = psi * (A1 + A2 * psi2 + psi6 * (A3 + A4 * psi2)); - - /* Adjusting x and y for authalic radius */ - xy.x *= Q->rqda; - xy.y *= Q->rqda; - - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal/spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double yc, y2, y6; - int i; - - /* Adjusting x and y for authalic radius */ - xy.x /= Q->rqda; - xy.y /= Q->rqda; - - /* Make sure y is inside valid range */ - if (xy.y > MAX_Y) - xy.y = MAX_Y; - else if (xy.y < -MAX_Y) - xy.y = -MAX_Y; - - yc = xy.y; - - /* Newton-Raphson */ - for (i = MAX_ITER; i ; --i) { - double f, fder, tol; - - y2 = yc * yc; - y6 = y2 * y2 * y2; - - f = yc * (A1 + A2 * y2 + y6 * (A3 + A4 * y2)) - xy.y; - fder = A1 + 3 * A2 * y2 + y6 * (7 * A3 + 9 * A4 * y2); - - tol = f / fder; - yc -= tol; - - if (fabs(tol) < EPS) - break; - } - - if( i == 0 ) { - pj_ctx_set_errno( P->ctx, PJD_ERR_NON_CONVERGENT ); - return lp; - } - - /* Longitude */ - y2 = yc * yc; - y6 = y2 * y2 * y2; - - lp.lam = M * xy.x * (A1 + 3 * A2 * y2 + y6 * (7 * A3 + 9 * A4 * y2)) / cos(yc); - - /* Latitude (for spheroidal case, this is latitude */ - lp.phi = asin(sin(yc) / M); - - /* Ellipsoidal case, converting auth. latitude */ - if (P->es != 0.0) - lp.phi = pj_authlat(lp.phi, Q->apa); - - return lp; -} - -static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->apa); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(eqearth) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - P->fwd = e_forward; - P->inv = e_inverse; - Q->rqda = 1.0; - - /* Ellipsoidal case */ - if (P->es != 0.0) { - Q->apa = pj_authset(P->es); /* For auth_lat(). */ - if (nullptr == Q->apa) - return destructor(P, ENOMEM); - Q->qp = pj_qsfn(1.0, P->e, P->one_es); /* For auth_lat(). */ - Q->rqda = sqrt(0.5*Q->qp); /* Authalic radius divided by major axis */ - } - - return P; -} diff --git a/src/PJ_fahey.cpp b/src/PJ_fahey.cpp deleted file mode 100644 index 85e0ab69..00000000 --- a/src/PJ_fahey.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(fahey, "Fahey") "\n\tPcyl, Sph"; - -#define TOL 1e-6 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - - xy.x = tan(0.5 * lp.phi); - xy.y = 1.819152 * xy.x; - xy.x = 0.819152 * lp.lam * asqrt(1 - xy.x * xy.x); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - (void) P; - - xy.y /= 1.819152; - lp.phi = 2. * atan(xy.y); - xy.y = 1. - xy.y * xy.y; - lp.lam = fabs(xy.y) < TOL ? 0. : xy.x / (0.819152 * sqrt(xy.y)); - return lp; -} - - -PJ *PROJECTION(fahey) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_fouc_s.cpp b/src/PJ_fouc_s.cpp deleted file mode 100644 index c5e711de..00000000 --- a/src/PJ_fouc_s.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(fouc_s, "Foucaut Sinusoidal") "\n\tPCyl, Sph"; - -#define MAX_ITER 10 -#define LOOP_TOL 1e-7 - -namespace { // anonymous namespace -struct pj_opaque { - double n, n1; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double t; - - t = cos(lp.phi); - xy.x = lp.lam * t / (Q->n + Q->n1 * t); - xy.y = Q->n * lp.phi + Q->n1 * sin(lp.phi); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double V; - int i; - - if (Q->n != 0.0) { - lp.phi = xy.y; - for (i = MAX_ITER; i ; --i) { - lp.phi -= V = (Q->n * lp.phi + Q->n1 * sin(lp.phi) - xy.y ) / - (Q->n + Q->n1 * cos(lp.phi)); - if (fabs(V) < LOOP_TOL) - break; - } - if (!i) - lp.phi = xy.y < 0. ? -M_HALFPI : M_HALFPI; - } else - lp.phi = aasin(P->ctx,xy.y); - V = cos(lp.phi); - lp.lam = xy.x * (Q->n + Q->n1 * V) / V; - return lp; -} - - -PJ *PROJECTION(fouc_s) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->n = pj_param(P->ctx, P->params, "dn").f; - if (Q->n < 0. || Q->n > 1.) - return pj_default_destructor (P, PJD_ERR_N_OUT_OF_RANGE); - - Q->n1 = 1. - Q->n; - P->es = 0; - P->inv = s_inverse; - P->fwd = s_forward; - return P; -} diff --git a/src/PJ_gall.cpp b/src/PJ_gall.cpp deleted file mode 100644 index a8697482..00000000 --- a/src/PJ_gall.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(gall, "Gall (Gall Stereographic)") "\n\tCyl, Sph"; - -#define YF 1.70710678118654752440 -#define XF 0.70710678118654752440 -#define RYF 0.58578643762690495119 -#define RXF 1.41421356237309504880 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - - xy.x = XF * lp.lam; - xy.y = YF * tan(.5 * lp.phi); - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - (void) P; - - lp.lam = RXF * xy.x; - lp.phi = 2. * atan(xy.y * RYF); - - return lp; -} - - -PJ *PROJECTION(gall) { - P->es = 0.0; - - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_geoc.cpp b/src/PJ_geoc.cpp deleted file mode 100644 index 0455fada..00000000 --- a/src/PJ_geoc.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Conversion from geographic to geocentric latitude and back. - * Author: Thomas Knudsen (2017) - * - ****************************************************************************** - * Copyright (c) 2017, SDFE, http://www.sdfe.dk - * Copyright (c) 2017, Thomas Knudsen - * - * 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. - *****************************************************************************/ - -#define PJ_LIB__ - -#include - -#include "proj.h" -#include "proj_internal.h" -#include "projects.h" - -PROJ_HEAD(geoc, "Geocentric Latitude"); - -/* Geographical to geocentric */ -static PJ_COORD forward(PJ_COORD coo, PJ *P) { - return pj_geocentric_latitude (P, PJ_FWD, coo); -} - -/* Geocentric to geographical */ -static PJ_COORD inverse(PJ_COORD coo, PJ *P) { - return pj_geocentric_latitude (P, PJ_INV, coo); -} - - -static PJ *CONVERSION(geoc, 1) { - P->inv4d = inverse; - P->fwd4d = forward; - - P->left = PJ_IO_UNITS_ANGULAR; - P->right = PJ_IO_UNITS_ANGULAR; - - P->is_latlong = 1; - return P; -} diff --git a/src/PJ_geos.cpp b/src/PJ_geos.cpp deleted file mode 100644 index 90fb01ab..00000000 --- a/src/PJ_geos.cpp +++ /dev/null @@ -1,238 +0,0 @@ -/* -** libproj -- library of cartographic projections -** -** Copyright (c) 2004 Gerald I. Evenden -** Copyright (c) 2012 Martin Raspaud -** -** See also (section 4.4.3.2): -** http://www.eumetsat.int/en/area4/msg/news/us_doc/cgms_03_26.pdf -** -** 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. -*/ - -#define PJ_LIB__ -#include -#include -#include - -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -namespace { // anonymous namespace -struct pj_opaque { - double h; - double radius_p; - double radius_p2; - double radius_p_inv2; - double radius_g; - double radius_g_1; - double C; - int flip_axis; -}; -} // anonymous namespace - -PROJ_HEAD(geos, "Geostationary Satellite View") "\n\tAzi, Sph&Ell\n\th="; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double Vx, Vy, Vz, tmp; - - /* Calculation of the three components of the vector from satellite to - ** position on earth surface (lon,lat).*/ - tmp = cos(lp.phi); - Vx = cos (lp.lam) * tmp; - Vy = sin (lp.lam) * tmp; - Vz = sin (lp.phi); - - /* Check visibility*/ - - - /* Calculation based on view angles from satellite.*/ - tmp = Q->radius_g - Vx; - - if(Q->flip_axis) { - xy.x = Q->radius_g_1 * atan(Vy / hypot(Vz, tmp)); - xy.y = Q->radius_g_1 * atan(Vz / tmp); - } else { - xy.x = Q->radius_g_1 * atan(Vy / tmp); - xy.y = Q->radius_g_1 * atan(Vz / hypot(Vy, tmp)); - } - - return xy; -} - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double r, Vx, Vy, Vz, tmp; - - /* Calculation of geocentric latitude. */ - lp.phi = atan (Q->radius_p2 * tan (lp.phi)); - - /* Calculation of the three components of the vector from satellite to - ** position on earth surface (lon,lat).*/ - r = (Q->radius_p) / hypot(Q->radius_p * cos (lp.phi), sin (lp.phi)); - Vx = r * cos (lp.lam) * cos (lp.phi); - Vy = r * sin (lp.lam) * cos (lp.phi); - Vz = r * sin (lp.phi); - - /* Check visibility. */ - if (((Q->radius_g - Vx) * Vx - Vy * Vy - Vz * Vz * Q->radius_p_inv2) < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - - /* Calculation based on view angles from satellite. */ - tmp = Q->radius_g - Vx; - - if(Q->flip_axis) { - xy.x = Q->radius_g_1 * atan (Vy / hypot (Vz, tmp)); - xy.y = Q->radius_g_1 * atan (Vz / tmp); - } else { - xy.x = Q->radius_g_1 * atan (Vy / tmp); - xy.y = Q->radius_g_1 * atan (Vz / hypot (Vy, tmp)); - } - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double Vx, Vy, Vz, a, b, det, k; - - /* Setting three components of vector from satellite to position.*/ - Vx = -1.0; - if(Q->flip_axis) { - Vz = tan (xy.y / (Q->radius_g - 1.0)); - Vy = tan (xy.x / (Q->radius_g - 1.0)) * sqrt (1.0 + Vz * Vz); - } else { - Vy = tan (xy.x / (Q->radius_g - 1.0)); - Vz = tan (xy.y / (Q->radius_g - 1.0)) * sqrt (1.0 + Vy * Vy); - } - - /* Calculation of terms in cubic equation and determinant.*/ - a = Vy * Vy + Vz * Vz + Vx * Vx; - b = 2 * Q->radius_g * Vx; - if ((det = (b * b) - 4 * a * Q->C) < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - - /* Calculation of three components of vector from satellite to position.*/ - k = (-b - sqrt(det)) / (2 * a); - Vx = Q->radius_g + k * Vx; - Vy *= k; - Vz *= k; - - /* Calculation of longitude and latitude.*/ - lp.lam = atan2 (Vy, Vx); - lp.phi = atan (Vz * cos (lp.lam) / Vx); - - return lp; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double Vx, Vy, Vz, a, b, det, k; - - /* Setting three components of vector from satellite to position.*/ - Vx = -1.0; - - if(Q->flip_axis) { - Vz = tan (xy.y / Q->radius_g_1); - Vy = tan (xy.x / Q->radius_g_1) * hypot(1.0, Vz); - } else { - Vy = tan (xy.x / Q->radius_g_1); - Vz = tan (xy.y / Q->radius_g_1) * hypot(1.0, Vy); - } - - /* Calculation of terms in cubic equation and determinant.*/ - a = Vz / Q->radius_p; - a = Vy * Vy + a * a + Vx * Vx; - b = 2 * Q->radius_g * Vx; - if ((det = (b * b) - 4 * a * Q->C) < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - - /* Calculation of three components of vector from satellite to position.*/ - k = (-b - sqrt(det)) / (2. * a); - Vx = Q->radius_g + k * Vx; - Vy *= k; - Vz *= k; - - /* Calculation of longitude and latitude.*/ - lp.lam = atan2 (Vy, Vx); - lp.phi = atan (Vz * cos (lp.lam) / Vx); - lp.phi = atan (Q->radius_p_inv2 * tan (lp.phi)); - - return lp; -} - - -PJ *PROJECTION(geos) { - char *sweep_axis; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - if ((Q->h = pj_param(P->ctx, P->params, "dh").f) <= 0.) - return pj_default_destructor (P, PJD_ERR_H_LESS_THAN_ZERO); - - sweep_axis = pj_param(P->ctx, P->params, "ssweep").s; - if (sweep_axis == nullptr) - Q->flip_axis = 0; - else { - if ((sweep_axis[0] != 'x' && sweep_axis[0] != 'y') || - sweep_axis[1] != '\0') - return pj_default_destructor (P, PJD_ERR_INVALID_SWEEP_AXIS); - - if (sweep_axis[0] == 'x') - Q->flip_axis = 1; - else - Q->flip_axis = 0; - } - - Q->radius_g_1 = Q->h / P->a; - Q->radius_g = 1. + Q->radius_g_1; - Q->C = Q->radius_g * Q->radius_g - 1.0; - if (P->es != 0.0) { - Q->radius_p = sqrt (P->one_es); - Q->radius_p2 = P->one_es; - Q->radius_p_inv2 = P->rone_es; - P->inv = e_inverse; - P->fwd = e_forward; - } else { - Q->radius_p = Q->radius_p2 = Q->radius_p_inv2 = 1.0; - P->inv = s_inverse; - P->fwd = s_forward; - } - - return P; -} diff --git a/src/PJ_gins8.cpp b/src/PJ_gins8.cpp deleted file mode 100644 index cc422437..00000000 --- a/src/PJ_gins8.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#define PJ_LIB__ -#include "projects.h" - -PROJ_HEAD(gins8, "Ginsburg VIII (TsNIIGAiK)") "\n\tPCyl, Sph, no inv"; - -#define Cl 0.000952426 -#define Cp 0.162388 -#define C12 0.08333333333333333 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double t = lp.phi * lp.phi; - (void) P; - - xy.y = lp.phi * (1. + t * C12); - xy.x = lp.lam * (1. - Cp * t); - t = lp.lam * lp.lam; - xy.x *= (0.87 - Cl * t * t); - - return xy; -} - - -PJ *PROJECTION(gins8) { - P->es = 0.0; - P->inv = nullptr; - P->fwd = s_forward; - - return P; -} - - diff --git a/src/PJ_gn_sinu.cpp b/src/PJ_gn_sinu.cpp deleted file mode 100644 index 530de229..00000000 --- a/src/PJ_gn_sinu.cpp +++ /dev/null @@ -1,189 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(gn_sinu, "General Sinusoidal Series") "\n\tPCyl, Sph\n\tm= n="; -PROJ_HEAD(sinu, "Sinusoidal (Sanson-Flamsteed)") "\n\tPCyl, Sph&Ell"; -PROJ_HEAD(eck6, "Eckert VI") "\n\tPCyl, Sph"; -PROJ_HEAD(mbtfps, "McBryde-Thomas Flat-Polar Sinusoidal") "\n\tPCyl, Sph"; - -#define EPS10 1e-10 -#define MAX_ITER 8 -#define LOOP_TOL 1e-7 - -namespace { // anonymous namespace -struct pj_opaque { - double *en; - double m, n, C_x, C_y; -}; -} // anonymous namespace - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - double s, c; - - xy.y = pj_mlfn(lp.phi, s = sin(lp.phi), c = cos(lp.phi), static_cast(P->opaque)->en); - xy.x = lp.lam * c / sqrt(1. - P->es * s * s); - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - double s; - - if ((s = fabs(lp.phi = pj_inv_mlfn(P->ctx, xy.y, P->es, static_cast(P->opaque)->en))) < M_HALFPI) { - s = sin(lp.phi); - lp.lam = xy.x * sqrt(1. - P->es * s * s) / cos(lp.phi); - } else if ((s - EPS10) < M_HALFPI) { - lp.lam = 0.; - } else { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - } - - return lp; -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - if (Q->m == 0.0) - lp.phi = Q->n != 1. ? aasin(P->ctx,Q->n * sin(lp.phi)): lp.phi; - else { - double k, V; - int i; - - k = Q->n * sin(lp.phi); - for (i = MAX_ITER; i ; --i) { - lp.phi -= V = (Q->m * lp.phi + sin(lp.phi) - k) / - (Q->m + cos(lp.phi)); - if (fabs(V) < LOOP_TOL) - break; - } - if (!i) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - - } - xy.x = Q->C_x * lp.lam * (Q->m + cos(lp.phi)); - xy.y = Q->C_y * lp.phi; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - xy.y /= Q->C_y; - lp.phi = (Q->m != 0.0) ? aasin(P->ctx,(Q->m * xy.y + sin(xy.y)) / Q->n) : - ( Q->n != 1. ? aasin(P->ctx,sin(xy.y) / Q->n) : xy.y ); - lp.lam = xy.x / (Q->C_x * (Q->m + cos(xy.y))); - return lp; -} - - -static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->en); - return pj_default_destructor (P, errlev); -} - - - -/* for spheres, only */ -static void setup(PJ *P) { - struct pj_opaque *Q = static_cast(P->opaque); - P->es = 0; - P->inv = s_inverse; - P->fwd = s_forward; - - Q->C_x = (Q->C_y = sqrt((Q->m + 1.) / Q->n))/(Q->m + 1.); -} - - -PJ *PROJECTION(sinu) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - if (!(Q->en = pj_enfn(P->es))) - return pj_default_destructor (P, ENOMEM); - - if (P->es != 0.0) { - P->inv = e_inverse; - P->fwd = e_forward; - } else { - Q->n = 1.; - Q->m = 0.; - setup(P); - } - return P; -} - - -PJ *PROJECTION(eck6) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - Q->m = 1.; - Q->n = 2.570796326794896619231321691; - setup(P); - - return P; -} - - -PJ *PROJECTION(mbtfps) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - Q->m = 0.5; - Q->n = 1.785398163397448309615660845; - setup(P); - - return P; -} - - -PJ *PROJECTION(gn_sinu) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - if (pj_param(P->ctx, P->params, "tn").i && pj_param(P->ctx, P->params, "tm").i) { - Q->n = pj_param(P->ctx, P->params, "dn").f; - Q->m = pj_param(P->ctx, P->params, "dm").f; - if (Q->n <= 0 || Q->m < 0) - return destructor (P, PJD_ERR_INVALID_M_OR_N); - } else - return destructor (P, PJD_ERR_INVALID_M_OR_N); - - setup(P); - - return P; -} diff --git a/src/PJ_gnom.cpp b/src/PJ_gnom.cpp deleted file mode 100644 index a4b5e35d..00000000 --- a/src/PJ_gnom.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -PROJ_HEAD(gnom, "Gnomonic") "\n\tAzi, Sph"; - -#define EPS10 1.e-10 - -namespace { // anonymous namespace -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque { - double sinph0; - double cosph0; - enum Mode mode; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double coslam, cosphi, sinphi; - - sinphi = sin(lp.phi); - cosphi = cos(lp.phi); - coslam = cos(lp.lam); - - switch (Q->mode) { - case EQUIT: - xy.y = cosphi * coslam; - break; - case OBLIQ: - xy.y = Q->sinph0 * sinphi + Q->cosph0 * cosphi * coslam; - break; - case S_POLE: - xy.y = - sinphi; - break; - case N_POLE: - xy.y = sinphi; - break; - } - - if (xy.y <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - - xy.x = (xy.y = 1. / xy.y) * cosphi * sin(lp.lam); - switch (Q->mode) { - case EQUIT: - xy.y *= sinphi; - break; - case OBLIQ: - xy.y *= Q->cosph0 * sinphi - Q->sinph0 * cosphi * coslam; - break; - case N_POLE: - coslam = - coslam; - /*-fallthrough*/ - case S_POLE: - xy.y *= cosphi * coslam; - break; - } - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double rh, cosz, sinz; - - rh = hypot(xy.x, xy.y); - sinz = sin(lp.phi = atan(rh)); - cosz = sqrt(1. - sinz * sinz); - - if (fabs(rh) <= EPS10) { - lp.phi = P->phi0; - lp.lam = 0.; - } else { - switch (Q->mode) { - case OBLIQ: - lp.phi = cosz * Q->sinph0 + xy.y * sinz * Q->cosph0 / rh; - if (fabs(lp.phi) >= 1.) - lp.phi = lp.phi > 0. ? M_HALFPI : - M_HALFPI; - else - lp.phi = asin(lp.phi); - xy.y = (cosz - Q->sinph0 * sin(lp.phi)) * rh; - xy.x *= sinz * Q->cosph0; - break; - case EQUIT: - lp.phi = xy.y * sinz / rh; - if (fabs(lp.phi) >= 1.) - lp.phi = lp.phi > 0. ? M_HALFPI : - M_HALFPI; - else - lp.phi = asin(lp.phi); - xy.y = cosz * rh; - xy.x *= sinz; - break; - case S_POLE: - lp.phi -= M_HALFPI; - break; - case N_POLE: - lp.phi = M_HALFPI - lp.phi; - xy.y = -xy.y; - break; - } - lp.lam = atan2(xy.x, xy.y); - } - return lp; -} - - -PJ *PROJECTION(gnom) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - if (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) { - Q->mode = P->phi0 < 0. ? S_POLE : N_POLE; - } else if (fabs(P->phi0) < EPS10) { - Q->mode = EQUIT; - } else { - Q->mode = OBLIQ; - Q->sinph0 = sin(P->phi0); - Q->cosph0 = cos(P->phi0); - } - - P->inv = s_inverse; - P->fwd = s_forward; - P->es = 0.; - - return P; -} diff --git a/src/PJ_goode.cpp b/src/PJ_goode.cpp deleted file mode 100644 index c79d125e..00000000 --- a/src/PJ_goode.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(goode, "Goode Homolosine") "\n\tPCyl, Sph"; - -#define Y_COR 0.05280 -#define PHI_LIM 0.71093078197902358062 - -C_NAMESPACE PJ *pj_sinu(PJ *), *pj_moll(PJ *); - -namespace { // anonymous namespace -struct pj_opaque { - PJ *sinu; - PJ *moll; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy; - struct pj_opaque *Q = static_cast(P->opaque); - - if (fabs(lp.phi) <= PHI_LIM) - xy = Q->sinu->fwd(lp, Q->sinu); - else { - xy = Q->moll->fwd(lp, Q->moll); - xy.y -= lp.phi >= 0.0 ? Y_COR : -Y_COR; - } - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp; - struct pj_opaque *Q = static_cast(P->opaque); - - if (fabs(xy.y) <= PHI_LIM) - lp = Q->sinu->inv(xy, Q->sinu); - else { - xy.y += xy.y >= 0.0 ? Y_COR : -Y_COR; - lp = Q->moll->inv(xy, Q->moll); - } - return lp; -} - - -static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - pj_free (static_cast(P->opaque)->sinu); - pj_free (static_cast(P->opaque)->moll); - return pj_default_destructor (P, errlev); -} - - - -PJ *PROJECTION(goode) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - P->es = 0.; - if (!(Q->sinu = pj_sinu(nullptr)) || !(Q->moll = pj_moll(nullptr))) - return destructor (P, ENOMEM); - Q->sinu->es = 0.; - Q->sinu->ctx = P->ctx; - Q->moll->ctx = P->ctx; - if (!(Q->sinu = pj_sinu(Q->sinu)) || !(Q->moll = pj_moll(Q->moll))) - return destructor (P, ENOMEM); - - P->fwd = s_forward; - P->inv = s_inverse; - - return P; -} diff --git a/src/PJ_gstmerc.cpp b/src/PJ_gstmerc.cpp deleted file mode 100644 index 9b819bac..00000000 --- a/src/PJ_gstmerc.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(gstmerc, "Gauss-Schreiber Transverse Mercator (aka Gauss-Laborde Reunion)") - "\n\tCyl, Sph&Ell\n\tlat_0= lon_0= k_0="; - -namespace { // anonymous namespace -struct pj_opaque { - double lamc; - double phic; - double c; - double n1; - double n2; - double XS; - double YS; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double L, Ls, sinLs1, Ls1; - - L = Q->n1*lp.lam; - Ls = Q->c + Q->n1 * log(pj_tsfn(-1.0 * lp.phi, -1.0 * sin(lp.phi), P->e)); - sinLs1 = sin(L) / cosh(Ls); - Ls1 = log(pj_tsfn(-1.0 * asin(sinLs1), 0.0, 0.0)); - xy.x = (Q->XS + Q->n2*Ls1) * P->ra; - xy.y = (Q->YS + Q->n2*atan(sinh(Ls) / cos(L))) * P->ra; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double L, LC, sinC; - - L = atan(sinh((xy.x * P->a - Q->XS) / Q->n2) / cos((xy.y * P->a - Q->YS) / Q->n2)); - sinC = sin((xy.y * P->a - Q->YS) / Q->n2) / cosh((xy.x * P->a - Q->XS) / Q->n2); - LC = log(pj_tsfn(-1.0 * asin(sinC), 0.0, 0.0)); - lp.lam = L / Q->n1; - lp.phi = -1.0 * pj_phi2(P->ctx, exp((LC - Q->c) / Q->n1), P->e); - - return lp; -} - - -PJ *PROJECTION(gstmerc) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->lamc = P->lam0; - Q->n1 = sqrt(1.0 + P->es * pow(cos(P->phi0), 4.0) / (1.0 - P->es)); - Q->phic = asin(sin(P->phi0) / Q->n1); - Q->c = log(pj_tsfn(-1.0 * Q->phic, 0.0, 0.0)) - - Q->n1 * log(pj_tsfn(-1.0 * P->phi0, -1.0 * sin(P->phi0), P->e)); - Q->n2 = P->k0 * P->a * sqrt(1.0 - P->es) / (1.0 - P->es * sin(P->phi0) * sin(P->phi0)); - Q->XS = 0; - Q->YS = -1.0 * Q->n2 * Q->phic; - - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_hammer.cpp b/src/PJ_hammer.cpp deleted file mode 100644 index d4caa656..00000000 --- a/src/PJ_hammer.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(hammer, "Hammer & Eckert-Greifendorff") - "\n\tMisc Sph, \n\tW= M="; - -#define EPS 1.0e-10 - -namespace { // anonymous namespace -struct pj_opaque { - double w; - double m, rm; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double cosphi, d; - - d = sqrt(2./(1. + (cosphi = cos(lp.phi)) * cos(lp.lam *= Q->w))); - xy.x = Q->m * d * cosphi * sin(lp.lam); - xy.y = Q->rm * d * sin(lp.phi); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double z; - - z = sqrt(1. - 0.25*Q->w*Q->w*xy.x*xy.x - 0.25*xy.y*xy.y); - if (fabs(2.*z*z-1.) < EPS) { - lp.lam = HUGE_VAL; - lp.phi = HUGE_VAL; - proj_errno_set(P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); - } else { - lp.lam = aatan2(Q->w * xy.x * z,2. * z * z - 1)/Q->w; - lp.phi = aasin(P->ctx,z * xy.y); - } - return lp; -} - - -PJ *PROJECTION(hammer) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - if (pj_param(P->ctx, P->params, "tW").i) { - if ((Q->w = fabs(pj_param(P->ctx, P->params, "dW").f)) <= 0.) - return pj_default_destructor (P, PJD_ERR_W_OR_M_ZERO_OR_LESS); - } else - Q->w = .5; - if (pj_param(P->ctx, P->params, "tM").i) { - if ((Q->m = fabs(pj_param(P->ctx, P->params, "dM").f)) <= 0.) - return pj_default_destructor (P, PJD_ERR_W_OR_M_ZERO_OR_LESS); - } else - Q->m = 1.; - - Q->rm = 1. / Q->m; - Q->m /= Q->w; - - P->es = 0.; - P->fwd = s_forward; - P->inv = s_inverse; - - return P; -} diff --git a/src/PJ_hatano.cpp b/src/PJ_hatano.cpp deleted file mode 100644 index 019671cc..00000000 --- a/src/PJ_hatano.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(hatano, "Hatano Asymmetrical Equal Area") "\n\tPCyl, Sph"; - -#define NITER 20 -#define EPS 1e-7 -#define ONETOL 1.000001 -#define CN 2.67595 -#define CS 2.43763 -#define RCN 0.37369906014686373063 -#define RCS 0.41023453108141924738 -#define FYCN 1.75859 -#define FYCS 1.93052 -#define RYCN 0.56863737426006061674 -#define RYCS 0.51799515156538134803 -#define FXC 0.85 -#define RXC 1.17647058823529411764 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double th1, c; - int i; - (void) P; - - c = sin(lp.phi) * (lp.phi < 0. ? CS : CN); - for (i = NITER; i; --i) { - lp.phi -= th1 = (lp.phi + sin(lp.phi) - c) / (1. + cos(lp.phi)); - if (fabs(th1) < EPS) break; - } - xy.x = FXC * lp.lam * cos(lp.phi *= .5); - xy.y = sin(lp.phi) * (lp.phi < 0. ? FYCS : FYCN); - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double th; - - th = xy.y * ( xy.y < 0. ? RYCS : RYCN); - if (fabs(th) > 1.) { - if (fabs(th) > ONETOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } else { - th = th > 0. ? M_HALFPI : - M_HALFPI; - } - } else { - th = asin(th); - } - - lp.lam = RXC * xy.x / cos(th); - th += th; - lp.phi = (th + sin(th)) * (xy.y < 0. ? RCS : RCN); - if (fabs(lp.phi) > 1.) { - if (fabs(lp.phi) > ONETOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } else { - lp.phi = lp.phi > 0. ? M_HALFPI : - M_HALFPI; - } - } else { - lp.phi = asin(lp.phi); - } - - return (lp); -} - - -PJ *PROJECTION(hatano) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_healpix.cpp b/src/PJ_healpix.cpp deleted file mode 100644 index 7f0b3e83..00000000 --- a/src/PJ_healpix.cpp +++ /dev/null @@ -1,674 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of the HEALPix and rHEALPix projections. - * For background see . - * Authors: Alex Raichev (raichev@cs.auckland.ac.nz) - * Michael Speth (spethm@landcareresearch.co.nz) - * Notes: Raichev implemented these projections in Python and - * Speth translated them into C here. - ****************************************************************************** - * Copyright (c) 2001, Thomas Flemming, tf@ttqv.com - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substcounteral 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. - *****************************************************************************/ -#define PJ_LIB__ - -#include -#include - -#include "proj_internal.h" -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(healpix, "HEALPix") "\n\tSph&Ell"; -PROJ_HEAD(rhealpix, "rHEALPix") "\n\tSph&Ell\n\tnorth_square= south_square="; - -/* Matrix for counterclockwise rotation by pi/2: */ -# define R1 {{ 0,-1},{ 1, 0}} -/* Matrix for counterclockwise rotation by pi: */ -# define R2 {{-1, 0},{ 0,-1}} -/* Matrix for counterclockwise rotation by 3*pi/2: */ -# define R3 {{ 0, 1},{-1, 0}} -/* Identity matrix */ -# define IDENT {{1, 0},{0, 1}} -/* IDENT, R1, R2, R3, R1 inverse, R2 inverse, R3 inverse:*/ -# define ROT {IDENT, R1, R2, R3, R3, R2, R1} -/* Fuzz to handle rounding errors: */ -# define EPS 1e-15 - -namespace { // anonymous namespace -struct pj_opaque { - int north_square; - int south_square; - double qp; - double *apa; -}; -} // anonymous namespace - -typedef struct { - int cn; /* An integer 0--3 indicating the position of the polar cap. */ - double x, y; /* Coordinates of the pole point (point of most extreme latitude on the polar caps). */ - enum Region {north, south, equatorial} region; -} CapMap; - -static const double rot[7][2][2] = ROT; - -/** - * Returns the sign of the double. - * @param v the parameter whose sign is returned. - * @return 1 for positive number, -1 for negative, and 0 for zero. - **/ -static double sign (double v) { - return v > 0 ? 1 : (v < 0 ? -1 : 0); -} - - -/** - * Return the index of the matrix in ROT. - * @param index ranges from -3 to 3. - */ -static int get_rotate_index(int index) { - switch(index) { - case 0: - return 0; - case 1: - return 1; - case 2: - return 2; - case 3: - return 3; - case -1: - return 4; - case -2: - return 5; - case -3: - return 6; - } - return 0; -} - - -/** - * Return 1 if point (testx, testy) lies in the interior of the polygon - * determined by the vertices in vert, and return 0 otherwise. - * See http://paulbourke.net/geometry/polygonmesh/ for more details. - * @param nvert the number of vertices in the polygon. - * @param vert the (x, y)-coordinates of the polygon's vertices - **/ -static int pnpoly(int nvert, double vert[][2], double testx, double testy) { - int i; - int counter = 0; - double xinters; - XY p1, p2; - - /* Check for boundrary cases */ - for (i = 0; i < nvert; i++) { - if (testx == vert[i][0] && testy == vert[i][1]) { - return 1; - } - } - - p1.x = vert[0][0]; - p1.y = vert[0][1]; - - for (i = 1; i < nvert; i++) { - p2.x = vert[i % nvert][0]; - p2.y = vert[i % nvert][1]; - if (testy > MIN(p1.y, p2.y) && - testy <= MAX(p1.y, p2.y) && - testx <= MAX(p1.x, p2.x) && - p1.y != p2.y) - { - xinters = (testy-p1.y)*(p2.x-p1.x)/(p2.y-p1.y)+p1.x; - if (p1.x == p2.x || testx <= xinters) - counter++; - } - p1 = p2; - } - - if (counter % 2 == 0) { - return 0; - } else { - return 1; - } -} - - -/** - * Return 1 if (x, y) lies in (the interior or boundary of) the image of the - * HEALPix projection (in case proj=0) or in the image the rHEALPix projection - * (in case proj=1), and return 0 otherwise. - * @param north_square the position of the north polar square (rHEALPix only) - * @param south_square the position of the south polar square (rHEALPix only) - **/ -static int in_image(double x, double y, int proj, int north_square, - int south_square) { - if (proj == 0) { - double healpixVertsJit[][2] = { - {-M_PI - EPS, M_FORTPI}, - {-3*M_FORTPI, M_HALFPI + EPS}, - {-M_HALFPI, M_FORTPI + EPS}, - {-M_FORTPI, M_HALFPI + EPS}, - {0.0, M_FORTPI + EPS}, - {M_FORTPI, M_HALFPI + EPS}, - {M_HALFPI, M_FORTPI + EPS}, - {3*M_FORTPI, M_HALFPI + EPS}, - {M_PI + EPS, M_FORTPI}, - {M_PI + EPS, -M_FORTPI}, - {3*M_FORTPI, -M_HALFPI - EPS}, - {M_HALFPI, -M_FORTPI - EPS}, - {M_FORTPI, -M_HALFPI - EPS}, - {0.0, -M_FORTPI - EPS}, - {-M_FORTPI, -M_HALFPI - EPS}, - {-M_HALFPI, -M_FORTPI - EPS}, - {-3*M_FORTPI, -M_HALFPI - EPS}, - {-M_PI - EPS, -M_FORTPI} - }; - return pnpoly((int)sizeof(healpixVertsJit)/ - sizeof(healpixVertsJit[0]), healpixVertsJit, x, y); - } else { - /** - * Assigning each element by index to avoid warnings such as - * 'initializer element is not computable at load time'. - * Before C99 this was not allowed and to keep as portable as - * possible we do it the C89 way here. - * We need to assign the array this way because the input is - * dynamic (north_square and south_square vars are unknown at - * compile time). - **/ - double rhealpixVertsJit[12][2]; - rhealpixVertsJit[0][0] = -M_PI - EPS; - rhealpixVertsJit[0][1] = M_FORTPI + EPS; - rhealpixVertsJit[1][0] = -M_PI + north_square*M_HALFPI- EPS; - rhealpixVertsJit[1][1] = M_FORTPI + EPS; - rhealpixVertsJit[2][0] = -M_PI + north_square*M_HALFPI- EPS; - rhealpixVertsJit[2][1] = 3*M_FORTPI + EPS; - rhealpixVertsJit[3][0] = -M_PI + (north_square + 1.0)*M_HALFPI + EPS; - rhealpixVertsJit[3][1] = 3*M_FORTPI + EPS; - rhealpixVertsJit[4][0] = -M_PI + (north_square + 1.0)*M_HALFPI + EPS; - rhealpixVertsJit[4][1] = M_FORTPI + EPS; - rhealpixVertsJit[5][0] = M_PI + EPS; - rhealpixVertsJit[5][1] = M_FORTPI + EPS; - rhealpixVertsJit[6][0] = M_PI + EPS; - rhealpixVertsJit[6][1] = -M_FORTPI - EPS; - rhealpixVertsJit[7][0] = -M_PI + (south_square + 1.0)*M_HALFPI + EPS; - rhealpixVertsJit[7][1] = -M_FORTPI - EPS; - rhealpixVertsJit[8][0] = -M_PI + (south_square + 1.0)*M_HALFPI + EPS; - rhealpixVertsJit[8][1] = -3*M_FORTPI - EPS; - rhealpixVertsJit[9][0] = -M_PI + south_square*M_HALFPI - EPS; - rhealpixVertsJit[9][1] = -3*M_FORTPI - EPS; - rhealpixVertsJit[10][0] = -M_PI + south_square*M_HALFPI - EPS; - rhealpixVertsJit[10][1] = -M_FORTPI - EPS; - rhealpixVertsJit[11][0] = -M_PI - EPS; - rhealpixVertsJit[11][1] = -M_FORTPI - EPS; - - return pnpoly((int)sizeof(rhealpixVertsJit)/ - sizeof(rhealpixVertsJit[0]), rhealpixVertsJit, x, y); - } -} - - -/** - * Return the authalic latitude of latitude alpha (if inverse=0) or - * return the approximate latitude of authalic latitude alpha (if inverse=1). - * P contains the relevant ellipsoid parameters. - **/ -static double auth_lat(PJ *P, double alpha, int inverse) { - struct pj_opaque *Q = static_cast(P->opaque); - if (inverse == 0) { - /* Authalic latitude. */ - double q = pj_qsfn(sin(alpha), P->e, 1.0 - P->es); - double qp = Q->qp; - double ratio = q/qp; - - if (fabs(ratio) > 1) { - /* Rounding error. */ - ratio = sign(ratio); - } - return asin(ratio); - } else { - /* Approximation to inverse authalic latitude. */ - return pj_authlat(alpha, Q->apa); - } -} - - -/** - * Return the HEALPix projection of the longitude-latitude point lp on - * the unit sphere. -**/ -static XY healpix_sphere(LP lp) { - double lam = lp.lam; - double phi = lp.phi; - double phi0 = asin(2.0/3.0); - XY xy; - - /* equatorial region */ - if ( fabs(phi) <= phi0) { - xy.x = lam; - xy.y = 3*M_PI/8*sin(phi); - } else { - double lamc; - double sigma = sqrt(3*(1 - fabs(sin(phi)))); - double cn = floor(2*lam / M_PI + 2); - if (cn >= 4) { - cn = 3; - } - lamc = -3*M_FORTPI + M_HALFPI*cn; - xy.x = lamc + (lam - lamc)*sigma; - xy.y = sign(phi)*M_FORTPI*(2 - sigma); - } - return xy; -} - - -/** - * Return the inverse of healpix_sphere(). -**/ -static LP healpix_sphere_inverse(XY xy) { - LP lp; - double x = xy.x; - double y = xy.y; - double y0 = M_FORTPI; - - /* Equatorial region. */ - if (fabs(y) <= y0) { - lp.lam = x; - lp.phi = asin(8*y/(3*M_PI)); - } else if (fabs(y) < M_HALFPI) { - double cn = floor(2*x/M_PI + 2); - double xc, tau; - if (cn >= 4) { - cn = 3; - } - xc = -3*M_FORTPI + M_HALFPI*cn; - tau = 2.0 - 4*fabs(y)/M_PI; - lp.lam = xc + (x - xc)/tau; - lp.phi = sign(y)*asin(1.0 - pow(tau, 2)/3.0); - } else { - lp.lam = -M_PI; - lp.phi = sign(y)*M_HALFPI; - } - return (lp); -} - - -/** - * Return the vector sum a + b, where a and b are 2-dimensional vectors. - * @param ret holds a + b. - **/ -static void vector_add(const double a[2], const double b[2], double *ret) { - int i; - for(i = 0; i < 2; i++) { - ret[i] = a[i] + b[i]; - } -} - - -/** - * Return the vector difference a - b, where a and b are 2-dimensional vectors. - * @param ret holds a - b. - **/ -static void vector_sub(const double a[2], const double b[2], double*ret) { - int i; - for(i = 0; i < 2; i++) { - ret[i] = a[i] - b[i]; - } -} - - -/** - * Return the 2 x 1 matrix product a*b, where a is a 2 x 2 matrix and - * b is a 2 x 1 matrix. - * @param ret holds a*b. - **/ -static void dot_product(const double a[2][2], const double b[2], double *ret) { - int i, j; - int length = 2; - for(i = 0; i < length; i++) { - ret[i] = 0; - for(j = 0; j < length; j++) { - ret[i] += a[i][j]*b[j]; - } - } -} - - -/** - * Return the number of the polar cap, the pole point coordinates, and - * the region that (x, y) lies in. - * If inverse=0, then assume (x,y) lies in the image of the HEALPix - * projection of the unit sphere. - * If inverse=1, then assume (x,y) lies in the image of the - * (north_square, south_square)-rHEALPix projection of the unit sphere. - **/ -static CapMap get_cap(double x, double y, int north_square, int south_square, - int inverse) { - CapMap capmap; - double c; - - capmap.x = x; - capmap.y = y; - if (inverse == 0) { - if (y > M_FORTPI) { - capmap.region = CapMap::north; - c = M_HALFPI; - } else if (y < -M_FORTPI) { - capmap.region = CapMap::south; - c = -M_HALFPI; - } else { - capmap.region = CapMap::equatorial; - capmap.cn = 0; - return capmap; - } - /* polar region */ - if (x < -M_HALFPI) { - capmap.cn = 0; - capmap.x = (-3*M_FORTPI); - capmap.y = c; - } else if (x >= -M_HALFPI && x < 0) { - capmap.cn = 1; - capmap.x = -M_FORTPI; - capmap.y = c; - } else if (x >= 0 && x < M_HALFPI) { - capmap.cn = 2; - capmap.x = M_FORTPI; - capmap.y = c; - } else { - capmap.cn = 3; - capmap.x = 3*M_FORTPI; - capmap.y = c; - } - } else { - if (y > M_FORTPI) { - capmap.region = CapMap::north; - capmap.x = -3*M_FORTPI + north_square*M_HALFPI; - capmap.y = M_HALFPI; - x = x - north_square*M_HALFPI; - } else if (y < -M_FORTPI) { - capmap.region = CapMap::south; - capmap.x = -3*M_FORTPI + south_square*M_HALFPI; - capmap.y = -M_HALFPI; - x = x - south_square*M_HALFPI; - } else { - capmap.region = CapMap::equatorial; - capmap.cn = 0; - return capmap; - } - /* Polar Region, find the HEALPix polar cap number that - x, y moves to when rHEALPix polar square is disassembled. */ - if (capmap.region == CapMap::north) { - if (y >= -x - M_FORTPI - EPS && y < x + 5*M_FORTPI - EPS) { - capmap.cn = (north_square + 1) % 4; - } else if (y > -x -M_FORTPI + EPS && y >= x + 5*M_FORTPI - EPS) { - capmap.cn = (north_square + 2) % 4; - } else if (y <= -x -M_FORTPI + EPS && y > x + 5*M_FORTPI + EPS) { - capmap.cn = (north_square + 3) % 4; - } else { - capmap.cn = north_square; - } - } else if (capmap.region == CapMap::south) { - if (y <= x + M_FORTPI + EPS && y > -x - 5*M_FORTPI + EPS) { - capmap.cn = (south_square + 1) % 4; - } else if (y < x + M_FORTPI - EPS && y <= -x - 5*M_FORTPI + EPS) { - capmap.cn = (south_square + 2) % 4; - } else if (y >= x + M_FORTPI - EPS && y < -x - 5*M_FORTPI - EPS) { - capmap.cn = (south_square + 3) % 4; - } else { - capmap.cn = south_square; - } - } - } - return capmap; -} - - -/** - * Rearrange point (x, y) in the HEALPix projection by - * combining the polar caps into two polar squares. - * Put the north polar square in position north_square and - * the south polar square in position south_square. - * If inverse=1, then uncombine the polar caps. - * @param north_square integer between 0 and 3. - * @param south_square integer between 0 and 3. - **/ -static XY combine_caps(double x, double y, int north_square, int south_square, - int inverse) { - XY xy; - double v[2]; - double c[2]; - double vector[2]; - double v_min_c[2]; - double ret_dot[2]; - const double (*tmpRot)[2]; - int pole = 0; - - CapMap capmap = get_cap(x, y, north_square, south_square, inverse); - if (capmap.region == CapMap::equatorial) { - xy.x = capmap.x; - xy.y = capmap.y; - return xy; - } - - v[0] = x; v[1] = y; - c[0] = capmap.x; c[1] = capmap.y; - - if (inverse == 0) { - /* Rotate (x, y) about its polar cap tip and then translate it to - north_square or south_square. */ - - if (capmap.region == CapMap::north) { - pole = north_square; - tmpRot = rot[get_rotate_index(capmap.cn - pole)]; - } else { - pole = south_square; - tmpRot = rot[get_rotate_index(-1*(capmap.cn - pole))]; - } - } else { - /* Inverse function. - Unrotate (x, y) and then translate it back. */ - - /* disassemble */ - if (capmap.region == CapMap::north) { - pole = north_square; - tmpRot = rot[get_rotate_index(-1*(capmap.cn - pole))]; - } else { - pole = south_square; - tmpRot = rot[get_rotate_index(capmap.cn - pole)]; - } - } - - vector_sub(v, c, v_min_c); - dot_product(tmpRot, v_min_c, ret_dot); - { - double a[2]; - /* Workaround cppcheck git issue */ - double* pa = a; - pa[0] = -3*M_FORTPI + ((inverse == 0) ? pole : capmap.cn) *M_HALFPI; - pa[1] = ((capmap.region == CapMap::north) ? 1 : -1) *M_HALFPI; - vector_add(ret_dot, a, vector); - } - - xy.x = vector[0]; - xy.y = vector[1]; - return xy; -} - - -static XY s_healpix_forward(LP lp, PJ *P) { /* sphere */ - (void) P; - return healpix_sphere(lp); -} - - -static XY e_healpix_forward(LP lp, PJ *P) { /* ellipsoid */ - lp.phi = auth_lat(P, lp.phi, 0); - return healpix_sphere(lp); -} - - -static LP s_healpix_inverse(XY xy, PJ *P) { /* sphere */ - /* Check whether (x, y) lies in the HEALPix image */ - if (in_image(xy.x, xy.y, 0, 0, 0) == 0) { - LP lp; - lp.lam = HUGE_VAL; - lp.phi = HUGE_VAL; - pj_ctx_set_errno(P->ctx, PJD_ERR_INVALID_X_OR_Y); - return lp; - } - return healpix_sphere_inverse(xy); -} - - -static LP e_healpix_inverse(XY xy, PJ *P) { /* ellipsoid */ - LP lp = {0.0,0.0}; - - /* Check whether (x, y) lies in the HEALPix image. */ - if (in_image(xy.x, xy.y, 0, 0, 0) == 0) { - lp.lam = HUGE_VAL; - lp.phi = HUGE_VAL; - pj_ctx_set_errno(P->ctx, PJD_ERR_INVALID_X_OR_Y); - return lp; - } - lp = healpix_sphere_inverse(xy); - lp.phi = auth_lat(P, lp.phi, 1); - return lp; -} - - -static XY s_rhealpix_forward(LP lp, PJ *P) { /* sphere */ - struct pj_opaque *Q = static_cast(P->opaque); - - XY xy = healpix_sphere(lp); - return combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 0); -} - - -static XY e_rhealpix_forward(LP lp, PJ *P) { /* ellipsoid */ - struct pj_opaque *Q = static_cast(P->opaque); - XY xy; - lp.phi = auth_lat(P, lp.phi, 0); - xy = healpix_sphere(lp); - return combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 0); -} - - -static LP s_rhealpix_inverse(XY xy, PJ *P) { /* sphere */ - struct pj_opaque *Q = static_cast(P->opaque); - - /* Check whether (x, y) lies in the rHEALPix image. */ - if (in_image(xy.x, xy.y, 1, Q->north_square, Q->south_square) == 0) { - LP lp; - lp.lam = HUGE_VAL; - lp.phi = HUGE_VAL; - pj_ctx_set_errno(P->ctx, PJD_ERR_INVALID_X_OR_Y); - return lp; - } - xy = combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 1); - return healpix_sphere_inverse(xy); -} - - -static LP e_rhealpix_inverse(XY xy, PJ *P) { /* ellipsoid */ - struct pj_opaque *Q = static_cast(P->opaque); - LP lp = {0.0,0.0}; - - /* Check whether (x, y) lies in the rHEALPix image. */ - if (in_image(xy.x, xy.y, 1, Q->north_square, Q->south_square) == 0) { - lp.lam = HUGE_VAL; - lp.phi = HUGE_VAL; - pj_ctx_set_errno(P->ctx, PJD_ERR_INVALID_X_OR_Y); - return lp; - } - xy = combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 1); - lp = healpix_sphere_inverse(xy); - lp.phi = auth_lat(P, lp.phi, 1); - return lp; -} - - -static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->apa); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(healpix) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - if (P->es != 0.0) { - Q->apa = pj_authset(P->es); /* For auth_lat(). */ - if (nullptr==Q->apa) - return destructor(P, ENOMEM); - Q->qp = pj_qsfn(1.0, P->e, P->one_es); /* For auth_lat(). */ - P->a = P->a*sqrt(0.5*Q->qp); /* Set P->a to authalic radius. */ - pj_calc_ellipsoid_params (P, P->a, P->es); /* Ensure we have a consistent parameter set */ - P->fwd = e_healpix_forward; - P->inv = e_healpix_inverse; - } else { - P->fwd = s_healpix_forward; - P->inv = s_healpix_inverse; - } - - return P; -} - - -PJ *PROJECTION(rhealpix) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - Q->north_square = pj_param(P->ctx, P->params,"inorth_square").i; - Q->south_square = pj_param(P->ctx, P->params,"isouth_square").i; - - /* Check for valid north_square and south_square inputs. */ - if (Q->north_square < 0 || Q->north_square > 3) - return destructor (P, PJD_ERR_AXIS); - if (Q->south_square < 0 || Q->south_square > 3) - return destructor (P, PJD_ERR_AXIS); - if (P->es != 0.0) { - Q->apa = pj_authset(P->es); /* For auth_lat(). */ - if (nullptr==Q->apa) - return destructor(P, ENOMEM); - Q->qp = pj_qsfn(1.0, P->e, P->one_es); /* For auth_lat(). */ - P->a = P->a*sqrt(0.5*Q->qp); /* Set P->a to authalic radius. */ - P->ra = 1.0/P->a; - P->fwd = e_rhealpix_forward; - P->inv = e_rhealpix_inverse; - } else { - P->fwd = s_rhealpix_forward; - P->inv = s_rhealpix_inverse; - } - - return P; -} diff --git a/src/PJ_helmert.cpp b/src/PJ_helmert.cpp deleted file mode 100644 index 4a3abf4e..00000000 --- a/src/PJ_helmert.cpp +++ /dev/null @@ -1,755 +0,0 @@ -/*********************************************************************** - - 3-, 4-and 7-parameter shifts, and their 6-, 8- - and 14-parameter kinematic counterparts. - - Thomas Knudsen, 2016-05-24 - -************************************************************************ - - Implements 3(6)-, 4(8) and 7(14)-parameter Helmert transformations for - 3D data. - - Also incorporates Molodensky-Badekas variant of 7-parameter Helmert - transformation, where the rotation is not applied regarding the centre - of the spheroid, but given a reference point. - - Primarily useful for implementation of datum shifts in transformation - pipelines. - -************************************************************************ - -Thomas Knudsen, thokn@sdfe.dk, 2016-05-24/06-05 -Kristian Evers, kreve@sdfe.dk, 2017-05-01 -Even Rouault, even.roault@spatialys.com -Last update: 2018-10-26 - -************************************************************************ -* Copyright (c) 2016, Thomas Knudsen / SDFE -* -* 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. -* -***********************************************************************/ - -#define PJ_LIB__ - -#include -#include - -#include "proj_internal.h" -#include "projects.h" -#include "geocent.h" - -PROJ_HEAD(helmert, "3(6)-, 4(8)- and 7(14)-parameter Helmert shift"); -PROJ_HEAD(molobadekas, "Molodensky-Badekas transformation"); - -static XYZ helmert_forward_3d (LPZ lpz, PJ *P); -static LPZ helmert_reverse_3d (XYZ xyz, PJ *P); - - - -/***********************************************************************/ -namespace { // anonymous namespace -struct pj_opaque_helmert { -/************************************************************************ - Projection specific elements for the "helmert" PJ object -************************************************************************/ - XYZ xyz; - XYZ xyz_0; - XYZ dxyz; - XYZ refp; - PJ_OPK opk; - PJ_OPK opk_0; - PJ_OPK dopk; - double scale; - double scale_0; - double dscale; - double theta; - double theta_0; - double dtheta; - double R[3][3]; - double t_epoch, t_obs; - int no_rotation, exact, fourparam; - int is_position_vector; /* 1 = position_vector, 0 = coordinate_frame */ -}; -} // anonymous namespace - - -/* Make the maths of the rotation operations somewhat more readable and textbook like */ -#define R00 (Q->R[0][0]) -#define R01 (Q->R[0][1]) -#define R02 (Q->R[0][2]) - -#define R10 (Q->R[1][0]) -#define R11 (Q->R[1][1]) -#define R12 (Q->R[1][2]) - -#define R20 (Q->R[2][0]) -#define R21 (Q->R[2][1]) -#define R22 (Q->R[2][2]) - -/**************************************************************************/ -static void update_parameters(PJ *P) { -/*************************************************************************** - - Update transformation parameters. - --------------------------------- - - The 14-parameter Helmert transformation is at it's core the same as the - 7-parameter transformation, since the transformation parameters are - projected forward or backwards in time via the rate of changes of the - parameters. The transformation parameters are calculated for a specific - epoch before the actual Helmert transformation is carried out. - - The transformation parameters are updated with the following equation [0]: - - . - P(t) = P(EPOCH) + P * (t - EPOCH) - - . - where EPOCH is the epoch indicated in the above table and P is the rate - of that parameter. - - [0] http://itrf.ign.fr/doc_ITRF/Transfo-ITRF2008_ITRFs.txt - -*******************************************************************************/ - - struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; - double dt = Q->t_obs - Q->t_epoch; - - Q->xyz.x = Q->xyz_0.x + Q->dxyz.x * dt; - Q->xyz.y = Q->xyz_0.y + Q->dxyz.y * dt; - Q->xyz.z = Q->xyz_0.z + Q->dxyz.z * dt; - - Q->opk.o = Q->opk_0.o + Q->dopk.o * dt; - Q->opk.p = Q->opk_0.p + Q->dopk.p * dt; - Q->opk.k = Q->opk_0.k + Q->dopk.k * dt; - - Q->scale = Q->scale_0 + Q->dscale * dt; - - Q->theta = Q->theta_0 + Q->dtheta * dt; - - /* debugging output */ - if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { - proj_log_trace(P, "Transformation parameters for observation " - "t_obs=%g (t_epoch=%g):", Q->t_obs, Q->t_epoch); - proj_log_trace(P, "x: %g", Q->xyz.x); - proj_log_trace(P, "y: %g", Q->xyz.y); - proj_log_trace(P, "z: %g", Q->xyz.z); - proj_log_trace(P, "s: %g", Q->scale*1e-6); - proj_log_trace(P, "rx: %g", Q->opk.o); - proj_log_trace(P, "ry: %g", Q->opk.p); - proj_log_trace(P, "rz: %g", Q->opk.k); - proj_log_trace(P, "theta: %g", Q->theta); - } -} - -/**************************************************************************/ -static void build_rot_matrix(PJ *P) { -/*************************************************************************** - - Build rotation matrix. - ---------------------- - - Here we rename rotation indices from omega, phi, kappa (opk), to - fi (i.e. phi), theta, psi (ftp), in order to reduce the mental agility - needed to implement the expression for the rotation matrix derived over - at https://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions - The relevant section is Euler angles ( z-’-x" intrinsic) -> Rotation matrix - - By default small angle approximations are used: - The matrix elements are approximated by expanding the trigonometric - functions to linear order (i.e. cos(x) = 1, sin(x) = x), and discarding - products of second order. - - This was a useful hack when calculating by hand was the only option, - but in general, today, should be avoided because: - - 1. It does not save much computation time, as the rotation matrix - is built only once and probably used many times (except when - transforming spatio-temporal coordinates). - - 2. The error induced may be too large for ultra high accuracy - applications: the Earth is huge and the linear error is - approximately the angular error multiplied by the Earth radius. - - However, in many cases the approximation is necessary, since it has - been used historically: Rotation angles from older published datum - shifts may actually be a least squares fit to the linearized rotation - approximation, hence not being strictly valid for deriving the exact - rotation matrix. In fact, most publicly available transformation - parameters are based on the approximate Helmert transform, which is why - we use that as the default setting, even though it is more correct to - use the exact form of the equations. - - So in order to fit historically derived coordinates, the access to - the approximate rotation matrix is necessary - at least in principle. - - Also, when using any published datum transformation information, one - should always check which convention (exact or approximate rotation - matrix) is expected, and whether the induced error for selecting - the opposite convention is acceptable (which it often is). - - - Sign conventions - ---------------- - - Take care: Two different sign conventions exist for the rotation terms. - - Conceptually they relate to whether we rotate the coordinate system - or the "position vector" (the vector going from the coordinate system - origin to the point being transformed, i.e. the point coordinates - interpreted as vector coordinates). - - Switching between the "position vector" and "coordinate system" - conventions is simply a matter of switching the sign of the rotation - angles, which algebraically also translates into a transposition of - the rotation matrix. - - Hence, as geodetic constants should preferably be referred to exactly - as published, the "convention" option provides the ability to switch - between the conventions. - -***************************************************************************/ - struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; - - double f, t, p; /* phi/fi , theta, psi */ - double cf, ct, cp; /* cos (fi, theta, psi) */ - double sf, st, sp; /* sin (fi, theta, psi) */ - - /* rename (omega, phi, kappa) to (fi, theta, psi) */ - f = Q->opk.o; - t = Q->opk.p; - p = Q->opk.k; - - /* Those equations are given assuming coordinate frame convention. */ - /* For the position vector convention, we transpose the matrix just after. */ - if (Q->exact) { - cf = cos(f); - sf = sin(f); - ct = cos(t); - st = sin(t); - cp = cos(p); - sp = sin(p); - - - R00 = ct*cp; - R01 = cf*sp + sf*st*cp; - R02 = sf*sp - cf*st*cp; - - R10 = -ct*sp; - R11 = cf*cp - sf*st*sp; - R12 = sf*cp + cf*st*sp; - - R20 = st; - R21 = -sf*ct; - R22 = cf*ct; - } else{ - R00 = 1; - R01 = p; - R02 = -t; - - R10 = -p; - R11 = 1; - R12 = f; - - R20 = t; - R21 = -f; - R22 = 1; - } - - - /* - For comparison: Description from Engsager/Poder implementation - in set_dtm_1.c (trlib) - - DATUM SHIFT: - TO = scale * ROTZ * ROTY * ROTX * FROM + TRANSLA - - ( cz sz 0) (cy 0 -sy) (1 0 0) - ROTZ=(-sz cz 0), ROTY=(0 1 0), ROTX=(0 cx sx) - ( 0 0 1) (sy 0 cy) (0 -sx cx) - - trp->r11 = cos_ry*cos_rz; - trp->r12 = cos_rx*sin_rz + sin_rx*sin_ry*cos_rz; - trp->r13 = sin_rx*sin_rz - cos_rx*sin_ry*cos_rz; - - trp->r21 = -cos_ry*sin_rz; - trp->r22 = cos_rx*cos_rz - sin_rx*sin_ry*sin_rz; - trp->r23 = sin_rx*cos_rz + cos_rx*sin_ry*sin_rz; - - trp->r31 = sin_ry; - trp->r32 = -sin_rx*cos_ry; - trp->r33 = cos_rx*cos_ry; - - trp->scale = 1.0 + scale; - */ - - - if (Q->is_position_vector) { - double r; - r = R01; R01 = R10; R10 = r; - r = R02; R02 = R20; R20 = r; - r = R12; R12 = R21; R21 = r; - } - - /* some debugging output */ - if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { - proj_log_trace(P, "Rotation Matrix:"); - proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R00, R01, R02); - proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R10, R11, R12); - proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R20, R21, R22); - } -} - - - - -/***********************************************************************/ -static XY helmert_forward (LP lp, PJ *P) { -/***********************************************************************/ - struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - double x, y, cr, sr; - point.lp = lp; - - cr = cos(Q->theta) * Q->scale; - sr = sin(Q->theta) * Q->scale; - x = point.xy.x; - y = point.xy.y; - - point.xy.x = cr*x + sr*y + Q->xyz_0.x; - point.xy.y = -sr*x + cr*y + Q->xyz_0.y; - - return point.xy; -} - - -/***********************************************************************/ -static LP helmert_reverse (XY xy, PJ *P) { -/***********************************************************************/ - struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - double x, y, sr, cr; - point.xy = xy; - - cr = cos(Q->theta) / Q->scale; - sr = sin(Q->theta) / Q->scale; - x = point.xy.x - Q->xyz_0.x; - y = point.xy.y - Q->xyz_0.y; - - point.xy.x = x*cr - y*sr; - point.xy.y = x*sr + y*cr; - - return point.lp; -} - - -/***********************************************************************/ -static XYZ helmert_forward_3d (LPZ lpz, PJ *P) { -/***********************************************************************/ - struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - double X, Y, Z, scale; - - point.lpz = lpz; - - if (Q->fourparam) { - point.xy = helmert_forward(point.lp, P); - return point.xyz; - } - - if (Q->no_rotation) { - point.xyz.x = lpz.lam + Q->xyz.x; - point.xyz.y = lpz.phi + Q->xyz.y; - point.xyz.z = lpz.z + Q->xyz.z; - return point.xyz; - } - - scale = 1 + Q->scale * 1e-6; - - X = lpz.lam - Q->refp.x; - Y = lpz.phi - Q->refp.y; - Z = lpz.z - Q->refp.z; - - - point.xyz.x = scale * ( R00 * X + R01 * Y + R02 * Z); - point.xyz.y = scale * ( R10 * X + R11 * Y + R12 * Z); - point.xyz.z = scale * ( R20 * X + R21 * Y + R22 * Z); - - point.xyz.x += Q->xyz.x; /* for Molodensky-Badekas, Q->xyz already incorporates the Q->refp offset */ - point.xyz.y += Q->xyz.y; - point.xyz.z += Q->xyz.z; - - return point.xyz; -} - - -/***********************************************************************/ -static LPZ helmert_reverse_3d (XYZ xyz, PJ *P) { -/***********************************************************************/ - struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - double X, Y, Z, scale; - - point.xyz = xyz; - - if (Q->fourparam) { - point.lp = helmert_reverse(point.xy, P); - return point.lpz; - } - - if (Q->no_rotation) { - point.xyz.x = xyz.x - Q->xyz.x; - point.xyz.y = xyz.y - Q->xyz.y; - point.xyz.z = xyz.z - Q->xyz.z; - return point.lpz; - } - - scale = 1 + Q->scale * 1e-6; - - /* Unscale and deoffset */ - X = (xyz.x - Q->xyz.x) / scale; - Y = (xyz.y - Q->xyz.y) / scale; - Z = (xyz.z - Q->xyz.z) / scale; - - /* Inverse rotation through transpose multiplication */ - point.xyz.x = ( R00 * X + R10 * Y + R20 * Z) + Q->refp.x; - point.xyz.y = ( R01 * X + R11 * Y + R21 * Z) + Q->refp.y; - point.xyz.z = ( R02 * X + R12 * Y + R22 * Z) + Q->refp.z; - - return point.lpz; -} - - -static PJ_COORD helmert_forward_4d (PJ_COORD point, PJ *P) { - struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; - - /* We only need to rebuild the rotation matrix if the - * observation time is different from the last call */ - double t_obs = (point.xyzt.t == HUGE_VAL) ? Q->t_epoch : point.xyzt.t; - if (t_obs != Q->t_obs) { - Q->t_obs = t_obs; - update_parameters(P); - build_rot_matrix(P); - } - - point.xyz = helmert_forward_3d (point.lpz, P); - - return point; -} - - -static PJ_COORD helmert_reverse_4d (PJ_COORD point, PJ *P) { - struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; - - /* We only need to rebuild the rotation matrix if the - * observation time is different from the last call */ - double t_obs = (point.xyzt.t == HUGE_VAL) ? Q->t_epoch : point.xyzt.t; - if (t_obs != Q->t_obs) { - Q->t_obs = t_obs; - update_parameters(P); - build_rot_matrix(P); - } - - point.lpz = helmert_reverse_3d (point.xyz, P); - - return point; -} - -/* Arcsecond to radians */ -#define ARCSEC_TO_RAD (DEG_TO_RAD / 3600.0) - - -static PJ* init_helmert_six_parameters(PJ* P) { - struct pj_opaque_helmert *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque_helmert))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = (void *) Q; - - /* In most cases, we work on 3D cartesian coordinates */ - P->left = PJ_IO_UNITS_CARTESIAN; - P->right = PJ_IO_UNITS_CARTESIAN; - - /* Translations */ - if (pj_param (P->ctx, P->params, "tx").i) - Q->xyz_0.x = pj_param (P->ctx, P->params, "dx").f; - - if (pj_param (P->ctx, P->params, "ty").i) - Q->xyz_0.y = pj_param (P->ctx, P->params, "dy").f; - - if (pj_param (P->ctx, P->params, "tz").i) - Q->xyz_0.z = pj_param (P->ctx, P->params, "dz").f; - - /* Rotations */ - if (pj_param (P->ctx, P->params, "trx").i) - Q->opk_0.o = pj_param (P->ctx, P->params, "drx").f * ARCSEC_TO_RAD; - - if (pj_param (P->ctx, P->params, "try").i) - Q->opk_0.p = pj_param (P->ctx, P->params, "dry").f * ARCSEC_TO_RAD; - - if (pj_param (P->ctx, P->params, "trz").i) - Q->opk_0.k = pj_param (P->ctx, P->params, "drz").f * ARCSEC_TO_RAD; - - /* Use small angle approximations? */ - if (pj_param (P->ctx, P->params, "bexact").i) - Q->exact = 1; - - return P; -} - - -static PJ* read_convention(PJ* P) { - - struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; - - /* In case there are rotational terms, we require an explicit convention - * to be provided. */ - if (!Q->no_rotation) { - const char* convention = pj_param (P->ctx, P->params, "sconvention").s; - if( !convention ) { - proj_log_error (P, "helmert: missing 'convention' argument"); - return pj_default_destructor (P, PJD_ERR_MISSING_ARGS); - } - if( strcmp(convention, "position_vector") == 0 ) { - Q->is_position_vector = 1; - } - else if( strcmp(convention, "coordinate_frame") == 0 ) { - Q->is_position_vector = 0; - } - else { - proj_log_error (P, "helmert: invalid value for 'convention' argument"); - return pj_default_destructor (P, PJD_ERR_INVALID_ARG); - } - - /* historically towgs84 in PROJ has always been using position_vector - * convention. Accepting coordinate_frame would be confusing. */ - if (pj_param_exists (P->params, "towgs84")) { - if( !Q->is_position_vector ) { - proj_log_error (P, "helmert: towgs84 should only be used with " - "convention=position_vector"); - return pj_default_destructor (P, PJD_ERR_INVALID_ARG); - } - } - } - - return P; -} - - -/***********************************************************************/ -PJ *TRANSFORMATION(helmert, 0) { -/***********************************************************************/ - - struct pj_opaque_helmert *Q; - - if( !init_helmert_six_parameters(P) ) { - return nullptr; - } - - /* In the 2D case, the coordinates are projected */ - if (pj_param_exists (P->params, "theta")) { - P->left = PJ_IO_UNITS_PROJECTED; - P->right = PJ_IO_UNITS_PROJECTED; - } - - P->fwd4d = helmert_forward_4d; - P->inv4d = helmert_reverse_4d; - P->fwd3d = helmert_forward_3d; - P->inv3d = helmert_reverse_3d; - P->fwd = helmert_forward; - P->inv = helmert_reverse; - - Q = (struct pj_opaque_helmert *)P->opaque; - - /* Detect obsolete transpose flag and error out if found */ - if (pj_param (P->ctx, P->params, "ttranspose").i) { - proj_log_error (P, "helmert: 'transpose' argument is no longer valid. " - "Use convention=position_vector/coordinate_frame"); - return pj_default_destructor (P, PJD_ERR_INVALID_ARG); - } - - /* Support the classic PROJ towgs84 parameter, but allow later overrides.*/ - /* Note that if towgs84 is specified, the datum_params array is set up */ - /* for us automagically by the pj_datum_set call in pj_init_ctx */ - if (pj_param_exists (P->params, "towgs84")) { - Q->xyz_0.x = P->datum_params[0]; - Q->xyz_0.y = P->datum_params[1]; - Q->xyz_0.z = P->datum_params[2]; - - Q->opk_0.o = P->datum_params[3]; - Q->opk_0.p = P->datum_params[4]; - Q->opk_0.k = P->datum_params[5]; - - /* We must undo conversion to absolute scale from pj_datum_set */ - if (0==P->datum_params[6]) - Q->scale_0 = 0; - else - Q->scale_0 = (P->datum_params[6] - 1) * 1e6; - } - - if (pj_param (P->ctx, P->params, "ttheta").i) { - Q->theta_0 = pj_param (P->ctx, P->params, "dtheta").f * ARCSEC_TO_RAD; - Q->fourparam = 1; - Q->scale_0 = 1.0; /* default scale for the 4-param shift */ - } - - /* Scale */ - if (pj_param (P->ctx, P->params, "ts").i) { - Q->scale_0 = pj_param (P->ctx, P->params, "ds").f; - if (pj_param (P->ctx, P->params, "ttheta").i && Q->scale_0 == 0.0) - return pj_default_destructor (P, PJD_ERR_INVALID_SCALE); - } - - /* Translation rates */ - if (pj_param(P->ctx, P->params, "tdx").i) - Q->dxyz.x = pj_param (P->ctx, P->params, "ddx").f; - - if (pj_param(P->ctx, P->params, "tdy").i) - Q->dxyz.y = pj_param (P->ctx, P->params, "ddy").f; - - if (pj_param(P->ctx, P->params, "tdz").i) - Q->dxyz.z = pj_param (P->ctx, P->params, "ddz").f; - - /* Rotations rates */ - if (pj_param (P->ctx, P->params, "tdrx").i) - Q->dopk.o = pj_param (P->ctx, P->params, "ddrx").f * ARCSEC_TO_RAD; - - if (pj_param (P->ctx, P->params, "tdry").i) - Q->dopk.p = pj_param (P->ctx, P->params, "ddry").f * ARCSEC_TO_RAD; - - if (pj_param (P->ctx, P->params, "tdrz").i) - Q->dopk.k = pj_param (P->ctx, P->params, "ddrz").f * ARCSEC_TO_RAD; - - if (pj_param (P->ctx, P->params, "tdtheta").i) - Q->dtheta = pj_param (P->ctx, P->params, "ddtheta").f * ARCSEC_TO_RAD; - - /* Scale rate */ - if (pj_param (P->ctx, P->params, "tds").i) - Q->dscale = pj_param (P->ctx, P->params, "dds").f; - - - /* Epoch */ - if (pj_param(P->ctx, P->params, "tt_epoch").i) - Q->t_epoch = pj_param (P->ctx, P->params, "dt_epoch").f; - - if (pj_param(P->ctx, P->params, "tt_obs").i) - Q->t_obs = pj_param (P->ctx, P->params, "dt_obs").f; - - Q->xyz = Q->xyz_0; - Q->opk = Q->opk_0; - Q->scale = Q->scale_0; - Q->theta = Q->theta_0; - - if ((Q->opk.o==0) && (Q->opk.p==0) && (Q->opk.k==0) && (Q->scale==0) && - (Q->dopk.o==0) && (Q->dopk.p==0) && (Q->dopk.k==0)) { - Q->no_rotation = 1; - } - - if( !read_convention(P) ) { - return nullptr; - } - - /* Let's help with debugging */ - if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { - proj_log_debug(P, "Helmert parameters:"); - proj_log_debug(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz.x, Q->xyz.y, Q->xyz.z); - proj_log_debug(P, "rx= %8.5f ry= %8.5f rz= %8.5f", - Q->opk.o / ARCSEC_TO_RAD, Q->opk.p / ARCSEC_TO_RAD, Q->opk.k / ARCSEC_TO_RAD); - proj_log_debug(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, - Q->no_rotation ? "" : - Q->is_position_vector ? " convention=position_vector" : - " convention=coordinate_frame"); - proj_log_debug(P, "dx= %8.5f dy= %8.5f dz= %8.5f", Q->dxyz.x, Q->dxyz.y, Q->dxyz.z); - proj_log_debug(P, "drx=%8.5f dry=%8.5f drz=%8.5f", Q->dopk.o, Q->dopk.p, Q->dopk.k); - proj_log_debug(P, "ds= %8.5f t_epoch=%8.5f t_obs=%8.5f", Q->dscale, Q->t_epoch, Q->t_obs); - } - - if (Q->no_rotation) { - return P; - } - - update_parameters(P); - build_rot_matrix(P); - - return P; -} - - -/***********************************************************************/ -PJ *TRANSFORMATION(molobadekas, 0) { -/***********************************************************************/ - - struct pj_opaque_helmert *Q; - - if( !init_helmert_six_parameters(P) ) { - return nullptr; - } - - P->fwd3d = helmert_forward_3d; - P->inv3d = helmert_reverse_3d; - - Q = (struct pj_opaque_helmert *)P->opaque; - - /* Scale */ - if (pj_param (P->ctx, P->params, "ts").i) { - Q->scale_0 = pj_param (P->ctx, P->params, "ds").f; - } - - Q->opk = Q->opk_0; - Q->scale = Q->scale_0; - - if( !read_convention(P) ) { - return nullptr; - } - - /* Reference point */ - if (pj_param (P->ctx, P->params, "tpx").i) - Q->refp.x = pj_param (P->ctx, P->params, "dpx").f; - - if (pj_param (P->ctx, P->params, "tpy").i) - Q->refp.y = pj_param (P->ctx, P->params, "dpy").f; - - if (pj_param (P->ctx, P->params, "tpz").i) - Q->refp.z = pj_param (P->ctx, P->params, "dpz").f; - - - /* Let's help with debugging */ - if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { - proj_log_debug(P, "Molodensky-Badekas parameters:"); - proj_log_debug(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz_0.x, Q->xyz_0.y, Q->xyz_0.z); - proj_log_debug(P, "rx= %8.5f ry= %8.5f rz= %8.5f", - Q->opk.o / ARCSEC_TO_RAD, Q->opk.p / ARCSEC_TO_RAD, Q->opk.k / ARCSEC_TO_RAD); - proj_log_debug(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, - Q->is_position_vector ? " convention=position_vector" : - " convention=coordinate_frame"); - proj_log_debug(P, "px= %8.5f py= %8.5f pz= %8.5f", Q->refp.x, Q->refp.y, Q->refp.z); - } - - /* as an optimization, we incorporate the refp in the translation terms */ - Q->xyz_0.x += Q->refp.x; - Q->xyz_0.y += Q->refp.y; - Q->xyz_0.z += Q->refp.z; - - Q->xyz = Q->xyz_0; - - build_rot_matrix(P); - - return P; -} diff --git a/src/PJ_hgridshift.cpp b/src/PJ_hgridshift.cpp deleted file mode 100644 index f0e57251..00000000 --- a/src/PJ_hgridshift.cpp +++ /dev/null @@ -1,133 +0,0 @@ -#define PJ_LIB__ - -#include -#include -#include -#include - -#include "proj_internal.h" -#include "projects.h" - -PROJ_HEAD(hgridshift, "Horizontal grid shift"); - -namespace { // anonymous namespace -struct pj_opaque_hgridshift { - double t_final; - double t_epoch; -}; -} // anonymous namespace - -static XYZ forward_3d(LPZ lpz, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - point.lpz = lpz; - - if (P->gridlist != nullptr) { - /* Only try the gridshift if at least one grid is loaded, - * otherwise just pass the coordinate through unchanged. */ - point.lp = proj_hgrid_apply(P, point.lp, PJ_FWD); - } - - return point.xyz; -} - - -static LPZ reverse_3d(XYZ xyz, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - point.xyz = xyz; - - if (P->gridlist != nullptr) { - /* Only try the gridshift if at least one grid is loaded, - * otherwise just pass the coordinate through unchanged. */ - point.lp = proj_hgrid_apply(P, point.lp, PJ_INV); - } - - return point.lpz; -} - -static PJ_COORD forward_4d(PJ_COORD obs, PJ *P) { - struct pj_opaque_hgridshift *Q = (struct pj_opaque_hgridshift *) P->opaque; - PJ_COORD point = obs; - - /* If transformation is not time restricted, we always call it */ - if (Q->t_final==0 || Q->t_epoch==0) { - point.xyz = forward_3d (obs.lpz, P); - return point; - } - - /* Time restricted - only apply transform if within time bracket */ - if (obs.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) - point.xyz = forward_3d (obs.lpz, P); - - - return point; -} - -static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) { - struct pj_opaque_hgridshift *Q = (struct pj_opaque_hgridshift *) P->opaque; - PJ_COORD point = obs; - - /* If transformation is not time restricted, we always call it */ - if (Q->t_final==0 || Q->t_epoch==0) { - point.lpz = reverse_3d (obs.xyz, P); - return point; - } - - /* Time restricted - only apply transform if within time bracket */ - if (obs.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) - point.lpz = reverse_3d (obs.xyz, P); - - return point; -} - - -PJ *TRANSFORMATION(hgridshift,0) { - struct pj_opaque_hgridshift *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque_hgridshift))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = (void *) Q; - - P->fwd4d = forward_4d; - P->inv4d = reverse_4d; - P->fwd3d = forward_3d; - P->inv3d = reverse_3d; - P->fwd = nullptr; - P->inv = nullptr; - - P->left = PJ_IO_UNITS_ANGULAR; - P->right = PJ_IO_UNITS_ANGULAR; - - if (0==pj_param(P->ctx, P->params, "tgrids").i) { - proj_log_error(P, "hgridshift: +grids parameter missing."); - return pj_default_destructor (P, PJD_ERR_NO_ARGS); - } - - /* TODO: Refactor into shared function that can be used */ - /* by both vgridshift and hgridshift */ - if (pj_param(P->ctx, P->params, "tt_final").i) { - Q->t_final = pj_param (P->ctx, P->params, "dt_final").f; - if (Q->t_final == 0) { - /* a number wasn't passed to +t_final, let's see if it was "now" */ - /* and set the time accordingly. */ - if (!strcmp("now", pj_param(P->ctx, P->params, "st_final").s)) { - time_t now; - struct tm *date; - time(&now); - date = localtime(&now); - Q->t_final = 1900.0 + date->tm_year + date->tm_yday/365.0; - } - } - } - - if (pj_param(P->ctx, P->params, "tt_epoch").i) - Q->t_epoch = pj_param (P->ctx, P->params, "dt_epoch").f; - - - proj_hgrid_init(P, "grids"); - /* Was gridlist compiled properly? */ - if ( proj_errno(P) ) { - proj_log_error(P, "hgridshift: could not find required grid(s)."); - return pj_default_destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID); - } - - return P; -} diff --git a/src/PJ_horner.cpp b/src/PJ_horner.cpp deleted file mode 100644 index 73977de6..00000000 --- a/src/PJ_horner.cpp +++ /dev/null @@ -1,513 +0,0 @@ -/*********************************************************************** - - Interfacing to a classic piece of geodetic software - -************************************************************************ - - gen_pol is a highly efficient, classic implementation of a generic - 2D Horner's Scheme polynomial evaluation routine by Knud Poder and - Karsten Engsager, originating in the vivid geodetic environment at - what was then (1960-ish) the Danish Geodetic Institute. - - The original Poder/Engsager gen_pol implementation (where - the polynomial degree and two sets of polynomial coefficients - are packed together in one compound array, handled via a plain - double pointer) is compelling and "true to the code history": - - It has a beautiful classical 1960s ring to it, not unlike the - original fft implementations, which revolutionized spectral - analysis in twenty lines of code. - - The Poder coding sound, as classic 1960s as Phil Spector's Wall - of Sound, is beautiful and inimitable. - - On the other hand: For the uninitiated, the gen_pol code is hard - to follow, despite being compact. - - Also, since adding metadata and improving maintainability - of the code are among the implied goals of a current SDFE/DTU Space - project, the material in this file introduces a version with a - more modern (or at least 1990s) look, introducing a "double 2D - polynomial" data type, HORNER. - - Despite introducing a new data type for handling the polynomial - coefficients, great care has been taken to keep the coefficient - array organization identical to that of gen_pol. - - Hence, on one hand, the HORNER data type helps improving the - long term maintainability of the code by making the data - organization more mentally accessible. - - On the other hand, it allows us to preserve the business end of - the original gen_pol implementation - although not including the - famous "Poder dual autocheck" in all its enigmatic elegance. - - ********************************************************************** - - The material included here was written by Knud Poder, starting - around 1960, and Karsten Engsager, starting around 1970. It was - originally written in Algol 60, later (1980s) reimplemented in C. - - The HORNER data type interface, and the organization as a header - library was implemented by Thomas Knudsen, starting around 2015. - - *********************************************************************** - * - * Copyright (c) 2016, SDFE http://www.sdfe.dk / Thomas Knudsen / Karsten Engsager - * - * 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. - * - *****************************************************************************/ - -#define PJ_LIB__ - -#include -#include -#include -#include -#include - -#include "proj_internal.h" -#include "projects.h" - -PROJ_HEAD(horner, "Horner polynomial evaluation"); - -/* make horner.h interface with proj's memory management */ -#define horner_dealloc(x) pj_dealloc(x) -#define horner_calloc(n,x) pj_calloc(n,x) - -namespace { // anonymous namespace -struct horner { - int uneg; /* u axis negated? */ - int vneg; /* v axis negated? */ - int order; /* maximum degree of polynomium */ - int coefs; /* number of coefficients for each polynomium */ - double range; /* radius of the region of validity */ - - double *fwd_u; /* coefficients for the forward transformations */ - double *fwd_v; /* i.e. latitude/longitude to northing/easting */ - - double *inv_u; /* coefficients for the inverse transformations */ - double *inv_v; /* i.e. northing/easting to latitude/longitude */ - - double *fwd_c; /* coefficients for the complex forward transformations */ - double *inv_c; /* coefficients for the complex inverse transformations */ - - UV *fwd_origin; /* False longitude/latitude */ - UV *inv_origin; /* False easting/northing */ -}; -} // anonymous namespace - -typedef struct horner HORNER; -static UV horner_func (const HORNER *transformation, PJ_DIRECTION direction, UV position); -static HORNER *horner_alloc (size_t order, int complex_polynomia); -static void horner_free (HORNER *h); - -/* e.g. degree = 2: a + bx + cy + dxx + eyy + fxy, i.e. 6 coefficients */ -#define horner_number_of_coefficients(order) \ - (((order + 1)*(order + 2)/2)) - - -static void horner_free (HORNER *h) { - horner_dealloc (h->inv_v); - horner_dealloc (h->inv_u); - horner_dealloc (h->fwd_v); - horner_dealloc (h->fwd_u); - horner_dealloc (h->fwd_c); - horner_dealloc (h->inv_c); - horner_dealloc (h->fwd_origin); - horner_dealloc (h->inv_origin); - horner_dealloc (h); -} - - -static HORNER *horner_alloc (size_t order, int complex_polynomia) { - /* size_t is unsigned, so we need not check for order > 0 */ - int n = (int)horner_number_of_coefficients(order); - int polynomia_ok = 0; - HORNER *h = static_cast(horner_calloc (1, sizeof (HORNER))); - - if (nullptr==h) - return nullptr; - - if (complex_polynomia) - n = 2*(int)order + 2; - h->order = (int)order; - h->coefs = n; - - if (complex_polynomia) { - h->fwd_c = static_cast(horner_calloc (n, sizeof(double))); - h->inv_c = static_cast(horner_calloc (n, sizeof(double))); - if (h->fwd_c && h->inv_c) - polynomia_ok = 1; - } - else { - h->fwd_u = static_cast(horner_calloc (n, sizeof(double))); - h->fwd_v = static_cast(horner_calloc (n, sizeof(double))); - h->inv_u = static_cast(horner_calloc (n, sizeof(double))); - h->inv_v = static_cast(horner_calloc (n, sizeof(double))); - if (h->fwd_u && h->fwd_v && h->inv_u && h->inv_v) - polynomia_ok = 1; - } - - h->fwd_origin = static_cast(horner_calloc (1, sizeof(UV))); - h->inv_origin = static_cast(horner_calloc (1, sizeof(UV))); - - if (polynomia_ok && h->fwd_origin && h->inv_origin) - return h; - - /* safe, since all pointers are null-initialized (by calloc) */ - horner_free (h); - return nullptr; -} - - - - -/**********************************************************************/ -static UV horner_func (const HORNER *transformation, PJ_DIRECTION direction, UV position) { -/*********************************************************************** - -A reimplementation of the classic Engsager/Poder 2D Horner polynomial -evaluation engine "gen_pol". - -This version omits the inimitable Poder "dual autocheck"-machinery, -which here is intended to be implemented at a higher level of the -library: We separate the polynomial evaluation from the quality -control (which, given the limited MTBF for "computing machinery", -typical when Knud Poder invented the dual autocheck method, -was not defensible at that time). - -Another difference from the original version is that we return the -result on the stack, rather than accepting pointers to result variables -as input. This results in code that is easy to read: - - projected = horner (s34j, 1, geographic); - geographic = horner (s34j, -1, projected ); - -and experiments have shown that on contemporary architectures, the time -taken for returning even comparatively large objects on the stack (and -the UV is not that large - typically only 16 bytes) is negligibly -different from passing two pointers (i.e. typically also 16 bytes) the -other way. - -The polynomium has the form: - -P = sum (i = [0 : order]) - sum (j = [0 : order - i]) - pow(par_1, i) * pow(par_2, j) * coef(index(order, i, j)) - -For numerical stability, the summation is carried out backwards, -summing the tiny high order elements first. - -***********************************************************************/ - - /* These variable names follow the Engsager/Poder implementation */ - int sz; /* Number of coefficients per polynomial */ - double *tcx, *tcy; /* Coefficient pointers */ - double range; /* Equivalent to the gen_pol's FLOATLIMIT constant */ - double n, e; - UV uv_error; - uv_error.u = uv_error.v = HUGE_VAL; - - if (nullptr==transformation) - return uv_error; - - /* Check for valid value of direction (-1, 0, 1) */ - switch (direction) { - case PJ_IDENT: /* no-op */ - return position; - case PJ_FWD: /* forward */ - case PJ_INV: /* inverse */ - break; - default: /* invalid */ - errno = EINVAL; - return uv_error; - } - - /* Prepare for double Horner */ - sz = horner_number_of_coefficients(transformation->order); - range = transformation->range; - - - if (direction==PJ_FWD) { /* forward */ - tcx = transformation->fwd_u + sz; - tcy = transformation->fwd_v + sz; - e = position.u - transformation->fwd_origin->u; - n = position.v - transformation->fwd_origin->v; - } else { /* inverse */ - tcx = transformation->inv_u + sz; - tcy = transformation->inv_v + sz; - e = position.u - transformation->inv_origin->u; - n = position.v - transformation->inv_origin->v; - } - - if ((fabs(n) > range) || (fabs(e) > range)) { - errno = EDOM; - return uv_error; - } - - /* The melody of this block is straight out of the great Engsager/Poder songbook */ - else { - int g = transformation->order; - int r = g, c; - double u, v, N, E; - - /* Double Horner's scheme: N = n*Cy*e -> yout, E = e*Cx*n -> xout */ - N = *--tcy; - E = *--tcx; - for (; r > 0; r--) { - u = *--tcy; - v = *--tcx; - for (c = g; c >= r; c--) { - u = n*u + *--tcy; - v = e*v + *--tcx; - } - N = e*N + u; - E = n*E + v; - } - - position.u = E; - position.v = N; - } - - return position; -} - - - - - - - -static PJ_COORD horner_forward_4d (PJ_COORD point, PJ *P) { - point.uv = horner_func ((HORNER *) P->opaque, PJ_FWD, point.uv); - return point; -} - -static PJ_COORD horner_reverse_4d (PJ_COORD point, PJ *P) { - point.uv = horner_func ((HORNER *) P->opaque, PJ_INV, point.uv); - return point; -} - - - - -/**********************************************************************/ -static UV complex_horner (const HORNER *transformation, PJ_DIRECTION direction, UV position) { -/*********************************************************************** - -A reimplementation of a classic Engsager/Poder Horner complex -polynomial evaluation engine. - -***********************************************************************/ - - /* These variable names follow the Engsager/Poder implementation */ - int sz; /* Number of coefficients */ - double *c, *cb; /* Coefficient pointers */ - double range; /* Equivalent to the gen_pol's FLOATLIMIT constant */ - double n, e, w, N, E; - UV uv_error; - uv_error.u = uv_error.v = HUGE_VAL; - - if (nullptr==transformation) - return uv_error; - - /* Check for valid value of direction (-1, 0, 1) */ - switch (direction) { - case PJ_IDENT: /* no-op */ - return position; - case PJ_FWD: /* forward */ - case PJ_INV: /* inverse */ - break; - default: /* invalid */ - errno = EINVAL; - return uv_error; - } - - /* Prepare for double Horner */ - sz = 2*transformation->order + 2; - range = transformation->range; - - if (direction==PJ_FWD) { /* forward */ - cb = transformation->fwd_c; - c = cb + sz; - e = position.u - transformation->fwd_origin->u; - n = position.v - transformation->fwd_origin->v; - if (transformation->uneg) - e = -e; - if (transformation->vneg) - n = -n; - } else { /* inverse */ - cb = transformation->inv_c; - c = cb + sz; - e = position.u - transformation->inv_origin->u; - n = position.v - transformation->inv_origin->v; - if (transformation->uneg) - e = -e; - if (transformation->vneg) - n = -n; - } - - if ((fabs(n) > range) || (fabs(e) > range)) { - errno = EDOM; - return uv_error; - } - - /* Everything's set up properly - now do the actual polynomium evaluation */ - E = *--c; - N = *--c; - while (c > cb) { - w = n*E + e*N + *--c; - N = n*N - e*E + *--c; - E = w; - } - - position.u = E; - position.v = N; - return position; -} - - - -static PJ_COORD complex_horner_forward_4d (PJ_COORD point, PJ *P) { - point.uv = complex_horner ((HORNER *) P->opaque, PJ_FWD, point.uv); - return point; -} - -static PJ_COORD complex_horner_reverse_4d (PJ_COORD point, PJ *P) { - point.uv = complex_horner ((HORNER *) P->opaque, PJ_INV, point.uv); - return point; -} - - -static PJ *horner_freeup (PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - horner_free ((HORNER *) P->opaque); - P->opaque = nullptr; - return pj_default_destructor (P, errlev); -} - - -static int parse_coefs (PJ *P, double *coefs, const char *param, int ncoefs) { - char *buf, *init, *next = nullptr; - int i; - - buf = static_cast(pj_calloc (strlen (param) + 2, sizeof(char))); - if (nullptr==buf) { - proj_log_error (P, "Horner: No memory left"); - return 0; - } - - sprintf (buf, "t%s", param); - if (0==pj_param (P->ctx, P->params, buf).i) { - pj_dealloc (buf); - return 0; - } - sprintf (buf, "s%s", param); - init = pj_param(P->ctx, P->params, buf).s; - pj_dealloc (buf); - - for (i = 0; i < ncoefs; i++) { - if (i > 0) { - if ( next == nullptr || ','!=*next) { - proj_log_error (P, "Horner: Malformed polynomium set %s. need %d coefs", param, ncoefs); - return 0; - } - init = ++next; - } - coefs[i] = pj_strtod (init, &next); - } - return 1; -} - - -/*********************************************************************/ -PJ *PROJECTION(horner) { -/*********************************************************************/ - int degree = 0, n, complex_polynomia = 0; - HORNER *Q; - P->fwd4d = horner_forward_4d; - P->inv4d = horner_reverse_4d; - P->fwd3d = nullptr; - P->inv3d = nullptr; - P->fwd = nullptr; - P->inv = nullptr; - P->left = P->right = PJ_IO_UNITS_PROJECTED; - P->destructor = horner_freeup; - - /* Polynomial degree specified? */ - if (pj_param (P->ctx, P->params, "tdeg").i) { /* degree specified? */ - degree = pj_param(P->ctx, P->params, "ideg").i; - if (degree < 0 || degree > 10000) { - /* What are reasonable minimum and maximums for degree? */ - proj_log_debug (P, "Horner: Degree is unreasonable: %d", degree); - return horner_freeup (P, PJD_ERR_INVALID_ARG); - } - } else { - proj_log_debug (P, "Horner: Must specify polynomial degree, (+deg=n)"); - return horner_freeup (P, PJD_ERR_MISSING_ARGS); - } - - if (pj_param (P->ctx, P->params, "tfwd_c").i || pj_param (P->ctx, P->params, "tinv_c").i) /* complex polynomium? */ - complex_polynomia = 1; - - Q = horner_alloc (degree, complex_polynomia); - if (Q == nullptr) - return horner_freeup (P, ENOMEM); - P->opaque = Q; - - if (complex_polynomia) { - /* Westings and/or southings? */ - Q->uneg = pj_param_exists (P->params, "uneg") ? 1 : 0; - Q->vneg = pj_param_exists (P->params, "vneg") ? 1 : 0; - - n = 2*degree + 2; - if (0==parse_coefs (P, Q->fwd_c, "fwd_c", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); - if (0==parse_coefs (P, Q->inv_c, "inv_c", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); - P->fwd4d = complex_horner_forward_4d; - P->inv4d = complex_horner_reverse_4d; - } - - else { - n = horner_number_of_coefficients (degree); - if (0==parse_coefs (P, Q->fwd_u, "fwd_u", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); - if (0==parse_coefs (P, Q->fwd_v, "fwd_v", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); - if (0==parse_coefs (P, Q->inv_u, "inv_u", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); - if (0==parse_coefs (P, Q->inv_v, "inv_v", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); - } - - if (0==parse_coefs (P, (double *)(Q->fwd_origin), "fwd_origin", 2)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); - if (0==parse_coefs (P, (double *)(Q->inv_origin), "inv_origin", 2)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); - if (0==parse_coefs (P, &Q->range, "range", 1)) - Q->range = 500000; - - return P; -} diff --git a/src/PJ_igh.cpp b/src/PJ_igh.cpp deleted file mode 100644 index e3576861..00000000 --- a/src/PJ_igh.cpp +++ /dev/null @@ -1,227 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(igh, "Interrupted Goode Homolosine") "\n\tPCyl, Sph"; - -C_NAMESPACE PJ *pj_sinu(PJ *), *pj_moll(PJ *); - -/* 40d 44' 11.8" [degrees] */ -/* -static const double d4044118 = (40 + 44/60. + 11.8/3600.) * DEG_TO_RAD; -has been replaced by this define, to eliminate portability issue: -Initializer element not computable at load time -*/ -#define d4044118 ((40 + 44/60. + 11.8/3600.) * DEG_TO_RAD) - -static const double d10 = 10 * DEG_TO_RAD; -static const double d20 = 20 * DEG_TO_RAD; -static const double d30 = 30 * DEG_TO_RAD; -static const double d40 = 40 * DEG_TO_RAD; -static const double d50 = 50 * DEG_TO_RAD; -static const double d60 = 60 * DEG_TO_RAD; -static const double d80 = 80 * DEG_TO_RAD; -static const double d90 = 90 * DEG_TO_RAD; -static const double d100 = 100 * DEG_TO_RAD; -static const double d140 = 140 * DEG_TO_RAD; -static const double d160 = 160 * DEG_TO_RAD; -static const double d180 = 180 * DEG_TO_RAD; - -static const double EPSLN = 1.e-10; /* allow a little 'slack' on zone edge positions */ - -namespace { // anonymous namespace -struct pj_opaque { - struct PJconsts* pj[12]; \ - double dy0; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy; - struct pj_opaque *Q = static_cast(P->opaque); - int z; - - if (lp.phi >= d4044118) { /* 1|2 */ - z = (lp.lam <= -d40 ? 1: 2); - } - else if (lp.phi >= 0) { /* 3|4 */ - z = (lp.lam <= -d40 ? 3: 4); - } - else if (lp.phi >= -d4044118) { /* 5|6|7|8 */ - if (lp.lam <= -d100) z = 5; /* 5 */ - else if (lp.lam <= -d20) z = 6; /* 6 */ - else if (lp.lam <= d80) z = 7; /* 7 */ - else z = 8; /* 8 */ - } - else { /* 9|10|11|12 */ - if (lp.lam <= -d100) z = 9; /* 9 */ - else if (lp.lam <= -d20) z = 10; /* 10 */ - else if (lp.lam <= d80) z = 11; /* 11 */ - else z = 12; /* 12 */ - } - - lp.lam -= Q->pj[z-1]->lam0; - xy = Q->pj[z-1]->fwd(lp, Q->pj[z-1]); - xy.x += Q->pj[z-1]->x0; - xy.y += Q->pj[z-1]->y0; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - const double y90 = Q->dy0 + sqrt(2); /* lt=90 corresponds to y=y0+sqrt(2) */ - - int z = 0; - if (xy.y > y90+EPSLN || xy.y < -y90+EPSLN) /* 0 */ - z = 0; - else if (xy.y >= d4044118) /* 1|2 */ - z = (xy.x <= -d40? 1: 2); - else if (xy.y >= 0) /* 3|4 */ - z = (xy.x <= -d40? 3: 4); - else if (xy.y >= -d4044118) { /* 5|6|7|8 */ - if (xy.x <= -d100) z = 5; /* 5 */ - else if (xy.x <= -d20) z = 6; /* 6 */ - else if (xy.x <= d80) z = 7; /* 7 */ - else z = 8; /* 8 */ - } - else { /* 9|10|11|12 */ - if (xy.x <= -d100) z = 9; /* 9 */ - else if (xy.x <= -d20) z = 10; /* 10 */ - else if (xy.x <= d80) z = 11; /* 11 */ - else z = 12; /* 12 */ - } - - if (z) { - int ok = 0; - - xy.x -= Q->pj[z-1]->x0; - xy.y -= Q->pj[z-1]->y0; - lp = Q->pj[z-1]->inv(xy, Q->pj[z-1]); - lp.lam += Q->pj[z-1]->lam0; - - switch (z) { - case 1: ok = (lp.lam >= -d180-EPSLN && lp.lam <= -d40+EPSLN) || - ((lp.lam >= -d40-EPSLN && lp.lam <= -d10+EPSLN) && - (lp.phi >= d60-EPSLN && lp.phi <= d90+EPSLN)); break; - case 2: ok = (lp.lam >= -d40-EPSLN && lp.lam <= d180+EPSLN) || - ((lp.lam >= -d180-EPSLN && lp.lam <= -d160+EPSLN) && - (lp.phi >= d50-EPSLN && lp.phi <= d90+EPSLN)) || - ((lp.lam >= -d50-EPSLN && lp.lam <= -d40+EPSLN) && - (lp.phi >= d60-EPSLN && lp.phi <= d90+EPSLN)); break; - case 3: ok = (lp.lam >= -d180-EPSLN && lp.lam <= -d40+EPSLN); break; - case 4: ok = (lp.lam >= -d40-EPSLN && lp.lam <= d180+EPSLN); break; - case 5: ok = (lp.lam >= -d180-EPSLN && lp.lam <= -d100+EPSLN); break; - case 6: ok = (lp.lam >= -d100-EPSLN && lp.lam <= -d20+EPSLN); break; - case 7: ok = (lp.lam >= -d20-EPSLN && lp.lam <= d80+EPSLN); break; - case 8: ok = (lp.lam >= d80-EPSLN && lp.lam <= d180+EPSLN); break; - case 9: ok = (lp.lam >= -d180-EPSLN && lp.lam <= -d100+EPSLN); break; - case 10: ok = (lp.lam >= -d100-EPSLN && lp.lam <= -d20+EPSLN); break; - case 11: ok = (lp.lam >= -d20-EPSLN && lp.lam <= d80+EPSLN); break; - case 12: ok = (lp.lam >= d80-EPSLN && lp.lam <= d180+EPSLN); break; - } - z = (!ok? 0: z); /* projectable? */ - } - - if (!z) lp.lam = HUGE_VAL; - if (!z) lp.phi = HUGE_VAL; - - return lp; -} - - -static PJ *destructor (PJ *P, int errlev) { - int i; - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - for (i = 0; i < 12; ++i) { - if (static_cast(P->opaque)->pj[i]) - static_cast(P->opaque)->pj[i]->destructor(static_cast(P->opaque)->pj[i], errlev); - } - - return pj_default_destructor(P, errlev); -} - - - -/* - Zones: - - -180 -40 180 - +--------------+-------------------------+ Zones 1,2,9,10,11 & 12: - |1 |2 | Mollweide projection - | | | - +--------------+-------------------------+ Zones 3,4,5,6,7 & 8: - |3 |4 | Sinusoidal projection - | | | - 0 +-------+------+-+-----------+-----------+ - |5 |6 |7 |8 | - | | | | | - +-------+--------+-----------+-----------+ - |9 |10 |11 |12 | - | | | | | - +-------+--------+-----------+-----------+ - -180 -100 -20 80 180 -*/ - -#define SETUP(n, proj, x_0, y_0, lon_0) \ - if (!(Q->pj[n-1] = pj_##proj(nullptr))) return destructor(P, ENOMEM); \ - if (!(Q->pj[n-1] = pj_##proj(Q->pj[n-1]))) return destructor(P, ENOMEM); \ - Q->pj[n-1]->ctx = P->ctx; \ - Q->pj[n-1]->x0 = x_0; \ - Q->pj[n-1]->y0 = y_0; \ - Q->pj[n-1]->lam0 = lon_0; - - -PJ *PROJECTION(igh) { - XY xy1, xy3; - LP lp = { 0, d4044118 }; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - - /* sinusoidal zones */ - SETUP(3, sinu, -d100, 0, -d100); - SETUP(4, sinu, d30, 0, d30); - SETUP(5, sinu, -d160, 0, -d160); - SETUP(6, sinu, -d60, 0, -d60); - SETUP(7, sinu, d20, 0, d20); - SETUP(8, sinu, d140, 0, d140); - - /* mollweide zones */ - SETUP(1, moll, -d100, 0, -d100); - - /* y0 ? */ - xy1 = Q->pj[0]->fwd(lp, Q->pj[0]); /* zone 1 */ - xy3 = Q->pj[2]->fwd(lp, Q->pj[2]); /* zone 3 */ - /* y0 + xy1.y = xy3.y for lt = 40d44'11.8" */ - Q->dy0 = xy3.y - xy1.y; - - Q->pj[0]->y0 = Q->dy0; - - /* mollweide zones (cont'd) */ - SETUP( 2, moll, d30, Q->dy0, d30); - SETUP( 9, moll, -d160, -Q->dy0, -d160); - SETUP(10, moll, -d60, -Q->dy0, -d60); - SETUP(11, moll, d20, -Q->dy0, d20); - SETUP(12, moll, d140, -Q->dy0, d140); - - P->inv = s_inverse; - P->fwd = s_forward; - P->destructor = destructor; - P->es = 0.; - - return P; -} diff --git a/src/PJ_imw_p.cpp b/src/PJ_imw_p.cpp deleted file mode 100644 index 012c5caa..00000000 --- a/src/PJ_imw_p.cpp +++ /dev/null @@ -1,217 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(imw_p, "International Map of the World Polyconic") - "\n\tMod. Polyconic, Ell\n\tlat_1= and lat_2= [lon_1=]"; - -#define TOL 1e-10 -#define EPS 1e-10 - -namespace { // anonymous namespace -enum Mode { - NONE_IS_ZERO = 0, /* phi_1 and phi_2 != 0 */ - PHI_1_IS_ZERO = 1, /* phi_1 = 0 */ - PHI_2_IS_ZERO = -1 /* phi_2 = 0 */ -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque { - double P, Pp, Q, Qp, R_1, R_2, sphi_1, sphi_2, C2; - double phi_1, phi_2, lam_1; - double *en; - enum Mode mode; -}; -} // anonymous namespace - - -static int phi12(PJ *P, double *del, double *sig) { - struct pj_opaque *Q = static_cast(P->opaque); - int err = 0; - - if (!pj_param(P->ctx, P->params, "tlat_1").i || - !pj_param(P->ctx, P->params, "tlat_2").i) { - err = PJD_ERR_LAT_1_2_UNSPECIFIED; - } else { - Q->phi_1 = pj_param(P->ctx, P->params, "rlat_1").f; - Q->phi_2 = pj_param(P->ctx, P->params, "rlat_2").f; - *del = 0.5 * (Q->phi_2 - Q->phi_1); - *sig = 0.5 * (Q->phi_2 + Q->phi_1); - err = (fabs(*del) < EPS || fabs(*sig) < EPS) ? PJD_ERR_ABS_LAT1_EQ_ABS_LAT2 : 0; - } - return err; -} - - -static XY loc_for(LP lp, PJ *P, double *yc) { - struct pj_opaque *Q = static_cast(P->opaque); - XY xy; - - if (lp.phi == 0.0) { - xy.x = lp.lam; - xy.y = 0.; - } else { - double xa, ya, xb, yb, xc, D, B, m, sp, t, R, C; - - sp = sin(lp.phi); - m = pj_mlfn(lp.phi, sp, cos(lp.phi), Q->en); - xa = Q->Pp + Q->Qp * m; - ya = Q->P + Q->Q * m; - R = 1. / (tan(lp.phi) * sqrt(1. - P->es * sp * sp)); - C = sqrt(R * R - xa * xa); - if (lp.phi < 0.) C = - C; - C += ya - R; - if (Q->mode == PHI_2_IS_ZERO) { - xb = lp.lam; - yb = Q->C2; - } else { - t = lp.lam * Q->sphi_2; - xb = Q->R_2 * sin(t); - yb = Q->C2 + Q->R_2 * (1. - cos(t)); - } - if (Q->mode == PHI_1_IS_ZERO) { - xc = lp.lam; - *yc = 0.; - } else { - t = lp.lam * Q->sphi_1; - xc = Q->R_1 * sin(t); - *yc = Q->R_1 * (1. - cos(t)); - } - D = (xb - xc)/(yb - *yc); - B = xc + D * (C + R - *yc); - xy.x = D * sqrt(R * R * (1 + D * D) - B * B); - if (lp.phi > 0) - xy.x = - xy.x; - xy.x = (B + xy.x) / (1. + D * D); - xy.y = sqrt(R * R - xy.x * xy.x); - if (lp.phi > 0) - xy.y = - xy.y; - xy.y += C + R; - } - return xy; -} - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - double yc; - XY xy = loc_for(lp, P, &yc); - return (xy); -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - XY t; - double yc = 0.0; - int i = 0; - const int N_MAX_ITER = 1000; /* Arbitrarily chosen number... */ - - lp.phi = Q->phi_2; - lp.lam = xy.x / cos(lp.phi); - do { - t = loc_for(lp, P, &yc); - lp.phi = ((lp.phi - Q->phi_1) * (xy.y - yc) / (t.y - yc)) + Q->phi_1; - lp.lam = lp.lam * xy.x / t.x; - i ++; - } while (i < N_MAX_ITER && - (fabs(t.x - xy.x) > TOL || fabs(t.y - xy.y) > TOL)); - - if( i == N_MAX_ITER ) - { - lp.lam = lp.phi = HUGE_VAL; - } - - return lp; -} - - -static void xy(PJ *P, double phi, double *x, double *y, double *sp, double *R) { - double F; - - *sp = sin(phi); - *R = 1./(tan(phi) * sqrt(1. - P->es * *sp * *sp )); - F = static_cast(P->opaque)->lam_1 * *sp; - *y = *R * (1 - cos(F)); - *x = *R * sin(F); -} - - -static PJ *destructor (PJ *P, int errlev) { - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - if( static_cast(P->opaque)->en ) - pj_dealloc (static_cast(P->opaque)->en); - - return pj_default_destructor(P, errlev); -} - - -PJ *PROJECTION(imw_p) { - double del, sig, s, t, x1, x2, T2, y1, m1, m2, y2; - int err; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - if (!(Q->en = pj_enfn(P->es))) return pj_default_destructor (P, ENOMEM); - if( (err = phi12(P, &del, &sig)) != 0) { - return destructor(P, err); - } - if (Q->phi_2 < Q->phi_1) { /* make sure P->phi_1 most southerly */ - del = Q->phi_1; - Q->phi_1 = Q->phi_2; - Q->phi_2 = del; - } - if (pj_param(P->ctx, P->params, "tlon_1").i) - Q->lam_1 = pj_param(P->ctx, P->params, "rlon_1").f; - else { /* use predefined based upon latitude */ - sig = fabs(sig * RAD_TO_DEG); - if (sig <= 60) sig = 2.; - else if (sig <= 76) sig = 4.; - else sig = 8.; - Q->lam_1 = sig * DEG_TO_RAD; - } - Q->mode = NONE_IS_ZERO; - if (Q->phi_1 != 0.0) - xy(P, Q->phi_1, &x1, &y1, &Q->sphi_1, &Q->R_1); - else { - Q->mode = PHI_1_IS_ZERO; - y1 = 0.; - x1 = Q->lam_1; - } - if (Q->phi_2 != 0.0) - xy(P, Q->phi_2, &x2, &T2, &Q->sphi_2, &Q->R_2); - else { - Q->mode = PHI_2_IS_ZERO; - T2 = 0.; - x2 = Q->lam_1; - } - m1 = pj_mlfn(Q->phi_1, Q->sphi_1, cos(Q->phi_1), Q->en); - m2 = pj_mlfn(Q->phi_2, Q->sphi_2, cos(Q->phi_2), Q->en); - t = m2 - m1; - s = x2 - x1; - y2 = sqrt(t * t - s * s) + y1; - Q->C2 = y2 - T2; - t = 1. / t; - Q->P = (m2 * y1 - m1 * y2) * t; - Q->Q = (y2 - y1) * t; - Q->Pp = (m2 * x1 - m1 * x2) * t; - Q->Qp = (x2 - x1) * t; - - P->fwd = e_forward; - P->inv = e_inverse; - P->destructor = destructor; - - return P; -} diff --git a/src/PJ_isea.cpp b/src/PJ_isea.cpp deleted file mode 100644 index 522e6813..00000000 --- a/src/PJ_isea.cpp +++ /dev/null @@ -1,1098 +0,0 @@ -/* - * This code was entirely written by Nathan Wagner - * and is in the public domain. - */ - -#include -#include -#include -#include -#include -#include - -#define PJ_LIB__ -#include "proj_internal.h" -#include "proj_math.h" -#include "proj.h" -#include "projects.h" - -#define DEG36 0.62831853071795864768 -#define DEG72 1.25663706143591729537 -#define DEG90 M_PI_2 -#define DEG108 1.88495559215387594306 -#define DEG120 2.09439510239319549229 -#define DEG144 2.51327412287183459075 -#define DEG180 M_PI - -/* sqrt(5)/M_PI */ -#define ISEA_SCALE 0.8301572857837594396028083 - -/* 26.565051177 degrees */ -#define V_LAT 0.46364760899944494524 - -/* 52.62263186 */ -#define E_RAD 0.91843818702186776133 - -/* 10.81231696 */ -#define F_RAD 0.18871053072122403508 - -/* R tan(g) sin(60) */ -#define TABLE_G 0.6615845383 - -/* H = 0.25 R tan g = */ -#define TABLE_H 0.1909830056 - -/* in radians */ -#define ISEA_STD_LAT 1.01722196792335072101 -#define ISEA_STD_LON .19634954084936207740 - -namespace { // anonymous namespace -struct hex { - int iso; - long x, y, z; -}; -} // anonymous namespace - -/* y *must* be positive down as the xy /iso conversion assumes this */ -static void hex_xy(struct hex *h) { - if (!h->iso) return; - if (h->x >= 0) { - h->y = -h->y - (h->x+1)/2; - } else { - /* need to round toward -inf, not toward zero, so x-1 */ - h->y = -h->y - h->x/2; - } - h->iso = 0; -} - -static void hex_iso(struct hex *h) { - if (h->iso) return; - - if (h->x >= 0) { - h->y = (-h->y - (h->x+1)/2); - } else { - /* need to round toward -inf, not toward zero, so x-1 */ - h->y = (-h->y - (h->x)/2); - } - - h->z = -h->x - h->y; - h->iso = 1; -} - -static void hexbin2(double width, double x, double y, long *i, long *j) { - double z, rx, ry, rz; - double abs_dx, abs_dy, abs_dz; - long ix, iy, iz, s; - struct hex h; - - x = x / cos(30 * M_PI / 180.0); /* rotated X coord */ - y = y - x / 2.0; /* adjustment for rotated X */ - - /* adjust for actual hexwidth */ - x /= width; - y /= width; - - z = -x - y; - - rx = floor(x + 0.5); - ix = lround(rx); - ry = floor(y + 0.5); - iy = lround(ry); - rz = floor(z + 0.5); - iz = lround(rz); - - s = ix + iy + iz; - - if (s) { - abs_dx = fabs(rx - x); - abs_dy = fabs(ry - y); - abs_dz = fabs(rz - z); - - if (abs_dx >= abs_dy && abs_dx >= abs_dz) { - ix -= s; - } else if (abs_dy >= abs_dx && abs_dy >= abs_dz) { - iy -= s; - } else { - iz -= s; - } - } - h.x = ix; - h.y = iy; - h.z = iz; - h.iso = 1; - - hex_xy(&h); - *i = h.x; - *j = h.y; -} - -namespace { // anonymous namespace -enum isea_poly { ISEA_NONE, ISEA_ICOSAHEDRON = 20 }; -enum isea_topology { ISEA_HEXAGON=6, ISEA_TRIANGLE=3, ISEA_DIAMOND=4 }; -enum isea_address_form { ISEA_GEO, ISEA_Q2DI, ISEA_SEQNUM, ISEA_INTERLEAVE, - ISEA_PLANE, ISEA_Q2DD, ISEA_PROJTRI, ISEA_VERTEX2DD, ISEA_HEX -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct isea_dgg { - int polyhedron; /* ignored, icosahedron */ - double o_lat, o_lon, o_az; /* orientation, radians */ - int pole; /* true if standard snyder */ - int topology; /* ignored, hexagon */ - int aperture; /* valid values depend on partitioning method */ - int resolution; - double radius; /* radius of the earth in meters, ignored 1.0 */ - int output; /* an isea_address_form */ - int triangle; /* triangle of last transformed point */ - int quad; /* quad of last transformed point */ - unsigned long serial; -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct isea_pt { - double x, y; -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct isea_geo { - double lon, lat; -}; -} // anonymous namespace - -/* ENDINC */ - -namespace { // anonymous namespace -enum snyder_polyhedron { - SNYDER_POLY_HEXAGON, SNYDER_POLY_PENTAGON, - SNYDER_POLY_TETRAHEDRON, SNYDER_POLY_CUBE, - SNYDER_POLY_OCTAHEDRON, SNYDER_POLY_DODECAHEDRON, - SNYDER_POLY_ICOSAHEDRON -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct snyder_constants { - double g, G, theta; - /* cppcheck-suppress unusedStructMember */ - double ea_w, ea_a, ea_b, g_w, g_a, g_b; -}; -} // anonymous namespace - -/* TODO put these in radians to avoid a later conversion */ -static const struct snyder_constants constants[] = { - {23.80018260, 62.15458023, 60.0, 3.75, 1.033, 0.968, 5.09, 1.195, 1.0}, - {20.07675127, 55.69063953, 54.0, 2.65, 1.030, 0.983, 3.59, 1.141, 1.027}, - {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, 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}, - {37.37736814, 36.0, 30.0, 17.27, 1.163, 0.860, 13.14, 1.584, 1.0}, -}; - -static struct isea_geo vertex[] = { - {0.0, DEG90}, - {DEG180, V_LAT}, - {-DEG108, V_LAT}, - {-DEG36, V_LAT}, - {DEG36, V_LAT}, - {DEG108, V_LAT}, - {-DEG144, -V_LAT}, - {-DEG72, -V_LAT}, - {0.0, -V_LAT}, - {DEG72, -V_LAT}, - {DEG144, -V_LAT}, - {0.0, -DEG90} -}; - -/* TODO make an isea_pt array of the vertices as well */ - -static int tri_v1[] = {0, 0, 0, 0, 0, 0, 6, 7, 8, 9, 10, 2, 3, 4, 5, 1, 11, 11, 11, 11, 11}; - -/* triangle Centers */ -static const struct isea_geo icostriangles[] = { - {0.0, 0.0}, - {-DEG144, E_RAD}, - {-DEG72, E_RAD}, - {0.0, E_RAD}, - {DEG72, E_RAD}, - {DEG144, E_RAD}, - {-DEG144, F_RAD}, - {-DEG72, F_RAD}, - {0.0, F_RAD}, - {DEG72, F_RAD}, - {DEG144, F_RAD}, - {-DEG108, -F_RAD}, - {-DEG36, -F_RAD}, - {DEG36, -F_RAD}, - {DEG108, -F_RAD}, - {DEG180, -F_RAD}, - {-DEG108, -E_RAD}, - {-DEG36, -E_RAD}, - {DEG36, -E_RAD}, - {DEG108, -E_RAD}, - {DEG180, -E_RAD}, -}; - -static double az_adjustment(int triangle) -{ - double adj; - - struct isea_geo v; - struct isea_geo c; - - v = vertex[tri_v1[triangle]]; - c = icostriangles[triangle]; - - /* TODO looks like the adjustment is always either 0 or 180 */ - /* at least if you pick your vertex carefully */ - adj = atan2(cos(v.lat) * sin(v.lon - c.lon), - cos(c.lat) * sin(v.lat) - - sin(c.lat) * cos(v.lat) * cos(v.lon - c.lon)); - return adj; -} - -static struct isea_pt isea_triangle_xy(int triangle) -{ - struct isea_pt c; - const double Rprime = 0.91038328153090290025; - - triangle = (triangle - 1) % 20; - - c.x = TABLE_G * ((triangle % 5) - 2) * 2.0; - if (triangle > 9) { - c.x += TABLE_G; - } - switch (triangle / 5) { - case 0: - c.y = 5.0 * TABLE_H; - break; - case 1: - c.y = TABLE_H; - break; - case 2: - c.y = -TABLE_H; - break; - case 3: - c.y = -5.0 * TABLE_H; - break; - default: - /* should be impossible */ - exit(EXIT_FAILURE); - }; - c.x *= Rprime; - c.y *= Rprime; - - return c; -} - -/* snyder eq 14 */ -static double sph_azimuth(double f_lon, double f_lat, - double t_lon, double t_lat) -{ - double az; - - az = atan2(cos(t_lat) * sin(t_lon - f_lon), - cos(f_lat) * sin(t_lat) - - sin(f_lat) * cos(t_lat) * cos(t_lon - f_lon) - ); - return az; -} - -#ifdef _MSC_VER -#pragma warning( push ) -/* disable unreachable code warning for return 0 */ -#pragma warning( disable : 4702 ) -#endif - -/* coord needs to be in radians */ -static int isea_snyder_forward(struct isea_geo * ll, struct isea_pt * out) -{ - int i; - - /* - * spherical distance from center of polygon face to any of its - * vertexes on the globe - */ - double g; - - /* - * spherical angle between radius vector to center and adjacent edge - * of spherical polygon on the globe - */ - double G; - - /* - * plane angle between radius vector to center and adjacent edge of - * plane polygon - */ - double theta; - - /* additional variables from snyder */ - double q, Rprime, H, Ag, Azprime, Az, dprime, f, rho, - x, y; - - /* variables used to store intermediate results */ - double cot_theta, tan_g, az_offset; - - /* how many multiples of 60 degrees we adjust the azimuth */ - int Az_adjust_multiples; - - struct snyder_constants c; - - /* - * TODO by locality of reference, start by trying the same triangle - * as last time - */ - - /* TODO put these constants in as radians to begin with */ - c = constants[SNYDER_POLY_ICOSAHEDRON]; - theta = PJ_TORAD(c.theta); - g = PJ_TORAD(c.g); - G = PJ_TORAD(c.G); - - for (i = 1; i <= 20; i++) { - double z; - struct isea_geo center; - - center = icostriangles[i]; - - /* step 1 */ - z = acos(sin(center.lat) * sin(ll->lat) - + cos(center.lat) * cos(ll->lat) * cos(ll->lon - center.lon)); - /* not on this triangle */ - if (z > g + 0.000005) { /* TODO DBL_EPSILON */ - continue; - } - - Az = sph_azimuth(center.lon, center.lat, ll->lon, ll->lat); - - /* step 2 */ - - /* This calculates "some" vertex coordinate */ - az_offset = az_adjustment(i); - - Az -= az_offset; - - /* TODO I don't know why we do this. It's not in snyder */ - /* maybe because we should have picked a better vertex */ - if (Az < 0.0) { - Az += 2.0 * M_PI; - } - /* - * adjust Az for the point to fall within the range of 0 to - * 2(90 - theta) or 60 degrees for the hexagon, by - * and therefore 120 degrees for the triangle - * of the icosahedron - * subtracting or adding multiples of 60 degrees to Az and - * recording the amount of adjustment - */ - - Az_adjust_multiples = 0; - while (Az < 0.0) { - Az += DEG120; - Az_adjust_multiples--; - } - while (Az > DEG120 + DBL_EPSILON) { - Az -= DEG120; - Az_adjust_multiples++; - } - - /* step 3 */ - cot_theta = 1.0 / tan(theta); - tan_g = tan(g); /* TODO this is a constant */ - - /* Calculate q from eq 9. */ - /* TODO cot_theta is cot(30) */ - q = atan2(tan_g, cos(Az) + sin(Az) * cot_theta); - - /* not in this triangle */ - if (z > q + 0.000005) { - continue; - } - /* step 4 */ - - /* Apply equations 5-8 and 10-12 in order */ - - /* eq 5 */ - /* Rprime = 0.9449322893 * R; */ - /* R' in the paper is for the truncated */ - Rprime = 0.91038328153090290025; - - /* eq 6 */ - H = acos(sin(Az) * sin(G) * cos(g) - cos(Az) * cos(G)); - - /* eq 7 */ - /* Ag = (Az + G + H - DEG180) * M_PI * R * R / DEG180; */ - Ag = Az + G + H - DEG180; - - /* eq 8 */ - Azprime = atan2(2.0 * Ag, Rprime * Rprime * tan_g * tan_g - 2.0 * Ag * cot_theta); - - /* eq 10 */ - /* cot(theta) = 1.73205080756887729355 */ - dprime = Rprime * tan_g / (cos(Azprime) + sin(Azprime) * cot_theta); - - /* eq 11 */ - f = dprime / (2.0 * Rprime * sin(q / 2.0)); - - /* eq 12 */ - rho = 2.0 * Rprime * f * sin(z / 2.0); - - /* - * add back the same 60 degree multiple adjustment from step - * 2 to Azprime - */ - - Azprime += DEG120 * Az_adjust_multiples; - - /* calculate rectangular coordinates */ - - x = rho * sin(Azprime); - y = rho * cos(Azprime); - - /* - * TODO - * translate coordinates to the origin for the particular - * hexagon on the flattened polyhedral map plot - */ - - out->x = x; - out->y = y; - - return i; - } - - /* - * should be impossible, this implies that the coordinate is not on - * any triangle - */ - - fprintf(stderr, "impossible transform: %f %f is not on any triangle\n", - PJ_TODEG(ll->lon), PJ_TODEG(ll->lat)); - - exit(EXIT_FAILURE); - - /* not reached */ - return 0; /* suppresses a warning */ -} - -#ifdef _MSC_VER -#pragma warning( pop ) -#endif - -/* - * return the new coordinates of any point in original coordinate system. - * Define a point (newNPold) in original coordinate system as the North Pole in - * new coordinate system, and the great circle connect the original and new - * North Pole as the lon0 longitude in new coordinate system, given any point - * in original coordinate system, this function return the new coordinates. - */ - -/* formula from Snyder, Map Projections: A working manual, p31 */ -/* - * old north pole at np in new coordinates - * could be simplified a bit with fewer intermediates - * - * TODO take a result pointer - */ -static struct isea_geo snyder_ctran(struct isea_geo * np, struct isea_geo * pt) -{ - struct isea_geo npt; - double alpha, phi, lambda, lambda0, beta, lambdap, phip; - double sin_phip; - double lp_b; /* lambda prime minus beta */ - double cos_p, sin_a; - - phi = pt->lat; - lambda = pt->lon; - alpha = np->lat; - beta = np->lon; - lambda0 = beta; - - cos_p = cos(phi); - sin_a = sin(alpha); - - /* mpawm 5-7 */ - sin_phip = sin_a * sin(phi) - cos(alpha) * cos_p * cos(lambda - lambda0); - - /* mpawm 5-8b */ - - /* use the two argument form so we end up in the right quadrant */ - lp_b = atan2(cos_p * sin(lambda - lambda0), - (sin_a * cos_p * cos(lambda - lambda0) + cos(alpha) * sin(phi))); - - lambdap = lp_b + beta; - - /* normalize longitude */ - /* TODO can we just do a modulus ? */ - lambdap = fmod(lambdap, 2 * M_PI); - while (lambdap > M_PI) - lambdap -= 2 * M_PI; - while (lambdap < -M_PI) - lambdap += 2 * M_PI; - - phip = asin(sin_phip); - - npt.lat = phip; - npt.lon = lambdap; - - return npt; -} - -static struct isea_geo isea_ctran(struct isea_geo * np, struct isea_geo * pt, - double lon0) -{ - struct isea_geo npt; - - np->lon += M_PI; - npt = snyder_ctran(np, pt); - np->lon -= M_PI; - - npt.lon -= (M_PI - lon0 + np->lon); - - /* - * snyder is down tri 3, isea is along side of tri1 from vertex 0 to - * vertex 1 these are 180 degrees apart - */ - npt.lon += M_PI; - /* normalize longitude */ - npt.lon = fmod(npt.lon, 2 * M_PI); - while (npt.lon > M_PI) - npt.lon -= 2 * M_PI; - while (npt.lon < -M_PI) - npt.lon += 2 * M_PI; - - return npt; -} - -/* fuller's at 5.2454 west, 2.3009 N, adjacent at 7.46658 deg */ - -static int isea_grid_init(struct isea_dgg * g) -{ - if (!g) - return 0; - - g->polyhedron = 20; - g->o_lat = ISEA_STD_LAT; - g->o_lon = ISEA_STD_LON; - g->o_az = 0.0; - g->aperture = 4; - g->resolution = 6; - g->radius = 1.0; - g->topology = 6; - - return 1; -} - -static void isea_orient_isea(struct isea_dgg * g) -{ - if (!g) - return; - g->o_lat = ISEA_STD_LAT; - g->o_lon = ISEA_STD_LON; - g->o_az = 0.0; -} - -static void isea_orient_pole(struct isea_dgg * g) -{ - if (!g) - return; - g->o_lat = M_PI / 2.0; - g->o_lon = 0.0; - g->o_az = 0; -} - -static int isea_transform(struct isea_dgg * g, struct isea_geo * in, - struct isea_pt * out) -{ - struct isea_geo i, pole; - int tri; - - pole.lat = g->o_lat; - pole.lon = g->o_lon; - - i = isea_ctran(&pole, in, g->o_az); - - tri = isea_snyder_forward(&i, out); - out->x *= g->radius; - out->y *= g->radius; - g->triangle = tri; - - return tri; -} - -#define DOWNTRI(tri) (((tri - 1) / 5) % 2 == 1) - -static void isea_rotate(struct isea_pt * pt, double degrees) -{ - double rad; - - double x, y; - - rad = -degrees * M_PI / 180.0; - while (rad >= 2.0 * M_PI) rad -= 2.0 * M_PI; - while (rad <= -2.0 * M_PI) rad += 2.0 * M_PI; - - x = pt->x * cos(rad) + pt->y * sin(rad); - y = -pt->x * sin(rad) + pt->y * cos(rad); - - pt->x = x; - pt->y = y; -} - -static int isea_tri_plane(int tri, struct isea_pt *pt, double radius) { - struct isea_pt tc; /* center of triangle */ - - if (DOWNTRI(tri)) { - isea_rotate(pt, 180.0); - } - tc = isea_triangle_xy(tri); - tc.x *= radius; - tc.y *= radius; - pt->x += tc.x; - pt->y += tc.y; - - return tri; -} - -/* convert projected triangle coords to quad xy coords, return quad number */ -static int isea_ptdd(int tri, struct isea_pt *pt) { - int downtri, quad; - - downtri = (((tri - 1) / 5) % 2 == 1); - quad = ((tri - 1) % 5) + ((tri - 1) / 10) * 5 + 1; - - isea_rotate(pt, downtri ? 240.0 : 60.0); - if (downtri) { - pt->x += 0.5; - /* pt->y += cos(30.0 * M_PI / 180.0); */ - pt->y += .86602540378443864672; - } - return quad; -} - -static int isea_dddi_ap3odd(struct isea_dgg *g, int quad, struct isea_pt *pt, - struct isea_pt *di) -{ - struct isea_pt v; - double hexwidth; - double sidelength; /* in hexes */ - long d, i; - long maxcoord; - struct hex h; - - /* This is the number of hexes from apex to base of a triangle */ - sidelength = (pow(2.0, g->resolution) + 1.0) / 2.0; - - /* apex to base is cos(30deg) */ - hexwidth = cos(M_PI / 6.0) / sidelength; - - /* TODO I think sidelength is always x.5, so - * (int)sidelength * 2 + 1 might be just as good - */ - maxcoord = lround((sidelength * 2.0)); - - v = *pt; - hexbin2(hexwidth, v.x, v.y, &h.x, &h.y); - h.iso = 0; - hex_iso(&h); - - d = h.x - h.z; - i = h.x + h.y + h.y; - - /* - * you want to test for max coords for the next quad in the same - * "row" first to get the case where both are max - */ - if (quad <= 5) { - if (d == 0 && i == maxcoord) { - /* north pole */ - quad = 0; - d = 0; - i = 0; - } else if (i == maxcoord) { - /* upper right in next quad */ - quad += 1; - if (quad == 6) - quad = 1; - i = maxcoord - d; - d = 0; - } else if (d == maxcoord) { - /* lower right in quad to lower right */ - quad += 5; - d = 0; - } - } else if (quad >= 6) { - if (i == 0 && d == maxcoord) { - /* south pole */ - quad = 11; - d = 0; - i = 0; - } else if (d == maxcoord) { - /* lower right in next quad */ - quad += 1; - if (quad == 11) - quad = 6; - d = maxcoord - i; - i = 0; - } else if (i == maxcoord) { - /* upper right in quad to upper right */ - quad = (quad - 4) % 5; - i = 0; - } - } - - di->x = d; - di->y = i; - - g->quad = quad; - return quad; -} - -static int isea_dddi(struct isea_dgg *g, int quad, struct isea_pt *pt, - struct isea_pt *di) { - struct isea_pt v; - double hexwidth; - long sidelength; /* in hexes */ - struct hex h; - - if (g->aperture == 3 && g->resolution % 2 != 0) { - return isea_dddi_ap3odd(g, quad, pt, di); - } - /* todo might want to do this as an iterated loop */ - if (g->aperture >0) { - sidelength = lround(pow(g->aperture, g->resolution / 2.0)); - } else { - sidelength = g->resolution; - } - - hexwidth = 1.0 / sidelength; - - v = *pt; - isea_rotate(&v, -30.0); - hexbin2(hexwidth, v.x, v.y, &h.x, &h.y); - h.iso = 0; - hex_iso(&h); - - /* we may actually be on another quad */ - if (quad <= 5) { - if (h.x == 0 && h.z == -sidelength) { - /* north pole */ - quad = 0; - h.z = 0; - h.y = 0; - h.x = 0; - } else if (h.z == -sidelength) { - quad = quad + 1; - if (quad == 6) - quad = 1; - h.y = sidelength - h.x; - h.z = h.x - sidelength; - h.x = 0; - } else if (h.x == sidelength) { - quad += 5; - h.y = -h.z; - h.x = 0; - } - } else if (quad >= 6) { - if (h.z == 0 && h.x == sidelength) { - /* south pole */ - quad = 11; - h.x = 0; - h.y = 0; - h.z = 0; - } else if (h.x == sidelength) { - quad = quad + 1; - if (quad == 11) - quad = 6; - h.x = h.y + sidelength; - h.y = 0; - h.z = -h.x; - } else if (h.y == -sidelength) { - quad -= 4; - h.y = 0; - h.z = -h.x; - } - } - di->x = h.x; - di->y = -h.z; - - g->quad = quad; - return quad; -} - -static int isea_ptdi(struct isea_dgg *g, int tri, struct isea_pt *pt, - struct isea_pt *di) { - struct isea_pt v; - int quad; - - v = *pt; - quad = isea_ptdd(tri, &v); - quad = isea_dddi(g, quad, &v, di); - return quad; -} - -/* q2di to seqnum */ - -static long isea_disn(struct isea_dgg *g, int quad, struct isea_pt *di) { - long sidelength; - long sn, height; - long hexes; - - if (quad == 0) { - g->serial = 1; - return g->serial; - } - /* hexes in a quad */ - hexes = lround(pow(g->aperture, g->resolution)); - if (quad == 11) { - g->serial = 1 + 10 * hexes + 1; - return g->serial; - } - if (g->aperture == 3 && g->resolution % 2 == 1) { - height = lround(floor((pow(g->aperture, (g->resolution - 1) / 2.0)))); - sn = ((long)di->x) * height; - sn += ((long)di->y) / height; - sn += (quad - 1) * hexes; - sn += 2; - } else { - sidelength = lround((pow(g->aperture, g->resolution / 2.0))); - sn = lround(floor(((quad - 1) * hexes + sidelength * di->x + di->y + 2))); - } - - g->serial = sn; - return sn; -} - -/* TODO just encode the quad in the d or i coordinate - * quad is 0-11, which can be four bits. - * d' = d << 4 + q, d = d' >> 4, q = d' & 0xf - */ -/* convert a q2di to global hex coord */ -static int isea_hex(struct isea_dgg *g, int tri, - struct isea_pt *pt, struct isea_pt *hex) { - struct isea_pt v; -#ifdef FIXME - long sidelength; - long d, i, x, y; -#endif - int quad; - - quad = isea_ptdi(g, tri, pt, &v); - - hex->x = ((int)v.x << 4) + quad; - hex->y = v.y; - - return 1; -#ifdef FIXME - d = lround(floor(v.x)); - i = lround(floor(v.y)); - - /* Aperture 3 odd resolutions */ - if (g->aperture == 3 && g->resolution % 2 != 0) { - long offset = lround((pow(3.0, g->resolution - 1) + 0.5)); - - d += offset * ((g->quad-1) % 5); - i += offset * ((g->quad-1) % 5); - - if (quad == 0) { - d = 0; - i = offset; - } else if (quad == 11) { - d = 2 * offset; - i = 0; - } else if (quad > 5) { - d += offset; - } - - x = (2*d - i) /3; - y = (2*i - d) /3; - - hex->x = x + offset / 3; - hex->y = y + 2 * offset / 3; - return 1; - } - - /* aperture 3 even resolutions and aperture 4 */ - sidelength = lround((pow(g->aperture, g->resolution / 2.0))); - if (g->quad == 0) { - hex->x = 0; - hex->y = sidelength; - } else if (g->quad == 11) { - hex->x = sidelength * 2; - hex->y = 0; - } else { - hex->x = d + sidelength * ((g->quad-1) % 5); - if (g->quad > 5) hex->x += sidelength; - hex->y = i + sidelength * ((g->quad-1) % 5); - } - - return 1; -#endif -} - -static struct isea_pt isea_forward(struct isea_dgg *g, struct isea_geo *in) -{ - int tri; - struct isea_pt out, coord; - - tri = isea_transform(g, in, &out); - - if (g->output == ISEA_PLANE) { - isea_tri_plane(tri, &out, g->radius); - return out; - } - - /* convert to isea standard triangle size */ - out.x = out.x / g->radius * ISEA_SCALE; - out.y = out.y / g->radius * ISEA_SCALE; - out.x += 0.5; - out.y += 2.0 * .14433756729740644112; - - switch (g->output) { - case ISEA_PROJTRI: - /* nothing to do, already in projected triangle */ - break; - case ISEA_VERTEX2DD: - g->quad = isea_ptdd(tri, &out); - break; - case ISEA_Q2DD: - /* Same as above, we just don't print as much */ - g->quad = isea_ptdd(tri, &out); - break; - case ISEA_Q2DI: - g->quad = isea_ptdi(g, tri, &out, &coord); - return coord; - break; - case ISEA_SEQNUM: - isea_ptdi(g, tri, &out, &coord); - /* disn will set g->serial */ - isea_disn(g, g->quad, &coord); - return coord; - break; - case ISEA_HEX: - isea_hex(g, tri, &out, &coord); - return coord; - break; - } - - return out; -} - -/* - * Proj 4 integration code follows - */ - -PROJ_HEAD(isea, "Icosahedral Snyder Equal Area") "\n\tSph"; - -namespace { // anonymous namespace -struct pj_opaque { - struct isea_dgg dgg; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - struct isea_pt out; - struct isea_geo in; - - in.lon = lp.lam; - in.lat = lp.phi; - - out = isea_forward(&Q->dgg, &in); - - xy.x = out.x; - xy.y = out.y; - - return xy; -} - - -PJ *PROJECTION(isea) { - char *opt; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - - P->fwd = s_forward; - isea_grid_init(&Q->dgg); - - Q->dgg.output = ISEA_PLANE; -/* P->dgg.radius = P->a; / * otherwise defaults to 1 */ - /* calling library will scale, I think */ - - opt = pj_param(P->ctx,P->params, "sorient").s; - if (opt) { - if (!strcmp(opt, "isea")) { - isea_orient_isea(&Q->dgg); - } else if (!strcmp(opt, "pole")) { - isea_orient_pole(&Q->dgg); - } else { - return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); - } - } - - if (pj_param(P->ctx,P->params, "tazi").i) { - Q->dgg.o_az = pj_param(P->ctx,P->params, "razi").f; - } - - if (pj_param(P->ctx,P->params, "tlon_0").i) { - Q->dgg.o_lon = pj_param(P->ctx,P->params, "rlon_0").f; - } - - if (pj_param(P->ctx,P->params, "tlat_0").i) { - Q->dgg.o_lat = pj_param(P->ctx,P->params, "rlat_0").f; - } - - if (pj_param(P->ctx,P->params, "taperture").i) { - Q->dgg.aperture = pj_param(P->ctx,P->params, "iaperture").i; - } - - if (pj_param(P->ctx,P->params, "tresolution").i) { - Q->dgg.resolution = pj_param(P->ctx,P->params, "iresolution").i; - } - - opt = pj_param(P->ctx,P->params, "smode").s; - if (opt) { - if (!strcmp(opt, "plane")) { - Q->dgg.output = ISEA_PLANE; - } else if (!strcmp(opt, "di")) { - Q->dgg.output = ISEA_Q2DI; - } - else if (!strcmp(opt, "dd")) { - Q->dgg.output = ISEA_Q2DD; - } - else if (!strcmp(opt, "hex")) { - Q->dgg.output = ISEA_HEX; - } - else { - /* TODO verify error code. Possibly eliminate magic */ - return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); - } - } - - if (pj_param(P->ctx,P->params, "trescale").i) { - Q->dgg.radius = ISEA_SCALE; - } - - if (pj_param(P->ctx,P->params, "tresolution").i) { - Q->dgg.resolution = pj_param(P->ctx,P->params, "iresolution").i; - } else { - Q->dgg.resolution = 4; - } - - if (pj_param(P->ctx,P->params, "taperture").i) { - Q->dgg.aperture = pj_param(P->ctx,P->params, "iaperture").i; - } else { - Q->dgg.aperture = 3; - } - - return P; -} diff --git a/src/PJ_krovak.cpp b/src/PJ_krovak.cpp deleted file mode 100644 index 9ecffb89..00000000 --- a/src/PJ_krovak.cpp +++ /dev/null @@ -1,222 +0,0 @@ - /* - * Project: PROJ - * Purpose: Implementation of the krovak (Krovak) projection. - * Definition: http://www.ihsenergy.com/epsg/guid7.html#1.4.3 - * Author: Thomas Flemming, tf@ttqv.com - * - ****************************************************************************** - * Copyright (c) 2001, Thomas Flemming, tf@ttqv.com - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - ****************************************************************************** - * A description of the (forward) projection is found in: - * - * Bohuslav Veverka, - * - * KROVAK’S PROJECTION AND ITS USE FOR THE - * CZECH REPUBLIC AND THE SLOVAK REPUBLIC, - * - * 50 years of the Research Institute of - * and the Slovak Republic Geodesy, Topography and Cartography - * - * which can be found via the Wayback Machine: - * - * https://web.archive.org/web/20150216143806/https://www.vugtk.cz/odis/sborniky/sb2005/Sbornik_50_let_VUGTK/Part_1-Scientific_Contribution/16-Veverka.pdf - * - * Further info, including the inverse projection, is given by EPSG: - * - * Guidance Note 7 part 2 - * Coordinate Conversions and Transformations including Formulas - * - * http://www.iogp.org/pubs/373-07-2.pdf - * - * Variable names in this file mostly follows what is used in the - * paper by Veverka. - * - * According to EPSG the full Krovak projection method should have - * the following parameters. Within PROJ the azimuth, and pseudo - * standard parallel are hardcoded in the algorithm and can't be - * altered from outside. The others all have defaults to match the - * common usage with Krovak projection. - * - * lat_0 = latitude of centre of the projection - * - * lon_0 = longitude of centre of the projection - * - * ** = azimuth (true) of the centre line passing through the - * centre of the projection - * - * ** = latitude of pseudo standard parallel - * - * k = scale factor on the pseudo standard parallel - * - * x_0 = False Easting of the centre of the projection at the - * apex of the cone - * - * y_0 = False Northing of the centre of the projection at - * the apex of the cone - * - *****************************************************************************/ - -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(krovak, "Krovak") "\n\tPCyl, Ell"; - -#define EPS 1e-15 -#define UQ 1.04216856380474 /* DU(2, 59, 42, 42.69689) */ -#define S0 1.37008346281555 /* Latitude of pseudo standard parallel 78deg 30'00" N */ -/* Not sure at all of the appropriate number for MAX_ITER... */ -#define MAX_ITER 100 - -namespace { // anonymous namespace -struct pj_opaque { - double alpha; - double k; - double n; - double rho0; - double ad; - int czech; -}; -} // anonymous namespace - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - struct pj_opaque *Q = static_cast(P->opaque); - XY xy = {0.0,0.0}; - - double gfi, u, deltav, s, d, eps, rho; - - gfi = pow ( (1. + P->e * sin(lp.phi)) / (1. - P->e * sin(lp.phi)), Q->alpha * P->e / 2.); - - u = 2. * (atan(Q->k * pow( tan(lp.phi / 2. + M_PI_4), Q->alpha) / gfi)-M_PI_4); - deltav = -lp.lam * Q->alpha; - - s = asin(cos(Q->ad) * sin(u) + sin(Q->ad) * cos(u) * cos(deltav)); - d = asin(cos(u) * sin(deltav) / cos(s)); - - eps = Q->n * d; - rho = Q->rho0 * pow(tan(S0 / 2. + M_PI_4) , Q->n) / pow(tan(s / 2. + M_PI_4) , Q->n); - - xy.y = rho * cos(eps); - xy.x = rho * sin(eps); - - xy.y *= Q->czech; - xy.x *= Q->czech; - - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - struct pj_opaque *Q = static_cast(P->opaque); - LP lp = {0.0,0.0}; - - double u, deltav, s, d, eps, rho, fi1, xy0; - int i; - - xy0 = xy.x; - xy.x = xy.y; - xy.y = xy0; - - xy.x *= Q->czech; - xy.y *= Q->czech; - - rho = sqrt(xy.x * xy.x + xy.y * xy.y); - eps = atan2(xy.y, xy.x); - - d = eps / sin(S0); - s = 2. * (atan( pow(Q->rho0 / rho, 1. / Q->n) * tan(S0 / 2. + M_PI_4)) - M_PI_4); - - u = asin(cos(Q->ad) * sin(s) - sin(Q->ad) * cos(s) * cos(d)); - deltav = asin(cos(s) * sin(d) / cos(u)); - - lp.lam = P->lam0 - deltav / Q->alpha; - - /* ITERATION FOR lp.phi */ - fi1 = u; - - for (i = MAX_ITER; i ; --i) { - lp.phi = 2. * ( atan( pow( Q->k, -1. / Q->alpha) * - pow( tan(u / 2. + M_PI_4) , 1. / Q->alpha) * - pow( (1. + P->e * sin(fi1)) / (1. - P->e * sin(fi1)) , P->e / 2.) - ) - M_PI_4); - - if (fabs(fi1 - lp.phi) < EPS) - break; - fi1 = lp.phi; - } - if( i == 0 ) - pj_ctx_set_errno( P->ctx, PJD_ERR_NON_CONVERGENT ); - - lp.lam -= P->lam0; - - return lp; -} - - -PJ *PROJECTION(krovak) { - double u0, n0, g; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - /* we want Bessel as fixed ellipsoid */ - P->a = 6377397.155; - P->e = sqrt(P->es = 0.006674372230614); - - /* if latitude of projection center is not set, use 49d30'N */ - if (!pj_param(P->ctx, P->params, "tlat_0").i) - P->phi0 = 0.863937979737193; - - /* if center long is not set use 42d30'E of Ferro - 17d40' for Ferro */ - /* that will correspond to using longitudes relative to greenwich */ - /* as input and output, instead of lat/long relative to Ferro */ - if (!pj_param(P->ctx, P->params, "tlon_0").i) - P->lam0 = 0.7417649320975901 - 0.308341501185665; - - /* if scale not set default to 0.9999 */ - if (!pj_param(P->ctx, P->params, "tk").i && !pj_param(P->ctx, P->params, "tk_0").i) - P->k0 = 0.9999; - - Q->czech = 1; - if( !pj_param(P->ctx, P->params, "tczech").i ) - Q->czech = -1; - - /* Set up shared parameters between forward and inverse */ - Q->alpha = sqrt(1. + (P->es * pow(cos(P->phi0), 4)) / (1. - P->es)); - u0 = asin(sin(P->phi0) / Q->alpha); - g = pow( (1. + P->e * sin(P->phi0)) / (1. - P->e * sin(P->phi0)) , Q->alpha * P->e / 2. ); - Q->k = tan( u0 / 2. + M_PI_4) / pow (tan(P->phi0 / 2. + M_PI_4) , Q->alpha) * g; - n0 = sqrt(1. - P->es) / (1. - P->es * pow(sin(P->phi0), 2)); - Q->n = sin(S0); - Q->rho0 = P->k0 * n0 / tan(S0); - Q->ad = M_PI_2 - UQ; - - P->inv = e_inverse; - P->fwd = e_forward; - - return P; -} diff --git a/src/PJ_labrd.cpp b/src/PJ_labrd.cpp deleted file mode 100644 index d3930243..00000000 --- a/src/PJ_labrd.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(labrd, "Laborde") "\n\tCyl, Sph\n\tSpecial for Madagascar"; -#define EPS 1.e-10 - -namespace { // anonymous namespace -struct pj_opaque { - double kRg, p0s, A, C, Ca, Cb, Cc, Cd; -}; -} // anonymous namespace - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double V1, V2, ps, sinps, cosps, sinps2, cosps2; - double I1, I2, I3, I4, I5, I6, x2, y2, t; - - V1 = Q->A * log( tan(M_FORTPI + .5 * lp.phi) ); - t = P->e * sin(lp.phi); - V2 = .5 * P->e * Q->A * log ((1. + t)/(1. - t)); - ps = 2. * (atan(exp(V1 - V2 + Q->C)) - M_FORTPI); - I1 = ps - Q->p0s; - cosps = cos(ps); cosps2 = cosps * cosps; - sinps = sin(ps); sinps2 = sinps * sinps; - I4 = Q->A * cosps; - I2 = .5 * Q->A * I4 * sinps; - I3 = I2 * Q->A * Q->A * (5. * cosps2 - sinps2) / 12.; - I6 = I4 * Q->A * Q->A; - I5 = I6 * (cosps2 - sinps2) / 6.; - I6 *= Q->A * Q->A * - (5. * cosps2 * cosps2 + sinps2 * (sinps2 - 18. * cosps2)) / 120.; - t = lp.lam * lp.lam; - xy.x = Q->kRg * lp.lam * (I4 + t * (I5 + t * I6)); - xy.y = Q->kRg * (I1 + t * (I2 + t * I3)); - x2 = xy.x * xy.x; - y2 = xy.y * xy.y; - V1 = 3. * xy.x * y2 - xy.x * x2; - V2 = xy.y * y2 - 3. * x2 * xy.y; - xy.x += Q->Ca * V1 + Q->Cb * V2; - xy.y += Q->Ca * V2 - Q->Cb * V1; - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - /* t = 0.0 optimization is to avoid a false positive cppcheck warning */ - /* (cppcheck git beaf29c15867984aa3c2a15cf15bd7576ccde2b3). Might no */ - /* longer be necessary with later versions. */ - double x2, y2, V1, V2, V3, V4, t = 0.0, t2, ps, pe, tpe, s; - double I7, I8, I9, I10, I11, d, Re; - int i; - - x2 = xy.x * xy.x; - y2 = xy.y * xy.y; - V1 = 3. * xy.x * y2 - xy.x * x2; - V2 = xy.y * y2 - 3. * x2 * xy.y; - V3 = xy.x * (5. * y2 * y2 + x2 * (-10. * y2 + x2 )); - V4 = xy.y * (5. * x2 * x2 + y2 * (-10. * x2 + y2 )); - xy.x += - Q->Ca * V1 - Q->Cb * V2 + Q->Cc * V3 + Q->Cd * V4; - xy.y += Q->Cb * V1 - Q->Ca * V2 - Q->Cd * V3 + Q->Cc * V4; - ps = Q->p0s + xy.y / Q->kRg; - pe = ps + P->phi0 - Q->p0s; - - for ( i = 20; i; --i) { - V1 = Q->A * log(tan(M_FORTPI + .5 * pe)); - tpe = P->e * sin(pe); - V2 = .5 * P->e * Q->A * log((1. + tpe)/(1. - tpe)); - t = ps - 2. * (atan(exp(V1 - V2 + Q->C)) - M_FORTPI); - pe += t; - if (fabs(t) < EPS) - break; - } - - t = P->e * sin(pe); - t = 1. - t * t; - Re = P->one_es / ( t * sqrt(t) ); - t = tan(ps); - t2 = t * t; - s = Q->kRg * Q->kRg; - d = Re * P->k0 * Q->kRg; - I7 = t / (2. * d); - I8 = t * (5. + 3. * t2) / (24. * d * s); - d = cos(ps) * Q->kRg * Q->A; - I9 = 1. / d; - d *= s; - I10 = (1. + 2. * t2) / (6. * d); - I11 = (5. + t2 * (28. + 24. * t2)) / (120. * d * s); - x2 = xy.x * xy.x; - lp.phi = pe + x2 * (-I7 + I8 * x2); - lp.lam = xy.x * (I9 + x2 * (-I10 + x2 * I11)); - return lp; -} - - -PJ *PROJECTION(labrd) { - double Az, sinp, R, N, t; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Az = pj_param(P->ctx, P->params, "razi").f; - sinp = sin(P->phi0); - t = 1. - P->es * sinp * sinp; - N = 1. / sqrt(t); - R = P->one_es * N / t; - Q->kRg = P->k0 * sqrt( N * R ); - Q->p0s = atan( sqrt(R / N) * tan(P->phi0) ); - Q->A = sinp / sin(Q->p0s); - t = P->e * sinp; - Q->C = .5 * P->e * Q->A * log((1. + t)/(1. - t)) + - - Q->A * log( tan(M_FORTPI + .5 * P->phi0)) - + log( tan(M_FORTPI + .5 * Q->p0s)); - t = Az + Az; - Q->Ca = (1. - cos(t)) * ( Q->Cb = 1. / (12. * Q->kRg * Q->kRg) ); - Q->Cb *= sin(t); - Q->Cc = 3. * (Q->Ca * Q->Ca - Q->Cb * Q->Cb); - Q->Cd = 6. * Q->Ca * Q->Cb; - - P->inv = e_inverse; - P->fwd = e_forward; - - return P; -} diff --git a/src/PJ_laea.cpp b/src/PJ_laea.cpp deleted file mode 100644 index dd02c75a..00000000 --- a/src/PJ_laea.cpp +++ /dev/null @@ -1,300 +0,0 @@ -#define PJ_LIB__ -#include -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -PROJ_HEAD(laea, "Lambert Azimuthal Equal Area") "\n\tAzi, Sph&Ell"; - -namespace { // anonymous namespace -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque { - double sinb1; - double cosb1; - double xmf; - double ymf; - double mmf; - double qp; - double dd; - double rq; - double *apa; - enum Mode mode; -}; -} // anonymous namespace - -#define EPS10 1.e-10 - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double coslam, sinlam, sinphi, q, sinb=0.0, cosb=0.0, b=0.0; - - coslam = cos(lp.lam); - sinlam = sin(lp.lam); - sinphi = sin(lp.phi); - q = pj_qsfn(sinphi, P->e, P->one_es); - - if (Q->mode == OBLIQ || Q->mode == EQUIT) { - sinb = q / Q->qp; - cosb = sqrt(1. - sinb * sinb); - } - - switch (Q->mode) { - case OBLIQ: - b = 1. + Q->sinb1 * sinb + Q->cosb1 * cosb * coslam; - break; - case EQUIT: - b = 1. + cosb * coslam; - break; - case N_POLE: - b = M_HALFPI + lp.phi; - q = Q->qp - q; - break; - case S_POLE: - b = lp.phi - M_HALFPI; - q = Q->qp + q; - break; - } - if (fabs(b) < EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - - switch (Q->mode) { - case OBLIQ: - b = sqrt(2. / b); - xy.y = Q->ymf * b * (Q->cosb1 * sinb - Q->sinb1 * cosb * coslam); - goto eqcon; - break; - case EQUIT: - b = sqrt(2. / (1. + cosb * coslam)); - xy.y = b * sinb * Q->ymf; -eqcon: - xy.x = Q->xmf * b * cosb * sinlam; - break; - case N_POLE: - case S_POLE: - if (q >= 0.) { - b = sqrt(q); - xy.x = b * sinlam; - xy.y = coslam * (Q->mode == S_POLE ? b : -b); - } else - xy.x = xy.y = 0.; - break; - } - return xy; -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double coslam, cosphi, sinphi; - - sinphi = sin(lp.phi); - cosphi = cos(lp.phi); - coslam = cos(lp.lam); - switch (Q->mode) { - case EQUIT: - xy.y = 1. + cosphi * coslam; - goto oblcon; - case OBLIQ: - xy.y = 1. + Q->sinb1 * sinphi + Q->cosb1 * cosphi * coslam; -oblcon: - if (xy.y <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - xy.y = sqrt(2. / xy.y); - xy.x = xy.y * cosphi * sin(lp.lam); - xy.y *= Q->mode == EQUIT ? sinphi : - Q->cosb1 * sinphi - Q->sinb1 * cosphi * coslam; - break; - case N_POLE: - coslam = -coslam; - /*-fallthrough*/ - case S_POLE: - if (fabs(lp.phi + P->phi0) < EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - xy.y = M_FORTPI - lp.phi * .5; - xy.y = 2. * (Q->mode == S_POLE ? cos(xy.y) : sin(xy.y)); - xy.x = xy.y * sin(lp.lam); - xy.y *= coslam; - break; - } - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double cCe, sCe, q, rho, ab=0.0; - - switch (Q->mode) { - case EQUIT: - case OBLIQ: - xy.x /= Q->dd; - xy.y *= Q->dd; - rho = hypot(xy.x, xy.y); - if (rho < EPS10) { - lp.lam = 0.; - lp.phi = P->phi0; - return lp; - } - sCe = 2. * asin(.5 * rho / Q->rq); - cCe = cos(sCe); - sCe = sin(sCe); - xy.x *= sCe; - if (Q->mode == OBLIQ) { - ab = cCe * Q->sinb1 + xy.y * sCe * Q->cosb1 / rho; - xy.y = rho * Q->cosb1 * cCe - xy.y * Q->sinb1 * sCe; - } else { - ab = xy.y * sCe / rho; - xy.y = rho * cCe; - } - break; - case N_POLE: - xy.y = -xy.y; - /*-fallthrough*/ - case S_POLE: - q = (xy.x * xy.x + xy.y * xy.y); - if (q == 0.0) { - lp.lam = 0.; - lp.phi = P->phi0; - return (lp); - } - ab = 1. - q / Q->qp; - if (Q->mode == S_POLE) - ab = - ab; - break; - } - lp.lam = atan2(xy.x, xy.y); - lp.phi = pj_authlat(asin(ab), Q->apa); - return lp; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double cosz=0.0, rh, sinz=0.0; - - rh = hypot(xy.x, xy.y); - if ((lp.phi = rh * .5 ) > 1.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - lp.phi = 2. * asin(lp.phi); - if (Q->mode == OBLIQ || Q->mode == EQUIT) { - sinz = sin(lp.phi); - cosz = cos(lp.phi); - } - switch (Q->mode) { - case EQUIT: - lp.phi = fabs(rh) <= EPS10 ? 0. : asin(xy.y * sinz / rh); - xy.x *= sinz; - xy.y = cosz * rh; - break; - case OBLIQ: - lp.phi = fabs(rh) <= EPS10 ? P->phi0 : - asin(cosz * Q->sinb1 + xy.y * sinz * Q->cosb1 / rh); - xy.x *= sinz * Q->cosb1; - xy.y = (cosz - sin(lp.phi) * Q->sinb1) * rh; - break; - case N_POLE: - xy.y = -xy.y; - lp.phi = M_HALFPI - lp.phi; - break; - case S_POLE: - lp.phi -= M_HALFPI; - break; - } - lp.lam = (xy.y == 0. && (Q->mode == EQUIT || Q->mode == OBLIQ)) ? - 0. : atan2(xy.x, xy.y); - return (lp); -} - - -static PJ *destructor (PJ *P, int errlev) { - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->apa); - - return pj_default_destructor(P, errlev); -} - - -PJ *PROJECTION(laea) { - double t; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - t = fabs(P->phi0); - if (fabs(t - M_HALFPI) < EPS10) - Q->mode = P->phi0 < 0. ? S_POLE : N_POLE; - else if (fabs(t) < EPS10) - Q->mode = EQUIT; - else - Q->mode = OBLIQ; - if (P->es != 0.0) { - double sinphi; - - P->e = sqrt(P->es); - Q->qp = pj_qsfn(1., P->e, P->one_es); - Q->mmf = .5 / (1. - P->es); - Q->apa = pj_authset(P->es); - if (nullptr==Q->apa) - return destructor(P, ENOMEM); - switch (Q->mode) { - case N_POLE: - case S_POLE: - Q->dd = 1.; - break; - case EQUIT: - Q->dd = 1. / (Q->rq = sqrt(.5 * Q->qp)); - Q->xmf = 1.; - Q->ymf = .5 * Q->qp; - break; - case OBLIQ: - Q->rq = sqrt(.5 * Q->qp); - sinphi = sin(P->phi0); - Q->sinb1 = pj_qsfn(sinphi, P->e, P->one_es) / Q->qp; - Q->cosb1 = sqrt(1. - Q->sinb1 * Q->sinb1); - Q->dd = cos(P->phi0) / (sqrt(1. - P->es * sinphi * sinphi) * - Q->rq * Q->cosb1); - Q->ymf = (Q->xmf = Q->rq) / Q->dd; - Q->xmf *= Q->dd; - break; - } - P->inv = e_inverse; - P->fwd = e_forward; - } else { - if (Q->mode == OBLIQ) { - Q->sinb1 = sin(P->phi0); - Q->cosb1 = cos(P->phi0); - } - P->inv = s_inverse; - P->fwd = s_forward; - } - - return P; -} - diff --git a/src/PJ_lagrng.cpp b/src/PJ_lagrng.cpp deleted file mode 100644 index 8c0150aa..00000000 --- a/src/PJ_lagrng.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#define PJ_LIB__ -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(lagrng, "Lagrange") "\n\tMisc Sph\n\tW="; - -#define TOL 1e-10 - -namespace { // anonymous namespace -struct pj_opaque { - double a1; - double a2; - double hrw; - double hw; - double rw; - double w; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double v, c; - - if (fabs(fabs(lp.phi) - M_HALFPI) < TOL) { - xy.x = 0; - xy.y = lp.phi < 0 ? -2. : 2.; - } else { - lp.phi = sin(lp.phi); - v = Q->a1 * pow((1. + lp.phi)/(1. - lp.phi), Q->hrw); - lp.lam *= Q->rw; - c = 0.5 * (v + 1./v) + cos(lp.lam); - if (c < TOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - xy.x = 2. * sin(lp.lam) / c; - xy.y = (v - 1./v) / c; - } - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double c, x2, y2p, y2m; - - if (fabs(fabs(xy.y) - 2.) < TOL) { - lp.phi = xy.y < 0 ? -M_HALFPI : M_HALFPI; - lp.lam = 0; - } else { - x2 = xy.x * xy.x; - y2p = 2. + xy.y; - y2m = 2. - xy.y; - c = y2p * y2m - x2; - if (fabs(c) < TOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - lp.phi = 2. * atan(pow((y2p * y2p + x2) / (Q->a2 * (y2m * y2m + x2)), Q->hw)) - M_HALFPI; - lp.lam = Q->w * atan2(4. * xy.x, c); - } - return lp; -} - - -PJ *PROJECTION(lagrng) { - double phi1; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->w = pj_param(P->ctx, P->params, "dW").f; - if (Q->w <= 0) - return pj_default_destructor(P, PJD_ERR_W_OR_M_ZERO_OR_LESS); - Q->hw = 0.5 * Q->w; - Q->rw = 1. / Q->w; - Q->hrw = 0.5 * Q->rw; - phi1 = sin(pj_param(P->ctx, P->params, "rlat_1").f); - if (fabs(fabs(phi1) - 1.) < TOL) - return pj_default_destructor(P, PJD_ERR_LAT_LARGER_THAN_90); - - Q->a1 = pow((1. - phi1)/(1. + phi1), Q->hrw); - Q->a2 = Q->a1 * Q->a1; - - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} - diff --git a/src/PJ_larr.cpp b/src/PJ_larr.cpp deleted file mode 100644 index e4d5d240..00000000 --- a/src/PJ_larr.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(larr, "Larrivee") "\n\tMisc Sph, no inv"; - -#define SIXTH .16666666666666666 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - - xy.x = 0.5 * lp.lam * (1. + sqrt(cos(lp.phi))); - xy.y = lp.phi / (cos(0.5 * lp.phi) * cos(SIXTH * lp.lam)); - return xy; -} - - -PJ *PROJECTION(larr) { - - P->es = 0; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_lask.cpp b/src/PJ_lask.cpp deleted file mode 100644 index 46f23edb..00000000 --- a/src/PJ_lask.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#define PJ_LIB__ -#include "projects.h" - -PROJ_HEAD(lask, "Laskowski") "\n\tMisc Sph, no inv"; - -#define a10 0.975534 -#define a12 -0.119161 -#define a32 -0.0143059 -#define a14 -0.0547009 -#define b01 1.00384 -#define b21 0.0802894 -#define b03 0.0998909 -#define b41 0.000199025 -#define b23 -0.0285500 -#define b05 -0.0491032 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double l2, p2; - (void) P; - - l2 = lp.lam * lp.lam; - p2 = lp.phi * lp.phi; - xy.x = lp.lam * (a10 + p2 * (a12 + l2 * a32 + p2 * a14)); - xy.y = lp.phi * (b01 + l2 * (b21 + p2 * b23 + l2 * b41) + - p2 * (b03 + p2 * b05)); - return xy; -} - - -PJ *PROJECTION(lask) { - - P->fwd = s_forward; - P->es = 0.; - - return P; -} - diff --git a/src/PJ_latlong.cpp b/src/PJ_latlong.cpp deleted file mode 100644 index 1331d59a..00000000 --- a/src/PJ_latlong.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Stub projection implementation for lat/long coordinates. We - * don't actually change the coordinates, but we want proj=latlong - * to act sort of like a projection. - * Author: Frank Warmerdam, warmerdam@pobox.com - * - ****************************************************************************** - * Copyright (c) 2000, Frank Warmerdam - * - * 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. - *****************************************************************************/ - -/* very loosely based upon DMA code by Bradford W. Drew */ -#define PJ_LIB__ -#include "proj_internal.h" -#include "projects.h" - -PROJ_HEAD(lonlat, "Lat/long (Geodetic)") "\n\t"; -PROJ_HEAD(latlon, "Lat/long (Geodetic alias)") "\n\t"; -PROJ_HEAD(latlong, "Lat/long (Geodetic alias)") "\n\t"; -PROJ_HEAD(longlat, "Lat/long (Geodetic alias)") "\n\t"; - - - static XY latlong_forward(LP lp, PJ *P) { - XY xy = {0.0,0.0}; - (void) P; - xy.x = lp.lam; - xy.y = lp.phi; - return xy; -} - - -static LP latlong_inverse(XY xy, PJ *P) { - LP lp = {0.0,0.0}; - (void) P; - lp.phi = xy.y; - lp.lam = xy.x; - return lp; -} - - - static XYZ latlong_forward_3d (LPZ lpz, PJ *P) { - XYZ xyz = {0,0,0}; - (void) P; - xyz.x = lpz.lam; - xyz.y = lpz.phi; - xyz.z = lpz.z; - return xyz; -} - - -static LPZ latlong_inverse_3d (XYZ xyz, PJ *P) { - LPZ lpz = {0,0,0}; - (void) P; - lpz.lam = xyz.x; - lpz.phi = xyz.y; - lpz.z = xyz.z; - return lpz; -} - -static PJ_COORD latlong_forward_4d (PJ_COORD obs, PJ *P) { - (void) P; - return obs; -} - - -static PJ_COORD latlong_inverse_4d (PJ_COORD obs, PJ *P) { - (void) P; - return obs; -} - - - -static PJ *latlong_setup (PJ *P) { - P->is_latlong = 1; - P->x0 = 0; - P->y0 = 0; - P->inv = latlong_inverse; - P->fwd = latlong_forward; - P->inv3d = latlong_inverse_3d; - P->fwd3d = latlong_forward_3d; - P->inv4d = latlong_inverse_4d; - P->fwd4d = latlong_forward_4d; - P->left = PJ_IO_UNITS_ANGULAR; - P->right = PJ_IO_UNITS_ANGULAR; - return P; -} - -PJ *PROJECTION(latlong) { - return latlong_setup (P); -} - - -PJ *PROJECTION(longlat) { - return latlong_setup (P); -} - - -PJ *PROJECTION(latlon) { - return latlong_setup (P); -} - - -PJ *PROJECTION(lonlat) { - return latlong_setup (P); -} - diff --git a/src/PJ_lcc.cpp b/src/PJ_lcc.cpp deleted file mode 100644 index 7d6e3f57..00000000 --- a/src/PJ_lcc.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#define PJ_LIB__ -#include -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -PROJ_HEAD(lcc, "Lambert Conformal Conic") - "\n\tConic, Sph&Ell\n\tlat_1= and lat_2= or lat_0, k_0="; - -#define EPS10 1.e-10 - -namespace { // anonymous namespace -struct pj_opaque { - double phi1; - double phi2; - double n; - double rho0; - double c; -}; -} // anonymous namespace - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0., 0.}; - struct pj_opaque *Q = static_cast(P->opaque); - double rho; - - if (fabs(fabs(lp.phi) - M_HALFPI) < EPS10) { - if ((lp.phi * Q->n) <= 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - rho = 0.; - } else { - rho = Q->c * (P->es != 0. ? - pow(pj_tsfn(lp.phi, sin(lp.phi), P->e), Q->n) : - pow(tan(M_FORTPI + .5 * lp.phi), -Q->n)); - } - lp.lam *= Q->n; - xy.x = P->k0 * (rho * sin(lp.lam)); - xy.y = P->k0 * (Q->rho0 - rho * cos(lp.lam)); - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0., 0.}; - struct pj_opaque *Q = static_cast(P->opaque); - double rho; - - xy.x /= P->k0; - xy.y /= P->k0; - - xy.y = Q->rho0 - xy.y; - rho = hypot(xy.x, xy.y); - if (rho != 0.) { - if (Q->n < 0.) { - rho = -rho; - xy.x = -xy.x; - xy.y = -xy.y; - } - if (P->es != 0.) { - lp.phi = pj_phi2(P->ctx, pow(rho / Q->c, 1./Q->n), P->e); - if (lp.phi == HUGE_VAL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - - } else - lp.phi = 2. * atan(pow(Q->c / rho, 1./Q->n)) - M_HALFPI; - lp.lam = atan2(xy.x, xy.y) / Q->n; - } else { - lp.lam = 0.; - lp.phi = Q->n > 0. ? M_HALFPI : -M_HALFPI; - } - return lp; -} - - -PJ *PROJECTION(lcc) { - double cosphi, sinphi; - int secant; - struct pj_opaque *Q = static_cast(pj_calloc(1, sizeof (struct pj_opaque))); - - if (nullptr == Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; - if (pj_param(P->ctx, P->params, "tlat_2").i) - Q->phi2 = pj_param(P->ctx, P->params, "rlat_2").f; - else { - Q->phi2 = Q->phi1; - if (!pj_param(P->ctx, P->params, "tlat_0").i) - P->phi0 = Q->phi1; - } - if (fabs(Q->phi1 + Q->phi2) < EPS10) - return pj_default_destructor(P, PJD_ERR_CONIC_LAT_EQUAL); - - Q->n = sinphi = sin(Q->phi1); - cosphi = cos(Q->phi1); - secant = fabs(Q->phi1 - Q->phi2) >= EPS10; - if (P->es != 0.) { - double ml1, m1; - - m1 = pj_msfn(sinphi, cosphi, P->es); - ml1 = pj_tsfn(Q->phi1, sinphi, P->e); - if (secant) { /* secant cone */ - sinphi = sin(Q->phi2); - Q->n = log(m1 / pj_msfn(sinphi, cos(Q->phi2), P->es)); - Q->n /= log(ml1 / pj_tsfn(Q->phi2, sinphi, P->e)); - } - Q->c = (Q->rho0 = m1 * pow(ml1, -Q->n) / Q->n); - Q->rho0 *= (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) ? 0. : - pow(pj_tsfn(P->phi0, sin(P->phi0), P->e), Q->n); - } else { - if (secant) - Q->n = log(cosphi / cos(Q->phi2)) / - log(tan(M_FORTPI + .5 * Q->phi2) / - tan(M_FORTPI + .5 * Q->phi1)); - Q->c = cosphi * pow(tan(M_FORTPI + .5 * Q->phi1), Q->n) / Q->n; - Q->rho0 = (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) ? 0. : - Q->c * pow(tan(M_FORTPI + .5 * P->phi0), -Q->n); - } - - P->inv = e_inverse; - P->fwd = e_forward; - - return P; -} diff --git a/src/PJ_lcca.cpp b/src/PJ_lcca.cpp deleted file mode 100644 index 70b5dff9..00000000 --- a/src/PJ_lcca.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/***************************************************************************** - - Lambert Conformal Conic Alternative - ----------------------------------- - - This is Gerald Evenden's 2003 implementation of an alternative - "almost" LCC, which has been in use historically, but which - should NOT be used for new projects - i.e: use this implementation - if you need interoperability with old data represented in this - projection, but not in any other case. - - The code was originally discussed on the PROJ.4 mailing list in - a thread archived over at - - http://lists.maptools.org/pipermail/proj/2003-March/000644.html - - It was discussed again in the thread starting at - - http://lists.maptools.org/pipermail/proj/2017-October/007828.html - and continuing at - http://lists.maptools.org/pipermail/proj/2017-November/007831.html - - which prompted Clifford J. Mugnier to add these clarifying notes: - - The French Army Truncated Cubic Lambert (partially conformal) Conic - projection is the Legal system for the projection in France between - the late 1800s and 1948 when the French Legislature changed the law - to recognize the fully conformal version. - - It was (might still be in one or two North African prior French - Colonies) used in North Africa in Algeria, Tunisia, & Morocco, as - well as in Syria during the Levant. - - Last time I have seen it used was about 30+ years ago in - Algeria when it was used to define Lease Block boundaries for - Petroleum Exploration & Production. - - (signed) - - Clifford J. Mugnier, c.p., c.m.s. - Chief of Geodesy - LSU Center for GeoInformatics - Dept. of Civil Engineering - LOUISIANA STATE UNIVERSITY - -*****************************************************************************/ - -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(lcca, "Lambert Conformal Conic Alternative") - "\n\tConic, Sph&Ell\n\tlat_0="; - -#define MAX_ITER 10 -#define DEL_TOL 1e-12 - -namespace { // anonymous namespace -struct pj_opaque { - double *en; - double r0, l, M0; - double C; -}; -} // anonymous namespace - - -static double fS(double S, double C) { /* func to compute dr */ - - return S * ( 1. + S * S * C); -} - - -static double fSp(double S, double C) { /* deriv of fs */ - - return 1. + 3.* S * S * C; -} - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double S, r, dr; - - S = pj_mlfn(lp.phi, sin(lp.phi), cos(lp.phi), Q->en) - Q->M0; - dr = fS(S, Q->C); - r = Q->r0 - dr; - xy.x = P->k0 * (r * sin( lp.lam *= Q->l ) ); - xy.y = P->k0 * (Q->r0 - r * cos(lp.lam) ); - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double theta, dr, S, dif; - int i; - - xy.x /= P->k0; - xy.y /= P->k0; - theta = atan2(xy.x , Q->r0 - xy.y); - dr = xy.y - xy.x * tan(0.5 * theta); - lp.lam = theta / Q->l; - S = dr; - for (i = MAX_ITER; i ; --i) { - S -= (dif = (fS(S, Q->C) - dr) / fSp(S, Q->C)); - if (fabs(dif) < DEL_TOL) break; - } - if (!i) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - lp.phi = pj_inv_mlfn(P->ctx, S + Q->M0, P->es, Q->en); - - return lp; -} - - -static PJ *destructor (PJ *P, int errlev) { - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->en); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(lcca) { - double s2p0, N0, R0, tan0; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - (Q->en = pj_enfn(P->es)); - if (!Q->en) - return pj_default_destructor (P, ENOMEM); - - if (P->phi0 == 0.) { - return destructor(P, PJD_ERR_LAT_0_IS_ZERO); - } - Q->l = sin(P->phi0); - Q->M0 = pj_mlfn(P->phi0, Q->l, cos(P->phi0), Q->en); - s2p0 = Q->l * Q->l; - R0 = 1. / (1. - P->es * s2p0); - N0 = sqrt(R0); - R0 *= P->one_es * N0; - tan0 = tan(P->phi0); - Q->r0 = N0 / tan0; - Q->C = 1. / (6. * R0 * N0); - - P->inv = e_inverse; - P->fwd = e_forward; - P->destructor = destructor; - - return P; -} diff --git a/src/PJ_loxim.cpp b/src/PJ_loxim.cpp deleted file mode 100644 index f68e844a..00000000 --- a/src/PJ_loxim.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(loxim, "Loximuthal") "\n\tPCyl Sph"; - -#define EPS 1e-8 - -namespace { // anonymous namespace -struct pj_opaque { - double phi1; - double cosphi1; - double tanphi1; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - xy.y = lp.phi - Q->phi1; - if (fabs(xy.y) < EPS) - xy.x = lp.lam * Q->cosphi1; - else { - xy.x = M_FORTPI + 0.5 * lp.phi; - if (fabs(xy.x) < EPS || fabs(fabs(xy.x) - M_HALFPI) < EPS) - xy.x = 0.; - else - xy.x = lp.lam * xy.y / log( tan(xy.x) / Q->tanphi1 ); - } - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - lp.phi = xy.y + Q->phi1; - if (fabs(xy.y) < EPS) { - lp.lam = xy.x / Q->cosphi1; - } else { - lp.lam = M_FORTPI + 0.5 * lp.phi; - if (fabs(lp.lam) < EPS || fabs(fabs(lp.lam) - M_HALFPI) < EPS) - lp.lam = 0.; - else - lp.lam = xy.x * log( tan(lp.lam) / Q->tanphi1 ) / xy.y ; - } - return lp; -} - - -PJ *PROJECTION(loxim) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; - Q->cosphi1 = cos(Q->phi1); - if (Q->cosphi1 < EPS) - return pj_default_destructor(P, PJD_ERR_LAT_LARGER_THAN_90); - - - Q->tanphi1 = tan(M_FORTPI + 0.5 * Q->phi1); - - P->inv = s_inverse; - P->fwd = s_forward; - P->es = 0.; - - return P; -} diff --git a/src/PJ_lsat.cpp b/src/PJ_lsat.cpp deleted file mode 100644 index a0eca1bd..00000000 --- a/src/PJ_lsat.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* based upon Snyder and Linck, USGS-NMD */ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(lsat, "Space oblique for LANDSAT") - "\n\tCyl, Sph&Ell\n\tlsat= path="; - -#define TOL 1e-7 - -namespace { // anonymous namespace -struct pj_opaque { - double a2, a4, b, c1, c3; - double q, t, u, w, p22, sa, ca, xj, rlm, rlm2; -}; -} // anonymous namespace - -static void seraz0(double lam, double mult, PJ *P) { - struct pj_opaque *Q = static_cast(P->opaque); - double sdsq, h, s, fc, sd, sq, d__1 = 0; - - lam *= DEG_TO_RAD; - sd = sin(lam); - sdsq = sd * sd; - s = Q->p22 * Q->sa * cos(lam) * sqrt((1. + Q->t * sdsq) - / ((1. + Q->w * sdsq) * (1. + Q->q * sdsq))); - - d__1 = 1. + Q->q * sdsq; - h = sqrt((1. + Q->q * sdsq) / (1. + Q->w * sdsq)) * ((1. + Q->w * sdsq) - / (d__1 * d__1) - Q->p22 * Q->ca); - - sq = sqrt(Q->xj * Q->xj + s * s); - fc = mult * (h * Q->xj - s * s) / sq; - Q->b += fc; - Q->a2 += fc * cos(lam + lam); - Q->a4 += fc * cos(lam * 4.); - fc = mult * s * (h + Q->xj) / sq; - Q->c1 += fc * cos(lam); - Q->c3 += fc * cos(lam * 3.); -} - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - int l, nn; - double lamt = 0.0, xlam, sdsq, c, d, s, lamdp = 0.0, phidp, lampp, tanph; - double lamtp, cl, sd, sp, sav, tanphi; - - if (lp.phi > M_HALFPI) - lp.phi = M_HALFPI; - else if (lp.phi < -M_HALFPI) - lp.phi = -M_HALFPI; - - if (lp.phi >= 0. ) - lampp = M_HALFPI; - else - lampp = M_PI_HALFPI; - tanphi = tan(lp.phi); - for (nn = 0;;) { - double fac; - sav = lampp; - lamtp = lp.lam + Q->p22 * lampp; - cl = cos(lamtp); - if( cl < 0 ) - fac = lampp + sin(lampp) * M_HALFPI; - else - fac = lampp - sin(lampp) * M_HALFPI; - for (l = 50; l >= 0; --l) { - lamt = lp.lam + Q->p22 * sav; - c = cos(lamt); - if (fabs(c) < TOL) - lamt -= TOL; - xlam = (P->one_es * tanphi * Q->sa + sin(lamt) * Q->ca) / c; - lamdp = atan(xlam) + fac; - if (fabs(fabs(sav) - fabs(lamdp)) < TOL) - break; - sav = lamdp; - } - if (!l || ++nn >= 3 || (lamdp > Q->rlm && lamdp < Q->rlm2)) - break; - if (lamdp <= Q->rlm) - lampp = M_TWOPI_HALFPI; - else if (lamdp >= Q->rlm2) - lampp = M_HALFPI; - } - if (l) { - sp = sin(lp.phi); - phidp = aasin(P->ctx,(P->one_es * Q->ca * sp - Q->sa * cos(lp.phi) * - sin(lamt)) / sqrt(1. - P->es * sp * sp)); - tanph = log(tan(M_FORTPI + .5 * phidp)); - sd = sin(lamdp); - sdsq = sd * sd; - s = Q->p22 * Q->sa * cos(lamdp) * sqrt((1. + Q->t * sdsq) - / ((1. + Q->w * sdsq) * (1. + Q->q * sdsq))); - d = sqrt(Q->xj * Q->xj + s * s); - xy.x = Q->b * lamdp + Q->a2 * sin(2. * lamdp) + Q->a4 * - sin(lamdp * 4.) - tanph * s / d; - xy.y = Q->c1 * sd + Q->c3 * sin(lamdp * 3.) + tanph * Q->xj / d; - } else - xy.x = xy.y = HUGE_VAL; - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - int nn; - double lamt, sdsq, s, lamdp, phidp, sppsq, dd, sd, sl, fac, scl, sav, spp; - - lamdp = xy.x / Q->b; - nn = 50; - do { - sav = lamdp; - sd = sin(lamdp); - sdsq = sd * sd; - s = Q->p22 * Q->sa * cos(lamdp) * sqrt((1. + Q->t * sdsq) - / ((1. + Q->w * sdsq) * (1. + Q->q * sdsq))); - lamdp = xy.x + xy.y * s / Q->xj - Q->a2 * sin( - 2. * lamdp) - Q->a4 * sin(lamdp * 4.) - s / Q->xj * ( - Q->c1 * sin(lamdp) + Q->c3 * sin(lamdp * 3.)); - lamdp /= Q->b; - } while (fabs(lamdp - sav) >= TOL && --nn); - sl = sin(lamdp); - fac = exp(sqrt(1. + s * s / Q->xj / Q->xj) * (xy.y - - Q->c1 * sl - Q->c3 * sin(lamdp * 3.))); - phidp = 2. * (atan(fac) - M_FORTPI); - dd = sl * sl; - if (fabs(cos(lamdp)) < TOL) - lamdp -= TOL; - spp = sin(phidp); - sppsq = spp * spp; - lamt = atan(((1. - sppsq * P->rone_es) * tan(lamdp) * - Q->ca - spp * Q->sa * sqrt((1. + Q->q * dd) * ( - 1. - sppsq) - sppsq * Q->u) / cos(lamdp)) / (1. - sppsq - * (1. + Q->u))); - sl = lamt >= 0. ? 1. : -1.; - scl = cos(lamdp) >= 0. ? 1. : -1; - lamt -= M_HALFPI * (1. - scl) * sl; - lp.lam = lamt - Q->p22 * lamdp; - if (fabs(Q->sa) < TOL) - lp.phi = aasin(P->ctx,spp / sqrt(P->one_es * P->one_es + P->es * sppsq)); - else - lp.phi = atan((tan(lamdp) * cos(lamt) - Q->ca * sin(lamt)) / - (P->one_es * Q->sa)); - return lp; -} - - -PJ *PROJECTION(lsat) { - int land, path; - double lam, alf, esc, ess; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - land = pj_param(P->ctx, P->params, "ilsat").i; - if (land <= 0 || land > 5) - return pj_default_destructor(P, PJD_ERR_LSAT_NOT_IN_RANGE); - - path = pj_param(P->ctx, P->params, "ipath").i; - if (path <= 0 || path > (land <= 3 ? 251 : 233)) - return pj_default_destructor(P, PJD_ERR_PATH_NOT_IN_RANGE); - - if (land <= 3) { - P->lam0 = DEG_TO_RAD * 128.87 - M_TWOPI / 251. * path; - Q->p22 = 103.2669323; - alf = DEG_TO_RAD * 99.092; - } else { - P->lam0 = DEG_TO_RAD * 129.3 - M_TWOPI / 233. * path; - Q->p22 = 98.8841202; - alf = DEG_TO_RAD * 98.2; - } - Q->p22 /= 1440.; - Q->sa = sin(alf); - Q->ca = cos(alf); - if (fabs(Q->ca) < 1e-9) - Q->ca = 1e-9; - esc = P->es * Q->ca * Q->ca; - ess = P->es * Q->sa * Q->sa; - Q->w = (1. - esc) * P->rone_es; - Q->w = Q->w * Q->w - 1.; - Q->q = ess * P->rone_es; - Q->t = ess * (2. - P->es) * P->rone_es * P->rone_es; - Q->u = esc * P->rone_es; - Q->xj = P->one_es * P->one_es * P->one_es; - Q->rlm = M_PI * (1. / 248. + .5161290322580645); - Q->rlm2 = Q->rlm + M_TWOPI; - Q->a2 = Q->a4 = Q->b = Q->c1 = Q->c3 = 0.; - seraz0(0., 1., P); - for (lam = 9.; lam <= 81.0001; lam += 18.) - seraz0(lam, 4., P); - for (lam = 18; lam <= 72.0001; lam += 18.) - seraz0(lam, 2., P); - seraz0(90., 1., P); - Q->a2 /= 30.; - Q->a4 /= 60.; - Q->b /= 30.; - Q->c1 /= 15.; - Q->c3 /= 45.; - - P->inv = e_inverse; - P->fwd = e_forward; - - return P; -} diff --git a/src/PJ_mbt_fps.cpp b/src/PJ_mbt_fps.cpp deleted file mode 100644 index 66ed9458..00000000 --- a/src/PJ_mbt_fps.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(mbt_fps, "McBryde-Thomas Flat-Pole Sine (No. 2)") "\n\tCyl, Sph"; - -#define MAX_ITER 10 -#define LOOP_TOL 1e-7 -#define C1 0.45503 -#define C2 1.36509 -#define C3 1.41546 -#define C_x 0.22248 -#define C_y 1.44492 -#define C1_2 0.33333333333333333333333333 - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double k, V, t; - int i; - (void) P; - - k = C3 * sin(lp.phi); - for (i = MAX_ITER; i ; --i) { - t = lp.phi / C2; - lp.phi -= V = (C1 * sin(t) + sin(lp.phi) - k) / - (C1_2 * cos(t) + cos(lp.phi)); - if (fabs(V) < LOOP_TOL) - break; - } - t = lp.phi / C2; - xy.x = C_x * lp.lam * (1. + 3. * cos(lp.phi)/cos(t) ); - xy.y = C_y * sin(t); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double t; - - lp.phi = C2 * (t = aasin(P->ctx,xy.y / C_y)); - lp.lam = xy.x / (C_x * (1. + 3. * cos(lp.phi)/cos(t))); - lp.phi = aasin(P->ctx,(C1 * sin(t) + sin(lp.phi)) / C3); - return (lp); -} - - -PJ *PROJECTION(mbt_fps) { - - P->es = 0; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_mbtfpp.cpp b/src/PJ_mbtfpp.cpp deleted file mode 100644 index 276a43eb..00000000 --- a/src/PJ_mbtfpp.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(mbtfpp, "McBride-Thomas Flat-Polar Parabolic") "\n\tCyl, Sph"; - -#define CS .95257934441568037152 -#define FXC .92582009977255146156 -#define FYC 3.40168025708304504493 -#define C23 .66666666666666666666 -#define C13 .33333333333333333333 -#define ONEEPS 1.0000001 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - - lp.phi = asin(CS * sin(lp.phi)); - xy.x = FXC * lp.lam * (2. * cos(C23 * lp.phi) - 1.); - xy.y = FYC * sin(C13 * lp.phi); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - - lp.phi = xy.y / FYC; - if (fabs(lp.phi) >= 1.) { - if (fabs(lp.phi) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } else { - lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI; - } - } else - lp.phi = asin(lp.phi); - - lp.lam = xy.x / ( FXC * (2. * cos(C23 * (lp.phi *= 3.)) - 1.) ); - if (fabs(lp.phi = sin(lp.phi) / CS) >= 1.) { - if (fabs(lp.phi) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } else { - lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI; - } - } else - lp.phi = asin(lp.phi); - - return lp; -} - - -PJ *PROJECTION(mbtfpp) { - - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_mbtfpq.cpp b/src/PJ_mbtfpq.cpp deleted file mode 100644 index b7c0eb16..00000000 --- a/src/PJ_mbtfpq.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(mbtfpq, "McBryde-Thomas Flat-Polar Quartic") "\n\tCyl, Sph"; - -#define NITER 20 -#define EPS 1e-7 -#define ONETOL 1.000001 -#define C 1.70710678118654752440 -#define RC 0.58578643762690495119 -#define FYC 1.87475828462269495505 -#define RYC 0.53340209679417701685 -#define FXC 0.31245971410378249250 -#define RXC 3.20041258076506210122 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double th1, c; - int i; - (void) P; - - c = C * sin(lp.phi); - for (i = NITER; i; --i) { - lp.phi -= th1 = (sin(.5*lp.phi) + sin(lp.phi) - c) / - (.5*cos(.5*lp.phi) + cos(lp.phi)); - if (fabs(th1) < EPS) break; - } - xy.x = FXC * lp.lam * (1.0 + 2. * cos(lp.phi)/cos(0.5 * lp.phi)); - xy.y = FYC * sin(0.5 * lp.phi); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double t; - - lp.phi = RYC * xy.y; - if (fabs(lp.phi) > 1.) { - if (fabs(lp.phi) > ONETOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - else if (lp.phi < 0.) { t = -1.; lp.phi = -M_PI; } - else { t = 1.; lp.phi = M_PI; } - } else - lp.phi = 2. * asin(t = lp.phi); - lp.lam = RXC * xy.x / (1. + 2. * cos(lp.phi)/cos(0.5 * lp.phi)); - lp.phi = RC * (t + sin(lp.phi)); - if (fabs(lp.phi) > 1.) - if (fabs(lp.phi) > ONETOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - else lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; - else - lp.phi = asin(lp.phi); - return lp; -} - - -PJ *PROJECTION(mbtfpq) { - - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_merc.cpp b/src/PJ_merc.cpp deleted file mode 100644 index 1998234e..00000000 --- a/src/PJ_merc.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj_internal.h" -#include "proj.h" -#include "proj_math.h" -#include "projects.h" - -PROJ_HEAD(merc, "Mercator") "\n\tCyl, Sph&Ell\n\tlat_ts="; -PROJ_HEAD(webmerc, "Web Mercator / Pseudo Mercator") "\n\tCyl, Ell\n\t"; - -#define EPS10 1.e-10 -static double logtanpfpim1(double x) { /* log(tan(x/2 + M_FORTPI)) */ - if (fabs(x) <= DBL_EPSILON) { - /* tan(M_FORTPI + .5 * x) can be approximated by 1.0 + x */ - return log1p(x); - } - return log(tan(M_FORTPI + .5 * x)); -} - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - xy.x = P->k0 * lp.lam; - xy.y = - P->k0 * log(pj_tsfn(lp.phi, sin(lp.phi), P->e)); - return xy; -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; -} - xy.x = P->k0 * lp.lam; - xy.y = P->k0 * logtanpfpim1(lp.phi); - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - if ((lp.phi = pj_phi2(P->ctx, exp(- xy.y / P->k0), P->e)) == HUGE_VAL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; -} - lp.lam = xy.x / P->k0; - return lp; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - lp.phi = atan(sinh(xy.y / P->k0)); - lp.lam = xy.x / P->k0; - return lp; -} - - -PJ *PROJECTION(merc) { - double phits=0.0; - int is_phits; - - if( (is_phits = pj_param(P->ctx, P->params, "tlat_ts").i) ) { - phits = fabs(pj_param(P->ctx, P->params, "rlat_ts").f); - if (phits >= M_HALFPI) - return pj_default_destructor(P, PJD_ERR_LAT_TS_LARGER_THAN_90); - } - - if (P->es != 0.0) { /* ellipsoid */ - if (is_phits) - P->k0 = pj_msfn(sin(phits), cos(phits), P->es); - P->inv = e_inverse; - P->fwd = e_forward; - } - - else { /* sphere */ - if (is_phits) - P->k0 = cos(phits); - P->inv = s_inverse; - P->fwd = s_forward; - } - - return P; -} - -PJ *PROJECTION(webmerc) { - - /* Overriding k_0 with fixed parameter */ - P->k0 = 1.0; - - P->inv = s_inverse; - P->fwd = s_forward; - return P; -} diff --git a/src/PJ_mill.cpp b/src/PJ_mill.cpp deleted file mode 100644 index 3ea9636f..00000000 --- a/src/PJ_mill.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(mill, "Miller Cylindrical") "\n\tCyl, Sph"; - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - - xy.x = lp.lam; - xy.y = log(tan(M_FORTPI + lp.phi * .4)) * 1.25; - - return (xy); -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - (void) P; - - lp.lam = xy.x; - lp.phi = 2.5 * (atan(exp(.8 * xy.y)) - M_FORTPI); - - return (lp); -} - - -PJ *PROJECTION(mill) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_misrsom.cpp b/src/PJ_misrsom.cpp deleted file mode 100644 index c84b96e3..00000000 --- a/src/PJ_misrsom.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/****************************************************************************** - * This implements Space Oblique Mercator (SOM) projection, used by the - * Multi-angle Imaging SpectroRadiometer (MISR) products, from the NASA EOS Terra - * platform. - * - * The code is identical to that of Landsat SOM (PJ_lsat.c) with the following - * parameter changes: - * - * inclination angle = 98.30382 degrees - * period of revolution = 98.88 minutes - * ascending longitude = 129.3056 degrees - (360 / 233) * path_number - * - * and the following code change: - * - * Q->rlm = PI * (1. / 248. + .5161290322580645); - * - * changed to: - * - * Q->rlm = 0 - * - *****************************************************************************/ -/* based upon Snyder and Linck, USGS-NMD */ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(misrsom, "Space oblique for MISR") - "\n\tCyl, Sph&Ell\n\tpath="; - -#define TOL 1e-7 - -namespace { // anonymous namespace -struct pj_opaque { - double a2, a4, b, c1, c3; - double q, t, u, w, p22, sa, ca, xj, rlm, rlm2; -}; -} // anonymous namespace - -static void seraz0(double lam, double mult, PJ *P) { - struct pj_opaque *Q = static_cast(P->opaque); - double sdsq, h, s, fc, sd, sq, d__1; - - lam *= DEG_TO_RAD; - sd = sin(lam); - sdsq = sd * sd; - s = Q->p22 * Q->sa * cos(lam) * sqrt((1. + Q->t * sdsq) / (( - 1. + Q->w * sdsq) * (1. + Q->q * sdsq))); - d__1 = 1. + Q->q * sdsq; - h = sqrt((1. + Q->q * sdsq) / (1. + Q->w * sdsq)) * ((1. + - Q->w * sdsq) / (d__1 * d__1) - Q->p22 * Q->ca); - sq = sqrt(Q->xj * Q->xj + s * s); - Q->b += fc = mult * (h * Q->xj - s * s) / sq; - Q->a2 += fc * cos(lam + lam); - Q->a4 += fc * cos(lam * 4.); - fc = mult * s * (h + Q->xj) / sq; - Q->c1 += fc * cos(lam); - Q->c3 += fc * cos(lam * 3.); -} - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - int l, nn; - double lamt = 0.0, xlam, sdsq, c, d, s, lamdp = 0.0, phidp, lampp, tanph; - double lamtp, cl, sd, sp, sav, tanphi; - - if (lp.phi > M_HALFPI) - lp.phi = M_HALFPI; - else if (lp.phi < -M_HALFPI) - lp.phi = -M_HALFPI; - if (lp.phi >= 0. ) - lampp = M_HALFPI; - else - lampp = M_PI_HALFPI; - tanphi = tan(lp.phi); - for (nn = 0;;) { - double fac; - sav = lampp; - lamtp = lp.lam + Q->p22 * lampp; - cl = cos(lamtp); - if( cl < 0 ) - fac = lampp + sin(lampp) * M_HALFPI; - else - fac = lampp - sin(lampp) * M_HALFPI; - for (l = 50; l; --l) { - lamt = lp.lam + Q->p22 * sav; - if (fabs(c = cos(lamt)) < TOL) - lamt -= TOL; - xlam = (P->one_es * tanphi * Q->sa + sin(lamt) * Q->ca) / c; - lamdp = atan(xlam) + fac; - if (fabs(fabs(sav) - fabs(lamdp)) < TOL) - break; - sav = lamdp; - } - if (!l || ++nn >= 3 || (lamdp > Q->rlm && lamdp < Q->rlm2)) - break; - if (lamdp <= Q->rlm) - lampp = M_TWOPI_HALFPI; - else if (lamdp >= Q->rlm2) - lampp = M_HALFPI; - } - if (l) { - sp = sin(lp.phi); - phidp = aasin(P->ctx,(P->one_es * Q->ca * sp - Q->sa * cos(lp.phi) * - sin(lamt)) / sqrt(1. - P->es * sp * sp)); - tanph = log(tan(M_FORTPI + .5 * phidp)); - sd = sin(lamdp); - sdsq = sd * sd; - s = Q->p22 * Q->sa * cos(lamdp) * sqrt((1. + Q->t * sdsq) - / ((1. + Q->w * sdsq) * (1. + Q->q * sdsq))); - d = sqrt(Q->xj * Q->xj + s * s); - xy.x = Q->b * lamdp + Q->a2 * sin(2. * lamdp) + Q->a4 * - sin(lamdp * 4.) - tanph * s / d; - xy.y = Q->c1 * sd + Q->c3 * sin(lamdp * 3.) + tanph * Q->xj / d; - } else - xy.x = xy.y = HUGE_VAL; - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - int nn; - double lamt, sdsq, s, lamdp, phidp, sppsq, dd, sd, sl, fac, scl, sav, spp; - - lamdp = xy.x / Q->b; - nn = 50; - do { - sav = lamdp; - sd = sin(lamdp); - sdsq = sd * sd; - s = Q->p22 * Q->sa * cos(lamdp) * sqrt((1. + Q->t * sdsq) - / ((1. + Q->w * sdsq) * (1. + Q->q * sdsq))); - lamdp = xy.x + xy.y * s / Q->xj - Q->a2 * sin( - 2. * lamdp) - Q->a4 * sin(lamdp * 4.) - s / Q->xj * ( - Q->c1 * sin(lamdp) + Q->c3 * sin(lamdp * 3.)); - lamdp /= Q->b; - } while (fabs(lamdp - sav) >= TOL && --nn); - sl = sin(lamdp); - fac = exp(sqrt(1. + s * s / Q->xj / Q->xj) * (xy.y - - Q->c1 * sl - Q->c3 * sin(lamdp * 3.))); - phidp = 2. * (atan(fac) - M_FORTPI); - dd = sl * sl; - if (fabs(cos(lamdp)) < TOL) - lamdp -= TOL; - spp = sin(phidp); - sppsq = spp * spp; - lamt = atan(((1. - sppsq * P->rone_es) * tan(lamdp) * - Q->ca - spp * Q->sa * sqrt((1. + Q->q * dd) * ( - 1. - sppsq) - sppsq * Q->u) / cos(lamdp)) / (1. - sppsq - * (1. + Q->u))); - sl = lamt >= 0. ? 1. : -1.; - scl = cos(lamdp) >= 0. ? 1. : -1; - lamt -= M_HALFPI * (1. - scl) * sl; - lp.lam = lamt - Q->p22 * lamdp; - if (fabs(Q->sa) < TOL) - lp.phi = aasin(P->ctx,spp / sqrt(P->one_es * P->one_es + P->es * sppsq)); - else - lp.phi = atan((tan(lamdp) * cos(lamt) - Q->ca * sin(lamt)) / - (P->one_es * Q->sa)); - return lp; -} - - -PJ *PROJECTION(misrsom) { - int path; - double lam, alf, esc, ess; - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - path = pj_param(P->ctx, P->params, "ipath").i; - if (path <= 0 || path > 233) - return pj_default_destructor(P, PJD_ERR_PATH_NOT_IN_RANGE); - - P->lam0 = DEG_TO_RAD * 129.3056 - M_TWOPI / 233. * path; - alf = 98.30382 * DEG_TO_RAD; - Q->p22 = 98.88 / 1440.0; - - Q->sa = sin(alf); - Q->ca = cos(alf); - if (fabs(Q->ca) < 1e-9) - Q->ca = 1e-9; - esc = P->es * Q->ca * Q->ca; - ess = P->es * Q->sa * Q->sa; - Q->w = (1. - esc) * P->rone_es; - Q->w = Q->w * Q->w - 1.; - Q->q = ess * P->rone_es; - Q->t = ess * (2. - P->es) * P->rone_es * P->rone_es; - Q->u = esc * P->rone_es; - Q->xj = P->one_es * P->one_es * P->one_es; - Q->rlm = 0; - Q->rlm2 = Q->rlm + M_TWOPI; - Q->a2 = Q->a4 = Q->b = Q->c1 = Q->c3 = 0.; - seraz0(0., 1., P); - for (lam = 9.; lam <= 81.0001; lam += 18.) - seraz0(lam, 4., P); - for (lam = 18; lam <= 72.0001; lam += 18.) - seraz0(lam, 2., P); - seraz0(90., 1., P); - Q->a2 /= 30.; - Q->a4 /= 60.; - Q->b /= 30.; - Q->c1 /= 15.; - Q->c3 /= 45.; - - P->inv = e_inverse; - P->fwd = e_forward; - - return P; -} diff --git a/src/PJ_mod_ster.cpp b/src/PJ_mod_ster.cpp deleted file mode 100644 index 7c4f363b..00000000 --- a/src/PJ_mod_ster.cpp +++ /dev/null @@ -1,282 +0,0 @@ -/* based upon Snyder and Linck, USGS-NMD */ -#define PJ_LIB__ -#include -#include "projects.h" -#include "proj_math.h" - -PROJ_HEAD(mil_os, "Miller Oblated Stereographic") "\n\tAzi(mod)"; -PROJ_HEAD(lee_os, "Lee Oblated Stereographic") "\n\tAzi(mod)"; -PROJ_HEAD(gs48, "Mod. Stereographic of 48 U.S.") "\n\tAzi(mod)"; -PROJ_HEAD(alsk, "Mod. Stereographic of Alaska") "\n\tAzi(mod)"; -PROJ_HEAD(gs50, "Mod. Stereographic of 50 U.S.") "\n\tAzi(mod)"; - -#define EPSLN 1e-12 - -namespace { // anonymous namespace -struct pj_opaque { - const COMPLEX *zcoeff; \ - double cchio, schio; \ - int n; -}; -} // anonymous namespace - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double sinlon, coslon, esphi, chi, schi, cchi, s; - COMPLEX p; - - sinlon = sin(lp.lam); - coslon = cos(lp.lam); - esphi = P->e * sin(lp.phi); - chi = 2. * atan(tan((M_HALFPI + lp.phi) * .5) * - pow((1. - esphi) / (1. + esphi), P->e * .5)) - M_HALFPI; - schi = sin(chi); - cchi = cos(chi); - s = 2. / (1. + Q->schio * schi + Q->cchio * cchi * coslon); - p.r = s * cchi * sinlon; - p.i = s * (Q->cchio * schi - Q->schio * cchi * coslon); - p = pj_zpoly1(p, Q->zcoeff, Q->n); - xy.x = p.r; - xy.y = p.i; - - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - int nn; - COMPLEX p, fxy, fpxy, dp; - double den, rh = 0.0, z, sinz = 0.0, cosz = 0.0, chi, phi = 0.0, esphi; - - p.r = xy.x; - p.i = xy.y; - for (nn = 20; nn ;--nn) { - fxy = pj_zpolyd1(p, Q->zcoeff, Q->n, &fpxy); - fxy.r -= xy.x; - fxy.i -= xy.y; - den = fpxy.r * fpxy.r + fpxy.i * fpxy.i; - dp.r = -(fxy.r * fpxy.r + fxy.i * fpxy.i) / den; - dp.i = -(fxy.i * fpxy.r - fxy.r * fpxy.i) / den; - p.r += dp.r; - p.i += dp.i; - if ((fabs(dp.r) + fabs(dp.i)) <= EPSLN) - break; - } - if (nn) { - rh = hypot(p.r, p.i); - z = 2. * atan(.5 * rh); - sinz = sin(z); - cosz = cos(z); - lp.lam = P->lam0; - if (fabs(rh) <= EPSLN) { - /* if we end up here input coordinates were (0,0). - * pj_inv() adds P->lam0 to lp.lam, this way we are - * sure to get the correct offset */ - lp.lam = 0.0; - lp.phi = P->phi0; - return lp; - } - chi = aasin(P->ctx, cosz * Q->schio + p.i * sinz * Q->cchio / rh); - phi = chi; - for (nn = 20; nn ;--nn) { - double dphi; - esphi = P->e * sin(phi); - dphi = 2. * atan(tan((M_HALFPI + chi) * .5) * - pow((1. + esphi) / (1. - esphi), P->e * .5)) - M_HALFPI - phi; - phi += dphi; - if (fabs(dphi) <= EPSLN) - break; - } - } - if (nn) { - lp.phi = phi; - lp.lam = atan2(p.r * sinz, rh * Q->cchio * cosz - p.i * - Q->schio * sinz); - } else - lp.lam = lp.phi = HUGE_VAL; - return lp; -} - - -static PJ *setup(PJ *P) { /* general initialization */ - struct pj_opaque *Q = static_cast(P->opaque); - double esphi, chio; - - if (P->es != 0.0) { - esphi = P->e * sin(P->phi0); - chio = 2. * atan(tan((M_HALFPI + P->phi0) * .5) * - pow((1. - esphi) / (1. + esphi), P->e * .5)) - M_HALFPI; - } else - chio = P->phi0; - Q->schio = sin(chio); - Q->cchio = cos(chio); - P->inv = e_inverse; - P->fwd = e_forward; - - return P; -} - - -/* Miller Oblated Stereographic */ -PJ *PROJECTION(mil_os) { - static const COMPLEX AB[] = { - {0.924500, 0.}, - {0., 0.}, - {0.019430, 0.} - }; - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->n = 2; - P->lam0 = DEG_TO_RAD * 20.; - P->phi0 = DEG_TO_RAD * 18.; - Q->zcoeff = AB; - P->es = 0.; - - return setup(P); -} - - -/* Lee Oblated Stereographic */ -PJ *PROJECTION(lee_os) { - static const COMPLEX AB[] = { - {0.721316, 0.}, - {0., 0.}, - {-0.0088162, -0.00617325} - }; - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->n = 2; - P->lam0 = DEG_TO_RAD * -165.; - P->phi0 = DEG_TO_RAD * -10.; - Q->zcoeff = AB; - P->es = 0.; - - return setup(P); -} - - -PJ *PROJECTION(gs48) { - static const COMPLEX /* 48 United States */ - AB[] = { - {0.98879, 0.}, - {0., 0.}, - {-0.050909, 0.}, - {0., 0.}, - {0.075528, 0.} - }; - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->n = 4; - P->lam0 = DEG_TO_RAD * -96.; - P->phi0 = DEG_TO_RAD * 39.; - Q->zcoeff = AB; - P->es = 0.; - P->a = 6370997.; - - return setup(P); -} - - -PJ *PROJECTION(alsk) { - static const COMPLEX ABe[] = { /* Alaska ellipsoid */ - { .9945303, 0.}, - { .0052083, -.0027404}, - { .0072721, .0048181}, - {-.0151089, -.1932526}, - { .0642675, -.1381226}, - { .3582802, -.2884586}, - }; - - static const COMPLEX ABs[] = { /* Alaska sphere */ - { .9972523, 0.}, - { .0052513, -.0041175}, - { .0074606, .0048125}, - {-.0153783, -.1968253}, - { .0636871, -.1408027}, - { .3660976, -.2937382} - }; - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->n = 5; - P->lam0 = DEG_TO_RAD * -152.; - P->phi0 = DEG_TO_RAD * 64.; - if (P->es != 0.0) { /* fixed ellipsoid/sphere */ - Q->zcoeff = ABe; - P->a = 6378206.4; - P->e = sqrt(P->es = 0.00676866); - } else { - Q->zcoeff = ABs; - P->a = 6370997.; - } - - return setup(P); -} - - -PJ *PROJECTION(gs50) { - static const COMPLEX ABe[] = { /* GS50 ellipsoid */ - { .9827497, 0.}, - { .0210669, .0053804}, - {-.1031415, -.0571664}, - {-.0323337, -.0322847}, - { .0502303, .1211983}, - { .0251805, .0895678}, - {-.0012315, -.1416121}, - { .0072202, -.1317091}, - {-.0194029, .0759677}, - {-.0210072, .0834037} - }; - - static const COMPLEX ABs[] = { /* GS50 sphere */ - { .9842990, 0.}, - { .0211642, .0037608}, - {-.1036018, -.0575102}, - {-.0329095, -.0320119}, - { .0499471, .1223335}, - { .0260460, .0899805}, - { .0007388, -.1435792}, - { .0075848, -.1334108}, - {-.0216473, .0776645}, - {-.0225161, .0853673} - }; - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->n = 9; - P->lam0 = DEG_TO_RAD * -120.; - P->phi0 = DEG_TO_RAD * 45.; - if (P->es != 0.0) { /* fixed ellipsoid/sphere */ - Q->zcoeff = ABe; - P->a = 6378206.4; - P->e = sqrt(P->es = 0.00676866); - } else { - Q->zcoeff = ABs; - P->a = 6370997.; - } - - return setup(P); -} - diff --git a/src/PJ_moll.cpp b/src/PJ_moll.cpp deleted file mode 100644 index c877a1bb..00000000 --- a/src/PJ_moll.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(moll, "Mollweide") "\n\tPCyl, Sph"; -PROJ_HEAD(wag4, "Wagner IV") "\n\tPCyl, Sph"; -PROJ_HEAD(wag5, "Wagner V") "\n\tPCyl, Sph"; - -#define MAX_ITER 10 -#define LOOP_TOL 1e-7 - -namespace { // anonymous namespace -struct pj_opaque { - double C_x, C_y, C_p; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double k, V; - int i; - - k = Q->C_p * sin(lp.phi); - for (i = MAX_ITER; i ; --i) { - lp.phi -= V = (lp.phi + sin(lp.phi) - k) / - (1. + cos(lp.phi)); - if (fabs(V) < LOOP_TOL) - break; - } - if (!i) - lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI; - else - lp.phi *= 0.5; - xy.x = Q->C_x * lp.lam * cos(lp.phi); - xy.y = Q->C_y * sin(lp.phi); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - lp.phi = aasin(P->ctx, xy.y / Q->C_y); - lp.lam = xy.x / (Q->C_x * cos(lp.phi)); - if (fabs(lp.lam) < M_PI) { - lp.phi += lp.phi; - lp.phi = aasin(P->ctx, (lp.phi + sin(lp.phi)) / Q->C_p); - } else { - lp.lam = lp.phi = HUGE_VAL; - } - return lp; -} - - -static PJ * setup(PJ *P, double p) { - struct pj_opaque *Q = static_cast(P->opaque); - double r, sp, p2 = p + p; - - P->es = 0; - sp = sin(p); - r = sqrt(M_TWOPI * sp / (p2 + sin(p2))); - - Q->C_x = 2. * r / M_PI; - Q->C_y = r / sp; - Q->C_p = p2 + sin(p2); - - P->inv = s_inverse; - P->fwd = s_forward; - return P; -} - - -PJ *PROJECTION(moll) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - return setup(P, M_HALFPI); -} - - -PJ *PROJECTION(wag4) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - return setup(P, M_PI/3.); -} - -PJ *PROJECTION(wag5) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - P->es = 0; - Q->C_x = 0.90977; - Q->C_y = 1.65014; - Q->C_p = 3.00896; - - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_molodensky.cpp b/src/PJ_molodensky.cpp deleted file mode 100644 index 91743fda..00000000 --- a/src/PJ_molodensky.cpp +++ /dev/null @@ -1,329 +0,0 @@ -/*********************************************************************** - - (Abridged) Molodensky Transform - - Kristian Evers, 2017-07-07 - -************************************************************************ - - Implements the (abridged) Molodensky transformations for 2D and 3D - data. - - Primarily useful for implementation of datum shifts in transformation - pipelines. - - The code in this file is mostly based on - - The Standard and Abridged Molodensky Coordinate Transformation - Formulae, 2004, R.E. Deaking, - http://www.mygeodesy.id.au/documents/Molodensky%20V2.pdf - - - -************************************************************************ -* Copyright (c) 2017, Kristian Evers / SDFE -* -* 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. -* -***********************************************************************/ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "proj_internal.h" -#include "projects.h" - -PROJ_HEAD(molodensky, "Molodensky transform"); - -static XYZ forward_3d(LPZ lpz, PJ *P); -static LPZ reverse_3d(XYZ xyz, PJ *P); - -namespace { // anonymous namespace -struct pj_opaque_molodensky { - double dx; - double dy; - double dz; - double da; - double df; - int abridged; -}; -} // anonymous namespace - - -static double RN (double a, double es, double phi) { -/********************************************************** - N(phi) - prime vertical radius of curvature - ------------------------------------------- - - This is basically the same function as in PJ_cart.c - should probably be refactored into it's own file at some - point. - -**********************************************************/ - double s = sin(phi); - if (es==0) - return a; - - return a / sqrt (1 - es*s*s); -} - - -static double RM (double a, double es, double phi) { -/********************************************************** - M(phi) - Meridian radius of curvature - ------------------------------------- - - Source: - - E.J Krakiwsky & D.B. Thomson, 1974, - GEODETIC POSITION COMPUTATIONS, - - Fredericton NB, Canada: - University of New Brunswick, - Department of Geodesy and Geomatics Engineering, - Lecture Notes No. 39, - 99 pp. - - http://www2.unb.ca/gge/Pubs/LN39.pdf - -**********************************************************/ - double s = sin(phi); - if (es==0) - return a; - - /* eq. 13a */ - if (phi == 0) - return a * (1-es); - - /* eq. 13b */ - if (fabs(phi) == M_PI_2) - return a / sqrt(1-es); - - /* eq. 13 */ - return (a * (1 - es) ) / pow(1 - es*s*s, 1.5); - -} - - -static LPZ calc_standard_params(LPZ lpz, PJ *P) { - struct pj_opaque_molodensky *Q = (struct pj_opaque_molodensky *) P->opaque; - double dphi, dlam, dh; - - /* sines and cosines */ - double slam = sin(lpz.lam); - double clam = cos(lpz.lam); - double sphi = sin(lpz.phi); - double cphi = cos(lpz.phi); - - /* ellipsoid parameters and differences */ - double f = P->f, a = P->a; - double dx = Q->dx, dy = Q->dy, dz = Q->dz; - double da = Q->da, df = Q->df; - - /* ellipsoid radii of curvature */ - double rho = RM(a, P->es, lpz.phi); - double nu = RN(a, P->e2s, lpz.phi); - - /* delta phi */ - dphi = (-dx*sphi*clam) - (dy*sphi*slam) + (dz*cphi) - + ((nu * P->es * sphi * cphi * da) / a) - + (sphi*cphi * ( rho/(1-f) + nu*(1-f))*df); - dphi /= (rho + lpz.z); - - /* delta lambda */ - dlam = (-dx*slam + dy*clam) / ((nu+lpz.z)*cphi); - - /* delta h */ - dh = dx*cphi*clam + dy*cphi*slam + dz*sphi - (a/nu)*da + nu*(1-f)*sphi*sphi*df; - - lpz.phi = dphi; - lpz.lam = dlam; - lpz.z = dh; - - return lpz; -} - - -static LPZ calc_abridged_params(LPZ lpz, PJ *P) { - struct pj_opaque_molodensky *Q = (struct pj_opaque_molodensky *) P->opaque; - double dphi, dlam, dh; - - /* sines and cosines */ - double slam = sin(lpz.lam); - double clam = cos(lpz.lam); - double sphi = sin(lpz.phi); - double cphi = cos(lpz.phi); - - /* ellipsoid parameters and differences */ - double dx = Q->dx, dy = Q->dy, dz = Q->dz; - double da = Q->da, df = Q->df; - double adffda = (P->a*df + P->f*da); - - /* delta phi */ - dphi = -dx*sphi*clam - dy*sphi*slam + dz*cphi + adffda*sin(2*lpz.phi); - dphi /= RM(P->a, P->es, lpz.phi); - - /* delta lambda */ - dlam = -dx*slam + dy*clam; - dlam /= RN(P->a, P->e2s, lpz.phi)*cphi; - - /* delta h */ - dh = dx*cphi*clam + dy*cphi*slam + dz*sphi - da + adffda*sphi*sphi; - - /* offset coordinate */ - lpz.phi = dphi; - lpz.lam = dlam; - lpz.z = dh; - - return lpz; -} - - -static XY forward_2d(LP lp, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - - point.lp = lp; - point.xyz = forward_3d(point.lpz, P); - - return point.xy; -} - - -static LP reverse_2d(XY xy, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - - point.xy = xy; - point.xyz.z = 0; - point.lpz = reverse_3d(point.xyz, P); - - return point.lp; -} - - -static XYZ forward_3d(LPZ lpz, PJ *P) { - struct pj_opaque_molodensky *Q = (struct pj_opaque_molodensky *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - - point.lpz = lpz; - - /* calculate parameters depending on the mode we are in */ - if (Q->abridged) { - lpz = calc_abridged_params(lpz, P); - } else { - lpz = calc_standard_params(lpz, P); - } - - /* offset coordinate */ - point.lpz.phi += lpz.phi; - point.lpz.lam += lpz.lam; - point.lpz.z += lpz.z; - - return point.xyz; -} - - -static PJ_COORD forward_4d(PJ_COORD obs, PJ *P) { - obs.xyz = forward_3d(obs.lpz, P); - return obs; -} - - -static LPZ reverse_3d(XYZ xyz, PJ *P) { - struct pj_opaque_molodensky *Q = (struct pj_opaque_molodensky *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - LPZ lpz; - - /* calculate parameters depending on the mode we are in */ - point.xyz = xyz; - if (Q->abridged) - lpz = calc_abridged_params(point.lpz, P); - else - lpz = calc_standard_params(point.lpz, P); - - /* offset coordinate */ - point.lpz.phi -= lpz.phi; - point.lpz.lam -= lpz.lam; - point.lpz.z -= lpz.z; - - return point.lpz; -} - - -static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) { - obs.lpz = reverse_3d(obs.xyz, P); - return obs; -} - - -PJ *TRANSFORMATION(molodensky,1) { - int count_required_params = 0; - struct pj_opaque_molodensky *Q = static_cast(pj_calloc(1, sizeof(struct pj_opaque_molodensky))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = (void *) Q; - - P->fwd4d = forward_4d; - P->inv4d = reverse_4d; - P->fwd3d = forward_3d; - P->inv3d = reverse_3d; - P->fwd = forward_2d; - P->inv = reverse_2d; - - P->left = PJ_IO_UNITS_ANGULAR; - P->right = PJ_IO_UNITS_ANGULAR; - - /* read args */ - if (pj_param(P->ctx, P->params, "tdx").i) { - count_required_params ++; - Q->dx = pj_param(P->ctx, P->params, "ddx").f; - } - - if (pj_param(P->ctx, P->params, "tdy").i) { - count_required_params ++; - Q->dy = pj_param(P->ctx, P->params, "ddy").f; - } - - if (pj_param(P->ctx, P->params, "tdz").i) { - count_required_params ++; - Q->dz = pj_param(P->ctx, P->params, "ddz").f; - } - - if (pj_param(P->ctx, P->params, "tda").i) { - count_required_params ++; - Q->da = pj_param(P->ctx, P->params, "dda").f; - } - - if (pj_param(P->ctx, P->params, "tdf").i) { - count_required_params ++; - Q->df = pj_param(P->ctx, P->params, "ddf").f; - } - - Q->abridged = pj_param(P->ctx, P->params, "tabridged").i; - - /* We want all parameters (except +abridged) to be set */ - if (count_required_params == 0) - return pj_default_destructor(P, PJD_ERR_NO_ARGS); - - if (count_required_params != 5) - return pj_default_destructor(P, PJD_ERR_MISSING_ARGS); - - return P; -} diff --git a/src/PJ_natearth.cpp b/src/PJ_natearth.cpp deleted file mode 100644 index 27a6b137..00000000 --- a/src/PJ_natearth.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* -The Natural Earth projection was designed by Tom Patterson, US National Park -Service, in 2007, using Flex Projector. The shape of the original projection -was defined at every 5 degrees and piece-wise cubic spline interpolation was -used to compute the complete graticule. -The code here uses polynomial functions instead of cubic splines and -is therefore much simpler to program. The polynomial approximation was -developed by Bojan Savric, in collaboration with Tom Patterson and Bernhard -Jenny, Institute of Cartography, ETH Zurich. It slightly deviates from -Patterson's original projection by adding additional curvature to meridians -where they meet the horizontal pole line. This improvement is by intention -and designed in collaboration with Tom Patterson. -Port to PROJ.4 by Bernhard Jenny, 6 June 2011 -*/ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(natearth, "Natural Earth") "\n\tPCyl, Sph"; - -#define A0 0.8707 -#define A1 -0.131979 -#define A2 -0.013791 -#define A3 0.003971 -#define A4 -0.001529 -#define B0 1.007226 -#define B1 0.015085 -#define B2 -0.044475 -#define B3 0.028874 -#define B4 -0.005916 -#define C0 B0 -#define C1 (3 * B1) -#define C2 (7 * B2) -#define C3 (9 * B3) -#define C4 (11 * B4) -#define EPS 1e-11 -#define MAX_Y (0.8707 * 0.52 * M_PI) -/* Not sure at all of the appropriate number for MAX_ITER... */ -#define MAX_ITER 100 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double phi2, phi4; - (void) P; - - phi2 = lp.phi * lp.phi; - phi4 = phi2 * phi2; - xy.x = lp.lam * (A0 + phi2 * (A1 + phi2 * (A2 + phi4 * phi2 * (A3 + phi2 * A4)))); - xy.y = lp.phi * (B0 + phi2 * (B1 + phi4 * (B2 + B3 * phi2 + B4 * phi4))); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double yc, tol, y2, y4, f, fder; - int i; - (void) P; - - /* make sure y is inside valid range */ - if (xy.y > MAX_Y) { - xy.y = MAX_Y; - } else if (xy.y < -MAX_Y) { - xy.y = -MAX_Y; - } - - /* latitude */ - yc = xy.y; - for (i = MAX_ITER; i ; --i) { /* Newton-Raphson */ - y2 = yc * yc; - y4 = y2 * y2; - f = (yc * (B0 + y2 * (B1 + y4 * (B2 + B3 * y2 + B4 * y4)))) - xy.y; - fder = C0 + y2 * (C1 + y4 * (C2 + C3 * y2 + C4 * y4)); - yc -= tol = f / fder; - if (fabs(tol) < EPS) { - break; - } - } - if( i == 0 ) - pj_ctx_set_errno( P->ctx, PJD_ERR_NON_CONVERGENT ); - lp.phi = yc; - - /* longitude */ - y2 = yc * yc; - lp.lam = xy.x / (A0 + y2 * (A1 + y2 * (A2 + y2 * y2 * y2 * (A3 + y2 * A4)))); - - return lp; -} - - -PJ *PROJECTION(natearth) { - P->es = 0; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_natearth2.cpp b/src/PJ_natearth2.cpp deleted file mode 100644 index f6aba671..00000000 --- a/src/PJ_natearth2.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* -The Natural Earth II projection was designed by Tom Patterson, US National -Park Service, in 2012, using Flex Projector. The polynomial equation was -developed by Bojan Savric and Bernhard Jenny, College of Earth, Ocean, -and Atmospheric Sciences, Oregon State University. -Port to PROJ.4 by Bojan Savric, 4 April 2016 -*/ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(natearth2, "Natural Earth 2") "\n\tPCyl, Sph"; - -#define A0 0.84719 -#define A1 -0.13063 -#define A2 -0.04515 -#define A3 0.05494 -#define A4 -0.02326 -#define A5 0.00331 -#define B0 1.01183 -#define B1 -0.02625 -#define B2 0.01926 -#define B3 -0.00396 -#define C0 B0 -#define C1 (9 * B1) -#define C2 (11 * B2) -#define C3 (13 * B3) -#define EPS 1e-11 -#define MAX_Y (0.84719 * 0.535117535153096 * M_PI) -/* Not sure at all of the appropriate number for MAX_ITER... */ -#define MAX_ITER 100 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double phi2, phi4, phi6; - (void) P; - - phi2 = lp.phi * lp.phi; - phi4 = phi2 * phi2; - phi6 = phi2 * phi4; - - xy.x = lp.lam * (A0 + A1 * phi2 + phi6 * phi6 * (A2 + A3 * phi2 + A4 * phi4 + A5 * phi6)); - xy.y = lp.phi * (B0 + phi4 * phi4 * (B1 + B2 * phi2 + B3 * phi4)); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double yc, tol, y2, y4, y6, f, fder; - int i; - (void) P; - - /* make sure y is inside valid range */ - if (xy.y > MAX_Y) { - xy.y = MAX_Y; - } else if (xy.y < -MAX_Y) { - xy.y = -MAX_Y; - } - - /* latitude */ - yc = xy.y; - for (i = MAX_ITER; i ; --i) { /* Newton-Raphson */ - y2 = yc * yc; - y4 = y2 * y2; - f = (yc * (B0 + y4 * y4 * (B1 + B2 * y2 + B3 * y4))) - xy.y; - fder = C0 + y4 * y4 * (C1 + C2 * y2 + C3 * y4); - yc -= tol = f / fder; - if (fabs(tol) < EPS) { - break; - } - } - if( i == 0 ) - pj_ctx_set_errno( P->ctx, PJD_ERR_NON_CONVERGENT ); - lp.phi = yc; - - /* longitude */ - y2 = yc * yc; - y4 = y2 * y2; - y6 = y2 * y4; - - lp.lam = xy.x / (A0 + A1 * y2 + y6 * y6 * (A2 + A3 * y2 + A4 * y4 + A5 * y6)); - - return lp; -} - - -PJ *PROJECTION(natearth2) { - P->es = 0; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_nell.cpp b/src/PJ_nell.cpp deleted file mode 100644 index 2a7ea32c..00000000 --- a/src/PJ_nell.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(nell, "Nell") "\n\tPCyl, Sph"; - -#define MAX_ITER 10 -#define LOOP_TOL 1e-7 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double k, V; - int i; - (void) P; - - k = 2. * sin(lp.phi); - V = lp.phi * lp.phi; - lp.phi *= 1.00371 + V * (-0.0935382 + V * -0.011412); - for (i = MAX_ITER; i ; --i) { - lp.phi -= V = (lp.phi + sin(lp.phi) - k) / - (1. + cos(lp.phi)); - if (fabs(V) < LOOP_TOL) - break; - } - xy.x = 0.5 * lp.lam * (1. + cos(lp.phi)); - xy.y = lp.phi; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - lp.lam = 2. * xy.x / (1. + cos(xy.y)); - lp.phi = aasin(P->ctx,0.5 * (xy.y + sin(xy.y))); - - return lp; -} - - -PJ *PROJECTION(nell) { - - P->es = 0; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_nell_h.cpp b/src/PJ_nell_h.cpp deleted file mode 100644 index 28c3ace7..00000000 --- a/src/PJ_nell_h.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(nell_h, "Nell-Hammer") "\n\tPCyl, Sph"; - -#define NITER 9 -#define EPS 1e-7 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - - xy.x = 0.5 * lp.lam * (1. + cos(lp.phi)); - xy.y = 2.0 * (lp.phi - tan(0.5 *lp.phi)); - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double V, c, p; - int i; - (void) P; - - p = 0.5 * xy.y; - for (i = NITER; i ; --i) { - c = cos(0.5 * lp.phi); - lp.phi -= V = (lp.phi - tan(lp.phi/2) - p)/(1. - 0.5/(c*c)); - if (fabs(V) < EPS) - break; - } - if (!i) { - lp.phi = p < 0. ? -M_HALFPI : M_HALFPI; - lp.lam = 2. * xy.x; - } else - lp.lam = 2. * xy.x / (1. + cos(lp.phi)); - - return lp; -} - - -PJ *PROJECTION(nell_h) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_nocol.cpp b/src/PJ_nocol.cpp deleted file mode 100644 index 541d08b2..00000000 --- a/src/PJ_nocol.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(nicol, "Nicolosi Globular") "\n\tMisc Sph, no inv"; - -#define EPS 1e-10 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - (void) P; - - if (fabs(lp.lam) < EPS) { - xy.x = 0; - xy.y = lp.phi; - } else if (fabs(lp.phi) < EPS) { - xy.x = lp.lam; - xy.y = 0.; - } else if (fabs(fabs(lp.lam) - M_HALFPI) < EPS) { - xy.x = lp.lam * cos(lp.phi); - xy.y = M_HALFPI * sin(lp.phi); - } else if (fabs(fabs(lp.phi) - M_HALFPI) < EPS) { - xy.x = 0; - xy.y = lp.phi; - } else { - double tb, c, d, m, n, r2, sp; - - tb = M_HALFPI / lp.lam - lp.lam / M_HALFPI; - c = lp.phi / M_HALFPI; - d = (1 - c * c)/((sp = sin(lp.phi)) - c); - r2 = tb / d; - r2 *= r2; - m = (tb * sp / d - 0.5 * tb)/(1. + r2); - n = (sp / r2 + 0.5 * d)/(1. + 1./r2); - xy.x = cos(lp.phi); - xy.x = sqrt(m * m + xy.x * xy.x / (1. + r2)); - xy.x = M_HALFPI * ( m + (lp.lam < 0. ? -xy.x : xy.x)); - xy.y = sqrt(n * n - (sp * sp / r2 + d * sp - 1.) / - (1. + 1./r2)); - xy.y = M_HALFPI * ( n + (lp.phi < 0. ? xy.y : -xy.y )); - } - return (xy); -} - - -PJ *PROJECTION(nicol) { - P->es = 0.; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_nsper.cpp b/src/PJ_nsper.cpp deleted file mode 100644 index f93010f8..00000000 --- a/src/PJ_nsper.cpp +++ /dev/null @@ -1,202 +0,0 @@ -#define PJ_LIB__ -#include -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -namespace { // anonymous namespace -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque { - double height; - double sinph0; - double cosph0; - double p; - double rp; - double pn1; - double pfact; - double h; - double cg; - double sg; - double sw; - double cw; - enum Mode mode; - int tilt; -}; -} // anonymous namespace - -PROJ_HEAD(nsper, "Near-sided perspective") "\n\tAzi, Sph\n\th="; -PROJ_HEAD(tpers, "Tilted perspective") "\n\tAzi, Sph\n\ttilt= azi= h="; - -# define EPS10 1.e-10 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double coslam, cosphi, sinphi; - - sinphi = sin(lp.phi); - cosphi = cos(lp.phi); - coslam = cos(lp.lam); - switch (Q->mode) { - case OBLIQ: - xy.y = Q->sinph0 * sinphi + Q->cosph0 * cosphi * coslam; - break; - case EQUIT: - xy.y = cosphi * coslam; - break; - case S_POLE: - xy.y = - sinphi; - break; - case N_POLE: - xy.y = sinphi; - break; - } - if (xy.y < Q->rp) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - xy.y = Q->pn1 / (Q->p - xy.y); - xy.x = xy.y * cosphi * sin(lp.lam); - switch (Q->mode) { - case OBLIQ: - xy.y *= (Q->cosph0 * sinphi - - Q->sinph0 * cosphi * coslam); - break; - case EQUIT: - xy.y *= sinphi; - break; - case N_POLE: - coslam = - coslam; - /*-fallthrough*/ - case S_POLE: - xy.y *= cosphi * coslam; - break; - } - if (Q->tilt) { - double yt, ba; - - yt = xy.y * Q->cg + xy.x * Q->sg; - ba = 1. / (yt * Q->sw * Q->h + Q->cw); - xy.x = (xy.x * Q->cg - xy.y * Q->sg) * Q->cw * ba; - xy.y = yt * ba; - } - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double rh, cosz, sinz; - - if (Q->tilt) { - double bm, bq, yt; - - yt = 1./(Q->pn1 - xy.y * Q->sw); - bm = Q->pn1 * xy.x * yt; - bq = Q->pn1 * xy.y * Q->cw * yt; - xy.x = bm * Q->cg + bq * Q->sg; - xy.y = bq * Q->cg - bm * Q->sg; - } - rh = hypot(xy.x, xy.y); - if ((sinz = 1. - rh * rh * Q->pfact) < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - sinz = (Q->p - sqrt(sinz)) / (Q->pn1 / rh + rh / Q->pn1); - cosz = sqrt(1. - sinz * sinz); - if (fabs(rh) <= EPS10) { - lp.lam = 0.; - lp.phi = P->phi0; - } else { - switch (Q->mode) { - case OBLIQ: - lp.phi = asin(cosz * Q->sinph0 + xy.y * sinz * Q->cosph0 / rh); - xy.y = (cosz - Q->sinph0 * sin(lp.phi)) * rh; - xy.x *= sinz * Q->cosph0; - break; - case EQUIT: - lp.phi = asin(xy.y * sinz / rh); - xy.y = cosz * rh; - xy.x *= sinz; - break; - case N_POLE: - lp.phi = asin(cosz); - xy.y = -xy.y; - break; - case S_POLE: - lp.phi = - asin(cosz); - break; - } - lp.lam = atan2(xy.x, xy.y); - } - return lp; -} - - -static PJ *setup(PJ *P) { - struct pj_opaque *Q = static_cast(P->opaque); - - if ((Q->height = pj_param(P->ctx, P->params, "dh").f) <= 0.) - return pj_default_destructor(P, PJD_ERR_H_LESS_THAN_ZERO); - - if (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) - Q->mode = P->phi0 < 0. ? S_POLE : N_POLE; - else if (fabs(P->phi0) < EPS10) - Q->mode = EQUIT; - else { - Q->mode = OBLIQ; - Q->sinph0 = sin(P->phi0); - Q->cosph0 = cos(P->phi0); - } - Q->pn1 = Q->height / P->a; /* normalize by radius */ - Q->p = 1. + Q->pn1; - Q->rp = 1. / Q->p; - Q->h = 1. / Q->pn1; - Q->pfact = (Q->p + 1.) * Q->h; - P->inv = s_inverse; - P->fwd = s_forward; - P->es = 0.; - - return P; -} - - -PJ *PROJECTION(nsper) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->tilt = 0; - - return setup(P); -} - - -PJ *PROJECTION(tpers) { - double omega, gamma; - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - omega = pj_param(P->ctx, P->params, "rtilt").f; - gamma = pj_param(P->ctx, P->params, "razi").f; - Q->tilt = 1; - Q->cg = cos(gamma); Q->sg = sin(gamma); - Q->cw = cos(omega); Q->sw = sin(omega); - - return setup(P); -} - diff --git a/src/PJ_nzmg.cpp b/src/PJ_nzmg.cpp deleted file mode 100644 index bf0862fb..00000000 --- a/src/PJ_nzmg.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of the nzmg (New Zealand Map Grid) projection. - * Very loosely based upon DMA code by Bradford W. Drew - * Author: Gerald Evenden - * - ****************************************************************************** - * Copyright (c) 1995, Gerald Evenden - * - * 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. - *****************************************************************************/ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(nzmg, "New Zealand Map Grid") "\n\tfixed Earth"; - -#define EPSLN 1e-10 -#define SEC5_TO_RAD 0.4848136811095359935899141023 -#define RAD_TO_SEC5 2.062648062470963551564733573 - -static const COMPLEX bf[] = { - { .7557853228, 0.0}, - { .249204646, 0.003371507}, - {-.001541739, 0.041058560}, - {-.10162907, 0.01727609}, - {-.26623489, -0.36249218}, - {-.6870983, -1.1651967} }; - -static const double tphi[] = { 1.5627014243, .5185406398, -.03333098, - -.1052906, -.0368594, .007317, - .01220, .00394, -.0013 }; - -static const double tpsi[] = { .6399175073, -.1358797613, .063294409, -.02526853, .0117879, - -.0055161, .0026906, -.001333, .00067, -.00034 }; - -#define Nbf 5 -#define Ntpsi 9 -#define Ntphi 8 - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - COMPLEX p; - const double *C; - int i; - - lp.phi = (lp.phi - P->phi0) * RAD_TO_SEC5; - for (p.r = *(C = tpsi + (i = Ntpsi)); i ; --i) - p.r = *--C + lp.phi * p.r; - p.r *= lp.phi; - p.i = lp.lam; - p = pj_zpoly1(p, bf, Nbf); - xy.x = p.i; - xy.y = p.r; - - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - int nn, i; - COMPLEX p, f, fp, dp; - double den; - const double *C; - - p.r = xy.y; - p.i = xy.x; - for (nn = 20; nn ;--nn) { - f = pj_zpolyd1(p, bf, Nbf, &fp); - f.r -= xy.y; - f.i -= xy.x; - den = fp.r * fp.r + fp.i * fp.i; - p.r += dp.r = -(f.r * fp.r + f.i * fp.i) / den; - p.i += dp.i = -(f.i * fp.r - f.r * fp.i) / den; - if ((fabs(dp.r) + fabs(dp.i)) <= EPSLN) - break; - } - if (nn) { - lp.lam = p.i; - for (lp.phi = *(C = tphi + (i = Ntphi)); i ; --i) - lp.phi = *--C + p.r * lp.phi; - lp.phi = P->phi0 + p.r * lp.phi * SEC5_TO_RAD; - } else - lp.lam = lp.phi = HUGE_VAL; - - return lp; -} - - -PJ *PROJECTION(nzmg) { - /* force to International major axis */ - P->ra = 1. / (P->a = 6378388.0); - P->lam0 = DEG_TO_RAD * 173.; - P->phi0 = DEG_TO_RAD * -41.; - P->x0 = 2510000.; - P->y0 = 6023150.; - - P->inv = e_inverse; - P->fwd = e_forward; - - - return P; -} diff --git a/src/PJ_ob_tran.cpp b/src/PJ_ob_tran.cpp deleted file mode 100644 index d34059a9..00000000 --- a/src/PJ_ob_tran.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#define PJ_LIB__ -#include -#include -#include -#include - -#include "proj.h" -#include "projects.h" - -namespace { // anonymous namespace -struct pj_opaque { - struct PJconsts *link; - double lamp; - double cphip, sphip; -}; -} // anonymous namespace - -PROJ_HEAD(ob_tran, "General Oblique Transformation") "\n\tMisc Sph" -"\n\to_proj= plus parameters for projection" -"\n\to_lat_p= o_lon_p= (new pole) or" -"\n\to_alpha= o_lon_c= o_lat_c= or" -"\n\to_lon_1= o_lat_1= o_lon_2= o_lat_2="; - -#define TOL 1e-10 - - -static XY o_forward(LP lp, PJ *P) { /* spheroid */ - struct pj_opaque *Q = static_cast(P->opaque); - double coslam, sinphi, cosphi; - - coslam = cos(lp.lam); - sinphi = sin(lp.phi); - cosphi = cos(lp.phi); - lp.lam = adjlon(aatan2(cosphi * sin(lp.lam), Q->sphip * cosphi * coslam + - Q->cphip * sinphi) + Q->lamp); - lp.phi = aasin(P->ctx,Q->sphip * sinphi - Q->cphip * cosphi * coslam); - - return Q->link->fwd(lp, Q->link); -} - - -static XY t_forward(LP lp, PJ *P) { /* spheroid */ - struct pj_opaque *Q = static_cast(P->opaque); - double cosphi, coslam; - - cosphi = cos(lp.phi); - coslam = cos(lp.lam); - lp.lam = adjlon(aatan2(cosphi * sin(lp.lam), sin(lp.phi)) + Q->lamp); - lp.phi = aasin(P->ctx, - cosphi * coslam); - - return Q->link->fwd(lp, Q->link); -} - - -static LP o_inverse(XY xy, PJ *P) { /* spheroid */ - - struct pj_opaque *Q = static_cast(P->opaque); - double coslam, sinphi, cosphi; - - LP lp = Q->link->inv(xy, Q->link); - if (lp.lam != HUGE_VAL) { - coslam = cos(lp.lam -= Q->lamp); - sinphi = sin(lp.phi); - cosphi = cos(lp.phi); - lp.phi = aasin(P->ctx,Q->sphip * sinphi + Q->cphip * cosphi * coslam); - lp.lam = aatan2(cosphi * sin(lp.lam), Q->sphip * cosphi * coslam - - Q->cphip * sinphi); - } - return lp; -} - - -static LP t_inverse(XY xy, PJ *P) { /* spheroid */ - - struct pj_opaque *Q = static_cast(P->opaque); - double cosphi, t; - - LP lp = Q->link->inv(xy, Q->link); - if (lp.lam != HUGE_VAL) { - cosphi = cos(lp.phi); - t = lp.lam - Q->lamp; - lp.lam = aatan2(cosphi * sin(t), - sin(lp.phi)); - lp.phi = aasin(P->ctx,cosphi * cos(t)); - } - return lp; -} - - -static PJ *destructor(PJ *P, int errlev) { - if (nullptr==P) - return nullptr; - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - if (static_cast(P->opaque)->link) - static_cast(P->opaque)->link->destructor (static_cast(P->opaque)->link, errlev); - - return pj_default_destructor(P, errlev); -} - - - - -/*********************************************************************** - -These functions are modified versions of the functions "argc_params" -and "argv_params" from PJ_pipeline.c - -Basically, they do the somewhat backwards stunt of turning the paralist -representation of the +args back into the original +argv, +argc -representation accepted by pj_init_ctx(). - -This, however, also begs the question of whether we really need the -paralist linked list representation, or if we could do with a simpler -null-terminated argv style array? This would simplfy some code, and -keep memory allocations more localized. - -***********************************************************************/ - -typedef struct {int argc; char **argv;} ARGS; - -/* count the number of args in the linked list */ -static size_t paralist_params_argc (paralist *params) { - size_t argc = 0; - for (; params != nullptr; params = params->next) - argc++; - return argc; -} - - -/* turn paralist into argc/argv style argument list */ -static ARGS ob_tran_target_params (paralist *params) { - int i = 0; - ARGS args = {0, nullptr}; - size_t argc = paralist_params_argc (params); - if (argc < 2) - return args; - - /* all args except the proj_ob_tran */ - args.argv = static_cast(pj_calloc (argc - 1, sizeof (char *))); - if (nullptr==args.argv) - return args; - - /* Copy all args *except* the proj=ob_tran arg to the argv array */ - for (i = 0; params != nullptr; params = params->next) { - if (0==strcmp (params->param, "proj=ob_tran")) - continue; - args.argv[i++] = params->param; - } - args.argc = i; - - /* Then convert the o_proj=xxx element to proj=xxx */ - for (i = 0; i < args.argc; i++) { - if (0!=strncmp (args.argv[i], "o_proj=", 7)) - continue; - args.argv[i] += 2; - break; - } - - return args; -} - - - -PJ *PROJECTION(ob_tran) { - double phip; - char *name; - ARGS args; - PJ *R; /* projection to rotate */ - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return destructor(P, ENOMEM); - - P->opaque = Q; - P->destructor = destructor; - - /* get name of projection to be translated */ - if (!(name = pj_param(P->ctx, P->params, "so_proj").s)) - return destructor(P, PJD_ERR_NO_ROTATION_PROJ); - - /* avoid endless recursion */ - if( strcmp(name, "ob_tran") == 0 ) - return destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ); - - /* Create the target projection object to rotate */ - args = ob_tran_target_params (P->params); - R = pj_init_ctx (pj_get_ctx(P), args.argc, args.argv); - pj_dealloc (args.argv); - - if (nullptr==R) - return destructor (P, PJD_ERR_UNKNOWN_PROJECTION_ID); - Q->link = R; - - if (pj_param(P->ctx, P->params, "to_alpha").i) { - double lamc, phic, alpha; - - lamc = pj_param(P->ctx, P->params, "ro_lon_c").f; - phic = pj_param(P->ctx, P->params, "ro_lat_c").f; - alpha = pj_param(P->ctx, P->params, "ro_alpha").f; - - if (fabs(fabs(phic) - M_HALFPI) <= TOL) - return destructor(P, PJD_ERR_LAT_0_OR_ALPHA_EQ_90); - - Q->lamp = lamc + aatan2(-cos(alpha), -sin(alpha) * sin(phic)); - phip = aasin(P->ctx,cos(phic) * sin(alpha)); - } else if (pj_param(P->ctx, P->params, "to_lat_p").i) { /* specified new pole */ - Q->lamp = pj_param(P->ctx, P->params, "ro_lon_p").f; - phip = pj_param(P->ctx, P->params, "ro_lat_p").f; - } else { /* specified new "equator" points */ - double lam1, lam2, phi1, phi2, con; - - lam1 = pj_param(P->ctx, P->params, "ro_lon_1").f; - phi1 = pj_param(P->ctx, P->params, "ro_lat_1").f; - lam2 = pj_param(P->ctx, P->params, "ro_lon_2").f; - phi2 = pj_param(P->ctx, P->params, "ro_lat_2").f; - if (fabs(phi1 - phi2) <= TOL || (con = fabs(phi1)) <= TOL || - fabs(con - M_HALFPI) <= TOL || fabs(fabs(phi2) - M_HALFPI) <= TOL) - return destructor(P, PJD_ERR_LAT_1_OR_2_ZERO_OR_90); - - Q->lamp = atan2(cos(phi1) * sin(phi2) * cos(lam1) - - sin(phi1) * cos(phi2) * cos(lam2), - sin(phi1) * cos(phi2) * sin(lam2) - - cos(phi1) * sin(phi2) * sin(lam1)); - phip = atan(-cos(Q->lamp - lam1) / tan(phi1)); - } - - if (fabs(phip) > TOL) { /* oblique */ - Q->cphip = cos(phip); - Q->sphip = sin(phip); - P->fwd = Q->link->fwd ? o_forward : nullptr; - P->inv = Q->link->inv ? o_inverse : nullptr; - } else { /* transverse */ - P->fwd = Q->link->fwd ? t_forward : nullptr; - P->inv = Q->link->inv ? t_inverse : nullptr; - } - - /* Support some rather speculative test cases, where the rotated projection */ - /* is actually latlong. We do not want scaling in that case... */ - if (Q->link->right==PJ_IO_UNITS_ANGULAR) - P->right = PJ_IO_UNITS_PROJECTED; - - - return P; -} diff --git a/src/PJ_ocea.cpp b/src/PJ_ocea.cpp deleted file mode 100644 index 0576ace7..00000000 --- a/src/PJ_ocea.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(ocea, "Oblique Cylindrical Equal Area") "\n\tCyl, Sph" - "lonc= alpha= or\n\tlat_1= lat_2= lon_1= lon_2="; - -namespace { // anonymous namespace -struct pj_opaque { - double rok; - double rtk; - double sinphi; - double cosphi; - double singam; - double cosgam; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double t; - xy.y = sin(lp.lam); - t = cos(lp.lam); - xy.x = atan((tan(lp.phi) * Q->cosphi + Q->sinphi * xy.y) / t); - if (t < 0.) - xy.x += M_PI; - xy.x *= Q->rtk; - xy.y = Q->rok * (Q->sinphi * sin(lp.phi) - Q->cosphi * cos(lp.phi) * xy.y); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double t, s; - - xy.y /= Q->rok; - xy.x /= Q->rtk; - t = sqrt(1. - xy.y * xy.y); - lp.phi = asin(xy.y * Q->sinphi + t * Q->cosphi * (s = sin(xy.x))); - lp.lam = atan2(t * Q->sinphi * s - xy.y * Q->cosphi, - t * cos(xy.x)); - return lp; -} - - -PJ *PROJECTION(ocea) { - double phi_0=0.0, phi_1, phi_2, lam_1, lam_2, lonz, alpha; - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->rok = 1. / P->k0; - Q->rtk = P->k0; - /*If the keyword "alpha" is found in the sentence then use 1point+1azimuth*/ - if ( pj_param(P->ctx, P->params, "talpha").i) { - /*Define Pole of oblique transformation from 1 point & 1 azimuth*/ - alpha = pj_param(P->ctx, P->params, "ralpha").f; - lonz = pj_param(P->ctx, P->params, "rlonc").f; - /*Equation 9-8 page 80 (http://pubs.usgs.gov/pp/1395/report.pdf)*/ - Q->singam = atan(-cos(alpha)/(-sin(phi_0) * sin(alpha))) + lonz; - /*Equation 9-7 page 80 (http://pubs.usgs.gov/pp/1395/report.pdf)*/ - Q->sinphi = asin(cos(phi_0) * sin(alpha)); - /*If the keyword "alpha" is NOT found in the sentence then use 2points*/ - } else { - /*Define Pole of oblique transformation from 2 points*/ - phi_1 = pj_param(P->ctx, P->params, "rlat_1").f; - phi_2 = pj_param(P->ctx, P->params, "rlat_2").f; - lam_1 = pj_param(P->ctx, P->params, "rlon_1").f; - lam_2 = pj_param(P->ctx, P->params, "rlon_2").f; - /*Equation 9-1 page 80 (http://pubs.usgs.gov/pp/1395/report.pdf)*/ - Q->singam = atan2(cos(phi_1) * sin(phi_2) * cos(lam_1) - - sin(phi_1) * cos(phi_2) * cos(lam_2), - sin(phi_1) * cos(phi_2) * sin(lam_2) - - cos(phi_1) * sin(phi_2) * sin(lam_1) ); - - /* take care of P->lam0 wrap-around when +lam_1=-90*/ - if (lam_1 == -M_HALFPI) - Q->singam = -Q->singam; - - /*Equation 9-2 page 80 (http://pubs.usgs.gov/pp/1395/report.pdf)*/ - Q->sinphi = atan(-cos(Q->singam - lam_1) / tan(phi_1)); - } - P->lam0 = Q->singam + M_HALFPI; - Q->cosphi = cos(Q->sinphi); - Q->sinphi = sin(Q->sinphi); - Q->cosgam = cos(Q->singam); - Q->singam = sin(Q->singam); - P->inv = s_inverse; - P->fwd = s_forward; - P->es = 0.; - - return P; -} diff --git a/src/PJ_oea.cpp b/src/PJ_oea.cpp deleted file mode 100644 index 0c401b2f..00000000 --- a/src/PJ_oea.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#define PJ_LIB__ -#include -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -PROJ_HEAD(oea, "Oblated Equal Area") "\n\tMisc Sph\n\tn= m= theta="; - -namespace { // anonymous namespace -struct pj_opaque { - double theta; - double m, n; - double two_r_m, two_r_n, rm, rn, hm, hn; - double cp0, sp0; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double Az, M, N, cp, sp, cl, shz; - - cp = cos(lp.phi); - sp = sin(lp.phi); - cl = cos(lp.lam); - Az = aatan2(cp * sin(lp.lam), Q->cp0 * sp - Q->sp0 * cp * cl) + Q->theta; - shz = sin(0.5 * aacos(P->ctx, Q->sp0 * sp + Q->cp0 * cp * cl)); - M = aasin(P->ctx, shz * sin(Az)); - N = aasin(P->ctx, shz * cos(Az) * cos(M) / cos(M * Q->two_r_m)); - xy.y = Q->n * sin(N * Q->two_r_n); - xy.x = Q->m * sin(M * Q->two_r_m) * cos(N) / cos(N * Q->two_r_n); - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double N, M, xp, yp, z, Az, cz, sz, cAz; - - N = Q->hn * aasin(P->ctx,xy.y * Q->rn); - M = Q->hm * aasin(P->ctx,xy.x * Q->rm * cos(N * Q->two_r_n) / cos(N)); - xp = 2. * sin(M); - yp = 2. * sin(N) * cos(M * Q->two_r_m) / cos(M); - cAz = cos(Az = aatan2(xp, yp) - Q->theta); - z = 2. * aasin(P->ctx, 0.5 * hypot(xp, yp)); - sz = sin(z); - cz = cos(z); - lp.phi = aasin(P->ctx, Q->sp0 * cz + Q->cp0 * sz * cAz); - lp.lam = aatan2(sz * sin(Az), - Q->cp0 * cz - Q->sp0 * sz * cAz); - - return lp; -} - - - - -PJ *PROJECTION(oea) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - if (((Q->n = pj_param(P->ctx, P->params, "dn").f) <= 0.) || - ((Q->m = pj_param(P->ctx, P->params, "dm").f) <= 0.)) { - return pj_default_destructor(P, PJD_ERR_INVALID_M_OR_N); - } else { - Q->theta = pj_param(P->ctx, P->params, "rtheta").f; - Q->sp0 = sin(P->phi0); - Q->cp0 = cos(P->phi0); - Q->rn = 1./ Q->n; - Q->rm = 1./ Q->m; - Q->two_r_n = 2. * Q->rn; - Q->two_r_m = 2. * Q->rm; - Q->hm = 0.5 * Q->m; - Q->hn = 0.5 * Q->n; - P->fwd = s_forward; - P->inv = s_inverse; - P->es = 0.; - } - - return P; -} - diff --git a/src/PJ_omerc.cpp b/src/PJ_omerc.cpp deleted file mode 100644 index ead07128..00000000 --- a/src/PJ_omerc.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* -** Copyright (c) 2003, 2006 Gerald I. Evenden -*/ -/* -** 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. -*/ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(omerc, "Oblique Mercator") - "\n\tCyl, Sph&Ell no_rot\n\t" - "alpha= [gamma=] [no_off] lonc= or\n\t lon_1= lat_1= lon_2= lat_2="; - -namespace { // anonymous namespace -struct pj_opaque { - double A, B, E, AB, ArB, BrA, rB, singam, cosgam, sinrot, cosrot; - double v_pole_n, v_pole_s, u_0; - int no_rot; -}; -} // anonymous namespace - -#define TOL 1.e-7 -#define EPS 1.e-10 - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double S, T, U, V, W, temp, u, v; - - if (fabs(fabs(lp.phi) - M_HALFPI) > EPS) { - W = Q->E / pow(pj_tsfn(lp.phi, sin(lp.phi), P->e), Q->B); - temp = 1. / W; - S = .5 * (W - temp); - T = .5 * (W + temp); - V = sin(Q->B * lp.lam); - U = (S * Q->singam - V * Q->cosgam) / T; - if (fabs(fabs(U) - 1.0) < EPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - v = 0.5 * Q->ArB * log((1. - U)/(1. + U)); - temp = cos(Q->B * lp.lam); - if(fabs(temp) < TOL) { - u = Q->A * lp.lam; - } else { - u = Q->ArB * atan2((S * Q->cosgam + V * Q->singam), temp); - } - } else { - v = lp.phi > 0 ? Q->v_pole_n : Q->v_pole_s; - u = Q->ArB * lp.phi; - } - if (Q->no_rot) { - xy.x = u; - xy.y = v; - } else { - u -= Q->u_0; - xy.x = v * Q->cosrot + u * Q->sinrot; - xy.y = u * Q->cosrot - v * Q->sinrot; - } - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double u, v, Qp, Sp, Tp, Vp, Up; - - if (Q->no_rot) { - v = xy.y; - u = xy.x; - } else { - v = xy.x * Q->cosrot - xy.y * Q->sinrot; - u = xy.y * Q->cosrot + xy.x * Q->sinrot + Q->u_0; - } - Qp = exp(- Q->BrA * v); - Sp = .5 * (Qp - 1. / Qp); - Tp = .5 * (Qp + 1. / Qp); - Vp = sin(Q->BrA * u); - Up = (Vp * Q->cosgam + Sp * Q->singam) / Tp; - if (fabs(fabs(Up) - 1.) < EPS) { - lp.lam = 0.; - lp.phi = Up < 0. ? -M_HALFPI : M_HALFPI; - } else { - lp.phi = Q->E / sqrt((1. + Up) / (1. - Up)); - if ((lp.phi = pj_phi2(P->ctx, pow(lp.phi, 1. / Q->B), P->e)) == HUGE_VAL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - lp.lam = - Q->rB * atan2((Sp * Q->cosgam - - Vp * Q->singam), cos(Q->BrA * u)); - } - return lp; -} - - -PJ *PROJECTION(omerc) { - double con, com, cosph0, D, F, H, L, sinph0, p, J, gamma=0, - gamma0, lamc=0, lam1=0, lam2=0, phi1=0, phi2=0, alpha_c=0; - int alp, gam, no_off = 0; - - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->no_rot = pj_param(P->ctx, P->params, "bno_rot").i; - if ((alp = pj_param(P->ctx, P->params, "talpha").i) != 0) - alpha_c = pj_param(P->ctx, P->params, "ralpha").f; - if ((gam = pj_param(P->ctx, P->params, "tgamma").i) != 0) - gamma = pj_param(P->ctx, P->params, "rgamma").f; - if (alp || gam) { - lamc = pj_param(P->ctx, P->params, "rlonc").f; - no_off = - /* For libproj4 compatibility */ - pj_param(P->ctx, P->params, "tno_off").i - /* for backward compatibility */ - || pj_param(P->ctx, P->params, "tno_uoff").i; - if( no_off ) - { - /* Mark the parameter as used, so that the pj_get_def() return them */ - pj_param(P->ctx, P->params, "sno_uoff"); - pj_param(P->ctx, P->params, "sno_off"); - } - } else { - lam1 = pj_param(P->ctx, P->params, "rlon_1").f; - phi1 = pj_param(P->ctx, P->params, "rlat_1").f; - lam2 = pj_param(P->ctx, P->params, "rlon_2").f; - phi2 = pj_param(P->ctx, P->params, "rlat_2").f; - if (fabs(phi1 - phi2) <= TOL || - (con = fabs(phi1)) <= TOL || - fabs(con - M_HALFPI) <= TOL || - fabs(fabs(P->phi0) - M_HALFPI) <= TOL || - fabs(fabs(phi2) - M_HALFPI) <= TOL) - return pj_default_destructor(P, PJD_ERR_LAT_0_OR_ALPHA_EQ_90); - } - com = sqrt(P->one_es); - if (fabs(P->phi0) > EPS) { - sinph0 = sin(P->phi0); - cosph0 = cos(P->phi0); - con = 1. - P->es * sinph0 * sinph0; - Q->B = cosph0 * cosph0; - Q->B = sqrt(1. + P->es * Q->B * Q->B / P->one_es); - Q->A = Q->B * P->k0 * com / con; - D = Q->B * com / (cosph0 * sqrt(con)); - if ((F = D * D - 1.) <= 0.) - F = 0.; - else { - F = sqrt(F); - if (P->phi0 < 0.) - F = -F; - } - Q->E = F += D; - Q->E *= pow(pj_tsfn(P->phi0, sinph0, P->e), Q->B); - } else { - Q->B = 1. / com; - Q->A = P->k0; - Q->E = D = F = 1.; - } - if (alp || gam) { - if (alp) { - gamma0 = aasin(P->ctx, sin(alpha_c) / D); - if (!gam) - gamma = alpha_c; - } else - alpha_c = aasin(P->ctx, D*sin(gamma0 = gamma)); - P->lam0 = lamc - aasin(P->ctx, .5 * (F - 1. / F) * - tan(gamma0)) / Q->B; - } else { - H = pow(pj_tsfn(phi1, sin(phi1), P->e), Q->B); - L = pow(pj_tsfn(phi2, sin(phi2), P->e), Q->B); - F = Q->E / H; - p = (L - H) / (L + H); - J = Q->E * Q->E; - J = (J - L * H) / (J + L * H); - if ((con = lam1 - lam2) < -M_PI) - lam2 -= M_TWOPI; - else if (con > M_PI) - lam2 += M_TWOPI; - P->lam0 = adjlon(.5 * (lam1 + lam2) - atan( - J * tan(.5 * Q->B * (lam1 - lam2)) / p) / Q->B); - gamma0 = atan(2. * sin(Q->B * adjlon(lam1 - P->lam0)) / - (F - 1. / F)); - gamma = alpha_c = aasin(P->ctx, D * sin(gamma0)); - } - Q->singam = sin(gamma0); - Q->cosgam = cos(gamma0); - Q->sinrot = sin(gamma); - Q->cosrot = cos(gamma); - Q->BrA = 1. / (Q->ArB = Q->A * (Q->rB = 1. / Q->B)); - Q->AB = Q->A * Q->B; - if (no_off) - Q->u_0 = 0; - else { - Q->u_0 = fabs(Q->ArB * atan(sqrt(D * D - 1.) / cos(alpha_c))); - if (P->phi0 < 0.) - Q->u_0 = - Q->u_0; - } - F = 0.5 * gamma0; - Q->v_pole_n = Q->ArB * log(tan(M_FORTPI - F)); - Q->v_pole_s = Q->ArB * log(tan(M_FORTPI + F)); - P->inv = e_inverse; - P->fwd = e_forward; - - return P; -} diff --git a/src/PJ_ortho.cpp b/src/PJ_ortho.cpp deleted file mode 100644 index 6ea55248..00000000 --- a/src/PJ_ortho.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#define PJ_LIB__ -#include -#include "proj.h" -#include "proj_internal.h" -#include "proj_math.h" -#include "projects.h" - -PROJ_HEAD(ortho, "Orthographic") "\n\tAzi, Sph"; - -namespace { // anonymous namespace -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque { - double sinph0; - double cosph0; - enum Mode mode; -}; -} // anonymous namespace - -#define EPS10 1.e-10 - -static XY forward_error(PJ *P, LP lp, XY xy) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - proj_log_trace(P, "Coordinate (%.3f, %.3f) is on the unprojected hemisphere", - proj_todeg(lp.lam), proj_todeg(lp.phi)); - return xy; -} - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy; - struct pj_opaque *Q = static_cast(P->opaque); - double coslam, cosphi, sinphi; - - xy.x = HUGE_VAL; xy.y = HUGE_VAL; - - cosphi = cos(lp.phi); - coslam = cos(lp.lam); - switch (Q->mode) { - case EQUIT: - if (cosphi * coslam < - EPS10) - return forward_error(P, lp, xy); - xy.y = sin(lp.phi); - break; - case OBLIQ: - if (Q->sinph0 * (sinphi = sin(lp.phi)) + Q->cosph0 * cosphi * coslam < - EPS10) - return forward_error(P, lp, xy); - xy.y = Q->cosph0 * sinphi - Q->sinph0 * cosphi * coslam; - break; - case N_POLE: - coslam = - coslam; - /*-fallthrough*/ - case S_POLE: - if (fabs(lp.phi - P->phi0) - EPS10 > M_HALFPI) - return forward_error(P, lp, xy); - xy.y = cosphi * coslam; - break; - } - xy.x = cosphi * sin(lp.lam); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp; - struct pj_opaque *Q = static_cast(P->opaque); - double rh, cosc, sinc; - - lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; - - if ((sinc = (rh = hypot(xy.x, xy.y))) > 1.) { - if ((sinc - 1.) > EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - proj_log_trace(P, "Point (%.3f, %.3f) is outside the projection boundary"); - return lp; - } - sinc = 1.; - } - cosc = sqrt(1. - sinc * sinc); /* in this range OK */ - if (fabs(rh) <= EPS10) { - lp.phi = P->phi0; - lp.lam = 0.0; - } else { - switch (Q->mode) { - case N_POLE: - xy.y = -xy.y; - lp.phi = acos(sinc); - break; - case S_POLE: - lp.phi = - acos(sinc); - break; - case EQUIT: - lp.phi = xy.y * sinc / rh; - xy.x *= sinc; - xy.y = cosc * rh; - goto sinchk; - case OBLIQ: - lp.phi = cosc * Q->sinph0 + xy.y * sinc * Q->cosph0 /rh; - xy.y = (cosc - Q->sinph0 * lp.phi) * rh; - xy.x *= sinc * Q->cosph0; - sinchk: - if (fabs(lp.phi) >= 1.) - lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; - else - lp.phi = asin(lp.phi); - break; - } - lp.lam = (xy.y == 0. && (Q->mode == OBLIQ || Q->mode == EQUIT)) - ? (xy.x == 0. ? 0. : xy.x < 0. ? -M_HALFPI : M_HALFPI) - : atan2(xy.x, xy.y); - } - return lp; -} - - - -PJ *PROJECTION(ortho) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - if (fabs(fabs(P->phi0) - M_HALFPI) <= EPS10) - Q->mode = P->phi0 < 0. ? S_POLE : N_POLE; - else if (fabs(P->phi0) > EPS10) { - Q->mode = OBLIQ; - Q->sinph0 = sin(P->phi0); - Q->cosph0 = cos(P->phi0); - } else - Q->mode = EQUIT; - P->inv = s_inverse; - P->fwd = s_forward; - P->es = 0.; - - return P; -} - diff --git a/src/PJ_patterson.cpp b/src/PJ_patterson.cpp deleted file mode 100644 index 0d19414e..00000000 --- a/src/PJ_patterson.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2014 Bojan Savric - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * The Patterson Cylindrical projection was designed by Tom Patterson, US National - * Park Service, in 2014, using Flex Projector. The polynomial equations for the - * projection were developed by Bojan Savric, Oregon State University, in - * collaboration with Tom Patterson and Bernhard Jenny, Oregon State University. - * - * Java reference algorithm implemented by Bojan Savric in Java Map Projection - * Library (a Java port of PROJ.4) in the file PattersonProjection.java. - * - * References: - * Java Map Projection Library - * https://github.com/OSUCartography/JMapProjLib - * - * Patterson Cylindrical Projection - * http://shadedrelief.com/patterson/ - * - * Patterson, T., Savric, B., and Jenny, B. (2015). Cartographic Perspectives - * (No.78). Describes the projection design and characteristics, and - * developing the equations. doi:10.14714/CP78.1270 - * https://doi.org/10.14714/CP78.1270 - * - * Port to PROJ.4 by Micah Cochran, 26 March 2016 - */ - -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(patterson, "Patterson Cylindrical") "\n\tCyl"; - -#define K1 1.0148 -#define K2 0.23185 -#define K3 -0.14499 -#define K4 0.02406 -#define C1 K1 -#define C2 (5.0 * K2) -#define C3 (7.0 * K3) -#define C4 (9.0 * K4) -#define EPS11 1.0e-11 -#define MAX_Y 1.790857183 -/* Not sure at all of the appropriate number for MAX_ITER... */ -#define MAX_ITER 100 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double phi2; - (void) P; - - phi2 = lp.phi * lp.phi; - xy.x = lp.lam; - xy.y = lp.phi * (K1 + phi2 * phi2 * (K2 + phi2 * (K3 + K4 * phi2))); - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double yc, tol, y2, f, fder; - int i; - (void) P; - - yc = xy.y; - - /* make sure y is inside valid range */ - if (xy.y > MAX_Y) { - xy.y = MAX_Y; - } else if (xy.y < -MAX_Y) { - xy.y = -MAX_Y; - } - - for (i = MAX_ITER; i ; --i) { /* Newton-Raphson */ - y2 = yc * yc; - f = (yc * (K1 + y2 * y2 * (K2 + y2 * (K3 + K4 * y2)))) - xy.y; - fder = C1 + y2 * y2 * (C2 + y2 * (C3 + C4 * y2)); - yc -= tol = f / fder; - if (fabs(tol) < EPS11) { - break; - } - } - if( i == 0 ) - pj_ctx_set_errno( P->ctx, PJD_ERR_NON_CONVERGENT ); - lp.phi = yc; - - /* longitude */ - lp.lam = xy.x; - - return lp; -} - - -PJ *PROJECTION(patterson) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_poly.cpp b/src/PJ_poly.cpp deleted file mode 100644 index a970fdb1..00000000 --- a/src/PJ_poly.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(poly, "Polyconic (American)") - "\n\tConic, Sph&Ell"; - -namespace { // anonymous namespace -struct pj_opaque { - double ml0; \ - double *en; -}; -} // anonymous namespace - -#define TOL 1e-10 -#define CONV 1e-10 -#define N_ITER 10 -#define I_ITER 20 -#define ITOL 1.e-12 - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double ms, sp, cp; - - if (fabs(lp.phi) <= TOL) { - xy.x = lp.lam; - xy.y = -Q->ml0; - } else { - sp = sin(lp.phi); - ms = fabs(cp = cos(lp.phi)) > TOL ? pj_msfn(sp, cp, P->es) / sp : 0.; - xy.x = ms * sin(lp.lam *= sp); - xy.y = (pj_mlfn(lp.phi, sp, cp, Q->en) - Q->ml0) + ms * (1. - cos(lp.lam)); - } - - return xy; -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double cot, E; - - if (fabs(lp.phi) <= TOL) { - xy.x = lp.lam; - xy.y = Q->ml0; - } else { - cot = 1. / tan(lp.phi); - xy.x = sin(E = lp.lam * sin(lp.phi)) * cot; - xy.y = lp.phi - P->phi0 + cot * (1. - cos(E)); - } - - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - xy.y += Q->ml0; - if (fabs(xy.y) <= TOL) { - lp.lam = xy.x; - lp.phi = 0.; - } else { - double r, c, sp, cp, s2ph, ml, mlb, mlp, dPhi; - int i; - - r = xy.y * xy.y + xy.x * xy.x; - lp.phi = xy.y; - for (i = I_ITER; i ; --i) { - sp = sin(lp.phi); - s2ph = sp * ( cp = cos(lp.phi)); - if (fabs(cp) < ITOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - c = sp * (mlp = sqrt(1. - P->es * sp * sp)) / cp; - ml = pj_mlfn(lp.phi, sp, cp, Q->en); - mlb = ml * ml + r; - mlp = P->one_es / (mlp * mlp * mlp); - lp.phi += ( dPhi = - ( ml + ml + c * mlb - 2. * xy.y * (c * ml + 1.) ) / ( - P->es * s2ph * (mlb - 2. * xy.y * ml) / c + - 2.* (xy.y - ml) * (c * mlp - 1. / s2ph) - mlp - mlp )); - if (fabs(dPhi) <= ITOL) - break; - } - if (!i) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - c = sin(lp.phi); - lp.lam = asin(xy.x * tan(lp.phi) * sqrt(1. - P->es * c * c)) / sin(lp.phi); - } - - return lp; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double B, dphi, tp; - int i; - - if (fabs(xy.y = P->phi0 + xy.y) <= TOL) { - lp.lam = xy.x; - lp.phi = 0.; - } else { - lp.phi = xy.y; - B = xy.x * xy.x + xy.y * xy.y; - i = N_ITER; - do { - tp = tan(lp.phi); - lp.phi -= (dphi = (xy.y * (lp.phi * tp + 1.) - lp.phi - - .5 * ( lp.phi * lp.phi + B) * tp) / - ((lp.phi - xy.y) / tp - 1.)); - } while (fabs(dphi) > CONV && --i); - if (! i) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - lp.lam = asin(xy.x * tan(lp.phi)) / sin(lp.phi); - } - - return lp; -} - - -static PJ *destructor(PJ *P, int errlev) { - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - if (static_cast(P->opaque)->en) - pj_dealloc (static_cast(P->opaque)->en); - - return pj_default_destructor(P, errlev); -} - - -PJ *PROJECTION(poly) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - - P->opaque = Q; - P->destructor = destructor; - - if (P->es != 0.0) { - if (!(Q->en = pj_enfn(P->es))) - return pj_default_destructor (P, ENOMEM); - Q->ml0 = pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en); - P->inv = e_inverse; - P->fwd = e_forward; - } else { - Q->ml0 = -P->phi0; - P->inv = s_inverse; - P->fwd = s_forward; - } - - return P; -} diff --git a/src/PJ_putp2.cpp b/src/PJ_putp2.cpp deleted file mode 100644 index d7a847c8..00000000 --- a/src/PJ_putp2.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(putp2, "Putnins P2") "\n\tPCyl, Sph"; - -#define C_x 1.89490 -#define C_y 1.71848 -#define C_p 0.6141848493043784 -#define EPS 1e-10 -#define NITER 10 -#define PI_DIV_3 1.0471975511965977 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double p, c, s, V; - int i; - (void) P; - - p = C_p * sin(lp.phi); - s = lp.phi * lp.phi; - lp.phi *= 0.615709 + s * ( 0.00909953 + s * 0.0046292 ); - for (i = NITER; i ; --i) { - c = cos(lp.phi); - s = sin(lp.phi); - lp.phi -= V = (lp.phi + s * (c - 1.) - p) / - (1. + c * (c - 1.) - s * s); - if (fabs(V) < EPS) - break; - } - if (!i) - lp.phi = lp.phi < 0 ? - PI_DIV_3 : PI_DIV_3; - xy.x = C_x * lp.lam * (cos(lp.phi) - 0.5); - xy.y = C_y * sin(lp.phi); - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double c; - - lp.phi = aasin(P->ctx,xy.y / C_y); - lp.lam = xy.x / (C_x * ((c = cos(lp.phi)) - 0.5)); - lp.phi = aasin(P->ctx,(lp.phi + sin(lp.phi) * (c - 1.)) / C_p); - - return lp; -} - - -PJ *PROJECTION(putp2) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_putp3.cpp b/src/PJ_putp3.cpp deleted file mode 100644 index 98bb2ff0..00000000 --- a/src/PJ_putp3.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#define PJ_LIB__ -#include -#include "projects.h" - -namespace { // anonymous namespace -struct pj_opaque { - double A; -}; -} // anonymous namespace - -PROJ_HEAD(putp3, "Putnins P3") "\n\tPCyl, Sph"; -PROJ_HEAD(putp3p, "Putnins P3'") "\n\tPCyl, Sph"; - -#define C 0.79788456 -#define RPISQ 0.1013211836 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - - xy.x = C * lp.lam * (1. - static_cast(P->opaque)->A * lp.phi * lp.phi); - xy.y = C * lp.phi; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - - lp.phi = xy.y / C; - lp.lam = xy.x / (C * (1. - static_cast(P->opaque)->A * lp.phi * lp.phi)); - - return lp; -} - - -PJ *PROJECTION(putp3) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->A = 4. * RPISQ; - - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} - -PJ *PROJECTION(putp3p) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->A = 2. * RPISQ; - - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} - diff --git a/src/PJ_putp4p.cpp b/src/PJ_putp4p.cpp deleted file mode 100644 index 608fc76e..00000000 --- a/src/PJ_putp4p.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -namespace { // anonymous namespace -struct pj_opaque { - double C_x, C_y; -}; -} // anonymous namespace - -PROJ_HEAD(putp4p, "Putnins P4'") "\n\tPCyl, Sph"; -PROJ_HEAD(weren, "Werenskiold I") "\n\tPCyl, Sph"; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - lp.phi = aasin(P->ctx,0.883883476 * sin(lp.phi)); - xy.x = Q->C_x * lp.lam * cos(lp.phi); - xy.x /= cos(lp.phi *= 0.333333333333333); - xy.y = Q->C_y * sin(lp.phi); - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - lp.phi = aasin(P->ctx,xy.y / Q->C_y); - lp.lam = xy.x * cos(lp.phi) / Q->C_x; - lp.phi *= 3.; - lp.lam /= cos(lp.phi); - lp.phi = aasin(P->ctx,1.13137085 * sin(lp.phi)); - - return lp; -} - - -PJ *PROJECTION(putp4p) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->C_x = 0.874038744; - Q->C_y = 3.883251825; - - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} - - -PJ *PROJECTION(weren) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->C_x = 1.; - Q->C_y = 4.442882938; - - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_putp5.cpp b/src/PJ_putp5.cpp deleted file mode 100644 index 79e2ad15..00000000 --- a/src/PJ_putp5.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -namespace { // anonymous namespace -struct pj_opaque { - double A, B; -}; -} // anonymous namespace - -PROJ_HEAD(putp5, "Putnins P5") "\n\tPCyl, Sph"; -PROJ_HEAD(putp5p, "Putnins P5'") "\n\tPCyl, Sph"; - -#define C 1.01346 -#define D 1.2158542 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - xy.x = C * lp.lam * (Q->A - Q->B * sqrt(1. + D * lp.phi * lp.phi)); - xy.y = C * lp.phi; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - - lp.phi = xy.y / C; - lp.lam = xy.x / (C * (Q->A - Q->B * sqrt(1. + D * lp.phi * lp.phi))); - - return lp; -} - - - -PJ *PROJECTION(putp5) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->A = 2.; - Q->B = 1.; - - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} - - -PJ *PROJECTION(putp5p) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->A = 1.5; - Q->B = 0.5; - - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_putp6.cpp b/src/PJ_putp6.cpp deleted file mode 100644 index 1186b18b..00000000 --- a/src/PJ_putp6.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -namespace { // anonymous namespace -struct pj_opaque { - double C_x, C_y, A, B, D; -}; -} // anonymous namespace - -PROJ_HEAD(putp6, "Putnins P6") "\n\tPCyl, Sph"; -PROJ_HEAD(putp6p, "Putnins P6'") "\n\tPCyl, Sph"; - -#define EPS 1e-10 -#define NITER 10 -#define CON_POLE 1.732050807568877 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double p, r, V; - int i; - - p = Q->B * sin(lp.phi); - lp.phi *= 1.10265779; - for (i = NITER; i ; --i) { - r = sqrt(1. + lp.phi * lp.phi); - lp.phi -= V = ( (Q->A - r) * lp.phi - log(lp.phi + r) - p ) / - (Q->A - 2. * r); - if (fabs(V) < EPS) - break; - } - if (!i) - lp.phi = p < 0. ? -CON_POLE : CON_POLE; - xy.x = Q->C_x * lp.lam * (Q->D - sqrt(1. + lp.phi * lp.phi)); - xy.y = Q->C_y * lp.phi; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double r; - - lp.phi = xy.y / Q->C_y; - r = sqrt(1. + lp.phi * lp.phi); - lp.lam = xy.x / (Q->C_x * (Q->D - r)); - lp.phi = aasin( P->ctx, ( (Q->A - r) * lp.phi - log(lp.phi + r) ) / Q->B); - - return lp; -} - - -PJ *PROJECTION(putp6) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - Q->C_x = 1.01346; - Q->C_y = 0.91910; - Q->A = 4.; - Q->B = 2.1471437182129378784; - Q->D = 2.; - - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} - - -PJ *PROJECTION(putp6p) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - Q->C_x = 0.44329; - Q->C_y = 0.80404; - Q->A = 6.; - Q->B = 5.61125; - Q->D = 3.; - - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_qsc.cpp b/src/PJ_qsc.cpp deleted file mode 100644 index b50a7c95..00000000 --- a/src/PJ_qsc.cpp +++ /dev/null @@ -1,408 +0,0 @@ -/* - * This implements the Quadrilateralized Spherical Cube (QSC) projection. - * - * Copyright (c) 2011, 2012 Martin Lambers - * - * The QSC projection was introduced in: - * [OL76] - * E.M. O'Neill and R.E. Laubscher, "Extended Studies of a Quadrilateralized - * Spherical Cube Earth Data Base", Naval Environmental Prediction Research - * Facility Tech. Report NEPRF 3-76 (CSC), May 1976. - * - * The preceding shift from an ellipsoid to a sphere, which allows to apply - * this projection to ellipsoids as used in the Ellipsoidal Cube Map model, - * is described in - * [LK12] - * M. Lambers and A. Kolb, "Ellipsoidal Cube Maps for Accurate Rendering of - * Planetary-Scale Terrain Data", Proc. Pacific Graphics (Short Papers), Sep. - * 2012 - * - * You have to choose one of the following projection centers, - * corresponding to the centers of the six cube faces: - * phi0 = 0.0, lam0 = 0.0 ("front" face) - * phi0 = 0.0, lam0 = 90.0 ("right" face) - * phi0 = 0.0, lam0 = 180.0 ("back" face) - * phi0 = 0.0, lam0 = -90.0 ("left" face) - * phi0 = 90.0 ("top" face) - * phi0 = -90.0 ("bottom" face) - * Other projection centers will not work! - * - * In the projection code below, each cube face is handled differently. - * See the computation of the face parameter in the PROJECTION(qsc) function - * and the handling of different face values (FACE_*) in the forward and - * inverse projections. - * - * Furthermore, the projection is originally only defined for theta angles - * between (-1/4 * PI) and (+1/4 * PI) on the current cube face. This area - * of definition is named AREA_0 in the projection code below. The other - * three areas of a cube face are handled by rotation of AREA_0. - */ - -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -/* The six cube faces. */ -namespace { // anonymous namespace -enum Face { - FACE_FRONT = 0, - FACE_RIGHT = 1, - FACE_BACK = 2, - FACE_LEFT = 3, - FACE_TOP = 4, - FACE_BOTTOM = 5 -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque { - enum Face face; - double a_squared; - double b; - double one_minus_f; - double one_minus_f_squared; -}; -} // anonymous namespace -PROJ_HEAD(qsc, "Quadrilateralized Spherical Cube") "\n\tAzi, Sph"; - -#define EPS10 1.e-10 - -/* The four areas on a cube face. AREA_0 is the area of definition, - * the other three areas are counted counterclockwise. */ -namespace { // anonymous namespace -enum Area { - AREA_0 = 0, - AREA_1 = 1, - AREA_2 = 2, - AREA_3 = 3 -}; -} // anonymous namespace - -/* Helper function for forward projection: compute the theta angle - * and determine the area number. */ -static double qsc_fwd_equat_face_theta(double phi, double y, double x, enum Area *area) { - double theta; - if (phi < EPS10) { - *area = AREA_0; - theta = 0.0; - } else { - theta = atan2(y, x); - if (fabs(theta) <= M_FORTPI) { - *area = AREA_0; - } else if (theta > M_FORTPI && theta <= M_HALFPI + M_FORTPI) { - *area = AREA_1; - theta -= M_HALFPI; - } else if (theta > M_HALFPI + M_FORTPI || theta <= -(M_HALFPI + M_FORTPI)) { - *area = AREA_2; - theta = (theta >= 0.0 ? theta - M_PI : theta + M_PI); - } else { - *area = AREA_3; - theta += M_HALFPI; - } - } - return theta; -} - -/* Helper function: shift the longitude. */ -static double qsc_shift_lon_origin(double lon, double offset) { - double slon = lon + offset; - if (slon < -M_PI) { - slon += M_TWOPI; - } else if (slon > +M_PI) { - slon -= M_TWOPI; - } - return slon; -} - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double lat, lon; - double theta, phi; - double t, mu; /* nu; */ - enum Area area; - - /* Convert the geodetic latitude to a geocentric latitude. - * This corresponds to the shift from the ellipsoid to the sphere - * described in [LK12]. */ - if (P->es != 0.0) { - lat = atan(Q->one_minus_f_squared * tan(lp.phi)); - } else { - lat = lp.phi; - } - - /* Convert the input lat, lon into theta, phi as used by QSC. - * This depends on the cube face and the area on it. - * For the top and bottom face, we can compute theta and phi - * directly from phi, lam. For the other faces, we must use - * unit sphere cartesian coordinates as an intermediate step. */ - lon = lp.lam; - if (Q->face == FACE_TOP) { - phi = M_HALFPI - lat; - if (lon >= M_FORTPI && lon <= M_HALFPI + M_FORTPI) { - area = AREA_0; - theta = lon - M_HALFPI; - } else if (lon > M_HALFPI + M_FORTPI || lon <= -(M_HALFPI + M_FORTPI)) { - area = AREA_1; - theta = (lon > 0.0 ? lon - M_PI : lon + M_PI); - } else if (lon > -(M_HALFPI + M_FORTPI) && lon <= -M_FORTPI) { - area = AREA_2; - theta = lon + M_HALFPI; - } else { - area = AREA_3; - theta = lon; - } - } else if (Q->face == FACE_BOTTOM) { - phi = M_HALFPI + lat; - if (lon >= M_FORTPI && lon <= M_HALFPI + M_FORTPI) { - area = AREA_0; - theta = -lon + M_HALFPI; - } else if (lon < M_FORTPI && lon >= -M_FORTPI) { - area = AREA_1; - theta = -lon; - } else if (lon < -M_FORTPI && lon >= -(M_HALFPI + M_FORTPI)) { - area = AREA_2; - theta = -lon - M_HALFPI; - } else { - area = AREA_3; - theta = (lon > 0.0 ? -lon + M_PI : -lon - M_PI); - } - } else { - double q, r, s; - double sinlat, coslat; - double sinlon, coslon; - - if (Q->face == FACE_RIGHT) { - lon = qsc_shift_lon_origin(lon, +M_HALFPI); - } else if (Q->face == FACE_BACK) { - lon = qsc_shift_lon_origin(lon, +M_PI); - } else if (Q->face == FACE_LEFT) { - lon = qsc_shift_lon_origin(lon, -M_HALFPI); - } - sinlat = sin(lat); - coslat = cos(lat); - sinlon = sin(lon); - coslon = cos(lon); - q = coslat * coslon; - r = coslat * sinlon; - s = sinlat; - - if (Q->face == FACE_FRONT) { - phi = acos(q); - theta = qsc_fwd_equat_face_theta(phi, s, r, &area); - } else if (Q->face == FACE_RIGHT) { - phi = acos(r); - theta = qsc_fwd_equat_face_theta(phi, s, -q, &area); - } else if (Q->face == FACE_BACK) { - phi = acos(-q); - theta = qsc_fwd_equat_face_theta(phi, s, -r, &area); - } else if (Q->face == FACE_LEFT) { - phi = acos(-r); - theta = qsc_fwd_equat_face_theta(phi, s, q, &area); - } else { - /* Impossible */ - phi = theta = 0.0; - area = AREA_0; - } - } - - /* Compute mu and nu for the area of definition. - * For mu, see Eq. (3-21) in [OL76], but note the typos: - * compare with Eq. (3-14). For nu, see Eq. (3-38). */ - mu = atan((12.0 / M_PI) * (theta + acos(sin(theta) * cos(M_FORTPI)) - M_HALFPI)); - t = sqrt((1.0 - cos(phi)) / (cos(mu) * cos(mu)) / (1.0 - cos(atan(1.0 / cos(theta))))); - /* nu = atan(t); We don't really need nu, just t, see below. */ - - /* Apply the result to the real area. */ - if (area == AREA_1) { - mu += M_HALFPI; - } else if (area == AREA_2) { - mu += M_PI; - } else if (area == AREA_3) { - mu += M_PI_HALFPI; - } - - /* Now compute x, y from mu and nu */ - /* t = tan(nu); */ - xy.x = t * cos(mu); - xy.y = t * sin(mu); - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double mu, nu, cosmu, tannu; - double tantheta, theta, cosphi, phi; - double t; - int area; - - /* Convert the input x, y to the mu and nu angles as used by QSC. - * This depends on the area of the cube face. */ - nu = atan(sqrt(xy.x * xy.x + xy.y * xy.y)); - mu = atan2(xy.y, xy.x); - if (xy.x >= 0.0 && xy.x >= fabs(xy.y)) { - area = AREA_0; - } else if (xy.y >= 0.0 && xy.y >= fabs(xy.x)) { - area = AREA_1; - mu -= M_HALFPI; - } else if (xy.x < 0.0 && -xy.x >= fabs(xy.y)) { - area = AREA_2; - mu = (mu < 0.0 ? mu + M_PI : mu - M_PI); - } else { - area = AREA_3; - mu += M_HALFPI; - } - - /* Compute phi and theta for the area of definition. - * The inverse projection is not described in the original paper, but some - * good hints can be found here (as of 2011-12-14): - * http://fits.gsfc.nasa.gov/fitsbits/saf.93/saf.9302 - * (search for "Message-Id: <9302181759.AA25477 at fits.cv.nrao.edu>") */ - t = (M_PI / 12.0) * tan(mu); - tantheta = sin(t) / (cos(t) - (1.0 / sqrt(2.0))); - theta = atan(tantheta); - cosmu = cos(mu); - tannu = tan(nu); - cosphi = 1.0 - cosmu * cosmu * tannu * tannu * (1.0 - cos(atan(1.0 / cos(theta)))); - if (cosphi < -1.0) { - cosphi = -1.0; - } else if (cosphi > +1.0) { - cosphi = +1.0; - } - - /* Apply the result to the real area on the cube face. - * For the top and bottom face, we can compute phi and lam directly. - * For the other faces, we must use unit sphere cartesian coordinates - * as an intermediate step. */ - if (Q->face == FACE_TOP) { - phi = acos(cosphi); - lp.phi = M_HALFPI - phi; - if (area == AREA_0) { - lp.lam = theta + M_HALFPI; - } else if (area == AREA_1) { - lp.lam = (theta < 0.0 ? theta + M_PI : theta - M_PI); - } else if (area == AREA_2) { - lp.lam = theta - M_HALFPI; - } else /* area == AREA_3 */ { - lp.lam = theta; - } - } else if (Q->face == FACE_BOTTOM) { - phi = acos(cosphi); - lp.phi = phi - M_HALFPI; - if (area == AREA_0) { - lp.lam = -theta + M_HALFPI; - } else if (area == AREA_1) { - lp.lam = -theta; - } else if (area == AREA_2) { - lp.lam = -theta - M_HALFPI; - } else /* area == AREA_3 */ { - lp.lam = (theta < 0.0 ? -theta - M_PI : -theta + M_PI); - } - } else { - /* Compute phi and lam via cartesian unit sphere coordinates. */ - double q, r, s; - q = cosphi; - t = q * q; - if (t >= 1.0) { - s = 0.0; - } else { - s = sqrt(1.0 - t) * sin(theta); - } - t += s * s; - if (t >= 1.0) { - r = 0.0; - } else { - r = sqrt(1.0 - t); - } - /* Rotate q,r,s into the correct area. */ - if (area == AREA_1) { - t = r; - r = -s; - s = t; - } else if (area == AREA_2) { - r = -r; - s = -s; - } else if (area == AREA_3) { - t = r; - r = s; - s = -t; - } - /* Rotate q,r,s into the correct cube face. */ - if (Q->face == FACE_RIGHT) { - t = q; - q = -r; - r = t; - } else if (Q->face == FACE_BACK) { - q = -q; - r = -r; - } else if (Q->face == FACE_LEFT) { - t = q; - q = r; - r = -t; - } - /* Now compute phi and lam from the unit sphere coordinates. */ - lp.phi = acos(-s) - M_HALFPI; - lp.lam = atan2(r, q); - if (Q->face == FACE_RIGHT) { - lp.lam = qsc_shift_lon_origin(lp.lam, -M_HALFPI); - } else if (Q->face == FACE_BACK) { - lp.lam = qsc_shift_lon_origin(lp.lam, -M_PI); - } else if (Q->face == FACE_LEFT) { - lp.lam = qsc_shift_lon_origin(lp.lam, +M_HALFPI); - } - } - - /* Apply the shift from the sphere to the ellipsoid as described - * in [LK12]. */ - if (P->es != 0.0) { - int invert_sign; - double tanphi, xa; - invert_sign = (lp.phi < 0.0 ? 1 : 0); - tanphi = tan(lp.phi); - xa = Q->b / sqrt(tanphi * tanphi + Q->one_minus_f_squared); - lp.phi = atan(sqrt(P->a * P->a - xa * xa) / (Q->one_minus_f * xa)); - if (invert_sign) { - lp.phi = -lp.phi; - } - } - return lp; -} - - -PJ *PROJECTION(qsc) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - P->inv = e_inverse; - P->fwd = e_forward; - /* Determine the cube face from the center of projection. */ - if (P->phi0 >= M_HALFPI - M_FORTPI / 2.0) { - Q->face = FACE_TOP; - } else if (P->phi0 <= -(M_HALFPI - M_FORTPI / 2.0)) { - Q->face = FACE_BOTTOM; - } else if (fabs(P->lam0) <= M_FORTPI) { - Q->face = FACE_FRONT; - } else if (fabs(P->lam0) <= M_HALFPI + M_FORTPI) { - Q->face = (P->lam0 > 0.0 ? FACE_RIGHT : FACE_LEFT); - } else { - Q->face = FACE_BACK; - } - /* Fill in useful values for the ellipsoid <-> sphere shift - * described in [LK12]. */ - if (P->es != 0.0) { - Q->a_squared = P->a * P->a; - Q->b = P->a * sqrt(1.0 - P->es); - Q->one_minus_f = 1.0 - (P->a - Q->b) / P->a; - Q->one_minus_f_squared = Q->one_minus_f * Q->one_minus_f; - } - - return P; -} diff --git a/src/PJ_robin.cpp b/src/PJ_robin.cpp deleted file mode 100644 index 987977ae..00000000 --- a/src/PJ_robin.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#define PJ_LIB__ -#include "proj_math.h" -#include "proj_internal.h" -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(robin, "Robinson") "\n\tPCyl, Sph"; - -#define V(C,z) (C.c0 + z * (C.c1 + z * (C.c2 + z * C.c3))) -#define DV(C,z) (C.c1 + z * (C.c2 + C.c2 + z * 3. * C.c3)) - -/* -note: following terms based upon 5 deg. intervals in degrees. - -Some background on these coefficients is available at: - -http://article.gmane.org/gmane.comp.gis.proj-4.devel/6039 -http://trac.osgeo.org/proj/ticket/113 -*/ - -namespace { // anonymous namespace -struct COEFS { - float c0, c1, c2, c3; -}; -} // anonymous namespace - -static const struct COEFS X[] = { - {1.0f, 2.2199e-17f, -7.15515e-05f, 3.1103e-06f}, - {0.9986f, -0.000482243f, -2.4897e-05f, -1.3309e-06f}, - {0.9954f, -0.00083103f, -4.48605e-05f, -9.86701e-07f}, - {0.99f, -0.00135364f, -5.9661e-05f, 3.6777e-06f}, - {0.9822f, -0.00167442f, -4.49547e-06f, -5.72411e-06f}, - {0.973f, -0.00214868f, -9.03571e-05f, 1.8736e-08f}, - {0.96f, -0.00305085f, -9.00761e-05f, 1.64917e-06f}, - {0.9427f, -0.00382792f, -6.53386e-05f, -2.6154e-06f}, - {0.9216f, -0.00467746f, -0.00010457f, 4.81243e-06f}, - {0.8962f, -0.00536223f, -3.23831e-05f, -5.43432e-06f}, - {0.8679f, -0.00609363f, -0.000113898f, 3.32484e-06f}, - {0.835f, -0.00698325f, -6.40253e-05f, 9.34959e-07f}, - {0.7986f, -0.00755338f, -5.00009e-05f, 9.35324e-07f}, - {0.7597f, -0.00798324f, -3.5971e-05f, -2.27626e-06f}, - {0.7186f, -0.00851367f, -7.01149e-05f, -8.6303e-06f}, - {0.6732f, -0.00986209f, -0.000199569f, 1.91974e-05f}, - {0.6213f, -0.010418f, 8.83923e-05f, 6.24051e-06f}, - {0.5722f, -0.00906601f, 0.000182f, 6.24051e-06f}, - {0.5322f, -0.00677797f, 0.000275608f, 6.24051e-06f} -}; - -static const struct COEFS Y[] = { - {-5.20417e-18f, 0.0124f, 1.21431e-18f, -8.45284e-11f}, - {0.062f, 0.0124f, -1.26793e-09f, 4.22642e-10f}, - {0.124f, 0.0124f, 5.07171e-09f, -1.60604e-09f}, - {0.186f, 0.0123999f, -1.90189e-08f, 6.00152e-09f}, - {0.248f, 0.0124002f, 7.10039e-08f, -2.24e-08f}, - {0.31f, 0.0123992f, -2.64997e-07f, 8.35986e-08f}, - {0.372f, 0.0124029f, 9.88983e-07f, -3.11994e-07f}, - {0.434f, 0.0123893f, -3.69093e-06f, -4.35621e-07f}, - {0.4958f, 0.0123198f, -1.02252e-05f, -3.45523e-07f}, - {0.5571f, 0.0121916f, -1.54081e-05f, -5.82288e-07f}, - {0.6176f, 0.0119938f, -2.41424e-05f, -5.25327e-07f}, - {0.6769f, 0.011713f, -3.20223e-05f, -5.16405e-07f}, - {0.7346f, 0.0113541f, -3.97684e-05f, -6.09052e-07f}, - {0.7903f, 0.0109107f, -4.89042e-05f, -1.04739e-06f}, - {0.8435f, 0.0103431f, -6.4615e-05f, -1.40374e-09f}, - {0.8936f, 0.00969686f, -6.4636e-05f, -8.547e-06f}, - {0.9394f, 0.00840947f, -0.000192841f, -4.2106e-06f}, - {0.9761f, 0.00616527f, -0.000256f, -4.2106e-06f}, - {1.0f, 0.00328947f, -0.000319159f, -4.2106e-06f} -}; - -#define FXC 0.8487 -#define FYC 1.3523 -#define C1 11.45915590261646417544 -#define RC1 0.08726646259971647884 -#define NODES 18 -#define ONEEPS 1.000001 -#define EPS 1e-8 -/* Not sure at all of the appropriate number for MAX_ITER... */ -#define MAX_ITER 100 - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - long i; - double dphi; - (void) P; - - dphi = fabs(lp.phi); - i = isnan(lp.phi) ? -1 : lround(floor(dphi * C1)); - if( i < 0 ){ - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - if (i >= NODES) i = NODES - 1; - dphi = RAD_TO_DEG * (dphi - RC1 * i); - xy.x = V(X[i], dphi) * FXC * lp.lam; - xy.y = V(Y[i], dphi) * FYC; - if (lp.phi < 0.) xy.y = -xy.y; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - long i; - double t, t1; - struct COEFS T; - int iters; - - lp.lam = xy.x / FXC; - lp.phi = fabs(xy.y / FYC); - if (lp.phi >= 1.) { /* simple pathologic cases */ - if (lp.phi > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - else { - lp.phi = xy.y < 0. ? -M_HALFPI : M_HALFPI; - lp.lam /= X[NODES].c0; - } - } else { /* general problem */ - /* in Y space, reduce to table interval */ - i = isnan(lp.phi) ? -1 : lround(floor(lp.phi * NODES)); - if( i < 0 || i >= NODES ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - for (;;) { - if (Y[i].c0 > lp.phi) --i; - else if (Y[i+1].c0 <= lp.phi) ++i; - else break; - } - T = Y[i]; - /* first guess, linear interp */ - t = 5. * (lp.phi - T.c0)/(Y[i+1].c0 - T.c0); - /* make into root */ - T.c0 = (float)(T.c0 - lp.phi); - for (iters = MAX_ITER; iters ; --iters) { /* Newton-Raphson */ - t -= t1 = V(T,t) / DV(T,t); - if (fabs(t1) < EPS) - break; - } - if( iters == 0 ) - pj_ctx_set_errno( P->ctx, PJD_ERR_NON_CONVERGENT ); - lp.phi = (5 * i + t) * DEG_TO_RAD; - if (xy.y < 0.) lp.phi = -lp.phi; - lp.lam /= V(X[i], t); - } - return lp; -} - - -PJ *PROJECTION(robin) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} - - diff --git a/src/PJ_rpoly.cpp b/src/PJ_rpoly.cpp deleted file mode 100644 index a34f6171..00000000 --- a/src/PJ_rpoly.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -namespace { // anonymous namespace -struct pj_opaque { - double phi1; - double fxa; - double fxb; - int mode; -}; -} // anonymous namespace - -PROJ_HEAD(rpoly, "Rectangular Polyconic") - "\n\tConic, Sph, no inv\n\tlat_ts="; - -#define EPS 1e-9 - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double fa; - - if (Q->mode) - fa = tan(lp.lam * Q->fxb) * Q->fxa; - else - fa = 0.5 * lp.lam; - if (fabs(lp.phi) < EPS) { - xy.x = fa + fa; - xy.y = - P->phi0; - } else { - xy.y = 1. / tan(lp.phi); - xy.x = sin(fa = 2. * atan(fa * sin(lp.phi))) * xy.y; - xy.y = lp.phi - P->phi0 + (1. - cos(fa)) * xy.y; - } - return xy; -} - - - -PJ *PROJECTION(rpoly) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - if ((Q->mode = (Q->phi1 = fabs(pj_param(P->ctx, P->params, "rlat_ts").f)) > EPS)) { - Q->fxb = 0.5 * sin(Q->phi1); - Q->fxa = 0.5 / Q->fxb; - } - P->es = 0.; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_sch.cpp b/src/PJ_sch.cpp deleted file mode 100644 index 5a2f944b..00000000 --- a/src/PJ_sch.cpp +++ /dev/null @@ -1,232 +0,0 @@ -/****************************************************************************** - * Project: SCH Coordinate system - * Purpose: Implementation of SCH Coordinate system - * References : - * 1. Hensley. Scott. SCH Coordinates and various transformations. June 15, 2000. - * 2. Buckley, Sean Monroe. Radar interferometry measurement of land subsidence. 2000.. - * PhD Thesis. UT Austin. (Appendix) - * 3. Hensley, Scott, Elaine Chapin, and T. Michel. "Improved processing of AIRSAR - * data based on the GeoSAR processor." Airsar earth science and applications - * workshop, March. 2002. (http://airsar.jpl.nasa.gov/documents/workshop2002/papers/T3.pdf) - * - * Author: Piyush Agram (piyush.agram@jpl.nasa.gov) - * Copyright (c) 2015 California Institute of Technology. - * Government sponsorship acknowledged. - * - * NOTE: The SCH coordinate system is a sensor aligned coordinate system - * developed at JPL for radar mapping missions. Details pertaining to the - * coordinate system have been release in the public domain (see references above). - * This code is an independent implementation of the SCH coordinate system - * that conforms to the PROJ.4 conventions and uses the details presented in these - * publicly released documents. All credit for the development of the coordinate - * system and its use should be directed towards the original developers at JPL. - ****************************************************************************** - * 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. - ****************************************************************************/ - -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" -#include "geocent.h" - -namespace { // anonymous namespace -struct pj_opaque { - double plat; /*Peg Latitude */ - double plon; /*Peg Longitude*/ - double phdg; /*Peg heading */ - double h0; /*Average altitude */ - double transMat[9]; - double xyzoff[3]; - double rcurv; - GeocentricInfo sph; - GeocentricInfo elp_0; -}; -} // anonymous namespace - -PROJ_HEAD(sch, "Spherical Cross-track Height") "\n\tMisc\n\tplat_0= plon_0= phdg_0= [h_0=]"; - -static LPZ inverse3d(XYZ xyz, PJ *P) { - LPZ lpz = {0.0, 0.0, 0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double temp[3]; - double pxyz[3]; - - /* Local lat,lon using radius */ - pxyz[0] = xyz.y * P->a / Q->rcurv; - pxyz[1] = xyz.x * P->a / Q->rcurv; - pxyz[2] = xyz.z; - - if( pj_Convert_Geodetic_To_Geocentric( &(Q->sph), pxyz[0], pxyz[1], pxyz[2], temp, temp+1, temp+2) != 0) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lpz; - } - - /* Apply rotation */ - pxyz[0] = Q->transMat[0] * temp[0] + Q->transMat[1] * temp[1] + Q->transMat[2] * temp[2]; - pxyz[1] = Q->transMat[3] * temp[0] + Q->transMat[4] * temp[1] + Q->transMat[5] * temp[2]; - pxyz[2] = Q->transMat[6] * temp[0] + Q->transMat[7] * temp[1] + Q->transMat[8] * temp[2]; - - /* Apply offset */ - pxyz[0] += Q->xyzoff[0]; - pxyz[1] += Q->xyzoff[1]; - pxyz[2] += Q->xyzoff[2]; - - /* Convert geocentric coordinates to lat lon */ - pj_Convert_Geocentric_To_Geodetic( &(Q->elp_0), pxyz[0], pxyz[1], pxyz[2], - temp, temp+1, temp+2); - - - lpz.lam = temp[1] ; - lpz.phi = temp[0] ; - lpz.z = temp[2]; - - return lpz; -} - -static XYZ forward3d(LPZ lpz, PJ *P) { - XYZ xyz = {0.0, 0.0, 0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double temp[3]; - double pxyz[3]; - - - /* Convert lat lon to geocentric coordinates */ - if( pj_Convert_Geodetic_To_Geocentric( &(Q->elp_0), lpz.phi, lpz.lam, lpz.z, temp, temp+1, temp+2 ) != 0 ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xyz; - } - - - /* Adjust for offset */ - temp[0] -= Q->xyzoff[0]; - temp[1] -= Q->xyzoff[1]; - temp[2] -= Q->xyzoff[2]; - - - /* Apply rotation */ - pxyz[0] = Q->transMat[0] * temp[0] + Q->transMat[3] * temp[1] + Q->transMat[6] * temp[2]; - pxyz[1] = Q->transMat[1] * temp[0] + Q->transMat[4] * temp[1] + Q->transMat[7] * temp[2]; - pxyz[2] = Q->transMat[2] * temp[0] + Q->transMat[5] * temp[1] + Q->transMat[8] * temp[2]; - - /* Convert to local lat,lon */ - pj_Convert_Geocentric_To_Geodetic( &(Q->sph), pxyz[0], pxyz[1], pxyz[2], - temp, temp+1, temp+2); - - - /* Scale by radius */ - xyz.x = temp[1] * Q->rcurv / P->a; - xyz.y = temp[0] * Q->rcurv / P->a; - xyz.z = temp[2]; - - return xyz; -} - - -static PJ *setup(PJ *P) { /* general initialization */ - struct pj_opaque *Q = static_cast(P->opaque); - double reast, rnorth; - double chdg, shdg; - double clt, slt; - double clo, slo; - double temp; - double pxyz[3]; - - temp = P->a * sqrt(1.0 - P->es); - - /* Setup original geocentric system */ - if ( pj_Set_Geocentric_Parameters(&(Q->elp_0), P->a, temp) != 0) - return pj_default_destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ); - - clt = cos(Q->plat); - slt = sin(Q->plat); - clo = cos(Q->plon); - slo = sin(Q->plon); - - /* Estimate the radius of curvature for given peg */ - temp = sqrt(1.0 - (P->es) * slt * slt); - reast = (P->a)/temp; - rnorth = (P->a) * (1.0 - (P->es))/pow(temp,3); - - chdg = cos(Q->phdg); - shdg = sin(Q->phdg); - - Q->rcurv = Q->h0 + (reast*rnorth)/(reast * chdg * chdg + rnorth * shdg * shdg); - - /* Set up local sphere at the given peg point */ - if ( pj_Set_Geocentric_Parameters(&(Q->sph), Q->rcurv, Q->rcurv) != 0) - return pj_default_destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ); - - /* Set up the transformation matrices */ - Q->transMat[0] = clt * clo; - Q->transMat[1] = -shdg*slo - slt*clo * chdg; - Q->transMat[2] = slo*chdg - slt*clo*shdg; - Q->transMat[3] = clt*slo; - Q->transMat[4] = clo*shdg - slt*slo*chdg; - Q->transMat[5] = -clo*chdg - slt*slo*shdg; - Q->transMat[6] = slt; - Q->transMat[7] = clt*chdg; - Q->transMat[8] = clt*shdg; - - - if( pj_Convert_Geodetic_To_Geocentric( &(Q->elp_0), Q->plat, Q->plon, Q->h0, - pxyz, pxyz+1, pxyz+2 ) != 0 ) - return pj_default_destructor(P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); - - - Q->xyzoff[0] = pxyz[0] - (Q->rcurv) * clt * clo; - Q->xyzoff[1] = pxyz[1] - (Q->rcurv) * clt * slo; - Q->xyzoff[2] = pxyz[2] - (Q->rcurv) * slt; - - P->fwd3d = forward3d; - P->inv3d = inverse3d; - return P; -} - - -PJ *PROJECTION(sch) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - Q->h0 = 0.0; - - /* Check if peg latitude was defined */ - if (pj_param(P->ctx, P->params, "tplat_0").i) - Q->plat = pj_param(P->ctx, P->params, "rplat_0").f; - else { - return pj_default_destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ); - } - - /* Check if peg longitude was defined */ - if (pj_param(P->ctx, P->params, "tplon_0").i) - Q->plon = pj_param(P->ctx, P->params, "rplon_0").f; - else { - return pj_default_destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ); - } - - /* Check if peg latitude is defined */ - if (pj_param(P->ctx, P->params, "tphdg_0").i) - Q->phdg = pj_param(P->ctx, P->params, "rphdg_0").f; - else { - return pj_default_destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ); - } - - - /* Check if average height was defined - If so read it in */ - if (pj_param(P->ctx, P->params, "th_0").i) - Q->h0 = pj_param(P->ctx, P->params, "dh_0").f; - - - return setup(P); -} diff --git a/src/PJ_sconics.cpp b/src/PJ_sconics.cpp deleted file mode 100644 index 1d19a13d..00000000 --- a/src/PJ_sconics.cpp +++ /dev/null @@ -1,220 +0,0 @@ -#define PJ_LIB__ -#include -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - - -namespace { // anonymous namespace -enum Type { - EULER = 0, - MURD1 = 1, - MURD2 = 2, - MURD3 = 3, - PCONIC = 4, - TISSOT = 5, - VITK1 = 6 -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque { - double n; - double rho_c; - double rho_0; - double sig; - double c1, c2; - enum Type type; -}; -} // anonymous namespace - - -#define EPS10 1.e-10 -#define EPS 1e-10 -#define LINE2 "\n\tConic, Sph\n\tlat_1= and lat_2=" - -PROJ_HEAD(euler, "Euler") LINE2; -PROJ_HEAD(murd1, "Murdoch I") LINE2; -PROJ_HEAD(murd2, "Murdoch II") LINE2; -PROJ_HEAD(murd3, "Murdoch III") LINE2; -PROJ_HEAD(pconic, "Perspective Conic") LINE2; -PROJ_HEAD(tissot, "Tissot") LINE2; -PROJ_HEAD(vitk1, "Vitkovsky I") LINE2; - - - -/* get common factors for simple conics */ -static int phi12(PJ *P, double *del) { - double p1, p2; - int err = 0; - - if (!pj_param(P->ctx, P->params, "tlat_1").i || - !pj_param(P->ctx, P->params, "tlat_2").i) { - err = -41; - } else { - p1 = pj_param(P->ctx, P->params, "rlat_1").f; - p2 = pj_param(P->ctx, P->params, "rlat_2").f; - *del = 0.5 * (p2 - p1); - static_cast(P->opaque)->sig = 0.5 * (p2 + p1); - err = (fabs(*del) < EPS || fabs(static_cast(P->opaque)->sig) < EPS) ? PJD_ERR_ABS_LAT1_EQ_ABS_LAT2 : 0; - } - return err; -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double rho; - - switch (Q->type) { - case MURD2: - rho = Q->rho_c + tan (Q->sig - lp.phi); - break; - case PCONIC: - rho = Q->c2 * (Q->c1 - tan (lp.phi - Q->sig)); - break; - default: - rho = Q->rho_c - lp.phi; - break; - } - - xy.x = rho * sin ( lp.lam *= Q->n ); - xy.y = Q->rho_0 - rho * cos (lp.lam); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, (and ellipsoidal?) inverse */ - LP lp = {0.0, 0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double rho; - - rho = hypot (xy.x, xy.y = Q->rho_0 - xy.y); - if (Q->n < 0.) { - rho = - rho; - xy.x = - xy.x; - xy.y = - xy.y; - } - - lp.lam = atan2 (xy.x, xy.y) / Q->n; - - switch (Q->type) { - case PCONIC: - lp.phi = atan (Q->c1 - rho / Q->c2) + Q->sig; - break; - case MURD2: - lp.phi = Q->sig - atan(rho - Q->rho_c); - break; - default: - lp.phi = Q->rho_c - rho; - } - return lp; -} - - -static PJ *setup(PJ *P, enum Type type) { - double del, cs; - int err; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - Q->type = type; - - err = phi12 (P, &del); - if(err) - return pj_default_destructor (P, err); - - switch (Q->type) { - - case TISSOT: - Q->n = sin (Q->sig); - cs = cos (del); - Q->rho_c = Q->n / cs + cs / Q->n; - Q->rho_0 = sqrt ((Q->rho_c - 2 * sin (P->phi0)) / Q->n); - break; - - case MURD1: - Q->rho_c = sin(del)/(del * tan(Q->sig)) + Q->sig; - Q->rho_0 = Q->rho_c - P->phi0; - Q->n = sin(Q->sig); - break; - - case MURD2: - Q->rho_c = (cs = sqrt (cos (del))) / tan (Q->sig); - Q->rho_0 = Q->rho_c + tan (Q->sig - P->phi0); - Q->n = sin (Q->sig) * cs; - break; - - case MURD3: - Q->rho_c = del / (tan(Q->sig) * tan(del)) + Q->sig; - Q->rho_0 = Q->rho_c - P->phi0; - Q->n = sin (Q->sig) * sin (del) * tan (del) / (del * del); - break; - - case EULER: - Q->n = sin (Q->sig) * sin (del) / del; - del *= 0.5; - Q->rho_c = del / (tan (del) * tan (Q->sig)) + Q->sig; - Q->rho_0 = Q->rho_c - P->phi0; - break; - - case PCONIC: - Q->n = sin (Q->sig); - Q->c2 = cos (del); - Q->c1 = 1./tan (Q->sig); - if (fabs (del = P->phi0 - Q->sig) - EPS10 >= M_HALFPI) - return pj_default_destructor(P, PJD_ERR_LAT_0_HALF_PI_FROM_MEAN); - - Q->rho_0 = Q->c2 * (Q->c1 - tan (del)); - break; - - case VITK1: - Q->n = (cs = tan (del)) * sin (Q->sig) / del; - Q->rho_c = del / (cs * tan (Q->sig)) + Q->sig; - Q->rho_0 = Q->rho_c - P->phi0; - break; - } - - P->inv = s_inverse; - P->fwd = s_forward; - P->es = 0; - return (P); -} - - -PJ *PROJECTION(euler) { - return setup(P, EULER); -} - - -PJ *PROJECTION(tissot) { - return setup(P, TISSOT); -} - - -PJ *PROJECTION(murd1) { - return setup(P, MURD1); -} - - -PJ *PROJECTION(murd2) { - return setup(P, MURD2); -} - - -PJ *PROJECTION(murd3) { - return setup(P, MURD3); -} - - -PJ *PROJECTION(pconic) { - return setup(P, PCONIC); -} - - -PJ *PROJECTION(vitk1) { - return setup(P, VITK1); -} - diff --git a/src/PJ_somerc.cpp b/src/PJ_somerc.cpp deleted file mode 100644 index 15d2e765..00000000 --- a/src/PJ_somerc.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(somerc, "Swiss. Obl. Mercator") "\n\tCyl, Ell\n\tFor CH1903"; - -namespace { // anonymous namespace -struct pj_opaque { - double K, c, hlf_e, kR, cosp0, sinp0; -}; -} // anonymous namespace - -#define EPS 1.e-10 -#define NITER 6 - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0, 0.0}; - double phip, lamp, phipp, lampp, sp, cp; - struct pj_opaque *Q = static_cast(P->opaque); - - sp = P->e * sin (lp.phi); - phip = 2.* atan ( exp ( Q->c * ( - log (tan (M_FORTPI + 0.5 * lp.phi)) - Q->hlf_e * log ((1. + sp)/(1. - sp))) - + Q->K)) - M_HALFPI; - lamp = Q->c * lp.lam; - cp = cos(phip); - phipp = aasin (P->ctx, Q->cosp0 * sin (phip) - Q->sinp0 * cp * cos (lamp)); - lampp = aasin (P->ctx, cp * sin (lamp) / cos (phipp)); - xy.x = Q->kR * lampp; - xy.y = Q->kR * log (tan (M_FORTPI + 0.5 * phipp)); - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double phip, lamp, phipp, lampp, cp, esp, con, delp; - int i; - - phipp = 2. * (atan (exp (xy.y / Q->kR)) - M_FORTPI); - lampp = xy.x / Q->kR; - cp = cos (phipp); - phip = aasin (P->ctx, Q->cosp0 * sin (phipp) + Q->sinp0 * cp * cos (lampp)); - lamp = aasin (P->ctx, cp * sin (lampp) / cos (phip)); - con = (Q->K - log (tan (M_FORTPI + 0.5 * phip)))/Q->c; - for (i = NITER; i ; --i) { - esp = P->e * sin(phip); - delp = (con + log(tan(M_FORTPI + 0.5 * phip)) - Q->hlf_e * - log((1. + esp)/(1. - esp)) ) * - (1. - esp * esp) * cos(phip) * P->rone_es; - phip -= delp; - if (fabs(delp) < EPS) - break; - } - if (i) { - lp.phi = phip; - lp.lam = lamp / Q->c; - } else { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - return (lp); -} - - -PJ *PROJECTION(somerc) { - double cp, phip0, sp; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - - Q->hlf_e = 0.5 * P->e; - cp = cos (P->phi0); - cp *= cp; - Q->c = sqrt (1 + P->es * cp * cp * P->rone_es); - sp = sin (P->phi0); - Q->cosp0 = cos( phip0 = aasin (P->ctx, Q->sinp0 = sp / Q->c) ); - sp *= P->e; - Q->K = log (tan (M_FORTPI + 0.5 * phip0)) - Q->c * ( - log (tan (M_FORTPI + 0.5 * P->phi0)) - Q->hlf_e * - log ((1. + sp) / (1. - sp))); - Q->kR = P->k0 * sqrt(P->one_es) / (1. - sp * sp); - P->inv = e_inverse; - P->fwd = e_forward; - return P; -} diff --git a/src/PJ_stere.cpp b/src/PJ_stere.cpp deleted file mode 100644 index 1502b2a6..00000000 --- a/src/PJ_stere.cpp +++ /dev/null @@ -1,320 +0,0 @@ -#define PJ_LIB__ -#include -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -PROJ_HEAD(stere, "Stereographic") "\n\tAzi, Sph&Ell\n\tlat_ts="; -PROJ_HEAD(ups, "Universal Polar Stereographic") "\n\tAzi, Sph&Ell\n\tsouth"; - - -namespace { // anonymous namespace -enum Mode { - S_POLE = 0, - N_POLE = 1, - OBLIQ = 2, - EQUIT = 3 -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque { - double phits; - double sinX1; - double cosX1; - double akm1; - enum Mode mode; -}; -} // anonymous namespace - -#define sinph0 static_cast(P->opaque)->sinX1 -#define cosph0 static_cast(P->opaque)->cosX1 -#define EPS10 1.e-10 -#define TOL 1.e-8 -#define NITER 8 -#define CONV 1.e-10 - -static double ssfn_ (double phit, double sinphi, double eccen) { - sinphi *= eccen; - return (tan (.5 * (M_HALFPI + phit)) * - pow ((1. - sinphi) / (1. + sinphi), .5 * eccen)); -} - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double coslam, sinlam, sinX = 0.0, cosX = 0.0, X, A = 0.0, sinphi; - - coslam = cos (lp.lam); - sinlam = sin (lp.lam); - sinphi = sin (lp.phi); - if (Q->mode == OBLIQ || Q->mode == EQUIT) { - sinX = sin (X = 2. * atan(ssfn_(lp.phi, sinphi, P->e)) - M_HALFPI); - cosX = cos (X); - } - - switch (Q->mode) { - case OBLIQ: - A = Q->akm1 / (Q->cosX1 * (1. + Q->sinX1 * sinX + - Q->cosX1 * cosX * coslam)); - xy.y = A * (Q->cosX1 * sinX - Q->sinX1 * cosX * coslam); - goto xmul; /* but why not just xy.x = A * cosX; break; ? */ - - case EQUIT: - /* avoid zero division */ - if (1. + cosX * coslam == 0.0) { - xy.y = HUGE_VAL; - } else { - A = Q->akm1 / (1. + cosX * coslam); - xy.y = A * sinX; - } -xmul: - xy.x = A * cosX; - break; - - case S_POLE: - lp.phi = -lp.phi; - coslam = - coslam; - sinphi = -sinphi; - /*-fallthrough*/ - case N_POLE: - xy.x = Q->akm1 * pj_tsfn (lp.phi, sinphi, P->e); - xy.y = - xy.x * coslam; - break; - } - - xy.x = xy.x * sinlam; - return xy; -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double sinphi, cosphi, coslam, sinlam; - - sinphi = sin(lp.phi); - cosphi = cos(lp.phi); - coslam = cos(lp.lam); - sinlam = sin(lp.lam); - - switch (Q->mode) { - case EQUIT: - xy.y = 1. + cosphi * coslam; - goto oblcon; - case OBLIQ: - xy.y = 1. + sinph0 * sinphi + cosph0 * cosphi * coslam; -oblcon: - if (xy.y <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - xy.x = (xy.y = Q->akm1 / xy.y) * cosphi * sinlam; - xy.y *= (Q->mode == EQUIT) ? sinphi : - cosph0 * sinphi - sinph0 * cosphi * coslam; - break; - case N_POLE: - coslam = - coslam; - lp.phi = - lp.phi; - /*-fallthrough*/ - case S_POLE: - if (fabs (lp.phi - M_HALFPI) < TOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - xy.x = sinlam * ( xy.y = Q->akm1 * tan (M_FORTPI + .5 * lp.phi) ); - xy.y *= coslam; - break; - } - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double cosphi, sinphi, tp=0.0, phi_l=0.0, rho, halfe=0.0, halfpi=0.0; - int i; - - rho = hypot (xy.x, xy.y); - - switch (Q->mode) { - case OBLIQ: - case EQUIT: - cosphi = cos ( tp = 2. * atan2 (rho * Q->cosX1 , Q->akm1) ); - sinphi = sin (tp); - if ( rho == 0.0 ) - phi_l = asin (cosphi * Q->sinX1); - else - phi_l = asin (cosphi * Q->sinX1 + (xy.y * sinphi * Q->cosX1 / rho)); - - tp = tan (.5 * (M_HALFPI + phi_l)); - xy.x *= sinphi; - xy.y = rho * Q->cosX1 * cosphi - xy.y * Q->sinX1* sinphi; - halfpi = M_HALFPI; - halfe = .5 * P->e; - break; - case N_POLE: - xy.y = -xy.y; - /*-fallthrough*/ - case S_POLE: - phi_l = M_HALFPI - 2. * atan (tp = - rho / Q->akm1); - halfpi = -M_HALFPI; - halfe = -.5 * P->e; - break; - } - - for (i = NITER; i--; phi_l = lp.phi) { - sinphi = P->e * sin(phi_l); - lp.phi = 2. * atan (tp * pow ((1.+sinphi)/(1.-sinphi), halfe)) - halfpi; - if (fabs (phi_l - lp.phi) < CONV) { - if (Q->mode == S_POLE) - lp.phi = -lp.phi; - lp.lam = (xy.x == 0. && xy.y == 0.) ? 0. : atan2 (xy.x, xy.y); - return lp; - } - } - - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double c, rh, sinc, cosc; - - sinc = sin (c = 2. * atan ((rh = hypot (xy.x, xy.y)) / Q->akm1)); - cosc = cos (c); - lp.lam = 0.; - - switch (Q->mode) { - case EQUIT: - if (fabs (rh) <= EPS10) - lp.phi = 0.; - else - lp.phi = asin (xy.y * sinc / rh); - if (cosc != 0. || xy.x != 0.) - lp.lam = atan2 (xy.x * sinc, cosc * rh); - break; - case OBLIQ: - if (fabs (rh) <= EPS10) - lp.phi = P->phi0; - else - lp.phi = asin (cosc * sinph0 + xy.y * sinc * cosph0 / rh); - if ((c = cosc - sinph0 * sin (lp.phi)) != 0. || xy.x != 0.) - lp.lam = atan2 (xy.x * sinc * cosph0, c * rh); - break; - case N_POLE: - xy.y = -xy.y; - /*-fallthrough*/ - case S_POLE: - if (fabs (rh) <= EPS10) - lp.phi = P->phi0; - else - lp.phi = asin (Q->mode == S_POLE ? - cosc : cosc); - lp.lam = (xy.x == 0. && xy.y == 0.) ? 0. : atan2 (xy.x, xy.y); - break; - } - return lp; -} - - -static PJ *setup(PJ *P) { /* general initialization */ - double t; - struct pj_opaque *Q = static_cast(P->opaque); - - if (fabs ((t = fabs (P->phi0)) - M_HALFPI) < EPS10) - Q->mode = P->phi0 < 0. ? S_POLE : N_POLE; - else - Q->mode = t > EPS10 ? OBLIQ : EQUIT; - Q->phits = fabs (Q->phits); - - if (P->es != 0.0) { - double X; - - switch (Q->mode) { - case N_POLE: - case S_POLE: - if (fabs (Q->phits - M_HALFPI) < EPS10) - Q->akm1 = 2. * P->k0 / - sqrt (pow (1+P->e,1+P->e) * pow (1-P->e,1-P->e)); - else { - Q->akm1 = cos (Q->phits) / - pj_tsfn (Q->phits, t = sin (Q->phits), P->e); - t *= P->e; - Q->akm1 /= sqrt(1. - t * t); - } - break; - case EQUIT: - case OBLIQ: - t = sin (P->phi0); - X = 2. * atan (ssfn_(P->phi0, t, P->e)) - M_HALFPI; - t *= P->e; - Q->akm1 = 2. * P->k0 * cos (P->phi0) / sqrt(1. - t * t); - Q->sinX1 = sin (X); - Q->cosX1 = cos (X); - break; - } - P->inv = e_inverse; - P->fwd = e_forward; - } else { - switch (Q->mode) { - case OBLIQ: - sinph0 = sin (P->phi0); - cosph0 = cos (P->phi0); - /*-fallthrough*/ - case EQUIT: - Q->akm1 = 2. * P->k0; - break; - case S_POLE: - case N_POLE: - Q->akm1 = fabs (Q->phits - M_HALFPI) >= EPS10 ? - cos (Q->phits) / tan (M_FORTPI - .5 * Q->phits) : - 2. * P->k0 ; - break; - } - - P->inv = s_inverse; - P->fwd = s_forward; - } - return P; -} - - -PJ *PROJECTION(stere) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->phits = pj_param (P->ctx, P->params, "tlat_ts").i ? - pj_param (P->ctx, P->params, "rlat_ts").f : M_HALFPI; - - return setup(P); -} - - -PJ *PROJECTION(ups) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - /* International Ellipsoid */ - P->phi0 = pj_param(P->ctx, P->params, "bsouth").i ? - M_HALFPI: M_HALFPI; - if (P->es == 0.0) { - proj_errno_set(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); - return pj_default_destructor (P, ENOMEM); - } - P->k0 = .994; - P->x0 = 2000000.; - P->y0 = 2000000.; - Q->phits = M_HALFPI; - P->lam0 = 0.; - - return setup(P); -} - diff --git a/src/PJ_sterea.cpp b/src/PJ_sterea.cpp deleted file mode 100644 index bb498068..00000000 --- a/src/PJ_sterea.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* -** libproj -- library of cartographic projections -** -** Copyright (c) 2003 Gerald I. Evenden -*/ -/* -** 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. -*/ -#define PJ_LIB__ -#include -#include "projects.h" -#include "proj_math.h" - - -namespace { // anonymous namespace -struct pj_opaque { - double phic0; - double cosc0, sinc0; - double R2; - void *en; -}; -} // anonymous namespace - - -PROJ_HEAD(sterea, "Oblique Stereographic Alternative") "\n\tAzimuthal, Sph&Ell"; - - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double cosc, sinc, cosl, k; - - lp = pj_gauss(P->ctx, lp, Q->en); - sinc = sin(lp.phi); - cosc = cos(lp.phi); - cosl = cos(lp.lam); - k = P->k0 * Q->R2 / (1. + Q->sinc0 * sinc + Q->cosc0 * cosc * cosl); - xy.x = k * cosc * sin(lp.lam); - xy.y = k * (Q->cosc0 * sinc - Q->sinc0 * cosc * cosl); - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double rho, c, sinc, cosc; - - xy.x /= P->k0; - xy.y /= P->k0; - if ( (rho = hypot (xy.x, xy.y)) != 0.0 ) { - c = 2. * atan2 (rho, Q->R2); - sinc = sin (c); - cosc = cos (c); - lp.phi = asin (cosc * Q->sinc0 + xy.y * sinc * Q->cosc0 / rho); - lp.lam = atan2 (xy.x * sinc, rho * Q->cosc0 * cosc - xy.y * Q->sinc0 * sinc); - } else { - lp.phi = Q->phic0; - lp.lam = 0.; - } - return pj_inv_gauss(P->ctx, lp, Q->en); -} - - -static PJ *destructor (PJ *P, int errlev) { - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (static_cast(P->opaque)->en); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(sterea) { - double R; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->en = pj_gauss_ini(P->e, P->phi0, &(Q->phic0), &R); - if (nullptr==Q->en) - return pj_default_destructor (P, ENOMEM); - - Q->sinc0 = sin (Q->phic0); - Q->cosc0 = cos (Q->phic0); - Q->R2 = 2. * R; - - P->inv = e_inverse; - P->fwd = e_forward; - P->destructor = destructor; - - return P; -} - diff --git a/src/PJ_sts.cpp b/src/PJ_sts.cpp deleted file mode 100644 index 9f889611..00000000 --- a/src/PJ_sts.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(kav5, "Kavraisky V") "\n\tPCyl, Sph"; -PROJ_HEAD(qua_aut, "Quartic Authalic") "\n\tPCyl, Sph"; -PROJ_HEAD(fouc, "Foucaut") "\n\tPCyl, Sph"; -PROJ_HEAD(mbt_s, "McBryde-Thomas Flat-Polar Sine (No. 1)") "\n\tPCyl, Sph"; - - -namespace { // anonymous namespace -struct pj_opaque { - double C_x, C_y, C_p; - int tan_mode; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double c; - - xy.x = Q->C_x * lp.lam * cos(lp.phi); - xy.y = Q->C_y; - lp.phi *= Q->C_p; - c = cos(lp.phi); - if (Q->tan_mode) { - xy.x *= c * c; - xy.y *= tan (lp.phi); - } else { - xy.x /= c; - xy.y *= sin (lp.phi); - } - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double c; - - xy.y /= Q->C_y; - c = cos (lp.phi = Q->tan_mode ? atan (xy.y) : aasin (P->ctx, xy.y)); - lp.phi /= Q->C_p; - lp.lam = xy.x / (Q->C_x * cos(lp.phi)); - if (Q->tan_mode) - lp.lam /= c * c; - else - lp.lam *= c; - return lp; -} - - -static PJ *setup(PJ *P, double p, double q, int mode) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - static_cast(P->opaque)->C_x = q / p; - static_cast(P->opaque)->C_y = p; - static_cast(P->opaque)->C_p = 1/ q; - static_cast(P->opaque)->tan_mode = mode; - return P; -} - - - -PJ *PROJECTION(fouc) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - return setup(P, 2., 2., 1); -} - - - -PJ *PROJECTION(kav5) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - return setup(P, 1.50488, 1.35439, 0); -} - - - -PJ *PROJECTION(qua_aut) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - return setup(P, 2., 2., 0); -} - - - -PJ *PROJECTION(mbt_s) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - return setup(P, 1.48875, 1.36509, 0); -} diff --git a/src/PJ_tcc.cpp b/src/PJ_tcc.cpp deleted file mode 100644 index 64fdc182..00000000 --- a/src/PJ_tcc.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(tcc, "Transverse Central Cylindrical") "\n\tCyl, Sph, no inv"; - -#define EPS10 1.e-10 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - double b, bt; - - b = cos (lp.phi) * sin (lp.lam); - if ((bt = 1. - b * b) < EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - xy.x = b / sqrt(bt); - xy.y = atan2 (tan (lp.phi) , cos (lp.lam)); - return xy; -} - - -PJ *PROJECTION(tcc) { - P->es = 0.; - P->fwd = s_forward; - P->inv = nullptr; - - return P; -} diff --git a/src/PJ_tcea.cpp b/src/PJ_tcea.cpp deleted file mode 100644 index d30f3df0..00000000 --- a/src/PJ_tcea.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(tcea, "Transverse Cylindrical Equal Area") "\n\tCyl, Sph"; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - xy.x = cos (lp.phi) * sin (lp.lam) / P->k0; - xy.y = P->k0 * (atan2 (tan (lp.phi), cos (lp.lam)) - P->phi0); - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0, 0.0}; - double t; - - xy.y = xy.y / P->k0 + P->phi0; - xy.x *= P->k0; - t = sqrt (1. - xy.x * xy.x); - lp.phi = asin (t * sin (xy.y)); - lp.lam = atan2 (xy.x, t * cos (xy.y)); - return lp; -} - - -PJ *PROJECTION(tcea) { - P->inv = s_inverse; - P->fwd = s_forward; - P->es = 0.; - return P; -} diff --git a/src/PJ_times.cpp b/src/PJ_times.cpp deleted file mode 100644 index e8b4499f..00000000 --- a/src/PJ_times.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of the Times projection. - * Author: Kristian Evers - * - ****************************************************************************** - * Copyright (c) 2016, Kristian Evers - * - * 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. - ***************************************************************************** - * Based on describtion of the Times Projection in - * - * Flattening the Earth, Snyder, J.P., 1993, p.213-214. - *****************************************************************************/ - -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(times, "Times") "\n\tCyl, Sph"; - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - double T, S, S2; - XY xy = {0.0,0.0}; - (void) P; - - T = tan(lp.phi/2.0); - S = sin(M_FORTPI * T); - S2 = S*S; - - xy.x = lp.lam * (0.74482 - 0.34588*S2); - xy.y = 1.70711 * T; - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - double T, S, S2; - LP lp = {0.0,0.0}; - (void) P; - - T = xy.y / 1.70711; - S = sin(M_FORTPI * T); - S2 = S*S; - - lp.lam = xy.x / (0.74482 - 0.34588 * S2); - lp.phi = 2 * atan(T); - - return lp; -} - - -PJ *PROJECTION(times) { - P->es = 0.0; - - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_tmerc.cpp b/src/PJ_tmerc.cpp deleted file mode 100644 index 5a2dacbd..00000000 --- a/src/PJ_tmerc.cpp +++ /dev/null @@ -1,210 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(tmerc, "Transverse Mercator") "\n\tCyl, Sph&Ell"; - - -namespace { // anonymous namespace -struct pj_opaque { - double esp; - double ml0; - double *en; -}; -} // anonymous namespace - -#define EPS10 1.e-10 -#define FC1 1. -#define FC2 .5 -#define FC3 .16666666666666666666 -#define FC4 .08333333333333333333 -#define FC5 .05 -#define FC6 .03333333333333333333 -#define FC7 .02380952380952380952 -#define FC8 .01785714285714285714 - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0, 0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double al, als, n, cosphi, sinphi, t; - - /* - * Fail if our longitude is more than 90 degrees from the - * central meridian since the results are essentially garbage. - * Is error -20 really an appropriate return value? - * - * http://trac.osgeo.org/proj/ticket/5 - */ - if( lp.lam < -M_HALFPI || lp.lam > M_HALFPI ) { - xy.x = HUGE_VAL; - xy.y = HUGE_VAL; - pj_ctx_set_errno( P->ctx, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT ); - return xy; - } - - sinphi = sin (lp.phi); - cosphi = cos (lp.phi); - t = fabs (cosphi) > 1e-10 ? sinphi/cosphi : 0.; - t *= t; - al = cosphi * lp.lam; - als = al * al; - al /= sqrt (1. - P->es * sinphi * sinphi); - n = Q->esp * cosphi * cosphi; - xy.x = P->k0 * al * (FC1 + - FC3 * als * (1. - t + n + - FC5 * als * (5. + t * (t - 18.) + n * (14. - 58. * t) - + FC7 * als * (61. + t * ( t * (179. - t) - 479. ) ) - ))); - xy.y = P->k0 * (pj_mlfn(lp.phi, sinphi, cosphi, Q->en) - Q->ml0 + - sinphi * al * lp.lam * FC2 * ( 1. + - FC4 * als * (5. - t + n * (9. + 4. * n) + - FC6 * als * (61. + t * (t - 58.) + n * (270. - 330 * t) - + FC8 * als * (1385. + t * ( t * (543. - t) - 3111.) ) - )))); - return (xy); -} - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double b, cosphi; - - /* - * Fail if our longitude is more than 90 degrees from the - * central meridian since the results are essentially garbage. - * Is error -20 really an appropriate return value? - * - * http://trac.osgeo.org/proj/ticket/5 - */ - if( lp.lam < -M_HALFPI || lp.lam > M_HALFPI ) { - xy.x = HUGE_VAL; - xy.y = HUGE_VAL; - pj_ctx_set_errno( P->ctx, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT ); - return xy; - } - - cosphi = cos(lp.phi); - b = cosphi * sin (lp.lam); - if (fabs (fabs (b) - 1.) <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - - xy.x = static_cast(P->opaque)->ml0 * log ((1. + b) / (1. - b)); - xy.y = cosphi * cos (lp.lam) / sqrt (1. - b * b); - - b = fabs ( xy.y ); - if (b >= 1.) { - if ((b - 1.) > EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - else xy.y = 0.; - } else - xy.y = acos (xy.y); - - if (lp.phi < 0.) - xy.y = -xy.y; - xy.y = static_cast(P->opaque)->esp * (xy.y - P->phi0); - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double n, con, cosphi, d, ds, sinphi, t; - - lp.phi = pj_inv_mlfn(P->ctx, Q->ml0 + xy.y / P->k0, P->es, Q->en); - if (fabs(lp.phi) >= M_HALFPI) { - lp.phi = xy.y < 0. ? -M_HALFPI : M_HALFPI; - lp.lam = 0.; - } else { - sinphi = sin(lp.phi); - cosphi = cos(lp.phi); - t = fabs (cosphi) > 1e-10 ? sinphi/cosphi : 0.; - n = Q->esp * cosphi * cosphi; - d = xy.x * sqrt (con = 1. - P->es * sinphi * sinphi) / P->k0; - con *= t; - t *= t; - ds = d * d; - lp.phi -= (con * ds / (1.-P->es)) * FC2 * (1. - - ds * FC4 * (5. + t * (3. - 9. * n) + n * (1. - 4 * n) - - ds * FC6 * (61. + t * (90. - 252. * n + - 45. * t) + 46. * n - - ds * FC8 * (1385. + t * (3633. + t * (4095. + 1575. * t)) ) - ))); - lp.lam = d*(FC1 - - ds*FC3*( 1. + 2.*t + n - - ds*FC5*(5. + t*(28. + 24.*t + 8.*n) + 6.*n - - ds * FC7 * (61. + t * (662. + t * (1320. + 720. * t)) ) - ))) / cosphi; - } - return lp; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0, 0.0}; - double h, g; - - h = exp(xy.x / static_cast(P->opaque)->esp); - g = .5 * (h - 1. / h); - h = cos (P->phi0 + xy.y / static_cast(P->opaque)->esp); - 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.lam = (g != 0.0 || h != 0.0) ? atan2 (g, h) : 0.; - return lp; -} - - -static PJ *destructor(PJ *P, int errlev) { /* Destructor */ - if (nullptr==P) - return nullptr; - - if (nullptr==P->opaque) - return pj_default_destructor(P, errlev); - - pj_dealloc (static_cast(P->opaque)->en); - return pj_default_destructor(P, errlev); -} - - -static PJ *setup(PJ *P) { /* general initialization */ - struct pj_opaque *Q = static_cast(P->opaque); - if (P->es != 0.0) { - if (!(Q->en = pj_enfn(P->es))) - return pj_default_destructor(P, ENOMEM); - - Q->ml0 = pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en); - Q->esp = P->es / (1. - P->es); - P->inv = e_inverse; - P->fwd = e_forward; - } else { - Q->esp = P->k0; - Q->ml0 = .5 * Q->esp; - P->inv = s_inverse; - P->fwd = s_forward; - } - return P; -} - - -PJ *PROJECTION(tmerc) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - - P->opaque = Q; - P->destructor = destructor; - - return setup(P); -} diff --git a/src/PJ_tobmerc.cpp b/src/PJ_tobmerc.cpp deleted file mode 100644 index 9c939f0b..00000000 --- a/src/PJ_tobmerc.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj_internal.h" -#include "proj.h" -#include "proj_math.h" -#include "projects.h" - -PROJ_HEAD(tobmerc, "Tobler-Mercator") "\n\tCyl, Sph"; - -#define EPS10 1.e-10 -static double logtanpfpim1(double x) { /* log(tan(x/2 + M_FORTPI)) */ - if (fabs(x) <= DBL_EPSILON) { - /* tan(M_FORTPI + .5 * x) can be approximated by 1.0 + x */ - return log1p(x); - } - return log(tan(M_FORTPI + .5 * x)); -} - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - double cosphi; - - if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - - cosphi = cos(lp.phi); - xy.x = P->k0 * lp.lam * cosphi * cosphi; - xy.y = P->k0 * logtanpfpim1(lp.phi); - return xy; -} - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0, 0.0}; - double cosphi; - - lp.phi = atan(sinh(xy.y / P->k0)); - cosphi = cos(lp.phi); - lp.lam = xy.x / P->k0 / (cosphi * cosphi); - return lp; -} - -PJ *PROJECTION(tobmerc) { - P->inv = s_inverse; - P->fwd = s_forward; - return P; -} diff --git a/src/PJ_tpeqd.cpp b/src/PJ_tpeqd.cpp deleted file mode 100644 index 2720327a..00000000 --- a/src/PJ_tpeqd.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#define PJ_LIB__ -#include -#include "proj.h" -#include "proj_math.h" -#include "projects.h" - - -PROJ_HEAD(tpeqd, "Two Point Equidistant") - "\n\tMisc Sph\n\tlat_1= lon_1= lat_2= lon_2="; - -namespace { // anonymous namespace -struct pj_opaque { - double cp1, sp1, cp2, sp2, ccs, cs, sc, r2z0, z02, dlam2; - double hz0, thz0, rhshz0, ca, sa, lp, lamc; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double t, z1, z2, dl1, dl2, sp, cp; - - sp = sin(lp.phi); - cp = cos(lp.phi); - z1 = aacos(P->ctx, Q->sp1 * sp + Q->cp1 * cp * cos (dl1 = lp.lam + Q->dlam2)); - z2 = aacos(P->ctx, Q->sp2 * sp + Q->cp2 * cp * cos (dl2 = lp.lam - Q->dlam2)); - z1 *= z1; - z2 *= z2; - - xy.x = Q->r2z0 * (t = z1 - z2); - t = Q->z02 - t; - xy.y = Q->r2z0 * asqrt (4. * Q->z02 * z2 - t * t); - if ((Q->ccs * sp - cp * (Q->cs * sin(dl1) - Q->sc * sin(dl2))) < 0.) - xy.y = -xy.y; - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double cz1, cz2, s, d, cp, sp; - - cz1 = cos (hypot(xy.y, xy.x + Q->hz0)); - cz2 = cos (hypot(xy.y, xy.x - Q->hz0)); - s = cz1 + cz2; - d = cz1 - cz2; - lp.lam = - atan2(d, (s * Q->thz0)); - lp.phi = aacos(P->ctx, hypot (Q->thz0 * s, d) * Q->rhshz0); - if ( xy.y < 0. ) - lp.phi = - lp.phi; - /* lam--phi now in system relative to P1--P2 base equator */ - sp = sin (lp.phi); - cp = cos (lp.phi); - lp.phi = aasin (P->ctx, Q->sa * sp + Q->ca * cp * (s = cos(lp.lam -= Q->lp))); - lp.lam = atan2 (cp * sin(lp.lam), Q->sa * cp * s - Q->ca * sp) + Q->lamc; - return lp; -} - - -PJ *PROJECTION(tpeqd) { - double lam_1, lam_2, phi_1, phi_2, A12, pp; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - - /* get control point locations */ - phi_1 = pj_param(P->ctx, P->params, "rlat_1").f; - lam_1 = pj_param(P->ctx, P->params, "rlon_1").f; - phi_2 = pj_param(P->ctx, P->params, "rlat_2").f; - lam_2 = pj_param(P->ctx, P->params, "rlon_2").f; - - if (phi_1 == phi_2 && lam_1 == lam_2) - return pj_default_destructor(P, PJD_ERR_CONTROL_POINT_NO_DIST); - - P->lam0 = adjlon (0.5 * (lam_1 + lam_2)); - Q->dlam2 = adjlon (lam_2 - lam_1); - - Q->cp1 = cos (phi_1); - Q->cp2 = cos (phi_2); - Q->sp1 = sin (phi_1); - Q->sp2 = sin (phi_2); - Q->cs = Q->cp1 * Q->sp2; - Q->sc = Q->sp1 * Q->cp2; - Q->ccs = Q->cp1 * Q->cp2 * sin(Q->dlam2); - Q->z02 = aacos(P->ctx, Q->sp1 * Q->sp2 + Q->cp1 * Q->cp2 * cos (Q->dlam2)); - Q->hz0 = .5 * Q->z02; - A12 = atan2(Q->cp2 * sin (Q->dlam2), - Q->cp1 * Q->sp2 - Q->sp1 * Q->cp2 * cos (Q->dlam2)); - Q->ca = cos(pp = aasin(P->ctx, Q->cp1 * sin(A12))); - Q->sa = sin(pp); - Q->lp = adjlon ( atan2 (Q->cp1 * cos(A12), Q->sp1) - Q->hz0); - Q->dlam2 *= .5; - Q->lamc = M_HALFPI - atan2(sin(A12) * Q->sp1, cos(A12)) - Q->dlam2; - Q->thz0 = tan (Q->hz0); - Q->rhshz0 = .5 / sin (Q->hz0); - Q->r2z0 = 0.5 / Q->z02; - Q->z02 *= Q->z02; - - P->inv = s_inverse; - P->fwd = s_forward; - P->es = 0.; - - return P; -} - diff --git a/src/PJ_unitconvert.cpp b/src/PJ_unitconvert.cpp deleted file mode 100644 index b25fd5d2..00000000 --- a/src/PJ_unitconvert.cpp +++ /dev/null @@ -1,552 +0,0 @@ -/*********************************************************************** - - Unit conversion pseudo-projection for use with - transformation pipelines. - - Kristian Evers, 2017-05-16 - -************************************************************************ - -A pseudo-projection that can be used to convert units of input and -output data. Primarily useful in pipelines. - -Unit conversion is performed by means of a pivot unit. The pivot unit -for distance units are the meter and for time we use the modified julian -date. A time unit conversion is performed like - - Unit A -> Modified Julian date -> Unit B - -distance units are converted in the same manner, with meter being the -central unit. - -The modified Julian date is chosen as the pivot unit since it has a -fairly high precision, goes sufficiently long backwards in time, has no -danger of hitting the upper limit in the near future and it is a fairly -common time unit in astronomy and geodesy. Note that we are using the -Julian date and not day. The difference being that the latter is defined -as an integer and is thus limited to days in resolution. This approach -has been extended wherever it makes sense, e.g. the GPS week unit also -has a fractional part that makes it possible to determine the day, hour -and minute of an observation. - -In- and output units are controlled with the parameters - - +xy_in, +xy_out, +z_in, +z_out, +t_in and +t_out - -where xy denotes horizontal units, z vertical units and t time units. - -************************************************************************ - -Kristian Evers, kreve@sdfe.dk, 2017-05-09 -Last update: 2017-05-16 - -************************************************************************ -* Copyright (c) 2017, Kristian Evers / SDFE -* -* 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. -* -***********************************************************************/ - -#define PJ_LIB__ - -#include -#include -#include -#include - -#include "proj_internal.h" -#include "proj_math.h" -#include "projects.h" - -PROJ_HEAD(unitconvert, "Unit conversion"); - -typedef double (*tconvert)(double); - -namespace { // anonymous namespace -struct TIME_UNITS { - const char *id; /* units keyword */ - tconvert t_in; /* unit -> mod. julian date function pointer */ - tconvert t_out; /* mod. julian date > unit function pointer */ - const char *name; /* comments */ -}; -} // anonymous namespace - -namespace { // anonymous namespace -struct pj_opaque_unitconvert { - int t_in_id; /* time unit id for the time input unit */ - int t_out_id; /* time unit id for the time output unit */ - double xy_factor; /* unit conversion factor for horizontal components */ - double z_factor; /* unit conversion factor for vertical components */ -}; -} // anonymous namespace - - -/***********************************************************************/ -static int is_leap_year(long year) { -/***********************************************************************/ - return ((year % 4 == 0 && year % 100 != 0) || year % 400 ==0); -} - - -/***********************************************************************/ -static int days_in_year(long year) { -/***********************************************************************/ - return is_leap_year(year) ? 366 : 365; -} - -/***********************************************************************/ -static unsigned int days_in_month(unsigned long year, unsigned long month) { -/***********************************************************************/ - const unsigned int month_table[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - unsigned int days; - - if (month > 12) month = 12; - if (month == 0) month = 1; - - days = month_table[month-1]; - if (is_leap_year(year) && month == 2) days++; - - return days; -} - - -/***********************************************************************/ -static int daynumber_in_year(unsigned long year, unsigned long month, unsigned long day) { -/***********************************************************************/ - unsigned int daynumber=0, i; - - if (month > 12) month = 12; - if (month == 0) month = 1; - if (day > days_in_month(year, month)) day = days_in_month(year, month); - - for (i = 1; i < month; i++) - daynumber += days_in_month(year, i); - - daynumber += day; - - return daynumber; - -} - -/***********************************************************************/ -static double mjd_to_mjd(double mjd) { -/*********************************************************************** - Modified julian date no-op function. - - The Julian date is defined as (fractional) days since midnight - on 16th of November in 1858. -************************************************************************/ - return mjd; -} - - -/***********************************************************************/ -static double decimalyear_to_mjd(double decimalyear) { -/*********************************************************************** - Epoch of modified julian date is 1858-11-16 00:00 -************************************************************************/ - long year; - double fractional_year; - double mjd; - - if( decimalyear < -10000 || decimalyear > 10000 ) - return 0; - - year = lround(floor(decimalyear)); - fractional_year = decimalyear - year; - mjd = (year - 1859)*365 + 14 + 31; - mjd += (double)fractional_year*(double)days_in_year(year); - - /* take care of leap days */ - year--; - for (; year > 1858; year--) - if (is_leap_year(year)) - mjd++; - - return mjd; -} - - -/***********************************************************************/ -static double mjd_to_decimalyear(double mjd) { -/*********************************************************************** - Epoch of modified julian date is 1858-11-16 00:00 -************************************************************************/ - double decimalyear = mjd; - double mjd_iter = 14 + 31; - int year = 1859; - - /* a smarter brain than mine could probably to do this more elegantly - - I'll just brute-force my way out of this... */ - for (; mjd >= mjd_iter; year++) { - mjd_iter += days_in_year(year); - } - year--; - mjd_iter -= days_in_year(year); - - decimalyear = year + (mjd-mjd_iter)/days_in_year(year); - return decimalyear; -} - - -/***********************************************************************/ -static double gps_week_to_mjd(double gps_week) { -/*********************************************************************** - GPS weeks are defined as the number of weeks since January the 6th - 1980. - - Epoch of gps weeks is 1980-01-06 00:00, which in modified Julian - date is 44244. -************************************************************************/ - return 44244.0 + gps_week*7.0; -} - - -/***********************************************************************/ -static double mjd_to_gps_week(double mjd) { -/*********************************************************************** - GPS weeks are defined as the number of weeks since January the 6th - 1980. - - Epoch of gps weeks is 1980-01-06 00:00, which in modified Julian - date is 44244. -************************************************************************/ - return (mjd - 44244.0) / 7.0; -} - - -/***********************************************************************/ -static double yyyymmdd_to_mjd(double yyyymmdd) { -/************************************************************************ - Date given in YYYY-MM-DD format. -************************************************************************/ - - long year = lround(floor( yyyymmdd / 10000 )); - long month = lround(floor((yyyymmdd - year*10000) / 100)); - long day = lround(floor( yyyymmdd - year*10000 - month*100)); - double mjd = daynumber_in_year(year, month, day); - - for (year -= 1; year > 1858; year--) - mjd += days_in_year(year); - - return mjd + 13 + 31; -} - - -/***********************************************************************/ -static double mjd_to_yyyymmdd(double mjd) { -/************************************************************************ - Date given in YYYY-MM-DD format. -************************************************************************/ - double mjd_iter = 14 + 31; - int year = 1859, month=0, day=0; - - for (; mjd >= mjd_iter; year++) { - mjd_iter += days_in_year(year); - } - year--; - mjd_iter -= days_in_year(year); - - for (month=1; mjd_iter + days_in_month(year, month) <= mjd; month++) - mjd_iter += days_in_month(year, month); - - day = (int)(mjd - mjd_iter + 1); - - return year*10000.0 + month*100.0 + day; -} - -static const struct TIME_UNITS time_units[] = { - {"mjd", mjd_to_mjd, mjd_to_mjd, "Modified julian date"}, - {"decimalyear", decimalyear_to_mjd, mjd_to_decimalyear, "Decimal year"}, - {"gps_week", gps_week_to_mjd, mjd_to_gps_week, "GPS Week"}, - {"yyyymmdd", yyyymmdd_to_mjd, mjd_to_yyyymmdd, "YYYYMMDD date"}, - {nullptr, nullptr, nullptr, nullptr} -}; - - -/***********************************************************************/ -static XY forward_2d(LP lp, PJ *P) { -/************************************************************************ - Forward unit conversions in the plane -************************************************************************/ - struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - point.lp = lp; - - point.xy.x *= Q->xy_factor; - point.xy.y *= Q->xy_factor; - - return point.xy; -} - - -/***********************************************************************/ -static LP reverse_2d(XY xy, PJ *P) { -/************************************************************************ - Reverse unit conversions in the plane -************************************************************************/ - struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - point.xy = xy; - - point.xy.x /= Q->xy_factor; - point.xy.y /= Q->xy_factor; - - return point.lp; -} - - -/***********************************************************************/ -static XYZ forward_3d(LPZ lpz, PJ *P) { -/************************************************************************ - Forward unit conversions the vertical component -************************************************************************/ - struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - point.lpz = lpz; - - /* take care of the horizontal components in the 2D function */ - point.xy = forward_2d(point.lp, P); - - point.xyz.z *= Q->z_factor; - - return point.xyz; -} - -/***********************************************************************/ -static LPZ reverse_3d(XYZ xyz, PJ *P) { -/************************************************************************ - Reverse unit conversions the vertical component -************************************************************************/ - struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - point.xyz = xyz; - - /* take care of the horizontal components in the 2D function */ - point.lp = reverse_2d(point.xy, P); - - point.xyz.z /= Q->z_factor; - - return point.lpz; -} - - -/***********************************************************************/ -static PJ_COORD forward_4d(PJ_COORD obs, PJ *P) { -/************************************************************************ - Forward conversion of time units -************************************************************************/ - struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque; - PJ_COORD out = obs; - - /* delegate unit conversion of physical dimensions to the 3D function */ - out.xyz = forward_3d(obs.lpz, P); - - if (Q->t_in_id >= 0) - out.xyzt.t = time_units[Q->t_in_id].t_in( obs.xyzt.t ); - if (Q->t_out_id >= 0) - out.xyzt.t = time_units[Q->t_out_id].t_out( out.xyzt.t ); - - return out; -} - - -/***********************************************************************/ -static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) { -/************************************************************************ - Reverse conversion of time units -************************************************************************/ - struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque; - PJ_COORD out = obs; - - /* delegate unit conversion of physical dimensions to the 3D function */ - out.lpz = reverse_3d(obs.xyz, P); - - if (Q->t_out_id >= 0) - out.xyzt.t = time_units[Q->t_out_id].t_in( obs.xyzt.t ); - if (Q->t_in_id >= 0) - out.xyzt.t = time_units[Q->t_in_id].t_out( out.xyzt.t ); - - return out; -} - -/***********************************************************************/ -static double get_unit_conversion_factor(const char* name, - int* p_is_linear, - const char** p_normalized_name) { -/***********************************************************************/ - int i; - const char* s; - const PJ_UNITS *units; - - units = proj_list_units(); - - /* Try first with linear units */ - for (i = 0; (s = units[i].id) ; ++i) { - if ( strcmp(s, name) == 0 ) { - if( p_normalized_name ) { - *p_normalized_name = units[i].name; - } - if( p_is_linear ) { - *p_is_linear = 1; - } - return units[i].factor; - } - } - - /* And then angular units */ - units = proj_list_angular_units(); - for (i = 0; (s = units[i].id) ; ++i) { - if ( strcmp(s, name) == 0 ) { - if( p_normalized_name ) { - *p_normalized_name = units[i].name; - } - if( p_is_linear ) { - *p_is_linear = 0; - } - return units[i].factor; - } - } - if( p_normalized_name ) { - *p_normalized_name = nullptr; - } - if( p_is_linear ) { - *p_is_linear = -1; - } - return 0.0; -} - -/***********************************************************************/ -PJ *CONVERSION(unitconvert,0) { -/***********************************************************************/ - struct pj_opaque_unitconvert *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque_unitconvert))); - const char *s, *name; - int i; - double f; - int xy_in_is_linear = -1; /* unknown */ - int xy_out_is_linear = -1; /* unknown */ - int z_in_is_linear = -1; /* unknown */ - int z_out_is_linear = -1; /* unknown */ - - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = (void *) Q; - - P->fwd4d = forward_4d; - P->inv4d = reverse_4d; - P->fwd3d = forward_3d; - P->inv3d = reverse_3d; - P->fwd = forward_2d; - P->inv = reverse_2d; - - P->left = PJ_IO_UNITS_WHATEVER; - P->right = PJ_IO_UNITS_WHATEVER; - - /* if no time input/output unit is specified we can skip them */ - Q->t_in_id = -1; - Q->t_out_id = -1; - - Q->xy_factor = 1.0; - Q->z_factor = 1.0; - - if ((name = pj_param (P->ctx, P->params, "sxy_in").s) != nullptr) { - const char* normalized_name = nullptr; - f = get_unit_conversion_factor(name, &xy_in_is_linear, &normalized_name); - if (f != 0.0) { - proj_log_debug(P, "xy_in unit: %s", normalized_name); - } else { - if ( (f = pj_param (P->ctx, P->params, "dxy_in").f) == 0.0) - return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); - } - if (f != 0.0) - Q->xy_factor *= f; - } - - if ((name = pj_param (P->ctx, P->params, "sxy_out").s) != nullptr) { - const char* normalized_name = nullptr; - f = get_unit_conversion_factor(name, &xy_out_is_linear, &normalized_name); - if (f != 0.0) { - proj_log_debug(P, "xy_out unit: %s", normalized_name); - } else { - if ( (f = pj_param (P->ctx, P->params, "dxy_out").f) == 0.0) - return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); - } - if (f != 0.0) - Q->xy_factor /= f; - } - - if( xy_in_is_linear >= 0 && xy_out_is_linear >= 0 && - xy_in_is_linear != xy_out_is_linear ) { - proj_log_debug(P, "inconsistent unit type between xy_in and xy_out"); - return pj_default_destructor(P, PJD_ERR_INCONSISTENT_UNIT); - } - - if ((name = pj_param (P->ctx, P->params, "sz_in").s) != nullptr) { - const char* normalized_name = nullptr; - f = get_unit_conversion_factor(name, &z_in_is_linear, &normalized_name); - if (f != 0.0) { - proj_log_debug(P, "z_in unit: %s", normalized_name); - } else { - if ( (f = pj_param (P->ctx, P->params, "dz_in").f) == 0.0) - return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); - } - if (f != 0.0) - Q->z_factor *= f; - } - - if ((name = pj_param (P->ctx, P->params, "sz_out").s) != nullptr) { - const char* normalized_name = nullptr; - f = get_unit_conversion_factor(name, &z_out_is_linear, &normalized_name); - if (f != 0.0) { - proj_log_debug(P, "z_out unit: %s", normalized_name); - } else { - if ( (f = pj_param (P->ctx, P->params, "dz_out").f) == 0.0) - return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); - } - if (f != 0.0) - Q->z_factor /= f; - } - - if( z_in_is_linear >= 0 && z_out_is_linear >= 0 && - z_in_is_linear != z_out_is_linear ) { - proj_log_debug(P, "inconsistent unit type between z_in and z_out"); - return pj_default_destructor(P, PJD_ERR_INCONSISTENT_UNIT); - } - - if ((name = pj_param (P->ctx, P->params, "st_in").s) != nullptr) { - for (i = 0; (s = time_units[i].id) && strcmp(name, s) ; ++i); - - if (!s) return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); /* unknown unit conversion id */ - - Q->t_in_id = i; - proj_log_debug(P, "t_in unit: %s", time_units[i].name); - } - - s = nullptr; - if ((name = pj_param (P->ctx, P->params, "st_out").s) != nullptr) { - for (i = 0; (s = time_units[i].id) && strcmp(name, s) ; ++i); - - if (!s) return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); /* unknown unit conversion id */ - - Q->t_out_id = i; - proj_log_debug(P, "t_out unit: %s", time_units[i].name); - } - - return P; -} diff --git a/src/PJ_urm5.cpp b/src/PJ_urm5.cpp deleted file mode 100644 index 0e3c7e3c..00000000 --- a/src/PJ_urm5.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(urm5, "Urmaev V") "\n\tPCyl, Sph, no inv\n\tn= q= alpha="; - -namespace { // anonymous namespace -struct pj_opaque { - double m, rmn, q3, n; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double t; - - t = lp.phi = aasin (P->ctx, Q->n * sin (lp.phi)); - xy.x = Q->m * lp.lam * cos (lp.phi); - t *= t; - xy.y = lp.phi * (1. + t * Q->q3) * Q->rmn; - return xy; -} - - -PJ *PROJECTION(urm5) { - double alpha, t; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - if (pj_param(P->ctx, P->params, "tn").i) { - Q->n = pj_param(P->ctx, P->params, "dn").f; - if (Q->n <= 0. || Q->n > 1.) - return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE); - } else { - return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE); - } - Q->q3 = pj_param(P->ctx, P->params, "dq").f / 3.; - alpha = pj_param(P->ctx, P->params, "ralpha").f; - t = Q->n * sin (alpha); - Q->m = cos (alpha) / sqrt (1. - t * t); - Q->rmn = 1. / (Q->m * Q->n); - - P->es = 0.; - P->inv = nullptr; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_urmfps.cpp b/src/PJ_urmfps.cpp deleted file mode 100644 index 7103222a..00000000 --- a/src/PJ_urmfps.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(urmfps, "Urmaev Flat-Polar Sinusoidal") "\n\tPCyl, Sph\n\tn="; -PROJ_HEAD(wag1, "Wagner I (Kavraisky VI)") "\n\tPCyl, Sph"; - -namespace { // anonymous namespace -struct pj_opaque { - double n, C_y; -}; -} // anonymous namespace - -#define C_x 0.8773826753 -#define Cy 1.139753528477 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - lp.phi = aasin (P->ctx,static_cast(P->opaque)->n * sin (lp.phi)); - xy.x = C_x * lp.lam * cos (lp.phi); - xy.y = static_cast(P->opaque)->C_y * lp.phi; - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0, 0.0}; - xy.y /= static_cast(P->opaque)->C_y; - lp.phi = aasin(P->ctx, sin (xy.y) / static_cast(P->opaque)->n); - lp.lam = xy.x / (C_x * cos (xy.y)); - return lp; -} - - -static PJ *setup(PJ *P) { - static_cast(P->opaque)->C_y = Cy / static_cast(P->opaque)->n; - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - return P; -} - - -PJ *PROJECTION(urmfps) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - - P->opaque = Q; - - if (pj_param(P->ctx, P->params, "tn").i) { - static_cast(P->opaque)->n = pj_param(P->ctx, P->params, "dn").f; - if (static_cast(P->opaque)->n <= 0. || static_cast(P->opaque)->n > 1.) - return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE); - } else { - return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE); - } - - return setup(P); -} - - -PJ *PROJECTION(wag1) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - static_cast(P->opaque)->n = 0.8660254037844386467637231707; - return setup(P); -} diff --git a/src/PJ_vandg.cpp b/src/PJ_vandg.cpp deleted file mode 100644 index d148e210..00000000 --- a/src/PJ_vandg.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#define PJ_LIB__ -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(vandg, "van der Grinten (I)") "\n\tMisc Sph"; - -# define TOL 1.e-10 -# define THIRD .33333333333333333333 -# define C2_27 .07407407407407407407 -# define PI4_3 4.18879020478639098458 -# define PISQ 9.86960440108935861869 -# define TPISQ 19.73920880217871723738 -# define HPISQ 4.93480220054467930934 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double al, al2, g, g2, p2; - - p2 = fabs(lp.phi / M_HALFPI); - if ((p2 - TOL) > 1.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - if (p2 > 1.) - p2 = 1.; - if (fabs(lp.phi) <= TOL) { - xy.x = lp.lam; - xy.y = 0.; - } else if (fabs(lp.lam) <= TOL || fabs(p2 - 1.) < TOL) { - xy.x = 0.; - xy.y = M_PI * tan(.5 * asin(p2)); - if (lp.phi < 0.) xy.y = -xy.y; - } else { - al = .5 * fabs(M_PI / lp.lam - lp.lam / M_PI); - al2 = al * al; - g = sqrt(1. - p2 * p2); - g = g / (p2 + g - 1.); - g2 = g * g; - p2 = g * (2. / p2 - 1.); - p2 = p2 * p2; - xy.x = g - p2; g = p2 + al2; - xy.x = M_PI * (al * xy.x + sqrt(al2 * xy.x * xy.x - g * (g2 - p2))) / g; - if (lp.lam < 0.) xy.x = -xy.x; - xy.y = fabs(xy.x / M_PI); - xy.y = 1. - xy.y * (xy.y + 2. * al); - if (xy.y < -TOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return xy; - } - if (xy.y < 0.) - xy.y = 0.; - else - xy.y = sqrt(xy.y) * (lp.phi < 0. ? -M_PI : M_PI); - } - - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - double t, c0, c1, c2, c3, al, r2, r, m, d, ay, x2, y2; - - x2 = xy.x * xy.x; - if ((ay = fabs(xy.y)) < TOL) { - lp.phi = 0.; - t = x2 * x2 + TPISQ * (x2 + HPISQ); - lp.lam = fabs(xy.x) <= TOL ? 0. : - .5 * (x2 - PISQ + sqrt(t)) / xy.x; - return (lp); - } - y2 = xy.y * xy.y; - r = x2 + y2; r2 = r * r; - c1 = - M_PI * ay * (r + PISQ); - c3 = r2 + M_TWOPI * (ay * r + M_PI * (y2 + M_PI * (ay + M_HALFPI))); - c2 = c1 + PISQ * (r - 3. * y2); - c0 = M_PI * ay; - c2 /= c3; - al = c1 / c3 - THIRD * c2 * c2; - m = 2. * sqrt(-THIRD * al); - d = C2_27 * c2 * c2 * c2 + (c0 * c0 - THIRD * c2 * c1) / c3; - if (((t = fabs(d = 3. * d / (al * m))) - TOL) <= 1.) { - d = t > 1. ? (d > 0. ? 0. : M_PI) : acos(d); - lp.phi = M_PI * (m * cos(d * THIRD + PI4_3) - THIRD * c2); - if (xy.y < 0.) lp.phi = -lp.phi; - t = r2 + TPISQ * (x2 - y2 + HPISQ); - lp.lam = fabs(xy.x) <= TOL ? 0. : - .5 * (r - PISQ + (t <= 0. ? 0. : sqrt(t))) / xy.x; - } else { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); - return lp; - } - - return lp; -} - - -PJ *PROJECTION(vandg) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} - diff --git a/src/PJ_vandg2.cpp b/src/PJ_vandg2.cpp deleted file mode 100644 index 61d50044..00000000 --- a/src/PJ_vandg2.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -namespace { // anonymous namespace -struct pj_opaque { - int vdg3; -}; -} // anonymous namespace - -PROJ_HEAD(vandg2, "van der Grinten II") "\n\tMisc Sph, no inv"; -PROJ_HEAD(vandg3, "van der Grinten III") "\n\tMisc Sph, no inv"; - -#define TOL 1e-10 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = static_cast(P->opaque); - double x1, at, bt, ct; - - bt = fabs(M_TWO_D_PI * lp.phi); - if ((ct = 1. - bt * bt) < 0.) - ct = 0.; - else - ct = sqrt(ct); - if (fabs(lp.lam) < TOL) { - xy.x = 0.; - xy.y = M_PI * (lp.phi < 0. ? -bt : bt) / (1. + ct); - } else { - at = 0.5 * fabs(M_PI / lp.lam - lp.lam / M_PI); - if (Q->vdg3) { - x1 = bt / (1. + ct); - xy.x = M_PI * (sqrt(at * at + 1. - x1 * x1) - at); - xy.y = M_PI * x1; - } else { - x1 = (ct * sqrt(1. + at * at) - at * ct * ct) / - (1. + at * at * bt * bt); - xy.x = M_PI * x1; - xy.y = M_PI * sqrt(1. - x1 * (x1 + 2. * at) + TOL); - } - if ( lp.lam < 0.) xy.x = -xy.x; - if ( lp.phi < 0.) xy.y = -xy.y; - } - - return xy; -} - - -PJ *PROJECTION(vandg2) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->vdg3 = 0; - P->fwd = s_forward; - - return P; -} - -PJ *PROJECTION(vandg3) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->vdg3 = 1; - P->es = 0.; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_vandg4.cpp b/src/PJ_vandg4.cpp deleted file mode 100644 index d9a53c87..00000000 --- a/src/PJ_vandg4.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(vandg4, "van der Grinten IV") "\n\tMisc Sph, no inv"; - -#define TOL 1e-10 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - double x1, t, bt, ct, ft, bt2, ct2, dt, dt2; - (void) P; - - if (fabs(lp.phi) < TOL) { - xy.x = lp.lam; - xy.y = 0.; - } else if (fabs(lp.lam) < TOL || fabs(fabs(lp.phi) - M_HALFPI) < TOL) { - xy.x = 0.; - xy.y = lp.phi; - } else { - bt = fabs(M_TWO_D_PI * lp.phi); - bt2 = bt * bt; - ct = 0.5 * (bt * (8. - bt * (2. + bt2)) - 5.) - / (bt2 * (bt - 1.)); - ct2 = ct * ct; - dt = M_TWO_D_PI * lp.lam; - dt = dt + 1. / dt; - dt = sqrt(dt * dt - 4.); - if ((fabs(lp.lam) - M_HALFPI) < 0.) dt = -dt; - dt2 = dt * dt; - x1 = bt + ct; x1 *= x1; - t = bt + 3.*ct; - ft = x1 * (bt2 + ct2 * dt2 - 1.) + (1.-bt2) * ( - bt2 * (t * t + 4. * ct2) + - ct2 * (12. * bt * ct + 4. * ct2) ); - x1 = (dt*(x1 + ct2 - 1.) + 2.*sqrt(ft)) / - (4.* x1 + dt2); - xy.x = M_HALFPI * x1; - xy.y = M_HALFPI * sqrt(1. + dt * fabs(x1) - x1 * x1); - if (lp.lam < 0.) xy.x = -xy.x; - if (lp.phi < 0.) xy.y = -xy.y; - } - return xy; -} - - -PJ *PROJECTION(vandg4) { - P->es = 0.; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_vgridshift.cpp b/src/PJ_vgridshift.cpp deleted file mode 100644 index b3da906d..00000000 --- a/src/PJ_vgridshift.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#define PJ_LIB__ - -#include -#include -#include -#include - -#include "proj_internal.h" -#include "projects.h" - -PROJ_HEAD(vgridshift, "Vertical grid shift"); - -namespace { // anonymous namespace -struct pj_opaque_vgridshift { - double t_final; - double t_epoch; - double forward_multiplier; -}; -} // anonymous namespace - -static XYZ forward_3d(LPZ lpz, PJ *P) { - struct pj_opaque_vgridshift *Q = (struct pj_opaque_vgridshift *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - point.lpz = lpz; - - if (P->vgridlist_geoid != nullptr) { - /* Only try the gridshift if at least one grid is loaded, - * otherwise just pass the coordinate through unchanged. */ - point.xyz.z += Q->forward_multiplier * proj_vgrid_value(P, point.lp); - } - - return point.xyz; -} - - -static LPZ reverse_3d(XYZ xyz, PJ *P) { - struct pj_opaque_vgridshift *Q = (struct pj_opaque_vgridshift *) P->opaque; - PJ_COORD point = {{0,0,0,0}}; - point.xyz = xyz; - - if (P->vgridlist_geoid != nullptr) { - /* Only try the gridshift if at least one grid is loaded, - * otherwise just pass the coordinate through unchanged. */ - point.xyz.z -= Q->forward_multiplier * proj_vgrid_value(P, point.lp); - } - - return point.lpz; -} - - -static PJ_COORD forward_4d(PJ_COORD obs, PJ *P) { - struct pj_opaque_vgridshift *Q = (struct pj_opaque_vgridshift *) P->opaque; - PJ_COORD point = obs; - - /* If transformation is not time restricted, we always call it */ - if (Q->t_final==0 || Q->t_epoch==0) { - point.xyz = forward_3d (obs.lpz, P); - return point; - } - - /* Time restricted - only apply transform if within time bracket */ - if (obs.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) - point.xyz = forward_3d (obs.lpz, P); - - - return point; -} - -static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) { - struct pj_opaque_vgridshift *Q = (struct pj_opaque_vgridshift *) P->opaque; - PJ_COORD point = obs; - - /* If transformation is not time restricted, we always call it */ - if (Q->t_final==0 || Q->t_epoch==0) { - point.lpz = reverse_3d (obs.xyz, P); - return point; - } - - /* Time restricted - only apply transform if within time bracket */ - if (obs.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) - point.lpz = reverse_3d (obs.xyz, P); - - return point; -} - - -PJ *TRANSFORMATION(vgridshift,0) { - struct pj_opaque_vgridshift *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque_vgridshift))); - if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = (void *) Q; - - if (!pj_param(P->ctx, P->params, "tgrids").i) { - proj_log_error(P, "vgridshift: +grids parameter missing."); - return pj_default_destructor(P, PJD_ERR_NO_ARGS); - } - - /* TODO: Refactor into shared function that can be used */ - /* by both vgridshift and hgridshift */ - if (pj_param(P->ctx, P->params, "tt_final").i) { - Q->t_final = pj_param (P->ctx, P->params, "dt_final").f; - if (Q->t_final == 0) { - /* a number wasn't passed to +t_final, let's see if it was "now" */ - /* and set the time accordingly. */ - if (!strcmp("now", pj_param(P->ctx, P->params, "st_final").s)) { - time_t now; - struct tm *date; - time(&now); - date = localtime(&now); - Q->t_final = 1900.0 + date->tm_year + date->tm_yday/365.0; - } - } - } - - if (pj_param(P->ctx, P->params, "tt_epoch").i) - Q->t_epoch = pj_param (P->ctx, P->params, "dt_epoch").f; - - /* historical: the forward direction subtracts the grid offset. */ - Q->forward_multiplier = -1.0; - if (pj_param(P->ctx, P->params, "tmultiplier").i) { - Q->forward_multiplier = pj_param(P->ctx, P->params, "dmultiplier").f; - } - - /* Build gridlist. P->vgridlist_geoid can be empty if +grids only ask for optional grids. */ - proj_vgrid_init(P, "grids"); - - /* Was gridlist compiled properly? */ - if ( proj_errno(P) ) { - proj_log_error(P, "vgridshift: could not find required grid(s)."); - return pj_default_destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID); - } - - P->fwd4d = forward_4d; - P->inv4d = reverse_4d; - P->fwd3d = forward_3d; - P->inv3d = reverse_3d; - P->fwd = nullptr; - P->inv = nullptr; - - P->left = PJ_IO_UNITS_ANGULAR; - P->right = PJ_IO_UNITS_ANGULAR; - - return P; -} diff --git a/src/PJ_wag2.cpp b/src/PJ_wag2.cpp deleted file mode 100644 index 1bee737a..00000000 --- a/src/PJ_wag2.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(wag2, "Wagner II") "\n\tPCyl, Sph"; - -#define C_x 0.92483 -#define C_y 1.38725 -#define C_p1 0.88022 -#define C_p2 0.88550 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - lp.phi = aasin (P->ctx,C_p1 * sin (C_p2 * lp.phi)); - xy.x = C_x * lp.lam * cos (lp.phi); - xy.y = C_y * lp.phi; - return (xy); -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - lp.phi = xy.y / C_y; - lp.lam = xy.x / (C_x * cos(lp.phi)); - lp.phi = aasin (P->ctx,sin(lp.phi) / C_p1) / C_p2; - return (lp); -} - - -PJ *PROJECTION(wag2) { - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - return P; -} diff --git a/src/PJ_wag3.cpp b/src/PJ_wag3.cpp deleted file mode 100644 index bb1b4d49..00000000 --- a/src/PJ_wag3.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(wag3, "Wagner III") "\n\tPCyl, Sph\n\tlat_ts="; - -#define TWOTHIRD 0.6666666666666666666667 - -namespace { // anonymous namespace -struct pj_opaque { - double C_x; -}; -} // anonymous namespace - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - xy.x = static_cast(P->opaque)->C_x * lp.lam * cos(TWOTHIRD * lp.phi); - xy.y = lp.phi; - return xy; -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - lp.phi = xy.y; - lp.lam = xy.x / (static_cast(P->opaque)->C_x * cos(TWOTHIRD * lp.phi)); - return lp; -} - - -PJ *PROJECTION(wag3) { - double ts; - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - - P->opaque = Q; - - ts = pj_param (P->ctx, P->params, "rlat_ts").f; - static_cast(P->opaque)->C_x = cos (ts) / cos (2.*ts/3.); - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_wag7.cpp b/src/PJ_wag7.cpp deleted file mode 100644 index c8807f12..00000000 --- a/src/PJ_wag7.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#define PJ_LIB__ - -#include - -#include "projects.h" - -PROJ_HEAD(wag7, "Wagner VII") "\n\tMisc Sph, no inv"; - - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - double theta, ct, D; - - (void) P; /* Shut up compiler warnnings about unused P */ - - theta = asin (xy.y = 0.90630778703664996 * sin(lp.phi)); - xy.x = 2.66723 * (ct = cos (theta)) * sin (lp.lam /= 3.); - xy.y *= 1.24104 * (D = 1/(sqrt (0.5 * (1 + ct * cos (lp.lam))))); - xy.x *= D; - return (xy); -} - - -PJ *PROJECTION(wag7) { - P->fwd = s_forward; - P->inv = nullptr; - P->es = 0.; - return P; -} diff --git a/src/PJ_wink1.cpp b/src/PJ_wink1.cpp deleted file mode 100644 index de2f55ee..00000000 --- a/src/PJ_wink1.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(wink1, "Winkel I") "\n\tPCyl, Sph\n\tlat_ts="; - -namespace { // anonymous namespace -struct pj_opaque { - double cosphi1; -}; -} // anonymous namespace - - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - xy.x = .5 * lp.lam * (static_cast(P->opaque)->cosphi1 + cos(lp.phi)); - xy.y = lp.phi; - return (xy); -} - - -static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ - LP lp = {0.0,0.0}; - lp.phi = xy.y; - lp.lam = 2. * xy.x / (static_cast(P->opaque)->cosphi1 + cos(lp.phi)); - return (lp); -} - - -PJ *PROJECTION(wink1) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - static_cast(P->opaque)->cosphi1 = cos (pj_param(P->ctx, P->params, "rlat_ts").f); - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_wink2.cpp b/src/PJ_wink2.cpp deleted file mode 100644 index 74a47283..00000000 --- a/src/PJ_wink2.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(wink2, "Winkel II") "\n\tPCyl, Sph, no inv\n\tlat_1="; - -namespace { // anonymous namespace -struct pj_opaque { - double cosphi1; -}; -} // anonymous namespace - -#define MAX_ITER 10 -#define LOOP_TOL 1e-7 - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - double k, V; - int i; - - xy.y = lp.phi * M_TWO_D_PI; - k = M_PI * sin (lp.phi); - lp.phi *= 1.8; - for (i = MAX_ITER; i ; --i) { - lp.phi -= V = (lp.phi + sin (lp.phi) - k) / - (1. + cos (lp.phi)); - if (fabs (V) < LOOP_TOL) - break; - } - if (!i) - lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI; - else - lp.phi *= 0.5; - xy.x = 0.5 * lp.lam * (cos (lp.phi) + static_cast(P->opaque)->cosphi1); - xy.y = M_FORTPI * (sin (lp.phi) + xy.y); - return xy; -} - - -PJ *PROJECTION(wink2) { - struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - static_cast(P->opaque)->cosphi1 = cos(pj_param(P->ctx, P->params, "rlat_1").f); - P->es = 0.; - P->inv = nullptr; - P->fwd = s_forward; - - return P; -} diff --git a/src/apps/cct.cpp b/src/apps/cct.cpp new file mode 100644 index 00000000..046257da --- /dev/null +++ b/src/apps/cct.cpp @@ -0,0 +1,472 @@ +/*********************************************************************** + + The cct 4D Transformation program + +************************************************************************ + +cct is a 4D equivalent to the "proj" projection program. + +cct is an acronym meaning "Coordinate Conversion and Transformation". + +The acronym refers to definitions given in the OGC 08-015r2/ISO-19111 +standard "Geographical Information -- Spatial Referencing by Coordinates", +which defines two different classes of coordinate operations: + +*Coordinate Conversions*, which are coordinate operations where input +and output datum are identical (e.g. conversion from geographical to +cartesian coordinates) and + +*Coordinate Transformations*, which are coordinate operations where +input and output datums differ (e.g. change of reference frame). + +cct, however, also refers to Carl Christian Tscherning (1942--2014), +professor of Geodesy at the University of Copenhagen, mentor and advisor +for a generation of Danish geodesists, colleague and collaborator for +two generations of global geodesists, Secretary General for the +International Association of Geodesy, IAG (1995--2007), fellow of the +American Geophysical Union (1991), recipient of the IAG Levallois Medal +(2007), the European Geosciences Union Vening Meinesz Medal (2008), and +of numerous other honours. + +cct, or Christian, as he was known to most of us, was recognized for his +good mood, his sharp wit, his tireless work, and his great commitment to +the development of geodesy - both through his scientific contributions, +comprising more than 250 publications, and by his mentoring and teaching +of the next generations of geodesists. + +As Christian was an avid Fortran programmer, and a keen Unix connoisseur, +he would have enjoyed to know that his initials would be used to name a +modest Unix style transformation filter, hinting at the tireless aspect +of his personality, which was certainly one of the reasons he accomplished +so much, and meant so much to so many people. + +Hence, in honour of cct (the geodesist) this is cct (the program). + +************************************************************************ + +Thomas Knudsen, thokn@sdfe.dk, 2016-05-25/2017-10-26 + +************************************************************************ + +* Copyright (c) 2016, 2017 Thomas Knudsen +* Copyright (c) 2017, SDFE +* +* 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 +#include +#include +#include +#include +#include + +#include "proj.h" +#include "proj_internal.h" +#include "proj_strtod.h" +#include "projects.h" +#include "optargpm.h" + + +static void logger(void *data, int level, const char *msg); +static void print(PJ_LOG_LEVEL log_level, const char *fmt, ...); + +/* Prototypes from functions in this file */ +char *column (char *buf, int n); +PJ_COORD parse_input_line (char *buf, int *columns, double fixed_height, double fixed_time); + + +static const char usage[] = { + "--------------------------------------------------------------------------------\n" + "Usage: %s [-options]... [+operator_specs]... infile...\n" + "--------------------------------------------------------------------------------\n" + "Options:\n" + "--------------------------------------------------------------------------------\n" + " -c x,y,z,t Specify input columns for (up to) 4 input parameters.\n" + " Defaults to 1,2,3,4\n" + " -d n Specify number of decimals in output.\n" + " -I Do the inverse transformation\n" + " -o /path/to/file Specify output file name\n" + " -t value Provide a fixed t value for all input data (e.g. -t 0)\n" + " -z value Provide a fixed z value for all input data (e.g. -z 0)\n" + " -s n Skip n first lines of a infile\n" + " -v Verbose: Provide non-essential informational output.\n" + " Repeat -v for more verbosity (e.g. -vv)\n" + "--------------------------------------------------------------------------------\n" + "Long Options:\n" + "--------------------------------------------------------------------------------\n" + " --output Alias for -o\n" + " --columns Alias for -c\n" + " --decimals Alias for -d\n" + " --height Alias for -z\n" + " --time Alias for -t\n" + " --verbose Alias for -v\n" + " --inverse Alias for -I\n" + " --skip-lines Alias for -s\n" + " --help Alias for -h\n" + " --version Print version number\n" + "--------------------------------------------------------------------------------\n" + "Operator Specs:\n" + "--------------------------------------------------------------------------------\n" + "The operator specs describe the action to be performed by cct, e.g:\n" + "\n" + " +proj=utm +ellps=GRS80 +zone=32\n" + "\n" + "instructs cct to convert input data to Universal Transverse Mercator, zone 32\n" + "coordinates, based on the GRS80 ellipsoid.\n" + "\n" + "Hence, the command\n" + "\n" + " echo 12 55 | cct -z0 -t0 +proj=utm +zone=32 +ellps=GRS80\n" + "\n" + "Should give results comparable to the classic proj command\n" + "\n" + " echo 12 55 | proj +proj=utm +zone=32 +ellps=GRS80\n" + "--------------------------------------------------------------------------------\n" + "Examples:\n" + "--------------------------------------------------------------------------------\n" + "1. convert geographical input to UTM zone 32 on the GRS80 ellipsoid:\n" + " cct +proj=utm +ellps=GRS80 +zone=32\n" + "2. roundtrip accuracy check for the case above:\n" + " cct +proj=pipeline +proj=utm +ellps=GRS80 +zone=32 +step +step +inv\n" + "3. as (1) but specify input columns for longitude, latitude, height and time:\n" + " cct -c 5,2,1,4 +proj=utm +ellps=GRS80 +zone=32\n" + "4. as (1) but specify fixed height and time, hence needing only 2 cols in input:\n" + " cct -t 0 -z 0 +proj=utm +ellps=GRS80 +zone=32\n" + "--------------------------------------------------------------------------------\n" +}; + + +static void logger(void *data, int level, const char *msg) { + FILE *stream; + int log_tell = proj_log_level(PJ_DEFAULT_CTX, PJ_LOG_TELL); + + stream = (FILE *) data; + + /* if we use PJ_LOG_NONE we always want to print stuff to stream */ + if (level == PJ_LOG_NONE) { + fprintf(stream, "%s", msg); + return; + } + + /* should always print to stderr if level == PJ_LOG_ERROR */ + if (level == PJ_LOG_ERROR) { + fprintf(stderr, "%s", msg); + return; + } + + /* otherwise only print if log level set by user is high enough */ + if (level <= log_tell) + fprintf(stream, "%s", msg); +} + +FILE *fout; + +static void print(PJ_LOG_LEVEL log_level, const char *fmt, ...) { + + va_list args; + char *msg_buf; + + va_start( args, fmt ); + + msg_buf = (char *) malloc(100000); + if( msg_buf == nullptr ) { + va_end( args ); + return; + } + + vsprintf( msg_buf, fmt, args ); + + logger((void *) fout, log_level, msg_buf); + + va_end( args ); + free( msg_buf ); +} + + +int main(int argc, char **argv) { + PJ *P; + PJ_COORD point; + PJ_PROJ_INFO info; + OPTARGS *o; + char blank_comment[] = ""; + char whitespace[] = " "; + char *comment; + char *comment_delimiter; + char *buf; + int i, nfields = 4, skip_lines = 0, verbose; + double fixed_z = HUGE_VAL, fixed_time = HUGE_VAL; + int decimals_angles = 10; + int decimals_distances = 4; + int columns_xyzt[] = {1, 2, 3, 4}; + const char *longflags[] = {"v=verbose", "h=help", "I=inverse", "version", nullptr}; + const char *longkeys[] = { + "o=output", + "c=columns", + "d=decimals", + "z=height", + "t=time", + "s=skip-lines", + nullptr}; + + fout = stdout; + + o = opt_parse (argc, argv, "hvI", "cdozts", longflags, longkeys); + if (nullptr==o) + return 0; + + if (opt_given (o, "h") || argc==1) { + printf (usage, o->progname); + return 0; + } + + PJ_DIRECTION direction = opt_given (o, "I")? PJ_INV: PJ_FWD; + + verbose = MIN(opt_given (o, "v"), 3); /* log level can't be larger than 3 */ + proj_log_level (PJ_DEFAULT_CTX, static_cast(verbose)); + proj_log_func (PJ_DEFAULT_CTX, (void *) fout, logger); + + if (opt_given (o, "version")) { + print (PJ_LOG_NONE, "%s: %s\n", o->progname, pj_get_release ()); + return 0; + } + + if (opt_given (o, "o")) + fout = fopen (opt_arg (o, "output"), "wt"); + if (nullptr==fout) { + print (PJ_LOG_ERROR, "%s: Cannot open '%s' for output\n", o->progname, opt_arg (o, "output")); + free (o); + return 1; + } + + print (PJ_LOG_TRACE, "%s: Running in very verbose mode\n", o->progname); + + if (opt_given (o, "z")) { + fixed_z = proj_atof (opt_arg (o, "z")); + nfields--; + } + + if (opt_given (o, "t")) { + fixed_time = proj_atof (opt_arg (o, "t")); + nfields--; + } + + if (opt_given (o, "d")) { + int dec = atoi (opt_arg (o, "d")); + decimals_angles = dec; + decimals_distances = dec; + } + + if (opt_given (o, "s")) { + skip_lines = atoi (opt_arg(o, "s")); + } + + if (opt_given (o, "c")) { + int ncols; + /* reset column numbers to ease comment output later on */ + for (i=0; i<4; i++) + columns_xyzt[i] = 0; + + /* cppcheck-suppress invalidscanf */ + ncols = sscanf (opt_arg (o, "c"), "%d,%d,%d,%d", columns_xyzt, columns_xyzt+1, columns_xyzt+2, columns_xyzt+3); + if (ncols != nfields) { + print (PJ_LOG_ERROR, "%s: Too few input columns given: '%s'\n", o->progname, opt_arg (o, "c")); + free (o); + if (stdout != fout) + fclose (fout); + return 1; + } + } + + /* Setup transformation */ + P = proj_create_argv (nullptr, o->pargc, o->pargv); + if ((nullptr==P) || (0==o->pargc)) { + print (PJ_LOG_ERROR, "%s: Bad transformation arguments - (%s)\n '%s -h' for help\n", + o->progname, pj_strerrno (proj_errno(P)), o->progname); + free (o); + if (stdout != fout) + fclose (fout); + return 1; + } + + info = proj_pj_info (P); + print (PJ_LOG_TRACE, "Final: %s argc=%d pargc=%d\n", info.definition, argc, o->pargc); + + if (direction== PJ_INV) { + /* fail if an inverse operation is not available */ + if (!info.has_inverse) { + print (PJ_LOG_ERROR, "Inverse operation not available\n"); + if (stdout != fout) + fclose (fout); + return 1; + } + /* We have no API call for inverting an operation, so we brute force it. */ + P->inverted = !(P->inverted); + } + direction = PJ_FWD; + + /* Allocate input buffer */ + buf = static_cast(calloc (1, 10000)); + if (nullptr==buf) { + print (PJ_LOG_ERROR, "%s: Out of memory\n", o->progname); + pj_free (P); + free (o); + if (stdout != fout) + fclose (fout); + return 1; + } + + + /* Loop over all records of all input files */ + while (opt_input_loop (o, optargs_file_format_text)) { + int err; + void *ret = fgets (buf, 10000, o->input); + char *c = column (buf, 1); + opt_eof_handler (o); + if (nullptr==ret) { + print (PJ_LOG_ERROR, "Read error in record %d\n", (int) o->record_index); + continue; + } + point = parse_input_line (buf, columns_xyzt, fixed_z, fixed_time); + if (skip_lines > 0) { + skip_lines--; + continue; + } + + /* if it's a comment or blank line, we reflect it */ + if (c && ((*c=='\0') || (*c=='#'))) { + fprintf (fout, "%s", buf); + continue; + } + + if (HUGE_VAL==point.xyzt.x) { + /* otherwise, it must be a syntax error */ + print (PJ_LOG_NONE, "# Record %d UNREADABLE: %s", (int) o->record_index, buf); + print (PJ_LOG_ERROR, "%s: Could not parse file '%s' line %d\n", o->progname, opt_filename (o), opt_record (o)); + continue; + } + + if (proj_angular_input (P, direction)) { + point.lpzt.lam = proj_torad (point.lpzt.lam); + point.lpzt.phi = proj_torad (point.lpzt.phi); + } + err = proj_errno_reset (P); + point = proj_trans (P, direction, point); + + if (HUGE_VAL==point.xyzt.x) { + /* transformation error */ + print (PJ_LOG_NONE, "# Record %d TRANSFORMATION ERROR: %s (%s)", + (int) o->record_index, buf, pj_strerrno (proj_errno(P))); + proj_errno_restore (P, err); + continue; + } + proj_errno_restore (P, err); + + /* handle comment string */ + comment = column(buf, nfields+1); + if (opt_given(o, "c")) { + /* what number is the last coordinate column in the input data? */ + int colmax = 0; + for (i=0; i<4; i++) + colmax = MAX(colmax, columns_xyzt[i]); + comment = column(buf, colmax+1); + } + 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); + print (PJ_LOG_NONE, "%14.*f %14.*f %12.*f %12.4f%s%s\n", + decimals_angles, point.xyzt.x, + decimals_angles, point.xyzt.y, + decimals_distances, point.xyzt.z, + point.xyzt.t, comment_delimiter, comment + ); + } + else + print (PJ_LOG_NONE, "%13.*f %13.*f %12.*f %12.4f%s%s\n", + decimals_distances, point.xyzt.x, + decimals_distances, point.xyzt.y, + decimals_distances, point.xyzt.z, + point.xyzt.t, comment_delimiter, comment + ); + } + + if (stdout != fout) + fclose (fout); + free (o); + free (buf); + return 0; +} + + + + + +/* return a pointer to the n'th column of buf */ +char *column (char *buf, int n) { + int i; + if (n <= 0) + return buf; + for (i = 0; i < n; i++) { + while (isspace(*buf)) + buf++; + if (i == n - 1) + break; + while ((0 != *buf) && !isspace(*buf)) + buf++; + } + return buf; +} + +/* column to double */ +static double cold (char *args, int col) { + char *endp; + char *target; + double d; + target = column (args, col); + d = proj_strtod (target, &endp); + if (endp==target) + return HUGE_VAL; + return d; +} + +PJ_COORD parse_input_line (char *buf, int *columns, double fixed_height, double fixed_time) { + PJ_COORD err = proj_coord (HUGE_VAL, HUGE_VAL, HUGE_VAL, HUGE_VAL); + PJ_COORD result = err; + int prev_errno = errno; + errno = 0; + + result.xyzt.z = fixed_height; + result.xyzt.t = fixed_time; + result.xyzt.x = cold (buf, columns[0]); + result.xyzt.y = cold (buf, columns[1]); + if (result.xyzt.z==HUGE_VAL) + result.xyzt.z = cold (buf, columns[2]); + if (result.xyzt.t==HUGE_VAL) + result.xyzt.t = cold (buf, columns[3]); + + if (0!=errno) + return err; + + errno = prev_errno; + return result; +} diff --git a/src/apps/cs2cs.cpp b/src/apps/cs2cs.cpp new file mode 100644 index 00000000..f63bedcc --- /dev/null +++ b/src/apps/cs2cs.cpp @@ -0,0 +1,640 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Mainline program sort of like ``proj'' for converting between + * two coordinate systems. + * Author: Frank Warmerdam, warmerda@home.com + * + ****************************************************************************** + * Copyright (c) 2000, Frank Warmerdam + * + * 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. + *****************************************************************************/ + +#define FROM_PROJ_CPP + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "projects.h" +#include "emess.h" +// clang-format on + +#define MAX_LINE 1000 + +static PJ *transformation = nullptr; + +static bool srcIsGeog = false; +static double srcToRadians = 0.0; + +static bool destIsGeog = false; +static double destToRadians = 0.0; +static bool destIsLatLong = false; + +static int reversein = 0, /* != 0 reverse input arguments */ + reverseout = 0, /* != 0 reverse output arguments */ + echoin = 0, /* echo input data to output line */ + tag = '#'; /* beginning of line tag character */ + +static const char *oform = + nullptr; /* output format for x-y or decimal degrees */ +static char oform_buffer[16]; /* buffer for oform when using -d */ +static const char *oterr = "*\t*"; /* output line for unprojectable input */ +static const char *usage = + "%s\nusage: %s [ -dDeEfIlrstvwW [args] ] [ +opts[=arg] ]\n" + " [+to [+opts[=arg] [ files ]\n"; + +static double (*informat)(const char *, + char **); /* input data deformatter function */ + +/************************************************************************/ +/* process() */ +/* */ +/* File processing function. */ +/************************************************************************/ +static void process(FILE *fid) + +{ + char line[MAX_LINE + 3], *s, pline[40]; + projUV data; + + for (;;) { + double z; + + ++emess_dat.File_line; + if (!(s = fgets(line, MAX_LINE, fid))) + break; + if (!strchr(s, '\n')) { /* overlong line */ + int c; + (void)strcat(s, "\n"); + /* gobble up to newline */ + while ((c = fgetc(fid)) != EOF && c != '\n') + ; + } + if (*s == tag) { + fputs(line, stdout); + continue; + } + + if (reversein) { + data.v = (*informat)(s, &s); + data.u = (*informat)(s, &s); + } else { + data.u = (*informat)(s, &s); + data.v = (*informat)(s, &s); + } + + z = strtod(s, &s); + + if (data.v == HUGE_VAL) + data.u = HUGE_VAL; + + if (!*s && (s > line)) + --s; /* assumed we gobbled \n */ + + if (echoin) { + char t; + t = *s; + *s = '\0'; + (void)fputs(line, stdout); + *s = t; + putchar('\t'); + } + + if (data.u != HUGE_VAL) { + + if (srcIsGeog) { + /* dmstor gives values to radians. Convert now to the SRS unit + */ + data.u /= srcToRadians; + data.v /= srcToRadians; + } + + PJ_COORD coord; + coord.xyzt.x = data.u; + coord.xyzt.y = data.v; + coord.xyzt.z = z; + coord.xyzt.t = HUGE_VAL; + coord = proj_trans(transformation, PJ_FWD, coord); + data.u = coord.xyz.x; + data.v = coord.xyz.y; + z = coord.xyz.z; + } + + if (data.u == HUGE_VAL) /* error output */ + fputs(oterr, stdout); + + else if (destIsGeog && !oform) { /*ascii DMS output */ + + // rtodms() expect radians: convert from the output SRS unit + data.u *= destToRadians; + data.v *= destToRadians; + + if (destIsLatLong) { + if (reverseout) { + fputs(rtodms(pline, data.v, 'E', 'W'), stdout); + putchar('\t'); + fputs(rtodms(pline, data.u, 'N', 'S'), stdout); + } else { + fputs(rtodms(pline, data.u, 'N', 'S'), stdout); + putchar('\t'); + fputs(rtodms(pline, data.v, 'E', 'W'), stdout); + } + } else if (reverseout) { + fputs(rtodms(pline, data.v, 'N', 'S'), stdout); + putchar('\t'); + fputs(rtodms(pline, data.u, 'E', 'W'), stdout); + } else { + fputs(rtodms(pline, data.u, 'E', 'W'), stdout); + putchar('\t'); + fputs(rtodms(pline, data.v, 'N', 'S'), stdout); + } + + } else { /* x-y or decimal degree ascii output */ + if (destIsGeog) { + data.v *= destToRadians * RAD_TO_DEG; + data.u *= destToRadians * RAD_TO_DEG; + } + if (reverseout) { + printf(oform, data.v); + putchar('\t'); + printf(oform, data.u); + } else { + printf(oform, data.u); + putchar('\t'); + printf(oform, data.v); + } + } + + putchar(' '); + if (oform != nullptr) + printf(oform, z); + else + printf("%.3f", z); + if (s) + printf("%s", s); + else + printf("\n"); + } +} + +/************************************************************************/ +/* instanciate_crs() */ +/************************************************************************/ + +static PJ_OBJ *instanciate_crs(const std::string &definition, + const char *const *optionsImportCRS, + bool &isGeog, double &toRadians, + bool &isLatFirst) { + PJ_OBJ *crs = proj_obj_create_from_user_input(nullptr, definition.c_str(), + optionsImportCRS); + if (!crs) { + return nullptr; + } + + isGeog = false; + toRadians = 0.0; + isLatFirst = false; + + auto type = proj_obj_get_type(crs); + if (type == PJ_OBJ_TYPE_BOUND_CRS) { + auto base = proj_obj_get_source_crs(nullptr, crs); + proj_obj_destroy(crs); + crs = base; + type = proj_obj_get_type(crs); + } + if (type == PJ_OBJ_TYPE_GEOGRAPHIC_2D_CRS || + type == PJ_OBJ_TYPE_GEOGRAPHIC_3D_CRS) { + auto cs = proj_obj_crs_get_coordinate_system(nullptr, crs); + assert(cs); + + isGeog = true; + const char *axisName = ""; + proj_obj_cs_get_axis_info(nullptr, cs, 0, + &axisName, // name, + nullptr, // abbrev + nullptr, // direction + &toRadians, + nullptr, // unit name + nullptr, // unit authority + nullptr // unit code + ); + isLatFirst = + NS_PROJ::internal::ci_find(std::string(axisName), "latitude") != + std::string::npos; + + proj_obj_destroy(cs); + } + + return crs; +} + +/************************************************************************/ +/* get_geog_crs_proj_string_from_proj_crs() */ +/************************************************************************/ + +static std::string get_geog_crs_proj_string_from_proj_crs(PJ_OBJ *src, + double &toRadians, + bool &isLatFirst) { + auto srcType = proj_obj_get_type(src); + if (srcType == PJ_OBJ_TYPE_BOUND_CRS) { + auto base = proj_obj_get_source_crs(nullptr, src); + assert(base); + proj_obj_destroy(src); + src = base; + srcType = proj_obj_get_type(src); + } + if (srcType != PJ_OBJ_TYPE_PROJECTED_CRS) { + return std::string(); + } + + auto base = proj_obj_get_source_crs(nullptr, src); + assert(base); + auto baseType = proj_obj_get_type(base); + if (baseType != PJ_OBJ_TYPE_GEOGRAPHIC_2D_CRS && + baseType != PJ_OBJ_TYPE_GEOGRAPHIC_3D_CRS) { + proj_obj_destroy(base); + return std::string(); + } + + auto cs = proj_obj_crs_get_coordinate_system(nullptr, base); + assert(cs); + + const char *axisName = ""; + proj_obj_cs_get_axis_info(nullptr, cs, 0, + &axisName, // name, + nullptr, // abbrev + nullptr, // direction + &toRadians, + nullptr, // unit name + nullptr, // unit authority + nullptr // unit code + ); + isLatFirst = NS_PROJ::internal::ci_find(std::string(axisName), + "latitude") != std::string::npos; + + proj_obj_destroy(cs); + + auto retCStr = proj_obj_as_proj_string(nullptr, base, PJ_PROJ_5, nullptr); + std::string ret(retCStr ? retCStr : ""); + proj_obj_destroy(base); + return ret; +} + +/************************************************************************/ +/* main() */ +/************************************************************************/ + +int main(int argc, char **argv) { + char *arg; + char **eargv = argv; + std::string fromStr; + std::string toStr; + FILE *fid; + int eargc = 0, mon = 0; + int have_to_flag = 0, inverse = 0; + int use_env_locale = 0; + + /* This is just to check that pj_init() is locale-safe */ + /* Used by nad/testvarious */ + if (getenv("PROJ_USE_ENV_LOCALE") != nullptr) + use_env_locale = 1; + + /* Enable compatibility mode for init=epsg:XXXX by default */ + if (getenv("PROJ_USE_PROJ4_INIT_RULES") == nullptr) { + proj_context_use_proj4_init_rules(nullptr, true); + } + + if ((emess_dat.Prog_name = strrchr(*argv, DIR_CHAR)) != nullptr) + ++emess_dat.Prog_name; + else + emess_dat.Prog_name = *argv; + inverse = !strncmp(emess_dat.Prog_name, "inv", 3); + if (argc <= 1) { + (void)fprintf(stderr, usage, pj_get_release(), emess_dat.Prog_name); + exit(0); + } + + // First pass to check if we have "cs2cs [-bla]* " syntax + int countNonOptionArg = 0; + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + if (argv[i][1] == '\0') { + countNonOptionArg++; + } + } else { + if (strcmp(argv[i], "+to") == 0) { + countNonOptionArg = -1; + break; + } + countNonOptionArg++; + } + } + const bool isSrcDestSyntax = (countNonOptionArg == 2); + + /* process run line arguments */ + while (--argc > 0) { /* collect run line arguments */ + if (**++argv == '-') { + for (arg = *argv;;) { + switch (*++arg) { + case '\0': /* position of "stdin" */ + if (arg[-1] == '-') + eargv[eargc++] = const_cast("-"); + break; + case 'v': /* monitor dump of initialization */ + mon = 1; + continue; + case 'I': /* alt. method to spec inverse */ + inverse = 1; + continue; + case 'E': /* echo ascii input to ascii output */ + echoin = 1; + continue; + case 't': /* set col. one char */ + if (arg[1]) + tag = *++arg; + else + emess(1, "missing -t col. 1 tag"); + continue; + case 'l': /* list projections, ellipses or units */ + if (!arg[1] || arg[1] == 'p' || arg[1] == 'P') { + /* list projections */ + const struct PJ_LIST *lp; + int do_long = arg[1] == 'P', c; + const char *str; + + for (lp = proj_list_operations(); lp->id; ++lp) { + (void)printf("%s : ", lp->id); + if (do_long) /* possibly multiline description */ + (void)puts(*lp->descr); + else { /* first line, only */ + str = *lp->descr; + while ((c = *str++) && c != '\n') + putchar(c); + putchar('\n'); + } + } + } else if (arg[1] == '=') { /* list projection 'descr' */ + const struct PJ_LIST *lp; + + arg += 2; + for (lp = proj_list_operations(); lp->id; ++lp) + if (!strcmp(lp->id, arg)) { + (void)printf("%9s : %s\n", lp->id, *lp->descr); + break; + } + } else if (arg[1] == 'e') { /* list ellipses */ + const struct PJ_ELLPS *le; + + for (le = proj_list_ellps(); le->id; ++le) + (void)printf("%9s %-16s %-16s %s\n", le->id, + le->major, le->ell, le->name); + } else if (arg[1] == 'u') { /* list units */ + const struct PJ_UNITS *lu; + + for (lu = proj_list_units(); lu->id; ++lu) + (void)printf("%12s %-20s %s\n", lu->id, + lu->to_meter, lu->name); + } else if (arg[1] == 'd') { /* list datums */ + const struct PJ_DATUMS *ld; + + printf("__datum_id__ __ellipse___ " + "__definition/" + "comments______________________________\n"); + for (ld = pj_get_datums_ref(); ld->id; ++ld) { + printf("%12s %-12s %-30s\n", ld->id, ld->ellipse_id, + ld->defn); + if (ld->comments != nullptr && + strlen(ld->comments) > 0) + printf("%25s %s\n", " ", ld->comments); + } + } else if (arg[1] == 'm') { /* list prime meridians */ + const struct PJ_PRIME_MERIDIANS *lpm; + + for (lpm = proj_list_prime_meridians(); lpm->id; ++lpm) + (void)printf("%12s %-30s\n", lpm->id, lpm->defn); + } else + emess(1, "invalid list option: l%c", arg[1]); + exit(0); + /* cppcheck-suppress duplicateBreak */ + continue; /* artificial */ + case 'e': /* error line alternative */ + if (--argc <= 0) + noargument: + emess(1, "missing argument for -%c", *arg); + oterr = *++argv; + continue; + case 'W': /* specify seconds precision */ + case 'w': /* -W for constant field width */ + { + char c = arg[1]; + if (c != 0 && isdigit(c)) { + set_rtodms(c - '0', *arg == 'W'); + ++arg; + } else + emess(1, "-W argument missing or non-digit"); + continue; + } + case 'f': /* alternate output format degrees or xy */ + if (--argc <= 0) + goto noargument; + oform = *++argv; + continue; + case 'r': /* reverse input */ + reversein = 1; + continue; + case 's': /* reverse output */ + reverseout = 1; + continue; + case 'D': /* set debug level */ + if (--argc <= 0) + goto noargument; + pj_ctx_set_debug(pj_get_default_ctx(), atoi(*++argv)); + continue; + case 'd': + if (--argc <= 0) + goto noargument; + sprintf(oform_buffer, "%%.%df", atoi(*++argv)); + oform = oform_buffer; + break; + default: + emess(1, "invalid option: -%c", *arg); + break; + } + break; + } + } else if (isSrcDestSyntax) { + if (fromStr.empty()) + fromStr = *argv; + else + toStr = *argv; + } else if (strcmp(*argv, "+to") == 0) { + have_to_flag = 1; + + } else if (**argv == '+') { /* + argument */ + if (have_to_flag) { + if (!toStr.empty()) + toStr += ' '; + toStr += *argv; + } else { + if (!fromStr.empty()) + fromStr += ' '; + fromStr += *argv; + } + } else if (!have_to_flag) { + fromStr = *argv; + } else if (toStr.empty()) { + toStr = *argv; + } else /* assumed to be input file name(s) */ + eargv[eargc++] = *argv; + } + if (eargc == 0) /* if no specific files force sysin */ + eargv[eargc++] = const_cast("-"); + + /* + * If the user has requested inverse, then just reverse the + * coordinate systems. + */ + if (inverse) { + std::swap(fromStr, toStr); + } + + if (use_env_locale) { + /* Set locale from environment */ + setlocale(LC_ALL, ""); + } + + if (fromStr.empty() && toStr.empty()) { + emess(3, "missing source and target coordinate systems"); + } + + const char *const optionsProj4Mode[] = {"USE_PROJ4_INIT_RULES=YES", + nullptr}; + const char *const *optionsImportCRS = + proj_context_get_use_proj4_init_rules(nullptr, TRUE) ? optionsProj4Mode + : nullptr; + + PJ_OBJ *src = nullptr; + if (!fromStr.empty()) { + bool ignored; + src = instanciate_crs(fromStr, optionsImportCRS, srcIsGeog, + srcToRadians, ignored); + if (!src) { + emess(3, "cannot instanciate source coordinate system"); + } + } + + PJ_OBJ *dst = nullptr; + if (!toStr.empty()) { + dst = instanciate_crs(toStr, optionsImportCRS, destIsGeog, + destToRadians, destIsLatLong); + if (!dst) { + emess(3, "cannot instanciate target coordinate system"); + } + } + + if (toStr.empty()) { + assert(src); + toStr = get_geog_crs_proj_string_from_proj_crs(src, destToRadians, + destIsLatLong); + if (toStr.empty()) { + emess(3, + "missing target CRS and source CRS is not a projected CRS"); + } + destIsGeog = true; + } else if (fromStr.empty()) { + assert(dst); + bool ignored; + fromStr = + get_geog_crs_proj_string_from_proj_crs(dst, srcToRadians, ignored); + if (fromStr.empty()) { + emess(3, + "missing source CRS and target CRS is not a projected CRS"); + } + srcIsGeog = true; + } + + proj_obj_destroy(src); + proj_obj_destroy(dst); + + transformation = proj_create_crs_to_crs(nullptr, fromStr.c_str(), + toStr.c_str(), nullptr); + if (!transformation) { + emess(3, "cannot initialize transformation\ncause: %s", + pj_strerrno(pj_errno)); + } + + if (use_env_locale) { + /* Restore C locale to avoid issues in parsing/outputting numbers*/ + setlocale(LC_ALL, "C"); + } + + if (mon) { + printf("%c ---- From Coordinate System ----\n", tag); + printf("%s\n", fromStr.c_str()); + printf("%c ---- To Coordinate System ----\n", tag); + printf("%s\n", toStr.c_str()); + } + + /* set input formatting control */ + if (!srcIsGeog) + informat = strtod; + else { + informat = dmstor; + } + + if (!destIsGeog && !oform) + oform = "%.2f"; + + /* process input file list */ + for (; eargc--; ++eargv) { + if (**eargv == '-') { + fid = stdin; + emess_dat.File_name = const_cast(""); + + } else { + if ((fid = fopen(*eargv, "rt")) == nullptr) { + emess(-2, *eargv, "input file"); + continue; + } + emess_dat.File_name = *eargv; + } + emess_dat.File_line = 0; + process(fid); + fclose(fid); + emess_dat.File_name = nullptr; + } + + proj_destroy(transformation); + + pj_deallocate_grids(); + + exit(0); /* normal completion */ +} diff --git a/src/apps/emess.cpp b/src/apps/emess.cpp new file mode 100644 index 00000000..144e9e23 --- /dev/null +++ b/src/apps/emess.cpp @@ -0,0 +1,68 @@ +/* Error message processing */ + +#ifdef _MSC_VER +# ifndef _CRT_SECURE_NO_DEPRECATE +# define _CRT_SECURE_NO_DEPRECATE +# endif +# ifndef _CRT_NONSTDC_NO_DEPRECATE +# define _CRT_NONSTDC_NO_DEPRECATE +# endif +#endif + +#ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H +#define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H +#endif + +#include +#include +#include +#include +#include + +#include "proj_api.h" +#define EMESS_ROUTINE +#include "emess.h" + + void +emess(int code, const char *fmt, ...) { + va_list args; + + va_start(args, fmt); + /* prefix program name, if given */ + if (fmt != nullptr) + (void)fprintf(stderr,"%s\n<%s>: ",pj_get_release(), + emess_dat.Prog_name); + /* print file name and line, if given */ + if (emess_dat.File_name != nullptr && *emess_dat.File_name) { + (void)fprintf(stderr,"while processing file: %s", emess_dat.File_name); + if (emess_dat.File_line > 0) + (void)fprintf(stderr,", line %d\n", emess_dat.File_line); + else + (void)fputc('\n', stderr); + } else + putc('\n', stderr); + /* if |code|==2, print errno code data */ + if (code == 2 || code == -2) + { + int my_errno = errno; +#ifdef HAVE_STRERROR + const char* my_strerror = strerror(my_errno); +#endif +#ifndef HAVE_STRERROR + const char* my_strerror = ""; +#endif + (void)fprintf(stderr, "Sys errno: %d: %s\n", + my_errno, my_strerror); + } + + /* post remainder of call data */ + (void)vfprintf(stderr,fmt,args); + va_end(args); + /* die if code positive */ + if (code > 0) { + (void)fputs("\nprogram abnormally terminated\n", stderr); + exit(code); + } + else + putc('\n', stderr); +} diff --git a/src/apps/emess.h b/src/apps/emess.h new file mode 100644 index 00000000..4c6f6783 --- /dev/null +++ b/src/apps/emess.h @@ -0,0 +1,29 @@ +/* Error message processing header file */ +#ifndef EMESS_H +#define EMESS_H + +struct EMESS { + char *File_name, /* input file name */ + *Prog_name; /* name of program */ + int File_line; /* approximate line read + where error occurred */ +}; + +#ifdef EMESS_ROUTINE /* use type */ +/* for emess procedure */ +struct EMESS emess_dat = { nullptr, nullptr, 0 }; + +#ifdef sun /* Archaic SunOs 4.1.1, etc. */ +extern char *sys_errlist[]; +#define strerror(n) (sys_errlist[n]) +#endif + +#else /* for for calling procedures */ + +extern struct EMESS emess_dat; + +#endif /* use type */ + +void emess(int, const char *, ...); + +#endif /* end EMESS_H */ diff --git a/src/apps/gen_cheb.cpp b/src/apps/gen_cheb.cpp new file mode 100644 index 00000000..4ba514d4 --- /dev/null +++ b/src/apps/gen_cheb.cpp @@ -0,0 +1,106 @@ +/* generates 'T' option output */ +#define PJ_LIB__ +#include "projects.h" +#include +#include +#include +#include "emess.h" +#ifndef COEF_LINE_MAX +#define COEF_LINE_MAX 50 +#endif + +static double strtod_type_safe(const char *s, const char ** endptr) +{ + char* l_endptr = nullptr; + double ret= strtod(s, &l_endptr); + if( endptr ) + *endptr = static_cast(l_endptr); + return ret; +} + +static double dmstor_type_safe(const char *s, const char ** endptr) +{ + char* l_endptr = nullptr; + double ret= dmstor(s, &l_endptr); + if( endptr ) + *endptr = static_cast(l_endptr); + return ret; +} + +static long strtol_type_safe(const char *s, const char ** endptr, int base) +{ + char* l_endptr = nullptr; + long ret = strtol(s, &l_endptr, base); + if( endptr ) + *endptr = static_cast(l_endptr); + return ret; +} + + +/* FIXME: put the declaration in a header. Also used in proj.c */ +void gen_cheb(int inverse, projUV (*proj)(projUV), const char *s, PJ *P, + int iargc, char **iargv); +extern void p_series(Tseries *, FILE *, char *); + +void gen_cheb(int inverse, projUV (*proj)(projUV), const char *s, PJ *P, + int iargc, char **iargv) { + long NU = 15, NV = 15; + int errin = 0, pwr; + long res = -1; + char *arg, fmt[32]; + projUV low, upp, resid; + Tseries *F; + double (*input)(const char *, const char **); + + input = inverse ? strtod_type_safe : dmstor_type_safe; + if (*s) low.u = input(s, &s); else { low.u = 0; ++errin; } + if (*s == ',') upp.u = input(s+1, &s); else { upp.u = 0; ++errin; } + if (*s == ',') low.v = input(s+1, &s); else { low.v = 0; ++errin; } + if (*s == ',') upp.v = input(s+1, &s); else { upp.v = 0; ++errin; } + if (errin) + emess(16,"null or absent -T parameters"); + if (*s == ',') if (*++s != ',') res = strtol_type_safe(s, &s, 10); + if (*s == ',') if (*++s != ',') NU = strtol_type_safe(s, &s, 10); + if (*s == ',') if (*++s != ',') NV = strtol_type_safe(s, &s, 10); + pwr = s && *s && !strcmp(s, ",P"); + (void)printf("#proj_%s\n# run-line:\n", + pwr ? "Power" : "Chebyshev"); + if (iargc > 0) { /* proj execution audit trail */ + int n = 0, L; + + for ( ; iargc ; --iargc) { + arg = *iargv++; + if (*arg != '+') { + if (!n) { putchar('#'); ++n; } + (void)printf(" %s%n",arg, &L); + if ((n += L) > COEF_LINE_MAX) { putchar('\n'); n = 0; } + } + } + if (n) putchar('\n'); + } + (void)printf("# projection parameters\n"); + pj_pr_list(P); + if (low.u == upp.u || low.v >= upp.v) + emess(16,"approx. argument range error"); + if (low.u > upp.u) + low.u -= M_TWOPI; + if (NU < 2 || NV < 2 || NU > INT_MAX || NV > INT_MAX) + emess(16,"approx. work dimensions (%ld %ld) too small or large",NU,NV); + if (!(F = mk_cheby(low, upp, pow(10., (double)res)*.5, &resid, proj, + (int)NU, (int)NV, pwr))) + emess(16,"generation of approx failed\nreason: %s\n", + pj_strerrno(errno)); + (void)printf("%c,%.12g,%.12g,%.12g,%.12g,%.12g\n",inverse?'I':'F', + P->lam0*RAD_TO_DEG, + low.u*(inverse?1.:RAD_TO_DEG),upp.u*(inverse?1.:RAD_TO_DEG), + low.v*(inverse?1.:RAD_TO_DEG),upp.v*(inverse?1.:RAD_TO_DEG)); + if (pwr) + strcpy(fmt, "%.15g"); + else if (res <= 0) + (void)sprintf(fmt,"%%.%ldf",-res+1); + else + (void)strcpy(fmt,"%.0f"); + p_series(F, stdout, fmt); + (void)printf("# |u,v| sums %g %g\n#end_proj_%s\n", + resid.u, resid.v, pwr ? "Power" : "Chebyshev"); +} diff --git a/src/apps/geod.cpp b/src/apps/geod.cpp new file mode 100644 index 00000000..7b6367c6 --- /dev/null +++ b/src/apps/geod.cpp @@ -0,0 +1,241 @@ +/* <<<< Geodesic filter program >>>> */ + +#include "proj.h" +# include "projects.h" +# include "geod_interface.h" +# include "emess.h" +# include +# include +# include + +# define MAXLINE 200 +# define MAX_PARGS 50 +# define TAB putchar('\t') + static int +fullout = 0, /* output full set of geodesic values */ +tag = '#', /* beginning of line tag character */ +pos_azi = 0, /* output azimuths as positive values */ +inverse = 0; /* != 0 then inverse geodesic */ + +static const char *oform = nullptr; /* output format for decimal degrees */ +static const char *osform = "%.3f"; /* output format for S */ + +static char pline[50]; /* work string */ +static const char *usage = +"%s\nusage: %s [ -afFIlptwW [args] ] [ +opts[=arg] ] [ files ]\n"; + + static void +printLL(double p, double l) { + if (oform) { + (void)printf(oform, p * RAD_TO_DEG); TAB; + (void)printf(oform, l * RAD_TO_DEG); + } else { + (void)fputs(rtodms(pline, p, 'N', 'S'),stdout); TAB; + (void)fputs(rtodms(pline, l, 'E', 'W'),stdout); + } +} + static void +do_arc(void) { + double az; + + printLL(phi2, lam2); putchar('\n'); + for (az = al12; n_alpha--; ) { + al12 = az = adjlon(az + del_alpha); + geod_pre(); + geod_for(); + printLL(phi2, lam2); putchar('\n'); + } +} + static void /* generate intermediate geodesic coordinates */ +do_geod(void) { + double phil, laml, del_S; + + phil = phi2; + laml = lam2; + printLL(phi1, lam1); putchar('\n'); + for ( geod_S = del_S = geod_S / n_S; --n_S; geod_S += del_S) { + geod_for(); + printLL(phi2, lam2); putchar('\n'); + } + printLL(phil, laml); putchar('\n'); +} + static void /* file processing function */ +process(FILE *fid) { + char line[MAXLINE+3], *s; + + for (;;) { + ++emess_dat.File_line; + if (!(s = fgets(line, MAXLINE, fid))) + break; + if (!strchr(s, '\n')) { /* overlong line */ + int c; + strcat(s, "\n"); + /* gobble up to newline */ + while ((c = fgetc(fid)) != EOF && c != '\n') ; + } + if (*s == tag) { + fputs(line, stdout); + continue; + } + phi1 = dmstor(s, &s); + lam1 = dmstor(s, &s); + if (inverse) { + phi2 = dmstor(s, &s); + lam2 = dmstor(s, &s); + geod_inv(); + } else { + al12 = dmstor(s, &s); + geod_S = strtod(s, &s) * to_meter; + geod_pre(); + geod_for(); + } + if (!*s && (s > line)) --s; /* assumed we gobbled \n */ + if (pos_azi) { + if (al12 < 0.) al12 += M_TWOPI; + if (al21 < 0.) al21 += M_TWOPI; + } + if (fullout) { + printLL(phi1, lam1); TAB; + printLL(phi2, lam2); TAB; + if (oform) { + (void)printf(oform, al12 * RAD_TO_DEG); TAB; + (void)printf(oform, al21 * RAD_TO_DEG); TAB; + (void)printf(osform, geod_S * fr_meter); + } else { + (void)fputs(rtodms(pline, al12, 0, 0), stdout); TAB; + (void)fputs(rtodms(pline, al21, 0, 0), stdout); TAB; + (void)printf(osform, geod_S * fr_meter); + } + } else if (inverse) + if (oform) { + (void)printf(oform, al12 * RAD_TO_DEG); TAB; + (void)printf(oform, al21 * RAD_TO_DEG); TAB; + (void)printf(osform, geod_S * fr_meter); + } else { + (void)fputs(rtodms(pline, al12, 0, 0), stdout); TAB; + (void)fputs(rtodms(pline, al21, 0, 0), stdout); TAB; + (void)printf(osform, geod_S * fr_meter); + } + else { + printLL(phi2, lam2); TAB; + if (oform) + (void)printf(oform, al21 * RAD_TO_DEG); + else + (void)fputs(rtodms(pline, al21, 0, 0), stdout); + } + (void)fputs(s, stdout); + } +} + +static char *pargv[MAX_PARGS]; +static int pargc = 0; + +int main(int argc, char **argv) { + char *arg, **eargv = argv; + FILE *fid; + static int eargc = 0, c; + + if ((emess_dat.Prog_name = strrchr(*argv,'/')) != nullptr) ++emess_dat.Prog_name; + else emess_dat.Prog_name = *argv; + inverse = ! strncmp(emess_dat.Prog_name, "inv", 3); + if (argc <= 1 ) { + (void)fprintf(stderr, usage, pj_get_release(), + emess_dat.Prog_name); + exit (0); + } + /* process run line arguments */ + while (--argc > 0) { /* collect run line arguments */ + if(**++argv == '-') for(arg = *argv;;) { + switch(*++arg) { + case '\0': /* position of "stdin" */ + if (arg[-1] == '-') eargv[eargc++] = const_cast("-"); + break; + case 'a': /* output full set of values */ + fullout = 1; + continue; + case 'I': /* alt. inverse spec. */ + inverse = 1; + continue; + case 't': /* set col. one char */ + if (arg[1]) tag = *++arg; + else emess(1,"missing -t col. 1 tag"); + continue; + case 'W': /* specify seconds precision */ + case 'w': /* -W for constant field width */ + if ((c = arg[1]) && isdigit(c)) { + set_rtodms(c - '0', *arg == 'W'); + ++arg; + } else + emess(1,"-W argument missing or non-digit"); + continue; + case 'f': /* alternate output format degrees or xy */ + if (--argc <= 0) +noargument: emess(1,"missing argument for -%c",*arg); + oform = *++argv; + continue; + case 'F': /* alternate output format degrees or xy */ + if (--argc <= 0) goto noargument; + osform = *++argv; + continue; + case 'l': + if (!arg[1] || arg[1] == 'e') { /* list of ellipsoids */ + const struct PJ_ELLPS *le; + + for (le=proj_list_ellps(); le->id ; ++le) + (void)printf("%9s %-16s %-16s %s\n", + le->id, le->major, le->ell, le->name); + } else if (arg[1] == 'u') { /* list of units */ + const struct PJ_UNITS *lu; + + for (lu = proj_list_units();lu->id ; ++lu) + (void)printf("%12s %-20s %s\n", + lu->id, lu->to_meter, lu->name); + } else + emess(1,"invalid list option: l%c",arg[1]); + exit( 0 ); + case 'p': /* output azimuths as positive */ + pos_azi = 1; + continue; + default: + emess(1, "invalid option: -%c",*arg); + break; + } + break; + } else if (**argv == '+') /* + argument */ + if (pargc < MAX_PARGS) + pargv[pargc++] = *argv + 1; + else + emess(1,"overflowed + argument table"); + else /* assumed to be input file name(s) */ + eargv[eargc++] = *argv; + } + /* done with parameter and control input */ + geod_set(pargc, pargv); /* setup projection */ + if ((n_alpha || n_S) && eargc) + emess(1,"files specified for arc/geodesic mode"); + if (n_alpha) + do_arc(); + else if (n_S) + do_geod(); + else { /* process input file list */ + if (eargc == 0) /* if no specific files force sysin */ + eargv[eargc++] = const_cast("-"); + for ( ; eargc-- ; ++eargv) { + if (**eargv == '-') { + fid = stdin; + emess_dat.File_name = const_cast(""); + } else { + if ((fid = fopen(*eargv, "r")) == nullptr) { + emess(-2, *eargv, "input file"); + continue; + } + emess_dat.File_name = *eargv; + } + emess_dat.File_line = 0; + process(fid); + (void)fclose(fid); + emess_dat.File_name = (char *)nullptr; + } + } + exit(0); /* normal completion */ +} diff --git a/src/apps/geod_interface.cpp b/src/apps/geod_interface.cpp new file mode 100644 index 00000000..a30377ac --- /dev/null +++ b/src/apps/geod_interface.cpp @@ -0,0 +1,33 @@ +#include "projects.h" +#include "geod_interface.h" + +void geod_ini(void) { + geod_init(&GlobalGeodesic, geod_a, geod_f); +} + +void geod_pre(void) { + double + lat1 = phi1 / DEG_TO_RAD, lon1 = lam1 / DEG_TO_RAD, + azi1 = al12 / DEG_TO_RAD; + geod_lineinit(&GlobalGeodesicLine, &GlobalGeodesic, lat1, lon1, azi1, 0U); +} + +void geod_for(void) { + double + s12 = geod_S, lat2, lon2, azi2; + geod_position(&GlobalGeodesicLine, s12, &lat2, &lon2, &azi2); + azi2 += azi2 >= 0 ? -180 : 180; /* Compute back azimuth */ + phi2 = lat2 * DEG_TO_RAD; + lam2 = lon2 * DEG_TO_RAD; + al21 = azi2 * DEG_TO_RAD; +} + +void geod_inv(void) { + double + lat1 = phi1 / DEG_TO_RAD, lon1 = lam1 / DEG_TO_RAD, + lat2 = phi2 / DEG_TO_RAD, lon2 = lam2 / DEG_TO_RAD, + azi1, azi2, s12; + geod_inverse(&GlobalGeodesic, lat1, lon1, lat2, lon2, &s12, &azi1, &azi2); + azi2 += azi2 >= 0 ? -180 : 180; /* Compute back azimuth */ + al12 = azi1 * DEG_TO_RAD; al21 = azi2 * DEG_TO_RAD; geod_S = s12; +} diff --git a/src/apps/geod_interface.h b/src/apps/geod_interface.h new file mode 100644 index 00000000..255d505a --- /dev/null +++ b/src/apps/geod_interface.h @@ -0,0 +1,45 @@ +#if !defined(GEOD_INTERFACE_H) +#define GEOD_INTERFACE_H + +#include "geodesic.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _IN_GEOD_SET +# define GEOD_EXTERN extern +#else +# define GEOD_EXTERN +#endif + +GEOD_EXTERN struct geodesic { + double A, FLAT, LAM1, PHI1, ALPHA12, LAM2, PHI2, ALPHA21, DIST; +} GEODESIC; + +# define geod_a GEODESIC.A +# define geod_f GEODESIC.FLAT +# define lam1 GEODESIC.LAM1 +# define phi1 GEODESIC.PHI1 +# define al12 GEODESIC.ALPHA12 +# define lam2 GEODESIC.LAM2 +# define phi2 GEODESIC.PHI2 +# define al21 GEODESIC.ALPHA21 +# define geod_S GEODESIC.DIST + +GEOD_EXTERN struct geod_geodesic GlobalGeodesic; +GEOD_EXTERN struct geod_geodesicline GlobalGeodesicLine; +GEOD_EXTERN int n_alpha, n_S; +GEOD_EXTERN double to_meter, fr_meter, del_alpha; + +void geod_set(int, char **); +void geod_ini(void); +void geod_pre(void); +void geod_for(void); +void geod_inv(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/apps/geod_set.cpp b/src/apps/geod_set.cpp new file mode 100644 index 00000000..b9e9c42f --- /dev/null +++ b/src/apps/geod_set.cpp @@ -0,0 +1,75 @@ +#define _IN_GEOD_SET + +#include +#include +#include + +#include "proj.h" +#include "projects.h" +#include "geod_interface.h" +#include "emess.h" + + void +geod_set(int argc, char **argv) { + paralist *start = nullptr, *curr; + double es; + char *name; + int i; + + /* put arguments into internal linked list */ + if (argc <= 0) + emess(1, "no arguments in initialization list"); + start = curr = pj_mkparam(argv[0]); + if (!curr) + emess(1, "memory allocation failed"); + for (i = 1; curr != nullptr && i < argc; ++i) { + curr->next = pj_mkparam(argv[i]); + if (!curr->next) + emess(1, "memory allocation failed"); + curr = curr->next; + } + /* set elliptical parameters */ + if (pj_ell_set(pj_get_default_ctx(),start, &geod_a, &es)) emess(1,"ellipse setup failure"); + /* set units */ + if ((name = pj_param(nullptr,start, "sunits").s) != nullptr) { + const char *s; + const struct PJ_UNITS *unit_list = proj_list_units(); + for (i = 0; (s = unit_list[i].id) && strcmp(name, s) ; ++i) ; + if (!s) + emess(1,"%s unknown unit conversion id", name); + to_meter = unit_list[i].factor; + fr_meter = 1 / to_meter; + } else + to_meter = fr_meter = 1; + geod_f = es/(1 + sqrt(1 - es)); + geod_ini(); + /* check if line or arc mode */ + if (pj_param(nullptr,start, "tlat_1").i) { + double del_S; +#undef f + phi1 = pj_param(nullptr,start, "rlat_1").f; + lam1 = pj_param(nullptr,start, "rlon_1").f; + if (pj_param(nullptr,start, "tlat_2").i) { + phi2 = pj_param(nullptr,start, "rlat_2").f; + lam2 = pj_param(nullptr,start, "rlon_2").f; + geod_inv(); + geod_pre(); + } else if ((geod_S = pj_param(nullptr,start, "dS").f) != 0.) { + al12 = pj_param(nullptr,start, "rA").f; + geod_pre(); + geod_for(); + } else emess(1,"incomplete geodesic/arc info"); + if ((n_alpha = pj_param(nullptr,start, "in_A").i) > 0) { + if ((del_alpha = pj_param(nullptr,start, "rdel_A").f) == 0.0) + emess(1,"del azimuth == 0"); + } else if ((del_S = fabs(pj_param(nullptr,start, "ddel_S").f)) != 0.) { + n_S = (int)(geod_S / del_S + .5); + } else if ((n_S = pj_param(nullptr,start, "in_S").i) <= 0) + emess(1,"no interval divisor selected"); + } + /* free up linked list */ + for ( ; start; start = curr) { + curr = start->next; + pj_dalloc(start); + } +} diff --git a/src/apps/gie.cpp b/src/apps/gie.cpp new file mode 100644 index 00000000..3e4770a2 --- /dev/null +++ b/src/apps/gie.cpp @@ -0,0 +1,1458 @@ +/*********************************************************************** + + gie - The Geospatial Integrity Investigation Environment + +************************************************************************ + +The Geospatial Integrity Investigation Environment "gie" is a modest +regression testing environment for the PROJ.4 transformation library. + +Its primary design goal was to be able to replace those thousands of +lines of regression testing code that are (at time of writing) part +of PROJ.4, while not requiring any other kind of tooling than the same +C compiler already employed for compiling the library. + +The basic functionality of the gie command language is implemented +through just 3 command verbs: + +operation, which defines the PROJ.4 operation to test, +accept, which defines the input coordinate to read, and +expect, which defines the result to expect. + +E.g: + +operation +proj=utm +zone=32 +ellps=GRS80 +accept 12 55 +expect 691_875.632_14 6_098_907.825_05 + +Note that gie accepts the underscore ("_") as a thousands separator. +It is not required (in fact, it is entirely ignored by the input +routine), but it significantly improves the readability of the very +long strings of numbers typically required in projected coordinates. + +By default, gie considers the EXPECTation met, if the result comes to +within 0.5 mm of the expected. This default can be changed using the +'tolerance' command verb (and yes, I know, linguistically speaking, both +"operation" and "tolerance" are nouns, not verbs). See the first +few hundred lines of the "builtins.gie" test file for more details of +the command verbs available (verbs of both the VERBal and NOUNistic +persuation). + +-- + +But more importantly than being an acronym for "Geospatial Integrity +Investigation Environment", gie were also the initials, user id, and +USGS email address of Gerald Ian Evenden (1935--2016), the geospatial +visionary, who, already in the 1980s, started what was to become the +PROJ.4 of today. + +Gerald's clear vision was that map projections are *just special +functions*. Some of them rather complex, most of them of two variables, +but all of them *just special functions*, and not particularly more +special than the sin(), cos(), tan(), and hypot() already available in +the C standard library. + +And hence, according to Gerald, *they should not be particularly much +harder to use*, for a programmer, than the sin()s, tan()s and hypot()s +so readily available. + +Gerald's ingenuity also showed in the implementation of the vision, +where he devised a comprehensive, yet simple, system of key-value +pairs for parameterising a map projection, and the highly flexible +PJ struct, storing run-time compiled versions of those key-value pairs, +hence making a map projection function call, pj_fwd(PJ, point), as easy +as a traditional function call like hypot(x,y). + +While today, we may have more formally well defined metadata systems +(most prominent the OGC WKT2 representation), nothing comes close being +as easily readable ("human compatible") as Gerald's key-value system. +This system in particular, and the PROJ.4 system in general, was +Gerald's great gift to anyone using and/or communicating about geodata. + +It is only reasonable to name a program, keeping an eye on the integrity +of the PROJ.4 system, in honour of Gerald. + +So in honour, and hopefully also in the spirit, of Gerald Ian Evenden +(1935--2016), this is the Geospatial Integrity Investigation Environment. + +************************************************************************ + +Thomas Knudsen, thokn@sdfe.dk, 2017-10-01/2017-10-08 + +************************************************************************ + +* Copyright (c) 2017 Thomas Knudsen +* Copyright (c) 2017, SDFE +* +* 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 +#include +#include +#include +#include +#include +#include +#include + +#include "proj.h" +#include "proj_internal.h" +#include "proj_math.h" +#include "proj_strtod.h" +#include "projects.h" + +#include "optargpm.h" + +/* Package for flexible format I/O - ffio */ +typedef struct ffio { + FILE *f; + const char **tags; + const char *tag; + char *args; + char *next_args; + size_t n_tags; + size_t args_size; + size_t next_args_size; + size_t argc; + size_t lineno, next_lineno; + size_t level; +} ffio; + +static int get_inp (ffio *G); +static int skip_to_next_tag (ffio *G); +static int step_into_gie_block (ffio *G); +static int locate_tag (ffio *G, const char *tag); +static int nextline (ffio *G); +static int at_end_delimiter (ffio *G); +static const char *at_tag (ffio *G); +static int at_decorative_element (ffio *G); +static ffio *ffio_destroy (ffio *G); +static ffio *ffio_create (const char **tags, size_t n_tags, size_t max_record_size); + +static const char *gie_tags[] = { + "", "operation", "use_proj4_init_rules", + "accept", "expect", "roundtrip", "banner", "verbose", + "direction", "tolerance", "ignore", "require_grid", "echo", "skip", "" +}; + +static const size_t n_gie_tags = sizeof gie_tags / sizeof gie_tags[0]; + + +int main(int argc, char **argv); + +static int dispatch (const char *cmnd, const char *args); +static int errmsg (int errlev, const char *msg, ...); +static int errno_from_err_const (const char *err_const); +static int list_err_codes (void); +static int process_file (const char *fname); + +static const char *column (const char *buf, int n); +static const char *err_const_from_errno (int err); + + + +#define SKIP -1 + +#define MAX_OPERATION 10000 + +typedef struct { + char operation[MAX_OPERATION+1]; + PJ *P; + PJ_COORD a, b, c, e; + PJ_DIRECTION dir; + int verbosity; + int skip; + int op_id; + int op_ok, op_ko, op_skip; + int total_ok, total_ko, total_skip; + int grand_ok, grand_ko, grand_skip; + size_t operation_lineno; + size_t dimensions_given, dimensions_given_at_last_accept; + double tolerance; + int use_proj4_init_rules; + int ignore; + int skip_test; + const char *curr_file; + FILE *fout; +} gie_ctx; + +ffio *F = nullptr; + +static gie_ctx T; +int tests=0, succs=0, succ_fails=0, fail_fails=0, succ_rtps=0, fail_rtps=0; + +static const char delim[] = {"-------------------------------------------------------------------------------\n"}; + + +static const char usage[] = { + "--------------------------------------------------------------------------------\n" + "Usage: %s [-options]... infile...\n" + "--------------------------------------------------------------------------------\n" + "Options:\n" + "--------------------------------------------------------------------------------\n" + " -h Help: print this usage information\n" + " -o /path/to/file Specify output file name\n" + " -v Verbose: Provide non-essential informational output.\n" + " Repeat -v for more verbosity (e.g. -vv)\n" + " -q Quiet: Opposite of verbose. In quiet mode not even errors\n" + " are reported. Only interaction is through the return code\n" + " (0 on success, non-zero indicates number of FAILED tests)\n" + " -l List the PROJ internal system error codes\n" + "--------------------------------------------------------------------------------\n" + "Long Options:\n" + "--------------------------------------------------------------------------------\n" + " --output Alias for -o\n" + " --verbose Alias for -v\n" + " --help Alias for -h\n" + " --list Alias for -l\n" + " --version Print version number\n" + "--------------------------------------------------------------------------------\n" + "Examples:\n" + "--------------------------------------------------------------------------------\n" + "1. Run all tests in file \"corner-cases.gie\", providing much extra information\n" + " gie -vvvv corner-cases.gie\n" + "2. Run all tests in files \"foo\" and \"bar\", providing info on failures only\n" + " gie foo bar\n" + "--------------------------------------------------------------------------------\n" +}; + +int main (int argc, char **argv) { + int i; + const char *longflags[] = {"v=verbose", "q=quiet", "h=help", "l=list", "version", nullptr}; + const char *longkeys[] = {"o=output", nullptr}; + OPTARGS *o; + + memset (&T, 0, sizeof (T)); + T.dir = PJ_FWD; + T.verbosity = 1; + T.tolerance = 5e-4; + T.ignore = 5555; /* Error code that will not be issued by proj_create() */ + T.use_proj4_init_rules = FALSE; + + o = opt_parse (argc, argv, "hlvq", "o", longflags, longkeys); + if (nullptr==o) + return 0; + + if (opt_given (o, "h") || argc==1) { + printf (usage, o->progname); + free (o); + return 0; + } + + + if (opt_given (o, "version")) { + fprintf (stdout, "%s: %s\n", o->progname, pj_get_release ()); + free (o); + return 0; + } + + T.verbosity = opt_given (o, "q"); + if (T.verbosity) + T.verbosity = -1; + if (T.verbosity != -1) + T.verbosity = opt_given (o, "v") + 1; + + T.fout = stdout; + if (opt_given (o, "o")) + T.fout = fopen (opt_arg (o, "output"), "rt"); + + if (nullptr==T.fout) { + fprintf (stderr, "%s: Cannot open '%s' for output\n", o->progname, opt_arg (o, "output")); + free (o); + return 1; + } + + if (opt_given (o, "l")) { + free (o); + return list_err_codes (); + } + + if (0==o->fargc) { + if (T.verbosity==-1) + return -1; + fprintf (T.fout, "Nothing to do\n"); + free (o); + return 0; + } + + F = ffio_create (gie_tags, n_gie_tags, 1000); + if (nullptr==F) { + fprintf (stderr, "%s: No memory\n", o->progname); + free (o); + return 1; + } + + for (i = 0; i < o->fargc; i++) + process_file (o->fargv[i]); + + if (T.verbosity > 0) { + if (o->fargc > 1) { + fprintf (T.fout, "%sGrand total: %d. Success: %d, Skipped: %d, Failure: %d\n", + delim, T.grand_ok+T.grand_ko+T.grand_skip, T.grand_ok, T.grand_skip, + T.grand_ko); + } + fprintf (T.fout, "%s", delim); + if (T.verbosity > 1) { + fprintf (T.fout, "Failing roundtrips: %4d, Succeeding roundtrips: %4d\n", fail_rtps, succ_rtps); + fprintf (T.fout, "Failing failures: %4d, Succeeding failures: %4d\n", fail_fails, succ_fails); + fprintf (T.fout, "Internal counters: %4.4d(%4.4d)\n", tests, succs); + fprintf (T.fout, "%s", delim); + } + } + else + if (T.grand_ko) + fprintf (T.fout, "Failures: %d", T.grand_ko); + + if (stdout != T.fout) + fclose (T.fout); + + free (o); + ffio_destroy (F); + return T.grand_ko; +} + +static int another_failure (void) { + T.op_ko++; + T.total_ko++; + proj_errno_reset (T.P); + return 0; +} + +static int another_skip (void) { + T.op_skip++; + T.total_skip++; + return 0; +} + +static int another_success (void) { + T.op_ok++; + T.total_ok++; + proj_errno_reset (T.P); + return 0; +} + +static int another_succeeding_failure (void) { + succ_fails++; + return another_success (); +} + +static int another_failing_failure (void) { + fail_fails++; + return another_failure (); +} + +static int another_succeeding_roundtrip (void) { + succ_rtps++; + return another_success (); +} + +static int another_failing_roundtrip (void) { + fail_rtps++; + return another_failure (); +} + +static int process_file (const char *fname) { + FILE *f; + + F->lineno = F->next_lineno = F->level = 0; + T.op_ok = T.total_ok = 0; + T.op_ko = T.total_ko = 0; + T.op_skip = T.total_skip = 0; + + if (T.skip) { + proj_destroy (T.P); + T.P = nullptr; + return 0; + } + + f = fopen (fname, "rt"); + if (nullptr==f) { + if (T.verbosity > 0) { + fprintf (T.fout, "%sCannot open spec'd input file '%s' - bye!\n", delim, fname); + return 2; + } + errmsg (2, "Cannot open spec'd input file '%s' - bye!\n", fname); + } + F->f = f; + + if (T.verbosity > 0) + fprintf (T.fout, "%sReading file '%s'\n", delim, fname); + T.curr_file = fname; + + while (get_inp(F)) { + if (SKIP==dispatch (F->tag, F->args)) { + proj_destroy (T.P); + T.P = nullptr; + return 0; + } + } + + fclose (f); + F->lineno = F->next_lineno = 0; + + T.grand_ok += T.total_ok; + T.grand_ko += T.total_ko; + T.grand_skip += T.grand_skip; + if (T.verbosity > 0) { + fprintf (T.fout, "%stotal: %2d tests succeeded, %2d tests skipped, %2d tests %s\n", + delim, T.total_ok, T.total_skip, T.total_ko, + T.total_ko? "FAILED!": "failed."); + } + if (F->level==0) + return errmsg (-3, "File '%s':Missing '' cmnd - bye!\n", fname); + if (F->level && F->level%2) + return errmsg (-4, "File '%s':Missing '' cmnd - bye!\n", fname); + return 0; +} + + +/*****************************************************************************/ +const char *column (const char *buf, int n) { +/***************************************************************************** +Return a pointer to the n'th column of buf. Column numbers start at 0. +******************************************************************************/ + int i; + if (n <= 0) + return buf; + for (i = 0; i < n; i++) { + while (isspace(*buf)) + buf++; + if (i == n - 1) + break; + while ((0 != *buf) && !isspace(*buf)) + buf++; + } + return buf; +} + + +/*****************************************************************************/ +static double strtod_scaled (const char *args, double default_scale) { +/***************************************************************************** +Interpret as a numeric followed by a linear decadal prefix. +Return the properly scaled numeric +******************************************************************************/ + double s; + const double GRS80_DEG = 111319.4908; /* deg-to-m at equator of GRS80 */ + const char *endp = args; + s = proj_strtod (args, (char **) &endp); + if (args==endp) + return HUGE_VAL; + + endp = column (args, 2); + + if (0==strcmp(endp, "km")) + s *= 1000; + else if (0==strcmp(endp, "m")) + s *= 1; + else if (0==strcmp(endp, "dm")) + s /= 10; + else if (0==strcmp(endp, "cm")) + s /= 100; + else if (0==strcmp(endp, "mm")) + s /= 1000; + else if (0==strcmp(endp, "um")) + s /= 1e6; + else if (0==strcmp(endp, "nm")) + s /= 1e9; + else if (0==strcmp(endp, "rad")) + s = GRS80_DEG * proj_todeg (s); + else if (0==strcmp(endp, "deg")) + s = GRS80_DEG * s; + else + s *= default_scale; + return s; +} + + +static int banner (const char *args) { + char dots[] = {"..."}, nodots[] = {""}, *thedots = nodots; + if (strlen(args) > 70) + thedots = dots; + fprintf (T.fout, "%s%-70.70s%s\n", delim, args, thedots); + return 0; +} + + +static int tolerance (const char *args) { + T.tolerance = strtod_scaled (args, 1); + if (HUGE_VAL==T.tolerance) { + T.tolerance = 0.0005; + return 1; + } + return 0; +} + + +static int use_proj4_init_rules (const char *args) { + T.use_proj4_init_rules = strcmp(args, "true") == 0; + return 0; +} + +static int ignore (const char *args) { + T.ignore = errno_from_err_const (column (args, 1)); + return 0; +} + +static int require_grid (const char *args) { + PJ_GRID_INFO grid_info; + const char* grid_filename = column (args, 1); + grid_info = proj_grid_info(grid_filename); + if( strlen(grid_info.filename) == 0 ) { + if (T.verbosity > 1) { + fprintf (T.fout, "Test skipped because of missing grid %s\n", + grid_filename); + } + T.skip_test = 1; + } + return 0; +} + +static int direction (const char *args) { + const char *endp = args; + while (isspace (*endp)) + endp++; + switch (*endp) { + case 'F': + case 'f': + T.dir = PJ_FWD; + break; + case 'I': + case 'i': + case 'R': + case 'r': + T.dir = PJ_INV; + break; + default: + return 1; + } + + return 0; +} + + +static void finish_previous_operation (const char *args) { + if (T.verbosity > 1 && T.op_id > 1 && T.op_ok+T.op_ko) + fprintf (T.fout, "%s %d tests succeeded, %d tests skipped, %d tests %s\n", + delim, T.op_ok, T.op_skip, T.op_ko, T.op_ko? "FAILED!": "failed."); + (void) args; +} + + + +/*****************************************************************************/ +static int operation (char *args) { +/***************************************************************************** +Define the operation to apply to the input data (in ISO 19100 lingo, +an operation is the general term describing something that can be +either a conversion or a transformation) +******************************************************************************/ + T.op_id++; + + T.operation_lineno = F->lineno; + + strncpy (&(T.operation[0]), F->args, MAX_OPERATION); + T.operation[MAX_OPERATION] = '\0'; + + if (T.verbosity > 1) { + finish_previous_operation (F->args); + banner (args); + } + + + T.op_ok = 0; + T.op_ko = 0; + T.op_skip = 0; + T.skip_test = 0; + + direction ("forward"); + tolerance ("0.5 mm"); + ignore ("pjd_err_dont_skip"); + + proj_errno_reset (T.P); + + if (T.P) + proj_destroy (T.P); + proj_errno_reset (nullptr); + proj_context_use_proj4_init_rules(nullptr, T.use_proj4_init_rules); + + T.P = proj_create (nullptr, F->args); + + /* Checking that proj_create succeeds is first done at "expect" time, */ + /* since we want to support "expect"ing specific error codes */ + + return 0; +} + +static PJ_COORD torad_coord (PJ *P, PJ_DIRECTION dir, PJ_COORD a) { + size_t i, n; + const char *axis = "enut"; + paralist *l = pj_param_exists (P->params, "axis"); + if (l && dir==PJ_INV) + axis = l->param + strlen ("axis="); + n = strlen (axis); + for (i = 0; i < n; i++) + if (strchr ("news", axis[i])) + a.v[i] = proj_torad (a.v[i]); + return a; +} + + +static PJ_COORD todeg_coord (PJ *P, PJ_DIRECTION dir, PJ_COORD a) { + size_t i, n; + const char *axis = "enut"; + paralist *l = pj_param_exists (P->params, "axis"); + if (l && dir==PJ_FWD) + axis = l->param + strlen ("axis="); + n = strlen (axis); + for (i = 0; i < n; i++) + if (strchr ("news", axis[i])) + a.v[i] = proj_todeg (a.v[i]); + return a; +} + + + +/*****************************************************************************/ +static PJ_COORD parse_coord (const char *args) { +/***************************************************************************** +Attempt to interpret args as a PJ_COORD. +******************************************************************************/ + int i; + const char *endp; + const char *dmsendp; + const char *prev = args; + PJ_COORD a = proj_coord (0,0,0,0); + + T.dimensions_given = 0; + for (i = 0; i < 4; i++) { + /* proj_strtod doesn't read values like 123d45'678W so we need a bit */ + /* of help from proj_dmstor. proj_strtod effectively ignores what */ + /* comes after "d", so we use that fact that when dms is larger than */ + /* d the value was stated in "dms" form. */ + /* This could be avoided if proj_dmstor used the same proj_strtod() */ + /* as gie, but that is not the case (yet). When we remove projects.h */ + /* from the public API we can change that. */ + double d = proj_strtod(prev, (char **) &endp); + double dms = PJ_TODEG(proj_dmstor (prev, (char **) &dmsendp)); + /* TODO: When projects.h is removed, call proj_dmstor() in all cases */ + if (d != dms && fabs(d) < fabs(dms) && fabs(dms) < fabs(d) + 1) { + d = dms; + endp = dmsendp; + } + /* A number like -81d00'00.000 will be parsed correctly by both */ + /* proj_strtod and proj_dmstor but only the latter will return */ + /* the correct end-pointer. */ + if (d == dms && endp != dmsendp) + endp = dmsendp; + + /* Break out if there were no more numerals */ + if (prev==endp) + return i > 1? a: proj_coord_error (); + + a.v[i] = d; + prev = endp; + T.dimensions_given++; + } + + return a; +} + + +/*****************************************************************************/ +static int accept (const char *args) { +/***************************************************************************** +Read ("ACCEPT") a 2, 3, or 4 dimensional input coordinate. +******************************************************************************/ + T.a = parse_coord (args); + if (T.verbosity > 3) + fprintf (T.fout, "# %s\n", args); + T.dimensions_given_at_last_accept = T.dimensions_given; + return 0; +} + + +/*****************************************************************************/ +static int roundtrip (const char *args) { +/***************************************************************************** +Check how far we go from the ACCEPTed point when doing successive +back/forward transformation pairs. + +Without args, roundtrip defaults to 100 iterations: + + roundtrip + +With one arg, roundtrip will default to a tolerance of T.tolerance: + + roundtrip ntrips + +With two args: + + roundtrip ntrips tolerance + +Always returns 0. +******************************************************************************/ + int ntrips; + double d, r, ans; + char *endp; + PJ_COORD coo; + + if (nullptr==T.P) { + if (T.ignore == proj_errno(T.P)) + return another_skip(); + + return another_failure (); + } + + ans = proj_strtod (args, &endp); + if (endp==args) { + /* Default to 100 iterations if not args. */ + ntrips = 100; + } else { + if (ans < 1.0 || ans > 1000000.0) { + errmsg (2, "Invalid number of roundtrips: %lf\n", ans); + return another_failing_roundtrip (); + } + ntrips = (int)ans; + } + + d = strtod_scaled (endp, 1); + d = d==HUGE_VAL? T.tolerance: d; + + /* input ("accepted") values - probably in degrees */ + coo = proj_angular_input (T.P, T.dir)? torad_coord (T.P, T.dir, T.a): T.a; + + r = proj_roundtrip (T.P, T.dir, ntrips, &coo); + if (r <= d) + return another_succeeding_roundtrip (); + + if (T.verbosity > -1) { + if (0==T.op_ko && T.verbosity < 2) + banner (T.operation); + fprintf (T.fout, "%s", T.op_ko? " -----\n": delim); + fprintf (T.fout, " FAILURE in %s(%d):\n", opt_strip_path (T.curr_file), (int) F->lineno); + fprintf (T.fout, " roundtrip deviation: %.6f mm, expected: %.6f mm\n", 1000*r, 1000*d); + } + return another_failing_roundtrip (); +} + + +static int expect_message (double d, const char *args) { + another_failure (); + + if (T.verbosity < 0) + return 1; + if (d > 1e6) + d = 999999.999999; + if (0==T.op_ko && T.verbosity < 2) + banner (T.operation); + fprintf (T.fout, "%s", T.op_ko? " -----\n": delim); + + fprintf (T.fout, " FAILURE in %s(%d):\n", opt_strip_path (T.curr_file), (int) F->lineno); + fprintf (T.fout, " expected: %s\n", args); + fprintf (T.fout, " got: %.12f %.12f", T.b.xy.x, T.b.xy.y); + if (T.b.xyzt.t!=0 || T.b.xyzt.z!=0) + fprintf (T.fout, " %.9f", T.b.xyz.z); + if (T.b.xyzt.t!=0) + fprintf (T.fout, " %.9f", T.b.xyzt.t); + fprintf (T.fout, "\n"); + fprintf (T.fout, " deviation: %.6f mm, expected: %.6f mm\n", 1000*d, 1000*T.tolerance); + return 1; +} + + +static int expect_message_cannot_parse (const char *args) { + another_failure (); + if (T.verbosity > -1) { + if (0==T.op_ko && T.verbosity < 2) + banner (T.operation); + fprintf (T.fout, "%s", T.op_ko? " -----\n": delim); + fprintf (T.fout, " FAILURE in %s(%d):\n Too few args: %s\n", opt_strip_path (T.curr_file), (int) F->lineno, args); + } + return 1; +} + +static int expect_failure_with_errno_message (int expected, int got) { + another_failing_failure (); + + if (T.verbosity < 0) + return 1; + if (0==T.op_ko && T.verbosity < 2) + banner (T.operation); + fprintf (T.fout, "%s", T.op_ko? " -----\n": delim); + fprintf (T.fout, " FAILURE in %s(%d):\n", opt_strip_path (T.curr_file), (int) F->lineno); + fprintf (T.fout, " got errno %s (%d): %s\n", err_const_from_errno(got), got, pj_strerrno (got)); + fprintf (T.fout, " expected %s (%d): %s", err_const_from_errno(expected), expected, pj_strerrno (expected)); + fprintf (T.fout, "\n"); + return 1; +} + + +/* For test purposes, we want to call a transformation of the same */ +/* dimensionality as the number of dimensions given in accept */ +static PJ_COORD expect_trans_n_dim (PJ_COORD ci) { + if (4==T.dimensions_given_at_last_accept) + return proj_trans (T.P, T.dir, ci); + + if (3==T.dimensions_given_at_last_accept) + return pj_approx_3D_trans (T.P, T.dir, ci); + + return pj_approx_2D_trans (T.P, T.dir, ci); +} + + +/*****************************************************************************/ +static int expect (const char *args) { +/***************************************************************************** +Tell GIE what to expect, when transforming the ACCEPTed input +******************************************************************************/ + PJ_COORD ci, co, ce; + double d; + int expect_failure = 0; + int expect_failure_with_errno = 0; + + if (0==strncmp (args, "failure", 7)) { + expect_failure = 1; + + /* Option: Fail with an expected errno (syntax: expect failure errno -33) */ + if (0==strncmp (column (args, 2), "errno", 5)) + expect_failure_with_errno = errno_from_err_const (column (args, 3)); + } + + if (T.ignore==proj_errno(T.P)) + return another_skip (); + + if (nullptr==T.P) { + /* If we expect failure, and fail, then it's a success... */ + if (expect_failure) { + /* Failed to fail correctly? */ + if (expect_failure_with_errno && proj_errno (T.P)!=expect_failure_with_errno) + return expect_failure_with_errno_message (expect_failure_with_errno, proj_errno(T.P)); + + return another_succeeding_failure (); + } + + /* Otherwise, it's a true failure */ + banner (T.operation); + errmsg (3, "%sInvalid operation definition in line no. %d:\n %s (errno=%s/%d)\n", + delim, (int) T.operation_lineno, pj_strerrno(proj_errno(T.P)), + err_const_from_errno (proj_errno(T.P)), proj_errno(T.P) + ); + return another_failing_failure (); + } + + /* We may still successfully fail even if the proj_create succeeded */ + if (expect_failure) { + proj_errno_reset (T.P); + + /* Try to carry out the operation - and expect failure */ + ci = proj_angular_input (T.P, T.dir)? torad_coord (T.P, T.dir, T.a): T.a; + co = expect_trans_n_dim (ci); + + if (expect_failure_with_errno) { + if (proj_errno (T.P)==expect_failure_with_errno) + return another_succeeding_failure (); + fprintf (T.fout, "errno=%d, expected=%d\n", proj_errno (T.P), expect_failure_with_errno); + return another_failing_failure (); + } + + + /* Succeeded in failing? - that's a success */ + if (co.xyz.x==HUGE_VAL) + return another_succeeding_failure (); + + /* Failed to fail? - that's a failure */ + banner (T.operation); + errmsg (3, "%sFailed to fail. Operation definition in line no. %d\n", + delim, (int) T.operation_lineno + ); + return another_failing_failure (); + } + + + if (T.verbosity > 3) { + fprintf (T.fout, "%s\n", T.P->inverted? "INVERTED": "NOT INVERTED"); + fprintf (T.fout, "%s\n", T.dir== 1? "forward": "reverse"); + fprintf (T.fout, "%s\n", proj_angular_input (T.P, T.dir)? "angular in": "linear in"); + fprintf (T.fout, "%s\n", proj_angular_output (T.P, T.dir)? "angular out": "linear out"); + fprintf (T.fout, "left: %d right: %d\n", T.P->left, T.P->right); + } + + tests++; + T.e = parse_coord (args); + if (HUGE_VAL==T.e.v[0]) + return expect_message_cannot_parse (args); + + + /* expected angular values, probably in degrees */ + ce = proj_angular_output (T.P, T.dir)? torad_coord (T.P, T.dir, T.e): T.e; + if (T.verbosity > 3) + fprintf (T.fout, "EXPECTS %.12f %.12f %.12f %.12f\n", + ce.v[0],ce.v[1],ce.v[2],ce.v[3]); + + /* input ("accepted") values, also probably in degrees */ + ci = proj_angular_input (T.P, T.dir)? torad_coord (T.P, T.dir, T.a): T.a; + if (T.verbosity > 3) + fprintf (T.fout, "ACCEPTS %.12f %.12f %.12f %.12f\n", + ci.v[0],ci.v[1],ci.v[2],ci.v[3]); + + /* do the transformation, but mask off dimensions not given in expect-ation */ + co = expect_trans_n_dim (ci); + if (T.dimensions_given < 4) + co.v[3] = 0; + if (T.dimensions_given < 3) + co.v[2] = 0; + + /* angular output from proj_trans comes in radians */ + T.b = proj_angular_output (T.P, T.dir)? todeg_coord (T.P, T.dir, co): co; + if (T.verbosity > 3) + fprintf (T.fout, "GOT %.12f %.12f %.12f %.12f\n", + co.v[0],co.v[1],co.v[2],co.v[3]); + +#if 0 + /* We need to handle unusual axis orders - that'll be an item for version 5.1 */ + if (T.P->axisswap) { + ce = proj_trans (T.P->axisswap, T.dir, ce); + co = proj_trans (T.P->axisswap, T.dir, co); + } +#endif + if (proj_angular_output (T.P, T.dir)) + d = proj_lpz_dist (T.P, ce, co); + else + d = proj_xyz_dist (co, ce); + + if (d > T.tolerance) + return expect_message (d, args); + succs++; + + another_success (); + return 0; +} + + + +/*****************************************************************************/ +static int verbose (const char *args) { +/***************************************************************************** +Tell the system how noisy it should be +******************************************************************************/ + int i = (int) proj_atof (args); + + /* if -q/--quiet flag has been given, we do nothing */ + if (T.verbosity < 0) + return 0; + + if (strlen (args)) + T.verbosity = i; + else + T.verbosity++; + return 0; +} + + +/*****************************************************************************/ +static int echo (const char *args) { +/***************************************************************************** +Add user defined noise to the output stream +******************************************************************************/ +fprintf (T.fout, "%s\n", args); + return 0; +} + + + +/*****************************************************************************/ +static int skip (const char *args) { +/***************************************************************************** +Indicate that the remaining material should be skipped. Mostly for debugging. +******************************************************************************/ + T.skip = 1; + (void) args; + F->level = 2; /* Silence complaints about missing element */ + return 0; +} + + +static int dispatch (const char *cmnd, const char *args) { + if (T.skip) + return SKIP; + if (0==strcmp (cmnd, "operation")) return operation ((char *) args); + if (T.skip_test) + { + if (0==strcmp (cmnd, "expect")) return another_skip(); + return 0; + } + if (0==strcmp (cmnd, "accept")) return accept (args); + if (0==strcmp (cmnd, "expect")) return expect (args); + if (0==strcmp (cmnd, "roundtrip")) return roundtrip (args); + if (0==strcmp (cmnd, "banner")) return banner (args); + if (0==strcmp (cmnd, "verbose")) return verbose (args); + if (0==strcmp (cmnd, "direction")) return direction (args); + if (0==strcmp (cmnd, "tolerance")) return tolerance (args); + if (0==strcmp (cmnd, "ignore")) return ignore (args); + if (0==strcmp (cmnd, "require_grid")) return require_grid (args); + if (0==strcmp (cmnd, "echo")) return echo (args); + if (0==strcmp (cmnd, "skip")) return skip (args); + if (0==strcmp (cmnd, "use_proj4_init_rules")) + return use_proj4_init_rules (args); + + return 0; +} + + + + +namespace { // anonymous namespace +struct errno_vs_err_const {const char *the_err_const; int the_errno;}; +static const struct errno_vs_err_const lookup[] = { + {"pjd_err_no_args" , -1}, + {"pjd_err_no_option_in_init_file" , -2}, + {"pjd_err_no_colon_in_init_string" , -3}, + {"pjd_err_proj_not_named" , -4}, + {"pjd_err_unknown_projection_id" , -5}, + {"pjd_err_eccentricity_is_one" , -6}, + {"pjd_err_unknown_unit_id" , -7}, + {"pjd_err_invalid_boolean_param" , -8}, + {"pjd_err_unknown_ellp_param" , -9}, + {"pjd_err_rev_flattening_is_zero" , -10}, + {"pjd_err_ref_rad_larger_than_90" , -11}, + {"pjd_err_es_less_than_zero" , -12}, + {"pjd_err_major_axis_not_given" , -13}, + {"pjd_err_lat_or_lon_exceed_limit" , -14}, + {"pjd_err_invalid_x_or_y" , -15}, + {"pjd_err_wrong_format_dms_value" , -16}, + {"pjd_err_non_conv_inv_meri_dist" , -17}, + {"pjd_err_non_con_inv_phi2" , -18}, + {"pjd_err_acos_asin_arg_too_large" , -19}, + {"pjd_err_tolerance_condition" , -20}, + {"pjd_err_conic_lat_equal" , -21}, + {"pjd_err_lat_larger_than_90" , -22}, + {"pjd_err_lat1_is_zero" , -23}, + {"pjd_err_lat_ts_larger_than_90" , -24}, + {"pjd_err_control_point_no_dist" , -25}, + {"pjd_err_no_rotation_proj" , -26}, + {"pjd_err_w_or_m_zero_or_less" , -27}, + {"pjd_err_lsat_not_in_range" , -28}, + {"pjd_err_path_not_in_range" , -29}, + {"pjd_err_h_less_than_zero" , -30}, + {"pjd_err_k_less_than_zero" , -31}, + {"pjd_err_lat_1_or_2_zero_or_90" , -32}, + {"pjd_err_lat_0_or_alpha_eq_90" , -33}, + {"pjd_err_ellipsoid_use_required" , -34}, + {"pjd_err_invalid_utm_zone" , -35}, + {"pjd_err_tcheby_val_out_of_range" , -36}, + {"pjd_err_failed_to_find_proj" , -37}, + {"pjd_err_failed_to_load_grid" , -38}, + {"pjd_err_invalid_m_or_n" , -39}, + {"pjd_err_n_out_of_range" , -40}, + {"pjd_err_lat_1_2_unspecified" , -41}, + {"pjd_err_abs_lat1_eq_abs_lat2" , -42}, + {"pjd_err_lat_0_half_pi_from_mean" , -43}, + {"pjd_err_unparseable_cs_def" , -44}, + {"pjd_err_geocentric" , -45}, + {"pjd_err_unknown_prime_meridian" , -46}, + {"pjd_err_axis" , -47}, + {"pjd_err_grid_area" , -48}, + {"pjd_err_invalid_sweep_axis" , -49}, + {"pjd_err_malformed_pipeline" , -50}, + {"pjd_err_unit_factor_less_than_0" , -51}, + {"pjd_err_invalid_scale" , -52}, + {"pjd_err_non_convergent" , -53}, + {"pjd_err_missing_args" , -54}, + {"pjd_err_lat_0_is_zero" , -55}, + {"pjd_err_ellipsoidal_unsupported" , -56}, + {"pjd_err_too_many_inits" , -57}, + {"pjd_err_invalid_arg" , -58}, + {"pjd_err_dont_skip" , 5555}, + {"pjd_err_unknown" , 9999}, + {"pjd_err_enomem" , ENOMEM}, +}; +} // anonymous namespace + +static const struct errno_vs_err_const unknown = {"PJD_ERR_UNKNOWN", 9999}; + + +static int list_err_codes (void) { + int i; + const int n = sizeof lookup / sizeof lookup[0]; + + for (i = 0; i < n; i++) { + if (9999==lookup[i].the_errno) + break; + fprintf (T.fout, "%25s (%2.2d): %s\n", lookup[i].the_err_const + 8, + lookup[i].the_errno, pj_strerrno(lookup[i].the_errno)); + } + return 0; +} + + +static const char *err_const_from_errno (int err) { + size_t i; + const size_t n = sizeof lookup / sizeof lookup[0]; + + for (i = 0; i < n; i++) { + if (err==lookup[i].the_errno) + return lookup[i].the_err_const + 8; + } + return unknown.the_err_const; +} + + +static int errno_from_err_const (const char *err_const) { + const size_t n = sizeof lookup / sizeof lookup[0]; + size_t i, len; + int ret; + char tolower_err_const[100]; + + /* Make a lower case copy for matching */ + for (i = 0; i < 99; i++) { + if (0==err_const[i] || isspace (err_const[i])) + break; + tolower_err_const[i] = (char) tolower (err_const[i]); + } + tolower_err_const[i] = 0; + + /* If it looks numeric, return that numeric */ + ret = (int) pj_atof (err_const); + if (0!=ret) + return ret; + + /* Else try to find a matching identifier */ + len = strlen (tolower_err_const); + + /* First try to find a match excluding the PJD_ERR_ prefix */ + for (i = 0; i < n; i++) { + if (0==strncmp (lookup[i].the_err_const + 8, err_const, len)) + return lookup[i].the_errno; + } + + /* If that did not work, try with the full name */ + for (i = 0; i < n; i++) { + if (0==strncmp (lookup[i].the_err_const, err_const, len)) + return lookup[i].the_errno; + } + + /* On failure, return something unlikely */ + return 9999; +} + + +static int errmsg (int errlev, const char *msg, ...) { + va_list args; + va_start(args, msg); + vfprintf(stdout, msg, args); + va_end(args); + if (errlev) + errno = errlev; + return errlev; +} + + + + + + + + +/**************************************************************************************** + +FFIO - Flexible format I/O + +FFIO provides functionality for reading proj style instruction strings written +in a less strict format than usual: + +* Whitespace is generally allowed everywhere +* Comments can be written inline, '#' style +* ... or as free format blocks + +The overall mission of FFIO is to facilitate communications of geodetic +parameters and test material in a format that is highly human readable, +and provides ample room for comment, documentation, and test material. + +See the PROJ ".gie" test suites for examples of supported formatting. + +****************************************************************************************/ + + +/***************************************************************************************/ +static ffio *ffio_create (const char **tags, size_t n_tags, size_t max_record_size) { +/**************************************************************************************** +Constructor for the ffio object. +****************************************************************************************/ + ffio *G = static_cast(calloc (1, sizeof (ffio))); + if (nullptr==G) + return nullptr; + + if (0==max_record_size) + max_record_size = 1000; + + G->args = static_cast(calloc (1, 5*max_record_size)); + if (nullptr==G->args) { + free (G); + return nullptr; + } + + G->next_args = static_cast(calloc (1, max_record_size)); + if (nullptr==G->args) { + free (G->args); + free (G); + return nullptr; + } + + G->args_size = 5*max_record_size; + G->next_args_size = max_record_size; + + G->tags = tags; + G->n_tags = n_tags; + return G; +} + + + +/***************************************************************************************/ +static ffio *ffio_destroy (ffio *G) { +/**************************************************************************************** +Free all allocated associated memory, then free G itself. For extra RAII compliancy, +the file object should also be closed if still open, but this will require additional +control logic, and ffio is a gie tool specific package, so we fall back to asserting that +fclose has been called prior to ffio_destroy. +****************************************************************************************/ + free (G->args); + free (G->next_args); + free (G); + return nullptr; +} + + + +/***************************************************************************************/ +static int at_decorative_element (ffio *G) { +/**************************************************************************************** +A decorative element consists of a line of at least 5 consecutive identical chars, +starting at buffer position 0: +"-----", "=====", "*****", etc. + +A decorative element serves as a end delimiter for the current element, and +continues until a gie command verb is found at the start of a line +****************************************************************************************/ + int i; + char *c; + if (nullptr==G) + return 0; + c = G->next_args; + if (nullptr==c) + return 0; + if (0==c[0]) + return 0; + for (i = 1; i < 5; i++) + if (c[i]!=c[0]) + return 0; + return 1; +} + + + +/***************************************************************************************/ +static const char *at_tag (ffio *G) { +/**************************************************************************************** +A start of a new command serves as an end delimiter for the current command +****************************************************************************************/ + size_t j; + for (j = 0; j < G->n_tags; j++) + if (strncmp (G->next_args, G->tags[j], strlen(G->tags[j]))==0) + return G->tags[j]; + return nullptr; +} + + + +/***************************************************************************************/ +static int at_end_delimiter (ffio *G) { +/**************************************************************************************** +An instruction consists of everything from its introductory tag to its end +delimiter. An end delimiter can be either the introductory tag of the next +instruction, or a "decorative element", i.e. one of the "ascii art" style +block delimiters typically used to mark up block comments in a free format +file. +****************************************************************************************/ + if (G==nullptr) + return 0; + if (at_decorative_element (G)) + return 1; + if (at_tag (G)) + return 1; + return 0; +} + + + +/***************************************************************************************/ +static int nextline (ffio *G) { +/**************************************************************************************** +Read next line of input file. Returns 1 on success, 0 on failure. +****************************************************************************************/ + G->next_args[0] = 0; + if (T.skip) + return 0; + if (nullptr==fgets (G->next_args, (int) G->next_args_size - 1, G->f)) + return 0; + if (feof (G->f)) + return 0; + pj_chomp (G->next_args); + G->next_lineno++; + return 1; +} + + + +/***************************************************************************************/ +static int locate_tag (ffio *G, const char *tag) { +/**************************************************************************************** +Find start-of-line tag (currently only used to search for for , but any tag +valid). + +Returns 1 on success, 0 on failure. +****************************************************************************************/ + size_t n = strlen (tag); + while (0!=strncmp (tag, G->next_args, n)) + if (0==nextline (G)) + return 0; + return 1; +} + + + +/***************************************************************************************/ +static int step_into_gie_block (ffio *G) { +/**************************************************************************************** +Make sure we're inside a -block. Return 1 on success, 0 otherwise. +****************************************************************************************/ + /* Already inside */ + if (G->level % 2) + return 1; + + if (0==locate_tag (G, "")) + return 0; + + while (0!=strncmp ("", G->next_args, 5)) { + G->next_args[0] = 0; + if (feof (G->f)) + return 0; + if (nullptr==fgets (G->next_args, (int) G->next_args_size - 1, G->f)) + return 0; + pj_chomp (G->next_args); + G->next_lineno++; + } + G->level++; + + /* We're ready at the start - now step into the block */ + return nextline (G); +} + + + +/***************************************************************************************/ +static int skip_to_next_tag (ffio *G) { +/**************************************************************************************** +Skip forward to the next command tag. Return 1 on success, 0 otherwise. +****************************************************************************************/ + const char *c; + if (0==step_into_gie_block (G)) + return 0; + + c = at_tag (G); + + /* If not already there - get there */ + while (!c) { + if (0==nextline (G)) + return 0; + c = at_tag (G); + } + + /* If we reached the end of a block, locate the next and retry */ + if (0==strcmp (c, "")) { + G->level++; + if (feof (G->f)) + return 0; + if (0==step_into_gie_block (G)) + return 0; + G->args[0] = 0; + return skip_to_next_tag (G); + } + G->lineno = G->next_lineno; + + return 1; +} + +/* Add the most recently read line of input to the block already stored. */ +static int append_args (ffio *G) { + size_t skip_chars = 0; + size_t next_len = strlen (G->next_args); + size_t args_len = strlen (G->args); + const char *tag = at_tag (G); + + if (tag) + skip_chars = strlen (tag); + + /* +2: 1 for the space separator and 1 for the NUL termination. */ + if (G->args_size < args_len + next_len - skip_chars + 2) { + char *p = static_cast(realloc (G->args, 2 * G->args_size)); + if (nullptr==p) + return 0; + G->args = p; + G->args_size = 2 * G->args_size; + } + + G->args[args_len] = ' '; + strcpy (G->args + args_len + 1, G->next_args + skip_chars); + + G->next_args[0] = 0; + return 1; +} + + + + + +/***************************************************************************************/ +static int get_inp (ffio *G) { +/**************************************************************************************** +The primary command reader for gie. Reads a block of gie input, cleans up repeated +whitespace etc. The block is stored in G->args. Returns 1 on success, 0 otherwise. +****************************************************************************************/ + G->args[0] = 0; + + if (0==skip_to_next_tag (G)) + return 0; + G->tag = at_tag (G); + + if (nullptr==G->tag) + return 0; + + do { + append_args (G); + if (0==nextline (G)) + return 0; + } while (!at_end_delimiter (G)); + + pj_shrink (G->args); + return 1; +} diff --git a/src/apps/nad2bin.cpp b/src/apps/nad2bin.cpp new file mode 100644 index 00000000..ff8f2ebd --- /dev/null +++ b/src/apps/nad2bin.cpp @@ -0,0 +1,382 @@ +/* Convert bivariate ASCII NAD27 to NAD83 tables to NTv2 binary structure */ +#include +#include + +#define PJ_LIB__ +#include "proj_internal.h" +#include "projects.h" +#define U_SEC_TO_RAD 4.848136811095359935899141023e-12 + +/************************************************************************/ +/* swap_words() */ +/* */ +/* Convert the byte order of the given word(s) in place. */ +/************************************************************************/ + +static const int byte_order_test = 1; +#define IS_LSB (((const unsigned char *) (&byte_order_test))[0] == 1) + +static void swap_words( void *data_in, int word_size, int word_count ) + +{ + int word; + unsigned char *data = (unsigned char *) data_in; + + for( word = 0; word < word_count; word++ ) + { + int i; + + for( i = 0; i < word_size/2; i++ ) + { + unsigned char t; + + t = data[i]; + data[i] = data[word_size-i-1]; + data[word_size-i-1] = t; + } + + data += word_size; + } +} + +/************************************************************************/ +/* Usage() */ +/************************************************************************/ + +static void Usage() +{ + fprintf(stderr, + "usage: nad2bin [-f ctable/ctable2/ntv2] binary_output < ascii_source\n" ); + exit(1); +} + +/************************************************************************/ +/* main() */ +/************************************************************************/ +int main(int argc, char **argv) { + struct CTABLE ct; + FLP *p, t; + size_t tsize; + int i, j, ichk; + long lam, laml, phi, phil; + FILE *fp; + + const char *output_file = nullptr; + + const char *format = "ctable2"; + const char *GS_TYPE = "SECONDS"; + const char *VERSION = ""; + const char *SYSTEM_F = "NAD27"; + const char *SYSTEM_T = "NAD83"; + const char *SUB_NAME = ""; + const char *CREATED = ""; + const char *UPDATED = ""; + +/* ==================================================================== */ +/* Process arguments. */ +/* ==================================================================== */ + for( i = 1; i < argc; i++ ) + { + if( i < argc-1 && strcmp(argv[i],"-f") == 0 ) + { + format = argv[++i]; + } + else if( output_file == nullptr ) + { + output_file = argv[i]; + } + else + Usage(); + } + + if( output_file == nullptr ) + Usage(); + + fprintf( stdout, "Output Binary File Format: %s\n", format ); + +/* ==================================================================== */ +/* Read the ASCII Table */ +/* ==================================================================== */ + + memset(ct.id,0,MAX_TAB_ID); + if ( nullptr == fgets(ct.id, MAX_TAB_ID, stdin) ) { + perror("fgets"); + exit(1); + } + /* cppcheck-suppress invalidscanf */ + if ( EOF == scanf("%d %d %*d %lf %lf %lf %lf", &ct.lim.lam, &ct.lim.phi, + &ct.ll.lam, &ct.del.lam, &ct.ll.phi, &ct.del.phi) ) { + perror("scanf"); + exit(1); + } + if (!(ct.cvs = (FLP *)malloc(tsize = ct.lim.lam * ct.lim.phi * + sizeof(FLP)))) { + perror("mem. alloc"); + exit(1); + } + ct.ll.lam *= DEG_TO_RAD; + ct.ll.phi *= DEG_TO_RAD; + ct.del.lam *= DEG_TO_RAD; + ct.del.phi *= DEG_TO_RAD; + /* load table */ + p = ct.cvs; + for (i = 0; i < ct.lim.phi; ++i) { + /* cppcheck-suppress invalidscanf */ + if ( EOF == scanf("%d:%ld %ld", &ichk, &laml, &phil) ) { + perror("scanf on row"); + exit(1); + } + if (ichk != i) { + fprintf(stderr,"format check on row\n"); + exit(1); + } + t.lam = (float) (laml * U_SEC_TO_RAD); + t.phi = (float) (phil * U_SEC_TO_RAD); + *p++ = t; + for (j = 1; j < ct.lim.lam; ++j) { + /* cppcheck-suppress invalidscanf */ + if ( EOF == scanf("%ld %ld", &lam, &phi) ) { + perror("scanf on column"); + exit(1); + } + t.lam = (float) ((laml += lam) * U_SEC_TO_RAD); + t.phi = (float) ((phil += phi) * U_SEC_TO_RAD); + *p++ = t; + } + } + if (feof(stdin)) { + fprintf(stderr, "premature EOF\n"); + exit(1); + } + +/* ==================================================================== */ +/* Write out the old ctable format - this is machine and byte */ +/* order specific. */ +/* ==================================================================== */ + if( strcmp(format,"ctable") == 0 ) + { + if (!(fp = fopen(output_file, "wb"))) { + perror(output_file); + exit(2); + } + if (fwrite(&ct, sizeof(ct), 1, fp) != 1 || + fwrite(ct.cvs, tsize, 1, fp) != 1) { + fprintf(stderr, "output failure\n"); + exit(2); + } + fclose( fp ); + exit(0); /* normal completion */ + } + +/* ==================================================================== */ +/* Write out the old ctable format - this is machine and byte */ +/* order specific. */ +/* ==================================================================== */ + if( strcmp(format,"ctable2") == 0 ) + { + char header[160]; + + if (!(fp = fopen(output_file, "wb"))) { + perror(output_file); + exit(2); + } + + /* cppcheck-suppress sizeofCalculation */ + STATIC_ASSERT( MAX_TAB_ID == 80 ); + /* cppcheck-suppress sizeofCalculation */ + STATIC_ASSERT( sizeof(pj_int32) == 4 ); /* for ct.lim.lam/phi */ + + memset( header, 0, sizeof(header) ); + + memcpy( header + 0, "CTABLE V2.0 ", 16 ); + memcpy( header + 16, ct.id, 80 ); + memcpy( header + 96, &ct.ll.lam, 8 ); + memcpy( header + 104, &ct.ll.phi, 8 ); + memcpy( header + 112, &ct.del.lam, 8 ); + memcpy( header + 120, &ct.del.phi, 8 ); + memcpy( header + 128, &ct.lim.lam, 4 ); + memcpy( header + 132, &ct.lim.phi, 4 ); + + /* force into LSB format */ + if( !IS_LSB ) + { + swap_words( header + 96, 8, 4 ); + swap_words( header + 128, 4, 2 ); + swap_words( ct.cvs, 4, ct.lim.lam * 2 * ct.lim.phi ); + } + + if( fwrite( header, sizeof(header), 1, fp ) != 1 ) { + perror( "fwrite" ); + exit( 2 ); + } + + if (fwrite(ct.cvs, tsize, 1, fp) != 1) { + perror( "fwrite" ); + exit(2); + } + + fclose( fp ); + exit(0); /* normal completion */ + } + +/* ==================================================================== */ +/* Write out the NTv2 format grid shift file. */ +/* ==================================================================== */ + if( strcmp(format,"ntv2") == 0 ) + { + if (!(fp = fopen(output_file, "wb"))) + { + perror(output_file); + exit(2); + } + +/* -------------------------------------------------------------------- */ +/* Write the file header. */ +/* -------------------------------------------------------------------- */ + { + char achHeader[11*16]; + + memset( achHeader, 0, sizeof(achHeader) ); + + memcpy( achHeader + 0*16, "NUM_OREC", 8 ); + achHeader[ 0*16 + 8] = 0xb; + + memcpy( achHeader + 1*16, "NUM_SREC", 8 ); + achHeader[ 1*16 + 8] = 0xb; + + memcpy( achHeader + 2*16, "NUM_FILE", 8 ); + achHeader[ 2*16 + 8] = 0x1; + + memcpy( achHeader + 3*16, "GS_TYPE ", 16 ); + memcpy( achHeader + 3*16+8, GS_TYPE, MIN(16,strlen(GS_TYPE)) ); + + memcpy( achHeader + 4*16, "VERSION ", 16 ); + memcpy( achHeader + 4*16+8, VERSION, MIN(16,strlen(VERSION)) ); + + memcpy( achHeader + 5*16, "SYSTEM_F ", 16 ); + memcpy( achHeader + 5*16+8, SYSTEM_F, MIN(16,strlen(SYSTEM_F)) ); + + memcpy( achHeader + 6*16, "SYSTEM_T ", 16 ); + memcpy( achHeader + 6*16+8, SYSTEM_T, MIN(16,strlen(SYSTEM_T)) ); + + memcpy( achHeader + 7*16, "MAJOR_F ", 8); + memcpy( achHeader + 8*16, "MINOR_F ", 8 ); + memcpy( achHeader + 9*16, "MAJOR_T ", 8 ); + memcpy( achHeader + 10*16, "MINOR_T ", 8 ); + + fwrite( achHeader, 1, sizeof(achHeader), fp ); + } + +/* -------------------------------------------------------------------- */ +/* Write the grid header. */ +/* -------------------------------------------------------------------- */ + { + unsigned char achHeader[11*16]; + double dfValue; + pj_int32 nGSCount = ct.lim.lam * ct.lim.phi; + LP ur; + + ur.lam = ct.ll.lam + (ct.lim.lam-1) * ct.del.lam; + ur.phi = ct.ll.phi + (ct.lim.phi-1) * ct.del.phi; + + /* cppcheck-suppress sizeofCalculation */ + STATIC_ASSERT( sizeof(nGSCount) == 4 ); + + memset( achHeader, 0, sizeof(achHeader) ); + + memcpy( achHeader + 0*16, "SUB_NAME ", 16 ); + memcpy( achHeader + 0*16+8, SUB_NAME, MIN(16,strlen(SUB_NAME)) ); + + memcpy( achHeader + 1*16, "PARENT ", 16 ); + memcpy( achHeader + 1*16+8, "NONE", MIN(16,strlen("NONE")) ); + + memcpy( achHeader + 2*16, "CREATED ", 16 ); + memcpy( achHeader + 2*16+8, CREATED, MIN(16,strlen(CREATED)) ); + + memcpy( achHeader + 3*16, "UPDATED ", 16 ); + memcpy( achHeader + 3*16+8, UPDATED, MIN(16,strlen(UPDATED)) ); + + memcpy( achHeader + 4*16, "S_LAT ", 8 ); + dfValue = ct.ll.phi * 3600.0 / DEG_TO_RAD; + memcpy( achHeader + 4*16 + 8, &dfValue, 8 ); + + memcpy( achHeader + 5*16, "N_LAT ", 8 ); + dfValue = ur.phi * 3600.0 / DEG_TO_RAD; + memcpy( achHeader + 5*16 + 8, &dfValue, 8 ); + + memcpy( achHeader + 6*16, "E_LONG ", 8 ); + dfValue = -1 * ur.lam * 3600.0 / DEG_TO_RAD; + memcpy( achHeader + 6*16 + 8, &dfValue, 8 ); + + memcpy( achHeader + 7*16, "W_LONG ", 8 ); + dfValue = -1 * ct.ll.lam * 3600.0 / DEG_TO_RAD; + memcpy( achHeader + 7*16 + 8, &dfValue, 8 ); + + memcpy( achHeader + 8*16, "LAT_INC ", 8 ); + dfValue = ct.del.phi * 3600.0 / DEG_TO_RAD; + memcpy( achHeader + 8*16 + 8, &dfValue, 8 ); + + memcpy( achHeader + 9*16, "LONG_INC", 8 ); + dfValue = ct.del.lam * 3600.0 / DEG_TO_RAD; + memcpy( achHeader + 9*16 + 8, &dfValue, 8 ); + + memcpy( achHeader + 10*16, "GS_COUNT", 8 ); + memcpy( achHeader + 10*16+8, &nGSCount, 4 ); + + if( !IS_LSB ) + { + swap_words( achHeader + 4*16 + 8, 8, 1 ); + swap_words( achHeader + 5*16 + 8, 8, 1 ); + swap_words( achHeader + 6*16 + 8, 8, 1 ); + swap_words( achHeader + 7*16 + 8, 8, 1 ); + swap_words( achHeader + 8*16 + 8, 8, 1 ); + swap_words( achHeader + 9*16 + 8, 8, 1 ); + swap_words( achHeader + 10*16 + 8, 4, 1 ); + } + + fwrite( achHeader, 1, sizeof(achHeader), fp ); + } + +/* -------------------------------------------------------------------- */ +/* Write the actual grid cells. */ +/* -------------------------------------------------------------------- */ + { + float *row_buf; + int row; + + row_buf = (float *) pj_malloc(ct.lim.lam * sizeof(float) * 4); + memset( row_buf, 0, sizeof(float)*4 ); + + for( row = 0; row < ct.lim.phi; row++ ) + { + for( i = 0; i < ct.lim.lam; i++ ) + { + FLP *cvs = ct.cvs + (row) * ct.lim.lam + + (ct.lim.lam - i - 1); + + /* convert radians to seconds */ + row_buf[i*4+0] = (float) (cvs->phi * (3600.0 / (M_PI/180.0))); + row_buf[i*4+1] = (float) (cvs->lam * (3600.0 / (M_PI/180.0))); + + /* We leave the accuracy values as zero */ + } + + if( !IS_LSB ) + swap_words( row_buf, 4, ct.lim.lam * 4 ); + + if( fwrite( row_buf, sizeof(float), ct.lim.lam*4, fp ) + != (size_t)( 4 * ct.lim.lam ) ) + { + perror( "write()" ); + exit( 2 ); + } + } + } + + fclose( fp ); + exit(0); /* normal completion */ + } + + fprintf( stderr, "Unsupported format, nothing written.\n" ); + exit( 3 ); +} diff --git a/src/apps/optargpm.h b/src/apps/optargpm.h new file mode 100644 index 00000000..035c6f92 --- /dev/null +++ b/src/apps/optargpm.h @@ -0,0 +1,635 @@ +/*********************************************************************** + + OPTARGPM - a header-only library for decoding + PROJ.4 style command line options + + Thomas Knudsen, 2017-09-10 + +************************************************************************ + +For PROJ.4 command line programs, we have a somewhat complex option +decoding situation, since we have to navigate in a cocktail of classic +single letter style options, prefixed by "-", GNU style long options +prefixed by "--", transformation specification elements prefixed by "+", +and input file names prefixed by "" (i.e. nothing). + +Hence, classic getopt.h style decoding does not cut the mustard, so +this is an attempt to catch up and chop the ketchup. + +Since optargpm (for "optarg plus minus") does not belong, in any +obvious way, in any systems development library, it is provided as +a "header only" library. + +While this is conventional in C++, it is frowned at in plain C. +But frown away - "header only" has its places, and this is one of +them. + +By convention, we expect a command line to consist of the following +elements: + + + [short ("-")/long ("--") options} + [operator ("+") specs] + [operands/input files] + +or less verbose: + + [options] [operator specs] [operands] + +or less abstract: + + proj -I --output=foo +proj=utm +zone=32 +ellps=GRS80 bar baz... + +Where + +Operator is proj +Options are -I --output=foo +Operator specs are +proj=utm +zone=32 +ellps=GRS80 +Operands are bar baz + + +While neither claiming to save the world, nor to hint at the "shape of +jazz to come", at least optargpm has shown useful in constructing cs2cs +style transformation filters. + +Supporting a wide range of option syntax, the getoptpm API is somewhat +quirky, but also compact, consisting of one data type, 3(+2) functions, +and one enumeration: + +OPTARGS + Housekeeping data type. An instance of OPTARGS is conventionally + called o or opt +opt_parse (opt, argc, argv ...): + The work horse: Define supported options; Split (argc, argv) + into groups (options, op specs, operands); Parse option + arguments. +opt_given (o, option): + The number of times element */ - return 0; -} - - -static int dispatch (const char *cmnd, const char *args) { - if (T.skip) - return SKIP; - if (0==strcmp (cmnd, "operation")) return operation ((char *) args); - if (T.skip_test) - { - if (0==strcmp (cmnd, "expect")) return another_skip(); - return 0; - } - if (0==strcmp (cmnd, "accept")) return accept (args); - if (0==strcmp (cmnd, "expect")) return expect (args); - if (0==strcmp (cmnd, "roundtrip")) return roundtrip (args); - if (0==strcmp (cmnd, "banner")) return banner (args); - if (0==strcmp (cmnd, "verbose")) return verbose (args); - if (0==strcmp (cmnd, "direction")) return direction (args); - if (0==strcmp (cmnd, "tolerance")) return tolerance (args); - if (0==strcmp (cmnd, "ignore")) return ignore (args); - if (0==strcmp (cmnd, "require_grid")) return require_grid (args); - if (0==strcmp (cmnd, "echo")) return echo (args); - if (0==strcmp (cmnd, "skip")) return skip (args); - if (0==strcmp (cmnd, "use_proj4_init_rules")) - return use_proj4_init_rules (args); - - return 0; -} - - - - -namespace { // anonymous namespace -struct errno_vs_err_const {const char *the_err_const; int the_errno;}; -static const struct errno_vs_err_const lookup[] = { - {"pjd_err_no_args" , -1}, - {"pjd_err_no_option_in_init_file" , -2}, - {"pjd_err_no_colon_in_init_string" , -3}, - {"pjd_err_proj_not_named" , -4}, - {"pjd_err_unknown_projection_id" , -5}, - {"pjd_err_eccentricity_is_one" , -6}, - {"pjd_err_unknown_unit_id" , -7}, - {"pjd_err_invalid_boolean_param" , -8}, - {"pjd_err_unknown_ellp_param" , -9}, - {"pjd_err_rev_flattening_is_zero" , -10}, - {"pjd_err_ref_rad_larger_than_90" , -11}, - {"pjd_err_es_less_than_zero" , -12}, - {"pjd_err_major_axis_not_given" , -13}, - {"pjd_err_lat_or_lon_exceed_limit" , -14}, - {"pjd_err_invalid_x_or_y" , -15}, - {"pjd_err_wrong_format_dms_value" , -16}, - {"pjd_err_non_conv_inv_meri_dist" , -17}, - {"pjd_err_non_con_inv_phi2" , -18}, - {"pjd_err_acos_asin_arg_too_large" , -19}, - {"pjd_err_tolerance_condition" , -20}, - {"pjd_err_conic_lat_equal" , -21}, - {"pjd_err_lat_larger_than_90" , -22}, - {"pjd_err_lat1_is_zero" , -23}, - {"pjd_err_lat_ts_larger_than_90" , -24}, - {"pjd_err_control_point_no_dist" , -25}, - {"pjd_err_no_rotation_proj" , -26}, - {"pjd_err_w_or_m_zero_or_less" , -27}, - {"pjd_err_lsat_not_in_range" , -28}, - {"pjd_err_path_not_in_range" , -29}, - {"pjd_err_h_less_than_zero" , -30}, - {"pjd_err_k_less_than_zero" , -31}, - {"pjd_err_lat_1_or_2_zero_or_90" , -32}, - {"pjd_err_lat_0_or_alpha_eq_90" , -33}, - {"pjd_err_ellipsoid_use_required" , -34}, - {"pjd_err_invalid_utm_zone" , -35}, - {"pjd_err_tcheby_val_out_of_range" , -36}, - {"pjd_err_failed_to_find_proj" , -37}, - {"pjd_err_failed_to_load_grid" , -38}, - {"pjd_err_invalid_m_or_n" , -39}, - {"pjd_err_n_out_of_range" , -40}, - {"pjd_err_lat_1_2_unspecified" , -41}, - {"pjd_err_abs_lat1_eq_abs_lat2" , -42}, - {"pjd_err_lat_0_half_pi_from_mean" , -43}, - {"pjd_err_unparseable_cs_def" , -44}, - {"pjd_err_geocentric" , -45}, - {"pjd_err_unknown_prime_meridian" , -46}, - {"pjd_err_axis" , -47}, - {"pjd_err_grid_area" , -48}, - {"pjd_err_invalid_sweep_axis" , -49}, - {"pjd_err_malformed_pipeline" , -50}, - {"pjd_err_unit_factor_less_than_0" , -51}, - {"pjd_err_invalid_scale" , -52}, - {"pjd_err_non_convergent" , -53}, - {"pjd_err_missing_args" , -54}, - {"pjd_err_lat_0_is_zero" , -55}, - {"pjd_err_ellipsoidal_unsupported" , -56}, - {"pjd_err_too_many_inits" , -57}, - {"pjd_err_invalid_arg" , -58}, - {"pjd_err_dont_skip" , 5555}, - {"pjd_err_unknown" , 9999}, - {"pjd_err_enomem" , ENOMEM}, -}; -} // anonymous namespace - -static const struct errno_vs_err_const unknown = {"PJD_ERR_UNKNOWN", 9999}; - - -static int list_err_codes (void) { - int i; - const int n = sizeof lookup / sizeof lookup[0]; - - for (i = 0; i < n; i++) { - if (9999==lookup[i].the_errno) - break; - fprintf (T.fout, "%25s (%2.2d): %s\n", lookup[i].the_err_const + 8, - lookup[i].the_errno, pj_strerrno(lookup[i].the_errno)); - } - return 0; -} - - -static const char *err_const_from_errno (int err) { - size_t i; - const size_t n = sizeof lookup / sizeof lookup[0]; - - for (i = 0; i < n; i++) { - if (err==lookup[i].the_errno) - return lookup[i].the_err_const + 8; - } - return unknown.the_err_const; -} - - -static int errno_from_err_const (const char *err_const) { - const size_t n = sizeof lookup / sizeof lookup[0]; - size_t i, len; - int ret; - char tolower_err_const[100]; - - /* Make a lower case copy for matching */ - for (i = 0; i < 99; i++) { - if (0==err_const[i] || isspace (err_const[i])) - break; - tolower_err_const[i] = (char) tolower (err_const[i]); - } - tolower_err_const[i] = 0; - - /* If it looks numeric, return that numeric */ - ret = (int) pj_atof (err_const); - if (0!=ret) - return ret; - - /* Else try to find a matching identifier */ - len = strlen (tolower_err_const); - - /* First try to find a match excluding the PJD_ERR_ prefix */ - for (i = 0; i < n; i++) { - if (0==strncmp (lookup[i].the_err_const + 8, err_const, len)) - return lookup[i].the_errno; - } - - /* If that did not work, try with the full name */ - for (i = 0; i < n; i++) { - if (0==strncmp (lookup[i].the_err_const, err_const, len)) - return lookup[i].the_errno; - } - - /* On failure, return something unlikely */ - return 9999; -} - - -static int errmsg (int errlev, const char *msg, ...) { - va_list args; - va_start(args, msg); - vfprintf(stdout, msg, args); - va_end(args); - if (errlev) - errno = errlev; - return errlev; -} - - - - - - - - -/**************************************************************************************** - -FFIO - Flexible format I/O - -FFIO provides functionality for reading proj style instruction strings written -in a less strict format than usual: - -* Whitespace is generally allowed everywhere -* Comments can be written inline, '#' style -* ... or as free format blocks - -The overall mission of FFIO is to facilitate communications of geodetic -parameters and test material in a format that is highly human readable, -and provides ample room for comment, documentation, and test material. - -See the PROJ ".gie" test suites for examples of supported formatting. - -****************************************************************************************/ - - -/***************************************************************************************/ -static ffio *ffio_create (const char **tags, size_t n_tags, size_t max_record_size) { -/**************************************************************************************** -Constructor for the ffio object. -****************************************************************************************/ - ffio *G = static_cast(calloc (1, sizeof (ffio))); - if (nullptr==G) - return nullptr; - - if (0==max_record_size) - max_record_size = 1000; - - G->args = static_cast(calloc (1, 5*max_record_size)); - if (nullptr==G->args) { - free (G); - return nullptr; - } - - G->next_args = static_cast(calloc (1, max_record_size)); - if (nullptr==G->args) { - free (G->args); - free (G); - return nullptr; - } - - G->args_size = 5*max_record_size; - G->next_args_size = max_record_size; - - G->tags = tags; - G->n_tags = n_tags; - return G; -} - - - -/***************************************************************************************/ -static ffio *ffio_destroy (ffio *G) { -/**************************************************************************************** -Free all allocated associated memory, then free G itself. For extra RAII compliancy, -the file object should also be closed if still open, but this will require additional -control logic, and ffio is a gie tool specific package, so we fall back to asserting that -fclose has been called prior to ffio_destroy. -****************************************************************************************/ - free (G->args); - free (G->next_args); - free (G); - return nullptr; -} - - - -/***************************************************************************************/ -static int at_decorative_element (ffio *G) { -/**************************************************************************************** -A decorative element consists of a line of at least 5 consecutive identical chars, -starting at buffer position 0: -"-----", "=====", "*****", etc. - -A decorative element serves as a end delimiter for the current element, and -continues until a gie command verb is found at the start of a line -****************************************************************************************/ - int i; - char *c; - if (nullptr==G) - return 0; - c = G->next_args; - if (nullptr==c) - return 0; - if (0==c[0]) - return 0; - for (i = 1; i < 5; i++) - if (c[i]!=c[0]) - return 0; - return 1; -} - - - -/***************************************************************************************/ -static const char *at_tag (ffio *G) { -/**************************************************************************************** -A start of a new command serves as an end delimiter for the current command -****************************************************************************************/ - size_t j; - for (j = 0; j < G->n_tags; j++) - if (strncmp (G->next_args, G->tags[j], strlen(G->tags[j]))==0) - return G->tags[j]; - return nullptr; -} - - - -/***************************************************************************************/ -static int at_end_delimiter (ffio *G) { -/**************************************************************************************** -An instruction consists of everything from its introductory tag to its end -delimiter. An end delimiter can be either the introductory tag of the next -instruction, or a "decorative element", i.e. one of the "ascii art" style -block delimiters typically used to mark up block comments in a free format -file. -****************************************************************************************/ - if (G==nullptr) - return 0; - if (at_decorative_element (G)) - return 1; - if (at_tag (G)) - return 1; - return 0; -} - - - -/***************************************************************************************/ -static int nextline (ffio *G) { -/**************************************************************************************** -Read next line of input file. Returns 1 on success, 0 on failure. -****************************************************************************************/ - G->next_args[0] = 0; - if (T.skip) - return 0; - if (nullptr==fgets (G->next_args, (int) G->next_args_size - 1, G->f)) - return 0; - if (feof (G->f)) - return 0; - pj_chomp (G->next_args); - G->next_lineno++; - return 1; -} - - - -/***************************************************************************************/ -static int locate_tag (ffio *G, const char *tag) { -/**************************************************************************************** -Find start-of-line tag (currently only used to search for for , but any tag -valid). - -Returns 1 on success, 0 on failure. -****************************************************************************************/ - size_t n = strlen (tag); - while (0!=strncmp (tag, G->next_args, n)) - if (0==nextline (G)) - return 0; - return 1; -} - - - -/***************************************************************************************/ -static int step_into_gie_block (ffio *G) { -/**************************************************************************************** -Make sure we're inside a -block. Return 1 on success, 0 otherwise. -****************************************************************************************/ - /* Already inside */ - if (G->level % 2) - return 1; - - if (0==locate_tag (G, "")) - return 0; - - while (0!=strncmp ("", G->next_args, 5)) { - G->next_args[0] = 0; - if (feof (G->f)) - return 0; - if (nullptr==fgets (G->next_args, (int) G->next_args_size - 1, G->f)) - return 0; - pj_chomp (G->next_args); - G->next_lineno++; - } - G->level++; - - /* We're ready at the start - now step into the block */ - return nextline (G); -} - - - -/***************************************************************************************/ -static int skip_to_next_tag (ffio *G) { -/**************************************************************************************** -Skip forward to the next command tag. Return 1 on success, 0 otherwise. -****************************************************************************************/ - const char *c; - if (0==step_into_gie_block (G)) - return 0; - - c = at_tag (G); - - /* If not already there - get there */ - while (!c) { - if (0==nextline (G)) - return 0; - c = at_tag (G); - } - - /* If we reached the end of a block, locate the next and retry */ - if (0==strcmp (c, "")) { - G->level++; - if (feof (G->f)) - return 0; - if (0==step_into_gie_block (G)) - return 0; - G->args[0] = 0; - return skip_to_next_tag (G); - } - G->lineno = G->next_lineno; - - return 1; -} - -/* Add the most recently read line of input to the block already stored. */ -static int append_args (ffio *G) { - size_t skip_chars = 0; - size_t next_len = strlen (G->next_args); - size_t args_len = strlen (G->args); - const char *tag = at_tag (G); - - if (tag) - skip_chars = strlen (tag); - - /* +2: 1 for the space separator and 1 for the NUL termination. */ - if (G->args_size < args_len + next_len - skip_chars + 2) { - char *p = static_cast(realloc (G->args, 2 * G->args_size)); - if (nullptr==p) - return 0; - G->args = p; - G->args_size = 2 * G->args_size; - } - - G->args[args_len] = ' '; - strcpy (G->args + args_len + 1, G->next_args + skip_chars); - - G->next_args[0] = 0; - return 1; -} - - - - - -/***************************************************************************************/ -static int get_inp (ffio *G) { -/**************************************************************************************** -The primary command reader for gie. Reads a block of gie input, cleans up repeated -whitespace etc. The block is stored in G->args. Returns 1 on success, 0 otherwise. -****************************************************************************************/ - G->args[0] = 0; - - if (0==skip_to_next_tag (G)) - return 0; - G->tag = at_tag (G); - - if (nullptr==G->tag) - return 0; - - do { - append_args (G); - if (0==nextline (G)) - return 0; - } while (!at_end_delimiter (G)); - - pj_shrink (G->args); - return 1; -} diff --git a/src/internal.cpp b/src/internal.cpp deleted file mode 100644 index c43605d1..00000000 --- a/src/internal.cpp +++ /dev/null @@ -1,374 +0,0 @@ -/****************************************************************************** - * - * Project: PROJ - * Purpose: ISO19111:2018 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/internal/internal.hpp" - -#include -#include -#ifdef _MSC_VER -#include -#else -#include -#endif -#include -#include // std::setprecision -#include -#include // std::istringstream and std::ostringstream -#include - -#include "sqlite3.h" - -NS_PROJ_START - -namespace internal { - -// --------------------------------------------------------------------------- - -/** - * Replace all occurrences of before with after. - */ -std::string replaceAll(const std::string &str, const std::string &before, - const std::string &after) { - std::string ret(str); - const size_t nBeforeSize = before.size(); - const size_t nAfterSize = after.size(); - if (nBeforeSize) { - size_t nStartPos = 0; - while ((nStartPos = ret.find(before, nStartPos)) != std::string::npos) { - ret.replace(nStartPos, nBeforeSize, after); - nStartPos += nAfterSize; - } - } - return ret; -} - -// --------------------------------------------------------------------------- - -inline static bool EQUALN(const char *a, const char *b, size_t size) { -#ifdef _MSC_VER - return _strnicmp(a, b, size) == 0; -#else - return strncasecmp(a, b, size) == 0; -#endif -} - -/** - * Case-insensitive equality test - */ -bool ci_equal(const std::string &a, const std::string &b) noexcept { - const auto size = a.size(); - if (size != b.size()) { - return false; - } - return EQUALN(a.c_str(), b.c_str(), size); -} - -bool ci_equal(const std::string &a, const char *b) noexcept { - const auto size = a.size(); - if (size != strlen(b)) { - return false; - } - return EQUALN(a.c_str(), b, size); -} - -bool ci_equal(const char *a, const char *b) noexcept { - const auto size = strlen(a); - if (size != strlen(b)) { - return false; - } - return EQUALN(a, b, size); -} - -// --------------------------------------------------------------------------- - -bool ci_less(const std::string &a, const std::string &b) noexcept { -#ifdef _MSC_VER - return _stricmp(a.c_str(), b.c_str()) < 0; -#else - return strcasecmp(a.c_str(), b.c_str()) < 0; -#endif -} - -// --------------------------------------------------------------------------- - -/** - * Convert to lower case. - */ - -std::string tolower(const std::string &str) - -{ - std::string ret(str); - for (size_t i = 0; i < ret.size(); i++) - ret[i] = static_cast(::tolower(ret[i])); - return ret; -} - -// --------------------------------------------------------------------------- - -/** - * Convert to upper case. - */ - -std::string toupper(const std::string &str) - -{ - std::string ret(str); - for (size_t i = 0; i < ret.size(); i++) - ret[i] = static_cast(::toupper(ret[i])); - return ret; -} - -// --------------------------------------------------------------------------- - -/** Strip leading and trailing double quote characters */ -std::string stripQuotes(const std::string &str) { - if (str.size() >= 2 && str[0] == '"' && str.back() == '"') { - return str.substr(1, str.size() - 2); - } - return str; -} - -// --------------------------------------------------------------------------- - -size_t ci_find(const std::string &str, const char *needle) noexcept { - const size_t needleSize = strlen(needle); - for (size_t i = 0; i + needleSize <= str.size(); i++) { - if (EQUALN(str.c_str() + i, needle, needleSize)) { - return i; - } - } - return std::string::npos; -} - -// --------------------------------------------------------------------------- - -size_t ci_find(const std::string &str, const std::string &needle, - size_t startPos) noexcept { - const size_t needleSize = needle.size(); - for (size_t i = startPos; i + needleSize <= str.size(); i++) { - if (EQUALN(str.c_str() + i, needle.c_str(), needleSize)) { - return i; - } - } - return std::string::npos; -} - -// --------------------------------------------------------------------------- - -/* -bool starts_with(const std::string &str, const std::string &prefix) noexcept { - if (str.size() < prefix.size()) { - return false; - } - return std::memcmp(str.c_str(), prefix.c_str(), prefix.size()) == 0; -} -*/ - -// --------------------------------------------------------------------------- - -/* -bool starts_with(const std::string &str, const char *prefix) noexcept { - const size_t prefixSize = std::strlen(prefix); - if (str.size() < prefixSize) { - return false; - } - return std::memcmp(str.c_str(), prefix, prefixSize) == 0; -} -*/ - -// --------------------------------------------------------------------------- - -bool ci_starts_with(const char *str, const char *prefix) noexcept { - const auto str_size = strlen(str); - const auto prefix_size = strlen(prefix); - if (str_size < prefix_size) { - return false; - } - return EQUALN(str, prefix, prefix_size); -} - -// --------------------------------------------------------------------------- - -bool ci_starts_with(const std::string &str, - const std::string &prefix) noexcept { - if (str.size() < prefix.size()) { - return false; - } - return EQUALN(str.c_str(), prefix.c_str(), prefix.size()); -} - -// --------------------------------------------------------------------------- - -bool ends_with(const std::string &str, const std::string &suffix) noexcept { - if (str.size() < suffix.size()) { - return false; - } - return std::memcmp(str.c_str() + str.size() - suffix.size(), suffix.c_str(), - suffix.size()) == 0; -} - -// --------------------------------------------------------------------------- - -double c_locale_stod(const std::string &s) { - - const auto s_size = s.size(); - // Fast path - if (s_size > 0 && s_size < 15) { - std::int64_t acc = 0; - std::int64_t div = 1; - bool afterDot = false; - size_t i = 0; - if (s[0] == '-') { - ++i; - div = -1; - } else if (s[0] == '+') { - ++i; - } - for (; i < s_size; ++i) { - const auto ch = s[i]; - if (ch >= '0' && ch <= '9') { - acc = acc * 10 + ch - '0'; - if (afterDot) { - div *= 10; - } - } else if (ch == '.') { - afterDot = true; - } else { - div = 0; - } - } - if (div) { - return static_cast(acc) / div; - } - } - - std::istringstream iss(s); - iss.imbue(std::locale::classic()); - double d; - iss >> d; - if (!iss.eof() || iss.fail()) { - throw std::invalid_argument("non double value"); - } - return d; -} - -// --------------------------------------------------------------------------- - -std::vector split(const std::string &str, char separator) { - std::vector res; - size_t lastPos = 0; - size_t newPos = 0; - while ((newPos = str.find(separator, lastPos)) != std::string::npos) { - res.push_back(str.substr(lastPos, newPos - lastPos)); - lastPos = newPos + 1; - } - res.push_back(str.substr(lastPos)); - return res; -} - -// --------------------------------------------------------------------------- - -#ifdef _WIN32 - -// For some reason, sqlite3_snprintf() in the sqlite3 builds used on AppVeyor -// doesn't round identically to the Unix builds, and thus breaks a number of -// unit test. So to avoid this, use the stdlib formatting - -std::string toString(int val) { - std::ostringstream buffer; - buffer.imbue(std::locale::classic()); - buffer << val; - return buffer.str(); -} - -std::string toString(double val, int precision) { - std::ostringstream buffer; - buffer.imbue(std::locale::classic()); - buffer << std::setprecision(precision); - buffer << val; - auto str = buffer.str(); - if (precision == 15 && str.find("9999999999") != std::string::npos) { - buffer.str(""); - buffer.clear(); - buffer << std::setprecision(14); - buffer << val; - return buffer.str(); - } - return str; -} - -#else - -std::string toString(int val) { - // use sqlite3 API that is slighly faster than std::ostringstream - // with forcing the C locale. sqlite3_snprintf() emulates a C locale. - constexpr int BUF_SIZE = 16; - char szBuffer[BUF_SIZE]; - sqlite3_snprintf(BUF_SIZE, szBuffer, "%d", val); - return szBuffer; -} - -std::string toString(double val, int precision) { - // use sqlite3 API that is slighly faster than std::ostringstream - // with forcing the C locale. sqlite3_snprintf() emulates a C locale. - constexpr int BUF_SIZE = 32; - char szBuffer[BUF_SIZE]; - sqlite3_snprintf(BUF_SIZE, szBuffer, "%.*g", precision, val); - if (precision == 15 && strstr(szBuffer, "9999999999")) { - sqlite3_snprintf(BUF_SIZE, szBuffer, "%.14g", val); - } - return szBuffer; -} - -#endif - -// --------------------------------------------------------------------------- - -std::string concat(const char *a, const std::string &b) { - std::string res(a); - res += b; - return res; -} - -std::string concat(const char *a, const std::string &b, const char *c) { - std::string res(a); - res += b; - res += c; - return res; -} - -// --------------------------------------------------------------------------- - -} // namespace internal - -NS_PROJ_END diff --git a/src/io.cpp b/src/io.cpp deleted file mode 100644 index fe3680fb..00000000 --- a/src/io.cpp +++ /dev/null @@ -1,7501 +0,0 @@ -/****************************************************************************** - * - * Project: PROJ - * Purpose: ISO19111:2018 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 -#include -#include -#include -#include -#include -#include -#include -#include // std::istringstream -#include -#include -#include - -#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/coordinateoperation_internal.hpp" -#include "proj/internal/coordinatesystem_internal.hpp" -#include "proj/internal/internal.hpp" -#include "proj/internal/io_internal.hpp" - -#include "proj_constants.h" - -#include "pj_wkt1_parser.h" -#include "pj_wkt2_parser.h" - -// PROJ include order is sensitive -// clang-format off -#include "proj.h" -#include "projects.h" -#include "proj_api.h" -// clang-format on - -using namespace NS_PROJ::common; -using namespace NS_PROJ::crs; -using namespace NS_PROJ::cs; -using namespace NS_PROJ::datum; -using namespace NS_PROJ::internal; -using namespace NS_PROJ::metadata; -using namespace NS_PROJ::operation; -using namespace NS_PROJ::util; - -//! @cond Doxygen_Suppress -static const std::string emptyString{}; -//! @endcond - -#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; -}} -#endif - -NS_PROJ_START -namespace io { - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -IWKTExportable::~IWKTExportable() = default; - -// --------------------------------------------------------------------------- - -std::string IWKTExportable::exportToWKT(WKTFormatter *formatter) const { - _exportToWKT(formatter); - return formatter->toString(); -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct WKTFormatter::Private { - struct Params { - WKTFormatter::Convention convention_ = WKTFormatter::Convention::WKT2; - WKTFormatter::Version version_ = WKTFormatter::Version::WKT2; - bool multiLine_ = true; - bool strict_ = true; - int indentWidth_ = 4; - bool idOnTopLevelOnly_ = false; - bool outputAxisOrder_ = false; - bool primeMeridianOmittedIfGreenwich_ = false; - bool ellipsoidUnitOmittedIfMetre_ = false; - bool primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = false; - bool forceUNITKeyword_ = false; - bool outputCSUnitOnlyOnceIfSame_ = false; - bool primeMeridianInDegree_ = false; - bool use2018Keywords_ = false; - bool useESRIDialect_ = false; - OutputAxisRule outputAxis_ = WKTFormatter::OutputAxisRule::YES; - }; - Params params_{}; - DatabaseContextPtr dbContext_{}; - - int indentLevel_ = 0; - int level_ = 0; - std::vector stackHasChild_{}; - std::vector stackHasId_{false}; - std::vector stackEmptyKeyword_{}; - std::vector outputUnitStack_{true}; - std::vector outputIdStack_{true}; - std::vector axisLinearUnitStack_{ - util::nn_make_shared(UnitOfMeasure::METRE)}; - std::vector axisAngularUnitStack_{ - util::nn_make_shared(UnitOfMeasure::DEGREE)}; - bool abridgedTransformation_ = false; - bool useDerivingConversion_ = false; - std::vector toWGS84Parameters_{}; - std::string hDatumExtension_{}; - std::string vDatumExtension_{}; - std::vector inversionStack_{false}; - std::string result_{}; - - // cppcheck-suppress functionStatic - void addNewLine(); - void addIndentation(); - // cppcheck-suppress functionStatic - void startNewChild(); -}; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Constructs a new formatter. - * - * A formatter can be used only once (its internal state is mutated) - * - * Its default behaviour can be adjusted with the different setters. - * - * @param convention WKT flavor. Defaults to Convention::WKT2 - * @param dbContext Database context, to allow queries in it if needed. - * This is used for example for WKT1_ESRI output to do name substitutions. - * - * @return new formatter. - */ -WKTFormatterNNPtr WKTFormatter::create(Convention convention, - // cppcheck-suppress passedByValue - DatabaseContextPtr dbContext) { - auto ret = NN_NO_CHECK(WKTFormatter::make_unique(convention)); - ret->d->dbContext_ = dbContext; - return ret; -} - -// --------------------------------------------------------------------------- - -/** \brief Constructs a new formatter from another one. - * - * A formatter can be used only once (its internal state is mutated) - * - * Its default behaviour can be adjusted with the different setters. - * - * @param other source formatter. - * @return new formatter. - */ -WKTFormatterNNPtr WKTFormatter::create(const WKTFormatterNNPtr &other) { - auto f = create(other->d->params_.convention_, other->d->dbContext_); - f->d->params_ = other->d->params_; - return f; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -WKTFormatter::~WKTFormatter() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Whether to use multi line output or not. */ -WKTFormatter &WKTFormatter::setMultiLine(bool multiLine) noexcept { - d->params_.multiLine_ = multiLine; - return *this; -} - -// --------------------------------------------------------------------------- - -/** \brief Set number of spaces for each indentation level (defaults to 4). - */ -WKTFormatter &WKTFormatter::setIndentationWidth(int width) noexcept { - d->params_.indentWidth_ = width; - return *this; -} - -// --------------------------------------------------------------------------- - -/** \brief Set whether AXIS nodes should be output. - */ -WKTFormatter & -WKTFormatter::setOutputAxis(OutputAxisRule outputAxisIn) noexcept { - d->params_.outputAxis_ = outputAxisIn; - return *this; -} - -// --------------------------------------------------------------------------- - -/** \brief Set whether the formatter should operate on strict more or not. - * - * The default is strit mode, in which case a FormattingException can be thrown. - */ -WKTFormatter &WKTFormatter::setStrict(bool strictIn) noexcept { - d->params_.strict_ = strictIn; - return *this; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns whether the formatter is in strict mode. */ -bool WKTFormatter::isStrict() const noexcept { return d->params_.strict_; } - -// --------------------------------------------------------------------------- - -/** Returns the WKT string from the formatter. */ -const std::string &WKTFormatter::toString() const { - if (d->indentLevel_ > 0 || d->level_ > 0) { - // For intermediary nodes, the formatter is in a inconsistent - // state. - throw FormattingException("toString() called on intermediate nodes"); - } - if (d->axisLinearUnitStack_.size() != 1) - throw FormattingException( - "Unbalanced pushAxisLinearUnit() / popAxisLinearUnit()"); - if (d->axisAngularUnitStack_.size() != 1) - throw FormattingException( - "Unbalanced pushAxisAngularUnit() / popAxisAngularUnit()"); - if (d->outputIdStack_.size() != 1) - throw FormattingException("Unbalanced pushOutputId() / popOutputId()"); - if (d->outputUnitStack_.size() != 1) - throw FormattingException( - "Unbalanced pushOutputUnit() / popOutputUnit()"); - - return d->result_; -} - -//! @cond Doxygen_Suppress - -// --------------------------------------------------------------------------- - -WKTFormatter::WKTFormatter(Convention convention) - : d(internal::make_unique()) { - d->params_.convention_ = convention; - switch (convention) { - case Convention::WKT2_2018: - d->params_.use2018Keywords_ = true; - PROJ_FALLTHROUGH - case Convention::WKT2: - d->params_.version_ = WKTFormatter::Version::WKT2; - d->params_.outputAxisOrder_ = true; - break; - - case Convention::WKT2_2018_SIMPLIFIED: - d->params_.use2018Keywords_ = true; - PROJ_FALLTHROUGH - case Convention::WKT2_SIMPLIFIED: - d->params_.version_ = WKTFormatter::Version::WKT2; - d->params_.idOnTopLevelOnly_ = true; - d->params_.outputAxisOrder_ = false; - d->params_.primeMeridianOmittedIfGreenwich_ = true; - d->params_.ellipsoidUnitOmittedIfMetre_ = true; - d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = true; - d->params_.forceUNITKeyword_ = true; - d->params_.outputCSUnitOnlyOnceIfSame_ = true; - break; - - case Convention::WKT1_GDAL: - d->params_.version_ = WKTFormatter::Version::WKT1; - d->params_.outputAxisOrder_ = false; - d->params_.forceUNITKeyword_ = true; - d->params_.primeMeridianInDegree_ = true; - d->params_.outputAxis_ = - WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE; - break; - - case Convention::WKT1_ESRI: - d->params_.version_ = WKTFormatter::Version::WKT1; - d->params_.outputAxisOrder_ = false; - d->params_.forceUNITKeyword_ = true; - d->params_.primeMeridianInDegree_ = true; - d->params_.useESRIDialect_ = true; - d->params_.multiLine_ = false; - d->params_.outputAxis_ = WKTFormatter::OutputAxisRule::NO; - break; - - default: - assert(false); - break; - } -} - -// --------------------------------------------------------------------------- - -WKTFormatter &WKTFormatter::setOutputId(bool outputIdIn) { - if (d->indentLevel_ != 0) { - throw Exception( - "setOutputId() shall only be called when the stack state is empty"); - } - d->outputIdStack_[0] = outputIdIn; - return *this; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::Private::addNewLine() { result_ += '\n'; } - -// --------------------------------------------------------------------------- - -void WKTFormatter::Private::addIndentation() { - result_ += std::string(indentLevel_ * params_.indentWidth_, ' '); -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::enter() { - if (d->indentLevel_ == 0 && d->level_ == 0) { - d->stackHasChild_.push_back(false); - } - ++d->level_; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::leave() { - assert(d->level_ > 0); - --d->level_; - if (d->indentLevel_ == 0 && d->level_ == 0) { - d->stackHasChild_.pop_back(); - } -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::startNode(const std::string &keyword, bool hasId) { - if (!d->stackHasChild_.empty()) { - d->startNewChild(); - } else if (!d->result_.empty()) { - d->result_ += ','; - if (d->params_.multiLine_ && !keyword.empty()) { - d->addNewLine(); - } - } - - if (d->params_.multiLine_) { - if ((d->indentLevel_ || d->level_) && !keyword.empty()) { - if (!d->result_.empty()) { - d->addNewLine(); - } - d->addIndentation(); - } - } - - if (!keyword.empty()) { - d->result_ += keyword; - d->result_ += '['; - } - d->indentLevel_++; - d->stackHasChild_.push_back(false); - d->stackEmptyKeyword_.push_back(keyword.empty()); - - // Starting from a node that has a ID, we should emit ID nodes for : - // - this node - // - and for METHOD&PARAMETER nodes in WKT2, unless idOnTopLevelOnly_ is - // set. - // For WKT2, all other intermediate nodes shouldn't have ID ("not - // recommended") - if (!d->params_.idOnTopLevelOnly_ && d->indentLevel_ >= 2 && - d->params_.version_ == WKTFormatter::Version::WKT2 && - (keyword == WKTConstants::METHOD || - keyword == WKTConstants::PARAMETER)) { - pushOutputId(d->outputIdStack_[0]); - } else if (d->indentLevel_ >= 2 && - d->params_.version_ == WKTFormatter::Version::WKT2) { - pushOutputId(d->outputIdStack_[0] && !d->stackHasId_.back()); - } else { - pushOutputId(outputId()); - } - - d->stackHasId_.push_back(hasId || d->stackHasId_.back()); -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::endNode() { - assert(d->indentLevel_ > 0); - d->stackHasId_.pop_back(); - popOutputId(); - d->indentLevel_--; - bool emptyKeyword = d->stackEmptyKeyword_.back(); - d->stackEmptyKeyword_.pop_back(); - d->stackHasChild_.pop_back(); - if (!emptyKeyword) - d->result_ += ']'; -} - -// --------------------------------------------------------------------------- - -WKTFormatter &WKTFormatter::simulCurNodeHasId() { - d->stackHasId_.back() = true; - return *this; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::Private::startNewChild() { - assert(!stackHasChild_.empty()); - if (stackHasChild_.back()) { - result_ += ','; - } - stackHasChild_.back() = true; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::addQuotedString(const char *str) { - addQuotedString(std::string(str)); -} - -void WKTFormatter::addQuotedString(const std::string &str) { - d->startNewChild(); - d->result_ += '"'; - d->result_ += replaceAll(str, "\"", "\"\""); - d->result_ += '"'; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::add(const std::string &str) { - d->startNewChild(); - d->result_ += str; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::add(int number) { - d->startNewChild(); - d->result_ += internal::toString(number); -} - -// --------------------------------------------------------------------------- - -#ifdef __MINGW32__ -static std::string normalizeSerializedString(const std::string &in) { - // mingw will output 1e-0xy instead of 1e-xy. Fix that - auto pos = in.find("e-0"); - if (pos == std::string::npos) { - return in; - } - if (pos + 4 < in.size() && isdigit(in[pos + 3]) && isdigit(in[pos + 4])) { - return in.substr(0, pos + 2) + in.substr(pos + 3); - } - return in; -} -#else -static inline std::string normalizeSerializedString(const std::string &in) { - return in; -} -#endif - -// --------------------------------------------------------------------------- - -void WKTFormatter::add(double number, int precision) { - d->startNewChild(); - if (number == 0.0) { - if (d->params_.useESRIDialect_) { - d->result_ += "0.0"; - } else { - d->result_ += '0'; - } - } else { - std::string val( - normalizeSerializedString(internal::toString(number, precision))); - d->result_ += val; - if (d->params_.useESRIDialect_ && val.find('.') == std::string::npos) { - d->result_ += ".0"; - } - } -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::pushOutputUnit(bool outputUnitIn) { - d->outputUnitStack_.push_back(outputUnitIn); -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::popOutputUnit() { d->outputUnitStack_.pop_back(); } - -// --------------------------------------------------------------------------- - -bool WKTFormatter::outputUnit() const { return d->outputUnitStack_.back(); } - -// --------------------------------------------------------------------------- - -void WKTFormatter::pushOutputId(bool outputIdIn) { - d->outputIdStack_.push_back(outputIdIn); -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::popOutputId() { d->outputIdStack_.pop_back(); } - -// --------------------------------------------------------------------------- - -bool WKTFormatter::outputId() const { - return !d->params_.useESRIDialect_ && d->outputIdStack_.back(); -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::pushAxisLinearUnit(const UnitOfMeasureNNPtr &unit) { - d->axisLinearUnitStack_.push_back(unit); -} -// --------------------------------------------------------------------------- - -void WKTFormatter::popAxisLinearUnit() { d->axisLinearUnitStack_.pop_back(); } - -// --------------------------------------------------------------------------- - -const UnitOfMeasureNNPtr &WKTFormatter::axisLinearUnit() const { - return d->axisLinearUnitStack_.back(); -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::pushAxisAngularUnit(const UnitOfMeasureNNPtr &unit) { - d->axisAngularUnitStack_.push_back(unit); -} -// --------------------------------------------------------------------------- - -void WKTFormatter::popAxisAngularUnit() { d->axisAngularUnitStack_.pop_back(); } - -// --------------------------------------------------------------------------- - -const UnitOfMeasureNNPtr &WKTFormatter::axisAngularUnit() const { - return d->axisAngularUnitStack_.back(); -} - -// --------------------------------------------------------------------------- - -WKTFormatter::OutputAxisRule WKTFormatter::outputAxis() const { - return d->params_.outputAxis_; -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::outputAxisOrder() const { - return d->params_.outputAxisOrder_; -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::primeMeridianOmittedIfGreenwich() const { - return d->params_.primeMeridianOmittedIfGreenwich_; -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::ellipsoidUnitOmittedIfMetre() const { - return d->params_.ellipsoidUnitOmittedIfMetre_; -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::primeMeridianOrParameterUnitOmittedIfSameAsAxis() const { - return d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_; -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::outputCSUnitOnlyOnceIfSame() const { - return d->params_.outputCSUnitOnlyOnceIfSame_; -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::forceUNITKeyword() const { - return d->params_.forceUNITKeyword_; -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::primeMeridianInDegree() const { - return d->params_.primeMeridianInDegree_; -} - -// --------------------------------------------------------------------------- - -WKTFormatter::Version WKTFormatter::version() const { - return d->params_.version_; -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::use2018Keywords() const { - return d->params_.use2018Keywords_; -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::useESRIDialect() const { return d->params_.useESRIDialect_; } - -// --------------------------------------------------------------------------- - -const DatabaseContextPtr &WKTFormatter::databaseContext() const { - return d->dbContext_; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::setAbridgedTransformation(bool outputIn) { - d->abridgedTransformation_ = outputIn; -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::abridgedTransformation() const { - return d->abridgedTransformation_; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::setUseDerivingConversion(bool useDerivingConversionIn) { - d->useDerivingConversion_ = useDerivingConversionIn; -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::useDerivingConversion() const { - return d->useDerivingConversion_; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::setTOWGS84Parameters(const std::vector ¶ms) { - d->toWGS84Parameters_ = params; -} - -// --------------------------------------------------------------------------- - -const std::vector &WKTFormatter::getTOWGS84Parameters() const { - return d->toWGS84Parameters_; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::setVDatumExtension(const std::string &filename) { - d->vDatumExtension_ = filename; -} - -// --------------------------------------------------------------------------- - -const std::string &WKTFormatter::getVDatumExtension() const { - return d->vDatumExtension_; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::setHDatumExtension(const std::string &filename) { - d->hDatumExtension_ = filename; -} - -// --------------------------------------------------------------------------- - -const std::string &WKTFormatter::getHDatumExtension() const { - return d->hDatumExtension_; -} - -// --------------------------------------------------------------------------- - -std::string WKTFormatter::morphNameToESRI(const std::string &name) { - std::string ret; - bool insertUnderscore = false; - // Replace any special character by underscore, except at the beginning - // and of the name where those characters are removed. - for (char ch : name) { - if (ch == '+' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || - (ch >= 'A' && ch <= 'Z')) { - if (insertUnderscore && !ret.empty()) { - ret += '_'; - } - ret += ch; - insertUnderscore = false; - } else { - insertUnderscore = true; - } - } - return ret; -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::ingestWKTNode(const WKTNodeNNPtr &node) { - startNode(node->value(), true); - for (const auto &child : node->children()) { - if (!child->children().empty()) { - ingestWKTNode(child); - } else { - add(child->value()); - } - } - endNode(); -} - -#ifdef unused -// --------------------------------------------------------------------------- - -void WKTFormatter::startInversion() { - d->inversionStack_.push_back(!d->inversionStack_.back()); -} - -// --------------------------------------------------------------------------- - -void WKTFormatter::stopInversion() { - assert(!d->inversionStack_.empty()); - d->inversionStack_.pop_back(); -} - -// --------------------------------------------------------------------------- - -bool WKTFormatter::isInverted() const { return d->inversionStack_.back(); } -#endif - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -static WKTNodeNNPtr - null_node(NN_NO_CHECK(internal::make_unique(std::string()))); - -static inline bool isNull(const WKTNodeNNPtr &node) { - return &node == &null_node; -} - -struct WKTNode::Private { - std::string value_{}; - std::vector children_{}; - - explicit Private(const std::string &valueIn) : value_(valueIn) {} - - // cppcheck-suppress functionStatic - inline const std::string &value() PROJ_CONST_DEFN { return value_; } - - // cppcheck-suppress functionStatic - inline const std::vector &children() PROJ_CONST_DEFN { - return children_; - } - - // cppcheck-suppress functionStatic - inline size_t childrenSize() PROJ_CONST_DEFN { return children_.size(); } - - // cppcheck-suppress functionStatic - const WKTNodeNNPtr &lookForChild(const std::string &childName, - int occurrence) const noexcept; - - // cppcheck-suppress functionStatic - const WKTNodeNNPtr &lookForChild(const std::string &name) const noexcept; - - // cppcheck-suppress functionStatic - const WKTNodeNNPtr &lookForChild(const std::string &name, - const std::string &name2) const noexcept; - - // cppcheck-suppress functionStatic - const WKTNodeNNPtr &lookForChild(const std::string &name, - const std::string &name2, - const std::string &name3) const noexcept; - - // cppcheck-suppress functionStatic - const WKTNodeNNPtr &lookForChild(const std::string &name, - const std::string &name2, - const std::string &name3, - const std::string &name4) const noexcept; -}; - -#define GP() getPrivate() - -// --------------------------------------------------------------------------- - -const WKTNodeNNPtr &WKTNode::Private::lookForChild(const std::string &childName, - int occurrence) const - noexcept { - int occCount = 0; - for (const auto &child : children_) { - if (ci_equal(child->GP()->value(), childName)) { - if (occurrence == occCount) { - return child; - } - occCount++; - } - } - return null_node; -} - -const WKTNodeNNPtr & -WKTNode::Private::lookForChild(const std::string &name) const noexcept { - for (const auto &child : children_) { - const auto &v = child->GP()->value(); - if (ci_equal(v, name)) { - return child; - } - } - return null_node; -} - -const WKTNodeNNPtr & -WKTNode::Private::lookForChild(const std::string &name, - const std::string &name2) const noexcept { - for (const auto &child : children_) { - const auto &v = child->GP()->value(); - if (ci_equal(v, name) || ci_equal(v, name2)) { - return child; - } - } - return null_node; -} - -const WKTNodeNNPtr & -WKTNode::Private::lookForChild(const std::string &name, - const std::string &name2, - const std::string &name3) const noexcept { - for (const auto &child : children_) { - const auto &v = child->GP()->value(); - if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3)) { - return child; - } - } - return null_node; -} - -const WKTNodeNNPtr &WKTNode::Private::lookForChild( - const std::string &name, const std::string &name2, const std::string &name3, - const std::string &name4) const noexcept { - for (const auto &child : children_) { - const auto &v = child->GP()->value(); - if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3) || - ci_equal(v, name4)) { - return child; - } - } - return null_node; -} - -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instanciate a WKTNode. - * - * @param valueIn the name of the node. - */ -WKTNode::WKTNode(const std::string &valueIn) - : d(internal::make_unique(valueIn)) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -WKTNode::~WKTNode() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Adds a child to the current node. - * - * @param child child to add. This should not be a parent of this node. - */ -void WKTNode::addChild(WKTNodeNNPtr &&child) { - d->children_.push_back(std::move(child)); -} - -// --------------------------------------------------------------------------- - -/** \brief Return the (occurrence-1)th sub-node of name childName. - * - * @param childName name of the child. - * @param occurrence occurrence index (starting at 0) - * @return the child, or nullptr. - */ -const WKTNodePtr &WKTNode::lookForChild(const std::string &childName, - int occurrence) const noexcept { - int occCount = 0; - for (const auto &child : d->children_) { - if (ci_equal(child->GP()->value(), childName)) { - if (occurrence == occCount) { - return child; - } - occCount++; - } - } - return null_node; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the count of children of given name. - * - * @param childName name of the children to look for. - * @return count - */ -int WKTNode::countChildrenOfName(const std::string &childName) const noexcept { - int occCount = 0; - for (const auto &child : d->children_) { - if (ci_equal(child->GP()->value(), childName)) { - occCount++; - } - } - return occCount; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the value of a node. - */ -const std::string &WKTNode::value() const { return d->value_; } - -// --------------------------------------------------------------------------- - -/** \brief Return the children of a node. - */ -const std::vector &WKTNode::children() const { - return d->children_; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static size_t skipSpace(const std::string &str, size_t start) { - size_t i = start; - while (i < str.size() && ::isspace(str[i])) { - ++i; - } - return i; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -// As used in examples of OGC 12-063r5 -static const std::string startPrintedQuote("\xE2\x80\x9C"); -static const std::string endPrintedQuote("\xE2\x80\x9D"); -//! @endcond - -WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart, - int recLevel, size_t &indexEnd) { - if (recLevel == 16) { - throw ParsingException("too many nesting levels"); - } - std::string value; - size_t i = skipSpace(wkt, indexStart); - if (i == wkt.size()) { - throw ParsingException("whitespace only string"); - } - std::string closingStringMarker; - bool inString = false; - - for (; i < wkt.size() && - (inString || (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' && - wkt[i] != ']' && wkt[i] != ')' && !::isspace(wkt[i]))); - ++i) { - if (wkt[i] == '"') { - if (!inString) { - inString = true; - closingStringMarker = "\""; - } else if (closingStringMarker == "\"") { - if (i + 1 < wkt.size() && wkt[i + 1] == '"') { - i++; - } else { - inString = false; - closingStringMarker.clear(); - } - } - } else if (i + 3 <= wkt.size() && - wkt.substr(i, 3) == startPrintedQuote) { - if (!inString) { - inString = true; - closingStringMarker = endPrintedQuote; - value += '"'; - i += 2; - continue; - } - } else if (i + 3 <= wkt.size() && - closingStringMarker == endPrintedQuote && - wkt.substr(i, 3) == endPrintedQuote) { - inString = false; - closingStringMarker.clear(); - value += '"'; - i += 2; - continue; - } - value += wkt[i]; - } - i = skipSpace(wkt, i); - if (i == wkt.size()) { - if (indexStart == 0) { - throw ParsingException("missing ["); - } else { - throw ParsingException("missing , or ]"); - } - } - - auto node = NN_NO_CHECK(internal::make_unique(value)); - - if (indexStart > 0) { - if (wkt[i] == ',') { - indexEnd = i + 1; - return node; - } - if (wkt[i] == ']' || wkt[i] == ')') { - indexEnd = i; - return node; - } - } - if (wkt[i] != '[' && wkt[i] != '(') { - throw ParsingException("missing ["); - } - ++i; // skip [ - i = skipSpace(wkt, i); - while (i < wkt.size() && wkt[i] != ']' && wkt[i] != ')') { - size_t indexEndChild; - node->addChild(createFrom(wkt, i, recLevel + 1, indexEndChild)); - assert(indexEndChild > i); - i = indexEndChild; - i = skipSpace(wkt, i); - if (i < wkt.size() && wkt[i] == ',') { - ++i; - i = skipSpace(wkt, i); - } - } - if (i == wkt.size() || (wkt[i] != ']' && wkt[i] != ')')) { - throw ParsingException("missing ]"); - } - indexEnd = i + 1; - return node; -} -// --------------------------------------------------------------------------- - -/** \brief Instanciate a WKTNode hierarchy from a WKT string. - * - * @param wkt the WKT string to parse. - * @param indexStart the start index in the wkt string. - * @throw ParsingException - */ -WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart) { - size_t indexEnd; - return createFrom(wkt, indexStart, 0, indexEnd); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static std::string escapeIfQuotedString(const std::string &str) { - if (str.size() > 2 && str[0] == '"' && str.back() == '"') { - std::string res("\""); - res += replaceAll(str.substr(1, str.size() - 2), "\"", "\"\""); - res += '"'; - return res; - } else { - return str; - } -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Return a WKT representation of the tree structure. - */ -std::string WKTNode::toString() const { - std::string str(escapeIfQuotedString(d->value_)); - if (!d->children_.empty()) { - str += "["; - bool first = true; - for (auto &child : d->children_) { - if (!first) { - str += ','; - } - first = false; - str += child->toString(); - } - str += "]"; - } - return str; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct WKTParser::Private { - bool strict_ = true; - std::list warningList_{}; - std::vector toWGS84Parameters_{}; - std::string datumPROJ4Grids_{}; - bool esriStyle_ = false; - DatabaseContextPtr dbContext_{}; - - static constexpr int MAX_PROPERTY_SIZE = 1024; - PropertyMap **properties_{}; - int propertyCount_ = 0; - - Private() { properties_ = new PropertyMap *[MAX_PROPERTY_SIZE]; } - - ~Private() { - for (int i = 0; i < propertyCount_; i++) { - delete properties_[i]; - } - delete[] properties_; - } - Private(const Private &) = delete; - Private &operator=(const Private &) = delete; - - void emitRecoverableAssertion(const std::string &errorMsg); - - BaseObjectNNPtr build(const WKTNodeNNPtr &node); - - IdentifierPtr buildId(const WKTNodeNNPtr &node, bool tolerant = true); - - PropertyMap &buildProperties(const WKTNodeNNPtr &node); - - ObjectDomainPtr buildObjectDomain(const WKTNodeNNPtr &node); - - static std::string stripQuotes(const WKTNodeNNPtr &node); - - static double asDouble(const WKTNodeNNPtr &node); - - UnitOfMeasure - buildUnit(const WKTNodeNNPtr &node, - UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN); - - UnitOfMeasure buildUnitInSubNode( - const WKTNodeNNPtr &node, - common::UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN); - - EllipsoidNNPtr buildEllipsoid(const WKTNodeNNPtr &node); - - PrimeMeridianNNPtr - buildPrimeMeridian(const WKTNodeNNPtr &node, - const UnitOfMeasure &defaultAngularUnit); - - optional getAnchor(const WKTNodeNNPtr &node); - - static void parseDynamic(const WKTNodeNNPtr &dynamicNode, - double &frameReferenceEpoch, - util::optional &modelName); - - GeodeticReferenceFrameNNPtr - buildGeodeticReferenceFrame(const WKTNodeNNPtr &node, - const PrimeMeridianNNPtr &primeMeridian, - const WKTNodeNNPtr &dynamicNode); - - DatumEnsembleNNPtr buildDatumEnsemble(const WKTNodeNNPtr &node, - const PrimeMeridianPtr &primeMeridian, - bool expectEllipsoid); - - MeridianNNPtr buildMeridian(const WKTNodeNNPtr &node); - CoordinateSystemAxisNNPtr buildAxis(const WKTNodeNNPtr &node, - const UnitOfMeasure &unitIn, - const UnitOfMeasure::Type &unitType, - bool isGeocentric, - int expectedOrderNum); - - CoordinateSystemNNPtr buildCS(const WKTNodeNNPtr &node, /* maybe null */ - const WKTNodeNNPtr &parentNode, - const UnitOfMeasure &defaultAngularUnit); - - GeodeticCRSNNPtr buildGeodeticCRS(const WKTNodeNNPtr &node); - - CRSNNPtr buildDerivedGeodeticCRS(const WKTNodeNNPtr &node); - - static UnitOfMeasure - guessUnitForParameter(const std::string ¶mName, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit); - - void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged, - std::vector ¶meters, - std::vector &values, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit); - - static void addExtensionProj4ToProp(const WKTNode::Private *nodeP, - PropertyMap &props); - - ConversionNNPtr buildConversion(const WKTNodeNNPtr &node, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit); - - static bool hasWebMercPROJ4String(const WKTNodeNNPtr &projCRSNode, - const WKTNodeNNPtr &projectionNode); - - static std::string projectionGetParameter(const WKTNodeNNPtr &projCRSNode, - const char *paramName); - - ConversionNNPtr buildProjection(const WKTNodeNNPtr &projCRSNode, - const WKTNodeNNPtr &projectionNode, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit); - - ConversionNNPtr - buildProjectionStandard(const WKTNodeNNPtr &projCRSNode, - const WKTNodeNNPtr &projectionNode, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit); - - ConversionNNPtr - buildProjectionFromESRI(const WKTNodeNNPtr &projCRSNode, - const WKTNodeNNPtr &projectionNode, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit); - - ProjectedCRSNNPtr buildProjectedCRS(const WKTNodeNNPtr &node); - - VerticalReferenceFrameNNPtr - buildVerticalReferenceFrame(const WKTNodeNNPtr &node, - const WKTNodeNNPtr &dynamicNode); - - TemporalDatumNNPtr buildTemporalDatum(const WKTNodeNNPtr &node); - - EngineeringDatumNNPtr buildEngineeringDatum(const WKTNodeNNPtr &node); - - ParametricDatumNNPtr buildParametricDatum(const WKTNodeNNPtr &node); - - CRSNNPtr buildVerticalCRS(const WKTNodeNNPtr &node); - - DerivedVerticalCRSNNPtr buildDerivedVerticalCRS(const WKTNodeNNPtr &node); - - CompoundCRSNNPtr buildCompoundCRS(const WKTNodeNNPtr &node); - - BoundCRSNNPtr buildBoundCRS(const WKTNodeNNPtr &node); - - TemporalCSNNPtr buildTemporalCS(const WKTNodeNNPtr &parentNode); - - TemporalCRSNNPtr buildTemporalCRS(const WKTNodeNNPtr &node); - - DerivedTemporalCRSNNPtr buildDerivedTemporalCRS(const WKTNodeNNPtr &node); - - EngineeringCRSNNPtr buildEngineeringCRS(const WKTNodeNNPtr &node); - - EngineeringCRSNNPtr - buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node); - - DerivedEngineeringCRSNNPtr - buildDerivedEngineeringCRS(const WKTNodeNNPtr &node); - - ParametricCSNNPtr buildParametricCS(const WKTNodeNNPtr &parentNode); - - ParametricCRSNNPtr buildParametricCRS(const WKTNodeNNPtr &node); - - DerivedParametricCRSNNPtr - buildDerivedParametricCRS(const WKTNodeNNPtr &node); - - DerivedProjectedCRSNNPtr buildDerivedProjectedCRS(const WKTNodeNNPtr &node); - - CRSPtr buildCRS(const WKTNodeNNPtr &node); - - CoordinateOperationNNPtr buildCoordinateOperation(const WKTNodeNNPtr &node); - - ConcatenatedOperationNNPtr - buildConcatenatedOperation(const WKTNodeNNPtr &node); -}; - -// --------------------------------------------------------------------------- - -WKTParser::WKTParser() : d(internal::make_unique()) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -WKTParser::~WKTParser() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Set whether parsing should be done in strict mode. - */ -WKTParser &WKTParser::setStrict(bool strict) { - d->strict_ = strict; - return *this; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the list of warnings found during parsing. - * - * \note The list might be non-empty only is setStrict(false) has been called. - */ -std::list WKTParser::warningList() const { - return d->warningList_; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void WKTParser::Private::emitRecoverableAssertion(const std::string &errorMsg) { - if (strict_) { - throw ParsingException(errorMsg); - } else { - warningList_.push_back(errorMsg); - } -} - -// --------------------------------------------------------------------------- - -static double asDouble(const std::string &val) { return c_locale_stod(val); } - -// --------------------------------------------------------------------------- - -PROJ_NO_RETURN static void ThrowNotEnoughChildren(const std::string &nodeName) { - throw ParsingException( - concat("not enough children in ", nodeName, " node")); -} - -// --------------------------------------------------------------------------- - -PROJ_NO_RETURN static void -ThrowNotRequiredNumberOfChildren(const std::string &nodeName) { - throw ParsingException( - concat("not required number of children in ", nodeName, " node")); -} - -// --------------------------------------------------------------------------- - -PROJ_NO_RETURN static void ThrowMissing(const std::string &nodeName) { - throw ParsingException(concat("missing ", nodeName, " node")); -} - -// --------------------------------------------------------------------------- - -PROJ_NO_RETURN static void -ThrowNotExpectedCSType(const std::string &expectedCSType) { - throw ParsingException(concat("CS node is not of type ", expectedCSType)); -} - -// --------------------------------------------------------------------------- - -static ParsingException buildRethrow(const char *funcName, - const std::exception &e) { - std::string res(funcName); - res += ": "; - res += e.what(); - return ParsingException(res); -} - -// --------------------------------------------------------------------------- - -std::string WKTParser::Private::stripQuotes(const WKTNodeNNPtr &node) { - return ::stripQuotes(node->GP()->value()); -} - -// --------------------------------------------------------------------------- - -double WKTParser::Private::asDouble(const WKTNodeNNPtr &node) { - return io::asDouble(node->GP()->value()); -} - -// --------------------------------------------------------------------------- - -IdentifierPtr WKTParser::Private::buildId(const WKTNodeNNPtr &node, - bool tolerant) { - const auto *nodeP = node->GP(); - const auto &nodeChidren = nodeP->children(); - if (nodeChidren.size() >= 2) { - auto codeSpace = stripQuotes(nodeChidren[0]); - auto code = stripQuotes(nodeChidren[1]); - auto &citationNode = nodeP->lookForChild(WKTConstants::CITATION); - auto &uriNode = nodeP->lookForChild(WKTConstants::URI); - PropertyMap propertiesId; - propertiesId.set(Identifier::CODESPACE_KEY, codeSpace); - bool authoritySet = false; - /*if (!isNull(citationNode))*/ { - const auto *citationNodeP = citationNode->GP(); - if (citationNodeP->childrenSize() == 1) { - authoritySet = true; - propertiesId.set(Identifier::AUTHORITY_KEY, - stripQuotes(citationNodeP->children()[0])); - } - } - if (!authoritySet) { - propertiesId.set(Identifier::AUTHORITY_KEY, codeSpace); - } - /*if (!isNull(uriNode))*/ { - const auto *uriNodeP = uriNode->GP(); - if (uriNodeP->childrenSize() == 1) { - propertiesId.set(Identifier::URI_KEY, - stripQuotes(uriNodeP->children()[0])); - } - } - if (nodeChidren.size() >= 3 && - nodeChidren[2]->GP()->childrenSize() == 0) { - auto version = stripQuotes(nodeChidren[2]); - propertiesId.set(Identifier::VERSION_KEY, version); - } - return Identifier::create(code, propertiesId); - } else if (strict_ || !tolerant) { - ThrowNotEnoughChildren(nodeP->value()); - } else { - std::string msg("not enough children in "); - msg += nodeP->value(); - msg += " node"; - warningList_.emplace_back(std::move(msg)); - } - return nullptr; -} - -// --------------------------------------------------------------------------- - -PropertyMap &WKTParser::Private::buildProperties(const WKTNodeNNPtr &node) { - - if (propertyCount_ == MAX_PROPERTY_SIZE) { - throw ParsingException("MAX_PROPERTY_SIZE reached"); - } - properties_[propertyCount_] = new PropertyMap(); - auto &&properties = properties_[propertyCount_]; - propertyCount_++; - - std::string authNameFromAlias; - std::string codeFromAlias; - const auto *nodeP = node->GP(); - const auto &nodeChildren = nodeP->children(); - if (!nodeChildren.empty()) { - const auto &nodeName(nodeP->value()); - auto name(stripQuotes(nodeChildren[0])); - if (ends_with(name, " (deprecated)")) { - name.resize(name.size() - strlen(" (deprecated)")); - properties->set(common::IdentifiedObject::DEPRECATED_KEY, true); - } - - const char *tableNameForAlias = nullptr; - if (ci_equal(nodeName, WKTConstants::GEOGCS)) { - if (starts_with(name, "GCS_")) { - esriStyle_ = true; - if (name == "GCS_WGS_1984") { - name = "WGS 84"; - } else { - tableNameForAlias = "geodetic_crs"; - } - } - } else if (esriStyle_ && ci_equal(nodeName, WKTConstants::SPHEROID)) { - if (name == "WGS_1984") { - name = "WGS 84"; - authNameFromAlias = Identifier::EPSG; - codeFromAlias = "7030"; - } else { - tableNameForAlias = "ellipsoid"; - } - } - - if (dbContext_ && tableNameForAlias) { - std::string outTableName; - auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), - std::string()); - auto officialName = authFactory->getOfficialNameFromAlias( - name, tableNameForAlias, "ESRI", false, outTableName, - authNameFromAlias, codeFromAlias); - if (!officialName.empty()) { - name = officialName; - - // Clearing authority for geodetic_crs because of - // potential axis order mismatch. - if (strcmp(tableNameForAlias, "geodetic_crs") == 0) { - authNameFromAlias.clear(); - codeFromAlias.clear(); - } - } - } - - properties->set(IdentifiedObject::NAME_KEY, name); - } - - auto identifiers = ArrayOfBaseObject::create(); - 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); - if (id) { - identifiers->add(NN_NO_CHECK(id)); - } - } - } - if (identifiers->empty() && !authNameFromAlias.empty()) { - identifiers->add(Identifier::create( - codeFromAlias, - PropertyMap() - .set(Identifier::CODESPACE_KEY, authNameFromAlias) - .set(Identifier::AUTHORITY_KEY, authNameFromAlias))); - } - if (!identifiers->empty()) { - properties->set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); - } - - auto &remarkNode = nodeP->lookForChild(WKTConstants::REMARK); - if (!isNull(remarkNode)) { - const auto &remarkChildren = remarkNode->GP()->children(); - if (remarkChildren.size() == 1) { - properties->set(IdentifiedObject::REMARKS_KEY, - stripQuotes(remarkChildren[0])); - } else { - ThrowNotRequiredNumberOfChildren(remarkNode->GP()->value()); - } - } - - ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create(); - for (const auto &subNode : nodeP->children()) { - const auto &subNodeName(subNode->GP()->value()); - if (ci_equal(subNodeName, WKTConstants::USAGE)) { - auto objectDomain = buildObjectDomain(subNode); - if (!objectDomain) { - throw ParsingException( - concat("missing children in ", subNodeName, " node")); - } - array->add(NN_NO_CHECK(objectDomain)); - } - } - if (!array->empty()) { - properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, array); - } else { - auto objectDomain = buildObjectDomain(node); - if (objectDomain) { - properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, - NN_NO_CHECK(objectDomain)); - } - } - - return *properties; -} - -// --------------------------------------------------------------------------- - -ObjectDomainPtr -WKTParser::Private::buildObjectDomain(const WKTNodeNNPtr &node) { - - const auto *nodeP = node->GP(); - auto &scopeNode = nodeP->lookForChild(WKTConstants::SCOPE); - auto &areaNode = nodeP->lookForChild(WKTConstants::AREA); - auto &bboxNode = nodeP->lookForChild(WKTConstants::BBOX); - auto &verticalExtentNode = - nodeP->lookForChild(WKTConstants::VERTICALEXTENT); - auto &temporalExtentNode = nodeP->lookForChild(WKTConstants::TIMEEXTENT); - if (!isNull(scopeNode) || !isNull(areaNode) || !isNull(bboxNode) || - !isNull(verticalExtentNode) || !isNull(temporalExtentNode)) { - optional scope; - const auto *scopeNodeP = scopeNode->GP(); - const auto &scopeChildren = scopeNodeP->children(); - if (scopeChildren.size() == 1) { - scope = stripQuotes(scopeChildren[0]); - } - ExtentPtr extent; - if (!isNull(areaNode) || !isNull(bboxNode)) { - util::optional description; - std::vector geogExtent; - std::vector verticalExtent; - std::vector temporalExtent; - if (!isNull(areaNode)) { - const auto &areaChildren = areaNode->GP()->children(); - if (areaChildren.size() == 1) { - description = stripQuotes(areaChildren[0]); - } else { - ThrowNotRequiredNumberOfChildren(areaNode->GP()->value()); - } - } - if (!isNull(bboxNode)) { - const auto &bboxChildren = bboxNode->GP()->children(); - if (bboxChildren.size() == 4) { - try { - double south = asDouble(bboxChildren[0]); - double west = asDouble(bboxChildren[1]); - double north = asDouble(bboxChildren[2]); - double east = asDouble(bboxChildren[3]); - auto bbox = GeographicBoundingBox::create(west, south, - east, north); - geogExtent.emplace_back(bbox); - } catch (const std::exception &) { - throw ParsingException(concat("not 4 double values in ", - bboxNode->GP()->value(), - " node")); - } - } else { - ThrowNotRequiredNumberOfChildren(bboxNode->GP()->value()); - } - } - - if (!isNull(verticalExtentNode)) { - const auto &verticalExtentChildren = - verticalExtentNode->GP()->children(); - const auto verticalExtentChildrenSize = - verticalExtentChildren.size(); - if (verticalExtentChildrenSize == 2 || - verticalExtentChildrenSize == 3) { - double min; - double max; - try { - min = asDouble(verticalExtentChildren[0]); - max = asDouble(verticalExtentChildren[1]); - } catch (const std::exception &) { - throw ParsingException( - concat("not 2 double values in ", - verticalExtentNode->GP()->value(), " node")); - } - UnitOfMeasure unit = UnitOfMeasure::METRE; - if (verticalExtentChildrenSize == 3) { - unit = buildUnit(verticalExtentChildren[2], - UnitOfMeasure::Type::LINEAR); - } - verticalExtent.emplace_back(VerticalExtent::create( - min, max, util::nn_make_shared(unit))); - } else { - ThrowNotRequiredNumberOfChildren( - verticalExtentNode->GP()->value()); - } - } - - if (!isNull(temporalExtentNode)) { - const auto &temporalExtentChildren = - temporalExtentNode->GP()->children(); - if (temporalExtentChildren.size() == 2) { - temporalExtent.emplace_back(TemporalExtent::create( - stripQuotes(temporalExtentChildren[0]), - stripQuotes(temporalExtentChildren[1]))); - } else { - ThrowNotRequiredNumberOfChildren( - temporalExtentNode->GP()->value()); - } - } - extent = Extent::create(description, geogExtent, verticalExtent, - temporalExtent) - .as_nullable(); - } - return ObjectDomain::create(scope, extent).as_nullable(); - } - - return nullptr; -} - -// --------------------------------------------------------------------------- - -UnitOfMeasure WKTParser::Private::buildUnit(const WKTNodeNNPtr &node, - UnitOfMeasure::Type type) { - const auto *nodeP = node->GP(); - const auto &children = nodeP->children(); - if ((type != UnitOfMeasure::Type::TIME && children.size() < 2) || - (type == UnitOfMeasure::Type::TIME && children.size() < 1)) { - ThrowNotEnoughChildren(nodeP->value()); - } - try { - std::string unitName(stripQuotes(children[0])); - PropertyMap properties(buildProperties(node)); - auto &idNode = - nodeP->lookForChild(WKTConstants::ID, WKTConstants::AUTHORITY); - if (!isNull(idNode) && idNode->GP()->childrenSize() < 2) { - emitRecoverableAssertion("not enough children in " + - idNode->GP()->value() + " node"); - } - const bool hasValidIdNode = - !isNull(idNode) && idNode->GP()->childrenSize() >= 2; - - const auto &idNodeChildren(idNode->GP()->children()); - std::string codeSpace(hasValidIdNode ? stripQuotes(idNodeChildren[0]) - : std::string()); - std::string code(hasValidIdNode ? stripQuotes(idNodeChildren[1]) - : std::string()); - - bool queryDb = true; - if (type == UnitOfMeasure::Type::UNKNOWN) { - if (ci_equal(unitName, "METER") || ci_equal(unitName, "METRE")) { - type = UnitOfMeasure::Type::LINEAR; - unitName = "metre"; - if (codeSpace.empty()) { - codeSpace = Identifier::EPSG; - code = "9001"; - queryDb = false; - } - } else if (ci_equal(unitName, "DEGREE") || - ci_equal(unitName, "GRAD")) { - type = UnitOfMeasure::Type::ANGULAR; - } - } - - if (esriStyle_ && dbContext_ && queryDb) { - std::string outTableName; - std::string authNameFromAlias; - std::string codeFromAlias; - auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), - std::string()); - auto officialName = authFactory->getOfficialNameFromAlias( - unitName, "unit_of_measure", "ESRI", false, outTableName, - authNameFromAlias, codeFromAlias); - if (!officialName.empty()) { - unitName = officialName; - codeSpace = authNameFromAlias; - code = codeFromAlias; - } - } - - double convFactor = children.size() >= 2 ? asDouble(children[1]) : 0.0; - constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37; - constexpr double REL_ERROR = 1e-10; - // Fix common rounding errors - if (std::fabs(convFactor - UnitOfMeasure::DEGREE.conversionToSI()) < - REL_ERROR * convFactor) { - convFactor = UnitOfMeasure::DEGREE.conversionToSI(); - } else if (std::fabs(convFactor - US_FOOT_CONV_FACTOR) < - REL_ERROR * convFactor) { - convFactor = US_FOOT_CONV_FACTOR; - } - - return UnitOfMeasure(unitName, convFactor, type, codeSpace, code); - } catch (const std::exception &e) { - throw buildRethrow(__FUNCTION__, e); - } -} - -// --------------------------------------------------------------------------- - -// node here is a parent node, not a UNIT/LENGTHUNIT/ANGLEUNIT/TIMEUNIT/... node -UnitOfMeasure WKTParser::Private::buildUnitInSubNode(const WKTNodeNNPtr &node, - UnitOfMeasure::Type type) { - const auto *nodeP = node->GP(); - { - auto &unitNode = nodeP->lookForChild(WKTConstants::LENGTHUNIT); - if (!isNull(unitNode)) { - return buildUnit(unitNode, UnitOfMeasure::Type::LINEAR); - } - } - - { - auto &unitNode = nodeP->lookForChild(WKTConstants::ANGLEUNIT); - if (!isNull(unitNode)) { - return buildUnit(unitNode, UnitOfMeasure::Type::ANGULAR); - } - } - - { - auto &unitNode = nodeP->lookForChild(WKTConstants::SCALEUNIT); - if (!isNull(unitNode)) { - return buildUnit(unitNode, UnitOfMeasure::Type::SCALE); - } - } - - { - auto &unitNode = nodeP->lookForChild(WKTConstants::TIMEUNIT); - if (!isNull(unitNode)) { - return buildUnit(unitNode, UnitOfMeasure::Type::TIME); - } - } - { - auto &unitNode = nodeP->lookForChild(WKTConstants::TEMPORALQUANTITY); - if (!isNull(unitNode)) { - return buildUnit(unitNode, UnitOfMeasure::Type::TIME); - } - } - - { - auto &unitNode = nodeP->lookForChild(WKTConstants::PARAMETRICUNIT); - if (!isNull(unitNode)) { - return buildUnit(unitNode, UnitOfMeasure::Type::PARAMETRIC); - } - } - - { - auto &unitNode = nodeP->lookForChild(WKTConstants::UNIT); - if (!isNull(unitNode)) { - return buildUnit(unitNode, type); - } - } - - return UnitOfMeasure::NONE; -} - -// --------------------------------------------------------------------------- - -EllipsoidNNPtr WKTParser::Private::buildEllipsoid(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - const auto &children = nodeP->children(); - if (children.size() < 3) { - ThrowNotEnoughChildren(nodeP->value()); - } - try { - UnitOfMeasure unit = - buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR); - if (unit == UnitOfMeasure::NONE) { - unit = UnitOfMeasure::METRE; - } - Length semiMajorAxis(asDouble(children[1]), unit); - Scale invFlattening(asDouble(children[2])); - const auto celestialBody( - Ellipsoid::guessBodyName(dbContext_, semiMajorAxis.getSIValue())); - if (invFlattening.getSIValue() == 0) { - return Ellipsoid::createSphere(buildProperties(node), semiMajorAxis, - celestialBody); - } else { - return Ellipsoid::createFlattenedSphere( - buildProperties(node), semiMajorAxis, invFlattening, - celestialBody); - } - } catch (const std::exception &e) { - throw buildRethrow(__FUNCTION__, e); - } -} - -// --------------------------------------------------------------------------- - -PrimeMeridianNNPtr WKTParser::Private::buildPrimeMeridian( - const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit) { - const auto *nodeP = node->GP(); - const auto &children = nodeP->children(); - if (children.size() < 2) { - ThrowNotEnoughChildren(nodeP->value()); - } - auto name = stripQuotes(children[0]); - UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR); - if (unit == UnitOfMeasure::NONE) { - unit = defaultAngularUnit; - if (unit == UnitOfMeasure::NONE) { - unit = UnitOfMeasure::DEGREE; - } - } - try { - double angleValue = asDouble(children[1]); - - // Correct for GDAL WKT1 departure - if (name == "Paris" && std::fabs(angleValue - 2.33722917) < 1e-8 && - unit == UnitOfMeasure::GRAD) { - angleValue = 2.5969213; - } - - Angle angle(angleValue, unit); - return PrimeMeridian::create(buildProperties(node), angle); - } catch (const std::exception &e) { - throw buildRethrow(__FUNCTION__, e); - } -} - -// --------------------------------------------------------------------------- - -optional WKTParser::Private::getAnchor(const WKTNodeNNPtr &node) { - - auto &anchorNode = node->GP()->lookForChild(WKTConstants::ANCHOR); - if (anchorNode->GP()->childrenSize() == 1) { - return optional( - stripQuotes(anchorNode->GP()->children()[0])); - } - return optional(); -} - -// --------------------------------------------------------------------------- - -static const PrimeMeridianNNPtr & -fixupPrimeMeridan(const EllipsoidNNPtr &ellipsoid, - const PrimeMeridianNNPtr &pm) { - return (ellipsoid->celestialBody() != Ellipsoid::EARTH && - pm.get() == PrimeMeridian::GREENWICH.get()) - ? PrimeMeridian::REFERENCE_MERIDIAN - : pm; -} - -// --------------------------------------------------------------------------- - -GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame( - const WKTNodeNNPtr &node, const PrimeMeridianNNPtr &primeMeridian, - const WKTNodeNNPtr &dynamicNode) { - const auto *nodeP = node->GP(); - auto &ellipsoidNode = - nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID); - if (isNull(ellipsoidNode)) { - ThrowMissing(WKTConstants::ELLIPSOID); - } - auto &properties = buildProperties(node); - - // do that before buildEllipsoid() so that esriStyle_ can be set - auto name = stripQuotes(nodeP->children()[0]); - if (name == "WGS_1984") { - properties.set(IdentifiedObject::NAME_KEY, - GeodeticReferenceFrame::EPSG_6326->nameStr()); - } else if (starts_with(name, "D_")) { - esriStyle_ = true; - const char *tableNameForAlias = nullptr; - std::string authNameFromAlias; - std::string codeFromAlias; - if (name == "D_WGS_1984") { - name = "World Geodetic System 1984"; - authNameFromAlias = Identifier::EPSG; - codeFromAlias = "6326"; - } else { - tableNameForAlias = "geodetic_datum"; - } - - if (dbContext_ && tableNameForAlias) { - std::string outTableName; - auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), - std::string()); - auto officialName = authFactory->getOfficialNameFromAlias( - name, tableNameForAlias, "ESRI", false, outTableName, - authNameFromAlias, codeFromAlias); - if (!officialName.empty()) { - if (primeMeridian->nameStr() != - PrimeMeridian::GREENWICH->nameStr()) { - auto nameWithPM = - officialName + " (" + primeMeridian->nameStr() + ")"; - if (dbContext_->isKnownName(nameWithPM, "geodetic_datum")) { - officialName = nameWithPM; - } - } - name = officialName; - } - } - - properties.set(IdentifiedObject::NAME_KEY, name); - 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); - } - } else if (name.find('_') != std::string::npos) { - // Likely coming from WKT1 - if (dbContext_) { - auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), - std::string()); - auto res = authFactory->createObjectsFromName( - name, {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, - true, 1); - bool foundDatumName = false; - if (!res.empty()) { - const auto &refDatum = res.front(); - if (metadata::Identifier::isEquivalentName( - name.c_str(), refDatum->nameStr().c_str())) { - foundDatumName = true; - properties.set(IdentifiedObject::NAME_KEY, - refDatum->nameStr()); - if (!properties.get(Identifier::CODESPACE_KEY) && - refDatum->identifiers().size() == 1) { - const auto &id = refDatum->identifiers()[0]; - auto identifiers = ArrayOfBaseObject::create(); - identifiers->add(Identifier::create( - id->code(), PropertyMap() - .set(Identifier::CODESPACE_KEY, - *id->codeSpace()) - .set(Identifier::AUTHORITY_KEY, - *id->codeSpace()))); - properties.set(IdentifiedObject::IDENTIFIERS_KEY, - identifiers); - } - } - } else { - // Get official name from database if AUTHORITY is present - auto &idNode = nodeP->lookForChild(WKTConstants::AUTHORITY); - if (!isNull(idNode)) { - try { - auto id = buildId(idNode); - auto authFactory2 = AuthorityFactory::create( - NN_NO_CHECK(dbContext_), *id->codeSpace()); - auto dbDatum = - authFactory2->createGeodeticDatum(id->code()); - foundDatumName = true; - properties.set(IdentifiedObject::NAME_KEY, - dbDatum->nameStr()); - } catch (const std::exception &) { - } - } - } - - if (!foundDatumName) { - std::string outTableName; - std::string authNameFromAlias; - std::string codeFromAlias; - auto officialName = authFactory->getOfficialNameFromAlias( - name, "geodetic_datum", std::string(), true, outTableName, - authNameFromAlias, codeFromAlias); - if (!officialName.empty()) { - properties.set(IdentifiedObject::NAME_KEY, officialName); - } - } - } - } - - auto ellipsoid = buildEllipsoid(ellipsoidNode); - const auto &primeMeridianModified = - fixupPrimeMeridan(ellipsoid, primeMeridian); - - auto &TOWGS84Node = nodeP->lookForChild(WKTConstants::TOWGS84); - if (!isNull(TOWGS84Node)) { - const auto &TOWGS84Children = TOWGS84Node->GP()->children(); - const size_t TOWGS84Size = TOWGS84Children.size(); - if (TOWGS84Size == 3 || TOWGS84Size == 7) { - try { - for (const auto &child : TOWGS84Children) { - toWGS84Parameters_.push_back(asDouble(child)); - } - for (size_t i = TOWGS84Size; i < 7; ++i) { - toWGS84Parameters_.push_back(0.0); - } - } catch (const std::exception &) { - throw ParsingException("Invalid TOWGS84 node"); - } - } else { - throw ParsingException("Invalid TOWGS84 node"); - } - } - - auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION); - const auto &extensionChildren = extensionNode->GP()->children(); - if (extensionChildren.size() == 2) { - if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) { - datumPROJ4Grids_ = stripQuotes(extensionChildren[1]); - } - } - - if (!isNull(dynamicNode)) { - double frameReferenceEpoch = 0.0; - util::optional modelName; - parseDynamic(dynamicNode, frameReferenceEpoch, modelName); - return DynamicGeodeticReferenceFrame::create( - properties, ellipsoid, getAnchor(node), primeMeridianModified, - common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR), - modelName); - } - - return GeodeticReferenceFrame::create( - properties, ellipsoid, getAnchor(node), primeMeridianModified); -} - -// --------------------------------------------------------------------------- - -DatumEnsembleNNPtr -WKTParser::Private::buildDatumEnsemble(const WKTNodeNNPtr &node, - const PrimeMeridianPtr &primeMeridian, - bool expectEllipsoid) { - const auto *nodeP = node->GP(); - auto &ellipsoidNode = - nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID); - if (expectEllipsoid && isNull(ellipsoidNode)) { - ThrowMissing(WKTConstants::ELLIPSOID); - } - - std::vector datums; - for (const auto &subNode : nodeP->children()) { - if (ci_equal(subNode->GP()->value(), WKTConstants::MEMBER)) { - if (subNode->GP()->childrenSize() == 0) { - throw ParsingException("Invalid MEMBER node"); - } - if (expectEllipsoid) { - datums.emplace_back(GeodeticReferenceFrame::create( - buildProperties(subNode), buildEllipsoid(ellipsoidNode), - optional(), - primeMeridian ? NN_NO_CHECK(primeMeridian) - : PrimeMeridian::GREENWICH)); - } else { - datums.emplace_back( - VerticalReferenceFrame::create(buildProperties(subNode))); - } - } - } - - auto &accuracyNode = nodeP->lookForChild(WKTConstants::ENSEMBLEACCURACY); - auto &accuracyNodeChildren = accuracyNode->GP()->children(); - if (accuracyNodeChildren.empty()) { - ThrowMissing(WKTConstants::ENSEMBLEACCURACY); - } - auto accuracy = - PositionalAccuracy::create(accuracyNodeChildren[0]->GP()->value()); - - try { - return DatumEnsemble::create(buildProperties(node), datums, accuracy); - } catch (const util::Exception &e) { - throw buildRethrow(__FUNCTION__, e); - } -} - -// --------------------------------------------------------------------------- - -MeridianNNPtr WKTParser::Private::buildMeridian(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - const auto &children = nodeP->children(); - if (children.size() < 2) { - ThrowNotEnoughChildren(nodeP->value()); - } - UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR); - try { - double angleValue = asDouble(children[0]); - Angle angle(angleValue, unit); - return Meridian::create(angle); - } catch (const std::exception &e) { - throw buildRethrow(__FUNCTION__, e); - } -} - -// --------------------------------------------------------------------------- - -PROJ_NO_RETURN static void ThrowParsingExceptionMissingUNIT() { - throw ParsingException("buildCS: missing UNIT"); -} - -// --------------------------------------------------------------------------- - -CoordinateSystemAxisNNPtr -WKTParser::Private::buildAxis(const WKTNodeNNPtr &node, - const UnitOfMeasure &unitIn, - const UnitOfMeasure::Type &unitType, - bool isGeocentric, int expectedOrderNum) { - const auto *nodeP = node->GP(); - const auto &children = nodeP->children(); - if (children.size() < 2) { - ThrowNotEnoughChildren(nodeP->value()); - } - - auto &orderNode = nodeP->lookForChild(WKTConstants::ORDER); - if (!isNull(orderNode)) { - const auto &orderNodeChildren = orderNode->GP()->children(); - if (orderNodeChildren.size() != 1) { - ThrowNotEnoughChildren(WKTConstants::ORDER); - } - const auto &order = orderNodeChildren[0]->GP()->value(); - int orderNum; - try { - orderNum = std::stoi(order); - } catch (const std::exception &) { - throw ParsingException( - concat("buildAxis: invalid ORDER value: ", order)); - } - if (orderNum != expectedOrderNum) { - throw ParsingException( - concat("buildAxis: did not get expected ORDER value: ", order)); - } - } - - // The axis designation in WK2 can be: "name", "(abbrev)" or "name - // (abbrev)" - std::string axisDesignation(stripQuotes(children[0])); - size_t sepPos = axisDesignation.find(" ("); - std::string axisName; - std::string abbreviation; - if (sepPos != std::string::npos && axisDesignation.back() == ')') { - axisName = CoordinateSystemAxis::normalizeAxisName( - axisDesignation.substr(0, sepPos)); - abbreviation = axisDesignation.substr(sepPos + 2); - abbreviation.resize(abbreviation.size() - 1); - } else if (!axisDesignation.empty() && axisDesignation[0] == '(' && - axisDesignation.back() == ')') { - abbreviation = axisDesignation.substr(1, axisDesignation.size() - 2); - if (abbreviation == AxisAbbreviation::E) { - axisName = AxisName::Easting; - } else if (abbreviation == AxisAbbreviation::N) { - axisName = AxisName::Northing; - } else if (abbreviation == AxisAbbreviation::lat) { - axisName = AxisName::Latitude; - } else if (abbreviation == AxisAbbreviation::lon) { - axisName = AxisName::Longitude; - } - } else { - axisName = CoordinateSystemAxis::normalizeAxisName(axisDesignation); - if (axisName == AxisName::Latitude) { - abbreviation = AxisAbbreviation::lat; - } else if (axisName == AxisName::Longitude) { - abbreviation = AxisAbbreviation::lon; - } else if (axisName == AxisName::Ellipsoidal_height) { - abbreviation = AxisAbbreviation::h; - } - } - const std::string &dirString = children[1]->GP()->value(); - const AxisDirection *direction = AxisDirection::valueOf(dirString); - - // WKT2, geocentric CS: axis names are omitted - if (axisName.empty()) { - if (direction == &AxisDirection::GEOCENTRIC_X && - abbreviation == AxisAbbreviation::X) { - axisName = AxisName::Geocentric_X; - } else if (direction == &AxisDirection::GEOCENTRIC_Y && - abbreviation == AxisAbbreviation::Y) { - axisName = AxisName::Geocentric_Y; - } else if (direction == &AxisDirection::GEOCENTRIC_Z && - abbreviation == AxisAbbreviation::Z) { - axisName = AxisName::Geocentric_Z; - } - } - - // WKT1 - if (!direction && isGeocentric && axisName == AxisName::Geocentric_X) { - abbreviation = AxisAbbreviation::X; - direction = &AxisDirection::GEOCENTRIC_X; - } else if (!direction && isGeocentric && - axisName == AxisName::Geocentric_Y) { - abbreviation = AxisAbbreviation::Y; - direction = &AxisDirection::GEOCENTRIC_Y; - } else if (isGeocentric && axisName == AxisName::Geocentric_Z && - (dirString == AxisDirectionWKT1::NORTH.toString() || - dirString == AxisDirectionWKT1::OTHER.toString())) { - abbreviation = AxisAbbreviation::Z; - direction = &AxisDirection::GEOCENTRIC_Z; - } else if (dirString == AxisDirectionWKT1::OTHER.toString()) { - direction = &AxisDirection::UNSPECIFIED; - } else if (!direction && AxisDirectionWKT1::valueOf(dirString) != nullptr) { - direction = AxisDirection::valueOf(tolower(dirString)); - } - - if (!direction) { - throw ParsingException( - concat("unhandled axis direction: ", children[1]->GP()->value())); - } - UnitOfMeasure unit(buildUnitInSubNode(node)); - if (unit == UnitOfMeasure::NONE) { - // If no unit in the AXIS node, use the one potentially coming from - // the CS. - unit = unitIn; - if (unit == UnitOfMeasure::NONE && - unitType != UnitOfMeasure::Type::NONE && - unitType != UnitOfMeasure::Type::TIME) { - ThrowParsingExceptionMissingUNIT(); - } - } - - auto &meridianNode = nodeP->lookForChild(WKTConstants::MERIDIAN); - - return CoordinateSystemAxis::create( - buildProperties(node).set(IdentifiedObject::NAME_KEY, axisName), - abbreviation, *direction, unit, - !isNull(meridianNode) ? buildMeridian(meridianNode).as_nullable() - : nullptr); -} - -// --------------------------------------------------------------------------- - -static const PropertyMap emptyPropertyMap{}; - -PROJ_NO_RETURN static void ThrowParsingException(const std::string &msg) { - throw ParsingException(msg); -} - -static ParsingException -buildParsingExceptionInvalidAxisCount(const std::string &csType) { - return ParsingException( - concat("buildCS: invalid CS axis count for ", csType)); -} - -CoordinateSystemNNPtr -WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */ - const WKTNodeNNPtr &parentNode, - const UnitOfMeasure &defaultAngularUnit) { - bool isGeocentric = false; - std::string csType; - const int numberOfAxis = - parentNode->countChildrenOfName(WKTConstants::AXIS); - int axisCount = numberOfAxis; - if (!isNull(node)) { - const auto *nodeP = node->GP(); - const auto &children = nodeP->children(); - if (children.size() < 2) { - ThrowNotEnoughChildren(nodeP->value()); - } - csType = children[0]->GP()->value(); - try { - axisCount = std::stoi(children[1]->GP()->value()); - } catch (const std::exception &) { - ThrowParsingException(concat("buildCS: invalid CS axis count: ", - children[1]->GP()->value())); - } - } else { - const char *csTypeCStr = ""; - const auto &parentNodeName = parentNode->GP()->value(); - if (ci_equal(parentNodeName, WKTConstants::GEOCCS)) { - csTypeCStr = "Cartesian"; - isGeocentric = true; - if (axisCount == 0) { - auto unit = - buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); - if (unit == UnitOfMeasure::NONE) { - ThrowParsingExceptionMissingUNIT(); - } - return CartesianCS::createGeocentric(unit); - } - } else if (ci_equal(parentNodeName, WKTConstants::GEOGCS)) { - csTypeCStr = "Ellipsoidal"; - if (axisCount == 0) { - // Missing axis with GEOGCS ? Presumably Long/Lat order - // implied - auto unit = buildUnitInSubNode(parentNode, - UnitOfMeasure::Type::ANGULAR); - if (unit == UnitOfMeasure::NONE) { - ThrowParsingExceptionMissingUNIT(); - } - // WKT1 --> long/lat - return EllipsoidalCS::createLongitudeLatitude(unit); - } - } else if (ci_equal(parentNodeName, WKTConstants::BASEGEODCRS) || - ci_equal(parentNodeName, WKTConstants::BASEGEOGCRS)) { - csTypeCStr = "Ellipsoidal"; - if (axisCount == 0) { - auto unit = buildUnitInSubNode(parentNode, - UnitOfMeasure::Type::ANGULAR); - if (unit == UnitOfMeasure::NONE) { - unit = defaultAngularUnit; - } - // WKT2 --> presumably lat/long - return EllipsoidalCS::createLatitudeLongitude(unit); - } - } else if (ci_equal(parentNodeName, WKTConstants::PROJCS) || - ci_equal(parentNodeName, WKTConstants::BASEPROJCRS) || - ci_equal(parentNodeName, WKTConstants::BASEENGCRS)) { - csTypeCStr = "Cartesian"; - if (axisCount == 0) { - auto unit = - buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); - if (unit == UnitOfMeasure::NONE) { - if (ci_equal(parentNodeName, WKTConstants::PROJCS)) { - ThrowParsingExceptionMissingUNIT(); - } else { - unit = UnitOfMeasure::METRE; - } - } - return CartesianCS::createEastingNorthing(unit); - } - } else if (ci_equal(parentNodeName, WKTConstants::VERT_CS) || - ci_equal(parentNodeName, WKTConstants::BASEVERTCRS)) { - csTypeCStr = "vertical"; - if (axisCount == 0) { - auto unit = - buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); - if (unit == UnitOfMeasure::NONE) { - if (ci_equal(parentNodeName, WKTConstants::VERT_CS)) { - ThrowParsingExceptionMissingUNIT(); - } else { - unit = UnitOfMeasure::METRE; - } - } - return VerticalCS::createGravityRelatedHeight(unit); - } - } else if (ci_equal(parentNodeName, WKTConstants::LOCAL_CS)) { - if (axisCount == 0) { - auto unit = - buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); - if (unit == UnitOfMeasure::NONE) { - unit = UnitOfMeasure::METRE; - } - return CartesianCS::createEastingNorthing(unit); - } else if (axisCount == 1) { - csTypeCStr = "vertical"; - } else if (axisCount == 2) { - csTypeCStr = "Cartesian"; - } else { - throw ParsingException( - "buildCS: unexpected AXIS count for LOCAL_CS"); - } - } else if (ci_equal(parentNodeName, WKTConstants::BASEPARAMCRS)) { - csTypeCStr = "parametric"; - if (axisCount == 0) { - auto unit = - buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); - if (unit == UnitOfMeasure::NONE) { - unit = UnitOfMeasure("unknown", 1, - UnitOfMeasure::Type::PARAMETRIC); - } - return ParametricCS::create( - emptyPropertyMap, - CoordinateSystemAxis::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, - "unknown parametric"), - std::string(), AxisDirection::UNSPECIFIED, unit)); - } - } else if (ci_equal(parentNodeName, WKTConstants::BASETIMECRS)) { - csTypeCStr = "temporal"; - if (axisCount == 0) { - auto unit = - buildUnitInSubNode(parentNode, UnitOfMeasure::Type::TIME); - if (unit == UnitOfMeasure::NONE) { - unit = - UnitOfMeasure("unknown", 1, UnitOfMeasure::Type::TIME); - } - return DateTimeTemporalCS::create( - emptyPropertyMap, - CoordinateSystemAxis::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, - "unknown temporal"), - std::string(), AxisDirection::FUTURE, unit)); - } - } else { - // Shouldn't happen normally - throw ParsingException( - concat("buildCS: unexpected parent node: ", parentNodeName)); - } - csType = csTypeCStr; - } - - if (axisCount != 1 && axisCount != 2 && axisCount != 3) { - throw buildParsingExceptionInvalidAxisCount(csType); - } - if (numberOfAxis != axisCount) { - throw ParsingException("buildCS: declared number of axis by CS node " - "and number of AXIS are inconsistent"); - } - - const auto unitType = - ci_equal(csType, "ellipsoidal") - ? UnitOfMeasure::Type::ANGULAR - : ci_equal(csType, "ordinal") - ? UnitOfMeasure::Type::NONE - : ci_equal(csType, "parametric") - ? UnitOfMeasure::Type::PARAMETRIC - : ci_equal(csType, "Cartesian") || - ci_equal(csType, "vertical") - ? UnitOfMeasure::Type::LINEAR - : (ci_equal(csType, "temporal") || - ci_equal(csType, "TemporalDateTime") || - ci_equal(csType, "TemporalCount") || - ci_equal(csType, "TemporalMeasure")) - ? UnitOfMeasure::Type::TIME - : UnitOfMeasure::Type::UNKNOWN; - UnitOfMeasure unit = buildUnitInSubNode(parentNode, unitType); - - std::vector axisList; - for (int i = 0; i < axisCount; i++) { - axisList.emplace_back( - buildAxis(parentNode->GP()->lookForChild(WKTConstants::AXIS, i), - unit, unitType, isGeocentric, i + 1)); - }; - - const PropertyMap &csMap = emptyPropertyMap; - if (ci_equal(csType, "ellipsoidal")) { - if (axisCount == 2) { - return EllipsoidalCS::create(csMap, axisList[0], axisList[1]); - } else if (axisCount == 3) { - return EllipsoidalCS::create(csMap, axisList[0], axisList[1], - axisList[2]); - } - } else if (ci_equal(csType, "Cartesian")) { - if (axisCount == 2) { - return CartesianCS::create(csMap, axisList[0], axisList[1]); - } else if (axisCount == 3) { - return CartesianCS::create(csMap, axisList[0], axisList[1], - axisList[2]); - } - } else if (ci_equal(csType, "vertical")) { - if (axisCount == 1) { - return VerticalCS::create(csMap, axisList[0]); - } - } else if (ci_equal(csType, "spherical")) { - if (axisCount == 3) { - return SphericalCS::create(csMap, axisList[0], axisList[1], - axisList[2]); - } - } else if (ci_equal(csType, "ordinal")) { // WKT2-2018 - return OrdinalCS::create(csMap, axisList); - } else if (ci_equal(csType, "parametric")) { - if (axisCount == 1) { - return ParametricCS::create(csMap, axisList[0]); - } - } else if (ci_equal(csType, "temporal")) { // WKT2-2015 - if (axisCount == 1) { - return DateTimeTemporalCS::create( - csMap, - axisList[0]); // FIXME: there are 3 possible subtypes of - // TemporalCS - } - } else if (ci_equal(csType, "TemporalDateTime")) { // WKT2-2018 - if (axisCount == 1) { - return DateTimeTemporalCS::create(csMap, axisList[0]); - } - } else if (ci_equal(csType, "TemporalCount")) { // WKT2-2018 - if (axisCount == 1) { - return TemporalCountCS::create(csMap, axisList[0]); - } - } else if (ci_equal(csType, "TemporalMeasure")) { // WKT2-2018 - if (axisCount == 1) { - return TemporalMeasureCS::create(csMap, axisList[0]); - } - } else { - throw ParsingException(concat("unhandled CS type: ", csType)); - } - throw buildParsingExceptionInvalidAxisCount(csType); -} - -// --------------------------------------------------------------------------- - -void WKTParser::Private::addExtensionProj4ToProp(const WKTNode::Private *nodeP, - PropertyMap &props) { - auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION); - const auto &extensionChildren = extensionNode->GP()->children(); - if (extensionChildren.size() == 2) { - if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) { - props.set("EXTENSION_PROJ4", stripQuotes(extensionChildren[1])); - } - } -} - -// --------------------------------------------------------------------------- - -GeodeticCRSNNPtr -WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &datumNode = nodeP->lookForChild( - WKTConstants::DATUM, WKTConstants::GEODETICDATUM, WKTConstants::TRF); - auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE); - if (isNull(datumNode) && isNull(ensembleNode)) { - throw ParsingException("Missing DATUM or ENSEMBLE node"); - } - - auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC); - - auto &csNode = nodeP->lookForChild(WKTConstants::CS); - const auto &nodeName = nodeP->value(); - if (isNull(csNode) && !ci_equal(nodeName, WKTConstants::GEOGCS) && - !ci_equal(nodeName, WKTConstants::GEOCCS) && - !ci_equal(nodeName, WKTConstants::BASEGEODCRS) && - !ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) { - ThrowMissing(WKTConstants::CS); - } - - auto &primeMeridianNode = - nodeP->lookForChild(WKTConstants::PRIMEM, WKTConstants::PRIMEMERIDIAN); - if (isNull(primeMeridianNode)) { - // PRIMEM is required in WKT1 - if (ci_equal(nodeName, WKTConstants::GEOGCS) || - ci_equal(nodeName, WKTConstants::GEOCCS)) { - emitRecoverableAssertion(nodeName + " should have a PRIMEM node"); - } - } - - auto angularUnit = - buildUnitInSubNode(node, ci_equal(nodeName, WKTConstants::GEOGCS) - ? UnitOfMeasure::Type::ANGULAR - : UnitOfMeasure::Type::UNKNOWN); - if (angularUnit.type() != UnitOfMeasure::Type::ANGULAR) { - angularUnit = UnitOfMeasure::NONE; - } - - auto primeMeridian = - !isNull(primeMeridianNode) - ? buildPrimeMeridian(primeMeridianNode, angularUnit) - : PrimeMeridian::GREENWICH; - if (angularUnit == UnitOfMeasure::NONE) { - angularUnit = primeMeridian->longitude().unit(); - } - - auto props = buildProperties(node); - addExtensionProj4ToProp(nodeP, props); - - // No explicit AXIS node ? (WKT1) - if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) { - props.set("IMPLICIT_CS", true); - } - - auto datum = - !isNull(datumNode) - ? buildGeodeticReferenceFrame(datumNode, primeMeridian, dynamicNode) - .as_nullable() - : nullptr; - auto datumEnsemble = - !isNull(ensembleNode) - ? buildDatumEnsemble(ensembleNode, primeMeridian, true) - .as_nullable() - : nullptr; - auto cs = buildCS(csNode, node, angularUnit); - auto ellipsoidalCS = nn_dynamic_pointer_cast(cs); - if (ellipsoidalCS) { - assert(!ci_equal(nodeName, WKTConstants::GEOCCS)); - try { - return GeographicCRS::create(props, datum, datumEnsemble, - NN_NO_CHECK(ellipsoidalCS)); - } catch (const util::Exception &e) { - throw ParsingException(std::string("buildGeodeticCRS: ") + - e.what()); - } - } else if (ci_equal(nodeName, WKTConstants::GEOGCRS) || - ci_equal(nodeName, WKTConstants::GEOGRAPHICCRS) || - ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) { - // This is a WKT2-2018 GeographicCRS. An ellipsoidal CS is expected - throw ParsingException(concat("ellipsoidal CS expected, but found ", - cs->getWKT2Type(true))); - } - - auto cartesianCS = nn_dynamic_pointer_cast(cs); - if (cartesianCS) { - if (cartesianCS->axisList().size() != 3) { - throw ParsingException( - "Cartesian CS for a GeodeticCRS should have 3 axis"); - } - try { - return GeodeticCRS::create(props, datum, datumEnsemble, - NN_NO_CHECK(cartesianCS)); - } catch (const util::Exception &e) { - throw ParsingException(std::string("buildGeodeticCRS: ") + - e.what()); - } - } - - auto sphericalCS = nn_dynamic_pointer_cast(cs); - if (sphericalCS) { - try { - return GeodeticCRS::create(props, datum, datumEnsemble, - NN_NO_CHECK(sphericalCS)); - } catch (const util::Exception &e) { - throw ParsingException(std::string("buildGeodeticCRS: ") + - e.what()); - } - } - - throw ParsingException( - concat("unhandled CS type: ", cs->getWKT2Type(true))); -} - -// --------------------------------------------------------------------------- - -CRSNNPtr WKTParser::Private::buildDerivedGeodeticCRS(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &baseGeodCRSNode = nodeP->lookForChild(WKTConstants::BASEGEODCRS, - WKTConstants::BASEGEOGCRS); - // given the constraints enforced on calling code path - assert(!isNull(baseGeodCRSNode)); - - auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode); - - auto &derivingConversionNode = - nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); - if (isNull(derivingConversionNode)) { - ThrowMissing(WKTConstants::DERIVINGCONVERSION); - } - auto derivingConversion = buildConversion( - derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); - - auto &csNode = nodeP->lookForChild(WKTConstants::CS); - if (isNull(csNode)) { - ThrowMissing(WKTConstants::CS); - } - auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); - - auto ellipsoidalCS = nn_dynamic_pointer_cast(cs); - if (ellipsoidalCS) { - return DerivedGeographicCRS::create(buildProperties(node), baseGeodCRS, - derivingConversion, - NN_NO_CHECK(ellipsoidalCS)); - } else if (ci_equal(nodeP->value(), WKTConstants::GEOGCRS)) { - // This is a WKT2-2018 GeographicCRS. An ellipsoidal CS is expected - throw ParsingException(concat("ellipsoidal CS expected, but found ", - cs->getWKT2Type(true))); - } - - auto cartesianCS = nn_dynamic_pointer_cast(cs); - if (cartesianCS) { - if (cartesianCS->axisList().size() != 3) { - throw ParsingException( - "Cartesian CS for a GeodeticCRS should have 3 axis"); - } - return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS, - derivingConversion, - NN_NO_CHECK(cartesianCS)); - } - - auto sphericalCS = nn_dynamic_pointer_cast(cs); - if (sphericalCS) { - return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS, - derivingConversion, - NN_NO_CHECK(sphericalCS)); - } - - throw ParsingException( - concat("unhandled CS type: ", cs->getWKT2Type(true))); -} - -// --------------------------------------------------------------------------- - -UnitOfMeasure WKTParser::Private::guessUnitForParameter( - const std::string ¶mName, const UnitOfMeasure &defaultLinearUnit, - 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) { - unit = UnitOfMeasure::SCALE_UNITY; - } else if (ci_find(paramName, "latitude") != std::string::npos || - ci_find(paramName, "longitude") != std::string::npos || - ci_find(paramName, "meridian") != std::string::npos || - ci_find(paramName, "parallel") != std::string::npos || - ci_find(paramName, "azimuth") != std::string::npos || - ci_find(paramName, "angle") != std::string::npos || - ci_find(paramName, "heading") != std::string::npos) { - unit = defaultAngularUnit; - } else if (ci_find(paramName, "easting") != std::string::npos || - ci_find(paramName, "northing") != std::string::npos || - ci_find(paramName, "height") != std::string::npos) { - unit = defaultLinearUnit; - } - return unit; -} - -// --------------------------------------------------------------------------- - -void WKTParser::Private::consumeParameters( - const WKTNodeNNPtr &node, bool isAbridged, - std::vector ¶meters, - std::vector &values, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit) { - for (const auto &childNode : node->GP()->children()) { - const auto &childNodeChildren = childNode->GP()->children(); - if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { - if (childNodeChildren.size() < 2) { - ThrowNotEnoughChildren(childNode->GP()->value()); - } - parameters.push_back( - OperationParameter::create(buildProperties(childNode))); - const auto ¶mValue = childNodeChildren[1]->GP()->value(); - if (!paramValue.empty() && paramValue[0] == '"') { - values.push_back( - ParameterValue::create(stripQuotes(childNodeChildren[1]))); - } else { - try { - double val = asDouble(childNodeChildren[1]); - auto unit = buildUnitInSubNode(childNode); - if (unit == UnitOfMeasure::NONE) { - const auto ¶mName = - childNodeChildren[0]->GP()->value(); - unit = guessUnitForParameter( - paramName, defaultLinearUnit, defaultAngularUnit); - } - - if (isAbridged) { - const auto ¶mName = parameters.back()->nameStr(); - int paramEPSGCode = 0; - const auto ¶mIds = parameters.back()->identifiers(); - if (paramIds.size() == 1 && - ci_equal(*(paramIds[0]->codeSpace()), - Identifier::EPSG)) { - paramEPSGCode = ::atoi(paramIds[0]->code().c_str()); - } - const common::UnitOfMeasure *pUnit = nullptr; - if (OperationParameterValue::convertFromAbridged( - paramName, val, pUnit, paramEPSGCode)) { - unit = *pUnit; - parameters.back() = OperationParameter::create( - buildProperties(childNode) - .set(Identifier::CODESPACE_KEY, - Identifier::EPSG) - .set(Identifier::CODE_KEY, paramEPSGCode)); - } - } - - values.push_back( - ParameterValue::create(Measure(val, unit))); - } catch (const std::exception &) { - throw ParsingException(concat( - "unhandled parameter value type : ", paramValue)); - } - } - } else if (ci_equal(childNode->GP()->value(), - WKTConstants::PARAMETERFILE)) { - if (childNodeChildren.size() < 2) { - ThrowNotEnoughChildren(childNode->GP()->value()); - } - parameters.push_back( - OperationParameter::create(buildProperties(childNode))); - values.push_back(ParameterValue::createFilename( - stripQuotes(childNodeChildren[1]))); - } - } -} - -// --------------------------------------------------------------------------- - -ConversionNNPtr -WKTParser::Private::buildConversion(const WKTNodeNNPtr &node, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit) { - auto &methodNode = node->GP()->lookForChild(WKTConstants::METHOD, - WKTConstants::PROJECTION); - if (isNull(methodNode)) { - ThrowMissing(WKTConstants::METHOD); - } - if (methodNode->GP()->childrenSize() == 0) { - ThrowNotEnoughChildren(WKTConstants::METHOD); - } - - std::vector parameters; - std::vector values; - consumeParameters(node, false, parameters, values, defaultLinearUnit, - defaultAngularUnit); - - return Conversion::create(buildProperties(node), - buildProperties(methodNode), parameters, values); -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr -WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD); - if (isNull(methodNode)) { - ThrowMissing(WKTConstants::METHOD); - } - if (methodNode->GP()->childrenSize() == 0) { - ThrowNotEnoughChildren(WKTConstants::METHOD); - } - - auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS); - if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) { - ThrowMissing(WKTConstants::SOURCECRS); - } - auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]); - if (!sourceCRS) { - throw ParsingException("Invalid content in SOURCECRS node"); - } - - auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS); - if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) { - ThrowMissing(WKTConstants::TARGETCRS); - } - auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]); - if (!targetCRS) { - throw ParsingException("Invalid content in TARGETCRS node"); - } - - auto &interpolationCRSNode = - nodeP->lookForChild(WKTConstants::INTERPOLATIONCRS); - CRSPtr interpolationCRS; - if (/*!isNull(interpolationCRSNode) && */ interpolationCRSNode->GP() - ->childrenSize() == 1) { - interpolationCRS = buildCRS(interpolationCRSNode->GP()->children()[0]); - } - - std::vector parameters; - std::vector values; - auto defaultLinearUnit = UnitOfMeasure::NONE; - auto defaultAngularUnit = UnitOfMeasure::NONE; - consumeParameters(node, false, parameters, values, defaultLinearUnit, - defaultAngularUnit); - - std::vector accuracies; - auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY); - if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) { - accuracies.push_back(PositionalAccuracy::create( - stripQuotes(accuracyNode->GP()->children()[0]))); - } - - return util::nn_static_pointer_cast( - Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS), - NN_NO_CHECK(targetCRS), interpolationCRS, - buildProperties(methodNode), parameters, values, - accuracies)); -} - -// --------------------------------------------------------------------------- - -ConcatenatedOperationNNPtr -WKTParser::Private::buildConcatenatedOperation(const WKTNodeNNPtr &node) { - std::vector operations; - for (const auto &childNode : node->GP()->children()) { - if (ci_equal(childNode->GP()->value(), WKTConstants::STEP)) { - if (childNode->GP()->childrenSize() != 1) { - throw ParsingException("Invalid content in STEP node"); - } - auto op = nn_dynamic_pointer_cast( - build(childNode->GP()->children()[0])); - if (!op) { - throw ParsingException("Invalid content in STEP node"); - } - operations.emplace_back(NN_NO_CHECK(op)); - } - } - try { - return ConcatenatedOperation::create( - buildProperties(node), operations, - std::vector()); - } catch (const InvalidOperation &e) { - throw ParsingException( - std::string("Cannot build concatenated operation: ") + e.what()); - } -} - -// --------------------------------------------------------------------------- - -bool WKTParser::Private::hasWebMercPROJ4String( - const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode) { - if (projectionNode->GP()->childrenSize() == 0) { - ThrowNotEnoughChildren(WKTConstants::PROJECTION); - } - const std::string wkt1ProjectionName = - stripQuotes(projectionNode->GP()->children()[0]); - - auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION); - - if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(), - "Mercator_1SP") && - projCRSNode->countChildrenOfName("center_latitude") == 0) { - - // Hack to detect the hacky way of encodign webmerc in GDAL WKT1 - // with a EXTENSION["PROJ4", "+proj=merc +a=6378137 +b=6378137 - // +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m - // +nadgrids=@null +wktext +no_defs"] node - if (extensionNode && extensionNode->GP()->childrenSize() == 2 && - ci_equal(stripQuotes(extensionNode->GP()->children()[0]), - "PROJ4")) { - std::string projString = - stripQuotes(extensionNode->GP()->children()[1]); - if (projString.find("+proj=merc") != std::string::npos && - projString.find("+a=6378137") != std::string::npos && - projString.find("+b=6378137") != std::string::npos && - projString.find("+lon_0=0") != std::string::npos && - projString.find("+x_0=0") != std::string::npos && - projString.find("+y_0=0") != std::string::npos && - projString.find("+nadgrids=@null") != std::string::npos && - (projString.find("+lat_ts=") == std::string::npos || - projString.find("+lat_ts=0") != std::string::npos) && - (projString.find("+k=") == std::string::npos || - projString.find("+k=1") != std::string::npos) && - (projString.find("+units=") == std::string::npos || - projString.find("+units=m") != std::string::npos)) { - return true; - } - } - } - return false; -} - -// --------------------------------------------------------------------------- - -ConversionNNPtr WKTParser::Private::buildProjectionFromESRI( - const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit) { - const std::string esriProjectionName = - stripQuotes(projectionNode->GP()->children()[0]); - - // Lambert_Conformal_Conic or Krovak may map to different WKT2 methods - // depending - // on the parameters / their values - const auto esriMappings = getMappingsFromESRI(esriProjectionName); - if (esriMappings.empty()) { - return buildProjectionStandard(projCRSNode, projectionNode, - defaultLinearUnit, defaultAngularUnit); - } - - struct ci_less_struct { - bool operator()(const std::string &lhs, const std::string &rhs) const - noexcept { - return ci_less(lhs, rhs); - } - }; - - // Build a map of present parameters - std::map mapParamNameToValue; - for (const auto &childNode : projCRSNode->GP()->children()) { - if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { - const auto &childNodeChildren = childNode->GP()->children(); - if (childNodeChildren.size() < 2) { - ThrowNotEnoughChildren(WKTConstants::PARAMETER); - } - const std::string parameterName(stripQuotes(childNodeChildren[0])); - const auto ¶mValue = childNodeChildren[1]->GP()->value(); - mapParamNameToValue[parameterName] = paramValue; - } - } - - // Compare parameters present with the ones expected in the mapping - const ESRIMethodMapping *esriMapping = esriMappings[0]; - int bestMatchCount = -1; - for (const auto &mapping : esriMappings) { - int matchCount = 0; - for (const auto *param = mapping->params; param->esri_name; ++param) { - auto iter = mapParamNameToValue.find(param->esri_name); - if (iter != mapParamNameToValue.end()) { - if (param->wkt2_name == nullptr) { - try { - if (param->fixed_value == io::asDouble(iter->second)) { - matchCount++; - } - } catch (const std::exception &) { - } - } else { - matchCount++; - } - } - } - if (matchCount > bestMatchCount) { - esriMapping = mapping; - bestMatchCount = matchCount; - } - } - - std::map mapWKT2NameToESRIName; - for (const auto *param = esriMapping->params; param->esri_name; ++param) { - if (param->wkt2_name) { - mapWKT2NameToESRIName[param->wkt2_name] = param->esri_name; - } - } - - const auto *wkt2_mapping = getMapping(esriMapping->wkt2_name); - assert(wkt2_mapping); - if (ci_equal(esriProjectionName, "Stereographic")) { - try { - if (std::fabs(io::asDouble( - mapParamNameToValue["Latitude_Of_Origin"])) == 90.0) { - wkt2_mapping = - getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A); - } - } catch (const std::exception &) { - } - } - - PropertyMap propertiesMethod; - propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt2_mapping->wkt2_name); - if (wkt2_mapping->epsg_code != 0) { - propertiesMethod.set(Identifier::CODE_KEY, wkt2_mapping->epsg_code); - propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG); - } - - std::vector parameters; - std::vector values; - - if (wkt2_mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && - ci_equal(esriProjectionName, "Plate_Carree")) { - // Add a fixed Latitude of 1st parallel = 0 so as to have all - // parameters expected by Equidistant Cylindrical. - mapWKT2NameToESRIName[EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL] = - "Standard_Parallel_1"; - mapParamNameToValue["Standard_Parallel_1"] = "0"; - } else if ((wkt2_mapping->epsg_code == - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A || - wkt2_mapping->epsg_code == - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) && - !ci_equal(esriProjectionName, - "Rectified_Skew_Orthomorphic_Natural_Origin") && - !ci_equal(esriProjectionName, - "Rectified_Skew_Orthomorphic_Center")) { - // ESRI WKT lacks the angle to skew grid - // Take it from the azimuth value - mapWKT2NameToESRIName - [EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID] = "Azimuth"; - } - - for (int i = 0; wkt2_mapping->params[i] != nullptr; i++) { - const auto *paramMapping = wkt2_mapping->params[i]; - - auto iter = mapWKT2NameToESRIName.find(paramMapping->wkt2_name); - if (iter == mapWKT2NameToESRIName.end()) { - continue; - } - const auto &esriParamName = iter->second; - auto iter2 = mapParamNameToValue.find(esriParamName); - auto mapParamNameToValueEnd = mapParamNameToValue.end(); - if (iter2 == mapParamNameToValueEnd) { - // In case we don't find a direct match, try the aliases - for (iter2 = mapParamNameToValue.begin(); - iter2 != mapParamNameToValueEnd; ++iter2) { - if (areEquivalentParameters(iter2->first, esriParamName)) { - break; - } - } - if (iter2 == mapParamNameToValueEnd) { - continue; - } - } - - PropertyMap propertiesParameter; - propertiesParameter.set(IdentifiedObject::NAME_KEY, - paramMapping->wkt2_name); - if (paramMapping->epsg_code != 0) { - propertiesParameter.set(Identifier::CODE_KEY, - paramMapping->epsg_code); - propertiesParameter.set(Identifier::CODESPACE_KEY, - Identifier::EPSG); - } - parameters.push_back(OperationParameter::create(propertiesParameter)); - - try { - double val = io::asDouble(iter2->second); - auto unit = guessUnitForParameter( - paramMapping->wkt2_name, defaultLinearUnit, defaultAngularUnit); - values.push_back(ParameterValue::create(Measure(val, unit))); - } catch (const std::exception &) { - throw ParsingException( - concat("unhandled parameter value type : ", iter2->second)); - } - } - - return Conversion::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), - propertiesMethod, parameters, values) - ->identify(); -} - -// --------------------------------------------------------------------------- - -ConversionNNPtr -WKTParser::Private::buildProjection(const WKTNodeNNPtr &projCRSNode, - const WKTNodeNNPtr &projectionNode, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit) { - if (projectionNode->GP()->childrenSize() == 0) { - ThrowNotEnoughChildren(WKTConstants::PROJECTION); - } - if (esriStyle_) { - return buildProjectionFromESRI(projCRSNode, projectionNode, - defaultLinearUnit, defaultAngularUnit); - } - return buildProjectionStandard(projCRSNode, projectionNode, - defaultLinearUnit, defaultAngularUnit); -} - -// --------------------------------------------------------------------------- - -std::string -WKTParser::Private::projectionGetParameter(const WKTNodeNNPtr &projCRSNode, - const char *paramName) { - for (const auto &childNode : projCRSNode->GP()->children()) { - if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { - const auto &childNodeChildren = childNode->GP()->children(); - if (childNodeChildren.size() == 2 && - metadata::Identifier::isEquivalentName( - stripQuotes(childNodeChildren[0]).c_str(), paramName)) { - return childNodeChildren[1]->GP()->value(); - } - } - } - return std::string(); -} - -// --------------------------------------------------------------------------- - -ConversionNNPtr WKTParser::Private::buildProjectionStandard( - const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit) { - std::string wkt1ProjectionName = - stripQuotes(projectionNode->GP()->children()[0]); - - std::vector parameters; - std::vector values; - bool tryToIdentifyWKT1Method = true; - - auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION); - const auto &extensionChildren = extensionNode->GP()->children(); - - bool gdal_3026_hack = false; - if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(), - "Mercator_1SP") && - projectionGetParameter(projCRSNode, "center_latitude").empty()) { - - // Hack for https://trac.osgeo.org/gdal/ticket/3026 - std::string lat0( - projectionGetParameter(projCRSNode, "latitude_of_origin")); - if (!lat0.empty() && lat0 != "0" && lat0 != "0.0") { - wkt1ProjectionName = "Mercator_2SP"; - gdal_3026_hack = true; - } else { - // The latitude of origin, which should always be zero, is - // missing - // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP - // definition, - // so add it manually. - PropertyMap propertiesParameter; - propertiesParameter.set(IdentifiedObject::NAME_KEY, - "Latitude of natural origin"); - propertiesParameter.set(Identifier::CODE_KEY, 8801); - propertiesParameter.set(Identifier::CODESPACE_KEY, - Identifier::EPSG); - parameters.push_back( - OperationParameter::create(propertiesParameter)); - values.push_back( - ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE))); - } - - } else if (metadata::Identifier::isEquivalentName( - wkt1ProjectionName.c_str(), "Polar_Stereographic")) { - std::map mapParameters; - for (const auto &childNode : projCRSNode->GP()->children()) { - const auto &childNodeChildren = childNode->GP()->children(); - if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) && - childNodeChildren.size() == 2) { - const std::string wkt1ParameterName( - stripQuotes(childNodeChildren[0])); - try { - double val = asDouble(childNodeChildren[1]); - auto unit = guessUnitForParameter(wkt1ParameterName, - defaultLinearUnit, - defaultAngularUnit); - mapParameters.insert(std::pair( - tolower(wkt1ParameterName), Measure(val, unit))); - } catch (const std::exception &) { - } - } - } - - Measure latitudeOfOrigin = mapParameters["latitude_of_origin"]; - Measure centralMeridian = mapParameters["central_meridian"]; - Measure scaleFactorFromMap = mapParameters["scale_factor"]; - Measure scaleFactor((scaleFactorFromMap.unit() == UnitOfMeasure::NONE) - ? Measure(1.0, UnitOfMeasure::SCALE_UNITY) - : scaleFactorFromMap); - Measure falseEasting = mapParameters["false_easting"]; - Measure falseNorthing = mapParameters["false_northing"]; - if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE && - scaleFactor.getSIValue() == 1.0) { - return Conversion::createPolarStereographicVariantB( - PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), - Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()), - Angle(centralMeridian.value(), centralMeridian.unit()), - Length(falseEasting.value(), falseEasting.unit()), - Length(falseNorthing.value(), falseNorthing.unit())); - } - - if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE && - std::fabs(std::fabs(latitudeOfOrigin.convertToUnit( - UnitOfMeasure::DEGREE)) - - 90.0) < 1e-10) { - return Conversion::createPolarStereographicVariantA( - PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), - Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()), - Angle(centralMeridian.value(), centralMeridian.unit()), - Scale(scaleFactor.value(), scaleFactor.unit()), - Length(falseEasting.value(), falseEasting.unit()), - Length(falseNorthing.value(), falseNorthing.unit())); - } - - tryToIdentifyWKT1Method = false; - // Import GDAL PROJ4 extension nodes - } else if (extensionChildren.size() == 2 && - ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) { - std::string projString = stripQuotes(extensionChildren[1]); - if (starts_with(projString, "+proj=")) { - try { - auto projObj = - PROJStringParser().createFromPROJString(projString); - auto projObjCrs = - nn_dynamic_pointer_cast(projObj); - if (projObjCrs) { - return projObjCrs->derivingConversion(); - } - } catch (const io::ParsingException &) { - } - } - } - - std::string projectionName(wkt1ProjectionName); - const MethodMapping *mapping = - tryToIdentifyWKT1Method ? getMappingFromWKT1(projectionName) : nullptr; - - // For Krovak, we need to look at axis to decide between the Krovak and - // Krovak East-North Oriented methods - if (ci_equal(projectionName, "Krovak") && - projCRSNode->countChildrenOfName(WKTConstants::AXIS) == 2 && - &buildAxis( - projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 0), - defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, - 1)->direction() == &AxisDirection::SOUTH && - &buildAxis( - projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 1), - defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, - 2)->direction() == &AxisDirection::WEST) { - mapping = getMapping(EPSG_CODE_METHOD_KROVAK); - } - - PropertyMap propertiesMethod; - if (mapping) { - projectionName = mapping->wkt2_name; - if (mapping->epsg_code != 0) { - propertiesMethod.set(Identifier::CODE_KEY, mapping->epsg_code); - propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG); - } - } - propertiesMethod.set(IdentifiedObject::NAME_KEY, projectionName); - - for (const auto &childNode : projCRSNode->GP()->children()) { - if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { - const auto &childNodeChildren = childNode->GP()->children(); - if (childNodeChildren.size() < 2) { - ThrowNotEnoughChildren(WKTConstants::PARAMETER); - } - const auto ¶mValue = childNodeChildren[1]->GP()->value(); - - PropertyMap propertiesParameter; - const std::string wkt1ParameterName( - stripQuotes(childNodeChildren[0])); - std::string parameterName(wkt1ParameterName); - if (gdal_3026_hack) { - if (ci_equal(parameterName, "latitude_of_origin")) { - parameterName = "standard_parallel_1"; - } else if (ci_equal(parameterName, "scale_factor") && - paramValue == "1") { - continue; - } - } - const auto *paramMapping = - mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr; - if (mapping && - mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && - ci_equal(parameterName, "latitude_of_origin")) { - parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN; - propertiesParameter.set( - Identifier::CODE_KEY, - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); - propertiesParameter.set(Identifier::CODESPACE_KEY, - Identifier::EPSG); - } else if (paramMapping) { - parameterName = paramMapping->wkt2_name; - if (paramMapping->epsg_code != 0) { - propertiesParameter.set(Identifier::CODE_KEY, - paramMapping->epsg_code); - propertiesParameter.set(Identifier::CODESPACE_KEY, - Identifier::EPSG); - } - } - propertiesParameter.set(IdentifiedObject::NAME_KEY, parameterName); - parameters.push_back( - OperationParameter::create(propertiesParameter)); - try { - double val = io::asDouble(paramValue); - auto unit = guessUnitForParameter( - wkt1ParameterName, defaultLinearUnit, defaultAngularUnit); - values.push_back(ParameterValue::create(Measure(val, unit))); - } catch (const std::exception &) { - throw ParsingException( - concat("unhandled parameter value type : ", paramValue)); - } - } - } - - return Conversion::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), - propertiesMethod, parameters, values) - ->identify(); -} - -// --------------------------------------------------------------------------- - -static ProjectedCRSNNPtr createPseudoMercator(const PropertyMap &props) { - auto conversion = Conversion::createPopularVisualisationPseudoMercator( - PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0), - Angle(0), Length(0), Length(0)); - return ProjectedCRS::create( - props, GeographicCRS::EPSG_4326, conversion, - CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); -} - -// --------------------------------------------------------------------------- - -ProjectedCRSNNPtr -WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) { - - const auto *nodeP = node->GP(); - auto &conversionNode = nodeP->lookForChild(WKTConstants::CONVERSION); - auto &projectionNode = nodeP->lookForChild(WKTConstants::PROJECTION); - if (isNull(conversionNode) && isNull(projectionNode)) { - ThrowMissing(WKTConstants::CONVERSION); - } - - auto &baseGeodCRSNode = - nodeP->lookForChild(WKTConstants::BASEGEODCRS, - WKTConstants::BASEGEOGCRS, WKTConstants::GEOGCS); - if (isNull(baseGeodCRSNode)) { - throw ParsingException( - "Missing BASEGEODCRS / BASEGEOGCRS / GEOGCS node"); - } - auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode); - - auto props = buildProperties(node); - - const std::string projCRSName = stripQuotes(nodeP->children()[0]); - if (esriStyle_ && dbContext_) { - // It is likely that the ESRI definition of EPSG:32661 (UPS North) & - // EPSG:32761 (UPS South) uses the easting-northing order, instead - // of the EPSG northing-easting order - // so don't substitue names to avoid confusion. - if (projCRSName == "UPS_North") { - props.set(IdentifiedObject::NAME_KEY, "WGS 84 / UPS North (E,N)"); - } else if (projCRSName == "UPS_South") { - props.set(IdentifiedObject::NAME_KEY, "WGS 84 / UPS South (E,N)"); - } else { - std::string outTableName; - std::string authNameFromAlias; - std::string codeFromAlias; - auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), - std::string()); - auto officialName = authFactory->getOfficialNameFromAlias( - projCRSName, "projected_crs", "ESRI", false, outTableName, - authNameFromAlias, codeFromAlias); - if (!officialName.empty()) { - props.set(IdentifiedObject::NAME_KEY, officialName); - } - } - } - - if (isNull(conversionNode) && hasWebMercPROJ4String(node, projectionNode)) { - toWGS84Parameters_.clear(); - return createPseudoMercator(props); - } - - // WGS_84_Pseudo_Mercator: Particular case for corrupted ESRI WKT generated - // by older GDAL versions - // https://trac.osgeo.org/gdal/changeset/30732 - // WGS_1984_Web_Mercator: deprecated ESRI:102113 - if (metadata::Identifier::isEquivalentName(projCRSName.c_str(), - "WGS_84_Pseudo_Mercator") || - metadata::Identifier::isEquivalentName(projCRSName.c_str(), - "WGS_1984_Web_Mercator")) { - toWGS84Parameters_.clear(); - return createPseudoMercator(props); - } - - auto linearUnit = buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR); - auto angularUnit = baseGeodCRS->coordinateSystem()->axisList()[0]->unit(); - - auto conversion = - !isNull(conversionNode) - ? buildConversion(conversionNode, linearUnit, angularUnit) - : buildProjection(node, projectionNode, linearUnit, angularUnit); - - auto &csNode = nodeP->lookForChild(WKTConstants::CS); - const auto &nodeValue = nodeP->value(); - if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) && - !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) { - ThrowMissing(WKTConstants::CS); - } - auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); - auto cartesianCS = nn_dynamic_pointer_cast(cs); - - // No explicit AXIS node ? (WKT1) - if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) { - props.set("IMPLICIT_CS", true); - } - - if (isNull(csNode) && node->countChildrenOfName(WKTConstants::AXIS) == 0) { - - const auto methodCode = conversion->method()->getEPSGCode(); - // Krovak south oriented ? - if (methodCode == EPSG_CODE_METHOD_KROVAK) { - cartesianCS = - CartesianCS::create( - PropertyMap(), - CoordinateSystemAxis::create( - util::PropertyMap().set(IdentifiedObject::NAME_KEY, - AxisName::Southing), - emptyString, AxisDirection::SOUTH, linearUnit), - CoordinateSystemAxis::create( - util::PropertyMap().set(IdentifiedObject::NAME_KEY, - AxisName::Westing), - emptyString, AxisDirection::WEST, linearUnit)) - .as_nullable(); - } else if (methodCode == - EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A || - methodCode == - EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) { - // It is likely that the ESRI definition of EPSG:32661 (UPS North) & - // EPSG:32761 (UPS South) uses the easting-northing order, instead - // of the EPSG northing-easting order. - // Same for WKT1_GDAL - const double lat0 = conversion->parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - common::UnitOfMeasure::DEGREE); - if (std::fabs(lat0 - 90) < 1e-10) { - cartesianCS = - CartesianCS::createNorthPoleEastingSouthNorthingSouth( - linearUnit) - .as_nullable(); - } else if (std::fabs(lat0 - -90) < 1e-10) { - cartesianCS = - CartesianCS::createSouthPoleEastingNorthNorthingNorth( - linearUnit) - .as_nullable(); - } - } else if (methodCode == - EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) { - const double lat_ts = conversion->parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, - common::UnitOfMeasure::DEGREE); - if (lat_ts > 0) { - cartesianCS = - CartesianCS::createNorthPoleEastingSouthNorthingSouth( - linearUnit) - .as_nullable(); - } else if (lat_ts < 0) { - cartesianCS = - CartesianCS::createSouthPoleEastingNorthNorthingNorth( - linearUnit) - .as_nullable(); - } - } else if (methodCode == - EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) { - cartesianCS = - CartesianCS::createWestingSouthing(linearUnit).as_nullable(); - } - } - if (!cartesianCS) { - ThrowNotExpectedCSType("Cartesian"); - } - - addExtensionProj4ToProp(nodeP, props); - - return ProjectedCRS::create(props, baseGeodCRS, conversion, - NN_NO_CHECK(cartesianCS)); -} - -// --------------------------------------------------------------------------- - -void WKTParser::Private::parseDynamic(const WKTNodeNNPtr &dynamicNode, - double &frameReferenceEpoch, - util::optional &modelName) { - auto &frameEpochNode = dynamicNode->lookForChild(WKTConstants::FRAMEEPOCH); - const auto &frameEpochChildren = frameEpochNode->GP()->children(); - if (frameEpochChildren.empty()) { - ThrowMissing(WKTConstants::FRAMEEPOCH); - } - try { - frameReferenceEpoch = asDouble(frameEpochChildren[0]); - } catch (const std::exception &) { - throw ParsingException("Invalid FRAMEEPOCH node"); - } - auto &modelNode = dynamicNode->GP()->lookForChild( - WKTConstants::MODEL, WKTConstants::VELOCITYGRID); - const auto &modelChildren = modelNode->GP()->children(); - if (modelChildren.size() == 1) { - modelName = stripQuotes(modelChildren[0]); - } -} - -// --------------------------------------------------------------------------- - -VerticalReferenceFrameNNPtr WKTParser::Private::buildVerticalReferenceFrame( - const WKTNodeNNPtr &node, const WKTNodeNNPtr &dynamicNode) { - - if (!isNull(dynamicNode)) { - double frameReferenceEpoch = 0.0; - util::optional modelName; - parseDynamic(dynamicNode, frameReferenceEpoch, modelName); - return DynamicVerticalReferenceFrame::create( - buildProperties(node), getAnchor(node), - optional(), - common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR), - modelName); - } - - // WKT1 VERT_DATUM has a datum type after the datum name that we ignore. - return VerticalReferenceFrame::create(buildProperties(node), - getAnchor(node)); -} - -// --------------------------------------------------------------------------- - -TemporalDatumNNPtr -WKTParser::Private::buildTemporalDatum(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &calendarNode = nodeP->lookForChild(WKTConstants::CALENDAR); - std::string calendar = TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN; - const auto &calendarChildren = calendarNode->GP()->children(); - if (calendarChildren.size() == 1) { - calendar = stripQuotes(calendarChildren[0]); - } - - auto &timeOriginNode = nodeP->lookForChild(WKTConstants::TIMEORIGIN); - std::string originStr; - const auto &timeOriginNodeChildren = timeOriginNode->GP()->children(); - if (timeOriginNodeChildren.size() == 1) { - originStr = stripQuotes(timeOriginNodeChildren[0]); - } - auto origin = DateTime::create(originStr); - return TemporalDatum::create(buildProperties(node), origin, calendar); -} - -// --------------------------------------------------------------------------- - -EngineeringDatumNNPtr -WKTParser::Private::buildEngineeringDatum(const WKTNodeNNPtr &node) { - return EngineeringDatum::create(buildProperties(node), getAnchor(node)); -} - -// --------------------------------------------------------------------------- - -ParametricDatumNNPtr -WKTParser::Private::buildParametricDatum(const WKTNodeNNPtr &node) { - return ParametricDatum::create(buildProperties(node), getAnchor(node)); -} - -// --------------------------------------------------------------------------- - -CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &datumNode = - nodeP->lookForChild(WKTConstants::VDATUM, WKTConstants::VERT_DATUM, - WKTConstants::VERTICALDATUM, WKTConstants::VRF); - auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE); - if (isNull(datumNode) && isNull(ensembleNode)) { - throw ParsingException("Missing VDATUM or ENSEMBLE node"); - } - - auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC); - auto datum = - !isNull(datumNode) - ? buildVerticalReferenceFrame(datumNode, dynamicNode).as_nullable() - : nullptr; - auto datumEnsemble = - !isNull(ensembleNode) - ? buildDatumEnsemble(ensembleNode, nullptr, false).as_nullable() - : nullptr; - - auto &csNode = nodeP->lookForChild(WKTConstants::CS); - const auto &nodeValue = nodeP->value(); - if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::VERT_CS) && - !ci_equal(nodeValue, WKTConstants::BASEVERTCRS)) { - ThrowMissing(WKTConstants::CS); - } - auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); - auto verticalCS = nn_dynamic_pointer_cast(cs); - if (!verticalCS) { - ThrowNotExpectedCSType("vertical"); - } - - auto crs = nn_static_pointer_cast(VerticalCRS::create( - buildProperties(node), datum, datumEnsemble, NN_NO_CHECK(verticalCS))); - - if (!isNull(datumNode)) { - auto &extensionNode = datumNode->lookForChild(WKTConstants::EXTENSION); - const auto &extensionChildren = extensionNode->GP()->children(); - if (extensionChildren.size() == 2) { - if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) { - std::string transformationName(crs->nameStr()); - if (!ends_with(transformationName, " height")) { - transformationName += " height"; - } - transformationName += " to WGS84 ellipsoidal height"; - auto transformation = - Transformation::createGravityRelatedHeightToGeographic3D( - PropertyMap().set(IdentifiedObject::NAME_KEY, - transformationName), - crs, GeographicCRS::EPSG_4979, - stripQuotes(extensionChildren[1]), - std::vector()); - return nn_static_pointer_cast(BoundCRS::create( - crs, GeographicCRS::EPSG_4979, transformation)); - } - } - } - - return crs; -} - -// --------------------------------------------------------------------------- - -DerivedVerticalCRSNNPtr -WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &baseVertCRSNode = nodeP->lookForChild(WKTConstants::BASEVERTCRS); - // given the constraints enforced on calling code path - assert(!isNull(baseVertCRSNode)); - - auto baseVertCRS_tmp = buildVerticalCRS(baseVertCRSNode); - auto baseVertCRS = NN_NO_CHECK(baseVertCRS_tmp->extractVerticalCRS()); - - auto &derivingConversionNode = - nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); - if (isNull(derivingConversionNode)) { - ThrowMissing(WKTConstants::DERIVINGCONVERSION); - } - auto derivingConversion = buildConversion( - derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); - - auto &csNode = nodeP->lookForChild(WKTConstants::CS); - if (isNull(csNode)) { - ThrowMissing(WKTConstants::CS); - } - auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); - - auto verticalCS = nn_dynamic_pointer_cast(cs); - if (!verticalCS) { - throw ParsingException( - concat("vertical CS expected, but found ", cs->getWKT2Type(true))); - } - - return DerivedVerticalCRS::create(buildProperties(node), baseVertCRS, - derivingConversion, - NN_NO_CHECK(verticalCS)); -} - -// --------------------------------------------------------------------------- - -CompoundCRSNNPtr -WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) { - std::vector components; - for (const auto &child : node->GP()->children()) { - auto crs = buildCRS(child); - if (crs) { - components.push_back(NN_NO_CHECK(crs)); - } - } - return CompoundCRS::create(buildProperties(node), components); -} - -// --------------------------------------------------------------------------- - -BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &abridgedNode = - nodeP->lookForChild(WKTConstants::ABRIDGEDTRANSFORMATION); - if (isNull(abridgedNode)) { - ThrowNotEnoughChildren(WKTConstants::ABRIDGEDTRANSFORMATION); - } - - auto &methodNode = abridgedNode->GP()->lookForChild(WKTConstants::METHOD); - if (isNull(methodNode)) { - ThrowMissing(WKTConstants::METHOD); - } - if (methodNode->GP()->childrenSize() == 0) { - ThrowNotEnoughChildren(WKTConstants::METHOD); - } - - auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS); - const auto &sourceCRSNodeChildren = sourceCRSNode->GP()->children(); - if (sourceCRSNodeChildren.size() != 1) { - ThrowNotEnoughChildren(WKTConstants::SOURCECRS); - } - auto sourceCRS = buildCRS(sourceCRSNodeChildren[0]); - if (!sourceCRS) { - throw ParsingException("Invalid content in SOURCECRS node"); - } - - auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS); - const auto &targetCRSNodeChildren = targetCRSNode->GP()->children(); - if (targetCRSNodeChildren.size() != 1) { - ThrowNotEnoughChildren(WKTConstants::TARGETCRS); - } - auto targetCRS = buildCRS(targetCRSNodeChildren[0]); - if (!targetCRS) { - throw ParsingException("Invalid content in TARGETCRS node"); - } - - std::vector parameters; - std::vector values; - auto defaultLinearUnit = UnitOfMeasure::NONE; - auto defaultAngularUnit = UnitOfMeasure::NONE; - consumeParameters(abridgedNode, true, parameters, values, defaultLinearUnit, - defaultAngularUnit); - - CRSPtr sourceTransformationCRS; - if (dynamic_cast(targetCRS.get())) { - sourceTransformationCRS = sourceCRS->extractGeographicCRS(); - if (!sourceTransformationCRS) { - throw ParsingException("Cannot find GeographicCRS in sourceCRS"); - } - } else { - sourceTransformationCRS = sourceCRS; - } - - auto transformation = Transformation::create( - buildProperties(abridgedNode), NN_NO_CHECK(sourceTransformationCRS), - NN_NO_CHECK(targetCRS), nullptr, buildProperties(methodNode), - parameters, values, std::vector()); - - return BoundCRS::create(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), - transformation); -} - -// --------------------------------------------------------------------------- - -TemporalCSNNPtr -WKTParser::Private::buildTemporalCS(const WKTNodeNNPtr &parentNode) { - - auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS); - if (isNull(csNode) && - !ci_equal(parentNode->GP()->value(), WKTConstants::BASETIMECRS)) { - ThrowMissing(WKTConstants::CS); - } - auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE); - auto temporalCS = nn_dynamic_pointer_cast(cs); - if (!temporalCS) { - ThrowNotExpectedCSType("temporal"); - } - return NN_NO_CHECK(temporalCS); -} - -// --------------------------------------------------------------------------- - -TemporalCRSNNPtr -WKTParser::Private::buildTemporalCRS(const WKTNodeNNPtr &node) { - auto &datumNode = - node->GP()->lookForChild(WKTConstants::TDATUM, WKTConstants::TIMEDATUM); - if (isNull(datumNode)) { - throw ParsingException("Missing TDATUM / TIMEDATUM node"); - } - - return TemporalCRS::create(buildProperties(node), - buildTemporalDatum(datumNode), - buildTemporalCS(node)); -} - -// --------------------------------------------------------------------------- - -DerivedTemporalCRSNNPtr -WKTParser::Private::buildDerivedTemporalCRS(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &baseCRSNode = nodeP->lookForChild(WKTConstants::BASETIMECRS); - // given the constraints enforced on calling code path - assert(!isNull(baseCRSNode)); - - auto &derivingConversionNode = - nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); - if (isNull(derivingConversionNode)) { - ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); - } - - return DerivedTemporalCRS::create( - buildProperties(node), buildTemporalCRS(baseCRSNode), - buildConversion(derivingConversionNode, UnitOfMeasure::NONE, - UnitOfMeasure::NONE), - buildTemporalCS(node)); -} - -// --------------------------------------------------------------------------- - -EngineeringCRSNNPtr -WKTParser::Private::buildEngineeringCRS(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &datumNode = nodeP->lookForChild(WKTConstants::EDATUM, - WKTConstants::ENGINEERINGDATUM); - if (isNull(datumNode)) { - throw ParsingException("Missing EDATUM / ENGINEERINGDATUM node"); - } - - auto &csNode = nodeP->lookForChild(WKTConstants::CS); - if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::BASEENGCRS)) { - ThrowMissing(WKTConstants::CS); - } - - auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); - return EngineeringCRS::create(buildProperties(node), - buildEngineeringDatum(datumNode), cs); -} - -// --------------------------------------------------------------------------- - -EngineeringCRSNNPtr -WKTParser::Private::buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node) { - auto &datumNode = node->GP()->lookForChild(WKTConstants::LOCAL_DATUM); - auto cs = buildCS(null_node, node, UnitOfMeasure::NONE); - auto datum = EngineeringDatum::create( - !isNull(datumNode) - ? buildProperties(datumNode) - : - // In theory OGC 01-009 mandates LOCAL_DATUM, but GDAL has a - // tradition of emitting just LOCAL_CS["foo"] - emptyPropertyMap); - return EngineeringCRS::create(buildProperties(node), datum, cs); -} - -// --------------------------------------------------------------------------- - -DerivedEngineeringCRSNNPtr -WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &baseEngCRSNode = nodeP->lookForChild(WKTConstants::BASEENGCRS); - // given the constraints enforced on calling code path - assert(!isNull(baseEngCRSNode)); - - auto baseEngCRS = buildEngineeringCRS(baseEngCRSNode); - - auto &derivingConversionNode = - nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); - if (isNull(derivingConversionNode)) { - ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); - } - auto derivingConversion = buildConversion( - derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); - - auto &csNode = nodeP->lookForChild(WKTConstants::CS); - if (isNull(csNode)) { - ThrowMissing(WKTConstants::CS); - } - auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); - - return DerivedEngineeringCRS::create(buildProperties(node), baseEngCRS, - derivingConversion, cs); -} - -// --------------------------------------------------------------------------- - -ParametricCSNNPtr -WKTParser::Private::buildParametricCS(const WKTNodeNNPtr &parentNode) { - - auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS); - if (isNull(csNode) && - !ci_equal(parentNode->GP()->value(), WKTConstants::BASEPARAMCRS)) { - ThrowMissing(WKTConstants::CS); - } - auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE); - auto parametricCS = nn_dynamic_pointer_cast(cs); - if (!parametricCS) { - ThrowNotExpectedCSType("parametric"); - } - return NN_NO_CHECK(parametricCS); -} - -// --------------------------------------------------------------------------- - -ParametricCRSNNPtr -WKTParser::Private::buildParametricCRS(const WKTNodeNNPtr &node) { - auto &datumNode = node->GP()->lookForChild(WKTConstants::PDATUM, - WKTConstants::PARAMETRICDATUM); - if (isNull(datumNode)) { - throw ParsingException("Missing PDATUM / PARAMETRICDATUM node"); - } - - return ParametricCRS::create(buildProperties(node), - buildParametricDatum(datumNode), - buildParametricCS(node)); -} - -// --------------------------------------------------------------------------- - -DerivedParametricCRSNNPtr -WKTParser::Private::buildDerivedParametricCRS(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &baseParamCRSNode = nodeP->lookForChild(WKTConstants::BASEPARAMCRS); - // given the constraints enforced on calling code path - assert(!isNull(baseParamCRSNode)); - - auto &derivingConversionNode = - nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); - if (isNull(derivingConversionNode)) { - ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); - } - - return DerivedParametricCRS::create( - buildProperties(node), buildParametricCRS(baseParamCRSNode), - buildConversion(derivingConversionNode, UnitOfMeasure::NONE, - UnitOfMeasure::NONE), - buildParametricCS(node)); -} - -// --------------------------------------------------------------------------- - -DerivedProjectedCRSNNPtr -WKTParser::Private::buildDerivedProjectedCRS(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - auto &baseProjCRSNode = nodeP->lookForChild(WKTConstants::BASEPROJCRS); - if (isNull(baseProjCRSNode)) { - ThrowNotEnoughChildren(WKTConstants::BASEPROJCRS); - } - auto baseProjCRS = buildProjectedCRS(baseProjCRSNode); - - auto &conversionNode = - nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); - if (isNull(conversionNode)) { - ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); - } - - auto linearUnit = buildUnitInSubNode(node); - auto angularUnit = - baseProjCRS->baseCRS()->coordinateSystem()->axisList()[0]->unit(); - - auto conversion = buildConversion(conversionNode, linearUnit, angularUnit); - - auto &csNode = nodeP->lookForChild(WKTConstants::CS); - if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::PROJCS)) { - ThrowMissing(WKTConstants::CS); - } - auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); - return DerivedProjectedCRS::create(buildProperties(node), baseProjCRS, - conversion, cs); -} - -// --------------------------------------------------------------------------- - -static bool isGeodeticCRS(const std::string &name) { - return ci_equal(name, WKTConstants::GEODCRS) || // WKT2 - ci_equal(name, WKTConstants::GEODETICCRS) || // WKT2 - ci_equal(name, WKTConstants::GEOGCRS) || // WKT2 2018 - ci_equal(name, WKTConstants::GEOGRAPHICCRS) || // WKT2 2018 - ci_equal(name, WKTConstants::GEOGCS) || // WKT1 - ci_equal(name, WKTConstants::GEOCCS); // WKT1 -} - -// --------------------------------------------------------------------------- - -CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - const std::string &name(nodeP->value()); - - if (isGeodeticCRS(name)) { - if (!isNull(nodeP->lookForChild(WKTConstants::BASEGEOGCRS, - WKTConstants::BASEGEODCRS))) { - return buildDerivedGeodeticCRS(node); - } else { - return util::nn_static_pointer_cast(buildGeodeticCRS(node)); - } - } - - if (ci_equal(name, WKTConstants::PROJCS) || - ci_equal(name, WKTConstants::PROJCRS) || - ci_equal(name, WKTConstants::PROJECTEDCRS)) { - return util::nn_static_pointer_cast(buildProjectedCRS(node)); - } - - if (ci_equal(name, WKTConstants::VERT_CS) || - ci_equal(name, WKTConstants::VERTCRS) || - ci_equal(name, WKTConstants::VERTICALCRS)) { - if (!isNull(nodeP->lookForChild(WKTConstants::BASEVERTCRS))) { - return util::nn_static_pointer_cast( - buildDerivedVerticalCRS(node)); - } else { - return util::nn_static_pointer_cast(buildVerticalCRS(node)); - } - } - - if (ci_equal(name, WKTConstants::COMPD_CS) || - ci_equal(name, WKTConstants::COMPOUNDCRS)) { - return util::nn_static_pointer_cast(buildCompoundCRS(node)); - } - - if (ci_equal(name, WKTConstants::BOUNDCRS)) { - return util::nn_static_pointer_cast(buildBoundCRS(node)); - } - - if (ci_equal(name, WKTConstants::TIMECRS)) { - if (!isNull(nodeP->lookForChild(WKTConstants::BASETIMECRS))) { - return util::nn_static_pointer_cast( - buildDerivedTemporalCRS(node)); - } else { - return util::nn_static_pointer_cast(buildTemporalCRS(node)); - } - } - - if (ci_equal(name, WKTConstants::DERIVEDPROJCRS)) { - return util::nn_static_pointer_cast( - buildDerivedProjectedCRS(node)); - } - - if (ci_equal(name, WKTConstants::ENGCRS) || - ci_equal(name, WKTConstants::ENGINEERINGCRS)) { - if (!isNull(nodeP->lookForChild(WKTConstants::BASEENGCRS))) { - return util::nn_static_pointer_cast( - buildDerivedEngineeringCRS(node)); - } else { - return util::nn_static_pointer_cast(buildEngineeringCRS(node)); - } - } - - if (ci_equal(name, WKTConstants::LOCAL_CS)) { - return util::nn_static_pointer_cast( - buildEngineeringCRSFromLocalCS(node)); - } - - if (ci_equal(name, WKTConstants::PARAMETRICCRS)) { - if (!isNull(nodeP->lookForChild(WKTConstants::BASEPARAMCRS))) { - return util::nn_static_pointer_cast( - buildDerivedParametricCRS(node)); - } else { - return util::nn_static_pointer_cast(buildParametricCRS(node)); - } - } - - return nullptr; -} - -// --------------------------------------------------------------------------- - -BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) { - const auto *nodeP = node->GP(); - const std::string &name(nodeP->value()); - - auto crs = buildCRS(node); - if (crs) { - if (!toWGS84Parameters_.empty()) { - return util::nn_static_pointer_cast( - BoundCRS::createFromTOWGS84(NN_NO_CHECK(crs), - toWGS84Parameters_)); - } - if (!datumPROJ4Grids_.empty()) { - return util::nn_static_pointer_cast( - BoundCRS::createFromNadgrids(NN_NO_CHECK(crs), - datumPROJ4Grids_)); - } - return util::nn_static_pointer_cast(NN_NO_CHECK(crs)); - } - - if (ci_equal(name, WKTConstants::DATUM) || - ci_equal(name, WKTConstants::GEODETICDATUM) || - ci_equal(name, WKTConstants::TRF)) { - return util::nn_static_pointer_cast( - buildGeodeticReferenceFrame(node, PrimeMeridian::GREENWICH, - null_node)); - } - - if (ci_equal(name, WKTConstants::ENSEMBLE)) { - return util::nn_static_pointer_cast(buildDatumEnsemble( - node, PrimeMeridian::GREENWICH, - !isNull(nodeP->lookForChild(WKTConstants::ELLIPSOID)))); - } - - if (ci_equal(name, WKTConstants::VDATUM) || - ci_equal(name, WKTConstants::VERT_DATUM) || - ci_equal(name, WKTConstants::VERTICALDATUM) || - ci_equal(name, WKTConstants::VRF)) { - return util::nn_static_pointer_cast( - buildVerticalReferenceFrame(node, null_node)); - } - - if (ci_equal(name, WKTConstants::TDATUM) || - ci_equal(name, WKTConstants::TIMEDATUM)) { - return util::nn_static_pointer_cast( - buildTemporalDatum(node)); - } - - if (ci_equal(name, WKTConstants::EDATUM) || - ci_equal(name, WKTConstants::ENGINEERINGDATUM)) { - return util::nn_static_pointer_cast( - buildEngineeringDatum(node)); - } - - if (ci_equal(name, WKTConstants::PDATUM) || - ci_equal(name, WKTConstants::PARAMETRICDATUM)) { - return util::nn_static_pointer_cast( - buildParametricDatum(node)); - } - - if (ci_equal(name, WKTConstants::ELLIPSOID) || - ci_equal(name, WKTConstants::SPHEROID)) { - return util::nn_static_pointer_cast(buildEllipsoid(node)); - } - - if (ci_equal(name, WKTConstants::COORDINATEOPERATION)) { - return util::nn_static_pointer_cast( - buildCoordinateOperation(node)); - } - - if (ci_equal(name, WKTConstants::CONVERSION)) { - return util::nn_static_pointer_cast( - buildConversion(node, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE)); - } - - if (ci_equal(name, WKTConstants::CONCATENATEDOPERATION)) { - return util::nn_static_pointer_cast( - buildConcatenatedOperation(node)); - } - - if (ci_equal(name, WKTConstants::ID) || - ci_equal(name, WKTConstants::AUTHORITY)) { - return util::nn_static_pointer_cast( - NN_NO_CHECK(buildId(node, false))); - } - - throw ParsingException(concat("unhandled keyword: ", name)); -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instanciate a sub-class of BaseObject from a user specified text. - * - * The text can be a: - *
    - *
  • WKT string
  • - *
  • PROJ string
  • - *
  • database code, prefixed by its authoriy. e.g. "EPSG:4326"
  • - *
  • URN. e.g. "urn:ogc:def:crs:EPSG::4326", - * "urn:ogc:def:coordinateOperation:EPSG::1671"
  • - *
  • an objet name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as - * uniqueness is not guaranteed, the function may apply heuristics to - * determine the appropriate best match.
  • - *
- * - * @param text One of the above mentionned text format - * @param dbContext Database context, or nullptr (in which case database - * lookups will not work) - * @param usePROJ4InitRules When set to true, - * init=epsg:XXXX syntax will be allowed and will be interpreted according to - * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude - * order and will expect/output coordinates in radians. ProjectedCRS will have - * easting, northing axis order (except the ones with Transverse Mercator South - * Orientated projection). In that mode, the epsg:XXXX syntax will be also - * interprated the same way. - * @throw ParsingException - */ -BaseObjectNNPtr createFromUserInput(const std::string &text, - const DatabaseContextPtr &dbContext, - bool usePROJ4InitRules) { - - for (const auto &wktConstants : WKTConstants::constants()) { - if (ci_starts_with(text, wktConstants)) { - return WKTParser() - .attachDatabaseContext(dbContext) - .setStrict(false) - .createFromWKT(text); - } - } - const char *textWithoutPlusPrefix = text.c_str(); - if (textWithoutPlusPrefix[0] == '+') - textWithoutPlusPrefix++; - - if (strncmp(textWithoutPlusPrefix, "proj=", strlen("proj=")) == 0 || - strncmp(textWithoutPlusPrefix, "init=", strlen("init=")) == 0 || - strncmp(textWithoutPlusPrefix, "title=", strlen("title=")) == 0) { - return PROJStringParser() - .attachDatabaseContext(dbContext) - .setUsePROJ4InitRules(usePROJ4InitRules) - .createFromPROJString(text); - } - - auto tokens = split(text, ':'); - if (tokens.size() == 2) { - if (!dbContext) { - throw ParsingException("no database context specified"); - } - DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext)); - const auto &authName = tokens[0]; - const auto &code = tokens[1]; - auto factory = AuthorityFactory::create(dbContextNNPtr, authName); - try { - return factory->createCoordinateReferenceSystem(code); - } catch (...) { - const auto authorities = dbContextNNPtr->getAuthorities(); - for (const auto &authCandidate : authorities) { - if (ci_equal(authCandidate, authName)) { - return AuthorityFactory::create(dbContextNNPtr, - authCandidate) - ->createCoordinateReferenceSystem(code); - } - } - throw; - } - } - - // urn:ogc:def:crs:EPSG::4326 - if (tokens.size() == 7) { - if (!dbContext) { - throw ParsingException("no database context specified"); - } - const auto &type = tokens[3]; - auto factory = - AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[4]); - const auto &code = tokens[6]; - if (type == "crs") { - return factory->createCoordinateReferenceSystem(code); - } - if (type == "coordinateOperation") { - return factory->createCoordinateOperation(code, true); - } - if (type == "datum") { - return factory->createDatum(code); - } - if (type == "ellipsoid") { - return factory->createEllipsoid(code); - } - if (type == "meridian") { - return factory->createPrimeMeridian(code); - } - throw ParsingException(concat("unhandled object type: ", type)); - } - - if (dbContext) { - auto factory = - AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string()); - // First pass: exact match on CRS objects - // Second pass: exact match on other objects - // Third pass: approximate match on CRS objects - // Fourth pass: approximate match on other objects - constexpr size_t limitResultCount = 10; - for (int pass = 0; pass <= 3; ++pass) { - const bool approximateMatch = (pass >= 2); - auto res = factory->createObjectsFromName( - text, - (pass == 0 || pass == 2) - ? std::vector< - AuthorityFactory::ObjectType>{AuthorityFactory:: - ObjectType::CRS} - : std::vector< - AuthorityFactory:: - ObjectType>{AuthorityFactory::ObjectType:: - ELLIPSOID, - AuthorityFactory::ObjectType::DATUM, - AuthorityFactory::ObjectType:: - COORDINATE_OPERATION}, - approximateMatch, limitResultCount); - if (res.size() == 1) { - return res.front(); - } - if (res.size() > 1) { - if (pass == 0 || pass == 2) { - for (size_t ndim = 2; ndim <= 3; ndim++) { - for (const auto &obj : res) { - auto crs = - dynamic_cast(obj.get()); - if (crs && - crs->coordinateSystem()->axisList().size() == - ndim) { - return obj; - } - } - } - } - - std::string msg("several objects matching this name: "); - bool first = true; - for (const auto &obj : res) { - if (msg.size() > 200) { - msg += ", ..."; - break; - } - if (!first) { - msg += ", "; - } - first = false; - msg += obj->nameStr(); - } - throw ParsingException(msg); - } - } - } - - throw ParsingException("unrecognized format / unknown name"); -} - -// --------------------------------------------------------------------------- - -/** \brief Instanciate a sub-class of BaseObject from a WKT string. - * - * By default, validation is strict (to the extent of the checks that are - * actually implemented. Currently only WKT1 strict grammar is checked), and - * any issue detected will cause an exception to be thrown, unless - * setStrict(false) is called priorly. - * - * In non-strict mode, non-fatal issues will be recovered and simply listed - * in warningList(). This does not prevent more severe errors to cause an - * exception to be thrown. - * - * @throw ParsingException - */ -BaseObjectNNPtr WKTParser::createFromWKT(const std::string &wkt) { - WKTNodeNNPtr root = WKTNode::createFrom(wkt); - auto obj = d->build(root); - - const auto dialect = guessDialect(wkt); - if (dialect == WKTGuessedDialect::WKT1_GDAL || - dialect == WKTGuessedDialect::WKT1_ESRI) { - auto errorMsg = pj_wkt1_parse(wkt); - if (!errorMsg.empty()) { - d->emitRecoverableAssertion(errorMsg); - } - } else if (dialect == WKTGuessedDialect::WKT2_2015 || - dialect == WKTGuessedDialect::WKT2_2018) { - auto errorMsg = pj_wkt2_parse(wkt); - if (!errorMsg.empty()) { - d->emitRecoverableAssertion(errorMsg); - } - } - - return obj; -} - -// --------------------------------------------------------------------------- - -/** \brief Attach a database context, to allow queries in it if needed. - */ -WKTParser & -WKTParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) { - d->dbContext_ = dbContext; - return *this; -} - -// --------------------------------------------------------------------------- - -/** \brief Guess the "dialect" of the WKT string. - */ -WKTParser::WKTGuessedDialect -WKTParser::guessDialect(const std::string &wkt) noexcept { - const std::string *const wkt1_keywords[] = { - &WKTConstants::GEOCCS, &WKTConstants::GEOGCS, &WKTConstants::COMPD_CS, - &WKTConstants::PROJCS, &WKTConstants::VERT_CS, &WKTConstants::LOCAL_CS}; - for (const auto &pointerKeyword : wkt1_keywords) { - if (ci_starts_with(wkt, *pointerKeyword)) { - - if (ci_find(wkt, "GEOGCS[\"GCS_") != std::string::npos) { - return WKTGuessedDialect::WKT1_ESRI; - } - - return WKTGuessedDialect::WKT1_GDAL; - } - } - - const std::string *const wkt2_2018_only_keywords[] = { - &WKTConstants::GEOGCRS, - // contained in previous one - // &WKTConstants::BASEGEOGCRS, - &WKTConstants::CONCATENATEDOPERATION, &WKTConstants::USAGE, - &WKTConstants::DYNAMIC, &WKTConstants::FRAMEEPOCH, &WKTConstants::MODEL, - &WKTConstants::VELOCITYGRID, &WKTConstants::ENSEMBLE, - &WKTConstants::DERIVEDPROJCRS, &WKTConstants::BASEPROJCRS, - &WKTConstants::GEOGRAPHICCRS, &WKTConstants::TRF, &WKTConstants::VRF}; - - for (const auto &pointerKeyword : wkt2_2018_only_keywords) { - auto pos = ci_find(wkt, *pointerKeyword); - if (pos != std::string::npos && - wkt[pos + pointerKeyword->size()] == '[') { - return WKTGuessedDialect::WKT2_2018; - } - } - static const char *const wkt2_2018_only_substrings[] = { - "CS[TemporalDateTime,", "CS[TemporalCount,", "CS[TemporalMeasure,", - }; - for (const auto &substrings : wkt2_2018_only_substrings) { - if (ci_find(wkt, substrings) != std::string::npos) { - return WKTGuessedDialect::WKT2_2018; - } - } - - for (const auto &wktConstants : WKTConstants::constants()) { - if (ci_starts_with(wkt, wktConstants)) { - return WKTGuessedDialect::WKT2_2015; - } - } - - return WKTGuessedDialect::NOT_WKT; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -FormattingException::FormattingException(const char *message) - : Exception(message) {} - -// --------------------------------------------------------------------------- - -FormattingException::FormattingException(const std::string &message) - : Exception(message) {} - -// --------------------------------------------------------------------------- - -FormattingException::FormattingException(const FormattingException &) = default; - -// --------------------------------------------------------------------------- - -FormattingException::~FormattingException() = default; - -// --------------------------------------------------------------------------- - -void FormattingException::Throw(const char *msg) { - throw FormattingException(msg); -} - -// --------------------------------------------------------------------------- - -void FormattingException::Throw(const std::string &msg) { - throw FormattingException(msg); -} - -// --------------------------------------------------------------------------- - -ParsingException::ParsingException(const char *message) : Exception(message) {} - -// --------------------------------------------------------------------------- - -ParsingException::ParsingException(const std::string &message) - : Exception(message) {} - -// --------------------------------------------------------------------------- - -ParsingException::ParsingException(const ParsingException &) = default; - -// --------------------------------------------------------------------------- - -ParsingException::~ParsingException() = default; - -// --------------------------------------------------------------------------- - -IPROJStringExportable::~IPROJStringExportable() = default; - -// --------------------------------------------------------------------------- - -std::string IPROJStringExportable::exportToPROJString( - PROJStringFormatter *formatter) const { - _exportToPROJString(formatter); - if (formatter->getAddNoDefs() && - formatter->convention() == - io::PROJStringFormatter::Convention::PROJ_4 && - dynamic_cast(this)) { - if (!formatter->hasParam("no_defs")) { - formatter->addParam("no_defs"); - } - } - return formatter->toString(); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -struct Step { - std::string name{}; - bool isInit = false; - bool inverted{false}; - - struct KeyValue { - std::string key{}; - std::string value{}; - - explicit KeyValue(const std::string &keyIn) : key(keyIn) {} - - KeyValue(const char *keyIn, const std::string &valueIn); - - KeyValue(const std::string &keyIn, const std::string &valueIn) - : key(keyIn), value(valueIn) {} - - // cppcheck-suppress functionStatic - bool keyEquals(const char *otherKey) const noexcept { - return key == otherKey; - } - - // cppcheck-suppress functionStatic - bool equals(const char *otherKey, const char *otherVal) const noexcept { - return key == otherKey && value == otherVal; - } - - bool operator!=(const KeyValue &other) const noexcept { - return key != other.key || value != other.value; - } - }; - - std::vector paramValues{}; -}; - -Step::KeyValue::KeyValue(const char *keyIn, const std::string &valueIn) - : key(keyIn), value(valueIn) {} - -struct PROJStringFormatter::Private { - PROJStringFormatter::Convention convention_ = - PROJStringFormatter::Convention::PROJ_5; - std::vector toWGS84Parameters_{}; - std::string vDatumExtension_{}; - std::string hDatumExtension_{}; - - std::list steps_{}; - std::vector globalParamValues_{}; - - struct InversionStackElt { - std::list::iterator startIter{}; - bool iterValid = false; - bool currentInversionState = false; - }; - std::vector inversionStack_{InversionStackElt()}; - bool omitProjLongLatIfPossible_ = false; - bool omitZUnitConversion_ = false; - DatabaseContextPtr dbContext_{}; - bool useETMercForTMerc_ = false; - bool useETMercForTMercSet_ = false; - bool addNoDefs_ = true; - bool coordOperationOptimizations_ = false; - - std::string result_{}; - - // cppcheck-suppress functionStatic - void appendToResult(const char *str); - - // cppcheck-suppress functionStatic - void addStep(); -}; - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -PROJStringFormatter::PROJStringFormatter(Convention conventionIn, - const DatabaseContextPtr &dbContext) - : d(internal::make_unique()) { - d->convention_ = conventionIn; - d->dbContext_ = dbContext; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -PROJStringFormatter::~PROJStringFormatter() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Constructs a new formatter. - * - * A formatter can be used only once (its internal state is mutated) - * - * Its default behaviour can be adjusted with the different setters. - * - * @param conventionIn PROJ string flavor. Defaults to Convention::PROJ_5 - * @param dbContext Database context (can help to find alternative grid names). - * May be nullptr - * @return new formatter. - */ -PROJStringFormatterNNPtr -PROJStringFormatter::create(Convention conventionIn, - DatabaseContextPtr dbContext) { - return NN_NO_CHECK(PROJStringFormatter::make_unique( - conventionIn, dbContext)); -} - -// --------------------------------------------------------------------------- - -/** \brief Set whether Extented Transverse Mercator (etmerc) should be used - * instead of tmerc */ -void PROJStringFormatter::setUseETMercForTMerc(bool flag) { - d->useETMercForTMerc_ = flag; - d->useETMercForTMercSet_ = true; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns the PROJ string. */ -const std::string &PROJStringFormatter::toString() const { - - assert(d->inversionStack_.size() == 1); - - d->result_.clear(); - - for (auto iter = d->steps_.begin(); iter != d->steps_.end();) { - // Remove no-op helmert - auto &step = *iter; - const auto paramCount = step.paramValues.size(); - if (step.name == "helmert" && (paramCount == 3 || paramCount == 8) && - step.paramValues[0].equals("x", "0") && - step.paramValues[1].equals("y", "0") && - step.paramValues[2].equals("z", "0") && - (paramCount == 3 || - (step.paramValues[3].equals("rx", "0") && - step.paramValues[4].equals("ry", "0") && - step.paramValues[5].equals("rz", "0") && - step.paramValues[6].equals("s", "0") && - step.paramValues[7].keyEquals("convention")))) { - iter = d->steps_.erase(iter); - } else if (d->coordOperationOptimizations_ && - step.name == "unitconvert" && paramCount == 2 && - step.paramValues[0].keyEquals("xy_in") && - step.paramValues[1].keyEquals("xy_out") && - step.paramValues[0].value == step.paramValues[1].value) { - iter = d->steps_.erase(iter); - } else { - ++iter; - } - } - - for (auto &step : d->steps_) { - if (!step.inverted) { - continue; - } - - const auto paramCount = step.paramValues.size(); - - // axisswap order=2,1 is its own inverse - if (step.name == "axisswap" && paramCount == 1 && - step.paramValues[0].equals("order", "2,1")) { - step.inverted = false; - continue; - } - - // handle unitconvert inverse - if (step.name == "unitconvert" && paramCount == 2 && - step.paramValues[0].keyEquals("xy_in") && - step.paramValues[1].keyEquals("xy_out")) { - std::swap(step.paramValues[0].value, step.paramValues[1].value); - step.inverted = false; - continue; - } - - if (step.name == "unitconvert" && paramCount == 2 && - step.paramValues[0].keyEquals("z_in") && - step.paramValues[1].keyEquals("z_out")) { - std::swap(step.paramValues[0].value, step.paramValues[1].value); - step.inverted = false; - continue; - } - - if (step.name == "unitconvert" && paramCount == 4 && - step.paramValues[0].keyEquals("xy_in") && - step.paramValues[1].keyEquals("z_in") && - step.paramValues[2].keyEquals("xy_out") && - step.paramValues[3].keyEquals("z_out")) { - std::swap(step.paramValues[0].value, step.paramValues[2].value); - std::swap(step.paramValues[1].value, step.paramValues[3].value); - step.inverted = false; - continue; - } - } - - bool changeDone; - do { - changeDone = false; - auto iterPrev = d->steps_.begin(); - if (iterPrev == d->steps_.end()) { - break; - } - auto iterCur = iterPrev; - iterCur++; - for (size_t i = 1; i < d->steps_.size(); ++i, ++iterCur, ++iterPrev) { - - auto &prevStep = *iterPrev; - auto &curStep = *iterCur; - - const auto curStepParamCount = curStep.paramValues.size(); - const auto prevStepParamCount = prevStep.paramValues.size(); - - // longlat (or its inverse) with ellipsoid only is a no-op - // do that only for an internal step - if (i + 1 < d->steps_.size() && curStep.name == "longlat" && - curStepParamCount == 1 && - curStep.paramValues[0].keyEquals("ellps")) { - d->steps_.erase(iterCur); - changeDone = true; - break; - } - - // unitconvert (xy) followed by its inverse is a no-op - if (curStep.name == "unitconvert" && - prevStep.name == "unitconvert" && !curStep.inverted && - !prevStep.inverted && curStepParamCount == 2 && - prevStepParamCount == 2 && - curStep.paramValues[0].keyEquals("xy_in") && - prevStep.paramValues[0].keyEquals("xy_in") && - curStep.paramValues[1].keyEquals("xy_out") && - prevStep.paramValues[1].keyEquals("xy_out") && - curStep.paramValues[0].value == prevStep.paramValues[1].value && - curStep.paramValues[1].value == prevStep.paramValues[0].value) { - ++iterCur; - d->steps_.erase(iterPrev, iterCur); - changeDone = true; - break; - } - - // unitconvert (z) followed by its inverse is a no-op - if (curStep.name == "unitconvert" && - prevStep.name == "unitconvert" && !curStep.inverted && - !prevStep.inverted && curStepParamCount == 2 && - prevStepParamCount == 2 && - curStep.paramValues[0].keyEquals("z_in") && - prevStep.paramValues[0].keyEquals("z_in") && - curStep.paramValues[1].keyEquals("z_out") && - prevStep.paramValues[1].keyEquals("z_out") && - curStep.paramValues[0].value == prevStep.paramValues[1].value && - curStep.paramValues[1].value == prevStep.paramValues[0].value) { - ++iterCur; - d->steps_.erase(iterPrev, iterCur); - changeDone = true; - break; - } - - // unitconvert (xyz) followed by its inverse is a no-op - if (curStep.name == "unitconvert" && - prevStep.name == "unitconvert" && !curStep.inverted && - !prevStep.inverted && curStepParamCount == 4 && - prevStepParamCount == 4 && - curStep.paramValues[0].keyEquals("xy_in") && - prevStep.paramValues[0].keyEquals("xy_in") && - curStep.paramValues[1].keyEquals("z_in") && - prevStep.paramValues[1].keyEquals("z_in") && - curStep.paramValues[2].keyEquals("xy_out") && - prevStep.paramValues[2].keyEquals("xy_out") && - curStep.paramValues[3].keyEquals("z_out") && - prevStep.paramValues[3].keyEquals("z_out") && - curStep.paramValues[0].value == prevStep.paramValues[2].value && - curStep.paramValues[1].value == prevStep.paramValues[3].value && - curStep.paramValues[2].value == prevStep.paramValues[0].value && - curStep.paramValues[3].value == prevStep.paramValues[1].value) { - ++iterCur; - d->steps_.erase(iterPrev, iterCur); - changeDone = true; - break; - } - - // combine unitconvert (xy) and unitconvert (z) - for (int k = 0; k < 2; ++k) { - auto &first = (k == 0) ? curStep : prevStep; - auto &second = (k == 0) ? prevStep : curStep; - if (first.name == "unitconvert" && - second.name == "unitconvert" && !first.inverted && - !second.inverted && first.paramValues.size() == 2 && - second.paramValues.size() == 2 && - second.paramValues[0].keyEquals("xy_in") && - second.paramValues[1].keyEquals("xy_out") && - first.paramValues[0].keyEquals("z_in") && - first.paramValues[1].keyEquals("z_out")) { - - auto xy_in = second.paramValues[0].value; - auto xy_out = second.paramValues[1].value; - auto z_in = first.paramValues[0].value; - auto z_out = first.paramValues[1].value; - d->steps_.erase(iterPrev, iterCur); - iterCur->paramValues.clear(); - iterCur->paramValues.emplace_back( - Step::KeyValue("xy_in", xy_in)); - iterCur->paramValues.emplace_back( - Step::KeyValue("z_in", z_in)); - iterCur->paramValues.emplace_back( - Step::KeyValue("xy_out", xy_out)); - iterCur->paramValues.emplace_back( - Step::KeyValue("z_out", z_out)); - changeDone = true; - break; - } - } - if (changeDone) { - break; - } - - // +step +proj=unitconvert +xy_in=X1 +xy_out=X2 - // +step +proj=unitconvert +xy_in=X2 +z_in=Z1 +xy_out=X1 +z_out=Z2 - // ==> step +proj=unitconvert +z_in=Z1 +z_out=Z2 - for (int k = 0; k < 2; ++k) { - auto &first = (k == 0) ? curStep : prevStep; - auto &second = (k == 0) ? prevStep : curStep; - if (first.name == "unitconvert" && - second.name == "unitconvert" && !first.inverted && - !second.inverted && first.paramValues.size() == 4 && - second.paramValues.size() == 2 && - first.paramValues[0].keyEquals("xy_in") && - first.paramValues[1].keyEquals("z_in") && - first.paramValues[2].keyEquals("xy_out") && - first.paramValues[3].keyEquals("z_out") && - second.paramValues[0].keyEquals("xy_in=") && - second.paramValues[1].keyEquals("xy_out") && - first.paramValues[0].value == second.paramValues[1].value && - first.paramValues[2].value == second.paramValues[0].value) { - auto z_in = first.paramValues[1].value; - auto z_out = first.paramValues[3].value; - if (z_in != z_out) { - d->steps_.erase(iterPrev, iterCur); - iterCur->paramValues.clear(); - iterCur->paramValues.emplace_back( - Step::KeyValue("z_in", z_in)); - iterCur->paramValues.emplace_back( - Step::KeyValue("z_out", z_out)); - } else { - ++iterCur; - d->steps_.erase(iterPrev, iterCur); - } - changeDone = true; - break; - } - } - if (changeDone) { - break; - } - - // axisswap order=2,1 followed by itself is a no-op - if (curStep.name == "axisswap" && prevStep.name == "axisswap" && - curStepParamCount == 1 && prevStepParamCount == 1 && - curStep.paramValues[0].equals("order", "2,1") && - prevStep.paramValues[0].equals("order", "2,1")) { - ++iterCur; - d->steps_.erase(iterPrev, iterCur); - changeDone = true; - break; - } - - // axisswap order=2,1, unitconvert, axisswap order=2,1 -> can - // suppress axisswap - if (i + 1 < d->steps_.size() && prevStep.name == "axisswap" && - curStep.name == "unitconvert" && prevStepParamCount == 1 && - prevStep.paramValues[0].equals("order", "2,1")) { - auto iterNext = iterCur; - ++iterNext; - auto &nextStep = *iterNext; - if (nextStep.name == "axisswap" && - nextStep.paramValues.size() == 1 && - nextStep.paramValues[0].equals("order", "2,1")) { - d->steps_.erase(iterPrev); - d->steps_.erase(iterNext); - changeDone = true; - break; - } - } - - // for practical purposes WGS84 and GRS80 ellipsoids are - // equivalents (cartesian transform between both lead to differences - // of the order of 1e-14 deg..). - // No need to do a cart roundtrip for that... - // and actually IGNF uses the GRS80 definition for the WGS84 datum - if (curStep.name == "cart" && prevStep.name == "cart" && - curStep.inverted == !prevStep.inverted && - curStepParamCount == 1 && prevStepParamCount == 1 && - ((curStep.paramValues[0].equals("ellps", "WGS84") && - prevStep.paramValues[0].equals("ellps", "GRS80")) || - (curStep.paramValues[0].equals("ellps", "GRS80") && - prevStep.paramValues[0].equals("ellps", "WGS84")))) { - ++iterCur; - d->steps_.erase(iterPrev, iterCur); - changeDone = true; - break; - } - - if (curStep.name == "helmert" && prevStep.name == "helmert" && - !curStep.inverted && !prevStep.inverted && - curStepParamCount == 3 && - curStepParamCount == prevStepParamCount) { - std::map leftParamsMap; - std::map rightParamsMap; - try { - for (const auto &kv : prevStep.paramValues) { - leftParamsMap[kv.key] = c_locale_stod(kv.value); - } - for (const auto &kv : curStep.paramValues) { - rightParamsMap[kv.key] = c_locale_stod(kv.value); - } - } catch (const std::invalid_argument &) { - break; - } - const std::string x("x"); - const std::string y("y"); - const std::string z("z"); - if (leftParamsMap.find(x) != leftParamsMap.end() && - leftParamsMap.find(y) != leftParamsMap.end() && - leftParamsMap.find(z) != leftParamsMap.end() && - rightParamsMap.find(x) != rightParamsMap.end() && - rightParamsMap.find(y) != rightParamsMap.end() && - rightParamsMap.find(z) != rightParamsMap.end()) { - - const double xSum = leftParamsMap[x] + rightParamsMap[x]; - const double ySum = leftParamsMap[y] + rightParamsMap[y]; - const double zSum = leftParamsMap[z] + rightParamsMap[z]; - if (xSum == 0.0 && ySum == 0.0 && zSum == 0.0) { - ++iterCur; - d->steps_.erase(iterPrev, iterCur); - } else { - prevStep.paramValues[0] = - Step::KeyValue("x", internal::toString(xSum)); - prevStep.paramValues[1] = - Step::KeyValue("y", internal::toString(ySum)); - prevStep.paramValues[2] = - Step::KeyValue("z", internal::toString(zSum)); - - d->steps_.erase(iterCur); - } - changeDone = true; - break; - } - } - - // hermert followed by its inverse is a no-op - if (curStep.name == "helmert" && prevStep.name == "helmert" && - !curStep.inverted && !prevStep.inverted && - curStepParamCount == prevStepParamCount) { - std::set leftParamsSet; - std::set rightParamsSet; - std::map leftParamsMap; - std::map rightParamsMap; - for (const auto &kv : prevStep.paramValues) { - leftParamsSet.insert(kv.key); - leftParamsMap[kv.key] = kv.value; - } - for (const auto &kv : curStep.paramValues) { - rightParamsSet.insert(kv.key); - rightParamsMap[kv.key] = kv.value; - } - if (leftParamsSet == rightParamsSet) { - bool doErase = true; - try { - for (const auto ¶m : leftParamsSet) { - if (param == "convention" || param == "t_epoch" || - param == "t_obs") { - if (leftParamsMap[param] != - rightParamsMap[param]) { - doErase = false; - break; - } - } else if (c_locale_stod(leftParamsMap[param]) != - -c_locale_stod(rightParamsMap[param])) { - doErase = false; - break; - } - } - } catch (const std::invalid_argument &) { - break; - } - if (doErase) { - ++iterCur; - d->steps_.erase(iterPrev, iterCur); - changeDone = true; - break; - } - } - } - - // detect a step and its inverse - if (curStep.inverted != prevStep.inverted && - curStep.name == prevStep.name && - curStepParamCount == prevStepParamCount) { - bool allSame = true; - for (size_t j = 0; j < curStepParamCount; j++) { - if (curStep.paramValues[j] != prevStep.paramValues[j]) { - allSame = false; - break; - } - } - if (allSame) { - ++iterCur; - d->steps_.erase(iterPrev, iterCur); - changeDone = true; - break; - } - } - } - } while (changeDone); - - if (d->steps_.size() > 1 || - (d->steps_.size() == 1 && - (d->steps_.front().inverted || !d->globalParamValues_.empty()))) { - d->appendToResult("+proj=pipeline"); - - for (const auto ¶mValue : d->globalParamValues_) { - d->appendToResult("+"); - d->result_ += paramValue.key; - if (!paramValue.value.empty()) { - d->result_ += "="; - d->result_ += paramValue.value; - } - } - } - - for (const auto &step : d->steps_) { - if (!d->result_.empty()) { - d->appendToResult("+step"); - } - if (step.inverted) { - d->appendToResult("+inv"); - } - if (!step.name.empty()) { - d->appendToResult(step.isInit ? "+init=" : "+proj="); - d->result_ += step.name; - } - for (const auto ¶mValue : step.paramValues) { - d->appendToResult("+"); - d->result_ += paramValue.key; - if (!paramValue.value.empty()) { - d->result_ += "="; - d->result_ += paramValue.value; - } - } - } - return d->result_; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -PROJStringFormatter::Convention PROJStringFormatter::convention() const { - return d->convention_; -} - -// --------------------------------------------------------------------------- - -bool PROJStringFormatter::getUseETMercForTMerc(bool &settingSetOut) const { - settingSetOut = d->useETMercForTMercSet_; - return d->useETMercForTMerc_; -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::setCoordinateOperationOptimizations(bool enable) { - d->coordOperationOptimizations_ = enable; -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::Private::appendToResult(const char *str) { - if (!result_.empty()) { - result_ += " "; - } - result_ += str; -} - -// --------------------------------------------------------------------------- - -static void -PROJStringSyntaxParser(const std::string &projString, std::vector &steps, - std::vector &globalParamValues, - std::string &title, std::string &vunits, - std::string &vto_meter) { - std::string word; - std::istringstream iss(projString, std::istringstream::in); - bool inverted = false; - bool prevWasStep = false; - bool inProj = false; - bool inPipeline = false; - bool prevWasTitle = false; - bool prevWasInit = false; - - while (iss >> word) { - if (word[0] == '+') { - word = word.substr(1); - } else if (prevWasTitle && word.find('=') == std::string::npos) { - title += " "; - title += word; - continue; - } - - prevWasTitle = false; - if (word == "proj=pipeline") { - if (inPipeline) { - throw ParsingException("nested pipeline not supported"); - } - inverted = false; - prevWasStep = false; - inProj = true; - inPipeline = true; - } else if (word == "step") { - if (!inPipeline) { - throw ParsingException("+step found outside pipeline"); - } - inverted = false; - prevWasStep = true; - prevWasInit = false; - } else if (word == "inv") { - if (prevWasStep) { - inverted = true; - } else { - if (steps.empty()) { - throw ParsingException("+inv found at unexpected place"); - } - steps.back().inverted = true; - } - prevWasStep = false; - } else if (starts_with(word, "proj=")) { - auto stepName = word.substr(strlen("proj=")); - if (prevWasInit) { - steps.back() = Step(); - prevWasInit = false; - } else { - steps.push_back(Step()); - } - steps.back().name = stepName; - steps.back().inverted = inverted; - prevWasStep = false; - inProj = true; - } else if (starts_with(word, "init=")) { - if (prevWasInit) { - throw ParsingException("+init= found at unexpected place"); - } - auto initName = word.substr(strlen("init=")); - steps.push_back(Step()); - steps.back().name = initName; - steps.back().isInit = true; - steps.back().inverted = inverted; - prevWasStep = false; - prevWasInit = true; - inProj = true; - } else if (inProj) { - const auto pos = word.find('='); - auto key = word.substr(0, pos); - auto pair = (pos != std::string::npos) - ? Step::KeyValue(key, word.substr(pos + 1)) - : Step::KeyValue(key); - if (steps.empty()) { - globalParamValues.push_back(pair); - } else { - steps.back().paramValues.push_back(pair); - } - prevWasStep = false; - } else if (starts_with(word, "vunits=")) { - vunits = word.substr(strlen("vunits=")); - } else if (starts_with(word, "vto_meter=")) { - vto_meter = word.substr(strlen("vto_meter=")); - } else if (starts_with(word, "title=")) { - title = word.substr(strlen("title=")); - prevWasTitle = true; - } else { - throw ParsingException("Unexpected token: " + word); - } - } -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::ingestPROJString( - const std::string &str) // throw ParsingException -{ - std::vector steps; - std::string title; - std::string vunits; - std::string vto_meter; - PROJStringSyntaxParser(str, steps, d->globalParamValues_, title, vunits, - vto_meter); - d->steps_.insert(d->steps_.end(), steps.begin(), steps.end()); -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::startInversion() { - PROJStringFormatter::Private::InversionStackElt elt; - elt.startIter = d->steps_.end(); - if (elt.startIter != d->steps_.begin()) { - elt.iterValid = true; - --elt.startIter; // point to the last valid element - } else { - elt.iterValid = false; - } - elt.currentInversionState = - !d->inversionStack_.back().currentInversionState; - d->inversionStack_.push_back(elt); -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::stopInversion() { - assert(!d->inversionStack_.empty()); - auto startIter = d->inversionStack_.back().startIter; - if (!d->inversionStack_.back().iterValid) { - startIter = d->steps_.begin(); - } else { - ++startIter; // advance after the last valid element we marked above - } - // Invert the inversion status of the steps between the start point and - // the current end of steps - for (auto iter = startIter; iter != d->steps_.end(); ++iter) { - iter->inverted = !iter->inverted; - } - // And reverse the order of steps in that range as well. - std::reverse(startIter, d->steps_.end()); - d->inversionStack_.pop_back(); -} - -// --------------------------------------------------------------------------- - -bool PROJStringFormatter::isInverted() const { - return d->inversionStack_.back().currentInversionState; -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::Private::addStep() { steps_.emplace_back(Step()); } - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::addStep(const char *stepName) { - d->addStep(); - d->steps_.back().name.assign(stepName); -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::addStep(const std::string &stepName) { - d->addStep(); - d->steps_.back().name = stepName; -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::setCurrentStepInverted(bool inverted) { - assert(!d->steps_.empty()); - d->steps_.back().inverted = inverted; -} - -// --------------------------------------------------------------------------- - -bool PROJStringFormatter::hasParam(const char *paramName) const { - if (!d->steps_.empty()) { - for (const auto ¶mValue : d->steps_.back().paramValues) { - if (paramValue.keyEquals(paramName)) { - return true; - } - } - } - return false; -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::addNoDefs(bool b) { d->addNoDefs_ = b; } - -// --------------------------------------------------------------------------- - -bool PROJStringFormatter::getAddNoDefs() const { return d->addNoDefs_; } - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::addParam(const std::string ¶mName) { - if (d->steps_.empty()) { - d->addStep(); - } - d->steps_.back().paramValues.push_back(Step::KeyValue(paramName)); -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::addParam(const char *paramName, int val) { - addParam(std::string(paramName), val); -} - -void PROJStringFormatter::addParam(const std::string ¶mName, int val) { - addParam(paramName, internal::toString(val)); -} - -// --------------------------------------------------------------------------- - -static std::string formatToString(double val) { - if (std::abs(val * 10 - std::round(val * 10)) < 1e-8) { - // For the purpose of - // https://www.epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::27561 - // Latitude of natural of origin to be properly rounded from 55 grad - // to - // 49.5 deg - val = std::round(val * 10) / 10; - } - return normalizeSerializedString(internal::toString(val)); -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::addParam(const char *paramName, double val) { - addParam(std::string(paramName), val); -} - -void PROJStringFormatter::addParam(const std::string ¶mName, double val) { - addParam(paramName, formatToString(val)); -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::addParam(const char *paramName, - const std::vector &vals) { - std::string paramValue; - for (size_t i = 0; i < vals.size(); ++i) { - if (i > 0) { - paramValue += ','; - } - paramValue += formatToString(vals[i]); - } - addParam(paramName, paramValue); -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::addParam(const char *paramName, const char *val) { - addParam(std::string(paramName), val); -} - -void PROJStringFormatter::addParam(const char *paramName, - const std::string &val) { - addParam(std::string(paramName), val); -} - -void PROJStringFormatter::addParam(const std::string ¶mName, - const char *val) { - addParam(paramName, std::string(val)); -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::addParam(const std::string ¶mName, - const std::string &val) { - if (d->steps_.empty()) { - d->addStep(); - } - d->steps_.back().paramValues.push_back(Step::KeyValue(paramName, val)); -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::setTOWGS84Parameters( - const std::vector ¶ms) { - d->toWGS84Parameters_ = params; -} - -// --------------------------------------------------------------------------- - -const std::vector &PROJStringFormatter::getTOWGS84Parameters() const { - return d->toWGS84Parameters_; -} - -// --------------------------------------------------------------------------- - -std::set PROJStringFormatter::getUsedGridNames() const { - std::set res; - for (const auto &step : d->steps_) { - for (const auto ¶m : step.paramValues) { - if (param.keyEquals("grids")) { - res.insert(param.value); - } - } - } - return res; -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::setVDatumExtension(const std::string &filename) { - d->vDatumExtension_ = filename; -} - -// --------------------------------------------------------------------------- - -const std::string &PROJStringFormatter::getVDatumExtension() const { - return d->vDatumExtension_; -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::setHDatumExtension(const std::string &filename) { - d->hDatumExtension_ = filename; -} - -// --------------------------------------------------------------------------- - -const std::string &PROJStringFormatter::getHDatumExtension() const { - return d->hDatumExtension_; -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::setOmitProjLongLatIfPossible(bool omit) { - assert(d->omitProjLongLatIfPossible_ ^ omit); - d->omitProjLongLatIfPossible_ = omit; -} - -// --------------------------------------------------------------------------- - -bool PROJStringFormatter::omitProjLongLatIfPossible() const { - return d->omitProjLongLatIfPossible_; -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::setOmitZUnitConversion(bool omit) { - assert(d->omitZUnitConversion_ ^ omit); - d->omitZUnitConversion_ = omit; -} - -// --------------------------------------------------------------------------- - -bool PROJStringFormatter::omitZUnitConversion() const { - return d->omitZUnitConversion_; -} - -// --------------------------------------------------------------------------- - -const DatabaseContextPtr &PROJStringFormatter::databaseContext() const { - return d->dbContext_; -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -struct PROJStringParser::Private { - DatabaseContextPtr dbContext_{}; - bool usePROJ4InitRules_ = false; - std::vector warningList_{}; - - std::string projString_{}; - - std::vector steps_{}; - std::vector globalParamValues_{}; - std::string title_{}; - - template - // cppcheck-suppress functionStatic - bool hasParamValue(const Step &step, const T key) { - for (const auto &pair : globalParamValues_) { - if (ci_equal(pair.key, key)) { - return true; - } - } - for (const auto &pair : step.paramValues) { - if (ci_equal(pair.key, key)) { - return true; - } - } - return false; - } - - template - // cppcheck-suppress functionStatic - const std::string &getParamValue(const Step &step, const T key) { - for (const auto &pair : globalParamValues_) { - if (ci_equal(pair.key, key)) { - return pair.value; - } - } - for (const auto &pair : step.paramValues) { - if (ci_equal(pair.key, key)) { - return pair.value; - } - } - return emptyString; - } - - // cppcheck-suppress functionStatic - const std::string &getParamValueK(const Step &step) { - for (const auto &pair : step.paramValues) { - if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) { - return pair.value; - } - } - return emptyString; - } - - // cppcheck-suppress functionStatic - std::string guessBodyName(double a); - - PrimeMeridianNNPtr buildPrimeMeridian(const Step &step); - GeodeticReferenceFrameNNPtr buildDatum(const Step &step, - const std::string &title); - GeographicCRSNNPtr buildGeographicCRS(int iStep, int iUnitConvert, - int iAxisSwap, bool ignoreVUnits, - bool ignorePROJAxis); - GeodeticCRSNNPtr buildGeocentricCRS(int iStep, int iUnitConvert); - CRSNNPtr buildProjectedCRS(int iStep, GeographicCRSNNPtr geogCRS, - int iUnitConvert, int iAxisSwap); - CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs); - UnitOfMeasure buildUnit(const Step &step, const std::string &unitsParamName, - const std::string &toMeterParamName); - CoordinateOperationNNPtr buildHelmertTransformation( - int iStep, int iFirstAxisSwap = -1, int iFirstUnitConvert = -1, - int iFirstGeogStep = -1, int iSecondGeogStep = -1, - int iSecondAxisSwap = -1, int iSecondUnitConvert = -1); - CoordinateOperationNNPtr buildMolodenskyTransformation( - int iStep, int iFirstAxisSwap = -1, int iFirstUnitConvert = -1, - int iFirstGeogStep = -1, int iSecondGeogStep = -1, - int iSecondAxisSwap = -1, int iSecondUnitConvert = -1); - - enum class AxisType { REGULAR, NORTH_POLE, SOUTH_POLE }; - - std::vector - processAxisSwap(const Step &step, const UnitOfMeasure &unit, int iAxisSwap, - AxisType axisType, bool ignorePROJAxis); - - EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert, - int iAxisSwap, bool ignoreVUnits, - bool ignorePROJAxis); -}; - -// --------------------------------------------------------------------------- - -PROJStringParser::PROJStringParser() : d(internal::make_unique()) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -PROJStringParser::~PROJStringParser() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Attach a database context, to allow queries in it if needed. - */ -PROJStringParser & -PROJStringParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) { - d->dbContext_ = dbContext; - return *this; -} - -// --------------------------------------------------------------------------- - -/** \brief Set how init=epsg:XXXX syntax should be interpreted. - * - * @param enable When set to true, - * init=epsg:XXXX syntax will be allowed and will be interpreted according to - * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude - * order and will expect/output coordinates in radians. ProjectedCRS will have - * easting, northing axis order (except the ones with Transverse Mercator South - * Orientated projection). - */ -PROJStringParser &PROJStringParser::setUsePROJ4InitRules(bool enable) { - d->usePROJ4InitRules_ = enable; - return *this; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the list of warnings found during parsing. - */ -std::vector PROJStringParser::warningList() const { - return d->warningList_; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -// --------------------------------------------------------------------------- - -static const struct LinearUnitDesc { - const char *projName; - const char *convToMeter; - const char *name; - int epsgCode; -} linearUnitDescs[] = { - {"mm", "0.001", "millimetre", 1025}, - {"cm", "0.01", "centimetre", 1033}, - {"m", "1.0", "metre", 9001}, - {"meter", "1.0", "metre", 9001}, // alternative - {"metre", "1.0", "metre", 9001}, // alternative - {"ft", "0.3048", "foot", 9002}, - {"us-ft", "0.3048006096012192", "US survey foot", 9003}, - {"fath", "1.8288", "fathom", 9014}, - {"kmi", "1852", "nautical mile", 9030}, - {"us-ch", "20.11684023368047", "US survey chain", 9033}, - {"us-mi", "1609.347218694437", "US survey mile", 9035}, - {"km", "1000.0", "kilometre", 9036}, - {"ind-ft", "0.30479841", "Indian foot (1937)", 9081}, - {"ind-yd", "0.91439523", "Indian yard (1937)", 9085}, - {"mi", "1609.344", "Statute mile", 9093}, - {"yd", "0.9144", "yard", 9096}, - {"ch", "20.1168", "chain", 9097}, - {"link", "0.201168", "link", 9098}, - {"dm", "0.1", "decimetre", 0}, // no EPSG equivalent - {"in", "0.0254", "inch", 0}, // no EPSG equivalent - {"us-in", "0.025400050800101", "US survey inch", 0}, // no EPSG equivalent - {"us-yd", "0.914401828803658", "US survey yard", 0}, // no EPSG equivalent - {"ind-ch", "20.11669506", "Indian chain", 0}, // no EPSG equivalent -}; - -static const LinearUnitDesc *getLinearUnits(const std::string &projName) { - for (const auto &desc : linearUnitDescs) { - if (desc.projName == projName) - return &desc; - } - return nullptr; -} - -static const LinearUnitDesc *getLinearUnits(double toMeter) { - for (const auto &desc : linearUnitDescs) { - if (std::fabs(c_locale_stod(desc.convToMeter) - toMeter) < - 1e-10 * toMeter) { - return &desc; - } - } - return nullptr; -} - -// --------------------------------------------------------------------------- - -static UnitOfMeasure _buildUnit(const LinearUnitDesc *unitsMatch) { - std::string unitsCode; - if (unitsMatch->epsgCode) { - std::ostringstream buffer; - buffer.imbue(std::locale::classic()); - buffer << unitsMatch->epsgCode; - unitsCode = buffer.str(); - } - return UnitOfMeasure( - unitsMatch->name, c_locale_stod(unitsMatch->convToMeter), - UnitOfMeasure::Type::LINEAR, - unitsMatch->epsgCode ? Identifier::EPSG : std::string(), unitsCode); -} - -// --------------------------------------------------------------------------- - -static UnitOfMeasure _buildUnit(double to_meter_value) { - // TODO: look-up in EPSG catalog - return UnitOfMeasure("unknown", to_meter_value, - UnitOfMeasure::Type::LINEAR); -} - -// --------------------------------------------------------------------------- - -UnitOfMeasure -PROJStringParser::Private::buildUnit(const Step &step, - const std::string &unitsParamName, - const std::string &toMeterParamName) { - UnitOfMeasure unit = UnitOfMeasure::METRE; - const LinearUnitDesc *unitsMatch = nullptr; - const auto &projUnits = getParamValue(step, unitsParamName); - if (!projUnits.empty()) { - unitsMatch = getLinearUnits(projUnits); - if (unitsMatch == nullptr) { - throw ParsingException("unhandled " + unitsParamName + "=" + - projUnits); - } - } - - const auto &toMeter = getParamValue(step, toMeterParamName); - if (!toMeter.empty()) { - double to_meter_value; - try { - to_meter_value = c_locale_stod(toMeter); - } catch (const std::invalid_argument &) { - throw ParsingException("invalid value for " + toMeterParamName); - } - unitsMatch = getLinearUnits(to_meter_value); - if (unitsMatch == nullptr) { - unit = _buildUnit(to_meter_value); - } - } - - if (unitsMatch) { - unit = _buildUnit(unitsMatch); - } - - return unit; -} - -// --------------------------------------------------------------------------- - -static const struct DatumDesc { - const char *projName; - const char *gcsName; - int gcsCode; - const char *datumName; - int datumCode; - const char *ellipsoidName; - int ellipsoidCode; - double a; - double rf; -} datumDescs[] = { - {"GGRS87", "GGRS87", 4121, "Greek Geodetic Reference System 1987", 6121, - "GRS 1980", 7019, 6378137, 298.257222101}, - {"postdam", "DHDN", 4314, "Deutsches Hauptdreiecksnetz", 6314, - "Bessel 1841", 7004, 6377397.155, 299.1528128}, - {"carthage", "Carthage", 4223, "Carthage", 6223, "Clarke 1880 (IGN)", 7011, - 6378249.2, 293.4660213}, - {"hermannskogel", "MGI", 4312, "Militar-Geographische Institut", 6312, - "Bessel 1841", 7004, 6377397.155, 299.1528128}, - {"ire65", "TM65", 4299, "TM65", 6299, "Airy Modified 1849", 7002, - 6377340.189, 299.3249646}, - {"nzgd49", "NZGD49", 4272, "New Zealand Geodetic Datum 1949", 6272, - "International 1924", 7022, 6378388, 297}, - {"OSGB36", "OSGB 1936", 4277, "OSGB 1936", 6277, "Airy 1830", 7001, - 6377563.396, 299.3249646}, -}; - -// --------------------------------------------------------------------------- - -static bool isGeographicStep(const std::string &name) { - return name == "longlat" || name == "lonlat" || name == "latlong" || - name == "latlon"; -} - -// --------------------------------------------------------------------------- - -static bool isGeocentricStep(const std::string &name) { - return name == "geocent" || name == "cart"; -} - -// --------------------------------------------------------------------------- - -static bool isProjectedStep(const std::string &name) { - if (name == "etmerc" || name == "utm" || - !getMappingsFromPROJName(name).empty()) { - return true; - } - // IMPROVE ME: have a better way of distinguishing projections from - // other - // transformations. - if (name == "pipeline" || name == "geoc" || name == "deformation" || - name == "helmert" || name == "hgridshift" || name == "molodensky" || - name == "vgridshit") { - return false; - } - const auto *operations = proj_list_operations(); - for (int i = 0; operations[i].id != nullptr; ++i) { - if (name == operations[i].id) { - return true; - } - } - return false; -} - -// --------------------------------------------------------------------------- - -static PropertyMap createMapWithUnknownName() { - return PropertyMap().set(common::IdentifiedObject::NAME_KEY, "unknown"); -} - -// --------------------------------------------------------------------------- - -PrimeMeridianNNPtr -PROJStringParser::Private::buildPrimeMeridian(const Step &step) { - - PrimeMeridianNNPtr pm = PrimeMeridian::GREENWICH; - const auto &pmStr = getParamValue(step, "pm"); - if (!pmStr.empty()) { - char *end; - double pmValue = dmstor(pmStr.c_str(), &end) * RAD_TO_DEG; - if (pmValue != HUGE_VAL && *end == '\0') { - pm = PrimeMeridian::create(createMapWithUnknownName(), - Angle(pmValue)); - } else { - bool found = false; - if (pmStr == "paris") { - found = true; - pm = PrimeMeridian::PARIS; - } - auto proj_prime_meridians = proj_list_prime_meridians(); - for (int i = 0; !found && proj_prime_meridians[i].id != nullptr; - i++) { - if (pmStr == proj_prime_meridians[i].id) { - found = true; - std::string name = static_cast(::toupper(pmStr[0])) + - pmStr.substr(1); - pmValue = dmstor(proj_prime_meridians[i].defn, nullptr) * - RAD_TO_DEG; - pm = PrimeMeridian::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, name), - Angle(pmValue)); - break; - } - } - if (!found) { - throw ParsingException("unknown pm " + pmStr); - } - } - } - return pm; -} - -// --------------------------------------------------------------------------- - -std::string PROJStringParser::Private::guessBodyName(double a) { - return Ellipsoid::guessBodyName(dbContext_, a); -} - -// --------------------------------------------------------------------------- - -GeodeticReferenceFrameNNPtr -PROJStringParser::Private::buildDatum(const Step &step, - const std::string &title) { - - const auto &ellpsStr = getParamValue(step, "ellps"); - const auto &datumStr = getParamValue(step, "datum"); - const auto &RStr = getParamValue(step, "R"); - const auto &aStr = getParamValue(step, "a"); - const auto &bStr = getParamValue(step, "b"); - const auto &rfStr = getParamValue(step, "rf"); - const auto &fStr = getParamValue(step, "f"); - const auto &esStr = getParamValue(step, "es"); - const auto &eStr = getParamValue(step, "e"); - double a = -1.0; - double b = -1.0; - double rf = -1.0; - const util::optional optionalEmptyString{}; - const bool numericParamPresent = - !RStr.empty() || !aStr.empty() || !bStr.empty() || !rfStr.empty() || - !fStr.empty() || !esStr.empty() || !eStr.empty(); - - PrimeMeridianNNPtr pm(buildPrimeMeridian(step)); - PropertyMap grfMap; - - // It is arguable that we allow the prime meridian of a datum defined by - // its name to be overriden, but this is found at least in a regression test - // of GDAL. So let's keep the ellipsoid part of the datum in that case and - // use the specified prime meridian. - const auto overridePmIfNeeded = - [&pm](const GeodeticReferenceFrameNNPtr &grf) { - if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) { - return grf; - } else { - return GeodeticReferenceFrame::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, - "Unknown based on " + - grf->ellipsoid()->nameStr() + - " ellipsoid"), - grf->ellipsoid(), grf->anchorDefinition(), pm); - } - }; - - // R take precedence - if (!RStr.empty()) { - double R; - try { - R = c_locale_stod(RStr); - } catch (const std::invalid_argument &) { - throw ParsingException("Invalid R value"); - } - auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(), - Length(R), guessBodyName(R)); - return GeodeticReferenceFrame::create( - grfMap.set(IdentifiedObject::NAME_KEY, - title.empty() ? "unknown" : title.c_str()), - ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); - } - - if (!datumStr.empty()) { - auto l_datum = [&datumStr, &overridePmIfNeeded, &grfMap, - &optionalEmptyString, &pm]() { - if (datumStr == "WGS84") { - return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326); - } else if (datumStr == "NAD83") { - return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6269); - } else if (datumStr == "NAD27") { - return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6267); - } else { - - for (const auto &datumDesc : datumDescs) { - if (datumStr == datumDesc.projName) { - (void)datumDesc.gcsName; // to please cppcheck - (void)datumDesc.gcsCode; // to please cppcheck - auto ellipsoid = Ellipsoid::createFlattenedSphere( - grfMap - .set(IdentifiedObject::NAME_KEY, - datumDesc.ellipsoidName) - .set(Identifier::CODESPACE_KEY, - Identifier::EPSG) - .set(Identifier::CODE_KEY, - datumDesc.ellipsoidCode), - Length(datumDesc.a), Scale(datumDesc.rf)); - return GeodeticReferenceFrame::create( - grfMap - .set(IdentifiedObject::NAME_KEY, - datumDesc.datumName) - .set(Identifier::CODESPACE_KEY, - Identifier::EPSG) - .set(Identifier::CODE_KEY, datumDesc.datumCode), - ellipsoid, optionalEmptyString, pm); - } - } - } - throw ParsingException("unknown datum " + datumStr); - }(); - if (!numericParamPresent) { - return l_datum; - } - a = l_datum->ellipsoid()->semiMajorAxis().getSIValue(); - rf = l_datum->ellipsoid()->computedInverseFlattening(); - } - - else if (!ellpsStr.empty()) { - auto l_datum = [&ellpsStr, &title, &grfMap, &optionalEmptyString, - &pm]() { - if (ellpsStr == "WGS84") { - return GeodeticReferenceFrame::create( - grfMap.set(IdentifiedObject::NAME_KEY, - title.empty() - ? "Unknown based on WGS84 ellipsoid" - : title.c_str()), - Ellipsoid::WGS84, optionalEmptyString, pm); - } else if (ellpsStr == "GRS80") { - return GeodeticReferenceFrame::create( - grfMap.set(IdentifiedObject::NAME_KEY, - title.empty() - ? "Unknown based on GRS80 ellipsoid" - : title.c_str()), - Ellipsoid::GRS1980, optionalEmptyString, pm); - } else { - auto proj_ellps = proj_list_ellps(); - for (int i = 0; proj_ellps[i].id != nullptr; i++) { - if (ellpsStr == proj_ellps[i].id) { - assert(strncmp(proj_ellps[i].major, "a=", 2) == 0); - const double a_iter = - c_locale_stod(proj_ellps[i].major + 2); - EllipsoidPtr ellipsoid; - PropertyMap ellpsMap; - if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) { - const double b_iter = - c_locale_stod(proj_ellps[i].ell + 2); - ellipsoid = - Ellipsoid::createTwoAxis( - ellpsMap.set(IdentifiedObject::NAME_KEY, - proj_ellps[i].name), - Length(a_iter), Length(b_iter)) - .as_nullable(); - } else { - assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0); - const double rf_iter = - c_locale_stod(proj_ellps[i].ell + 3); - ellipsoid = - Ellipsoid::createFlattenedSphere( - ellpsMap.set(IdentifiedObject::NAME_KEY, - proj_ellps[i].name), - Length(a_iter), Scale(rf_iter)) - .as_nullable(); - } - return GeodeticReferenceFrame::create( - grfMap.set(IdentifiedObject::NAME_KEY, - title.empty() - ? std::string("Unknown based on ") + - proj_ellps[i].name + - " ellipsoid" - : title), - NN_NO_CHECK(ellipsoid), optionalEmptyString, pm); - } - } - throw ParsingException("unknown ellipsoid " + ellpsStr); - } - }(); - if (!numericParamPresent) { - return l_datum; - } - a = l_datum->ellipsoid()->semiMajorAxis().getSIValue(); - if (l_datum->ellipsoid()->semiMinorAxis().has_value()) { - b = l_datum->ellipsoid()->semiMinorAxis()->getSIValue(); - } else { - rf = l_datum->ellipsoid()->computedInverseFlattening(); - } - } - - if (!aStr.empty()) { - try { - a = c_locale_stod(aStr); - } catch (const std::invalid_argument &) { - throw ParsingException("Invalid a value"); - } - } - - if (a > 0 && (b > 0 || !bStr.empty())) { - if (!bStr.empty()) { - try { - b = c_locale_stod(bStr); - } catch (const std::invalid_argument &) { - throw ParsingException("Invalid b value"); - } - } - auto ellipsoid = - Ellipsoid::createTwoAxis(createMapWithUnknownName(), Length(a), - Length(b), guessBodyName(a)) - ->identify(); - return GeodeticReferenceFrame::create( - grfMap.set(IdentifiedObject::NAME_KEY, - title.empty() ? "unknown" : title.c_str()), - ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); - } - - else if (a > 0 && (rf >= 0 || !rfStr.empty())) { - if (!rfStr.empty()) { - try { - rf = c_locale_stod(rfStr); - } catch (const std::invalid_argument &) { - throw ParsingException("Invalid rf value"); - } - } - auto ellipsoid = Ellipsoid::createFlattenedSphere( - createMapWithUnknownName(), Length(a), Scale(rf), - guessBodyName(a)) - ->identify(); - return GeodeticReferenceFrame::create( - grfMap.set(IdentifiedObject::NAME_KEY, - title.empty() ? "unknown" : title.c_str()), - ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); - } - - else if (a > 0 && !fStr.empty()) { - double f; - try { - f = c_locale_stod(fStr); - } catch (const std::invalid_argument &) { - throw ParsingException("Invalid f value"); - } - auto ellipsoid = Ellipsoid::createFlattenedSphere( - createMapWithUnknownName(), Length(a), - Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a)) - ->identify(); - return GeodeticReferenceFrame::create( - grfMap.set(IdentifiedObject::NAME_KEY, - title.empty() ? "unknown" : title.c_str()), - ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); - } - - else if (a > 0 && !eStr.empty()) { - double e; - try { - e = c_locale_stod(eStr); - } catch (const std::invalid_argument &) { - throw ParsingException("Invalid e value"); - } - double alpha = asin(e); /* angular eccentricity */ - double f = 1 - cos(alpha); /* = 1 - sqrt (1 - es); */ - auto ellipsoid = Ellipsoid::createFlattenedSphere( - createMapWithUnknownName(), Length(a), - Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a)) - ->identify(); - return GeodeticReferenceFrame::create( - grfMap.set(IdentifiedObject::NAME_KEY, - title.empty() ? "unknown" : title.c_str()), - ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); - } - - else if (a > 0 && !esStr.empty()) { - double es; - try { - es = c_locale_stod(esStr); - } catch (const std::invalid_argument &) { - throw ParsingException("Invalid es value"); - } - double f = 1 - sqrt(1 - es); - auto ellipsoid = Ellipsoid::createFlattenedSphere( - createMapWithUnknownName(), Length(a), - Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a)) - ->identify(); - return GeodeticReferenceFrame::create( - grfMap.set(IdentifiedObject::NAME_KEY, - title.empty() ? "unknown" : title.c_str()), - ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); - } - - // If only a is specified, create a sphere - if (a > 0 && bStr.empty() && rfStr.empty() && eStr.empty() && - esStr.empty()) { - auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(), - Length(a), guessBodyName(a)); - return GeodeticReferenceFrame::create( - grfMap.set(IdentifiedObject::NAME_KEY, - title.empty() ? "unknown" : title.c_str()), - ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); - } - - if (!bStr.empty() && aStr.empty()) { - throw ParsingException("b found, but a missing"); - } - - if (!rfStr.empty() && aStr.empty()) { - throw ParsingException("rf found, but a missing"); - } - - if (!fStr.empty() && aStr.empty()) { - throw ParsingException("f found, but a missing"); - } - - if (!eStr.empty() && aStr.empty()) { - throw ParsingException("e found, but a missing"); - } - - if (!esStr.empty() && aStr.empty()) { - throw ParsingException("es found, but a missing"); - } - - return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326); -} - -// --------------------------------------------------------------------------- - -static const MeridianPtr nullMeridian{}; - -static CoordinateSystemAxisNNPtr -createAxis(const std::string &name, const std::string &abbreviation, - const AxisDirection &direction, const common::UnitOfMeasure &unit, - const MeridianPtr &meridian = nullMeridian) { - return CoordinateSystemAxis::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, name), abbreviation, - direction, unit, meridian); -} - -std::vector -PROJStringParser::Private::processAxisSwap(const Step &step, - const UnitOfMeasure &unit, - int iAxisSwap, AxisType axisType, - bool ignorePROJAxis) { - assert(iAxisSwap < 0 || ci_equal(steps_[iAxisSwap].name, "axisswap")); - - const bool isGeographic = unit.type() == UnitOfMeasure::Type::ANGULAR; - const auto &eastName = - isGeographic ? AxisName::Longitude : AxisName::Easting; - const auto &eastAbbev = - isGeographic ? AxisAbbreviation::lon : AxisAbbreviation::E; - const auto &eastDir = isGeographic - ? AxisDirection::EAST - : (axisType == AxisType::NORTH_POLE) - ? AxisDirection::SOUTH - : (axisType == AxisType::SOUTH_POLE) - ? AxisDirection::NORTH - : AxisDirection::EAST; - CoordinateSystemAxisNNPtr east = createAxis( - eastName, eastAbbev, eastDir, unit, - (!isGeographic && - (axisType == AxisType::NORTH_POLE || axisType == AxisType::SOUTH_POLE)) - ? Meridian::create(Angle(90, UnitOfMeasure::DEGREE)).as_nullable() - : nullMeridian); - - const auto &northName = - isGeographic ? AxisName::Latitude : AxisName::Northing; - const auto &northAbbev = - isGeographic ? AxisAbbreviation::lat : AxisAbbreviation::N; - const auto &northDir = isGeographic - ? AxisDirection::NORTH - : (axisType == AxisType::NORTH_POLE) - ? AxisDirection::SOUTH - : (axisType == AxisType::SOUTH_POLE) - ? AxisDirection::NORTH - : AxisDirection::NORTH; - CoordinateSystemAxisNNPtr north = createAxis( - northName, northAbbev, northDir, unit, - (!isGeographic && axisType == AxisType::NORTH_POLE) - ? Meridian::create(Angle(180, UnitOfMeasure::DEGREE)).as_nullable() - : (!isGeographic && axisType == AxisType::SOUTH_POLE) - ? Meridian::create(Angle(0, UnitOfMeasure::DEGREE)) - .as_nullable() - : nullMeridian); - - CoordinateSystemAxisNNPtr west = - createAxis(isGeographic ? AxisName::Longitude : AxisName::Westing, - isGeographic ? AxisAbbreviation::lon : std::string(), - AxisDirection::WEST, unit); - - CoordinateSystemAxisNNPtr south = - createAxis(isGeographic ? AxisName::Latitude : AxisName::Southing, - isGeographic ? AxisAbbreviation::lat : std::string(), - AxisDirection::SOUTH, unit); - - std::vector axis{east, north}; - - const auto &axisStr = getParamValue(step, "axis"); - if (!ignorePROJAxis && !axisStr.empty()) { - if (axisStr.size() == 3) { - for (int i = 0; i < 2; i++) { - if (axisStr[i] == 'n') { - axis[i] = north; - } else if (axisStr[i] == 's') { - axis[i] = south; - } else if (axisStr[i] == 'e') { - axis[i] = east; - } else if (axisStr[i] == 'w') { - axis[i] = west; - } else { - throw ParsingException("Unhandled axis=" + axisStr); - } - } - } else { - throw ParsingException("Unhandled axis=" + axisStr); - } - } else if (iAxisSwap >= 0) { - const auto &stepAxisSwap = steps_[iAxisSwap]; - const auto &orderStr = getParamValue(stepAxisSwap, "order"); - auto orderTab = split(orderStr, ','); - if (orderTab.size() != 2) { - throw ParsingException("Unhandled order=" + orderStr); - } - if (stepAxisSwap.inverted) { - throw ParsingException("Unhandled +inv for +proj=axisswap"); - } - - for (size_t i = 0; i < 2; i++) { - if (orderTab[i] == "1") { - axis[i] = east; - } else if (orderTab[i] == "-1") { - axis[i] = west; - } else if (orderTab[i] == "2") { - axis[i] = north; - } else if (orderTab[i] == "-2") { - axis[i] = south; - } else { - throw ParsingException("Unhandled order=" + orderStr); - } - } - } - return axis; -} - -// --------------------------------------------------------------------------- - -EllipsoidalCSNNPtr -PROJStringParser::Private::buildEllipsoidalCS(int iStep, int iUnitConvert, - int iAxisSwap, bool ignoreVUnits, - bool ignorePROJAxis) { - const auto &step = steps_[iStep]; - assert(iUnitConvert < 0 || - ci_equal(steps_[iUnitConvert].name, "unitconvert")); - - UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE; - if (iUnitConvert >= 0) { - const auto &stepUnitConvert = steps_[iUnitConvert]; - const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); - const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); - if (stepUnitConvert.inverted) { - std::swap(xy_in, xy_out); - } - if (iUnitConvert < iStep) { - std::swap(xy_in, xy_out); - } - if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" || - (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) { - throw ParsingException("unhandled values for xy_in and/or xy_out"); - } - if (*xy_out == "rad") { - angularUnit = UnitOfMeasure::RADIAN; - } else if (*xy_out == "grad") { - angularUnit = UnitOfMeasure::GRAD; - } - } - - std::vector axis = processAxisSwap( - step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis); - CoordinateSystemAxisNNPtr up = CoordinateSystemAxis::create( - util::PropertyMap().set(IdentifiedObject::NAME_KEY, - AxisName::Ellipsoidal_height), - AxisAbbreviation::h, AxisDirection::UP, - buildUnit(step, "vunits", "vto_meter")); - - return (!ignoreVUnits && !hasParamValue(step, "geoidgrids") && - (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter"))) - ? EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1], up) - : EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1]); -} - -// --------------------------------------------------------------------------- - -static double getNumericValue(const std::string ¶mValue, - bool *pHasError = nullptr) { - try { - double value = c_locale_stod(paramValue); - if (pHasError) - *pHasError = false; - return value; - } catch (const std::invalid_argument &) { - if (pHasError) - *pHasError = true; - return 0.0; - } -} - -// --------------------------------------------------------------------------- - -GeographicCRSNNPtr -PROJStringParser::Private::buildGeographicCRS(int iStep, int iUnitConvert, - int iAxisSwap, bool ignoreVUnits, - bool ignorePROJAxis) { - const auto &step = steps_[iStep]; - - const bool l_isGeographicStep = isGeographicStep(step.name); - const auto &title = l_isGeographicStep ? title_ : emptyString; - - auto datum = buildDatum(step, title); - - auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, - title.empty() ? "unknown" : title); - if (l_isGeographicStep && - (hasParamValue(step, "wktext") || - hasParamValue(step, "lon_wrap") | hasParamValue(step, "geoc") || - getNumericValue(getParamValue(step, "lon_0")) != 0.0)) { - props.set("EXTENSION_PROJ4", projString_); - } - - return GeographicCRS::create( - props, datum, buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, - ignoreVUnits, ignorePROJAxis)); -} - -// --------------------------------------------------------------------------- - -GeodeticCRSNNPtr -PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) { - const auto &step = steps_[iStep]; - - assert(isGeocentricStep(step.name)); - assert(iUnitConvert < 0 || - ci_equal(steps_[iUnitConvert].name, "unitconvert")); - - const auto &title = title_; - - auto datum = buildDatum(step, title); - - UnitOfMeasure unit = UnitOfMeasure::METRE; - if (iUnitConvert >= 0) { - const auto &stepUnitConvert = steps_[iUnitConvert]; - const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); - const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); - const std::string *z_in = &getParamValue(stepUnitConvert, "z_in"); - const std::string *z_out = &getParamValue(stepUnitConvert, "z_out"); - if (stepUnitConvert.inverted) { - std::swap(xy_in, xy_out); - std::swap(z_in, z_out); - } - if (xy_in->empty() || xy_out->empty() || *xy_in != "m" || - *z_in != "m" || *xy_out != *z_out) { - throw ParsingException( - "unhandled values for xy_in, z_in, xy_out or z_out"); - } - - const LinearUnitDesc *unitsMatch = nullptr; - try { - double to_meter_value = c_locale_stod(*xy_out); - unitsMatch = getLinearUnits(to_meter_value); - if (unitsMatch == nullptr) { - unit = _buildUnit(to_meter_value); - } - } catch (const std::invalid_argument &) { - unitsMatch = getLinearUnits(*xy_out); - if (!unitsMatch) { - throw ParsingException( - "unhandled values for xy_in, z_in, xy_out or z_out"); - } - unit = _buildUnit(unitsMatch); - } - } - - auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, - title.empty() ? "unknown" : title); - if (hasParamValue(step, "wktext")) { - props.set("EXTENSION_PROJ4", projString_); - } - - auto cs = CartesianCS::createGeocentric(unit); - return GeodeticCRS::create(props, datum, cs); -} - -// --------------------------------------------------------------------------- - -CRSNNPtr -PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep, - CRSNNPtr crs) { - const auto &step = steps_[iStep]; - const auto &towgs84 = getParamValue(step, "towgs84"); - if (!towgs84.empty()) { - std::vector towgs84Values; - const auto tokens = split(towgs84, ','); - for (const auto &str : tokens) { - try { - towgs84Values.push_back(c_locale_stod(str)); - } catch (const std::invalid_argument &) { - throw ParsingException("Non numerical value in towgs84 clause"); - } - } - crs = BoundCRS::createFromTOWGS84(crs, towgs84Values); - } - - const auto &nadgrids = getParamValue(step, "nadgrids"); - if (!nadgrids.empty()) { - crs = BoundCRS::createFromNadgrids(crs, nadgrids); - } - - const auto &geoidgrids = getParamValue(step, "geoidgrids"); - if (!geoidgrids.empty()) { - auto vdatum = - VerticalReferenceFrame::create(createMapWithUnknownName()); - - const UnitOfMeasure unit = buildUnit(step, "vunits", "vto_meter"); - - auto vcrs = - VerticalCRS::create(createMapWithUnknownName(), vdatum, - VerticalCS::createGravityRelatedHeight(unit)); - - auto transformation = - Transformation::createGravityRelatedHeightToGeographic3D( - PropertyMap().set(IdentifiedObject::NAME_KEY, - "unknown to WGS84 ellipsoidal height"), - crs, GeographicCRS::EPSG_4979, geoidgrids, - std::vector()); - auto boundvcrs = - BoundCRS::create(vcrs, GeographicCRS::EPSG_4979, transformation); - - crs = CompoundCRS::create(createMapWithUnknownName(), - std::vector{crs, boundvcrs}); - } - - return crs; -} - -// --------------------------------------------------------------------------- - -static double getAngularValue(const std::string ¶mValue, - bool *pHasError = nullptr) { - char *endptr = nullptr; - double value = dmstor(paramValue.c_str(), &endptr) * RAD_TO_DEG; - if (value == HUGE_VAL || endptr != paramValue.c_str() + paramValue.size()) { - if (pHasError) - *pHasError = true; - return 0.0; - } - if (pHasError) - *pHasError = false; - return value; -} - -// --------------------------------------------------------------------------- - -CRSNNPtr PROJStringParser::Private::buildProjectedCRS( - int iStep, GeographicCRSNNPtr geogCRS, int iUnitConvert, int iAxisSwap) { - auto &step = steps_[iStep]; - auto mappings = getMappingsFromPROJName(step.name); - const MethodMapping *mapping = mappings.empty() ? nullptr : mappings[0]; - - assert(isProjectedStep(step.name)); - assert(iUnitConvert < 0 || - ci_equal(steps_[iUnitConvert].name, "unitconvert")); - - const auto &title = title_; - - if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo( - geogCRS->primeMeridian()->longitude(), - util::IComparable::Criterion::EQUIVALENT)) { - throw ParsingException("inconsistent pm values between projectedCRS " - "and its base geographicalCRS"); - } - - auto axisType = AxisType::REGULAR; - - if (step.name == "tmerc" && - ((getParamValue(step, "axis") == "wsu" && iAxisSwap < 0) || - (iAxisSwap > 0 && - getParamValue(steps_[iAxisSwap], "order") == "-1,-2"))) { - mapping = - getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED); - } else if (step.name == "etmerc") { - mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR); - } else if (step.name == "lcc") { - const auto &lat_0 = getParamValue(step, "lat_0"); - const auto &lat_1 = getParamValue(step, "lat_1"); - const auto &lat_2 = getParamValue(step, "lat_2"); - const auto &k = getParamValueK(step); - if (lat_2.empty() && !lat_0.empty() && !lat_1.empty() && - getAngularValue(lat_0) == getAngularValue(lat_1)) { - mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); - } else if (!k.empty() && getNumericValue(k) != 1.0) { - mapping = getMapping( - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN); - } else { - mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); - } - } else if (step.name == "aeqd" && hasParamValue(step, "guam")) { - mapping = getMapping(EPSG_CODE_METHOD_GUAM_PROJECTION); - } else if (step.name == "cea" && !geogCRS->ellipsoid()->isSphere()) { - mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA); - } else if (step.name == "geos" && getParamValue(step, "sweep") == "x") { - mapping = - getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X); - } else if (step.name == "geos") { - mapping = - getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y); - } else if (step.name == "omerc") { - if (hasParamValue(step, "no_rot")) { - mapping = nullptr; - } else if (hasParamValue(step, "no_uoff") || - hasParamValue(step, "no_off")) { - mapping = - getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A); - } else if (hasParamValue(step, "lat_1") && - hasParamValue(step, "lon_1") && - hasParamValue(step, "lat_2") && - hasParamValue(step, "lon_2")) { - mapping = getMapping( - PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN); - } else { - mapping = - getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B); - } - } else if (step.name == "somerc") { - mapping = - getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B); - step.paramValues.emplace_back(Step::KeyValue("alpha", "90")); - step.paramValues.emplace_back(Step::KeyValue("gamma", "90")); - step.paramValues.emplace_back( - Step::KeyValue("lonc", getParamValue(step, "lon_0"))); - } else if (step.name == "krovak" && - ((getParamValue(step, "axis") == "swu" && iAxisSwap < 0) || - (iAxisSwap > 0 && - getParamValue(steps_[iAxisSwap], "order") == "-2,-1"))) { - mapping = getMapping(EPSG_CODE_METHOD_KROVAK); - } else if (step.name == "merc") { - if (hasParamValue(step, "a") && hasParamValue(step, "b") && - getParamValue(step, "a") == getParamValue(step, "b") && - (!hasParamValue(step, "lat_ts") || - getAngularValue(getParamValue(step, "lat_ts")) == 0.0) && - getNumericValue(getParamValueK(step)) == 1.0 && - getParamValue(step, "nadgrids") == "@null") { - mapping = getMapping( - EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR); - for (size_t i = 0; i < step.paramValues.size(); ++i) { - if (ci_equal(step.paramValues[i].key, "nadgrids")) { - step.paramValues.erase(step.paramValues.begin() + i); - break; - } - } - } else if (hasParamValue(step, "lat_ts")) { - mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); - } else { - mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A); - } - } else if (step.name == "stere") { - if (hasParamValue(step, "lat_0") && - std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) - - 90.0) < 1e-10) { - const double lat_0 = getAngularValue(getParamValue(step, "lat_0")); - if (lat_0 > 0) { - axisType = AxisType::NORTH_POLE; - } else { - axisType = AxisType::SOUTH_POLE; - } - const auto &lat_ts = getParamValue(step, "lat_ts"); - const auto &k = getParamValueK(step); - if (!lat_ts.empty() && - std::fabs(getAngularValue(lat_ts) - lat_0) > 1e-10 && - !k.empty() && std::fabs(getNumericValue(k) - 1) > 1e-10) { - throw ParsingException("lat_ts != lat_0 and k != 1 not " - "supported for Polar Stereographic"); - } - if (!lat_ts.empty() && - (k.empty() || std::fabs(getNumericValue(k) - 1) < 1e-10)) { - mapping = - getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B); - } else { - mapping = - getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A); - } - } else { - mapping = getMapping(PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC); - } - } else if (step.name == "laea") { - if (hasParamValue(step, "lat_0") && - std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) - - 90.0) < 1e-10) { - const double lat_0 = getAngularValue(getParamValue(step, "lat_0")); - if (lat_0 > 0) { - axisType = AxisType::NORTH_POLE; - } else { - axisType = AxisType::SOUTH_POLE; - } - } - if (geogCRS->ellipsoid()->isSphere()) { - mapping = getMapping( - EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL); - } - } else if (step.name == "eqc") { - if (geogCRS->ellipsoid()->isSphere()) { - mapping = - getMapping(EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL); - } - } - - UnitOfMeasure unit = buildUnit(step, "units", "to_meter"); - if (iUnitConvert >= 0) { - const auto &stepUnitConvert = steps_[iUnitConvert]; - const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); - const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); - if (stepUnitConvert.inverted) { - std::swap(xy_in, xy_out); - } - if (xy_in->empty() || xy_out->empty() || *xy_in != "m") { - if (step.name != "ob_tran") { - throw ParsingException( - "unhandled values for xy_in and/or xy_out"); - } - } - - const LinearUnitDesc *unitsMatch = nullptr; - try { - double to_meter_value = c_locale_stod(*xy_out); - unitsMatch = getLinearUnits(to_meter_value); - if (unitsMatch == nullptr) { - unit = _buildUnit(to_meter_value); - } - } catch (const std::invalid_argument &) { - unitsMatch = getLinearUnits(*xy_out); - if (!unitsMatch) { - if (step.name != "ob_tran") { - throw ParsingException( - "unhandled values for xy_in and/or xy_out"); - } - } else { - unit = _buildUnit(unitsMatch); - } - } - } - - ConversionPtr conv; - - auto mapWithUnknownName = createMapWithUnknownName(); - - if (step.name == "utm") { - const int zone = std::atoi(getParamValue(step, "zone").c_str()); - const bool north = !hasParamValue(step, "south"); - conv = - Conversion::createUTM(emptyPropertyMap, zone, north).as_nullable(); - } else if (mapping) { - - auto methodMap = - PropertyMap().set(IdentifiedObject::NAME_KEY, mapping->wkt2_name); - if (mapping->epsg_code) { - methodMap.set(Identifier::CODESPACE_KEY, Identifier::EPSG) - .set(Identifier::CODE_KEY, mapping->epsg_code); - } - std::vector parameters; - std::vector values; - for (int i = 0; mapping->params[i] != nullptr; i++) { - const auto *param = mapping->params[i]; - std::string proj_name(param->proj_name ? param->proj_name : ""); - const std::string *paramValue = - (proj_name == "k" || proj_name == "k_0") - ? &getParamValueK(step) - : !proj_name.empty() ? &getParamValue(step, proj_name) - : &emptyString; - double value = 0; - if (!paramValue->empty()) { - bool hasError = false; - if (param->unit_type == UnitOfMeasure::Type::ANGULAR) { - value = getAngularValue(*paramValue, &hasError); - } else { - value = getNumericValue(*paramValue, &hasError); - } - if (hasError) { - throw ParsingException("invalid value for " + proj_name); - } - - } else if (param->unit_type == UnitOfMeasure::Type::SCALE) { - value = 1; - } else { - // For omerc, if gamma is missing, the default value is - // alpha - if (step.name == "omerc" && proj_name == "gamma") { - paramValue = &getParamValue(step, "alpha"); - if (!paramValue->empty()) { - value = getAngularValue(*paramValue); - } - } else if (step.name == "krovak") { - if (param->epsg_code == - EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS) { - value = 30.28813975277777776; - } else if ( - param->epsg_code == - EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL) { - value = 78.5; - } - } - } - - PropertyMap propertiesParameter; - propertiesParameter.set(IdentifiedObject::NAME_KEY, - param->wkt2_name); - if (param->epsg_code) { - propertiesParameter.set(Identifier::CODE_KEY, param->epsg_code); - propertiesParameter.set(Identifier::CODESPACE_KEY, - Identifier::EPSG); - } - parameters.push_back( - OperationParameter::create(propertiesParameter)); - // In PROJ convention, angular parameters are always in degree - // and linear parameters always in metre. - double valRounded = - param->unit_type == UnitOfMeasure::Type::LINEAR - ? Length(value, UnitOfMeasure::METRE).convertToUnit(unit) - : value; - if (std::fabs(valRounded - std::round(valRounded)) < 1e-8) { - valRounded = std::round(valRounded); - } - values.push_back(ParameterValue::create(Measure( - valRounded, - param->unit_type == UnitOfMeasure::Type::ANGULAR - ? UnitOfMeasure::DEGREE - : param->unit_type == UnitOfMeasure::Type::LINEAR - ? unit - : param->unit_type == UnitOfMeasure::Type::SCALE - ? UnitOfMeasure::SCALE_UNITY - : UnitOfMeasure::NONE))); - } - - if (step.name == "etmerc") { - methodMap.set("proj_method", "etmerc"); - } - - conv = Conversion::create(mapWithUnknownName, methodMap, parameters, - values) - .as_nullable(); - } else { - std::vector parameters; - std::vector values; - std::string methodName = "PROJ " + step.name; - for (const auto ¶m : step.paramValues) { - if (param.key == "wktext" || param.key == "no_defs" || - param.key == "datum" || param.key == "ellps" || - param.key == "a" || param.key == "b" || param.key == "R" || - param.key == "towgs84" || param.key == "nadgrids" || - param.key == "geoidgrids" || param.key == "units" || - param.key == "to_meter" || param.key == "vunits" || - param.key == "vto_meter") { - continue; - } - if (param.value.empty()) { - methodName += " " + param.key; - } else if (param.key == "o_proj") { - methodName += " " + param.key + "=" + param.value; - } else { - parameters.push_back(OperationParameter::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, param.key))); - bool hasError = false; - if (param.key == "x_0" || param.key == "y_0") { - double value = getNumericValue(param.value, &hasError); - values.push_back(ParameterValue::create( - Measure(value, UnitOfMeasure::METRE))); - } else if (param.key == "k" || param.key == "k_0") { - double value = getNumericValue(param.value, &hasError); - values.push_back(ParameterValue::create( - Measure(value, UnitOfMeasure::SCALE_UNITY))); - } else { - double value = getAngularValue(param.value, &hasError); - values.push_back(ParameterValue::create( - Measure(value, UnitOfMeasure::DEGREE))); - } - if (hasError) { - throw ParsingException("invalid value for " + param.key); - } - } - } - conv = Conversion::create( - mapWithUnknownName, - PropertyMap().set(IdentifiedObject::NAME_KEY, methodName), - parameters, values) - .as_nullable(); - - if (methodName == "PROJ ob_tran o_proj=longlat") { - return DerivedGeographicCRS::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), - geogCRS, NN_NO_CHECK(conv), - buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, false, - false)); - } - } - - std::vector axis = - processAxisSwap(step, unit, iAxisSwap, axisType, false); - - auto cs = CartesianCS::create(emptyPropertyMap, axis[0], axis[1]); - - auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, - title.empty() ? "unknown" : title); - - if (hasParamValue(step, "wktext")) { - props.set("EXTENSION_PROJ4", projString_); - } - - CRSNNPtr crs = ProjectedCRS::create(props, geogCRS, NN_NO_CHECK(conv), cs); - - if (!hasParamValue(step, "geoidgrids") && - (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter"))) { - auto vdatum = VerticalReferenceFrame::create(mapWithUnknownName); - - const UnitOfMeasure vunit = buildUnit(step, "vunits", "vto_meter"); - - auto vcrs = - VerticalCRS::create(mapWithUnknownName, vdatum, - VerticalCS::createGravityRelatedHeight(vunit)); - - crs = CompoundCRS::create(mapWithUnknownName, - std::vector{crs, vcrs}); - } - - return crs; -} - -// --------------------------------------------------------------------------- - -static bool isDatumDefiningParam(const std::string ¶m) { - return (param == "datum" || param == "ellps" || param == "a" || - param == "b" || param == "rf" || param == "f" || param == "R"); -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr PROJStringParser::Private::buildHelmertTransformation( - int iStep, int iFirstAxisSwap, int iFirstUnitConvert, int iFirstGeogStep, - int iSecondGeogStep, int iSecondAxisSwap, int iSecondUnitConvert) { - auto &step = steps_[iStep]; - auto datum = buildDatum(step, std::string()); - auto cs = CartesianCS::createGeocentric(UnitOfMeasure::METRE); - - auto mapWithUnknownName = createMapWithUnknownName(); - - auto sourceCRS = - iFirstGeogStep >= 0 - ? util::nn_static_pointer_cast( - buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, - iFirstAxisSwap, true, false)) - : util::nn_static_pointer_cast( - GeodeticCRS::create(mapWithUnknownName, datum, cs)); - auto targetCRS = - iSecondGeogStep >= 0 - ? util::nn_static_pointer_cast( - buildGeographicCRS(iSecondGeogStep, iSecondUnitConvert, - iSecondAxisSwap, true, false)) - : util::nn_static_pointer_cast( - GeodeticCRS::create(mapWithUnknownName, datum, cs)); - - double x = 0; - double y = 0; - double z = 0; - double rx = 0; - double ry = 0; - double rz = 0; - double s = 0; - double dx = 0; - double dy = 0; - double dz = 0; - double drx = 0; - double dry = 0; - double drz = 0; - double ds = 0; - double t_epoch = 0; - bool rotationTerms = false; - bool timeDependent = false; - bool conventionFound = false; - bool positionVectorConvention = false; - - struct Params { - double *pValue; - const char *name; - bool *pPresent; - }; - const Params knownParams[] = { - {&x, "x", nullptr}, - {&y, "y", nullptr}, - {&z, "z", nullptr}, - {&rx, "rx", &rotationTerms}, - {&ry, "ry", &rotationTerms}, - {&rz, "rz", &rotationTerms}, - {&s, "s", &rotationTerms}, - {&dx, "dx", &timeDependent}, - {&dy, "dy", &timeDependent}, - {&dz, "dz", &timeDependent}, - {&drx, "drx", &timeDependent}, - {&dry, "dry", &timeDependent}, - {&drz, "drz", &timeDependent}, - {&ds, "ds", &timeDependent}, - {&t_epoch, "t_epoch", &timeDependent}, - {nullptr, "exact", nullptr}, - }; - - for (const auto ¶m : step.paramValues) { - if (isDatumDefiningParam(param.key)) { - continue; - } - if (param.key == "convention") { - if (param.value == "position_vector") { - positionVectorConvention = true; - conventionFound = true; - } else if (param.value == "coordinate_frame") { - positionVectorConvention = false; - conventionFound = true; - } else { - throw ParsingException("unsupported convention"); - } - } else { - bool found = false; - for (auto &&knownParam : knownParams) { - if (param.key == knownParam.name) { - found = true; - if (knownParam.pValue) - *(knownParam.pValue) = getNumericValue(param.value); - if (knownParam.pPresent) - *(knownParam.pPresent) = true; - break; - } - } - if (!found) { - throw ParsingException("unsupported keyword for Helmert: " + - param.key); - } - } - } - - rotationTerms |= timeDependent; - if (rotationTerms && !conventionFound) { - throw ParsingException("missing convention"); - } - - std::vector emptyAccuracies; - - auto transf = ([&]() { - if (!rotationTerms) { - return Transformation::createGeocentricTranslations( - mapWithUnknownName, sourceCRS, targetCRS, x, y, z, - emptyAccuracies); - } else if (positionVectorConvention) { - if (timeDependent) { - return Transformation::createTimeDependentPositionVector( - mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry, - rz, s, dx, dy, dz, drx, dry, drz, ds, t_epoch, - emptyAccuracies); - } else { - return Transformation::createPositionVector( - mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry, - rz, s, emptyAccuracies); - } - } else { - if (timeDependent) { - return Transformation:: - createTimeDependentCoordinateFrameRotation( - mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, - ry, rz, s, dx, dy, dz, drx, dry, drz, ds, t_epoch, - emptyAccuracies); - } else { - return Transformation::createCoordinateFrameRotation( - mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry, - rz, s, emptyAccuracies); - } - } - })(); - - if (step.inverted) { - return util::nn_static_pointer_cast( - transf->inverse()); - } else { - return util::nn_static_pointer_cast(transf); - } -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr -PROJStringParser::Private::buildMolodenskyTransformation( - int iStep, int iFirstAxisSwap, int iFirstUnitConvert, int iFirstGeogStep, - int iSecondGeogStep, int iSecondAxisSwap, int iSecondUnitConvert) { - auto &step = steps_[iStep]; - - double dx = 0; - double dy = 0; - double dz = 0; - double da = 0; - double df = 0; - - struct Params { - double *pValue; - const char *name; - }; - const Params knownParams[] = { - {&dx, "dx"}, {&dy, "dy"}, {&dz, "dz"}, {&da, "da"}, {&df, "df"}, - }; - bool abridged = false; - - for (const auto ¶m : step.paramValues) { - if (isDatumDefiningParam(param.key)) { - continue; - } else if (param.key == "abridged") { - abridged = true; - } else { - bool found = false; - for (auto &&knownParam : knownParams) { - if (param.key == knownParam.name) { - found = true; - if (knownParam.pValue) - *(knownParam.pValue) = getNumericValue(param.value); - break; - } - } - if (!found) { - throw ParsingException("unsupported keyword for Molodensky: " + - param.key); - } - } - } - - auto datum = buildDatum(step, std::string()); - auto sourceCRS = iFirstGeogStep >= 0 - ? buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, - iFirstAxisSwap, true, false) - : buildGeographicCRS(iStep, -1, -1, true, false); - - const auto &ellps = sourceCRS->ellipsoid(); - const double a = ellps->semiMajorAxis().getSIValue(); - const double rf = ellps->computedInverseFlattening(); - const double target_a = a + da; - const double target_rf = 1.0 / (1.0 / rf + df); - - auto mapWithUnknownName = createMapWithUnknownName(); - - auto target_ellipsoid = - Ellipsoid::createFlattenedSphere(mapWithUnknownName, Length(target_a), - Scale(target_rf)) - ->identify(); - auto target_datum = GeodeticReferenceFrame::create( - mapWithUnknownName, target_ellipsoid, util::optional(), - PrimeMeridian::GREENWICH); - - auto targetCRS = util::nn_static_pointer_cast( - iSecondGeogStep >= 0 - ? buildGeographicCRS(iSecondGeogStep, iSecondUnitConvert, - iSecondAxisSwap, true, false) - : GeographicCRS::create(mapWithUnknownName, target_datum, - EllipsoidalCS::createLongitudeLatitude( - UnitOfMeasure::DEGREE))); - - auto sourceCRS_as_CRS = util::nn_static_pointer_cast(sourceCRS); - - std::vector emptyAccuracies; - - auto transf = ([&]() { - if (abridged) { - return Transformation::createAbridgedMolodensky( - mapWithUnknownName, sourceCRS_as_CRS, targetCRS, dx, dy, dz, da, - df, emptyAccuracies); - } else { - return Transformation::createMolodensky( - mapWithUnknownName, sourceCRS_as_CRS, targetCRS, dx, dy, dz, da, - df, emptyAccuracies); - } - })(); - - if (step.inverted) { - return util::nn_static_pointer_cast( - transf->inverse()); - } else { - return util::nn_static_pointer_cast(transf); - } -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static const metadata::ExtentPtr nullExtent{}; - -static const metadata::ExtentPtr &getExtent(const crs::CRS *crs) { - const auto &domains = crs->domains(); - if (!domains.empty()) { - return domains[0]->domainOfValidity(); - } - return nullExtent; -} - -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instanciate a sub-class of BaseObject from a PROJ string. - * @throw ParsingException - */ -BaseObjectNNPtr -PROJStringParser::createFromPROJString(const std::string &projString) { - std::string vunits; - std::string vto_meter; - - d->steps_.clear(); - d->title_.clear(); - d->globalParamValues_.clear(); - d->projString_ = projString; - PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_, - d->title_, vunits, vto_meter); - - if (d->steps_.empty()) { - - if (!vunits.empty() || !vto_meter.empty()) { - Step fakeStep; - if (!vunits.empty()) { - fakeStep.paramValues.emplace_back( - Step::KeyValue("vunits", vunits)); - } - if (!vto_meter.empty()) { - fakeStep.paramValues.emplace_back( - Step::KeyValue("vto_meter", vto_meter)); - } - auto vdatum = - VerticalReferenceFrame::create(createMapWithUnknownName()); - auto vcrs = VerticalCRS::create( - createMapWithUnknownName(), vdatum, - VerticalCS::createGravityRelatedHeight( - d->buildUnit(fakeStep, "vunits", "vto_meter"))); - return vcrs; - } - } - - if ((d->steps_.size() == 1 || - (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) && - isGeocentricStep(d->steps_[0].name)) { - return d->buildBoundOrCompoundCRSIfNeeded( - 0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 && - d->steps_[1].name == "unitconvert") - ? 1 - : -1)); - } - - // +init=xxxx:yyyy syntax - if (d->steps_.size() == 1 && d->steps_[0].isInit && - d->steps_[0].paramValues.size() == 0) { - - // Those used to come from a text init file - // We only support them in compatibility mode - const std::string &stepName = d->steps_[0].name; - if (ci_starts_with(stepName, "epsg:") || - ci_starts_with(stepName, "IGNF:")) { - bool usePROJ4InitRules = d->usePROJ4InitRules_; - if (!usePROJ4InitRules) { - PJ_CONTEXT *ctx = proj_context_create(); - if (ctx) { - usePROJ4InitRules = proj_context_get_use_proj4_init_rules( - ctx, FALSE) == TRUE; - proj_context_destroy(ctx); - } - } - if (!usePROJ4InitRules) { - throw ParsingException("init=epsg:/init=IGNF: syntax not " - "supported in non-PROJ4 emulation mode"); - } - - PJ_CONTEXT *ctx = proj_context_create(); - char unused[256]; - std::string initname(stepName); - initname.resize(initname.find(':')); - int file_found = - pj_find_file(ctx, initname.c_str(), unused, sizeof(unused)); - proj_context_destroy(ctx); - if (!file_found) { - auto obj = createFromUserInput(stepName, d->dbContext_, true); - auto crs = dynamic_cast(obj.get()); - if (crs) { - PropertyMap properties; - properties.set(IdentifiedObject::NAME_KEY, crs->nameStr()); - const auto &extent = getExtent(crs); - if (extent) { - properties.set( - common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - NN_NO_CHECK(extent)); - } - auto geogCRS = dynamic_cast(crs); - if (geogCRS) { - // Override with longitude latitude in radian - return GeographicCRS::create( - properties, geogCRS->datum(), - geogCRS->datumEnsemble(), - EllipsoidalCS::createLongitudeLatitude( - UnitOfMeasure::RADIAN)); - } - auto projCRS = dynamic_cast(crs); - if (projCRS) { - // Override with easting northing order - const auto &conv = projCRS->derivingConversionRef(); - if (conv->method()->getEPSGCode() != - EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) { - return ProjectedCRS::create( - properties, projCRS->baseCRS(), conv, - CartesianCS::createEastingNorthing( - projCRS->coordinateSystem() - ->axisList()[0] - ->unit())); - } - } - } - return obj; - } - } - - paralist *init = pj_mkparam(("init=" + d->steps_[0].name).c_str()); - if (!init) { - throw ParsingException("out of memory"); - } - PJ_CONTEXT *ctx = proj_context_create(); - if (!ctx) { - pj_dealloc(init); - throw ParsingException("out of memory"); - } - paralist *list = pj_expand_init(ctx, init); - proj_context_destroy(ctx); - if (!list) { - pj_dealloc(init); - throw ParsingException("cannot expand " + projString); - } - std::string expanded; - bool first = true; - bool has_init_term = false; - for (auto t = list; t;) { - if (!expanded.empty()) { - expanded += ' '; - } - if (first) { - // first parameter is the init= itself - first = false; - } else if (starts_with(t->param, "init=")) { - has_init_term = true; - } else { - expanded += t->param; - } - - auto n = t->next; - pj_dealloc(t); - t = n; - } - - if (!has_init_term) { - return createFromPROJString(expanded); - } - } - - int iFirstGeogStep = -1; - int iSecondGeogStep = -1; - int iProjStep = -1; - int iFirstUnitConvert = -1; - int iSecondUnitConvert = -1; - int iFirstAxisSwap = -1; - int iSecondAxisSwap = -1; - int iHelmert = -1; - int iFirstCart = -1; - int iSecondCart = -1; - int iMolodensky = -1; - bool unexpectedStructure = false; - for (int i = 0; i < static_cast(d->steps_.size()); i++) { - const auto &stepName = d->steps_[i].name; - if (isGeographicStep(stepName)) { - if (iFirstGeogStep < 0) { - iFirstGeogStep = i; - } else if (iSecondGeogStep < 0) { - iSecondGeogStep = i; - } else { - unexpectedStructure = true; - break; - } - } else if (ci_equal(stepName, "unitconvert")) { - if (iFirstUnitConvert < 0) { - iFirstUnitConvert = i; - } else if (iSecondUnitConvert < 0) { - iSecondUnitConvert = i; - } else { - unexpectedStructure = true; - break; - } - } else if (ci_equal(stepName, "axisswap")) { - if (iFirstAxisSwap < 0) { - iFirstAxisSwap = i; - } else if (iSecondAxisSwap < 0) { - iSecondAxisSwap = i; - } else { - unexpectedStructure = true; - break; - } - } else if (stepName == "helmert") { - if (iHelmert >= 0) { - unexpectedStructure = true; - break; - } - iHelmert = i; - } else if (stepName == "cart") { - if (iFirstCart < 0) { - iFirstCart = i; - } else if (iSecondCart < 0) { - iSecondCart = i; - } else { - unexpectedStructure = true; - break; - } - } else if (stepName == "molodensky") { - if (iMolodensky >= 0) { - unexpectedStructure = true; - break; - } - iMolodensky = i; - } else if (isProjectedStep(stepName)) { - if (iProjStep >= 0) { - unexpectedStructure = true; - break; - } - iProjStep = i; - } else { - unexpectedStructure = true; - break; - } - } - - if (!unexpectedStructure) { - if (iFirstGeogStep == 0 && iSecondGeogStep < 0 && iProjStep < 0 && - iHelmert < 0 && iFirstCart < 0 && iMolodensky < 0 && - (iFirstUnitConvert < 0 || iSecondUnitConvert < 0) && - (iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) { - const bool ignoreVUnits = false; - return d->buildBoundOrCompoundCRSIfNeeded( - 0, d->buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, - iFirstAxisSwap, ignoreVUnits, false)); - } - if (iProjStep >= 0 && !d->steps_[iProjStep].inverted && - (iFirstGeogStep < 0 || iFirstGeogStep + 1 == iProjStep) && - iMolodensky < 0 && iSecondGeogStep < 0 && iFirstCart < 0 && - iHelmert < 0) { - if (iFirstGeogStep < 0) - iFirstGeogStep = iProjStep; - const bool ignoreVUnits = true; - return d->buildBoundOrCompoundCRSIfNeeded( - iProjStep, - d->buildProjectedCRS( - iProjStep, - d->buildGeographicCRS( - iFirstGeogStep, - iFirstUnitConvert < iFirstGeogStep ? iFirstUnitConvert - : -1, - iFirstAxisSwap < iFirstGeogStep ? iFirstAxisSwap : -1, - ignoreVUnits, true), - iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert - : iFirstUnitConvert, - iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap - : iFirstAxisSwap)); - } - if (d->steps_.size() == 1 && iHelmert == 0) { - return d->buildHelmertTransformation(iHelmert); - } - - if (iProjStep < 0 && iHelmert > 0 && iMolodensky < 0 && - (iFirstGeogStep < 0 || iFirstGeogStep == iFirstCart - 1 || - (iFirstGeogStep == iSecondCart + 1 && iSecondGeogStep < 0)) && - iFirstCart == iHelmert - 1 && iSecondCart == iHelmert + 1 && - (iSecondGeogStep < 0 || iSecondGeogStep == iSecondCart + 1) && - !d->steps_[iFirstCart].inverted && - d->steps_[iSecondCart].inverted && iFirstAxisSwap < iHelmert && - iFirstUnitConvert < iHelmert && - (iSecondAxisSwap < 0 || iSecondAxisSwap > iHelmert) && - (iSecondUnitConvert < 0 || iSecondUnitConvert > iHelmert)) { - return d->buildHelmertTransformation( - iHelmert, iFirstAxisSwap, iFirstUnitConvert, - iFirstGeogStep >= 0 && iFirstGeogStep == iFirstCart - 1 - ? iFirstGeogStep - : iFirstCart, - iFirstGeogStep == iSecondCart + 1 - ? iFirstGeogStep - : iSecondGeogStep == iSecondCart + 1 ? iSecondGeogStep - : iSecondCart, - iSecondAxisSwap, iSecondUnitConvert); - } - - if (d->steps_.size() == 1 && iMolodensky == 0) { - return d->buildMolodenskyTransformation(iMolodensky); - } - - if (iProjStep < 0 && iHelmert < 0 && iMolodensky > 0 && - (iFirstGeogStep < 0 || iFirstGeogStep == iMolodensky - 1 || - (iFirstGeogStep == iMolodensky + 1 && iSecondGeogStep < 0)) && - (iSecondGeogStep < 0 || iSecondGeogStep == iMolodensky + 1) && - iFirstAxisSwap < iMolodensky && iFirstUnitConvert < iMolodensky && - (iSecondAxisSwap < 0 || iSecondAxisSwap > iMolodensky) && - (iSecondUnitConvert < 0 || iSecondUnitConvert > iMolodensky)) { - return d->buildMolodenskyTransformation( - iMolodensky, iFirstAxisSwap, iFirstUnitConvert, - iFirstGeogStep >= 0 && iFirstGeogStep == iMolodensky - 1 - ? iFirstGeogStep - : iMolodensky, - iFirstGeogStep == iMolodensky + 1 - ? iFirstGeogStep - : iSecondGeogStep == iMolodensky + 1 ? iSecondGeogStep - : iMolodensky, - iSecondAxisSwap, iSecondUnitConvert); - } - } - - struct Logger { - std::string msg{}; - - // cppcheck-suppress functionStatic - void setMessage(const char *msgIn) noexcept { - try { - msg = msgIn; - } catch (const std::exception &) { - } - } - - static void log(void *user_data, int level, const char *msg) { - if (level == PJ_LOG_ERROR) { - static_cast(user_data)->setMessage(msg); - } - } - }; - - // If the structure is not recognized, then try to instanciate the - // pipeline, and if successful, wrap it in a PROJBasedOperation - Logger logger; - auto pj_context = proj_context_create(); - if (!pj_context) { - throw ParsingException("out of memory"); - } - proj_log_func(pj_context, &logger, Logger::log); - proj_context_use_proj4_init_rules(pj_context, d->usePROJ4InitRules_); - auto pj = proj_create(pj_context, projString.c_str()); - bool valid = pj != nullptr; - proj_destroy(pj); - - if (!valid && logger.msg.empty()) { - logger.setMessage(proj_errno_string(proj_context_errno(pj_context))); - } - - proj_context_destroy(pj_context); - - if (!valid) { - throw ParsingException(logger.msg); - } - - auto props = PropertyMap(); - if (!d->title_.empty()) { - props.set(IdentifiedObject::NAME_KEY, d->title_); - } - return operation::SingleOperation::createPROJBased(props, projString, - nullptr, nullptr, {}); -} - -} // namespace io -NS_PROJ_END diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp new file mode 100644 index 00000000..d0b5d720 --- /dev/null +++ b/src/iso19111/c_api.cpp @@ -0,0 +1,6540 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: C API wraper of C++ API + * 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 +#include +#include +#include +#include + +#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" + +// PROJ include order is sensitive +// clang-format off +#include "proj_internal.h" +#include "proj.h" +#include "proj_experimental.h" +#include "projects.h" +// clang-format on +#include "proj_constants.h" + +using namespace NS_PROJ::common; +using namespace NS_PROJ::crs; +using namespace NS_PROJ::cs; +using namespace NS_PROJ::datum; +using namespace NS_PROJ::io; +using namespace NS_PROJ::internal; +using namespace NS_PROJ::metadata; +using namespace NS_PROJ::operation; +using namespace NS_PROJ::util; +using namespace NS_PROJ; + +// --------------------------------------------------------------------------- + +static void PROJ_NO_INLINE proj_log_error(PJ_CONTEXT *ctx, const char *function, + const char *text) { + std::string msg(function); + msg += ": "; + msg += text; + ctx->logger(ctx->app_data, PJ_LOG_ERROR, msg.c_str()); +} + +// --------------------------------------------------------------------------- + +static void PROJ_NO_INLINE proj_log_debug(PJ_CONTEXT *ctx, const char *function, + const char *text) { + std::string msg(function); + msg += ": "; + msg += text; + ctx->logger(ctx->app_data, PJ_LOG_DEBUG, msg.c_str()); +} + +// --------------------------------------------------------------------------- + +/** \brief Opaque object representing a Ellipsoid, Datum, CRS or Coordinate + * Operation. Should be used by at most one thread at a time. */ +struct PJ_OBJ { + //! @cond Doxygen_Suppress + IdentifiedObjectNNPtr obj; + + // cached results + mutable std::string lastWKT{}; + mutable std::string lastPROJString{}; + mutable bool gridsNeededAsked = false; + mutable std::vector gridsNeeded{}; + + explicit PJ_OBJ(const IdentifiedObjectNNPtr &objIn) : obj(objIn) {} + static PJ_OBJ *create(const IdentifiedObjectNNPtr &objIn); + + PJ_OBJ(const PJ_OBJ &) = delete; + PJ_OBJ &operator=(const PJ_OBJ &) = delete; + //! @endcond +}; + +//! @cond Doxygen_Suppress +PJ_OBJ *PJ_OBJ::create(const IdentifiedObjectNNPtr &objIn) { + return new PJ_OBJ(objIn); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Opaque object representing a set of operation results. */ +struct PJ_OBJ_LIST { + //! @cond Doxygen_Suppress + std::vector objects; + + explicit PJ_OBJ_LIST(std::vector &&objectsIn) + : objects(std::move(objectsIn)) {} + + PJ_OBJ_LIST(const PJ_OBJ_LIST &) = delete; + PJ_OBJ_LIST &operator=(const PJ_OBJ_LIST &) = delete; + //! @endcond +}; + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +/** Auxiliary structure to PJ_CONTEXT storing C++ context stuff. */ +struct projCppContext { + DatabaseContextNNPtr databaseContext; + std::string lastUOMName_{}; + + explicit projCppContext(PJ_CONTEXT *ctx, const char *dbPath = nullptr, + const char *const *auxDbPaths = nullptr) + : databaseContext(DatabaseContext::create( + dbPath ? dbPath : std::string(), toVector(auxDbPaths))) { + databaseContext->attachPJContext(ctx); + } + + static std::vector toVector(const char *const *auxDbPaths) { + std::vector res; + for (auto iter = auxDbPaths; iter && *iter; ++iter) { + res.emplace_back(std::string(*iter)); + } + return res; + } +}; + +// --------------------------------------------------------------------------- + +void proj_context_delete_cpp_context(struct projCppContext *cppContext) { + delete cppContext; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +#define SANITIZE_CTX(ctx) \ + do { \ + if (ctx == nullptr) { \ + ctx = pj_get_default_ctx(); \ + } \ + } while (0) + +// --------------------------------------------------------------------------- + +static PROJ_NO_INLINE const DatabaseContextNNPtr & +getDBcontext(PJ_CONTEXT *ctx) { + if (ctx->cpp_context == nullptr) { + ctx->cpp_context = new projCppContext(ctx); + } + return ctx->cpp_context->databaseContext; +} + +// --------------------------------------------------------------------------- + +static PROJ_NO_INLINE DatabaseContextPtr +getDBcontextNoException(PJ_CONTEXT *ctx, const char *function) { + try { + return getDBcontext(ctx).as_nullable(); + } catch (const std::exception &e) { + proj_log_debug(ctx, function, e.what()); + return nullptr; + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Explicitly point to the main PROJ CRS and coordinate operation + * definition database ("proj.db"), and potentially auxiliary databases with + * same structure. + * + * @param ctx PROJ context, or NULL for default context + * @param dbPath Path to main database, or NULL for default. + * @param auxDbPaths NULL-terminated list of auxiliary database filenames, or + * NULL. + * @param options should be set to NULL for now + * @return TRUE in case of success + */ +int proj_context_set_database_path(PJ_CONTEXT *ctx, const char *dbPath, + const char *const *auxDbPaths, + const char *const *options) { + SANITIZE_CTX(ctx); + (void)options; + delete ctx->cpp_context; + ctx->cpp_context = nullptr; + try { + ctx->cpp_context = new projCppContext(ctx, dbPath, auxDbPaths); + return true; + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return false; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the path to the database. + * + * The returned pointer remains valid while ctx is valid, and until + * proj_context_set_database_path() is called. + * + * @param ctx PROJ context, or NULL for default context + * @return path, or nullptr + */ +const char *proj_context_get_database_path(PJ_CONTEXT *ctx) { + SANITIZE_CTX(ctx); + try { + return getDBcontext(ctx)->getPath().c_str(); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return a metadata from the database. + * + * The returned pointer remains valid while ctx is valid, and until + * proj_context_get_database_metadata() is called. + * + * @param ctx PROJ context, or NULL for default context + * @param key Metadata key. Must not be NULL + * @return value, or nullptr + */ +const char *proj_context_get_database_metadata(PJ_CONTEXT *ctx, + const char *key) { + SANITIZE_CTX(ctx); + try { + return getDBcontext(ctx)->getMetadata(key); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Guess the "dialect" of the WKT string. + * + * @param ctx PROJ context, or NULL for default context + * @param wkt String (must not be NULL) + */ +PJ_GUESSED_WKT_DIALECT proj_context_guess_wkt_dialect(PJ_CONTEXT *ctx, + const char *wkt) { + (void)ctx; + assert(wkt); + switch (WKTParser().guessDialect(wkt)) { + case WKTParser::WKTGuessedDialect::WKT2_2018: + return PJ_GUESSED_WKT2_2018; + case WKTParser::WKTGuessedDialect::WKT2_2015: + return PJ_GUESSED_WKT2_2015; + case WKTParser::WKTGuessedDialect::WKT1_GDAL: + return PJ_GUESSED_WKT1_GDAL; + case WKTParser::WKTGuessedDialect::WKT1_ESRI: + return PJ_GUESSED_WKT1_ESRI; + case WKTParser::WKTGuessedDialect::NOT_WKT: + break; + } + return PJ_GUESSED_NOT_WKT; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const char *getOptionValue(const char *option, + const char *keyWithEqual) noexcept { + if (ci_starts_with(option, keyWithEqual)) { + return option + strlen(keyWithEqual); + } + return nullptr; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief "Clone" an object. + * + * Technically this just increases the reference counter on the object, since + * PJ_OBJ objects are immutable. + * + * The returned object must be unreferenced with proj_obj_destroy() after use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object to clone. Must not be NULL. + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL in + * case of error. + */ +PJ_OBJ *proj_obj_clone(PJ_CONTEXT *ctx, const PJ_OBJ *obj) { + SANITIZE_CTX(ctx); + try { + return PJ_OBJ::create(obj->obj); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate an object from a WKT string, PROJ string or object code + * (like "EPSG:4326", "urn:ogc:def:crs:EPSG::4326", + * "urn:ogc:def:coordinateOperation:EPSG::1671"). + * + * This function calls osgeo::proj::io::createFromUserInput() + * + * The returned object must be unreferenced with proj_obj_destroy() after use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param text String (must not be NULL) + * @param options null-terminated list of options, or NULL. Currently + * supported options are: + *
    + *
  • USE_PROJ4_INIT_RULES=YES/NO. Defaults to NO. When set to YES, + * init=epsg:XXXX syntax will be allowed and will be interpreted according to + * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude + * order and will expect/output coordinates in radians. ProjectedCRS will have + * easting, northing axis order (except the ones with Transverse Mercator South + * Orientated projection). In that mode, the epsg:XXXX syntax will be also + * interprated the same way.
  • + *
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL in + * case of error. + */ +PJ_OBJ *proj_obj_create_from_user_input(PJ_CONTEXT *ctx, const char *text, + const char *const *options) { + SANITIZE_CTX(ctx); + assert(text); + (void)options; + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + try { + bool usePROJ4InitRules = false; + for (auto iter = options; iter && iter[0]; ++iter) { + const char *value; + if ((value = getOptionValue(*iter, "USE_PROJ4_INIT_RULES="))) { + usePROJ4InitRules = ci_equal(value, "YES"); + } else { + std::string msg("Unknown option :"); + msg += *iter; + proj_log_error(ctx, __FUNCTION__, msg.c_str()); + return nullptr; + } + } + auto identifiedObject = nn_dynamic_pointer_cast( + createFromUserInput(text, dbContext, usePROJ4InitRules)); + if (identifiedObject) { + return PJ_OBJ::create(NN_NO_CHECK(identifiedObject)); + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +template static PROJ_STRING_LIST to_string_list(T &&set) { + auto ret = new char *[set.size() + 1]; + size_t i = 0; + for (const auto &str : set) { + try { + ret[i] = new char[str.size() + 1]; + } catch (const std::exception &) { + while (--i > 0) { + delete[] ret[i]; + } + delete[] ret; + throw; + } + std::memcpy(ret[i], str.c_str(), str.size() + 1); + i++; + } + ret[i] = nullptr; + return ret; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate an object from a WKT string. + * + * This function calls osgeo::proj::io::WKTParser::createFromWKT() + * + * The returned object must be unreferenced with proj_obj_destroy() after use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param wkt WKT string (must not be NULL) + * @param options null-terminated list of options, or NULL. Currently + * supported options are: + *
    + *
  • STRICT=YES/NO. Defaults to NO. When set to YES, strict validation will + * be enabled.
  • + *
+ * @param out_warnings Pointer to a PROJ_STRING_LIST object, or NULL. + * If provided, *out_warnings will contain a list of warnings, typically for + * non recognized projection method or parameters. It must be freed with + * proj_string_list_destroy(). + * @param out_grammar_errors Pointer to a PROJ_STRING_LIST object, or NULL. + * If provided, *out_grammar_errors will contain a list of errors regarding the + * WKT grammaer. It must be freed with proj_string_list_destroy(). + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL in + * case of error. + */ +PJ_OBJ *proj_obj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt, + const char *const *options, + PROJ_STRING_LIST *out_warnings, + PROJ_STRING_LIST *out_grammar_errors) { + SANITIZE_CTX(ctx); + assert(wkt); + + if (out_warnings) { + *out_warnings = nullptr; + } + if (out_grammar_errors) { + *out_grammar_errors = nullptr; + } + + try { + WKTParser parser; + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + if (dbContext) { + parser.attachDatabaseContext(NN_NO_CHECK(dbContext)); + } + for (auto iter = options; iter && iter[0]; ++iter) { + const char *value; + if ((value = getOptionValue(*iter, "STRICT="))) { + parser.setStrict(ci_equal(value, "YES")); + } else { + std::string msg("Unknown option :"); + msg += *iter; + proj_log_error(ctx, __FUNCTION__, msg.c_str()); + return nullptr; + } + } + auto obj = nn_dynamic_pointer_cast( + parser.createFromWKT(wkt)); + + if (out_grammar_errors) { + auto warnings = parser.warningList(); + if (!warnings.empty()) { + *out_grammar_errors = to_string_list(warnings); + } + } + + if (obj && out_warnings) { + auto derivedCRS = dynamic_cast(obj.get()); + if (derivedCRS) { + auto warnings = + derivedCRS->derivingConversionRef()->validateParameters(); + if (!warnings.empty()) { + *out_warnings = to_string_list(warnings); + } + } else { + auto singleOp = + dynamic_cast(obj.get()); + if (singleOp) { + auto warnings = singleOp->validateParameters(); + if (!warnings.empty()) { + *out_warnings = to_string_list(warnings); + } + } + } + } + + if (obj) { + return PJ_OBJ::create(NN_NO_CHECK(obj)); + } + } catch (const std::exception &e) { + if (out_grammar_errors) { + std::list exc{e.what()}; + try { + *out_grammar_errors = to_string_list(exc); + } catch (const std::exception &) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + } else { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate an object from a PROJ string. + * + * This function calls osgeo::proj::io::PROJStringParser::createFromPROJString() + * + * The returned object must be unreferenced with proj_obj_destroy() after use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param proj_string PROJ string (must not be NULL) + * @param options should be set to NULL for now + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL in + * case of error. + */ +PJ_OBJ *proj_obj_create_from_proj_string(PJ_CONTEXT *ctx, + const char *proj_string, + const char *const *options) { + SANITIZE_CTX(ctx); + (void)options; + assert(proj_string); + try { + PROJStringParser parser; + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + if (dbContext) { + parser.attachDatabaseContext(NN_NO_CHECK(dbContext)); + } + auto identifiedObject = nn_dynamic_pointer_cast( + parser.createFromPROJString(proj_string)); + if (identifiedObject) { + return PJ_OBJ::create(NN_NO_CHECK(identifiedObject)); + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate an object from a database lookup. + * + * The returned object must be unreferenced with proj_obj_destroy() after use. + * It should be used by at most one thread at a time. + * + * @param ctx Context, or NULL for default context. + * @param auth_name Authority name (must not be NULL) + * @param code Object code (must not be NULL) + * @param category Object category + * @param usePROJAlternativeGridNames Whether PROJ alternative grid names + * should be substituted to the official grid names. Only used on + * transformations + * @param options should be set to NULL for now + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL in + * case of error. + */ +PJ_OBJ *proj_obj_create_from_database(PJ_CONTEXT *ctx, const char *auth_name, + const char *code, + PJ_OBJ_CATEGORY category, + int usePROJAlternativeGridNames, + const char *const *options) { + assert(auth_name); + assert(code); + (void)options; + SANITIZE_CTX(ctx); + try { + const std::string codeStr(code); + auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name); + IdentifiedObjectPtr obj; + switch (category) { + case PJ_OBJ_CATEGORY_ELLIPSOID: + obj = factory->createEllipsoid(codeStr).as_nullable(); + break; + case PJ_OBJ_CATEGORY_PRIME_MERIDIAN: + obj = factory->createPrimeMeridian(codeStr).as_nullable(); + break; + case PJ_OBJ_CATEGORY_DATUM: + obj = factory->createDatum(codeStr).as_nullable(); + break; + case PJ_OBJ_CATEGORY_CRS: + obj = + factory->createCoordinateReferenceSystem(codeStr).as_nullable(); + break; + case PJ_OBJ_CATEGORY_COORDINATE_OPERATION: + obj = factory + ->createCoordinateOperation( + codeStr, usePROJAlternativeGridNames != 0) + .as_nullable(); + break; + } + return PJ_OBJ::create(NN_NO_CHECK(obj)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const char *get_unit_category(UnitOfMeasure::Type type) { + const char *ret = nullptr; + switch (type) { + case UnitOfMeasure::Type::UNKNOWN: + ret = "unknown"; + break; + case UnitOfMeasure::Type::NONE: + ret = "none"; + break; + case UnitOfMeasure::Type::ANGULAR: + ret = "angular"; + break; + case UnitOfMeasure::Type::LINEAR: + ret = "linear"; + break; + case UnitOfMeasure::Type::SCALE: + ret = "scale"; + break; + case UnitOfMeasure::Type::TIME: + ret = "time"; + break; + case UnitOfMeasure::Type::PARAMETRIC: + ret = "parametric"; + break; + } + return ret; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Get information for a unit of measure from a database lookup. + * + * @param ctx Context, or NULL for default context. + * @param auth_name Authority name (must not be NULL) + * @param code Unit of measure code (must not be NULL) + * @param out_name Pointer to a string value to store the parameter name. or + * NULL. This value remains valid until the next call to + * proj_uom_get_info_from_database() or the context destruction. + * @param out_conv_factor Pointer to a value to store the conversion + * factor of the prime meridian longitude unit to radian. or NULL + * @param out_category Pointer to a string value to store the parameter name. or + * NULL. This value might be "unknown", "none", "linear", "angular", "scale", + * "time" or "parametric"; + * @return TRUE in case of success + */ +int proj_uom_get_info_from_database(PJ_CONTEXT *ctx, const char *auth_name, + const char *code, const char **out_name, + double *out_conv_factor, + const char **out_category) { + assert(auth_name); + assert(code); + SANITIZE_CTX(ctx); + try { + auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name); + auto obj = factory->createUnitOfMeasure(code); + if (out_name) { + ctx->cpp_context->lastUOMName_ = obj->name(); + *out_name = ctx->cpp_context->lastUOMName_.c_str(); + } + if (out_conv_factor) { + *out_conv_factor = obj->conversionToSI(); + } + if (out_category) { + *out_category = get_unit_category(obj->type()); + } + return true; + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return false; +} + +// --------------------------------------------------------------------------- + +/** \brief Return GeodeticCRS that use the specified datum. + * + * @param ctx Context, or NULL for default context. + * @param crs_auth_name CRS authority name, or NULL. + * @param datum_auth_name Datum authority name (must not be NULL) + * @param datum_code Datum code (must not be NULL) + * @param crs_type "geographic 2D", "geographic 3D", "geocentric" or NULL + * @return a result set that must be unreferenced with + * proj_obj_list_destroy(), or NULL in case of error. + */ +PJ_OBJ_LIST *proj_obj_query_geodetic_crs_from_datum(PJ_CONTEXT *ctx, + const char *crs_auth_name, + const char *datum_auth_name, + const char *datum_code, + const char *crs_type) { + assert(datum_auth_name); + assert(datum_code); + SANITIZE_CTX(ctx); + try { + auto factory = AuthorityFactory::create( + getDBcontext(ctx), crs_auth_name ? crs_auth_name : ""); + auto res = factory->createGeodeticCRSFromDatum( + datum_auth_name, datum_code, crs_type ? crs_type : ""); + std::vector objects; + for (const auto &obj : res) { + objects.push_back(obj); + } + return new PJ_OBJ_LIST(std::move(objects)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Drops a reference on an object. + * + * This method should be called one and exactly one for each function + * returning a PJ_OBJ* + * + * @param obj Object, or NULL. + */ +void proj_obj_destroy(PJ_OBJ *obj) { delete obj; } + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static AuthorityFactory::ObjectType +convertPJObjectTypeToObjectType(PJ_OBJ_TYPE type, bool &valid) { + valid = true; + AuthorityFactory::ObjectType cppType = AuthorityFactory::ObjectType::CRS; + switch (type) { + case PJ_OBJ_TYPE_ELLIPSOID: + cppType = AuthorityFactory::ObjectType::ELLIPSOID; + break; + + case PJ_OBJ_TYPE_PRIME_MERIDIAN: + cppType = AuthorityFactory::ObjectType::PRIME_MERIDIAN; + break; + + case PJ_OBJ_TYPE_GEODETIC_REFERENCE_FRAME: + case PJ_OBJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME: + cppType = AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME; + break; + + case PJ_OBJ_TYPE_VERTICAL_REFERENCE_FRAME: + case PJ_OBJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME: + cppType = AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME; + break; + + case PJ_OBJ_TYPE_DATUM_ENSEMBLE: + cppType = AuthorityFactory::ObjectType::DATUM; + break; + + case PJ_OBJ_TYPE_CRS: + cppType = AuthorityFactory::ObjectType::CRS; + break; + + case PJ_OBJ_TYPE_GEODETIC_CRS: + cppType = AuthorityFactory::ObjectType::GEODETIC_CRS; + break; + + case PJ_OBJ_TYPE_GEOCENTRIC_CRS: + cppType = AuthorityFactory::ObjectType::GEOCENTRIC_CRS; + break; + + case PJ_OBJ_TYPE_GEOGRAPHIC_CRS: + cppType = AuthorityFactory::ObjectType::GEOGRAPHIC_CRS; + break; + + case PJ_OBJ_TYPE_GEOGRAPHIC_2D_CRS: + cppType = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS; + break; + + case PJ_OBJ_TYPE_GEOGRAPHIC_3D_CRS: + cppType = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS; + break; + + case PJ_OBJ_TYPE_VERTICAL_CRS: + cppType = AuthorityFactory::ObjectType::VERTICAL_CRS; + break; + + case PJ_OBJ_TYPE_PROJECTED_CRS: + cppType = AuthorityFactory::ObjectType::PROJECTED_CRS; + break; + + case PJ_OBJ_TYPE_COMPOUND_CRS: + cppType = AuthorityFactory::ObjectType::COMPOUND_CRS; + break; + + case PJ_OBJ_TYPE_ENGINEERING_CRS: + valid = false; + break; + + case PJ_OBJ_TYPE_TEMPORAL_CRS: + valid = false; + break; + + case PJ_OBJ_TYPE_BOUND_CRS: + valid = false; + break; + + case PJ_OBJ_TYPE_OTHER_CRS: + cppType = AuthorityFactory::ObjectType::CRS; + break; + + case PJ_OBJ_TYPE_CONVERSION: + cppType = AuthorityFactory::ObjectType::CONVERSION; + break; + + case PJ_OBJ_TYPE_TRANSFORMATION: + cppType = AuthorityFactory::ObjectType::TRANSFORMATION; + break; + + case PJ_OBJ_TYPE_CONCATENATED_OPERATION: + cppType = AuthorityFactory::ObjectType::CONCATENATED_OPERATION; + break; + + case PJ_OBJ_TYPE_OTHER_COORDINATE_OPERATION: + cppType = AuthorityFactory::ObjectType::COORDINATE_OPERATION; + break; + + case PJ_OBJ_TYPE_UNKNOWN: + valid = false; + break; + } + return cppType; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return a list of objects by their name. + * + * @param ctx Context, or NULL for default context. + * @param auth_name Authority name, used to restrict the search. + * Or NULL for all authorities. + * @param searchedName Searched name. Must be at least 2 character long. + * @param types List of object types into which to search. If + * NULL, all object types will be searched. + * @param typesCount Number of elements in types, or 0 if types is NULL + * @param approximateMatch Whether approximate name identification is allowed. + * @param limitResultCount Maximum number of results to return. + * Or 0 for unlimited. + * @param options should be set to NULL for now + * @return a result set that must be unreferenced with + * proj_obj_list_destroy(), or NULL in case of error. + */ +PJ_OBJ_LIST *proj_obj_create_from_name(PJ_CONTEXT *ctx, const char *auth_name, + const char *searchedName, + const PJ_OBJ_TYPE *types, + size_t typesCount, int approximateMatch, + size_t limitResultCount, + const char *const *options) { + assert(searchedName); + assert((types != nullptr && typesCount > 0) || + (types == nullptr && typesCount == 0)); + (void)options; + SANITIZE_CTX(ctx); + try { + auto factory = AuthorityFactory::create(getDBcontext(ctx), + auth_name ? auth_name : ""); + std::vector allowedTypes; + for (size_t i = 0; i < typesCount; ++i) { + bool valid = false; + auto type = convertPJObjectTypeToObjectType(types[i], valid); + if (valid) { + allowedTypes.push_back(type); + } + } + auto res = factory->createObjectsFromName(searchedName, allowedTypes, + approximateMatch != 0, + limitResultCount); + std::vector objects; + for (const auto &obj : res) { + objects.push_back(obj); + } + return new PJ_OBJ_LIST(std::move(objects)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the type of an object. + * + * @param obj Object (must not be NULL) + * @return its type. + */ +PJ_OBJ_TYPE proj_obj_get_type(const PJ_OBJ *obj) { + assert(obj); + auto ptr = obj->obj.get(); + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_ELLIPSOID; + } + + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_PRIME_MERIDIAN; + } + + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_GEODETIC_REFERENCE_FRAME; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_VERTICAL_REFERENCE_FRAME; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_DATUM_ENSEMBLE; + } + + { + auto crs = dynamic_cast(ptr); + if (crs) { + if (crs->coordinateSystem()->axisList().size() == 2) { + return PJ_OBJ_TYPE_GEOGRAPHIC_2D_CRS; + } else { + return PJ_OBJ_TYPE_GEOGRAPHIC_3D_CRS; + } + } + } + + { + auto crs = dynamic_cast(ptr); + if (crs) { + if (crs->isGeocentric()) { + return PJ_OBJ_TYPE_GEOCENTRIC_CRS; + } else { + return PJ_OBJ_TYPE_GEODETIC_CRS; + } + } + } + + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_VERTICAL_CRS; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_PROJECTED_CRS; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_COMPOUND_CRS; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_TEMPORAL_CRS; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_ENGINEERING_CRS; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_BOUND_CRS; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_OTHER_CRS; + } + + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_CONVERSION; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_TRANSFORMATION; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_CONCATENATED_OPERATION; + } + if (dynamic_cast(ptr)) { + return PJ_OBJ_TYPE_OTHER_COORDINATE_OPERATION; + } + + return PJ_OBJ_TYPE_UNKNOWN; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether an object is deprecated. + * + * @param obj Object (must not be NULL) + * @return TRUE if it is deprecated, FALSE otherwise + */ +int proj_obj_is_deprecated(const PJ_OBJ *obj) { + assert(obj); + return obj->obj->isDeprecated(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return a list of non-deprecated objects related to the passed one + * + * @param ctx Context, or NULL for default context. + * @param obj Object (of type CRS for now) for which non-deprecated objects + * must be searched. Must not be NULL + * @return a result set that must be unreferenced with + * proj_obj_list_destroy(), or NULL in case of error. + */ +PJ_OBJ_LIST *proj_obj_get_non_deprecated(PJ_CONTEXT *ctx, const PJ_OBJ *obj) { + assert(obj); + SANITIZE_CTX(ctx); + auto crs = dynamic_cast(obj->obj.get()); + if (!crs) { + return nullptr; + } + try { + std::vector objects; + auto res = crs->getNonDeprecated(getDBcontext(ctx)); + for (const auto &resObj : res) { + objects.push_back(resObj); + } + return new PJ_OBJ_LIST(std::move(objects)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether two objects are equivalent. + * + * @param obj Object (must not be NULL) + * @param other Other object (must not be NULL) + * @param criterion Comparison criterion + * @return TRUE if they are equivalent + */ +int proj_obj_is_equivalent_to(const PJ_OBJ *obj, const PJ_OBJ *other, + PJ_COMPARISON_CRITERION criterion) { + assert(obj); + assert(other); + + // Make sure that the C and C++ enumerations match + static_assert(static_cast(PJ_COMP_STRICT) == + static_cast(IComparable::Criterion::STRICT), + ""); + static_assert(static_cast(PJ_COMP_EQUIVALENT) == + static_cast(IComparable::Criterion::EQUIVALENT), + ""); + static_assert( + static_cast(PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) == + static_cast( + IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS), + ""); + + // Make sure we enumerate all values. If adding a new value, as we + // don't have a default clause, the compiler will warn. + switch (criterion) { + case PJ_COMP_STRICT: + case PJ_COMP_EQUIVALENT: + case PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS: + break; + } + const IComparable::Criterion cppCriterion = + static_cast(criterion); + return obj->obj->isEquivalentTo(other->obj.get(), cppCriterion); +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether an object is a CRS + * + * @param obj Object (must not be NULL) + */ +int proj_obj_is_crs(const PJ_OBJ *obj) { + assert(obj); + return dynamic_cast(obj->obj.get()) != nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Get the name of an object. + * + * The lifetime of the returned string is the same as the input obj parameter. + * + * @param obj Object (must not be NULL) + * @return a string, or NULL in case of error or missing name. + */ +const char *proj_obj_get_name(const PJ_OBJ *obj) { + assert(obj); + const auto &desc = obj->obj->name()->description(); + if (!desc.has_value()) { + return nullptr; + } + // The object will still be alived after the function call. + // cppcheck-suppress stlcstr + return desc->c_str(); +} + +// --------------------------------------------------------------------------- + +/** \brief Get the authority name / codespace of an identifier of an object. + * + * The lifetime of the returned string is the same as the input obj parameter. + * + * @param obj Object (must not be NULL) + * @param index Index of the identifier. 0 = first identifier + * @return a string, or NULL in case of error or missing name. + */ +const char *proj_obj_get_id_auth_name(const PJ_OBJ *obj, int index) { + assert(obj); + const auto &ids = obj->obj->identifiers(); + if (static_cast(index) >= ids.size()) { + return nullptr; + } + const auto &codeSpace = ids[index]->codeSpace(); + if (!codeSpace.has_value()) { + return nullptr; + } + // The object will still be alived after the function call. + // cppcheck-suppress stlcstr + return codeSpace->c_str(); +} + +// --------------------------------------------------------------------------- + +/** \brief Get the code of an identifier of an object. + * + * The lifetime of the returned string is the same as the input obj parameter. + * + * @param obj Object (must not be NULL) + * @param index Index of the identifier. 0 = first identifier + * @return a string, or NULL in case of error or missing name. + */ +const char *proj_obj_get_id_code(const PJ_OBJ *obj, int index) { + assert(obj); + const auto &ids = obj->obj->identifiers(); + if (static_cast(index) >= ids.size()) { + return nullptr; + } + return ids[index]->code().c_str(); +} + +// --------------------------------------------------------------------------- + +/** \brief Get a WKT representation of an object. + * + * The returned string is valid while the input obj parameter is valid, + * and until a next call to proj_obj_as_wkt() with the same input object. + * + * This function calls osgeo::proj::io::IWKTExportable::exportToWKT(). + * + * This function may return NULL if the object is not compatible with an + * export to the requested type. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object (must not be NULL) + * @param type WKT version. + * @param options null-terminated list of options, or NULL. Currently + * supported options are: + *
    + *
  • MULTILINE=YES/NO. Defaults to YES, except for WKT1_ESRI
  • + *
  • INDENTATION_WIDTH=number. Defauls to 4 (when multiline output is + * on).
  • + *
  • OUTPUT_AXIS=AUTO/YES/NO. In AUTO mode, axis will be output for WKT2 + * variants, for WKT1_GDAL for ProjectedCRS with easting/northing ordering + * (otherwise stripped), but not for WKT1_ESRI. Setting to YES will output + * them unconditionaly, and to NO will omit them unconditionaly.
  • + *
+ * @return a string, or NULL in case of error. + */ +const char *proj_obj_as_wkt(PJ_CONTEXT *ctx, const PJ_OBJ *obj, + PJ_WKT_TYPE type, const char *const *options) { + SANITIZE_CTX(ctx); + assert(obj); + + // Make sure that the C and C++ enumerations match + static_assert(static_cast(PJ_WKT2_2015) == + static_cast(WKTFormatter::Convention::WKT2_2015), + ""); + static_assert( + static_cast(PJ_WKT2_2015_SIMPLIFIED) == + static_cast(WKTFormatter::Convention::WKT2_2015_SIMPLIFIED), + ""); + static_assert(static_cast(PJ_WKT2_2018) == + static_cast(WKTFormatter::Convention::WKT2_2018), + ""); + static_assert( + static_cast(PJ_WKT2_2018_SIMPLIFIED) == + static_cast(WKTFormatter::Convention::WKT2_2018_SIMPLIFIED), + ""); + static_assert(static_cast(PJ_WKT1_GDAL) == + static_cast(WKTFormatter::Convention::WKT1_GDAL), + ""); + static_assert(static_cast(PJ_WKT1_ESRI) == + static_cast(WKTFormatter::Convention::WKT1_ESRI), + ""); + // Make sure we enumerate all values. If adding a new value, as we + // don't have a default clause, the compiler will warn. + switch (type) { + case PJ_WKT2_2015: + case PJ_WKT2_2015_SIMPLIFIED: + case PJ_WKT2_2018: + case PJ_WKT2_2018_SIMPLIFIED: + case PJ_WKT1_GDAL: + case PJ_WKT1_ESRI: + break; + } + const WKTFormatter::Convention convention = + static_cast(type); + try { + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + auto formatter = WKTFormatter::create(convention, dbContext); + for (auto iter = options; iter && iter[0]; ++iter) { + const char *value; + if ((value = getOptionValue(*iter, "MULTILINE="))) { + formatter->setMultiLine(ci_equal(value, "YES")); + } else if ((value = getOptionValue(*iter, "INDENTATION_WIDTH="))) { + formatter->setIndentationWidth(std::atoi(value)); + } else if ((value = getOptionValue(*iter, "OUTPUT_AXIS="))) { + if (!ci_equal(value, "AUTO")) { + formatter->setOutputAxis( + ci_equal(value, "YES") + ? WKTFormatter::OutputAxisRule::YES + : WKTFormatter::OutputAxisRule::NO); + } + } else { + std::string msg("Unknown option :"); + msg += *iter; + proj_log_error(ctx, __FUNCTION__, msg.c_str()); + return nullptr; + } + } + obj->lastWKT = obj->obj->exportToWKT(formatter.get()); + return obj->lastWKT.c_str(); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Get a PROJ string representation of an object. + * + * The returned string is valid while the input obj parameter is valid, + * and until a next call to proj_obj_as_proj_string() with the same input + * object. + * + * This function calls + * osgeo::proj::io::IPROJStringExportable::exportToPROJString(). + * + * This function may return NULL if the object is not compatible with an + * export to the requested type. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object (must not be NULL) + * @param type PROJ String version. + * @param options NULL-terminated list of strings with "KEY=VALUE" format. or + * NULL. + * The currently recognized option is USE_ETMERC=YES to use + * +proj=etmerc instead of +proj=tmerc (or USE_ETMERC=NO to disable implicit + * use of etmerc by utm conversions) + * @return a string, or NULL in case of error. + */ +const char *proj_obj_as_proj_string(PJ_CONTEXT *ctx, const PJ_OBJ *obj, + PJ_PROJ_STRING_TYPE type, + const char *const *options) { + SANITIZE_CTX(ctx); + assert(obj); + auto exportable = + dynamic_cast(obj->obj.get()); + if (!exportable) { + proj_log_error(ctx, __FUNCTION__, "Object type not exportable to PROJ"); + return nullptr; + } + // Make sure that the C and C++ enumeration match + static_assert(static_cast(PJ_PROJ_5) == + static_cast(PROJStringFormatter::Convention::PROJ_5), + ""); + static_assert(static_cast(PJ_PROJ_4) == + static_cast(PROJStringFormatter::Convention::PROJ_4), + ""); + // Make sure we enumerate all values. If adding a new value, as we + // don't have a default clause, the compiler will warn. + switch (type) { + case PJ_PROJ_5: + case PJ_PROJ_4: + break; + } + const PROJStringFormatter::Convention convention = + static_cast(type); + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + try { + auto formatter = PROJStringFormatter::create(convention, dbContext); + if (options != nullptr && options[0] != nullptr) { + if (ci_equal(options[0], "USE_ETMERC=YES")) { + formatter->setUseETMercForTMerc(true); + } else if (ci_equal(options[0], "USE_ETMERC=NO")) { + formatter->setUseETMercForTMerc(false); + } + } + obj->lastPROJString = exportable->exportToPROJString(formatter.get()); + return obj->lastPROJString.c_str(); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return the area of use of an object. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object (must not be NULL) + * @param out_west_lon_degree Pointer to a double to receive the west longitude + * (in degrees). Or NULL. If the returned value is -1000, the bounding box is + * unknown. + * @param out_south_lat_degree Pointer to a double to receive the south latitude + * (in degrees). Or NULL. If the returned value is -1000, the bounding box is + * unknown. + * @param out_east_lon_degree Pointer to a double to receive the east longitude + * (in degrees). Or NULL. If the returned value is -1000, the bounding box is + * unknown. + * @param out_north_lat_degree Pointer to a double to receive the north latitude + * (in degrees). Or NULL. If the returned value is -1000, the bounding box is + * unknown. + * @param out_area_name Pointer to a string to receive the name of the area of + * use. Or NULL. *p_area_name is valid while obj is valid itself. + * @return TRUE in case of success, FALSE in case of error or if the area + * of use is unknown. + */ +int proj_obj_get_area_of_use(PJ_CONTEXT *ctx, const PJ_OBJ *obj, + double *out_west_lon_degree, + double *out_south_lat_degree, + double *out_east_lon_degree, + double *out_north_lat_degree, + const char **out_area_name) { + (void)ctx; + if (out_area_name) { + *out_area_name = nullptr; + } + auto objectUsage = dynamic_cast(obj->obj.get()); + if (!objectUsage) { + return false; + } + const auto &domains = objectUsage->domains(); + if (domains.empty()) { + return false; + } + const auto &extent = domains[0]->domainOfValidity(); + if (!extent) { + return false; + } + const auto &desc = extent->description(); + if (desc.has_value() && out_area_name) { + *out_area_name = desc->c_str(); + } + + const auto &geogElements = extent->geographicElements(); + if (!geogElements.empty()) { + auto bbox = + dynamic_cast(geogElements[0].get()); + if (bbox) { + if (out_west_lon_degree) { + *out_west_lon_degree = bbox->westBoundLongitude(); + } + if (out_south_lat_degree) { + *out_south_lat_degree = bbox->southBoundLatitude(); + } + if (out_east_lon_degree) { + *out_east_lon_degree = bbox->eastBoundLongitude(); + } + if (out_north_lat_degree) { + *out_north_lat_degree = bbox->northBoundLatitude(); + } + return true; + } + } + if (out_west_lon_degree) { + *out_west_lon_degree = -1000; + } + if (out_south_lat_degree) { + *out_south_lat_degree = -1000; + } + if (out_east_lon_degree) { + *out_east_lon_degree = -1000; + } + if (out_north_lat_degree) { + *out_north_lat_degree = -1000; + } + return true; +} + +// --------------------------------------------------------------------------- + +static const GeodeticCRS *extractGeodeticCRS(PJ_CONTEXT *ctx, const PJ_OBJ *crs, + const char *fname) { + assert(crs); + auto l_crs = dynamic_cast(crs->obj.get()); + if (!l_crs) { + proj_log_error(ctx, fname, "Object is not a CRS"); + return nullptr; + } + auto geodCRS = l_crs->extractGeodeticCRSRaw(); + if (!geodCRS) { + proj_log_error(ctx, fname, "CRS has no geodetic CRS"); + } + return geodCRS; +} + +// --------------------------------------------------------------------------- + +/** \brief Get the geodeticCRS / geographicCRS from a CRS + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs Objet of type CRS (must not be NULL) + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_crs_get_geodetic_crs(PJ_CONTEXT *ctx, const PJ_OBJ *crs) { + SANITIZE_CTX(ctx); + auto geodCRS = extractGeodeticCRS(ctx, crs, __FUNCTION__); + if (!geodCRS) { + return nullptr; + } + return PJ_OBJ::create(NN_NO_CHECK(nn_dynamic_pointer_cast( + geodCRS->shared_from_this()))); +} + +// --------------------------------------------------------------------------- + +/** \brief Get a CRS component from a CompoundCRS + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs Objet of type CRS (must not be NULL) + * @param index Index of the CRS component (typically 0 = horizontal, 1 = + * vertical) + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_crs_get_sub_crs(PJ_CONTEXT *ctx, const PJ_OBJ *crs, + int index) { + SANITIZE_CTX(ctx); + assert(crs); + auto l_crs = dynamic_cast(crs->obj.get()); + if (!l_crs) { + proj_log_error(ctx, __FUNCTION__, "Object is not a CompoundCRS"); + return nullptr; + } + const auto &components = l_crs->componentReferenceSystems(); + if (static_cast(index) >= components.size()) { + return nullptr; + } + return PJ_OBJ::create(components[index]); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a BoundCRS + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param base_crs Base CRS (must not be NULL) + * @param hub_crs Hub CRS (must not be NULL) + * @param transformation Transformation (must not be NULL) + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_crs_create_bound_crs(PJ_CONTEXT *ctx, const PJ_OBJ *base_crs, + const PJ_OBJ *hub_crs, + const PJ_OBJ *transformation) { + SANITIZE_CTX(ctx); + assert(base_crs); + assert(hub_crs); + assert(transformation); + auto l_base_crs = util::nn_dynamic_pointer_cast(base_crs->obj); + if (!l_base_crs) { + proj_log_error(ctx, __FUNCTION__, "base_crs is not a CRS"); + return nullptr; + } + auto l_hub_crs = util::nn_dynamic_pointer_cast(hub_crs->obj); + if (!l_hub_crs) { + proj_log_error(ctx, __FUNCTION__, "hub_crs is not a CRS"); + return nullptr; + } + auto l_transformation = + util::nn_dynamic_pointer_cast(transformation->obj); + if (!l_transformation) { + proj_log_error(ctx, __FUNCTION__, "transformation is not a CRS"); + return nullptr; + } + try { + return PJ_OBJ::create(BoundCRS::create(NN_NO_CHECK(l_base_crs), + NN_NO_CHECK(l_hub_crs), + NN_NO_CHECK(l_transformation))); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns potentially + * a BoundCRS, with a transformation to EPSG:4326, wrapping this CRS + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * This is the same as method + * osgeo::proj::crs::CRS::createBoundCRSToWGS84IfPossible() + * + * @param ctx PROJ context, or NULL for default context + * @param crs Objet of type CRS (must not be NULL) + * @param options null-terminated list of options, or NULL. Currently + * supported options are: + *
    + *
  • ALLOW_INTERMEDIATE_CRS=YES/NO. Defaults to NO. When set to YES, + * intermediate CRS may be considered when computing the possible + * tranformations. Slower.
  • + *
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, + const PJ_OBJ *crs, + const char *const *options) { + SANITIZE_CTX(ctx); + assert(crs); + auto l_crs = dynamic_cast(crs->obj.get()); + if (!l_crs) { + proj_log_error(ctx, __FUNCTION__, "Object is not a CRS"); + return nullptr; + } + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + try { + bool allowIntermediateCRS = false; + for (auto iter = options; iter && iter[0]; ++iter) { + const char *value; + if ((value = getOptionValue(*iter, "ALLOW_INTERMEDIATE_CRS="))) { + allowIntermediateCRS = ci_equal(value, "YES"); + } else { + std::string msg("Unknown option :"); + msg += *iter; + proj_log_error(ctx, __FUNCTION__, msg.c_str()); + return nullptr; + } + } + return PJ_OBJ::create(l_crs->createBoundCRSToWGS84IfPossible( + dbContext, allowIntermediateCRS)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Get the ellipsoid from a CRS or a GeodeticReferenceFrame. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Objet of type CRS or GeodeticReferenceFrame (must not be NULL) + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_get_ellipsoid(PJ_CONTEXT *ctx, const PJ_OBJ *obj) { + SANITIZE_CTX(ctx); + auto ptr = obj->obj.get(); + if (dynamic_cast(ptr)) { + auto geodCRS = extractGeodeticCRS(ctx, obj, __FUNCTION__); + if (geodCRS) { + return PJ_OBJ::create(geodCRS->ellipsoid()); + } + } else { + auto datum = dynamic_cast(ptr); + if (datum) { + return PJ_OBJ::create(datum->ellipsoid()); + } + } + proj_log_error(ctx, __FUNCTION__, + "Object is not a CRS or GeodeticReferenceFrame"); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Get the horizontal datum from a CRS + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs Objet of type CRS (must not be NULL) + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_crs_get_horizontal_datum(PJ_CONTEXT *ctx, const PJ_OBJ *crs) { + SANITIZE_CTX(ctx); + auto geodCRS = extractGeodeticCRS(ctx, crs, __FUNCTION__); + if (!geodCRS) { + return nullptr; + } + const auto &datum = geodCRS->datum(); + if (datum) { + return PJ_OBJ::create(NN_NO_CHECK(datum)); + } + + const auto &datumEnsemble = geodCRS->datumEnsemble(); + if (datumEnsemble) { + return PJ_OBJ::create(NN_NO_CHECK(datumEnsemble)); + } + proj_log_error(ctx, __FUNCTION__, "CRS has no datum"); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return ellipsoid parameters. + * + * @param ctx PROJ context, or NULL for default context + * @param ellipsoid Object of type Ellipsoid (must not be NULL) + * @param out_semi_major_metre Pointer to a value to store the semi-major axis + * in + * metre. or NULL + * @param out_semi_minor_metre Pointer to a value to store the semi-minor axis + * in + * metre. or NULL + * @param out_is_semi_minor_computed Pointer to a boolean value to indicate if + * the + * semi-minor value was computed. If FALSE, its value comes from the + * definition. or NULL + * @param out_inv_flattening Pointer to a value to store the inverse + * flattening. or NULL + * @return TRUE in case of success. + */ +int proj_obj_ellipsoid_get_parameters(PJ_CONTEXT *ctx, const PJ_OBJ *ellipsoid, + double *out_semi_major_metre, + double *out_semi_minor_metre, + int *out_is_semi_minor_computed, + double *out_inv_flattening) { + SANITIZE_CTX(ctx); + assert(ellipsoid); + auto l_ellipsoid = dynamic_cast(ellipsoid->obj.get()); + if (!l_ellipsoid) { + proj_log_error(ctx, __FUNCTION__, "Object is not a Ellipsoid"); + return FALSE; + } + + if (out_semi_major_metre) { + *out_semi_major_metre = l_ellipsoid->semiMajorAxis().getSIValue(); + } + if (out_semi_minor_metre) { + *out_semi_minor_metre = + l_ellipsoid->computeSemiMinorAxis().getSIValue(); + } + if (out_is_semi_minor_computed) { + *out_is_semi_minor_computed = + !(l_ellipsoid->semiMinorAxis().has_value()); + } + if (out_inv_flattening) { + *out_inv_flattening = l_ellipsoid->computedInverseFlattening(); + } + return TRUE; +} + +// --------------------------------------------------------------------------- + +/** \brief Get the prime meridian of a CRS or a GeodeticReferenceFrame. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Objet of type CRS or GeodeticReferenceFrame (must not be NULL) + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL + * in case of error. + */ + +PJ_OBJ *proj_obj_get_prime_meridian(PJ_CONTEXT *ctx, const PJ_OBJ *obj) { + SANITIZE_CTX(ctx); + auto ptr = obj->obj.get(); + if (dynamic_cast(ptr)) { + auto geodCRS = extractGeodeticCRS(ctx, obj, __FUNCTION__); + if (geodCRS) { + return PJ_OBJ::create(geodCRS->primeMeridian()); + } + } else { + auto datum = dynamic_cast(ptr); + if (datum) { + return PJ_OBJ::create(datum->primeMeridian()); + } + } + proj_log_error(ctx, __FUNCTION__, + "Object is not a CRS or GeodeticReferenceFrame"); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return prime meridian parameters. + * + * @param ctx PROJ context, or NULL for default context + * @param prime_meridian Object of type PrimeMeridian (must not be NULL) + * @param out_longitude Pointer to a value to store the longitude of the prime + * meridian, in its native unit. or NULL + * @param out_unit_conv_factor Pointer to a value to store the conversion + * factor of the prime meridian longitude unit to radian. or NULL + * @param out_unit_name Pointer to a string value to store the unit name. + * or NULL + * @return TRUE in case of success. + */ +int proj_obj_prime_meridian_get_parameters(PJ_CONTEXT *ctx, + const PJ_OBJ *prime_meridian, + double *out_longitude, + double *out_unit_conv_factor, + const char **out_unit_name) { + SANITIZE_CTX(ctx); + assert(prime_meridian); + auto l_pm = dynamic_cast(prime_meridian->obj.get()); + if (!l_pm) { + proj_log_error(ctx, __FUNCTION__, "Object is not a PrimeMeridian"); + return false; + } + const auto &longitude = l_pm->longitude(); + if (out_longitude) { + *out_longitude = longitude.value(); + } + const auto &unit = longitude.unit(); + if (out_unit_conv_factor) { + *out_unit_conv_factor = unit.conversionToSI(); + } + if (out_unit_name) { + *out_unit_name = unit.name().c_str(); + } + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS of a BoundCRS or a DerivedCRS/ProjectedCRS, or + * the source CRS of a CoordinateOperation. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Objet of type BoundCRS or CoordinateOperation (must not be NULL) + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL + * in case of error, or missing source CRS. + */ +PJ_OBJ *proj_obj_get_source_crs(PJ_CONTEXT *ctx, const PJ_OBJ *obj) { + SANITIZE_CTX(ctx); + assert(obj); + auto ptr = obj->obj.get(); + auto boundCRS = dynamic_cast(ptr); + if (boundCRS) { + return PJ_OBJ::create(boundCRS->baseCRS()); + } + auto derivedCRS = dynamic_cast(ptr); + if (derivedCRS) { + return PJ_OBJ::create(derivedCRS->baseCRS()); + } + auto co = dynamic_cast(ptr); + if (co) { + auto sourceCRS = co->sourceCRS(); + if (sourceCRS) { + return PJ_OBJ::create(NN_NO_CHECK(sourceCRS)); + } + return nullptr; + } + proj_log_error(ctx, __FUNCTION__, + "Object is not a BoundCRS or a CoordinateOperation"); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the hub CRS of a BoundCRS or the target CRS of a + * CoordinateOperation. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Objet of type BoundCRS or CoordinateOperation (must not be NULL) + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL + * in case of error, or missing target CRS. + */ +PJ_OBJ *proj_obj_get_target_crs(PJ_CONTEXT *ctx, const PJ_OBJ *obj) { + SANITIZE_CTX(ctx); + assert(obj); + auto ptr = obj->obj.get(); + auto boundCRS = dynamic_cast(ptr); + if (boundCRS) { + return PJ_OBJ::create(boundCRS->hubCRS()); + } + auto co = dynamic_cast(ptr); + if (co) { + auto targetCRS = co->targetCRS(); + if (targetCRS) { + return PJ_OBJ::create(NN_NO_CHECK(targetCRS)); + } + return nullptr; + } + proj_log_error(ctx, __FUNCTION__, + "Object is not a BoundCRS or a CoordinateOperation"); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are either hard-coded, or looked in the database when + * authorityFactory is not null. + * + * The method returns a list of matching reference CRS, and the percentage + * (0-100) of confidence in the match. The list is sorted by decreasing + * confidence. + * + * 100% means that the name of the reference entry + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent), but the names do not match at all. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * Other confidence values may be returned by some specialized implementations. + * + * This is implemented for GeodeticCRS, ProjectedCRS, VerticalCRS and + * CompoundCRS. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object of type CRS. Must not be NULL + * @param auth_name Authority name, or NULL for all authorities + * @param options Placeholder for future options. Should be set to NULL. + * @param out_confidence Output parameter. Pointer to an array of integers that + * will be allocated by the function and filled with the confidence values + * (0-100). There are as many elements in this array as + * proj_obj_list_get_count() + * returns on the return value of this function. *confidence should be + * released with proj_int_list_destroy(). + * @return a list of matching reference CRS, or nullptr in case of error. + */ +PJ_OBJ_LIST *proj_obj_identify(PJ_CONTEXT *ctx, const PJ_OBJ *obj, + const char *auth_name, + const char *const *options, + int **out_confidence) { + SANITIZE_CTX(ctx); + assert(obj); + (void)options; + if (out_confidence) { + *out_confidence = nullptr; + } + auto ptr = obj->obj.get(); + auto crs = dynamic_cast(ptr); + if (!crs) { + proj_log_error(ctx, __FUNCTION__, "Object is not a CRS"); + } else { + int *confidenceTemp = nullptr; + try { + auto factory = AuthorityFactory::create(getDBcontext(ctx), + auth_name ? auth_name : ""); + auto res = crs->identify(factory); + std::vector objects; + confidenceTemp = out_confidence ? new int[res.size()] : nullptr; + size_t i = 0; + for (const auto &pair : res) { + objects.push_back(pair.first); + if (confidenceTemp) { + confidenceTemp[i] = pair.second; + ++i; + } + } + auto ret = internal::make_unique(std::move(objects)); + if (out_confidence) { + *out_confidence = confidenceTemp; + confidenceTemp = nullptr; + } + return ret.release(); + } catch (const std::exception &e) { + delete[] confidenceTemp; + proj_log_error(ctx, __FUNCTION__, e.what()); + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Free an array of integer. */ +void proj_int_list_destroy(int *list) { delete[] list; } + +// --------------------------------------------------------------------------- + +/** \brief Return the list of authorities used in the database. + * + * The returned list is NULL terminated and must be freed with + * proj_string_list_destroy(). + * + * @param ctx PROJ context, or NULL for default context + * + * @return a NULL terminated list of NUL-terminated strings that must be + * freed with proj_string_list_destroy(), or NULL in case of error. + */ +PROJ_STRING_LIST proj_get_authorities_from_database(PJ_CONTEXT *ctx) { + SANITIZE_CTX(ctx); + try { + return to_string_list(getDBcontext(ctx)->getAuthorities()); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the set of authority codes of the given object type. + * + * The returned list is NULL terminated and must be freed with + * proj_string_list_destroy(). + * + * @param ctx PROJ context, or NULL for default context. + * @param auth_name Authority name (must not be NULL) + * @param type Object type. + * @param allow_deprecated whether we should return deprecated objects as well. + * + * @return a NULL terminated list of NUL-terminated strings that must be + * freed with proj_string_list_destroy(), or NULL in case of error. + */ +PROJ_STRING_LIST proj_get_codes_from_database(PJ_CONTEXT *ctx, + const char *auth_name, + PJ_OBJ_TYPE type, + int allow_deprecated) { + assert(auth_name); + SANITIZE_CTX(ctx); + try { + auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name); + bool valid = false; + auto typeInternal = convertPJObjectTypeToObjectType(type, valid); + if (!valid) { + return nullptr; + } + return to_string_list( + factory->getAuthorityCodes(typeInternal, allow_deprecated != 0)); + + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** Free a list of NULL terminated strings. */ +void proj_string_list_destroy(PROJ_STRING_LIST list) { + if (list) { + for (size_t i = 0; list[i] != nullptr; i++) { + delete[] list[i]; + } + delete[] list; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return the Conversion of a DerivedCRS (such as a ProjectedCRS), + * or the Transformation from the baseCRS to the hubCRS of a BoundCRS + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs Objet of type DerivedCRS or BoundCRSs (must not be NULL) + * @return Object of type SingleOperation that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_crs_get_coordoperation(PJ_CONTEXT *ctx, const PJ_OBJ *crs) { + SANITIZE_CTX(ctx); + assert(crs); + SingleOperationPtr co; + + auto derivedCRS = dynamic_cast(crs->obj.get()); + if (derivedCRS) { + co = derivedCRS->derivingConversion().as_nullable(); + } else { + auto boundCRS = dynamic_cast(crs->obj.get()); + if (boundCRS) { + co = boundCRS->transformation().as_nullable(); + } else { + proj_log_error(ctx, __FUNCTION__, + "Object is not a DerivedCRS or BoundCRS"); + return nullptr; + } + } + + return PJ_OBJ::create(NN_NO_CHECK(co)); +} + +// --------------------------------------------------------------------------- + +/** \brief Return informatin on the operation method of the SingleOperation. + * + * @param ctx PROJ context, or NULL for default context + * @param coordoperation Objet of type SingleOperation (typically a Conversion + * or Transformation) (must not be NULL) + * @param out_method_name Pointer to a string value to store the method + * (projection) name. or NULL + * @param out_method_auth_name Pointer to a string value to store the method + * authority name. or NULL + * @param out_method_code Pointer to a string value to store the method + * code. or NULL + * @return TRUE in case of success. + */ +int proj_coordoperation_get_method_info(PJ_CONTEXT *ctx, + const PJ_OBJ *coordoperation, + const char **out_method_name, + const char **out_method_auth_name, + const char **out_method_code) { + SANITIZE_CTX(ctx); + assert(coordoperation); + + auto singleOp = + dynamic_cast(coordoperation->obj.get()); + if (!singleOp) { + proj_log_error(ctx, __FUNCTION__, + "Object is not a DerivedCRS or BoundCRS"); + return false; + } + + const auto &method = singleOp->method(); + const auto &method_ids = method->identifiers(); + if (out_method_name) { + *out_method_name = method->name()->description()->c_str(); + } + if (out_method_auth_name) { + if (!method_ids.empty()) { + *out_method_auth_name = method_ids[0]->codeSpace()->c_str(); + } else { + *out_method_auth_name = nullptr; + } + } + if (out_method_code) { + if (!method_ids.empty()) { + *out_method_code = method_ids[0]->code().c_str(); + } else { + *out_method_code = nullptr; + } + } + return true; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static PropertyMap createPropertyMapName(const char *c_name) { + std::string name(c_name ? c_name : "unnamed"); + PropertyMap properties; + if (ends_with(name, " (deprecated)")) { + name.resize(name.size() - strlen(" (deprecated)")); + properties.set(common::IdentifiedObject::DEPRECATED_KEY, true); + } + return properties.set(common::IdentifiedObject::NAME_KEY, name); +} + +// --------------------------------------------------------------------------- + +static UnitOfMeasure createLinearUnit(const char *name, double convFactor, + const char *unit_auth_name = nullptr, + const char *unit_code = nullptr) { + return name == nullptr + ? UnitOfMeasure::METRE + : UnitOfMeasure(name, convFactor, UnitOfMeasure::Type::LINEAR, + unit_auth_name ? unit_auth_name : "", + unit_code ? unit_code : ""); +} + +// --------------------------------------------------------------------------- + +static UnitOfMeasure createAngularUnit(const char *name, double convFactor, + const char *unit_auth_name = nullptr, + const char *unit_code = nullptr) { + return name ? (ci_equal(name, "degree") + ? UnitOfMeasure::DEGREE + : ci_equal(name, "grad") + ? UnitOfMeasure::GRAD + : UnitOfMeasure(name, convFactor, + UnitOfMeasure::Type::ANGULAR, + unit_auth_name ? unit_auth_name + : "", + unit_code ? unit_code : "")) + : UnitOfMeasure::DEGREE; +} + +// --------------------------------------------------------------------------- + +static GeodeticReferenceFrameNNPtr createGeodeticReferenceFrame( + PJ_CONTEXT *ctx, const char *datum_name, const char *ellps_name, + double semi_major_metre, double inv_flattening, + const char *prime_meridian_name, double prime_meridian_offset, + const char *angular_units, double angular_units_conv) { + const UnitOfMeasure angUnit( + createAngularUnit(angular_units, angular_units_conv)); + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + auto body = Ellipsoid::guessBodyName(dbContext, semi_major_metre); + auto ellpsName = createPropertyMapName(ellps_name); + auto ellps = inv_flattening != 0.0 + ? Ellipsoid::createFlattenedSphere( + ellpsName, Length(semi_major_metre), + Scale(inv_flattening), body) + : Ellipsoid::createSphere(ellpsName, + Length(semi_major_metre), body); + auto pm = PrimeMeridian::create( + PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + prime_meridian_name + ? prime_meridian_name + : prime_meridian_offset == 0.0 + ? (ellps->celestialBody() == Ellipsoid::EARTH + ? PrimeMeridian::GREENWICH->nameStr().c_str() + : PrimeMeridian::REFERENCE_MERIDIAN->nameStr() + .c_str()) + : "unnamed"), + Angle(prime_meridian_offset, angUnit)); + + std::string datumName(datum_name ? datum_name : "unnamed"); + if (datumName == "WGS_1984") { + datumName = GeodeticReferenceFrame::EPSG_6326->nameStr(); + } else if (datumName.find('_') != std::string::npos) { + // Likely coming from WKT1 + if (dbContext) { + auto authFactory = + AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string()); + auto res = authFactory->createObjectsFromName( + datumName, + {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, true, + 1); + if (!res.empty()) { + const auto &refDatum = res.front(); + if (metadata::Identifier::isEquivalentName( + datumName.c_str(), refDatum->nameStr().c_str())) { + datumName = refDatum->nameStr(); + } + } else { + std::string outTableName; + std::string authNameFromAlias; + std::string codeFromAlias; + auto officialName = authFactory->getOfficialNameFromAlias( + datumName, "geodetic_datum", std::string(), true, + outTableName, authNameFromAlias, codeFromAlias); + if (!officialName.empty()) { + datumName = officialName; + } + } + } + } + + return GeodeticReferenceFrame::create( + createPropertyMapName(datumName.c_str()), ellps, + util::optional(), pm); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Create a GeographicCRS. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs_name Name of the GeographicCRS. Or NULL + * @param datum_name Name of the GeodeticReferenceFrame. Or NULL + * @param ellps_name Name of the Ellipsoid. Or NULL + * @param semi_major_metre Ellipsoid semi-major axis, in metres. + * @param inv_flattening Ellipsoid inverse flattening. Or 0 for a sphere. + * @param prime_meridian_name Name of the PrimeMeridian. Or NULL + * @param prime_meridian_offset Offset of the prime meridian, expressed in the + * specified angular units. + * @param pm_angular_units Name of the angular units. Or NULL for Degree + * @param pm_angular_units_conv Conversion factor from the angular unit to + * radian. + * Or + * 0 for Degree if pm_angular_units == NULL. Otherwise should be not NULL + * @param ellipsoidal_cs Coordinate system. Must not be NULL. + * + * @return Object of type GeographicCRS that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_create_geographic_crs( + PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, + const char *ellps_name, double semi_major_metre, double inv_flattening, + const char *prime_meridian_name, double prime_meridian_offset, + const char *pm_angular_units, double pm_angular_units_conv, + PJ_OBJ *ellipsoidal_cs) { + + SANITIZE_CTX(ctx); + auto cs = util::nn_dynamic_pointer_cast(ellipsoidal_cs->obj); + if (!cs) { + return nullptr; + } + try { + auto datum = createGeodeticReferenceFrame( + ctx, datum_name, ellps_name, semi_major_metre, inv_flattening, + prime_meridian_name, prime_meridian_offset, pm_angular_units, + pm_angular_units_conv); + auto geogCRS = GeographicCRS::create(createPropertyMapName(crs_name), + datum, NN_NO_CHECK(cs)); + return PJ_OBJ::create(geogCRS); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Create a GeographicCRS. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs_name Name of the GeographicCRS. Or NULL + * @param datum Datum. Must not be NULL. + * @param ellipsoidal_cs Coordinate system. Must not be NULL. + * + * @return Object of type GeographicCRS that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_create_geographic_crs_from_datum(PJ_CONTEXT *ctx, + const char *crs_name, + PJ_OBJ *datum, + PJ_OBJ *ellipsoidal_cs) { + + SANITIZE_CTX(ctx); + auto l_datum = + util::nn_dynamic_pointer_cast(datum->obj); + if (!l_datum) { + proj_log_error(ctx, __FUNCTION__, + "datum is not a GeodeticReferenceFrame"); + return nullptr; + } + auto cs = util::nn_dynamic_pointer_cast(ellipsoidal_cs->obj); + if (!cs) { + return nullptr; + } + try { + auto geogCRS = + GeographicCRS::create(createPropertyMapName(crs_name), + NN_NO_CHECK(l_datum), NN_NO_CHECK(cs)); + return PJ_OBJ::create(geogCRS); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Create a GeodeticCRS of geocentric type. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs_name Name of the GeographicCRS. Or NULL + * @param datum_name Name of the GeodeticReferenceFrame. Or NULL + * @param ellps_name Name of the Ellipsoid. Or NULL + * @param semi_major_metre Ellipsoid semi-major axis, in metres. + * @param inv_flattening Ellipsoid inverse flattening. Or 0 for a sphere. + * @param prime_meridian_name Name of the PrimeMeridian. Or NULL + * @param prime_meridian_offset Offset of the prime meridian, expressed in the + * specified angular units. + * @param angular_units Name of the angular units. Or NULL for Degree + * @param angular_units_conv Conversion factor from the angular unit to radian. + * Or + * 0 for Degree if angular_units == NULL. Otherwise should be not NULL + * @param linear_units Name of the linear units. Or NULL for Metre + * @param linear_units_conv Conversion factor from the linear unit to metre. Or + * 0 for Metre if linear_units == NULL. Otherwise should be not NULL + * + * @return Object of type GeodeticCRS that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_create_geocentric_crs( + PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, + const char *ellps_name, double semi_major_metre, double inv_flattening, + const char *prime_meridian_name, double prime_meridian_offset, + const char *angular_units, double angular_units_conv, + const char *linear_units, double linear_units_conv) { + + SANITIZE_CTX(ctx); + try { + const UnitOfMeasure linearUnit( + createLinearUnit(linear_units, linear_units_conv)); + auto datum = createGeodeticReferenceFrame( + ctx, datum_name, ellps_name, semi_major_metre, inv_flattening, + prime_meridian_name, prime_meridian_offset, angular_units, + angular_units_conv); + + auto geodCRS = + GeodeticCRS::create(createPropertyMapName(crs_name), datum, + cs::CartesianCS::createGeocentric(linearUnit)); + return PJ_OBJ::create(geodCRS); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Create a GeodeticCRS of geocentric type. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs_name Name of the GeographicCRS. Or NULL + * @param datum Datum. Must not be NULL. + * @param linear_units Name of the linear units. Or NULL for Metre + * @param linear_units_conv Conversion factor from the linear unit to metre. Or + * 0 for Metre if linear_units == NULL. Otherwise should be not NULL + * + * @return Object of type GeodeticCRS that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_create_geocentric_crs_from_datum(PJ_CONTEXT *ctx, + const char *crs_name, + const PJ_OBJ *datum, + const char *linear_units, + double linear_units_conv) { + SANITIZE_CTX(ctx); + try { + const UnitOfMeasure linearUnit( + createLinearUnit(linear_units, linear_units_conv)); + auto l_datum = + util::nn_dynamic_pointer_cast(datum->obj); + if (!l_datum) { + proj_log_error(ctx, __FUNCTION__, + "datum is not a GeodeticReferenceFrame"); + return nullptr; + } + auto geodCRS = GeodeticCRS::create( + createPropertyMapName(crs_name), NN_NO_CHECK(l_datum), + cs::CartesianCS::createGeocentric(linearUnit)); + return PJ_OBJ::create(geodCRS); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Create a VerticalCRS + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs_name Name of the GeographicCRS. Or NULL + * @param datum_name Name of the VerticalReferenceFrame. Or NULL + * @param linear_units Name of the linear units. Or NULL for Metre + * @param linear_units_conv Conversion factor from the linear unit to metre. Or + * 0 for Metre if linear_units == NULL. Otherwise should be not NULL + * + * @return Object of type VerticalCRS that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_create_vertical_crs(PJ_CONTEXT *ctx, const char *crs_name, + const char *datum_name, + const char *linear_units, + double linear_units_conv) { + + SANITIZE_CTX(ctx); + try { + const UnitOfMeasure linearUnit( + createLinearUnit(linear_units, linear_units_conv)); + auto datum = + VerticalReferenceFrame::create(createPropertyMapName(datum_name)); + auto vertCRS = VerticalCRS::create( + createPropertyMapName(crs_name), datum, + cs::VerticalCS::createGravityRelatedHeight(linearUnit)); + return PJ_OBJ::create(vertCRS); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Create a CompoundCRS + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs_name Name of the GeographicCRS. Or NULL + * @param horiz_crs Horizontal CRS. must not be NULL. + * @param vert_crs Vertical CRS. must not be NULL. + * + * @return Object of type CompoundCRS that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_create_compound_crs(PJ_CONTEXT *ctx, const char *crs_name, + PJ_OBJ *horiz_crs, PJ_OBJ *vert_crs) { + + assert(horiz_crs); + assert(vert_crs); + SANITIZE_CTX(ctx); + auto l_horiz_crs = util::nn_dynamic_pointer_cast(horiz_crs->obj); + if (!l_horiz_crs) { + return nullptr; + } + auto l_vert_crs = util::nn_dynamic_pointer_cast(vert_crs->obj); + if (!l_vert_crs) { + return nullptr; + } + try { + auto compoundCRS = CompoundCRS::create( + createPropertyMapName(crs_name), + {NN_NO_CHECK(l_horiz_crs), NN_NO_CHECK(l_vert_crs)}); + return PJ_OBJ::create(compoundCRS); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a copy of the object with its name changed + * + * Currently, only implemented on CRS objects. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object of type CRS. Must not be NULL + * @param name New name. Must not be NULL + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ PROJ_DLL *proj_obj_alter_name(PJ_CONTEXT *ctx, const PJ_OBJ *obj, + const char *name) { + SANITIZE_CTX(ctx); + auto crs = dynamic_cast(obj->obj.get()); + if (!crs) { + return nullptr; + } + try { + return PJ_OBJ::create(crs->alterName(name)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a copy of the object with its identifier changed/set + * + * Currently, only implemented on CRS objects. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object of type CRS. Must not be NULL + * @param auth_name Authority name. Must not be NULL + * @param code Code. Must not be NULL + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ PROJ_DLL *proj_obj_alter_id(PJ_CONTEXT *ctx, const PJ_OBJ *obj, + const char *auth_name, const char *code) { + SANITIZE_CTX(ctx); + auto crs = dynamic_cast(obj->obj.get()); + if (!crs) { + return nullptr; + } + try { + return PJ_OBJ::create(crs->alterId(auth_name, code)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a copy of the CRS with its geodetic CRS changed + * + * Currently, when obj is a GeodeticCRS, it returns a clone of new_geod_crs + * When obj is a ProjectedCRS, it replaces its base CRS with new_geod_crs. + * When obj is a CompoundCRS, it replaces the GeodeticCRS part of the horizontal + * CRS with new_geod_crs. + * In other cases, it returns a clone of obj. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object of type CRS. Must not be NULL + * @param new_geod_crs Object of type GeodeticCRS. Must not be NULL + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_crs_alter_geodetic_crs(PJ_CONTEXT *ctx, const PJ_OBJ *obj, + const PJ_OBJ *new_geod_crs) { + SANITIZE_CTX(ctx); + auto l_new_geod_crs = + util::nn_dynamic_pointer_cast(new_geod_crs->obj); + if (!l_new_geod_crs) { + proj_log_error(ctx, __FUNCTION__, "new_geod_crs is not a GeodeticCRS"); + return nullptr; + } + + auto crs = dynamic_cast(obj->obj.get()); + if (!crs) { + proj_log_error(ctx, __FUNCTION__, "obj is not a CRS"); + return nullptr; + } + + try { + return PJ_OBJ::create( + crs->alterGeodeticCRS(NN_NO_CHECK(l_new_geod_crs))); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return a copy of the CRS with its angular units changed + * + * The CRS must be or contain a GeographicCRS. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object of type CRS. Must not be NULL + * @param angular_units Name of the angular units. Or NULL for Degree + * @param angular_units_conv Conversion factor from the angular unit to radian. + * Or 0 for Degree if angular_units == NULL. Otherwise should be not NULL + * @param unit_auth_name Unit authority name. Or NULL. + * @param unit_code Unit code. Or NULL. + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_crs_alter_cs_angular_unit(PJ_CONTEXT *ctx, const PJ_OBJ *obj, + const char *angular_units, + double angular_units_conv, + const char *unit_auth_name, + const char *unit_code) { + + SANITIZE_CTX(ctx); + auto geodCRS = proj_obj_crs_get_geodetic_crs(ctx, obj); + if (!geodCRS) { + return nullptr; + } + auto geogCRS = dynamic_cast(geodCRS->obj.get()); + if (!geogCRS) { + proj_obj_destroy(geodCRS); + return nullptr; + } + + PJ_OBJ *geogCRSAltered = nullptr; + try { + const UnitOfMeasure angUnit(createAngularUnit( + angular_units, angular_units_conv, unit_auth_name, unit_code)); + geogCRSAltered = PJ_OBJ::create(GeographicCRS::create( + createPropertyMapName(proj_obj_get_name(geodCRS)), geogCRS->datum(), + geogCRS->datumEnsemble(), + geogCRS->coordinateSystem()->alterAngularUnit(angUnit))); + proj_obj_destroy(geodCRS); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + proj_obj_destroy(geodCRS); + return nullptr; + } + + auto ret = proj_obj_crs_alter_geodetic_crs(ctx, obj, geogCRSAltered); + proj_obj_destroy(geogCRSAltered); + return ret; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a copy of the CRS with the linear units of its coordinate + * system changed + * + * The CRS must be or contain a ProjectedCRS, VerticalCRS or a GeocentricCRS. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object of type CRS. Must not be NULL + * @param linear_units Name of the linear units. Or NULL for Metre + * @param linear_units_conv Conversion factor from the linear unit to metre. Or + * 0 for Metre if linear_units == NULL. Otherwise should be not NULL + * @param unit_auth_name Unit authority name. Or NULL. + * @param unit_code Unit code. Or NULL. + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_crs_alter_cs_linear_unit(PJ_CONTEXT *ctx, const PJ_OBJ *obj, + const char *linear_units, + double linear_units_conv, + const char *unit_auth_name, + const char *unit_code) { + SANITIZE_CTX(ctx); + auto crs = dynamic_cast(obj->obj.get()); + if (!crs) { + return nullptr; + } + + try { + const UnitOfMeasure linearUnit(createLinearUnit( + linear_units, linear_units_conv, unit_auth_name, unit_code)); + return PJ_OBJ::create(crs->alterCSLinearUnit(linearUnit)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return a copy of the CRS with the lineaer units of the parameters + * of its conversion modified. + * + * The CRS must be or contain a ProjectedCRS, VerticalCRS or a GeocentricCRS. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object of type ProjectedCRS. Must not be NULL + * @param linear_units Name of the linear units. Or NULL for Metre + * @param linear_units_conv Conversion factor from the linear unit to metre. Or + * 0 for Metre if linear_units == NULL. Otherwise should be not NULL + * @param unit_auth_name Unit authority name. Or NULL. + * @param unit_code Unit code. Or NULL. + * @param convert_to_new_unit TRUE if exisiting values should be converted from + * their current unit to the new unit. If FALSE, their value will be left + * unchanged and the unit overriden (so the resulting CRS will not be + * equivalent to the original one for reprojection purposes). + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_crs_alter_parameters_linear_unit( + PJ_CONTEXT *ctx, const PJ_OBJ *obj, const char *linear_units, + double linear_units_conv, const char *unit_auth_name, const char *unit_code, + int convert_to_new_unit) { + SANITIZE_CTX(ctx); + auto crs = dynamic_cast(obj->obj.get()); + if (!crs) { + return nullptr; + } + + try { + const UnitOfMeasure linearUnit(createLinearUnit( + linear_units, linear_units_conv, unit_auth_name, unit_code)); + return PJ_OBJ::create(crs->alterParametersLinearUnit( + linearUnit, convert_to_new_unit == TRUE)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EngineeringCRS with just a name + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs_name CRS name. Or NULL. + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ PROJ_DLL *proj_obj_create_engineering_crs(PJ_CONTEXT *ctx, + const char *crs_name) { + SANITIZE_CTX(ctx); + try { + return PJ_OBJ::create(EngineeringCRS::create( + createPropertyMapName(crs_name), + EngineeringDatum::create(PropertyMap()), + CartesianCS::createEastingNorthing(UnitOfMeasure::METRE))); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static void setSingleOperationElements( + const char *name, const char *auth_name, const char *code, + const char *method_name, const char *method_auth_name, + const char *method_code, int param_count, + const PJ_PARAM_DESCRIPTION *params, PropertyMap &propSingleOp, + PropertyMap &propMethod, std::vector ¶meters, + std::vector &values) { + propSingleOp.set(common::IdentifiedObject::NAME_KEY, + name ? name : "unnamed"); + if (auth_name && code) { + propSingleOp.set(metadata::Identifier::CODESPACE_KEY, auth_name) + .set(metadata::Identifier::CODE_KEY, code); + } + + propMethod.set(common::IdentifiedObject::NAME_KEY, + method_name ? method_name : "unnamed"); + if (method_auth_name && method_code) { + propMethod.set(metadata::Identifier::CODESPACE_KEY, method_auth_name) + .set(metadata::Identifier::CODE_KEY, method_code); + } + + for (int i = 0; i < param_count; i++) { + PropertyMap propParam; + propParam.set(common::IdentifiedObject::NAME_KEY, + params[i].name ? params[i].name : "unnamed"); + if (params[i].auth_name && params[i].code) { + propParam + .set(metadata::Identifier::CODESPACE_KEY, params[i].auth_name) + .set(metadata::Identifier::CODE_KEY, params[i].code); + } + parameters.emplace_back(OperationParameter::create(propParam)); + auto unit_type = UnitOfMeasure::Type::UNKNOWN; + switch (params[i].unit_type) { + case PJ_UT_ANGULAR: + unit_type = UnitOfMeasure::Type::ANGULAR; + break; + case PJ_UT_LINEAR: + unit_type = UnitOfMeasure::Type::LINEAR; + break; + case PJ_UT_SCALE: + unit_type = UnitOfMeasure::Type::SCALE; + break; + case PJ_UT_TIME: + unit_type = UnitOfMeasure::Type::TIME; + break; + case PJ_UT_PARAMETRIC: + unit_type = UnitOfMeasure::Type::PARAMETRIC; + break; + } + + Measure measure( + params[i].value, + params[i].unit_type == PJ_UT_ANGULAR + ? createAngularUnit(params[i].unit_name, + params[i].unit_conv_factor) + : params[i].unit_type == PJ_UT_LINEAR + ? createLinearUnit(params[i].unit_name, + params[i].unit_conv_factor) + : UnitOfMeasure(params[i].unit_name ? params[i].unit_name + : "unnamed", + params[i].unit_conv_factor, unit_type)); + values.emplace_back(ParameterValue::create(measure)); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Conversion + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param name Conversion name. Or NULL. + * @param auth_name Conversion authority name. Or NULL. + * @param code Conversion code. Or NULL. + * @param method_name Method name. Or NULL. + * @param method_auth_name Method authority name. Or NULL. + * @param method_code Method code. Or NULL. + * @param param_count Number of parameters (size of params argument) + * @param params Parameter descriptions (array of size param_count) + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ + +PJ_OBJ *proj_obj_create_conversion(PJ_CONTEXT *ctx, const char *name, + const char *auth_name, const char *code, + const char *method_name, + const char *method_auth_name, + const char *method_code, int param_count, + const PJ_PARAM_DESCRIPTION *params) { + SANITIZE_CTX(ctx); + try { + PropertyMap propSingleOp; + PropertyMap propMethod; + std::vector parameters; + std::vector values; + + setSingleOperationElements( + name, auth_name, code, method_name, method_auth_name, method_code, + param_count, params, propSingleOp, propMethod, parameters, values); + + return PJ_OBJ::create( + Conversion::create(propSingleOp, propMethod, parameters, values)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Transformation + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param name Transformation name. Or NULL. + * @param auth_name Transformation authority name. Or NULL. + * @param code Transformation code. Or NULL. + * @param source_crs Object of type CRS representing the source CRS. + * Must not be NULL. + * @param target_crs Object of type CRS representing the target CRS. + * Must not be NULL. + * @param interpolation_crs Object of type CRS representing the interpolation + * CRS. Or NULL. + * @param method_name Method name. Or NULL. + * @param method_auth_name Method authority name. Or NULL. + * @param method_code Method code. Or NULL. + * @param param_count Number of parameters (size of params argument) + * @param params Parameter descriptions (array of size param_count) + * @param accuracy Accuracy of the transformation in meters. A negative + * values means unknown. + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ + +PJ_OBJ *proj_obj_create_transformation( + PJ_CONTEXT *ctx, const char *name, const char *auth_name, const char *code, + PJ_OBJ *source_crs, PJ_OBJ *target_crs, PJ_OBJ *interpolation_crs, + const char *method_name, const char *method_auth_name, + const char *method_code, int param_count, + const PJ_PARAM_DESCRIPTION *params, double accuracy) { + SANITIZE_CTX(ctx); + assert(source_crs); + assert(target_crs); + + auto l_sourceCRS = util::nn_dynamic_pointer_cast(source_crs->obj); + if (!l_sourceCRS) { + proj_log_error(ctx, __FUNCTION__, "source_crs is not a CRS"); + return nullptr; + } + + auto l_targetCRS = util::nn_dynamic_pointer_cast(target_crs->obj); + if (!l_targetCRS) { + proj_log_error(ctx, __FUNCTION__, "target_crs is not a CRS"); + return nullptr; + } + + CRSPtr l_interpolationCRS; + if (interpolation_crs) { + l_interpolationCRS = + util::nn_dynamic_pointer_cast(interpolation_crs->obj); + if (!l_interpolationCRS) { + proj_log_error(ctx, __FUNCTION__, "interpolation_crs is not a CRS"); + return nullptr; + } + } + + try { + PropertyMap propSingleOp; + PropertyMap propMethod; + std::vector parameters; + std::vector values; + + setSingleOperationElements( + name, auth_name, code, method_name, method_auth_name, method_code, + param_count, params, propSingleOp, propMethod, parameters, values); + + std::vector accuracies; + if (accuracy >= 0.0) { + accuracies.emplace_back( + PositionalAccuracy::create(toString(accuracy))); + } + + return PJ_OBJ::create(Transformation::create( + propSingleOp, NN_NO_CHECK(l_sourceCRS), NN_NO_CHECK(l_targetCRS), + l_interpolationCRS, propMethod, parameters, values, accuracies)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** + * \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 ctx PROJ context, or NULL for default context + * @param conversion Object of type Conversion. Must not be NULL. + * @param new_method_epsg_code EPSG code of the target method. Or 0 (in which + * case new_method_name must be specified). + * @param new_method_name EPSG or PROJ target method name. Or nullptr (in which + * case new_method_epsg_code must be specified). + * @return new conversion that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_convert_conversion_to_other_method( + PJ_CONTEXT *ctx, const PJ_OBJ *conversion, int new_method_epsg_code, + const char *new_method_name) { + SANITIZE_CTX(ctx); + auto conv = dynamic_cast(conversion->obj.get()); + if (!conv) { + proj_log_error(ctx, __FUNCTION__, "not a Conversion"); + return nullptr; + } + if (new_method_epsg_code == 0) { + if (!new_method_name) { + return nullptr; + } + if (metadata::Identifier::isEquivalentName( + new_method_name, EPSG_NAME_METHOD_MERCATOR_VARIANT_A)) { + new_method_epsg_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_A; + } else if (metadata::Identifier::isEquivalentName( + new_method_name, EPSG_NAME_METHOD_MERCATOR_VARIANT_B)) { + new_method_epsg_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_B; + } else if (metadata::Identifier::isEquivalentName( + new_method_name, + EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) { + new_method_epsg_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP; + } else if (metadata::Identifier::isEquivalentName( + new_method_name, + EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP)) { + new_method_epsg_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP; + } + } + try { + auto new_conv = conv->convertToOtherMethod(new_method_epsg_code); + if (!new_conv) + return nullptr; + return PJ_OBJ::create(NN_NO_CHECK(new_conv)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static CoordinateSystemAxisNNPtr createAxis(const PJ_AXIS_DESCRIPTION &axis) { + const auto dir = + axis.direction ? AxisDirection::valueOf(axis.direction) : nullptr; + if (dir == nullptr) + throw Exception("invalid value for axis direction"); + auto unit_type = UnitOfMeasure::Type::UNKNOWN; + switch (axis.unit_type) { + case PJ_UT_ANGULAR: + unit_type = UnitOfMeasure::Type::ANGULAR; + break; + case PJ_UT_LINEAR: + unit_type = UnitOfMeasure::Type::LINEAR; + break; + case PJ_UT_SCALE: + unit_type = UnitOfMeasure::Type::SCALE; + break; + case PJ_UT_TIME: + unit_type = UnitOfMeasure::Type::TIME; + break; + case PJ_UT_PARAMETRIC: + unit_type = UnitOfMeasure::Type::PARAMETRIC; + break; + } + auto unit = + axis.unit_type == PJ_UT_ANGULAR + ? createAngularUnit(axis.unit_name, axis.unit_conv_factor) + : axis.unit_type == PJ_UT_LINEAR + ? createLinearUnit(axis.unit_name, axis.unit_conv_factor) + : UnitOfMeasure(axis.unit_name ? axis.unit_name : "unnamed", + axis.unit_conv_factor, unit_type); + + return CoordinateSystemAxis::create( + createPropertyMapName(axis.name), + axis.abbreviation ? axis.abbreviation : std::string(), *dir, unit); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CoordinateSystem. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param type Coordinate system type. + * @param axis_count Number of axis + * @param axis Axis description (array of size axis_count) + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ + +PJ_OBJ *proj_obj_create_cs(PJ_CONTEXT *ctx, PJ_COORDINATE_SYSTEM_TYPE type, + int axis_count, const PJ_AXIS_DESCRIPTION *axis) { + try { + switch (type) { + case PJ_CS_TYPE_UNKNOWN: + return nullptr; + + case PJ_CS_TYPE_CARTESIAN: { + if (axis_count == 2) { + return PJ_OBJ::create(CartesianCS::create( + PropertyMap(), createAxis(axis[0]), createAxis(axis[1]))); + } else if (axis_count == 3) { + return PJ_OBJ::create(CartesianCS::create( + PropertyMap(), createAxis(axis[0]), createAxis(axis[1]), + createAxis(axis[2]))); + } + break; + } + + case PJ_CS_TYPE_ELLIPSOIDAL: { + if (axis_count == 2) { + return PJ_OBJ::create(EllipsoidalCS::create( + PropertyMap(), createAxis(axis[0]), createAxis(axis[1]))); + } else if (axis_count == 3) { + return PJ_OBJ::create(EllipsoidalCS::create( + PropertyMap(), createAxis(axis[0]), createAxis(axis[1]), + createAxis(axis[2]))); + } + break; + } + + case PJ_CS_TYPE_VERTICAL: { + if (axis_count == 1) { + return PJ_OBJ::create( + VerticalCS::create(PropertyMap(), createAxis(axis[0]))); + } + break; + } + + case PJ_CS_TYPE_SPHERICAL: { + if (axis_count == 3) { + return PJ_OBJ::create(EllipsoidalCS::create( + PropertyMap(), createAxis(axis[0]), createAxis(axis[1]), + createAxis(axis[2]))); + } + break; + } + + case PJ_CS_TYPE_PARAMETRIC: { + if (axis_count == 1) { + return PJ_OBJ::create( + ParametricCS::create(PropertyMap(), createAxis(axis[0]))); + } + break; + } + + case PJ_CS_TYPE_ORDINAL: { + std::vector axisVector; + for (int i = 0; i < axis_count; i++) { + axisVector.emplace_back(createAxis(axis[i])); + } + + return PJ_OBJ::create(OrdinalCS::create(PropertyMap(), axisVector)); + } + + case PJ_CS_TYPE_DATETIMETEMPORAL: { + if (axis_count == 1) { + return PJ_OBJ::create(DateTimeTemporalCS::create( + PropertyMap(), createAxis(axis[0]))); + } + break; + } + + case PJ_CS_TYPE_TEMPORALCOUNT: { + if (axis_count == 1) { + return PJ_OBJ::create(TemporalCountCS::create( + PropertyMap(), createAxis(axis[0]))); + } + break; + } + + case PJ_CS_TYPE_TEMPORALMEASURE: { + if (axis_count == 1) { + return PJ_OBJ::create(TemporalMeasureCS::create( + PropertyMap(), createAxis(axis[0]))); + } + break; + } + } + + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } + proj_log_error(ctx, __FUNCTION__, "Wrong value for axis_count"); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesiansCS 2D + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @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. + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ + +PJ_OBJ *proj_obj_create_cartesian_2D_cs(PJ_CONTEXT *ctx, + PJ_CARTESIAN_CS_2D_TYPE type, + const char *unit_name, + double unit_conv_factor) { + try { + switch (type) { + case PJ_CART2D_EASTING_NORTHING: + return PJ_OBJ::create(CartesianCS::createEastingNorthing( + createLinearUnit(unit_name, unit_conv_factor))); + + case PJ_CART2D_NORTHING_EASTING: + return PJ_OBJ::create(CartesianCS::createNorthingEasting( + createLinearUnit(unit_name, unit_conv_factor))); + + case PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH: + return PJ_OBJ::create( + CartesianCS::createNorthPoleEastingSouthNorthingSouth( + createLinearUnit(unit_name, unit_conv_factor))); + + case PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH: + return PJ_OBJ::create( + CartesianCS::createSouthPoleEastingNorthNorthingNorth( + createLinearUnit(unit_name, unit_conv_factor))); + + case PJ_CART2D_WESTING_SOUTHING: + return PJ_OBJ::create(CartesianCS::createWestingSouthing( + createLinearUnit(unit_name, unit_conv_factor))); + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Ellipsoidal 2D + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @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. + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ + +PJ_OBJ *proj_obj_create_ellipsoidal_2D_cs(PJ_CONTEXT *ctx, + PJ_ELLIPSOIDAL_CS_2D_TYPE type, + const char *unit_name, + double unit_conv_factor) { + try { + switch (type) { + case PJ_ELLPS2D_LONGITUDE_LATITUDE: + return PJ_OBJ::create(EllipsoidalCS::createLongitudeLatitude( + createAngularUnit(unit_name, unit_conv_factor))); + + case PJ_ELLPS2D_LATITUDE_LONGITUDE: + return PJ_OBJ::create(EllipsoidalCS::createLatitudeLongitude( + createAngularUnit(unit_name, unit_conv_factor))); + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs_name CRS name. Or NULL + * @param geodetic_crs Base GeodeticCRS. Must not be NULL. + * @param conversion Conversion. Must not be NULL. + * @param coordinate_system Cartesian coordinate system. Must not be NULL. + * + * @return Object that must be unreferenced with + * proj_obj_destroy(), or NULL in case of error. + */ + +PJ_OBJ *proj_obj_create_projected_crs(PJ_CONTEXT *ctx, const char *crs_name, + const PJ_OBJ *geodetic_crs, + const PJ_OBJ *conversion, + const PJ_OBJ *coordinate_system) { + SANITIZE_CTX(ctx); + auto geodCRS = + util::nn_dynamic_pointer_cast(geodetic_crs->obj); + if (!geodCRS) { + return nullptr; + } + auto conv = util::nn_dynamic_pointer_cast(conversion->obj); + if (!conv) { + return nullptr; + } + auto cs = + util::nn_dynamic_pointer_cast(coordinate_system->obj); + if (!cs) { + return nullptr; + } + try { + return PJ_OBJ::create(ProjectedCRS::create( + createPropertyMapName(crs_name), NN_NO_CHECK(geodCRS), + NN_NO_CHECK(conv), NN_NO_CHECK(cs))); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static PJ_OBJ *proj_obj_create_conversion(const ConversionNNPtr &conv) { + return PJ_OBJ::create(conv); +} + +//! @endcond + +/* BEGIN: Generated by scripts/create_c_api_projections.py*/ + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a Universal Transverse Mercator + * conversion. + * + * See osgeo::proj::operation::Conversion::createUTM(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_utm(PJ_CONTEXT *ctx, int zone, int north) { + SANITIZE_CTX(ctx); + try { + auto conv = Conversion::createUTM(PropertyMap(), zone, north != 0); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Transverse + * Mercator projection method. + * + * See osgeo::proj::operation::Conversion::createTransverseMercator(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_transverse_mercator( + PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createTransverseMercator( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Scale(scale), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Gauss + * Schreiber Transverse Mercator projection method. + * + * See + * osgeo::proj::operation::Conversion::createGaussSchreiberTransverseMercator(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_gauss_schreiber_transverse_mercator( + PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createGaussSchreiberTransverseMercator( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Scale(scale), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Transverse + * Mercator South Orientated projection method. + * + * See + * osgeo::proj::operation::Conversion::createTransverseMercatorSouthOriented(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_transverse_mercator_south_oriented( + PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createTransverseMercatorSouthOriented( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Scale(scale), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Two Point + * Equidistant projection method. + * + * See osgeo::proj::operation::Conversion::createTwoPointEquidistant(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_two_point_equidistant( + PJ_CONTEXT *ctx, double latitude_first_point, double longitude_first_point, + double latitude_second_point, double longitude_secon_point, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createTwoPointEquidistant( + PropertyMap(), Angle(latitude_first_point, angUnit), + Angle(longitude_first_point, angUnit), + Angle(latitude_second_point, angUnit), + Angle(longitude_secon_point, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Tunisia + * Mapping Grid projection method. + * + * See osgeo::proj::operation::Conversion::createTunisiaMappingGrid(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_tunisia_mapping_grid( + PJ_CONTEXT *ctx, double center_lat, double center_long, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createTunisiaMappingGrid( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Albers + * Conic Equal Area projection method. + * + * See osgeo::proj::operation::Conversion::createAlbersEqualArea(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_albers_equal_area( + PJ_CONTEXT *ctx, double latitude_false_origin, + double longitude_false_origin, double latitude_first_parallel, + double latitude_second_parallel, double easting_false_origin, + double northing_false_origin, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createAlbersEqualArea( + PropertyMap(), Angle(latitude_false_origin, angUnit), + Angle(longitude_false_origin, angUnit), + Angle(latitude_first_parallel, angUnit), + Angle(latitude_second_parallel, angUnit), + Length(easting_false_origin, linearUnit), + Length(northing_false_origin, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Conic Conformal 1SP projection method. + * + * See osgeo::proj::operation::Conversion::createLambertConicConformal_1SP(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_lambert_conic_conformal_1sp( + PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createLambertConicConformal_1SP( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Scale(scale), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Conic Conformal (2SP) projection method. + * + * See osgeo::proj::operation::Conversion::createLambertConicConformal_2SP(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_lambert_conic_conformal_2sp( + PJ_CONTEXT *ctx, double latitude_false_origin, + double longitude_false_origin, double latitude_first_parallel, + double latitude_second_parallel, double easting_false_origin, + double northing_false_origin, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createLambertConicConformal_2SP( + PropertyMap(), Angle(latitude_false_origin, angUnit), + Angle(longitude_false_origin, angUnit), + Angle(latitude_first_parallel, angUnit), + Angle(latitude_second_parallel, angUnit), + Length(easting_false_origin, linearUnit), + Length(northing_false_origin, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Conic Conformal (2SP Michigan) projection method. + * + * See + * osgeo::proj::operation::Conversion::createLambertConicConformal_2SP_Michigan(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_lambert_conic_conformal_2sp_michigan( + PJ_CONTEXT *ctx, double latitude_false_origin, + double longitude_false_origin, double latitude_first_parallel, + double latitude_second_parallel, double easting_false_origin, + double northing_false_origin, double ellipsoid_scaling_factor, + const char *ang_unit_name, double ang_unit_conv_factor, + const char *linear_unit_name, double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createLambertConicConformal_2SP_Michigan( + PropertyMap(), Angle(latitude_false_origin, angUnit), + Angle(longitude_false_origin, angUnit), + Angle(latitude_first_parallel, angUnit), + Angle(latitude_second_parallel, angUnit), + Length(easting_false_origin, linearUnit), + Length(northing_false_origin, linearUnit), + Scale(ellipsoid_scaling_factor)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Conic Conformal (2SP Belgium) projection method. + * + * See + * osgeo::proj::operation::Conversion::createLambertConicConformal_2SP_Belgium(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_lambert_conic_conformal_2sp_belgium( + PJ_CONTEXT *ctx, double latitude_false_origin, + double longitude_false_origin, double latitude_first_parallel, + double latitude_second_parallel, double easting_false_origin, + double northing_false_origin, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createLambertConicConformal_2SP_Belgium( + PropertyMap(), Angle(latitude_false_origin, angUnit), + Angle(longitude_false_origin, angUnit), + Angle(latitude_first_parallel, angUnit), + Angle(latitude_second_parallel, angUnit), + Length(easting_false_origin, linearUnit), + Length(northing_false_origin, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Modified + * Azimuthal Equidistant projection method. + * + * See osgeo::proj::operation::Conversion::createAzimuthalEquidistant(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_azimuthal_equidistant( + PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createAzimuthalEquidistant( + PropertyMap(), Angle(latitude_nat_origin, angUnit), + Angle(longitude_nat_origin, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Guam + * Projection projection method. + * + * See osgeo::proj::operation::Conversion::createGuamProjection(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_guam_projection( + PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createGuamProjection( + PropertyMap(), Angle(latitude_nat_origin, angUnit), + Angle(longitude_nat_origin, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Bonne + * projection method. + * + * See osgeo::proj::operation::Conversion::createBonne(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_bonne( + PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createBonne( + PropertyMap(), Angle(latitude_nat_origin, angUnit), + Angle(longitude_nat_origin, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Cylindrical Equal Area (Spherical) projection method. + * + * See + * osgeo::proj::operation::Conversion::createLambertCylindricalEqualAreaSpherical(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_lambert_cylindrical_equal_area_spherical( + PJ_CONTEXT *ctx, double latitude_first_parallel, + double longitude_nat_origin, double false_easting, double false_northing, + const char *ang_unit_name, double ang_unit_conv_factor, + const char *linear_unit_name, double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createLambertCylindricalEqualAreaSpherical( + PropertyMap(), Angle(latitude_first_parallel, angUnit), + Angle(longitude_nat_origin, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Cylindrical Equal Area (ellipsoidal form) projection method. + * + * See osgeo::proj::operation::Conversion::createLambertCylindricalEqualArea(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_lambert_cylindrical_equal_area( + PJ_CONTEXT *ctx, double latitude_first_parallel, + double longitude_nat_origin, double false_easting, double false_northing, + const char *ang_unit_name, double ang_unit_conv_factor, + const char *linear_unit_name, double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createLambertCylindricalEqualArea( + PropertyMap(), Angle(latitude_first_parallel, angUnit), + Angle(longitude_nat_origin, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Cassini-Soldner projection method. + * + * See osgeo::proj::operation::Conversion::createCassiniSoldner(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_cassini_soldner( + PJ_CONTEXT *ctx, double center_lat, double center_long, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createCassiniSoldner( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Equidistant + * Conic projection method. + * + * See osgeo::proj::operation::Conversion::createEquidistantConic(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_equidistant_conic( + PJ_CONTEXT *ctx, double center_lat, double center_long, + double latitude_first_parallel, double latitude_second_parallel, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createEquidistantConic( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), + Angle(latitude_first_parallel, angUnit), + Angle(latitude_second_parallel, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert I + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertI(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_eckert_i(PJ_CONTEXT *ctx, double center_long, + double false_easting, + double false_northing, + const char *ang_unit_name, + double ang_unit_conv_factor, + const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createEckertI( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert II + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertII(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_eckert_ii( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createEckertII( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert III + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertIII(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_eckert_iii( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createEckertIII( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert IV + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertIV(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_eckert_iv( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createEckertIV( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert V + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertV(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_eckert_v(PJ_CONTEXT *ctx, double center_long, + double false_easting, + double false_northing, + const char *ang_unit_name, + double ang_unit_conv_factor, + const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createEckertV( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert VI + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertVI(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_eckert_vi( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createEckertVI( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Equidistant + * Cylindrical projection method. + * + * See osgeo::proj::operation::Conversion::createEquidistantCylindrical(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_equidistant_cylindrical( + PJ_CONTEXT *ctx, double latitude_first_parallel, + double longitude_nat_origin, double false_easting, double false_northing, + const char *ang_unit_name, double ang_unit_conv_factor, + const char *linear_unit_name, double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createEquidistantCylindrical( + PropertyMap(), Angle(latitude_first_parallel, angUnit), + Angle(longitude_nat_origin, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Equidistant + * Cylindrical (Spherical) projection method. + * + * See + * osgeo::proj::operation::Conversion::createEquidistantCylindricalSpherical(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_equidistant_cylindrical_spherical( + PJ_CONTEXT *ctx, double latitude_first_parallel, + double longitude_nat_origin, double false_easting, double false_northing, + const char *ang_unit_name, double ang_unit_conv_factor, + const char *linear_unit_name, double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createEquidistantCylindricalSpherical( + PropertyMap(), Angle(latitude_first_parallel, angUnit), + Angle(longitude_nat_origin, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Gall + * (Stereographic) projection method. + * + * See osgeo::proj::operation::Conversion::createGall(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_gall(PJ_CONTEXT *ctx, double center_long, + double false_easting, + double false_northing, + const char *ang_unit_name, + double ang_unit_conv_factor, + const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = + Conversion::createGall(PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Goode + * Homolosine projection method. + * + * See osgeo::proj::operation::Conversion::createGoodeHomolosine(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_goode_homolosine( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createGoodeHomolosine( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Interrupted + * Goode Homolosine projection method. + * + * See osgeo::proj::operation::Conversion::createInterruptedGoodeHomolosine(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_interrupted_goode_homolosine( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createInterruptedGoodeHomolosine( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Geostationary Satellite View projection method, with the sweep angle axis of + * the viewing instrument being x. + * + * See osgeo::proj::operation::Conversion::createGeostationarySatelliteSweepX(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_geostationary_satellite_sweep_x( + PJ_CONTEXT *ctx, double center_long, double height, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createGeostationarySatelliteSweepX( + PropertyMap(), Angle(center_long, angUnit), + Length(height, linearUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Geostationary Satellite View projection method, with the sweep angle axis of + * the viewing instrument being y. + * + * See osgeo::proj::operation::Conversion::createGeostationarySatelliteSweepY(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_geostationary_satellite_sweep_y( + PJ_CONTEXT *ctx, double center_long, double height, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createGeostationarySatelliteSweepY( + PropertyMap(), Angle(center_long, angUnit), + Length(height, linearUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Gnomonic + * projection method. + * + * See osgeo::proj::operation::Conversion::createGnomonic(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_gnomonic( + PJ_CONTEXT *ctx, double center_lat, double center_long, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createGnomonic( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Hotine + * Oblique Mercator (Variant A) projection method. + * + * See + * osgeo::proj::operation::Conversion::createHotineObliqueMercatorVariantA(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_hotine_oblique_mercator_variant_a( + PJ_CONTEXT *ctx, double latitude_projection_centre, + double longitude_projection_centre, double azimuth_initial_line, + double angle_from_rectified_to_skrew_grid, double scale, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createHotineObliqueMercatorVariantA( + PropertyMap(), Angle(latitude_projection_centre, angUnit), + Angle(longitude_projection_centre, angUnit), + Angle(azimuth_initial_line, angUnit), + Angle(angle_from_rectified_to_skrew_grid, angUnit), Scale(scale), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Hotine + * Oblique Mercator (Variant B) projection method. + * + * See + * osgeo::proj::operation::Conversion::createHotineObliqueMercatorVariantB(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_hotine_oblique_mercator_variant_b( + PJ_CONTEXT *ctx, double latitude_projection_centre, + double longitude_projection_centre, double azimuth_initial_line, + double angle_from_rectified_to_skrew_grid, double scale, + double easting_projection_centre, double northing_projection_centre, + const char *ang_unit_name, double ang_unit_conv_factor, + const char *linear_unit_name, double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createHotineObliqueMercatorVariantB( + PropertyMap(), Angle(latitude_projection_centre, angUnit), + Angle(longitude_projection_centre, angUnit), + Angle(azimuth_initial_line, angUnit), + Angle(angle_from_rectified_to_skrew_grid, angUnit), Scale(scale), + Length(easting_projection_centre, linearUnit), + Length(northing_projection_centre, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Hotine + * Oblique Mercator Two Point Natural Origin projection method. + * + * See + * osgeo::proj::operation::Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ * +proj_obj_create_conversion_hotine_oblique_mercator_two_point_natural_origin( + PJ_CONTEXT *ctx, double latitude_projection_centre, double latitude_point1, + double longitude_point1, double latitude_point2, double longitude_point2, + double scale, double easting_projection_centre, + double northing_projection_centre, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = + Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin( + PropertyMap(), Angle(latitude_projection_centre, angUnit), + Angle(latitude_point1, angUnit), + Angle(longitude_point1, angUnit), + Angle(latitude_point2, angUnit), + Angle(longitude_point2, angUnit), Scale(scale), + Length(easting_projection_centre, linearUnit), + Length(northing_projection_centre, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Laborde + * Oblique Mercator projection method. + * + * See + * osgeo::proj::operation::Conversion::createLabordeObliqueMercator(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_laborde_oblique_mercator( + PJ_CONTEXT *ctx, double latitude_projection_centre, + double longitude_projection_centre, double azimuth_initial_line, + double scale, double false_easting, double false_northing, + const char *ang_unit_name, double ang_unit_conv_factor, + const char *linear_unit_name, double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createLabordeObliqueMercator( + PropertyMap(), Angle(latitude_projection_centre, angUnit), + Angle(longitude_projection_centre, angUnit), + Angle(azimuth_initial_line, angUnit), Scale(scale), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * International Map of the World Polyconic projection method. + * + * See + * osgeo::proj::operation::Conversion::createInternationalMapWorldPolyconic(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_international_map_world_polyconic( + PJ_CONTEXT *ctx, double center_long, double latitude_first_parallel, + double latitude_second_parallel, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createInternationalMapWorldPolyconic( + PropertyMap(), Angle(center_long, angUnit), + Angle(latitude_first_parallel, angUnit), + Angle(latitude_second_parallel, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Krovak + * (north oriented) projection method. + * + * See osgeo::proj::operation::Conversion::createKrovakNorthOriented(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_krovak_north_oriented( + PJ_CONTEXT *ctx, double latitude_projection_centre, + double longitude_of_origin, double colatitude_cone_axis, + double latitude_pseudo_standard_parallel, + double scale_factor_pseudo_standard_parallel, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createKrovakNorthOriented( + PropertyMap(), Angle(latitude_projection_centre, angUnit), + Angle(longitude_of_origin, angUnit), + Angle(colatitude_cone_axis, angUnit), + Angle(latitude_pseudo_standard_parallel, angUnit), + Scale(scale_factor_pseudo_standard_parallel), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Krovak + * projection method. + * + * See osgeo::proj::operation::Conversion::createKrovak(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_krovak( + PJ_CONTEXT *ctx, double latitude_projection_centre, + double longitude_of_origin, double colatitude_cone_axis, + double latitude_pseudo_standard_parallel, + double scale_factor_pseudo_standard_parallel, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createKrovak( + PropertyMap(), Angle(latitude_projection_centre, angUnit), + Angle(longitude_of_origin, angUnit), + Angle(colatitude_cone_axis, angUnit), + Angle(latitude_pseudo_standard_parallel, angUnit), + Scale(scale_factor_pseudo_standard_parallel), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Azimuthal Equal Area projection method. + * + * See osgeo::proj::operation::Conversion::createLambertAzimuthalEqualArea(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_lambert_azimuthal_equal_area( + PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createLambertAzimuthalEqualArea( + PropertyMap(), Angle(latitude_nat_origin, angUnit), + Angle(longitude_nat_origin, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Miller + * Cylindrical projection method. + * + * See osgeo::proj::operation::Conversion::createMillerCylindrical(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_miller_cylindrical( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createMillerCylindrical( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Mercator + * projection method. + * + * See osgeo::proj::operation::Conversion::createMercatorVariantA(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_mercator_variant_a( + PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createMercatorVariantA( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Scale(scale), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Mercator + * projection method. + * + * See osgeo::proj::operation::Conversion::createMercatorVariantB(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_mercator_variant_b( + PJ_CONTEXT *ctx, double latitude_first_parallel, double center_long, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createMercatorVariantB( + PropertyMap(), Angle(latitude_first_parallel, angUnit), + Angle(center_long, angUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Popular + * Visualisation Pseudo Mercator projection method. + * + * See + * osgeo::proj::operation::Conversion::createPopularVisualisationPseudoMercator(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_popular_visualisation_pseudo_mercator( + PJ_CONTEXT *ctx, double center_lat, double center_long, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createPopularVisualisationPseudoMercator( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Mollweide + * projection method. + * + * See osgeo::proj::operation::Conversion::createMollweide(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_mollweide( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createMollweide( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the New Zealand + * Map Grid projection method. + * + * See osgeo::proj::operation::Conversion::createNewZealandMappingGrid(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_new_zealand_mapping_grid( + PJ_CONTEXT *ctx, double center_lat, double center_long, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createNewZealandMappingGrid( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Oblique + * Stereographic (Alternative) projection method. + * + * See osgeo::proj::operation::Conversion::createObliqueStereographic(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_oblique_stereographic( + PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createObliqueStereographic( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Scale(scale), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Orthographic projection method. + * + * See osgeo::proj::operation::Conversion::createOrthographic(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_orthographic( + PJ_CONTEXT *ctx, double center_lat, double center_long, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createOrthographic( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the American + * Polyconic projection method. + * + * See osgeo::proj::operation::Conversion::createAmericanPolyconic(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_american_polyconic( + PJ_CONTEXT *ctx, double center_lat, double center_long, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createAmericanPolyconic( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Polar + * Stereographic (Variant A) projection method. + * + * See osgeo::proj::operation::Conversion::createPolarStereographicVariantA(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_polar_stereographic_variant_a( + PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createPolarStereographicVariantA( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Scale(scale), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Polar + * Stereographic (Variant B) projection method. + * + * See osgeo::proj::operation::Conversion::createPolarStereographicVariantB(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_polar_stereographic_variant_b( + PJ_CONTEXT *ctx, double latitude_standard_parallel, + double longitude_of_origin, double false_easting, double false_northing, + const char *ang_unit_name, double ang_unit_conv_factor, + const char *linear_unit_name, double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createPolarStereographicVariantB( + PropertyMap(), Angle(latitude_standard_parallel, angUnit), + Angle(longitude_of_origin, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Robinson + * projection method. + * + * See osgeo::proj::operation::Conversion::createRobinson(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_robinson(PJ_CONTEXT *ctx, double center_long, + double false_easting, + double false_northing, + const char *ang_unit_name, + double ang_unit_conv_factor, + const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createRobinson( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Sinusoidal + * projection method. + * + * See osgeo::proj::operation::Conversion::createSinusoidal(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_sinusoidal( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createSinusoidal( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Stereographic projection method. + * + * See osgeo::proj::operation::Conversion::createStereographic(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_stereographic( + PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createStereographic( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Scale(scale), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Van der + * Grinten projection method. + * + * See osgeo::proj::operation::Conversion::createVanDerGrinten(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_van_der_grinten( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createVanDerGrinten( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner I + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerI(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_wagner_i(PJ_CONTEXT *ctx, double center_long, + double false_easting, + double false_northing, + const char *ang_unit_name, + double ang_unit_conv_factor, + const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createWagnerI( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner II + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerII(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_wagner_ii( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createWagnerII( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner III + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerIII(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_wagner_iii( + PJ_CONTEXT *ctx, double latitude_true_scale, double center_long, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createWagnerIII( + PropertyMap(), Angle(latitude_true_scale, angUnit), + Angle(center_long, angUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner IV + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerIV(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_wagner_iv( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createWagnerIV( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner V + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerV(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_wagner_v(PJ_CONTEXT *ctx, double center_long, + double false_easting, + double false_northing, + const char *ang_unit_name, + double ang_unit_conv_factor, + const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createWagnerV( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner VI + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerVI(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_wagner_vi( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createWagnerVI( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner VII + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerVII(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_wagner_vii( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createWagnerVII( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Quadrilateralized Spherical Cube projection method. + * + * See + * osgeo::proj::operation::Conversion::createQuadrilateralizedSphericalCube(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_quadrilateralized_spherical_cube( + PJ_CONTEXT *ctx, double center_lat, double center_long, + double false_easting, double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createQuadrilateralizedSphericalCube( + PropertyMap(), Angle(center_lat, angUnit), + Angle(center_long, angUnit), Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Spherical + * Cross-Track Height projection method. + * + * See osgeo::proj::operation::Conversion::createSphericalCrossTrackHeight(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_spherical_cross_track_height( + PJ_CONTEXT *ctx, double peg_point_lat, double peg_point_long, + double peg_point_heading, double peg_point_height, + const char *ang_unit_name, double ang_unit_conv_factor, + const char *linear_unit_name, double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createSphericalCrossTrackHeight( + PropertyMap(), Angle(peg_point_lat, angUnit), + Angle(peg_point_long, angUnit), Angle(peg_point_heading, angUnit), + Length(peg_point_height, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Equal Earth + * projection method. + * + * See osgeo::proj::operation::Conversion::createEqualEarth(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ_OBJ *proj_obj_create_conversion_equal_earth( + PJ_CONTEXT *ctx, double center_long, double false_easting, + double false_northing, const char *ang_unit_name, + double ang_unit_conv_factor, const char *linear_unit_name, + double linear_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure linearUnit( + createLinearUnit(linear_unit_name, linear_unit_conv_factor)); + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createEqualEarth( + PropertyMap(), Angle(center_long, angUnit), + Length(false_easting, linearUnit), + Length(false_northing, linearUnit)); + return proj_obj_create_conversion(conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} +/* END: Generated by scripts/create_c_api_projections.py*/ + +// --------------------------------------------------------------------------- + +/** \brief Return whether a coordinate operation can be instanciated as + * a PROJ pipeline, checking in particular that referenced grids are + * available. + * + * @param ctx PROJ context, or NULL for default context + * @param coordoperation Objet of type CoordinateOperation or derived classes + * (must not be NULL) + * @return TRUE or FALSE. + */ + +int proj_coordoperation_is_instanciable(PJ_CONTEXT *ctx, + const PJ_OBJ *coordoperation) { + assert(coordoperation); + auto op = + dynamic_cast(coordoperation->obj.get()); + if (!op) { + proj_log_error(ctx, __FUNCTION__, + "Object is not a CoordinateOperation"); + return 0; + } + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + try { + return op->isPROJInstanciable(dbContext) ? 1 : 0; + } catch (const std::exception &) { + return 0; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return the number of parameters of a SingleOperation + * + * @param ctx PROJ context, or NULL for default context + * @param coordoperation Objet of type SingleOperation or derived classes + * (must not be NULL) + */ + +int proj_coordoperation_get_param_count(PJ_CONTEXT *ctx, + const PJ_OBJ *coordoperation) { + SANITIZE_CTX(ctx); + assert(coordoperation); + auto op = dynamic_cast(coordoperation->obj.get()); + if (!op) { + proj_log_error(ctx, __FUNCTION__, "Object is not a SingleOperation"); + return 0; + } + return static_cast(op->parameterValues().size()); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the index of a parameter of a SingleOperation + * + * @param ctx PROJ context, or NULL for default context + * @param coordoperation Objet of type SingleOperation or derived classes + * (must not be NULL) + * @param name Parameter name. Must not be NULL + * @return index (>=0), or -1 in case of error. + */ + +int proj_coordoperation_get_param_index(PJ_CONTEXT *ctx, + const PJ_OBJ *coordoperation, + const char *name) { + SANITIZE_CTX(ctx); + assert(coordoperation); + assert(name); + auto op = dynamic_cast(coordoperation->obj.get()); + if (!op) { + proj_log_error(ctx, __FUNCTION__, "Object is not a SingleOperation"); + return -1; + } + int index = 0; + for (const auto &genParam : op->method()->parameters()) { + if (Identifier::isEquivalentName(genParam->nameStr().c_str(), name)) { + return index; + } + index++; + } + return -1; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a parameter of a SingleOperation + * + * @param ctx PROJ context, or NULL for default context + * @param coordoperation Objet of type SingleOperation or derived classes + * (must not be NULL) + * @param index Parameter index. + * @param out_name Pointer to a string value to store the parameter name. or + * NULL + * @param out_auth_name Pointer to a string value to store the parameter + * authority name. or NULL + * @param out_code Pointer to a string value to store the parameter + * code. or NULL + * @param out_value Pointer to a double value to store the parameter + * value (if numeric). or NULL + * @param out_value_string Pointer to a string value to store the parameter + * value (if of type string). or NULL + * @param out_unit_conv_factor Pointer to a double value to store the parameter + * unit conversion factor. or NULL + * @param out_unit_name Pointer to a string value to store the parameter + * unit name. or NULL + * @param out_unit_auth_name Pointer to a string value to store the + * unit authority name. or NULL + * @param out_unit_code Pointer to a string value to store the + * unit code. or NULL + * @param out_unit_category Pointer to a string value to store the parameter + * name. or + * NULL. This value might be "unknown", "none", "linear", "angular", "scale", + * "time" or "parametric"; + * @return TRUE in case of success. + */ + +int proj_coordoperation_get_param( + PJ_CONTEXT *ctx, const PJ_OBJ *coordoperation, int index, + const char **out_name, const char **out_auth_name, const char **out_code, + double *out_value, const char **out_value_string, + double *out_unit_conv_factor, const char **out_unit_name, + const char **out_unit_auth_name, const char **out_unit_code, + const char **out_unit_category) { + SANITIZE_CTX(ctx); + assert(coordoperation); + auto op = dynamic_cast(coordoperation->obj.get()); + if (!op) { + proj_log_error(ctx, __FUNCTION__, "Object is not a SingleOperation"); + return false; + } + const auto ¶meters = op->method()->parameters(); + const auto &values = op->parameterValues(); + if (static_cast(index) >= parameters.size() || + static_cast(index) >= values.size()) { + proj_log_error(ctx, __FUNCTION__, "Invalid index"); + return false; + } + + const auto ¶m = parameters[index]; + const auto ¶m_ids = param->identifiers(); + if (out_name) { + *out_name = param->name()->description()->c_str(); + } + if (out_auth_name) { + if (!param_ids.empty()) { + *out_auth_name = param_ids[0]->codeSpace()->c_str(); + } else { + *out_auth_name = nullptr; + } + } + if (out_code) { + if (!param_ids.empty()) { + *out_code = param_ids[0]->code().c_str(); + } else { + *out_code = nullptr; + } + } + + const auto &value = values[index]; + ParameterValuePtr paramValue = nullptr; + auto opParamValue = + dynamic_cast(value.get()); + if (opParamValue) { + paramValue = opParamValue->parameterValue().as_nullable(); + } + if (out_value) { + *out_value = 0; + if (paramValue) { + if (paramValue->type() == ParameterValue::Type::MEASURE) { + *out_value = paramValue->value().value(); + } + } + } + if (out_value_string) { + *out_value_string = nullptr; + if (paramValue) { + if (paramValue->type() == ParameterValue::Type::FILENAME) { + *out_value_string = paramValue->valueFile().c_str(); + } else if (paramValue->type() == ParameterValue::Type::STRING) { + *out_value_string = paramValue->stringValue().c_str(); + } + } + } + if (out_unit_conv_factor) { + *out_unit_conv_factor = 0; + } + if (out_unit_name) { + *out_unit_name = nullptr; + } + if (out_unit_auth_name) { + *out_unit_auth_name = nullptr; + } + if (out_unit_code) { + *out_unit_code = nullptr; + } + if (out_unit_category) { + *out_unit_category = nullptr; + } + if (paramValue) { + if (paramValue->type() == ParameterValue::Type::MEASURE) { + const auto &unit = paramValue->value().unit(); + if (out_unit_conv_factor) { + *out_unit_conv_factor = unit.conversionToSI(); + } + if (out_unit_name) { + *out_unit_name = unit.name().c_str(); + } + if (out_unit_auth_name) { + *out_unit_auth_name = unit.codeSpace().c_str(); + } + if (out_unit_code) { + *out_unit_code = unit.code().c_str(); + } + if (out_unit_category) { + *out_unit_category = get_unit_category(unit.type()); + } + } + } + + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the parameters of a Helmert transformation as WKT1 TOWGS84 + * values. + * + * @param ctx PROJ context, or NULL for default context + * @param coordoperation Objet of type Transformation, that can be represented + * as a WKT1 TOWGS84 node (must not be NULL) + * @param out_values Pointer to an array of value_count double values. + * @param value_count Size of out_values array. + * @param emit_error_if_incompatible Boolean to inicate if an error must be + * logged if coordoperation is not compatible with a WKT1 TOWGS84 + * representation. + * @return TRUE in case of success, or FALSE if coordoperation is not + * compatible with a WKT1 TOWGS84 representation. + */ + +int proj_coordoperation_get_towgs84_values(PJ_CONTEXT *ctx, + const PJ_OBJ *coordoperation, + double *out_values, int value_count, + int emit_error_if_incompatible) { + SANITIZE_CTX(ctx); + assert(coordoperation); + auto transf = + dynamic_cast(coordoperation->obj.get()); + if (!transf) { + if (emit_error_if_incompatible) { + proj_log_error(ctx, __FUNCTION__, "Object is not a Transformation"); + } + return FALSE; + } + try { + auto values = transf->getTOWGS84Parameters(); + for (int i = 0; + i < value_count && static_cast(i) < values.size(); i++) { + out_values[i] = values[i]; + } + return TRUE; + } catch (const std::exception &e) { + if (emit_error_if_incompatible) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return FALSE; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return the number of grids used by a CoordinateOperation + * + * @param ctx PROJ context, or NULL for default context + * @param coordoperation Objet of type CoordinateOperation or derived classes + * (must not be NULL) + */ + +int proj_coordoperation_get_grid_used_count(PJ_CONTEXT *ctx, + const PJ_OBJ *coordoperation) { + SANITIZE_CTX(ctx); + assert(coordoperation); + auto co = + dynamic_cast(coordoperation->obj.get()); + if (!co) { + proj_log_error(ctx, __FUNCTION__, + "Object is not a CoordinateOperation"); + return 0; + } + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + try { + if (!coordoperation->gridsNeededAsked) { + coordoperation->gridsNeededAsked = true; + const auto gridsNeeded = co->gridsNeeded(dbContext); + for (const auto &gridDesc : gridsNeeded) { + coordoperation->gridsNeeded.emplace_back(gridDesc); + } + } + return static_cast(coordoperation->gridsNeeded.size()); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return 0; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return a parameter of a SingleOperation + * + * @param ctx PROJ context, or NULL for default context + * @param coordoperation Objet of type SingleOperation or derived classes + * (must not be NULL) + * @param index Parameter index. + * @param out_short_name Pointer to a string value to store the grid short name. + * or NULL + * @param out_full_name Pointer to a string value to store the grid full + * filename. or NULL + * @param out_package_name Pointer to a string value to store the package name + * where + * the grid might be found. or NULL + * @param out_url Pointer to a string value to store the grid URL or the + * package URL where the grid might be found. or NULL + * @param out_direct_download Pointer to a int (boolean) value to store whether + * *out_url can be downloaded directly. or NULL + * @param out_open_license Pointer to a int (boolean) value to store whether + * the grid is released with an open license. or NULL + * @param out_available Pointer to a int (boolean) value to store whether the + * grid is available at runtime. or NULL + * @return TRUE in case of success. + */ + +int proj_coordoperation_get_grid_used( + PJ_CONTEXT *ctx, const PJ_OBJ *coordoperation, int index, + const char **out_short_name, const char **out_full_name, + const char **out_package_name, const char **out_url, + int *out_direct_download, int *out_open_license, int *out_available) { + SANITIZE_CTX(ctx); + const int count = + proj_coordoperation_get_grid_used_count(ctx, coordoperation); + if (index < 0 || index >= count) { + proj_log_error(ctx, __FUNCTION__, "Invalid index"); + return false; + } + + const auto &gridDesc = coordoperation->gridsNeeded[index]; + if (out_short_name) { + *out_short_name = gridDesc.shortName.c_str(); + } + + if (out_full_name) { + *out_full_name = gridDesc.fullName.c_str(); + } + + if (out_package_name) { + *out_package_name = gridDesc.packageName.c_str(); + } + + if (out_url) { + *out_url = gridDesc.url.c_str(); + } + + if (out_direct_download) { + *out_direct_download = gridDesc.directDownload; + } + + if (out_open_license) { + *out_open_license = gridDesc.openLicense; + } + + if (out_available) { + *out_available = gridDesc.available; + } + + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Opaque object representing an operation factory context. */ +struct PJ_OPERATION_FACTORY_CONTEXT { + //! @cond Doxygen_Suppress + CoordinateOperationContextNNPtr operationContext; + + explicit PJ_OPERATION_FACTORY_CONTEXT( + CoordinateOperationContextNNPtr &&operationContextIn) + : operationContext(std::move(operationContextIn)) {} + + PJ_OPERATION_FACTORY_CONTEXT(const PJ_OPERATION_FACTORY_CONTEXT &) = delete; + PJ_OPERATION_FACTORY_CONTEXT & + operator=(const PJ_OPERATION_FACTORY_CONTEXT &) = delete; + //! @endcond +}; + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a context for building coordinate operations between + * two CRS. + * + * The returned object must be unreferenced with + * proj_operation_factory_context_destroy() after use. + * + * If authority is NULL or 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 authority is set to "any", then coordinate + * operations from any authority will be searched + * If authority is a non-empty string different of "any", + * then coordinate operatiosn will be searched only in that authority namespace. + * + * @param ctx Context, or NULL for default context. + * @param authority Name of authority to which to restrict the search of + * candidate operations. + * @return Object that must be unreferenced with + * proj_operation_factory_context_destroy(), or NULL in + * case of error. + */ +PJ_OPERATION_FACTORY_CONTEXT * +proj_create_operation_factory_context(PJ_CONTEXT *ctx, const char *authority) { + SANITIZE_CTX(ctx); + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + try { + if (dbContext) { + auto factory = CoordinateOperationFactory::create(); + auto authFactory = AuthorityFactory::create( + NN_NO_CHECK(dbContext), + std::string(authority ? authority : "")); + auto operationContext = + CoordinateOperationContext::create(authFactory, nullptr, 0.0); + return new PJ_OPERATION_FACTORY_CONTEXT( + std::move(operationContext)); + } else { + auto operationContext = + CoordinateOperationContext::create(nullptr, nullptr, 0.0); + return new PJ_OPERATION_FACTORY_CONTEXT( + std::move(operationContext)); + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Drops a reference on an object. + * + * This method should be called one and exactly one for each function + * returning a PJ_OPERATION_FACTORY_CONTEXT* + * + * @param ctx Object, or NULL. + */ +void proj_operation_factory_context_destroy(PJ_OPERATION_FACTORY_CONTEXT *ctx) { + delete ctx; +} + +// --------------------------------------------------------------------------- + +/** \brief Set the desired accuracy of the resulting coordinate transformations. + * @param ctx PROJ context, or NULL for default context + * @param factory_ctx Operation factory context. must not be NULL + * @param accuracy Accuracy in meter (or 0 to disable the filter). + */ +void proj_operation_factory_context_set_desired_accuracy( + PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, + double accuracy) { + SANITIZE_CTX(ctx); + assert(factory_ctx); + try { + factory_ctx->operationContext->setDesiredAccuracy(accuracy); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Set the desired area of interest for the resulting coordinate + * transformations. + * + * For an area of interest crossing the anti-meridian, west_lon_degree will be + * greater than east_lon_degree. + * + * @param ctx PROJ context, or NULL for default context + * @param factory_ctx Operation factory context. must not be NULL + * @param west_lon_degree West longitude (in degrees). + * @param south_lat_degree South latitude (in degrees). + * @param east_lon_degree East longitude (in degrees). + * @param north_lat_degree North latitude (in degrees). + */ +void proj_operation_factory_context_set_area_of_interest( + PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, + double west_lon_degree, double south_lat_degree, double east_lon_degree, + double north_lat_degree) { + SANITIZE_CTX(ctx); + assert(factory_ctx); + try { + factory_ctx->operationContext->setAreaOfInterest( + Extent::createFromBBOX(west_lon_degree, south_lat_degree, + east_lon_degree, north_lat_degree)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } +} + +// --------------------------------------------------------------------------- + +/** \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 PJ_CRS_EXTENT_SMALLEST. + * + * @param ctx PROJ context, or NULL for default context + * @param factory_ctx Operation factory context. must not be NULL + * @param use How source and target CRS extent should be used. + */ +void proj_operation_factory_context_set_crs_extent_use( + PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, + PROJ_CRS_EXTENT_USE use) { + SANITIZE_CTX(ctx); + assert(factory_ctx); + try { + switch (use) { + case PJ_CRS_EXTENT_NONE: + factory_ctx->operationContext->setSourceAndTargetCRSExtentUse( + CoordinateOperationContext::SourceTargetCRSExtentUse::NONE); + break; + + case PJ_CRS_EXTENT_BOTH: + factory_ctx->operationContext->setSourceAndTargetCRSExtentUse( + CoordinateOperationContext::SourceTargetCRSExtentUse::BOTH); + break; + + case PJ_CRS_EXTENT_INTERSECTION: + factory_ctx->operationContext->setSourceAndTargetCRSExtentUse( + CoordinateOperationContext::SourceTargetCRSExtentUse:: + INTERSECTION); + break; + + case PJ_CRS_EXTENT_SMALLEST: + factory_ctx->operationContext->setSourceAndTargetCRSExtentUse( + CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST); + break; + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } +} + +// --------------------------------------------------------------------------- + +/** \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 PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT. + * + * @param ctx PROJ context, or NULL for default context + * @param factory_ctx Operation factory context. must not be NULL + * @param criterion patial criterion to use + */ +void PROJ_DLL proj_operation_factory_context_set_spatial_criterion( + PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, + PROJ_SPATIAL_CRITERION criterion) { + SANITIZE_CTX(ctx); + assert(factory_ctx); + try { + switch (criterion) { + case PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT: + factory_ctx->operationContext->setSpatialCriterion( + CoordinateOperationContext::SpatialCriterion:: + STRICT_CONTAINMENT); + break; + + case PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION: + factory_ctx->operationContext->setSpatialCriterion( + CoordinateOperationContext::SpatialCriterion:: + PARTIAL_INTERSECTION); + break; + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Set how grid availability is used. + * + * The default is USE_FOR_SORTING. + * + * @param ctx PROJ context, or NULL for default context + * @param factory_ctx Operation factory context. must not be NULL + * @param use how grid availability is used. + */ +void PROJ_DLL proj_operation_factory_context_set_grid_availability_use( + PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, + PROJ_GRID_AVAILABILITY_USE use) { + SANITIZE_CTX(ctx); + assert(factory_ctx); + try { + switch (use) { + case PROJ_GRID_AVAILABILITY_USED_FOR_SORTING: + factory_ctx->operationContext->setGridAvailabilityUse( + CoordinateOperationContext::GridAvailabilityUse:: + USE_FOR_SORTING); + break; + + case PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID: + factory_ctx->operationContext->setGridAvailabilityUse( + CoordinateOperationContext::GridAvailabilityUse:: + DISCARD_OPERATION_IF_MISSING_GRID); + break; + + case PROJ_GRID_AVAILABILITY_IGNORED: + factory_ctx->operationContext->setGridAvailabilityUse( + CoordinateOperationContext::GridAvailabilityUse:: + IGNORE_GRID_AVAILABILITY); + break; + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether PROJ alternative grid names should be substituted to + * the official authority names. + * + * The default is true. + * + * @param ctx PROJ context, or NULL for default context + * @param factory_ctx Operation factory context. must not be NULL + * @param usePROJNames whether PROJ alternative grid names should be used + */ +void proj_operation_factory_context_set_use_proj_alternative_grid_names( + PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, + int usePROJNames) { + SANITIZE_CTX(ctx); + assert(factory_ctx); + try { + factory_ctx->operationContext->setUsePROJAlternativeGridNames( + usePROJNames != 0); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } +} + +// --------------------------------------------------------------------------- + +/** \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 true, allow + * chaining both operations. + * + * The current implementation is limited to researching one intermediate + * step. + * + * By default, all potential C candidates will be used. + * proj_operation_factory_context_set_allowed_intermediate_crs() + * can be used to restrict them. + * + * The default is true. + * + * @param ctx PROJ context, or NULL for default context + * @param factory_ctx Operation factory context. must not be NULL + * @param allow whether intermediate CRS may be used. + */ +void proj_operation_factory_context_set_allow_use_intermediate_crs( + PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int allow) { + SANITIZE_CTX(ctx); + assert(factory_ctx); + try { + factory_ctx->operationContext->setAllowUseIntermediateCRS(allow != 0); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } +} + +// --------------------------------------------------------------------------- + +/** \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 ctx PROJ context, or NULL for default context + * @param factory_ctx Operation factory context. must not be NULL + * @param list_of_auth_name_codes an array of strings NLL terminated, + * with the format { "auth_name1", "code1", "auth_name2", "code2", ... NULL } + */ +void proj_operation_factory_context_set_allowed_intermediate_crs( + PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, + const char *const *list_of_auth_name_codes) { + SANITIZE_CTX(ctx); + assert(factory_ctx); + try { + std::vector> pivots; + for (auto iter = list_of_auth_name_codes; iter && iter[0] && iter[1]; + iter += 2) { + pivots.emplace_back(std::pair( + std::string(iter[0]), std::string(iter[1]))); + } + factory_ctx->operationContext->setIntermediateCRS(pivots); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Find a list of CoordinateOperation from source_crs to target_crs. + * + * 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. + * + * @param ctx PROJ context, or NULL for default context + * @param source_crs source CRS. Must not be NULL. + * @param target_crs source CRS. Must not be NULL. + * @param operationContext Search context. Must not be NULL. + * @return a result set that must be unreferenced with + * proj_obj_list_destroy(), or NULL in case of error. + */ +PJ_OBJ_LIST *proj_obj_create_operations( + PJ_CONTEXT *ctx, const PJ_OBJ *source_crs, const PJ_OBJ *target_crs, + const PJ_OPERATION_FACTORY_CONTEXT *operationContext) { + SANITIZE_CTX(ctx); + assert(source_crs); + assert(target_crs); + assert(operationContext); + + auto sourceCRS = nn_dynamic_pointer_cast(source_crs->obj); + if (!sourceCRS) { + proj_log_error(ctx, __FUNCTION__, "source_crs is not a CRS"); + return nullptr; + } + auto targetCRS = nn_dynamic_pointer_cast(target_crs->obj); + if (!targetCRS) { + proj_log_error(ctx, __FUNCTION__, "target_crs is not a CRS"); + return nullptr; + } + + try { + auto factory = CoordinateOperationFactory::create(); + std::vector objects; + auto ops = factory->createOperations( + NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), + operationContext->operationContext); + for (const auto &op : ops) { + objects.emplace_back(op); + } + return new PJ_OBJ_LIST(std::move(objects)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return the number of objects in the result set + * + * @param result Objet of type PJ_OBJ_LIST (must not be NULL) + */ +int proj_obj_list_get_count(const PJ_OBJ_LIST *result) { + assert(result); + return static_cast(result->objects.size()); +} + +// --------------------------------------------------------------------------- + +/** \brief Return an object from the result set + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param result Objet of type PJ_OBJ_LIST (must not be NULL) + * @param index Index + * @return a new object that must be unreferenced with proj_obj_destroy(), + * or nullptr in case of error. + */ + +PJ_OBJ *proj_obj_list_get(PJ_CONTEXT *ctx, const PJ_OBJ_LIST *result, + int index) { + SANITIZE_CTX(ctx); + assert(result); + if (index < 0 || index >= proj_obj_list_get_count(result)) { + proj_log_error(ctx, __FUNCTION__, "Invalid index"); + return nullptr; + } + return PJ_OBJ::create(result->objects[index]); +} + +// --------------------------------------------------------------------------- + +/** \brief Drops a reference on the result set. + * + * This method should be called one and exactly one for each function + * returning a PJ_OBJ_LIST* + * + * @param result Object, or NULL. + */ +void proj_obj_list_destroy(PJ_OBJ_LIST *result) { delete result; } + +// --------------------------------------------------------------------------- + +/** \brief Return the accuracy (in metre) of a coordinate operation. + * + * @param ctx PROJ context, or NULL for default context + * @param coordoperation Coordinate operation. Must not be NULL. + * @return the accuracy, or a negative value if unknown or in case of error. + */ +double proj_coordoperation_get_accuracy(PJ_CONTEXT *ctx, + const PJ_OBJ *coordoperation) { + SANITIZE_CTX(ctx); + assert(coordoperation); + auto co = + dynamic_cast(coordoperation->obj.get()); + if (!co) { + proj_log_error(ctx, __FUNCTION__, + "Object is not a CoordinateOperation"); + return -1; + } + const auto &accuracies = co->coordinateOperationAccuracies(); + if (accuracies.empty()) { + return -1; + } + try { + return c_locale_stod(accuracies[0]->value()); + } catch (const std::exception &) { + } + return -1; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the datum of a SingleCRS. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs Objet of type SingleCRS (must not be NULL) + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL + * in case of error (or if there is no datum) + */ +PJ_OBJ *proj_obj_crs_get_datum(PJ_CONTEXT *ctx, const PJ_OBJ *crs) { + SANITIZE_CTX(ctx); + assert(crs); + auto l_crs = dynamic_cast(crs->obj.get()); + if (!l_crs) { + proj_log_error(ctx, __FUNCTION__, "Object is not a SingleCRS"); + return nullptr; + } + const auto &datum = l_crs->datum(); + if (!datum) { + return nullptr; + } + return PJ_OBJ::create(NN_NO_CHECK(datum)); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the coordinate system of a SingleCRS. + * + * The returned object must be unreferenced with proj_obj_destroy() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param crs Objet of type SingleCRS (must not be NULL) + * @return Object that must be unreferenced with proj_obj_destroy(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_crs_get_coordinate_system(PJ_CONTEXT *ctx, const PJ_OBJ *crs) { + SANITIZE_CTX(ctx); + assert(crs); + auto l_crs = dynamic_cast(crs->obj.get()); + if (!l_crs) { + proj_log_error(ctx, __FUNCTION__, "Object is not a SingleCRS"); + return nullptr; + } + return PJ_OBJ::create(l_crs->coordinateSystem()); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the type of the coordinate system. + * + * @param ctx PROJ context, or NULL for default context + * @param cs Objet of type CoordinateSystem (must not be NULL) + * @return type, or PJ_CS_TYPE_UNKNOWN in case of error. + */ +PJ_COORDINATE_SYSTEM_TYPE proj_obj_cs_get_type(PJ_CONTEXT *ctx, + const PJ_OBJ *cs) { + SANITIZE_CTX(ctx); + assert(cs); + auto l_cs = dynamic_cast(cs->obj.get()); + if (!l_cs) { + proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateSystem"); + return PJ_CS_TYPE_UNKNOWN; + } + if (dynamic_cast(l_cs)) { + return PJ_CS_TYPE_CARTESIAN; + } + if (dynamic_cast(l_cs)) { + return PJ_CS_TYPE_ELLIPSOIDAL; + } + if (dynamic_cast(l_cs)) { + return PJ_CS_TYPE_VERTICAL; + } + if (dynamic_cast(l_cs)) { + return PJ_CS_TYPE_SPHERICAL; + } + if (dynamic_cast(l_cs)) { + return PJ_CS_TYPE_ORDINAL; + } + if (dynamic_cast(l_cs)) { + return PJ_CS_TYPE_PARAMETRIC; + } + if (dynamic_cast(l_cs)) { + return PJ_CS_TYPE_DATETIMETEMPORAL; + } + if (dynamic_cast(l_cs)) { + return PJ_CS_TYPE_TEMPORALCOUNT; + } + if (dynamic_cast(l_cs)) { + return PJ_CS_TYPE_TEMPORALMEASURE; + } + return PJ_CS_TYPE_UNKNOWN; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the number of axis of the coordinate system. + * + * @param ctx PROJ context, or NULL for default context + * @param cs Objet of type CoordinateSystem (must not be NULL) + * @return number of axis, or -1 in case of error. + */ +int proj_obj_cs_get_axis_count(PJ_CONTEXT *ctx, const PJ_OBJ *cs) { + SANITIZE_CTX(ctx); + assert(cs); + auto l_cs = dynamic_cast(cs->obj.get()); + if (!l_cs) { + proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateSystem"); + return -1; + } + return static_cast(l_cs->axisList().size()); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns information on an axis + * + * @param ctx PROJ context, or NULL for default context + * @param cs Objet of type CoordinateSystem (must not be NULL) + * @param index Index of the coordinate system (between 0 and + * proj_obj_cs_get_axis_count() - 1) + * @param out_name Pointer to a string value to store the axis name. or NULL + * @param out_abbrev Pointer to a string value to store the axis abbreviation. + * or NULL + * @param out_direction Pointer to a string value to store the axis direction. + * or NULL + * @param out_unit_conv_factor Pointer to a double value to store the axis + * unit conversion factor. or NULL + * @param out_unit_name Pointer to a string value to store the axis + * unit name. or NULL + * @param out_unit_auth_name Pointer to a string value to store the axis + * unit authority name. or NULL + * @param out_unit_code Pointer to a string value to store the axis + * unit code. or NULL + * @return TRUE in case of success + */ +int proj_obj_cs_get_axis_info(PJ_CONTEXT *ctx, const PJ_OBJ *cs, int index, + const char **out_name, const char **out_abbrev, + const char **out_direction, + double *out_unit_conv_factor, + const char **out_unit_name, + const char **out_unit_auth_name, + const char **out_unit_code) { + SANITIZE_CTX(ctx); + assert(cs); + auto l_cs = dynamic_cast(cs->obj.get()); + if (!l_cs) { + proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateSystem"); + return false; + } + const auto &axisList = l_cs->axisList(); + if (index < 0 || static_cast(index) >= axisList.size()) { + proj_log_error(ctx, __FUNCTION__, "Invalid index"); + return false; + } + const auto &axis = axisList[index]; + if (out_name) { + *out_name = axis->nameStr().c_str(); + } + if (out_abbrev) { + *out_abbrev = axis->abbreviation().c_str(); + } + if (out_direction) { + *out_direction = axis->direction().toString().c_str(); + } + if (out_unit_conv_factor) { + *out_unit_conv_factor = axis->unit().conversionToSI(); + } + if (out_unit_name) { + *out_unit_name = axis->unit().name().c_str(); + } + if (out_unit_auth_name) { + *out_unit_auth_name = axis->unit().codeSpace().c_str(); + } + if (out_unit_code) { + *out_unit_code = axis->unit().code().c_str(); + } + return true; +} diff --git a/src/iso19111/common.cpp b/src/iso19111/common.cpp new file mode 100644 index 00000000..bd690924 --- /dev/null +++ b/src/iso19111/common.cpp @@ -0,0 +1,1115 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 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/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include "proj.h" + +#include // M_PI +#include +#include +#include +#include + +using namespace NS_PROJ::internal; +using namespace NS_PROJ::io; +using namespace NS_PROJ::metadata; +using namespace NS_PROJ::util; + +#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 common { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct UnitOfMeasure::Private { + std::string name_{}; + double toSI_ = 1.0; + UnitOfMeasure::Type type_{UnitOfMeasure::Type::UNKNOWN}; + std::string codeSpace_{}; + std::string code_{}; + + Private(const std::string &nameIn, double toSIIn, + UnitOfMeasure::Type typeIn, const std::string &codeSpaceIn, + const std::string &codeIn) + : name_(nameIn), toSI_(toSIIn), type_(typeIn), codeSpace_(codeSpaceIn), + code_(codeIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Creates a UnitOfMeasure. */ +UnitOfMeasure::UnitOfMeasure(const std::string &nameIn, double toSIIn, + UnitOfMeasure::Type typeIn, + const std::string &codeSpaceIn, + const std::string &codeIn) + : d(internal::make_unique(nameIn, toSIIn, typeIn, codeSpaceIn, + codeIn)) {} + +// --------------------------------------------------------------------------- + +UnitOfMeasure::UnitOfMeasure(const UnitOfMeasure &other) + : d(internal::make_unique(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +UnitOfMeasure::~UnitOfMeasure() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +UnitOfMeasure &UnitOfMeasure::operator=(const UnitOfMeasure &other) { + if (this != &other) { + *d = *(other.d); + } + return *this; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +UnitOfMeasureNNPtr UnitOfMeasure::create(const UnitOfMeasure &other) { + return util::nn_make_shared(other); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the unit of measure. */ +const std::string &UnitOfMeasure::name() PROJ_PURE_DEFN { return d->name_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the conversion factor to the unit of the + * International System of Units of the same Type. + * + * For example, for foot, this would be 0.3048 (metre) + * + * @return the conversion factor, or 0 if no conversion exists. + */ +double UnitOfMeasure::conversionToSI() PROJ_PURE_DEFN { return d->toSI_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the type of the unit of measure. + */ +UnitOfMeasure::Type UnitOfMeasure::type() PROJ_PURE_DEFN { return d->type_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the code space of the unit of measure. + * + * For example "EPSG" + * + * @return the code space, or empty string. + */ +const std::string &UnitOfMeasure::codeSpace() PROJ_PURE_DEFN { + return d->codeSpace_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the code of the unit of measure. + * + * @return the code, or empty string. + */ +const std::string &UnitOfMeasure::code() PROJ_PURE_DEFN { return d->code_; } + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void UnitOfMeasure::_exportToWKT( + WKTFormatter *formatter, + const std::string &unitType) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; + + if (formatter->forceUNITKeyword() && type() != Type::PARAMETRIC) { + formatter->startNode(WKTConstants::UNIT, !codeSpace().empty()); + } else if (!unitType.empty()) { + formatter->startNode(unitType, !codeSpace().empty()); + } else { + if (isWKT2 && type() == Type::LINEAR) { + formatter->startNode(WKTConstants::LENGTHUNIT, + !codeSpace().empty()); + } else if (isWKT2 && type() == Type::ANGULAR) { + formatter->startNode(WKTConstants::ANGLEUNIT, !codeSpace().empty()); + } else if (isWKT2 && type() == Type::SCALE) { + formatter->startNode(WKTConstants::SCALEUNIT, !codeSpace().empty()); + } else if (isWKT2 && type() == Type::TIME) { + formatter->startNode(WKTConstants::TIMEUNIT, !codeSpace().empty()); + } else if (isWKT2 && type() == Type::PARAMETRIC) { + formatter->startNode(WKTConstants::PARAMETRICUNIT, + !codeSpace().empty()); + } else { + formatter->startNode(WKTConstants::UNIT, !codeSpace().empty()); + } + } + + { + const auto &l_name = name(); + const bool esri = formatter->useESRIDialect(); + if (esri) { + if (ci_equal(l_name, "degree")) { + formatter->addQuotedString("Degree"); + } else if (ci_equal(l_name, "grad")) { + formatter->addQuotedString("Grad"); + } else if (ci_equal(l_name, "metre")) { + formatter->addQuotedString("Meter"); + } else { + formatter->addQuotedString(l_name); + } + } else { + formatter->addQuotedString(l_name); + } + const auto &factor = conversionToSI(); + if (!isWKT2 || factor != 0.0) { + // Some TIMEUNIT do not have a conversion factor + formatter->add(factor); + } + if (!codeSpace().empty() && formatter->outputId()) { + formatter->startNode( + isWKT2 ? WKTConstants::ID : WKTConstants::AUTHORITY, false); + formatter->addQuotedString(codeSpace()); + const auto &l_code = code(); + if (isWKT2) { + try { + (void)std::stoi(l_code); + formatter->add(l_code); + } catch (const std::exception &) { + formatter->addQuotedString(l_code); + } + } else { + formatter->addQuotedString(l_code); + } + formatter->endNode(); + } + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** Returns whether two units of measures are equal. + * + * The comparison is based on the name. + */ +bool UnitOfMeasure::operator==(const UnitOfMeasure &other) PROJ_PURE_DEFN { + return name() == other.name(); +} + +// --------------------------------------------------------------------------- + +/** Returns whether two units of measures are different. + * + * The comparison is based on the name. + */ +bool UnitOfMeasure::operator!=(const UnitOfMeasure &other) PROJ_PURE_DEFN { + return name() != other.name(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::string UnitOfMeasure::exportToPROJString() const { + if (type() == Type::LINEAR) { + auto proj_units = proj_list_units(); + for (int i = 0; proj_units[i].id != nullptr; i++) { + if (::fabs(proj_units[i].factor - conversionToSI()) < + 1e-10 * conversionToSI()) { + return proj_units[i].id; + } + } + } else if (type() == Type::ANGULAR) { + auto proj_angular_units = proj_list_angular_units(); + for (int i = 0; proj_angular_units[i].id != nullptr; i++) { + if (::fabs(proj_angular_units[i].factor - conversionToSI()) < + 1e-10 * conversionToSI()) { + return proj_angular_units[i].id; + } + } + } + return std::string(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool UnitOfMeasure::_isEquivalentTo( + const UnitOfMeasure &other, util::IComparable::Criterion criterion) const { + if (criterion == util::IComparable::Criterion::STRICT) { + return operator==(other); + } + return std::fabs(conversionToSI() - other.conversionToSI()) <= + 1e-10 * std::fabs(conversionToSI()); +} + +//! @endcond +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Measure::Private { + double value_ = 0.0; + UnitOfMeasure unit_{}; + + Private(double valueIn, const UnitOfMeasure &unitIn) + : value_(valueIn), unit_(unitIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Measure. + */ +Measure::Measure(double valueIn, const UnitOfMeasure &unitIn) + : d(internal::make_unique(valueIn, unitIn)) {} + +// --------------------------------------------------------------------------- + +Measure::Measure(const Measure &other) + : d(internal::make_unique(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Measure::~Measure() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the unit of the Measure. + */ +const UnitOfMeasure &Measure::unit() PROJ_CONST_DEFN { return d->unit_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the value of the Measure, after conversion to the + * corresponding + * unit of the International System. + */ +double Measure::getSIValue() PROJ_CONST_DEFN { + return d->value_ * d->unit_.conversionToSI(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the value of the measure, expressed in the unit() + */ +double Measure::value() PROJ_CONST_DEFN { return d->value_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the value of this measure expressed into the provided unit. + */ +double Measure::convertToUnit(const UnitOfMeasure &otherUnit) PROJ_CONST_DEFN { + return getSIValue() / otherUnit.conversionToSI(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether two measures are equal. + * + * The comparison is done both on the value and the unit. + */ +bool Measure::operator==(const Measure &other) PROJ_CONST_DEFN { + return d->value_ == other.d->value_ && d->unit_ == other.d->unit_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns whether an object is equivalent to another one. + * @param other other object to compare to + * @param criterion comparaison criterion. + * @param maxRelativeError Maximum relative error allowed. + * @return true if objects are equivalent. + */ +bool Measure::_isEquivalentTo(const Measure &other, + util::IComparable::Criterion criterion, + double maxRelativeError) const { + if (criterion == util::IComparable::Criterion::STRICT) { + return operator==(other); + } + return std::fabs(getSIValue() - other.getSIValue()) <= + maxRelativeError * std::fabs(getSIValue()); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Scale. + * + * @param valueIn value + */ +Scale::Scale(double valueIn) : Measure(valueIn, UnitOfMeasure::SCALE_UNITY) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Scale. + * + * @param valueIn value + * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::SCALE + */ +Scale::Scale(double valueIn, const UnitOfMeasure &unitIn) + : Measure(valueIn, unitIn) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Scale::Scale(const Scale &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Scale::~Scale() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Angle. + * + * @param valueIn value + */ +Angle::Angle(double valueIn) : Measure(valueIn, UnitOfMeasure::DEGREE) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Angle. + * + * @param valueIn value + * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::ANGULAR + */ +Angle::Angle(double valueIn, const UnitOfMeasure &unitIn) + : Measure(valueIn, unitIn) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Angle::Angle(const Angle &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Angle::~Angle() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Length. + * + * @param valueIn value + */ +Length::Length(double valueIn) : Measure(valueIn, UnitOfMeasure::METRE) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Length. + * + * @param valueIn value + * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::LINEAR + */ +Length::Length(double valueIn, const UnitOfMeasure &unitIn) + : Measure(valueIn, unitIn) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Length::Length(const Length &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Length::~Length() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DateTime::Private { + std::string str_{}; + + explicit Private(const std::string &str) : str_(str) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +DateTime::DateTime() : d(internal::make_unique(std::string())) {} + +// --------------------------------------------------------------------------- + +DateTime::DateTime(const std::string &str) + : d(internal::make_unique(str)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DateTime::DateTime(const DateTime &other) + : d(internal::make_unique(*(other.d))) {} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DateTime::~DateTime() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DateTime. */ +DateTime DateTime::create(const std::string &str) { return DateTime(str); } + +// --------------------------------------------------------------------------- + +/** \brief Return whether the DateTime is ISO:8601 compliant. + * + * \remark The current implementation is really simplistic, and aimed at + * detecting date-times that are not ISO:8601 compliant. + */ +bool DateTime::isISO_8601() const { + return !d->str_.empty() && d->str_[0] >= '0' && d->str_[0] <= '9' && + d->str_.find(' ') == std::string::npos; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the DateTime as a string. + */ +std::string DateTime::toString() const { return d->str_; } + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +// cppcheck-suppress copyCtorAndEqOperator +struct IdentifiedObject::Private { + IdentifierNNPtr name{Identifier::create()}; + std::vector identifiers{}; + std::vector aliases{}; + std::string remarks{}; + bool isDeprecated{}; + + void setIdentifiers(const PropertyMap &properties); + void setName(const PropertyMap &properties); + void setAliases(const PropertyMap &properties); +}; +//! @endcond + +// --------------------------------------------------------------------------- + +IdentifiedObject::IdentifiedObject() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +IdentifiedObject::IdentifiedObject(const IdentifiedObject &other) + : d(internal::make_unique(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +IdentifiedObject::~IdentifiedObject() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the object. + * + * Generally, the only interesting field of the name will be + * name()->description(). + */ +const IdentifierNNPtr &IdentifiedObject::name() PROJ_CONST_DEFN { + return d->name; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the object. + * + * Return *(name()->description()) + */ +const std::string &IdentifiedObject::nameStr() PROJ_CONST_DEFN { + return *(d->name->description()); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the identifier(s) of the object + * + * Generally, those will have Identifier::code() and Identifier::codeSpace() + * filled. + */ +const std::vector & +IdentifiedObject::identifiers() PROJ_CONST_DEFN { + return d->identifiers; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the alias(es) of the object. + */ +const std::vector & +IdentifiedObject::aliases() PROJ_CONST_DEFN { + return d->aliases; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the (first) alias of the object as a string. + * + * Shortcut for aliases()[0]->toFullyQualifiedName()->toString() + */ +std::string IdentifiedObject::alias() PROJ_CONST_DEFN { + if (d->aliases.empty()) + return std::string(); + return d->aliases[0]->toFullyQualifiedName()->toString(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the EPSG code. + * @return code, or 0 if not found + */ +int IdentifiedObject::getEPSGCode() PROJ_CONST_DEFN { + for (const auto &id : identifiers()) { + if (ci_equal(*(id->codeSpace()), metadata::Identifier::EPSG)) { + return ::atoi(id->code().c_str()); + } + } + return 0; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the remarks. + */ +const std::string &IdentifiedObject::remarks() PROJ_CONST_DEFN { + return d->remarks; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether the object is deprecated. + * + * \remark Extension of \ref ISO_19111_2018 + */ +bool IdentifiedObject::isDeprecated() PROJ_CONST_DEFN { + return d->isDeprecated; +} + +// --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress + +void IdentifiedObject::Private::setName( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + const auto pVal = properties.get(NAME_KEY); + if (!pVal) { + return; + } + if (const auto genVal = dynamic_cast(pVal->get())) { + if (genVal->type() == BoxedValue::Type::STRING) { + name = Identifier::createFromDescription(genVal->stringValue()); + } else { + throw InvalidValueTypeException("Invalid value type for " + + NAME_KEY); + } + } else { + if (auto identifier = + util::nn_dynamic_pointer_cast(*pVal)) { + name = NN_NO_CHECK(identifier); + } else { + throw InvalidValueTypeException("Invalid value type for " + + NAME_KEY); + } + } +} + +// --------------------------------------------------------------------------- + +void IdentifiedObject::Private::setIdentifiers( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + auto pVal = properties.get(IDENTIFIERS_KEY); + if (!pVal) { + + pVal = properties.get(Identifier::CODE_KEY); + if (pVal) { + identifiers.push_back( + Identifier::create(std::string(), properties)); + } + return; + } + if (auto identifier = util::nn_dynamic_pointer_cast(*pVal)) { + identifiers.clear(); + identifiers.push_back(NN_NO_CHECK(identifier)); + } else { + if (auto array = dynamic_cast(pVal->get())) { + identifiers.clear(); + for (const auto &val : *array) { + identifier = util::nn_dynamic_pointer_cast(val); + if (identifier) { + identifiers.push_back(NN_NO_CHECK(identifier)); + } else { + throw InvalidValueTypeException("Invalid value type for " + + IDENTIFIERS_KEY); + } + } + } else { + throw InvalidValueTypeException("Invalid value type for " + + IDENTIFIERS_KEY); + } + } +} + +// --------------------------------------------------------------------------- + +void IdentifiedObject::Private::setAliases( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + const auto pVal = properties.get(ALIAS_KEY); + if (!pVal) { + return; + } + if (auto l_name = util::nn_dynamic_pointer_cast(*pVal)) { + aliases.clear(); + aliases.push_back(NN_NO_CHECK(l_name)); + } else { + if (const auto array = + dynamic_cast(pVal->get())) { + aliases.clear(); + for (const auto &val : *array) { + l_name = util::nn_dynamic_pointer_cast(val); + if (l_name) { + aliases.push_back(NN_NO_CHECK(l_name)); + } else { + if (auto genVal = + dynamic_cast(val.get())) { + if (genVal->type() == BoxedValue::Type::STRING) { + aliases.push_back(NameFactory::createLocalName( + nullptr, genVal->stringValue())); + } else { + throw InvalidValueTypeException( + "Invalid value type for " + ALIAS_KEY); + } + } else { + throw InvalidValueTypeException( + "Invalid value type for " + ALIAS_KEY); + } + } + } + } else { + std::string temp; + if (properties.getStringValue(ALIAS_KEY, temp)) { + aliases.clear(); + aliases.push_back(NameFactory::createLocalName(nullptr, temp)); + } else { + throw InvalidValueTypeException("Invalid value type for " + + ALIAS_KEY); + } + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +void IdentifiedObject::setProperties( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + d->setName(properties); + d->setIdentifiers(properties); + d->setAliases(properties); + + properties.getStringValue(REMARKS_KEY, d->remarks); + + { + const auto pVal = properties.get(DEPRECATED_KEY); + if (pVal) { + if (const auto genVal = + dynamic_cast(pVal->get())) { + if (genVal->type() == BoxedValue::Type::BOOLEAN) { + d->isDeprecated = genVal->booleanValue(); + } else { + throw InvalidValueTypeException("Invalid value type for " + + DEPRECATED_KEY); + } + } else { + throw InvalidValueTypeException("Invalid value type for " + + DEPRECATED_KEY); + } + } + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void IdentifiedObject::formatID(WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; + for (const auto &id : identifiers()) { + id->_exportToWKT(formatter); + if (!isWKT2) { + break; + } + } +} + +// --------------------------------------------------------------------------- + +void IdentifiedObject::formatRemarks(WKTFormatter *formatter) const { + if (!remarks().empty()) { + formatter->startNode(WKTConstants::REMARK, false); + formatter->addQuotedString(remarks()); + formatter->endNode(); + } +} + +// --------------------------------------------------------------------------- + +bool IdentifiedObject::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherIdObj = dynamic_cast(other); + if (!otherIdObj) + return false; + return _isEquivalentTo(otherIdObj, criterion); +} + +// --------------------------------------------------------------------------- + +bool IdentifiedObject::_isEquivalentTo(const IdentifiedObject *otherIdObj, + util::IComparable::Criterion criterion) + PROJ_CONST_DEFN { + if (criterion == util::IComparable::Criterion::STRICT) { + if (!ci_equal(nameStr(), otherIdObj->nameStr())) { + return false; + } + // TODO test id etc + } else { + if (!metadata::Identifier::isEquivalentName( + nameStr().c_str(), otherIdObj->nameStr().c_str())) { + return false; + } + } + return true; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ObjectDomain::Private { + optional scope_{}; + ExtentPtr domainOfValidity_{}; + + Private(const optional &scopeIn, const ExtentPtr &extent) + : scope_(scopeIn), domainOfValidity_(extent) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ObjectDomain::ObjectDomain(const optional &scopeIn, + const ExtentPtr &extent) + : d(internal::make_unique(scopeIn, extent)) {} +//! @endcond + +// --------------------------------------------------------------------------- + +ObjectDomain::ObjectDomain(const ObjectDomain &other) + : d(internal::make_unique(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ObjectDomain::~ObjectDomain() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the scope. + * + * @return the scope, or empty. + */ +const optional &ObjectDomain::scope() PROJ_CONST_DEFN { + return d->scope_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the domain of validity. + * + * @return the domain of validity, or nullptr. + */ +const ExtentPtr &ObjectDomain::domainOfValidity() PROJ_CONST_DEFN { + return d->domainOfValidity_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ObjectDomain. + */ +ObjectDomainNNPtr ObjectDomain::create(const optional &scopeIn, + const ExtentPtr &extent) { + return ObjectDomain::nn_make_shared(scopeIn, extent); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ObjectDomain::_exportToWKT(WKTFormatter *formatter) const { + if (d->scope_.has_value()) { + formatter->startNode(WKTConstants::SCOPE, false); + formatter->addQuotedString(*(d->scope_)); + formatter->endNode(); + } else if (formatter->use2018Keywords()) { + formatter->startNode(WKTConstants::SCOPE, false); + formatter->addQuotedString("unknown"); + formatter->endNode(); + } + if (d->domainOfValidity_) { + if (d->domainOfValidity_->description().has_value()) { + formatter->startNode(WKTConstants::AREA, false); + formatter->addQuotedString(*(d->domainOfValidity_->description())); + formatter->endNode(); + } + if (d->domainOfValidity_->geographicElements().size() == 1) { + const auto bbox = dynamic_cast( + d->domainOfValidity_->geographicElements()[0].get()); + if (bbox) { + formatter->startNode(WKTConstants::BBOX, false); + formatter->add(bbox->southBoundLatitude()); + formatter->add(bbox->westBoundLongitude()); + formatter->add(bbox->northBoundLatitude()); + formatter->add(bbox->eastBoundLongitude()); + formatter->endNode(); + } + } + if (d->domainOfValidity_->verticalElements().size() == 1) { + auto extent = d->domainOfValidity_->verticalElements()[0]; + formatter->startNode(WKTConstants::VERTICALEXTENT, false); + formatter->add(extent->minimumValue()); + formatter->add(extent->maximumValue()); + extent->unit()->_exportToWKT(formatter); + formatter->endNode(); + } + if (d->domainOfValidity_->temporalElements().size() == 1) { + auto extent = d->domainOfValidity_->temporalElements()[0]; + formatter->startNode(WKTConstants::TIMEEXTENT, false); + if (DateTime::create(extent->start()).isISO_8601()) { + formatter->add(extent->start()); + } else { + formatter->addQuotedString(extent->start()); + } + if (DateTime::create(extent->stop()).isISO_8601()) { + formatter->add(extent->stop()); + } else { + formatter->addQuotedString(extent->stop()); + } + formatter->endNode(); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool ObjectDomain::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDomain = dynamic_cast(other); + if (!otherDomain) + return false; + if (scope().has_value() != otherDomain->scope().has_value()) + return false; + if (*scope() != *otherDomain->scope()) + return false; + if ((domainOfValidity().get() != nullptr) ^ + (otherDomain->domainOfValidity().get() != nullptr)) + return false; + return domainOfValidity().get() == nullptr || + domainOfValidity()->_isEquivalentTo( + otherDomain->domainOfValidity().get(), criterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ObjectUsage::Private { + std::vector domains_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +ObjectUsage::ObjectUsage() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +ObjectUsage::ObjectUsage(const ObjectUsage &other) + : IdentifiedObject(other), d(internal::make_unique(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ObjectUsage::~ObjectUsage() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the domains of the object. + */ +const std::vector &ObjectUsage::domains() PROJ_CONST_DEFN { + return d->domains_; +} + +// --------------------------------------------------------------------------- + +void ObjectUsage::setProperties( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + IdentifiedObject::setProperties(properties); + + optional scope; + properties.getStringValue(SCOPE_KEY, scope); + + ExtentPtr domainOfValidity; + { + const auto pVal = properties.get(DOMAIN_OF_VALIDITY_KEY); + if (pVal) { + domainOfValidity = util::nn_dynamic_pointer_cast(*pVal); + if (!domainOfValidity) { + throw InvalidValueTypeException("Invalid value type for " + + DOMAIN_OF_VALIDITY_KEY); + } + } + } + + if (scope.has_value() || domainOfValidity) { + d->domains_.emplace_back(ObjectDomain::create(scope, domainOfValidity)); + } + + { + const auto pVal = properties.get(OBJECT_DOMAIN_KEY); + if (pVal) { + if (auto objectDomain = + util::nn_dynamic_pointer_cast(*pVal)) { + d->domains_.emplace_back(NN_NO_CHECK(objectDomain)); + } else if (const auto array = + dynamic_cast( + pVal->get())) { + for (const auto &val : *array) { + objectDomain = + util::nn_dynamic_pointer_cast(val); + if (objectDomain) { + d->domains_.emplace_back(NN_NO_CHECK(objectDomain)); + } else { + throw InvalidValueTypeException( + "Invalid value type for " + OBJECT_DOMAIN_KEY); + } + } + } else { + throw InvalidValueTypeException("Invalid value type for " + + OBJECT_DOMAIN_KEY); + } + } + } +} + +// --------------------------------------------------------------------------- + +void ObjectUsage::baseExportToWKT(WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; + if (isWKT2 && formatter->outputId()) { + auto l_domains = domains(); + if (!l_domains.empty()) { + if (formatter->use2018Keywords()) { + for (const auto &domain : l_domains) { + formatter->startNode(WKTConstants::USAGE, false); + domain->_exportToWKT(formatter); + formatter->endNode(); + } + } else { + l_domains[0]->_exportToWKT(formatter); + } + } + } + if (formatter->outputId()) { + formatID(formatter); + } + if (isWKT2) { + formatRemarks(formatter); + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool ObjectUsage::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherObjUsage = dynamic_cast(other); + if (!otherObjUsage) + return false; + + // TODO: incomplete + return IdentifiedObject::_isEquivalentTo(other, criterion); +} +//! @endcond + +} // namespace common +NS_PROJ_END diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp new file mode 100644 index 00000000..442a9b78 --- /dev/null +++ b/src/iso19111/coordinateoperation.cpp @@ -0,0 +1,11749 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 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 "projects.h" // M_PI + +#include +#include +#include +#include +#include +#include +#include +#include + +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 *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation"; +static const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset"; +//! @endcond + +//! @cond Doxygen_Suppress +static util::PropertyMap +createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom, + bool approximateInversion); +//! @endcond + +// --------------------------------------------------------------------------- + +//! @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 mentionned 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; + } + } + 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 concantenated 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(); + } + return nullExtent; +} + +// --------------------------------------------------------------------------- + +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_{}; + + // 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_), + strongRef_(other.strongRef_ ? internal::make_unique( + *(other.strongRef_)) + : nullptr) {} + + Private &operator=(const Private &) = delete; +}; + +// --------------------------------------------------------------------------- + +GridDescription::GridDescription() = default; + +GridDescription::~GridDescription() = default; + +GridDescription::GridDescription(const GridDescription &) = default; + +GridDescription::GridDescription(GridDescription &&) noexcept = default; + +//! @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 instanciated as + * a PROJ pipeline, checking in particular that referenced grids are + * available. + */ +bool CoordinateOperation::isPROJInstanciable( + const io::DatabaseContextPtr &databaseContext) const { + try { + exportToPROJString(io::PROJStringFormatter::create().get()); + } catch (const std::exception &) { + return false; + } + for (const auto &gridDesc : gridsNeeded(databaseContext)) { + if (!gridDesc.available) { + return false; + } + } + return true; +} + +// --------------------------------------------------------------------------- + +//! @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_CONST_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_CONST_DEFN { + return d->formulaCitation_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the parameters of this operation method. + * + * @return the parameters. + */ +const std::vector & +OperationMethod::parameters() PROJ_CONST_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_CONST_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 +bool OperationMethod::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherOM = dynamic_cast(other); + if (otherOM == nullptr || + !IdentifiedObject::_isEquivalentTo(other, criterion)) { + 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)) { + 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)) { + 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_CONST_DEFN { + return d->parameter; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the parameter value. + * + * @return the parameter value. + */ +const ParameterValueNNPtr & +OperationParameterValue::parameterValue() PROJ_CONST_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 + +/** 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 { + auto otherOPV = dynamic_cast(other); + if (otherOPV == nullptr) { + return false; + } + if (!d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(), + criterion)) { + 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)) { + 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 { + auto otherOP = dynamic_cast(other); + if (otherOP == nullptr) { + return false; + } + if (criterion == util::IComparable::Criterion::STRICT) { + return IdentifiedObject::_isEquivalentTo(other, criterion); + } + if (IdentifiedObject::_isEquivalentTo(other, criterion)) { + 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_CONST_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_CONST_DEFN { + return d->parameterValues_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the operation method associated to the operation. + * + * @return the operation method. + */ +const OperationMethodNNPtr &SingleOperation::method() PROJ_CONST_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; +} + +//! @endcond +// --------------------------------------------------------------------------- + +/** \brief Instanciate 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 std::vector &accuracies = + std::vector()) { + return util::nn_static_pointer_cast( + PROJBasedOperation::create(properties, projExportable, false, sourceCRS, + targetCRS, accuracies)); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool SingleOperation::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + return _isEquivalentTo(other, criterion, false); +} + +bool SingleOperation::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion, + bool inOtherDirection) const { + + auto otherSO = dynamic_cast(other); + if (otherSO == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion))) { + 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); + 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_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); + } 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); + } + } + } + } + + 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)) { + 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)) { + 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); + 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) && + value_2nd->_isEquivalentTo( + otherSO + ->parameterValue( + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) + .get(), + criterion); + } + } + } + + 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, 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); + } + } + } + return equivalent; +} +//! @endcond + +// --------------------------------------------------------------------------- + +std::set SingleOperation::gridsNeeded( + const io::DatabaseContextPtr &databaseContext) 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) { + GridDescription desc; + desc.shortName = value->valueFile(); + if (databaseContext) { + databaseContext->lookForGridInfo( + desc.shortName, desc.fullName, desc.packageName, + desc.url, desc.directDownload, desc.openLicense, + desc.available); + } + res.insert(desc); + } + } + } + return res; +} + +// --------------------------------------------------------------------------- + +/** \brief Validate the parameters used by a coodinate 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) { + 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("Paramater 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 Instanciate 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 Instanciate 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 Instanciate 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 Instanciate 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 Instanciate a ParameterValue from a integer value. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(int integerValueIn) { + return ParameterValue::nn_make_shared(integerValueIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate 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_CONST_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_CONST_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_CONST_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_CONST_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_CONST_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_CONST_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(); + const auto &l_value = value(); + if (formatter->abridgedTransformation() && l_type == Type::MEASURE) { + 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 if (l_type == Type::MEASURE) { + 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) { + formatter->add( + l_value.convertToUnit(*(formatter->axisLinearUnit()))); + } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { + formatter->add( + l_value.convertToUnit(*(formatter->axisAngularUnit()))); + } 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 { + 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); + } + + 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; +} +//! @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 Instanciate 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 Instanciate 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 Instanciate a [Universal Transverse Mercator] + *(https://proj4.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 Instanciate a conversion based on the [Transverse Mercator] + *(https://proj4.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 Instanciate a conversion based on the [Gauss Schreiber Transverse + *Mercator] + *(https://proj4.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 Instanciate a conversion based on the [Transverse Mercator South + *Orientated] + *(https://proj4.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 Instanciate a conversion based on the [Two Point Equidistant] + *(https://proj4.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 Instanciate 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 Instanciate a conversion based on the [Albers Conic Equal Area] + *(https://proj4.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 Instanciate a conversion based on the [Lambert Conic Conformal 1SP] + *(https://proj4.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 Instanciate a conversion based on the [Lambert Conic Conformal (2SP)] + *(https://proj4.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 Instanciate a conversion based on the [Lambert Conic Conformal (2SP + *Michigan)] + *(https://proj4.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 Instanciate a conversion based on the [Lambert Conic Conformal (2SP + *Belgium)] + *(https://proj4.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 Instanciate a conversion based on the [Modified Azimuthal + *Equidistant] + *(https://proj4.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 Instanciate a conversion based on the [Guam Projection] + *(https://proj4.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 Instanciate a conversion based on the [Bonne] + *(https://proj4.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 Instanciate a conversion based on the [Lambert Cylindrical Equal Area + *(Spherical)] + *(https://proj4.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 GeographicalCRS. + * + * @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 Instanciate a conversion based on the [Lambert Cylindrical Equal Area + *(ellipsoidal form)] + *(https://proj4.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 Instanciate a conversion based on the [Cassini-Soldner] + * (https://proj4.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 Instanciate a conversion based on the [Equidistant Conic] + *(https://proj4.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 Instanciate a conversion based on the [Eckert I] + * (https://proj4.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 Instanciate a conversion based on the [Eckert II] + * (https://proj4.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 Instanciate a conversion based on the [Eckert III] + * (https://proj4.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 Instanciate a conversion based on the [Eckert IV] + * (https://proj4.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 Instanciate a conversion based on the [Eckert V] + * (https://proj4.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 Instanciate a conversion based on the [Eckert VI] + * (https://proj4.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 Instanciate a conversion based on the [Equidistant Cylindrical] + *(https://proj4.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 Instanciate a conversion based on the [Equidistant Cylindrical + *(Spherical)] + *(https://proj4.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 Instanciate a conversion based on the [Gall (Stereographic)] + * (https://proj4.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 Instanciate a conversion based on the [Goode Homolosine] + * (https://proj4.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 Instanciate a conversion based on the [Interrupted Goode Homolosine] + * (https://proj4.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 Instanciate a conversion based on the [Geostationary Satellite View] + * (https://proj4.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 Instanciate a conversion based on the [Geostationary Satellite View] + * (https://proj4.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 Instanciate a conversion based on the [Gnomonic] + *(https://proj4.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 Instanciate a conversion based on the [Hotine Oblique Mercator + *(Variant A)] + *(https://proj4.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://proj4.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 Instanciate a conversion based on the [Hotine Oblique Mercator + *(Variant B)] + *(https://proj4.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://proj4.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 Instanciate a conversion based on the [Hotine Oblique Mercator Two + *Point Natural Origin] + *(https://proj4.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 Instanciate a conversion based on the [Laborde Oblique Mercator] + *(https://proj4.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 Instanciate a conversion based on the [International Map of the World + *Polyconic] + *(https://proj4.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 Instanciate a conversion based on the [Krovak (north oriented)] + *(https://proj4.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 Instanciate a conversion based on the [Krovak] + *(https://proj4.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 Instanciate a conversion based on the [Lambert Azimuthal Equal Area] + *(https://proj4.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 Instanciate a conversion based on the [Miller Cylindrical] + *(https://proj4.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 Instanciate a conversion based on the [Mercator] + *(https://proj4.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 Instanciate a conversion based on the [Mercator] + *(https://proj4.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 Instanciate a conversion based on the [Popular Visualisation Pseudo + *Mercator] + *(https://proj4.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 Instanciate a conversion based on the [Mollweide] + * (https://proj4.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 Instanciate a conversion based on the [New Zealand Map Grid] + * (https://proj4.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 Instanciate a conversion based on the [Oblique Stereographic + *(Alternative)] + *(https://proj4.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 Instanciate a conversion based on the [Orthographic] + *(https://proj4.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 At the time of writing, PROJ only implements the spherical formulation + * + * @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 Instanciate a conversion based on the [American Polyconic] + *(https://proj4.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 Instanciate a conversion based on the [Polar Stereographic (Variant + *A)] + *(https://proj4.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 Instanciate a conversion based on the [Polar Stereographic (Variant + *B)] + *(https://proj4.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 Instanciate a conversion based on the [Robinson] + * (https://proj4.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 Instanciate a conversion based on the [Sinusoidal] + * (https://proj4.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 Instanciate a conversion based on the [Stereographic] + *(https://proj4.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 Instanciate a conversion based on the [Van der Grinten] + * (https://proj4.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 Instanciate a conversion based on the [Wagner I] + * (https://proj4.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 Instanciate a conversion based on the [Wagner II] + * (https://proj4.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 Instanciate a conversion based on the [Wagner III] + * (https://proj4.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 Instanciate a conversion based on the [Wagner IV] + * (https://proj4.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 Instanciate a conversion based on the [Wagner V] + * (https://proj4.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 Instanciate a conversion based on the [Wagner VI] + * (https://proj4.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 Instanciate a conversion based on the [Wagner VII] + * (https://proj4.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 Instanciate a conversion based on the [Quadrilateralized Spherical + *Cube] + *(https://proj4.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 Instanciate a conversion based on the [Spherical Cross-Track Height] + *(https://proj4.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 Instanciate a conversion based on the [Equal Earth] + * (https://proj4.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)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static OperationParameterNNPtr createOpParamNameEPSGCode(int code) { + const char *name = OperationParameter::getNameForEPSGCode(code); + assert(name); + return OperationParameter::create(createMapNameEPSGCode(name, code)); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate 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 Instanciate 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 (geographic3D horizontal)", 15499), + createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D), + {}, {}); + } else { + return create(createMapNameEPSGCode("axis order change (2D)", 15498), + createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D), + {}, {}); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate 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 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; +} + +//! @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; + } + + 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 std::move(conv); + } + + 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 std::move(conv); + } + + 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 std::move(conv); + } 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 std::move(conv); + } + } + + 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 std::move(conv); + } + } + + 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 std::move(conv); + } + + 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 (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); + } + + 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; + } + } + } + } + genOpParamvalue->_exportToWKT(formatter, mapping); + } + } + + if (isWKT2) { + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); + } else { + formatter->popOutputUnit(); + formatter->popOutputId(); + formatter->leave(); + } +} +//! @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; + } + + 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", "m"); + 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(); + int zone = 0; + bool north = true; + if (l_method->getPrivate()->projMethodOverride_ == "etmerc" && + !isUTM(zone, north)) { + auto projFormatter = io::PROJStringFormatter::create( + io::PROJStringFormatter::Convention::PROJ_4); + projFormatter->setUseETMercForTMerc(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( + io::PROJStringFormatter::Convention::PROJ_4); + 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( + io::PROJStringFormatter::Convention::PROJ_4); + 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( + io::PROJStringFormatter::Convention::PROJ_4); + 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 applySourceCRSModifiers = + !isZUnitConversion && !isAffineParametric && + !isAxisOrderReversal(methodEPSGCode) && !isGeographicGeocentric; + const bool applyTargetCRSModifiers = applySourceCRSModifiers; + + auto l_sourceCRS = sourceCRS(); + if (l_sourceCRS && applySourceCRSModifiers && + formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + auto geogCRS = + dynamic_cast(l_sourceCRS.get()); + if (geogCRS) { + formatter->setOmitProjLongLatIfPossible(true); + formatter->startInversion(); + geogCRS->_exportToPROJString(formatter); + formatter->stopInversion(); + formatter->setOmitProjLongLatIfPossible(false); + } + + auto projCRS = + dynamic_cast(l_sourceCRS.get()); + if (projCRS) { + formatter->startInversion(); + projCRS->addUnitConvertAndAxisSwap(formatter, false); + formatter->stopInversion(); + } + } + + const auto &convName = nameStr(); + bool bConversionDone = false; + bool bEllipsoidParametersDone = false; + bool useETMerc = false; + if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { + // Check for UTM + int zone = 0; + bool north = true; + bool etMercSettingSet = false; + useETMerc = formatter->getUseETMercForTMerc(etMercSettingSet) || + l_method->getPrivate()->projMethodOverride_ == "etmerc"; + if (isUTM(zone, north) && !(etMercSettingSet && !useETMerc)) { + bConversionDone = true; + formatter->addStep("utm"); + 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); + } + // PROJ.4 specific hack for webmercator + } else if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4 && + 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; + } else if (ci_equal(convName, "Popular Visualisation Mercator")) { + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + if (!createPROJ4WebMercator(this, formatter)) { + throw io::FormattingException(concat( + "Cannot export ", convName, + " as PROJ.4 string outside of a ProjectedCRS context")); + } + } 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 (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 = + common::UnitOfMeasure(std::string(), 1.0 / convFactor, + common::UnitOfMeasure::Type::LINEAR) + .exportToPROJString(); + 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; + } + + bool bAxisSpecFound = false; + if (!bConversionDone) { + const MethodMapping *mapping = getMapping(l_method.get()); + if (mapping && mapping->proj_name_main) { + formatter->addStep(useETMerc ? "etmerc" : mapping->proj_name_main); + if (mapping->proj_name_aux) { + if (internal::starts_with(mapping->proj_name_aux, "axis=")) { + bAxisSpecFound = true; + } + 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; + } + auto value = + parameterValueMeasure(param->wkt2_name, param->epsg_code); + if (mapping->epsg_code == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && + strcmp(param->proj_name, "lat_1") == 0) { + formatter->addParam( + param->proj_name, + value.convertToUnit(common::UnitOfMeasure::DEGREE)); + formatter->addParam( + "lat_0", + value.convertToUnit(common::UnitOfMeasure::DEGREE)); + } else if (param->unit_type == + common::UnitOfMeasure::Type::ANGULAR) { + formatter->addParam( + param->proj_name, + value.convertToUnit(common::UnitOfMeasure::DEGREE)); + } else { + formatter->addParam(param->proj_name, value.getSIValue()); + } + } + + } else { + if (!exportToPROJStringGeneric(formatter)) { + throw io::FormattingException( + concat("Unsupported conversion method: ", methodName)); + } + } + } + + auto l_targetCRS = targetCRS(); + if (l_targetCRS && applyTargetCRSModifiers) { + if (!bEllipsoidParametersDone) { + auto targetGeogCRS = l_targetCRS->extractGeographicCRS(); + if (targetGeogCRS) { + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + targetGeogCRS->addDatumInfoToPROJString(formatter); + } else { + targetGeogCRS->ellipsoid()->_exportToPROJString(formatter); + targetGeogCRS->primeMeridian()->_exportToPROJString( + formatter); + } + } + } + + auto projCRS = + dynamic_cast(l_targetCRS.get()); + if (projCRS) { + projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound); + } + + auto derivedGeographicCRS = + dynamic_cast(l_targetCRS.get()); + if (derivedGeographicCRS) { + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + auto geogCRS = derivedGeographicCRS->baseCRS(); + formatter->setOmitProjLongLatIfPossible(true); + geogCRS->_exportToPROJString(formatter); + formatter->setOmitProjLongLatIfPossible(false); + } + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return whether a conversion is a [Universal Transverse Mercator] + * (https://proj4.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_{}; + + TransformationNNPtr registerInv(util::BaseObjectNNPtr 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_CONST_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_CONST_DEFN { + return CoordinateOperation::getPrivate()->strongRef_->targetCRS_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TransformationNNPtr Transformation::shallowClone() const { + auto conv = Transformation::nn_make_shared(*this); + conv->assignSelf(conv); + conv->setCRSs(this, false); + return conv; +} +//! @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 Instanciate 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 conv = Transformation::nn_make_shared( + sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values, + accuracies); + conv->assignSelf(conv); + conv->setProperties(properties); + return conv; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate 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; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate 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( + 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 Instanciate 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( + 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 Instanciate 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( + 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 Instanciate 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( + 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 Instanciate 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( + 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 Instanciate 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 Instanciate 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 Instanciate 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 Instanciate 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 std::string &filename, + const std::vector &accuracies) { + + return Transformation::create( + properties, sourceCRSIn, targetCRSIn, nullptr, + 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 Instanciate 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 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 std::string &filename, + const std::vector &accuracies) { + + return _createGravityRelatedHeightToGeographic3D( + properties, false, sourceCRSIn, targetCRSIn, filename, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate 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 Instanciate 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 Instanciate 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 Instanciate 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 Instanciate 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 Instanciate 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); +} + +// --------------------------------------------------------------------------- + +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; +} + +// --------------------------------------------------------------------------- + +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, NULL_GEOCENTRIC_TRANSLATION)) { + opType = NULL_GEOCENTRIC_TRANSLATION; + } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) { + opType = NULL_GEOGRAPHIC_OFFSET; + } 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 (starts_with(forwardName, INVERSE_OF)) { + name = forwardName.substr(INVERSE_OF.size()); + } else if (!sourceCRS || !targetCRS || + forwardName != buildOpName(opType, sourceCRS, targetCRS)) { + 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); + } + + addModifiedIdentifier(map, op, true, derivedFrom); + + 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(util::BaseObjectNNPtr thisIn, + TransformationNNPtr invTransform) { + invTransform->d->forwardOperation_ = + util::nn_dynamic_pointer_cast(thisIn); + 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); + return d->registerInv( + shared_from_this(), + createGeocentricTranslations( + createPropertiesForInverse(this, false, false), l_targetCRS, + l_sourceCRS, negate(x), negate(y), 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 d->registerInv( + shared_from_this(), + createAbridgedMolodensky( + createPropertiesForInverse(this, false, false), l_targetCRS, + l_sourceCRS, negate(x), negate(y), negate(z), negate(da), + negate(df), coordinateOperationAccuracies())); + } else { + return d->registerInv( + shared_from_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 d->registerInv( + shared_from_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 d->registerInv( + shared_from_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 d->registerInv( + shared_from_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 d->registerInv( + shared_from_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 d->registerInv( + shared_from_this(), + createVerticalOffset(createPropertiesForInverse(this, false, false), + l_targetCRS, l_sourceCRS, newOffsetHeight, + coordinateOperationAccuracies())); + } + + 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; +} + +// --------------------------------------------------------------------------- + +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); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Transformation::_exportToWKT(io::WKTFormatter *formatter) const { + exportTransformationToWKT(formatter); +} +//! @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"); + } + + auto l_sourceCRS = sourceCRS(); + assert(l_sourceCRS); + auto l_targetCRS = targetCRS(); + assert(l_targetCRS); + + if (formatter->abridgedTransformation()) { + formatter->startNode(io::WKTConstants::ABRIDGEDTRANSFORMATION, + !identifiers().empty()); + } else { + formatter->startNode(io::WKTConstants::COORDINATEOPERATION, + !identifiers().empty()); + } + + formatter->addQuotedString(nameStr()); + + if (!formatter->abridgedTransformation()) { + formatter->startNode(io::WKTConstants::SOURCECRS, false); + l_sourceCRS->_exportToWKT(formatter); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::TARGETCRS, false); + l_targetCRS->_exportToWKT(formatter); + formatter->endNode(); + } + + method()->_exportToWKT(formatter); + + const MethodMapping *mapping = + !isWKT2 ? getMapping(method().get()) : nullptr; + for (const auto ¶mValue : parameterValues()) { + paramValue->_exportToWKT(formatter, mapping); + } + + 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 & +_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 +const std::string &Transformation::getHeightToGeographic3DFilename() const { + + return _getHeightToGeographic3DFilename(this, false); +} +//! @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) + "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) + }; + + 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 +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) { + assert(!inverseDirection); + return self; + } + + const auto &l_sourceCRS = sourceCRS(); + const auto &l_targetCRS = targetCRS(); + const auto &l_accuracies = coordinateOperationAccuracies(); + 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); + } + } + } + + const auto &heightFilename = getHeightToGeographic3DFilename(); + if (!heightFilename.empty() && !projFilename.empty()) { + if (databaseContext->lookForGridAlternative( + heightFilename, projFilename, projGridFormat, + inverseDirection)) { + + if (heightFilename == projFilename) { + assert(!inverseDirection); + return self; + } + + if (inverseDirection) { + return createGravityRelatedHeightToGeographic3D( + createPropertiesForInverse(self.as_nullable().get(), + true, false), + targetCRS(), sourceCRS(), projFilename, + coordinateOperationAccuracies()) + ->inverseAsTransformation(); + } else { + return createGravityRelatedHeightToGeographic3D( + createSimilarPropertiesTransformation(self), sourceCRS(), + targetCRS(), projFilename, coordinateOperationAccuracies()); + } + } + } + + 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 (filename == projFilename) { + assert(!inverseDirection); + return self; + } + + auto parameters = std::vector{ + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)}; + 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 ThrowExpectionNotGeodeticGeographic(const char *trfrm_name) { + throw io::FormattingException(concat("Can apply ", std::string(trfrm_name), + " only to GeodeticCRS / " + "GeographicCRS")); +} + +// --------------------------------------------------------------------------- + +static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter, + const crs::CRSNNPtr &crs, + const char *trfrm_name) { + auto sourceCRSGeog = dynamic_cast(crs.get()); + if (sourceCRSGeog) { + formatter->startInversion(); + sourceCRSGeog->_exportToPROJString(formatter); + formatter->stopInversion(); + + formatter->addStep("cart"); + sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); + } else { + auto sourceCRSGeod = dynamic_cast(crs.get()); + if (!sourceCRSGeod) { + ThrowExpectionNotGeodeticGeographic(trfrm_name); + } + formatter->startInversion(); + sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter); + formatter->stopInversion(); + } +} +// --------------------------------------------------------------------------- + +static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter, + const crs::CRSNNPtr &crs, + const char *trfrm_name) { + auto targetCRSGeog = dynamic_cast(crs.get()); + if (targetCRSGeog) { + formatter->addStep("cart"); + formatter->setCurrentStepInverted(true); + targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); + + targetCRSGeog->_exportToPROJString(formatter); + } else { + auto targetCRSGeod = dynamic_cast(crs.get()); + if (!targetCRSGeod) { + ThrowExpectionNotGeodeticGeographic(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); + + setupPROJGeodeticSourceCRS(formatter, sourceCRS(), "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(), "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); + + setupPROJGeodeticSourceCRS(formatter, sourceCRS(), + "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(), + "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 &hGridShiftFilename = + !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty() + ? NTv2Filename + : CTABLE2Filename; + if (!hGridShiftFilename.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")); + } + + 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 &heightFilename = _getHeightToGeographic3DFilename(this, true); + if (!heightFilename.empty()) { + if (isMethodInverseOf) { + formatter->startInversion(); + } + formatter->addStep("vgridshift"); + formatter->addParam("grids", heightFilename); + if (isMethodInverseOf) { + formatter->stopInversion(); + } + 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(); + if (isMethodInverseOf) { + formatter->startInversion(); + } + formatter->addStep("vgridshift"); + formatter->addParam("grids", filename); + if (isMethodInverseOf) { + formatter->stopInversion(); + } + 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"); + // The vertcon grids go from NGVD 29 to NAVD 88, with units + // in millimeter (see + // https://github.com/OSGeo/proj.4/issues/1071) + formatter->addParam("grids", fileParameter->valueFile()); + formatter->addParam("multiplier", 0.001); + 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())) { + // 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"); + } + + 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) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ConcatenatedOperation::~ConcatenatedOperation() = default; +//! @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_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate 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; + for (size_t i = 0; i < operationsIn.size(); i++) { + auto l_sourceCRS = operationsIn[i]->sourceCRS(); + auto l_targetCRS = operationsIn[i]->targetCRS(); + 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) { + const auto &sourceCRSIds = l_sourceCRS->identifiers(); + const auto &targetCRSIds = lastTargetCRS->identifiers(); + if (sourceCRSIds.size() == 1 && targetCRSIds.size() == 1 && + sourceCRSIds[0]->code() == targetCRSIds[0]->code() && + *sourceCRSIds[0]->codeSpace() == + *targetCRSIds[0]->codeSpace()) { + // same id --> ok + } else if (!l_sourceCRS->_isEquivalentTo( + lastTargetCRS.get(), + util::IComparable::Criterion::EQUIVALENT)) { + 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()), nullptr); + op->setAccuracies(accuracies); + return op; +} + +// --------------------------------------------------------------------------- + +//! @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 Instanciate 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; + for (const auto &subOp : operationsIn) { + 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 concantenated " + "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->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_; + return op; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ConcatenatedOperation::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2 || !formatter->use2018Keywords()) { + throw io::FormattingException( + "Transformation can only be exported to WKT2:2018"); + } + + formatter->startNode(io::WKTConstants::CONCATENATEDOPERATION, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + formatter->startNode(io::WKTConstants::SOURCECRS, false); + sourceCRS()->_exportToWKT(formatter); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::TARGETCRS, false); + targetCRS()->_exportToWKT(formatter); + formatter->endNode(); + + for (const auto &operation : operations()) { + formatter->startNode(io::WKTConstants::STEP, false); + operation->_exportToWKT(formatter); + formatter->endNode(); + } + + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @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 { + auto otherCO = dynamic_cast(other); + if (otherCO == nullptr || !ObjectUsage::_isEquivalentTo(other, criterion)) { + 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)) { + return false; + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +std::set ConcatenatedOperation::gridsNeeded( + const io::DatabaseContextPtr &databaseContext) const { + std::set res; + for (const auto &operation : operations()) { + const auto l_gridsNeeded = operation->gridsNeeded(databaseContext); + 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; + bool allowUseIntermediateCRS_ = true; + std::vector> + intermediateCRSAuthCodes_{}; + bool discardSuperseded_ = 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 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 true, allow + * chaining both operations. + * + * The current implementation is limited to researching one intermediate + * step. + * + * By default, all potential C candidates will be used. setIntermediateCRS() + * can be used to restrict them. + * + * The default is true. + */ +void CoordinateOperationContext::setAllowUseIntermediateCRS(bool 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 true, allow + * chaining both operations. + * + * The default is true. + */ +bool 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 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 crs::CRSNNPtr &sourceCRS; + const crs::CRSNNPtr &targetCRS; + const CoordinateOperationContextNNPtr &context; + bool inCreateOperationsWithDatumPivotAntiRecursion = false; + + Context(const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, + const CoordinateOperationContextNNPtr &contextIn) + : sourceCRS(sourceCRSIn), targetCRS(targetCRSIn), + context(contextIn) {} + }; + + static std::vector + createOperations(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, Context &context); + + private: + static std::vector createOperationsGeogToGeog( + std::vector &res, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + 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 ConversionNNPtr + createGeographicGeocentric(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 hasGrids_ = false; + bool gridsAvailable_ = false; + bool gridsKnown_ = false; + size_t stepCount_ = 0; + + PrecomputedOpCharacteristics() = default; + PrecomputedOpCharacteristics(double area, double accuracy, bool hasGrids, + bool gridsAvailable, bool gridsKnown, + size_t stepCount) + : area_(area), accuracy_(accuracy), hasGrids_(hasGrids), + gridsAvailable_(gridsAvailable), gridsKnown_(gridsKnown), + stepCount_(stepCount) {} +}; + +// --------------------------------------------------------------------------- + +// 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 operator()(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.hasGrids_ && iterB->second.hasGrids_) { + // 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; + } + + // 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; + } + } else 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; + } + } + + // 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 + return a_name < b_name; + } +}; + +// --------------------------------------------------------------------------- + +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; +} + +// --------------------------------------------------------------------------- + +struct FilterResults { + + FilterResults(const std::vector &sourceListIn, + const CoordinateOperationContextNNPtr &contextIn, + const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, + bool forceStrictContainmentTest) + : sourceList(sourceListIn), context(contextIn), sourceCRS(sourceCRSIn), + targetCRS(targetCRSIn), sourceCRSExtent(getExtent(sourceCRS)), + targetCRSExtent(getExtent(targetCRS)), + areaOfInterest(context->getAreaOfInterest()), + desiredAccuracy(context->getDesiredAccuracy()), + sourceAndTargetCRSExtentUse( + context->getSourceAndTargetCRSExtentUse()) { + + computeAreaOfIntest(); + 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 crs::CRSNNPtr &sourceCRS; + const crs::CRSNNPtr &targetCRS; + const metadata::ExtentPtr &sourceCRSExtent; + const metadata::ExtentPtr &targetCRSExtent; + metadata::ExtentPtr areaOfInterest; + const double desiredAccuracy = context->getDesiredAccuracy(); + const CoordinateOperationContext::SourceTargetCRSExtentUse + sourceAndTargetCRSExtentUse; + + bool hasOpThatContainsAreaOfInterest = false; + std::vector res{}; + + // ---------------------------------------------------------------------- + void computeAreaOfIntest() { + + // Compute an area of interest from the CRS extent if the user did + // not specify one + if (!areaOfInterest) { + if (sourceAndTargetCRSExtentUse == + CoordinateOperationContext::SourceTargetCRSExtentUse:: + INTERSECTION) { + if (sourceCRSExtent && targetCRSExtent) { + areaOfInterest = sourceCRSExtent->intersection( + NN_NO_CHECK(targetCRSExtent)); + } + } else if (sourceAndTargetCRSExtentUse == + CoordinateOperationContext::SourceTargetCRSExtentUse:: + SMALLEST) { + if (sourceCRSExtent && targetCRSExtent) { + if (getPseudoArea(sourceCRSExtent) < + getPseudoArea(targetCRSExtent)) { + areaOfInterest = sourceCRSExtent; + } else { + areaOfInterest = targetCRSExtent; + } + } else if (sourceCRSExtent) { + areaOfInterest = sourceCRSExtent; + } else { + areaOfInterest = targetCRSExtent; + } + } + } + } + + // --------------------------------------------------------------------------- + + 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(); + for (const auto &op : sourceList) { + if (desiredAccuracy != 0) { + const double accuracy = getAccuracy(op); + if (accuracy < 0 || accuracy > desiredAccuracy) { + continue; + } + } + if (areaOfInterest) { + bool emptyIntersection = false; + auto extent = getExtent(op, true, emptyIntersection); + if (!extent) + continue; + bool extentContains = + extent->contains(NN_NO_CHECK(areaOfInterest)); + if (extentContains) { + hasOpThatContainsAreaOfInterest = 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; + bool extentContainsSource = + !sourceCRSExtent || + extent->contains(NN_NO_CHECK(sourceCRSExtent)); + bool extentContainsTarget = + !targetCRSExtent || + extent->contains(NN_NO_CHECK(targetCRSExtent)); + if (extentContainsSource && extentContainsTarget) { + hasOpThatContainsAreaOfInterest = true; + } + if (spatialCriterion == + CoordinateOperationContext::SpatialCriterion:: + STRICT_CONTAINMENT) { + if (!extentContainsSource || !extentContainsTarget) { + continue; + } + } else if (spatialCriterion == + CoordinateOperationContext::SpatialCriterion:: + PARTIAL_INTERSECTION) { + bool extentIntersectsSource = + !sourceCRSExtent || + extent->intersects(NN_NO_CHECK(sourceCRSExtent)); + bool extentIntersectsTarget = + targetCRSExtent && + extent->intersects(NN_NO_CHECK(targetCRSExtent)); + if (!extentIntersectsSource || !extentIntersectsTarget) { + 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 (sourceCRSExtent && targetCRSExtent) { + auto x = + extentOp->intersection(NN_NO_CHECK(sourceCRSExtent)); + auto y = + extentOp->intersection(NN_NO_CHECK(targetCRSExtent)); + area = getPseudoArea(x) + getPseudoArea(y) - + ((x && y) + ? getPseudoArea(x->intersection(NN_NO_CHECK(y))) + : 0.0); + } else if (sourceCRSExtent) { + area = getPseudoArea( + extentOp->intersection(NN_NO_CHECK(sourceCRSExtent))); + } else if (targetCRSExtent) { + area = getPseudoArea( + extentOp->intersection(NN_NO_CHECK(targetCRSExtent))); + } else { + area = getPseudoArea(extentOp); + } + } + + bool hasGrids = false; + bool gridsAvailable = true; + bool gridsKnown = true; + if (context->getAuthorityFactory() && + (gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + USE_FOR_SORTING || + gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + IGNORE_GRID_AVAILABILITY)) { + const auto gridsNeeded = op->gridsNeeded( + context->getAuthorityFactory()->databaseContext()); + for (const auto &gridDesc : gridsNeeded) { + hasGrids = true; + if (gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + USE_FOR_SORTING && + !gridDesc.available) { + gridsAvailable = false; + } + if (gridDesc.packageName.empty()) { + gridsKnown = false; + } + } + } + + const auto stepCount = getStepCount(op); + + map[op.get()] = PrecomputedOpCharacteristics( + area, getAccuracy(op), hasGrids, gridsAvailable, gridsKnown, + stepCount); + } + + // Sort ! + std::sort(res.begin(), res.end(), SortFunction(map)); + } + + // ---------------------------------------------------------------------- + + void removeSyntheticNullTransforms() { + + // If we have more than one result, and than the last result is the + // default "Null geographic offset" or "Null geocentric translation" + // operations we have synthetized, remove it as + // all previous results are necessarily better + if (hasOpThatContainsAreaOfInterest && res.size() > 1) { + const std::string &name = res.back()->nameStr(); + if (name.find(NULL_GEOGRAPHIC_OFFSET) != std::string::npos || + name.find(NULL_GEOCENTRIC_TRANSLATION) != std::string::npos) { + 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 greater accuracy. Actually we must + // be a bit more subtle than that, and take into account grid + // availability + std::vector resTemp; + metadata::ExtentPtr lastExtent; + double lastAccuracy = -1; + bool lastHasGrids = false; + bool lastGridsAvailable = true; + std::set> setOfSetOfGrids; + 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); + bool curHasGrids = false; + bool curGridsAvailable = true; + std::set curSetOfGrids; + + const auto curStepCount = getStepCount(op); + + if (context->getAuthorityFactory()) { + const auto gridsNeeded = op->gridsNeeded( + context->getAuthorityFactory()->databaseContext()); + for (const auto &gridDesc : gridsNeeded) { + curHasGrids = true; + curSetOfGrids.insert(gridDesc.shortName); + if (!gridDesc.available) { + curGridsAvailable = false; + } + } + } + + if (first) { + resTemp.emplace_back(op); + + lastHasGrids = curHasGrids; + lastGridsAvailable = curGridsAvailable; + 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) { + // If that set of grids has always been used for that + // extent, + // no need to add them again + if (setOfSetOfGrids.find(curSetOfGrids) != + setOfSetOfGrids.end()) { + continue; + } + // If we have already found a operation without grids for + // that extent, no need to add any lower accuracy operation + if (!lastHasGrids) { + continue; + } + // If we had only operations involving grids, but one + // past operation had available grids, no need to add + // the new one. + if (curHasGrids && curGridsAvailable && + lastGridsAvailable) { + continue; + } + } else if (curAccuracy == lastAccuracy && sameExtent) { + if (curStepCount > lastStepCount) { + continue; + } + } + + resTemp.emplace_back(op); + + if (sameExtent) { + if (!curHasGrids) { + lastHasGrids = false; + } + if (curGridsAvailable) { + lastGridsAvailable = true; + } + } else { + setOfSetOfGrids.clear(); + + lastHasGrids = curHasGrids; + lastGridsAvailable = curGridsAvailable; + } + } + + lastOp = op.as_nullable(); + lastStepCount = curStepCount; + lastExtent = curExtent; + lastAccuracy = curAccuracy; + if (!curSetOfGrids.empty()) { + setOfSetOfGrids.insert(curSetOfGrids); + } + } + 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 crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) { + return FilterResults(sourceList, context, sourceCRS, targetCRS, false) + .andSort() + .getRes(); +} +//! @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) { + op = op->inverse(); + } + return res; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +// Look in the authority registry for operations from sourceCRS to targetCRS +static std::vector +findOpsInRegistryDirect(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, + const CoordinateOperationContextNNPtr &context) { + const auto &authFactory = context->getAuthorityFactory(); + assert(authFactory); + const auto &authFactoryName = authFactory->getAuthority(); + + for (const auto &idSrc : sourceCRS->identifiers()) { + const auto &srcAuthName = *(idSrc->codeSpace()); + const auto &srcCode = idSrc->code(); + if (!srcAuthName.empty()) { + for (const auto &idTarget : targetCRS->identifiers()) { + const auto &targetAuthName = *(idTarget->codeSpace()); + const auto &targetCode = idTarget->code(); + if (!targetAuthName.empty()) { + 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); + } + for (const auto &authority : authorities) { + const auto tmpAuthFactory = + io::AuthorityFactory::create( + authFactory->databaseContext(), + authority == "any" ? std::string() : authority); + auto res = + tmpAuthFactory + ->createFromCoordinateReferenceSystemCodes( + srcAuthName, srcCode, targetAuthName, + targetCode, + context->getUsePROJAlternativeGridNames(), + context->getGridAvailabilityUse() == + CoordinateOperationContext:: + GridAvailabilityUse:: + DISCARD_OPERATION_IF_MISSING_GRID, + context->getDiscardSuperseded()); + if (!res.empty()) { + return res; + } + } + } + } + } + } + return std::vector(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// Look in the authority registry for operations from sourceCRS to targetCRS +// using an intermediate pivot +static std::vector findsOpsInRegistryWithIntermediate( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const CoordinateOperationContextNNPtr &context) { + if (!context->getAllowUseIntermediateCRS()) { + return std::vector(); + } + + const auto &authFactory = context->getAuthorityFactory(); + assert(authFactory); + const auto &authFactoryName = authFactory->getAuthority(); + + for (const auto &idSrc : sourceCRS->identifiers()) { + const auto &srcAuthName = *(idSrc->codeSpace()); + const auto &srcCode = idSrc->code(); + if (!srcAuthName.empty()) { + for (const auto &idTarget : targetCRS->identifiers()) { + const auto &targetAuthName = *(idTarget->codeSpace()); + const auto &targetCode = idTarget->code(); + if (!targetAuthName.empty()) { + 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); + } + for (const auto &authority : authorities) { + const auto tmpAuthFactory = + io::AuthorityFactory::create( + authFactory->databaseContext(), + authority == "any" ? std::string() : authority); + + auto res = + tmpAuthFactory->createFromCRSCodesWithIntermediates( + srcAuthName, srcCode, targetAuthName, + targetCode, + context->getUsePROJAlternativeGridNames(), + context->getGridAvailabilityUse() == + CoordinateOperationContext:: + GridAvailabilityUse:: + DISCARD_OPERATION_IF_MISSING_GRID, + context->getDiscardSuperseded(), + context->getIntermediateCRS()); + if (!res.empty()) { + return res; + } + } + } + } + } + } + return std::vector(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static TransformationNNPtr +createNullGeographicOffset(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS) { + std::string name(NULL_GEOGRAPHIC_OFFSET); + name += " from "; + name += sourceCRS->nameStr(); + name += " to "; + name += targetCRS->nameStr(); + + 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); + 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), {}); + } else { + return Transformation::createGeographic2DOffsets( + map, sourceCRS, targetCRS, angle0, angle0, {}); + } +} +//! @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->setOmitZUnitConversion(true); + horizTransform->_exportToPROJString(formatter); + + formatter->startInversion(); + geogDst->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + formatter->setOmitZUnitConversion(false); + + verticalTransform->_exportToPROJString(formatter); + + formatter->setOmitZUnitConversion(true); + geogDst->addAngularUnitConvertAndAxisSwap(formatter); + formatter->setOmitZUnitConversion(false); + } +}; + +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->setOmitZUnitConversion(true); + + opSrcCRSToGeogCRS->_exportToPROJString(formatter); + + formatter->startInversion(); + interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + formatter->setOmitZUnitConversion(false); + + verticalTransform->_exportToPROJString(formatter); + + formatter->setOmitZUnitConversion(true); + + interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter); + + opGeogCRStoDstCRS->_exportToPROJString(formatter); + + formatter->setOmitZUnitConversion(false); + } +}; + +MyPROJStringExportableHorizVerticalHorizPROJBased:: + ~MyPROJStringExportableHorizVerticalHorizPROJBased() = default; +} +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 { + +// --------------------------------------------------------------------------- + +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); +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationNNPtr createHorizVerticalPROJBased( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const operation::CoordinateOperationNNPtr &horizTransform, + const operation::CoordinateOperationNNPtr &verticalTransform) { + + auto geogDst = util::nn_dynamic_pointer_cast(targetCRS); + assert(geogDst); + + auto exportable = util::nn_make_shared( + horizTransform, verticalTransform, geogDst); + + bool dummy = false; + auto ops = std::vector{horizTransform, + verticalTransform}; + auto extent = getExtent(ops, true, dummy); + 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, + accuracies); +} + +// --------------------------------------------------------------------------- + +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) { + + auto exportable = + util::nn_make_shared( + opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS, + interpolationGeogCRS); + + bool dummy = false; + auto ops = std::vector{ + opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS}; + auto extent = getExtent(ops, true, dummy); + 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, + accuracies); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +ConversionNNPtr CoordinateOperationFactory::Private::createGeographicGeocentric( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) { + auto properties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildOpName("Conversion", sourceCRS, targetCRS)); + auto conv = Conversion::createGeographicGeocentric(properties); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + return conv; +} + +// --------------------------------------------------------------------------- + +std::vector +CoordinateOperationFactory::Private::createOperationsGeogToGeog( + std::vector &res, const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, const crs::GeographicCRS *geogSrc, + const crs::GeographicCRS *geogDst) { + + assert(sourceCRS.get() == geogSrc); + assert(targetCRS.get() == geogDst); + const bool allowEmptyIntersection = true; + + 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())); + + // Do they differ by vertical units ? + if (vconvSrc != vconvDst && + geogSrc->ellipsoid()->_isEquivalentTo( + geogDst->ellipsoid().get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (offset_pm.value() == 0) { + // 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); + res.push_back(conv); + return res; + } else { + res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS)); + return res; + } + } + + // Do the CRS differ only by their axis order ? + if (geogSrc->datum() != nullptr && geogDst->datum() != nullptr && + geogSrc->datum()->_isEquivalentTo( + geogDst->datum().get(), util::IComparable::Criterion::EQUIVALENT) && + !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 && + dstOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH) || + (srcOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH && + dstOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST)) { + auto conv = Conversion::createAxisOrderReversal(false); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + res.emplace_back(conv); + return res; + } + if ((srcOrder == + cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP && + dstOrder == + cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP) || + (srcOrder == + cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP && + dstOrder == + cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP)) { + auto conv = Conversion::createAxisOrderReversal(true); + 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(createNullGeographicOffset(sourceCRS, interm_crs)); + + 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( + createNullGeographicOffset(interm_crs, targetCRS)); + } else { + steps.emplace_back( + createNullGeographicOffset(sourceCRS, targetCRS)); + } + } + + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + steps, !allowEmptyIntersection)); + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +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; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static std::vector +findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory, + const datum::GeodeticReferenceFramePtr &datum) { + std::vector candidates; + for (const auto &id : datum->identifiers()) { + const auto &authName = *(id->codeSpace()); + const auto &code = id->code(); + if (!authName.empty()) { + auto l_candidates = authFactory->createGeodeticCRSFromDatum( + authName, code, std::string()); + for (const auto &candidate : l_candidates) { + candidates.emplace_back(candidate); + } + } + } + return candidates; +} + +// --------------------------------------------------------------------------- + +static bool isNullTransformation(const std::string &name) { + + return starts_with(name, NULL_GEOCENTRIC_TRANSLATION) || + starts_with(name, NULL_GEOGRAPHIC_OFFSET); +} + +// --------------------------------------------------------------------------- + +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) { + + const bool allowEmptyIntersection = true; + + 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 candidatesSrcGeod( + findCandidateGeodCRSForDatum(authFactory, geodSrc->datum())); + const auto candidatesDstGeod( + findCandidateGeodCRSForDatum(authFactory, geodDst->datum())); + + 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; + if (isNullFirst) { + opSecond->setCRSs( + sourceCRS, NN_CHECK_ASSERT(opSecond->targetCRS()), nullptr); + } else { + subOps.emplace_back(opFirst); + } + if (isNullTransformation(opsThird[0]->nameStr())) { + opSecond->setCRSs(NN_CHECK_ASSERT(opSecond->sourceCRS()), + targetCRS, nullptr); + subOps.emplace_back(opSecond); + } else { + subOps.emplace_back(opSecond); + subOps.emplace_back(opsThird[0]); + } + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + subOps, !allowEmptyIntersection)); + } + }; + + // Start in priority with candidates that have exactly the same name as + // the sourcCRS and targetCRS. Typically for the case of init=IGNF:XXXX + for (const auto &candidateSrcGeod : candidatesSrcGeod) { + if (candidateSrcGeod->nameStr() == sourceCRS->nameStr()) { + for (const auto &candidateDstGeod : candidatesDstGeod) { + if (candidateDstGeod->nameStr() == targetCRS->nameStr()) { + 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()) { + return; + } + break; + } + } + break; + } + } + + for (const auto &candidateSrcGeod : candidatesSrcGeod) { + const auto opsFirst = + createOperations(sourceCRS, candidateSrcGeod, context); + assert(!opsFirst.empty()); + const bool isNullFirst = isNullTransformation(opsFirst[0]->nameStr()); + + for (const auto &candidateDstGeod : candidatesDstGeod) { + createTransformations(candidateSrcGeod, candidateDstGeod, + opsFirst[0], isNullFirst); + } + if (!res.empty()) { + return; + } + } +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationNNPtr +createNullGeocentricTranslation(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS) { + std::string name(NULL_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, {})); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +bool CoordinateOperationFactory::Private::hasPerfectAccuracyResult( + const std::vector &res, const Context &context) { + auto resTmp = FilterResults(res, context.context, context.sourceCRS, + context.targetCRS, true) + .getRes(); + for (const auto &op : resTmp) { + const double acc = getAccuracy(op); + if (acc == 0.0) { + return true; + } + } + return false; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::vector +CoordinateOperationFactory::Private::createOperations( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context) { + + std::vector res; + const bool allowEmptyIntersection = true; + + const auto &sourceProj4Ext = sourceCRS->getExtensionProj4(); + const auto &targetProj4Ext = targetCRS->getExtensionProj4(); + if (!sourceProj4Ext.empty() || !targetProj4Ext.empty()) { + + auto sourceProjExportable = + dynamic_cast(sourceCRS.get()); + auto targetProjExportable = + dynamic_cast(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( + io::PROJStringFormatter::Convention::PROJ_4); + projFormatter->startInversion(); + sourceProjExportable->_exportToPROJString(projFormatter.get()); + + auto geogSrc = + dynamic_cast(sourceCRS.get()); + if (geogSrc) { + auto proj5Formatter = io::PROJStringFormatter::create( + io::PROJStringFormatter::Convention::PROJ_5); + geogSrc->addAngularUnitConvertAndAxisSwap(proj5Formatter.get()); + projFormatter->ingestPROJString(proj5Formatter->toString()); + } + + projFormatter->stopInversion(); + + targetProjExportable->_exportToPROJString(projFormatter.get()); + + auto geogDst = + dynamic_cast(targetCRS.get()); + if (geogDst) { + auto proj5Formatter = io::PROJStringFormatter::create( + io::PROJStringFormatter::Convention::PROJ_5); + geogDst->addAngularUnitConvertAndAxisSwap(proj5Formatter.get()); + projFormatter->ingestPROJString(proj5Formatter->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, {})); + return res; + } + + auto geodSrc = dynamic_cast(sourceCRS.get()); + auto geodDst = 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()); + if (context.context->getAuthorityFactory() && + (derivedSrc == nullptr || + !derivedSrc->baseCRS()->_isEquivalentTo( + targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) && + (derivedDst == nullptr || + !derivedDst->baseCRS()->_isEquivalentTo( + sourceCRS.get(), util::IComparable::Criterion::EQUIVALENT))) { + + bool doFilterAndCheckPerfectOp = true; + res = findOpsInRegistryDirect(sourceCRS, targetCRS, context.context); + if (!sourceCRS->_isEquivalentTo(targetCRS.get())) { + auto resFromInverse = applyInverse( + findOpsInRegistryDirect(targetCRS, sourceCRS, context.context)); + res.insert(res.end(), resFromInverse.begin(), resFromInverse.end()); + + // If we get at least a result with perfect accuracy, do not + // bother generating synthetic transforms. + if (hasPerfectAccuracyResult(res, context)) { + return res; + } + + doFilterAndCheckPerfectOp = false; + + // NAD27 to NAD83 has tens of results already. No need to look + // for a pivot + if (res.size() < 5 || getenv("PROJ_FORCE_SEARCH_PIVOT")) { + auto resWithIntermediate = findsOpsInRegistryWithIntermediate( + sourceCRS, targetCRS, context.context); + res.insert(res.end(), resWithIntermediate.begin(), + resWithIntermediate.end()); + doFilterAndCheckPerfectOp = true; + } + } + + if (res.empty() && + !context.inCreateOperationsWithDatumPivotAntiRecursion && geodSrc && + geodDst) { + // If we still didn't find a transformation, and that the source + // and target are GeodeticCRS, then go through their underlying + // datum to find potential transformations between other GeodeticRSs + // that are made of those datum + // The typical example is if transforming between two GeographicCRS, + // but transformations are only available between their + // corresponding geocentric CRS. + const auto &srcDatum = geodSrc->datum(); + const bool srcHasDatumWithId = + srcDatum && !srcDatum->identifiers().empty(); + const auto &dstDatum = geodDst->datum(); + const bool dstHasDatumWithId = + dstDatum && !dstDatum->identifiers().empty(); + if (srcHasDatumWithId && dstHasDatumWithId && + !srcDatum->_isEquivalentTo( + dstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) { + createOperationsWithDatumPivot(res, sourceCRS, targetCRS, + geodSrc, geodDst, context); + 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 res; + } + } + } + + // Special case if both CRS are geodetic + if (geodSrc && geodDst && !derivedSrc && !derivedDst) { + + 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(sourceCRS.get()); + auto geogDst = + dynamic_cast(targetCRS.get()); + if (geogSrc && geogDst) { + return createOperationsGeogToGeog(res, sourceCRS, targetCRS, + geogSrc, geogDst); + } + + const bool isSrcGeocentric = geodSrc->isGeocentric(); + const bool isSrcGeographic = geogSrc != nullptr; + const bool isTargetGeocentric = geodDst->isGeocentric(); + const bool isTargetGeographic = geogDst != nullptr; + if (((isSrcGeocentric && isTargetGeographic) || + (isSrcGeographic && isTargetGeocentric)) && + geodSrc->datum() != nullptr && geodDst->datum() != nullptr) { + + // Same datum ? + if (geodSrc->datum()->_isEquivalentTo( + geodDst->datum().get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back( + createGeographicGeocentric(sourceCRS, targetCRS)); + } else if (isSrcGeocentric) { + 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), + NN_NO_CHECK(geogDst->datum()), + NN_CHECK_ASSERT( + util::nn_dynamic_pointer_cast( + geodSrc->coordinateSystem())))); + auto opFirst = + createNullGeocentricTranslation(sourceCRS, interm_crs); + auto opSecond = + createGeographicGeocentric(interm_crs, targetCRS); + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + {opFirst, opSecond}, !allowEmptyIntersection)); + } else { + return applyInverse( + createOperations(targetCRS, sourceCRS, context)); + } + + return res; + } + + if (isSrcGeocentric && isTargetGeocentric) { + res.emplace_back( + createNullGeocentricTranslation(sourceCRS, targetCRS)); + return res; + } + + // Tranformation between two geodetic systems of unknown type + // This should normally not be triggered with "standard" CRS + res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS)); + 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) { + 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 res; + } + auto opsSecond = + createOperations(derivedSrc->baseCRS(), targetCRS, context); + for (const auto &opSecond : opsSecond) { + try { + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + {opFirst, opSecond}, !allowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + return res; + } + + // reverse of previous case + if (derivedDst) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + // boundCRS to a geogCRS that is the same as the hubCRS + auto boundSrc = dynamic_cast(sourceCRS.get()); + auto geogDst = dynamic_cast(targetCRS.get()); + if (boundSrc && geogDst) { + const auto &hubSrc = boundSrc->hubCRS(); + auto hubSrcGeog = + dynamic_cast(hubSrc.get()); + auto geogCRSOfBaseOfBoundSrc = + boundSrc->baseCRS()->extractGeographicCRS(); + if (hubSrcGeog && geogCRSOfBaseOfBoundSrc && + (hubSrcGeog->_isEquivalentTo( + geogDst, util::IComparable::Criterion::EQUIVALENT) || + hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst)))) { + if (boundSrc->baseCRS() == geogCRSOfBaseOfBoundSrc) { + // Optimization to avoid creating a useless concatenated + // operation + res.emplace_back(boundSrc->transformation()); + return res; + } + auto opsFirst = + createOperations(boundSrc->baseCRS(), + NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context); + if (!opsFirst.empty()) { + for (const auto &opFirst : opsFirst) { + try { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + {opFirst, boundSrc->transformation()}, + !allowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + if (!res.empty()) { + return res; + } + } + // If the datum are equivalent, this is also fine + } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog->datum() && + geogDst->datum() && + hubSrcGeog->datum()->_isEquivalentTo( + geogDst->datum().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()) { + for (const auto &opFirst : opsFirst) { + for (const auto &opLast : opsLast) { + try { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + {opFirst, boundSrc->transformation(), + opLast}, + !allowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + } + if (!res.empty()) { + return res; + } + } + // 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->datum() && + geogDst->datum() && + geogCRSOfBaseOfBoundSrc->ellipsoid()->_isEquivalentTo( + datum::Ellipsoid::CLARKE_1866.get(), + util::IComparable::Criterion::EQUIVALENT) && + hubSrcGeog->datum()->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6326.get(), + util::IComparable::Criterion::EQUIVALENT) && + geogDst->datum()->_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 res; + } 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}, + !allowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + if (!res.empty()) { + return res; + } + } + } + } + + if (hubSrcGeog && + hubSrcGeog->_isEquivalentTo( + geogDst, util::IComparable::Criterion::EQUIVALENT) && + dynamic_cast(boundSrc->baseCRS().get())) { + res.emplace_back(boundSrc->transformation()); + return res; + } + + return createOperations(boundSrc->baseCRS(), targetCRS, context); + } + + // reverse of previous case + auto boundDst = dynamic_cast(targetCRS.get()); + auto geogSrc = dynamic_cast(sourceCRS.get()); + if (geogSrc && boundDst) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + // vertCRS (as boundCRS with transformation to target vertCRS) to + // vertCRS + auto vertDst = dynamic_cast(targetCRS.get()); + if (boundSrc && vertDst) { + 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; + } + + return createOperations(boundSrc->baseCRS(), targetCRS, context); + } + + // reverse of previous case + auto vertSrc = dynamic_cast(sourceCRS.get()); + if (boundDst && vertSrc) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + if (vertSrc && vertDst) { + const auto &srcDatum = vertSrc->datum(); + const auto &dstDatum = vertDst->datum(); + if (srcDatum && dstDatum && + srcDatum->_isEquivalentTo( + dstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) { + const double convSrc = vertSrc->coordinateSystem() + ->axisList()[0] + ->unit() + .conversionToSI(); + const double convDst = vertDst->coordinateSystem() + ->axisList()[0] + ->unit() + .conversionToSI(); + if (convSrc != convDst) { + const double factor = convSrc / convDst; + auto conv = Conversion::createChangeVerticalUnit( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildTransfName(sourceCRS->nameStr(), + targetCRS->nameStr())), + common::Scale(factor)); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + res.push_back(conv); + 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) { + const double convSrc = + vertSrc->coordinateSystem()->axisList()[0]->unit().conversionToSI(); + double convDst = 1.0; + const auto &geogAxis = geogDst->coordinateSystem()->axisList(); + if (geogAxis.size() == 3) { + convDst = geogAxis[2]->unit().conversionToSI(); + } + if (convSrc != convDst) { + const double factor = convSrc / convDst; + auto conv = Conversion::createChangeVerticalUnit( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + buildTransfName(sourceCRS->nameStr(), + targetCRS->nameStr())), + common::Scale(factor)); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + res.push_back(conv); + return res; + } + } + + // reverse of previous case + if (vertDst && geogSrc) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + // boundCRS to boundCRS using the same geographic hubCRS + if (boundSrc && boundDst) { + 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, !allowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + } + if (!res.empty()) { + return res; + } + } + } + + return createOperations(boundSrc->baseCRS(), boundDst->baseCRS(), + context); + } + + auto compoundSrc = dynamic_cast(sourceCRS.get()); + if (compoundSrc && geogDst) { + const auto &componentsSrc = compoundSrc->componentReferenceSystems(); + if (!componentsSrc.empty()) { + std::vector horizTransforms; + if (componentsSrc[0]->extractGeographicCRS()) { + horizTransforms = + createOperations(componentsSrc[0], targetCRS, context); + } + std::vector verticalTransforms; + if (componentsSrc.size() >= 2 && + componentsSrc[1]->extractVerticalCRS()) { + verticalTransforms = + createOperations(componentsSrc[1], targetCRS, context); + } + if (!horizTransforms.empty() && !verticalTransforms.empty()) { + for (const auto &horizTransform : horizTransforms) { + for (const auto &verticalTransform : verticalTransforms) { + + auto op = createHorizVerticalPROJBased( + sourceCRS, targetCRS, horizTransform, + verticalTransform); + + res.emplace_back(op); + } + } + return res; + } else { + return horizTransforms; + } + } + } + + // reverse of previous case + auto compoundDst = dynamic_cast(targetCRS.get()); + if (geogSrc && compoundDst) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + if (compoundSrc && compoundDst) { + const auto &componentsSrc = compoundSrc->componentReferenceSystems(); + const auto &componentsDst = compoundDst->componentReferenceSystems(); + if (!componentsSrc.empty() && + componentsSrc.size() == componentsDst.size()) { + if (componentsSrc[0]->extractGeographicCRS() && + componentsDst[0]->extractGeographicCRS()) { + + std::vector verticalTransforms; + if (componentsSrc.size() >= 2 && + componentsSrc[1]->extractVerticalCRS() && + componentsDst[1]->extractVerticalCRS()) { + verticalTransforms = createOperations( + componentsSrc[1], componentsDst[1], context); + } + + for (const auto &verticalTransform : verticalTransforms) { + auto interpolationGeogCRS = + NN_NO_CHECK(componentsSrc[0]->extractGeographicCRS()); + auto transformationVerticalTransform = + dynamic_cast( + verticalTransform.get()); + if (transformationVerticalTransform) { + auto interpTransformCRS = + transformationVerticalTransform->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< + crs::GeographicCRS>( + nn_interpTransformCRS)); + } + } + } + auto opSrcCRSToGeogCRS = createOperations( + componentsSrc[0], interpolationGeogCRS, context); + auto opGeogCRStoDstCRS = createOperations( + interpolationGeogCRS, componentsDst[0], context); + for (const auto &opSrc : opSrcCRSToGeogCRS) { + for (const auto &opDst : opGeogCRStoDstCRS) { + + auto op = createHorizVerticalHorizPROJBased( + sourceCRS, targetCRS, opSrc, verticalTransform, + opDst, interpolationGeogCRS); + res.emplace_back(op); + } + } + } + + if (verticalTransforms.empty()) { + return createOperations(componentsSrc[0], componentsDst[0], + context); + } + } + } + } + + return res; +} +//! @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. + * + * @param sourceCRS source CRS. + * @param targetCRS source CRS. + * @param context Search context. + * @return a list + */ +std::vector +CoordinateOperationFactory::createOperations( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const CoordinateOperationContextNNPtr &context) const { + + // 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; + + Private::Context contextPrivate(sourceCRS, targetCRS, context); + return filterAndSort( + Private::createOperations(l_sourceCRS, l_targetCRS, contextPrivate), + context, l_sourceCRS, l_targetCRS); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CoordinateOperationFactory. + */ +CoordinateOperationFactoryNNPtr CoordinateOperationFactory::create() { + return NN_NO_CHECK( + CoordinateOperationFactory::make_unique()); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +InverseCoordinateOperation::~InverseCoordinateOperation() = default; + +// --------------------------------------------------------------------------- + +InverseCoordinateOperation::InverseCoordinateOperation( + const CoordinateOperationNNPtr &forwardOperation, bool wktSupportsInversion) + : forwardOperation_(forwardOperation), + wktSupportsInversion_(wktSupportsInversion) {} + +// --------------------------------------------------------------------------- + +void InverseCoordinateOperation::setPropertiesFromForward() { + setProperties( + createPropertiesForInverse(forwardOperation_.get(), false, false)); + setAccuracies(forwardOperation_->coordinateOperationAccuracies()); + if (forwardOperation_->sourceCRS() && forwardOperation_->targetCRS()) { + setCRSs(forwardOperation_.get(), true); + } +} + +// --------------------------------------------------------------------------- + +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 { + auto otherICO = dynamic_cast(other); + if (otherICO == nullptr || + !ObjectUsage::_isEquivalentTo(other, criterion)) { + return false; + } + return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion); +} + +// --------------------------------------------------------------------------- + +PROJBasedOperation::~PROJBasedOperation() = default; + +// --------------------------------------------------------------------------- + +PROJBasedOperation::PROJBasedOperation( + const OperationMethodNNPtr &methodIn, + const std::vector &values) + : SingleOperation(methodIn) { + setParameterValues(values); +} + +// --------------------------------------------------------------------------- + +static const std::string PROJSTRING_PARAMETER_NAME("PROJ string"); + +PROJBasedOperationNNPtr PROJBasedOperation::create( + const util::PropertyMap &properties, const std::string &PROJString, + const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, + const std::vector &accuracies) { + auto parameter = OperationParameter::create(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, PROJSTRING_PARAMETER_NAME)); + auto method = OperationMethod::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + "PROJ-based operation method"), + std::vector{parameter}); + std::vector values; + values.push_back(OperationParameterValue::create( + parameter, ParameterValue::create(PROJString))); + auto op = + PROJBasedOperation::nn_make_shared(method, values); + op->assignSelf(op); + 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; +} + +// --------------------------------------------------------------------------- + +static const std::string + APPROX_PROJSTRING_PARAMETER_NAME("(Approximte) PROJ string"); + +PROJBasedOperationNNPtr PROJBasedOperation::create( + const util::PropertyMap &properties, + const io::IPROJStringExportableNNPtr &projExportable, bool inverse, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const std::vector &accuracies) { + auto parameter = OperationParameter::create(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, APPROX_PROJSTRING_PARAMETER_NAME)); + auto method = OperationMethod::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + "PROJ-based operation method"), + std::vector{parameter}); + std::vector values; + + auto formatter = io::PROJStringFormatter::create(); + if (inverse) { + formatter->startInversion(); + } + projExportable->_exportToPROJString(formatter.get()); + if (inverse) { + formatter->stopInversion(); + } + auto projString = formatter->toString(); + + values.push_back(OperationParameterValue::create( + parameter, ParameterValue::create(projString))); + auto op = + PROJBasedOperation::nn_make_shared(method, values); + op->assignSelf(op); + op->setCRSs(sourceCRS, targetCRS, nullptr); + op->setProperties( + addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation")); + op->setAccuracies(accuracies); + op->projStringExportable_ = projExportable.as_nullable(); + op->inverse_ = inverse; + return op; +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr PROJBasedOperation::inverse() const { + + if (projStringExportable_) { + 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()), + coordinateOperationAccuracies())); + } + + auto formatter = io::PROJStringFormatter::create(); + formatter->startInversion(); + try { + formatter->ingestPROJString( + parameterValue(PROJSTRING_PARAMETER_NAME)->stringValue()); + } catch (const io::ParsingException &e) { + throw util::UnsupportedOperationException( + std::string("PROJBasedOperation::inverse() failed: ") + e.what()); + } + formatter->stopInversion(); + + return util::nn_static_pointer_cast( + PROJBasedOperation::create( + createPropertiesForInverse(this, false, false), + formatter->toString(), targetCRS(), sourceCRS(), + coordinateOperationAccuracies())); +} + +// --------------------------------------------------------------------------- + +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::_exportToPROJString( + io::PROJStringFormatter *formatter) const { + if (projStringExportable_) { + if (inverse_) { + formatter->startInversion(); + } + projStringExportable_->_exportToPROJString(formatter); + if (inverse_) { + formatter->stopInversion(); + } + return; + } + + try { + formatter->ingestPROJString( + parameterValue(PROJSTRING_PARAMETER_NAME)->stringValue()); + } catch (const io::ParsingException &e) { + throw io::FormattingException( + std::string("PROJBasedOperation::exportToPROJString() failed: ") + + e.what()); + } +} + +// --------------------------------------------------------------------------- + +std::set PROJBasedOperation::gridsNeeded( + const io::DatabaseContextPtr &databaseContext) 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, desc.fullName, desc.packageName, desc.url, + desc.directDownload, desc.openLicense, desc.available); + } + res.insert(desc); + } + } catch (const io::ParsingException &) { + } + + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END diff --git a/src/iso19111/coordinatesystem.cpp b/src/iso19111/coordinatesystem.cpp new file mode 100644 index 00000000..a3ad04e0 --- /dev/null +++ b/src/iso19111/coordinatesystem.cpp @@ -0,0 +1,1279 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 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/coordinatesystem.hpp" +#include "proj/common.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/coordinatesystem_internal.hpp" +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include +#include +#include +#include +#include + +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; +}} +#endif + +NS_PROJ_START +namespace cs { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Meridian::Private { + common::Angle longitude_{}; + + explicit Private(const common::Angle &longitude) : longitude_(longitude) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +Meridian::Meridian(const common::Angle &longitudeIn) + : d(internal::make_unique(longitudeIn)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +Meridian::Meridian(const Meridian &other) + : IdentifiedObject(other), d(internal::make_unique(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Meridian::~Meridian() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the longitude of the meridian that the axis follows from the + * pole. + * + * @return the longitude. + */ +const common::Angle &Meridian::longitude() PROJ_CONST_DEFN { + return d->longitude_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Meridian. + * + * @param longitudeIn longitude of the meridian that the axis follows from the + * pole. + * @return new Meridian. + */ +MeridianNNPtr Meridian::create(const common::Angle &longitudeIn) { + return Meridian::nn_make_shared(longitudeIn); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Meridian::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + formatter->startNode(io::WKTConstants::MERIDIAN, !identifiers().empty()); + formatter->add(longitude().value()); + longitude().unit()._exportToWKT(formatter, io::WKTConstants::ANGLEUNIT); + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CoordinateSystemAxis::Private { + std::string abbreviation{}; + const AxisDirection *direction = &(AxisDirection::UNSPECIFIED); + common::UnitOfMeasure unit{}; + util::optional minimumValue{}; + util::optional maximumValue{}; + MeridianPtr meridian{}; + // TODO rangeMeaning +}; +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateSystemAxis::CoordinateSystemAxis() + : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +CoordinateSystemAxis::CoordinateSystemAxis(const CoordinateSystemAxis &other) + : IdentifiedObject(other), d(internal::make_unique(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateSystemAxis::~CoordinateSystemAxis() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the axis abbreviation. + * + * The abbreviation used for this coordinate system axis; this abbreviation + * is also used to identify the coordinates in the coordinate tuple. + * Examples are X and Y. + * + * @return the abbreviation. + */ +const std::string &CoordinateSystemAxis::abbreviation() PROJ_CONST_DEFN { + return d->abbreviation; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the axis direction. + * + * The direction of this coordinate system axis (or in the case of Cartesian + * projected coordinates, the direction of this coordinate system axis locally) + * Examples: north or south, east or west, up or down. Within any set of + * coordinate system axes, only one of each pair of terms can be used. For + * Earth-fixed CRSs, this direction is often approximate and intended to + * provide a human interpretable meaning to the axis. When a geodetic reference + * frame is used, the precise directions of the axes may therefore vary + * slightly from this approximate direction. Note that an EngineeringCRS often + * requires specific descriptions of the directions of its coordinate system + * axes. + * + * @return the direction. + */ +const AxisDirection &CoordinateSystemAxis::direction() PROJ_CONST_DEFN { + return *(d->direction); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the axis unit. + * + * This is the spatial unit or temporal quantity used for this coordinate + * system axis. The value of a coordinate in a coordinate tuple shall be + * recorded using this unit. + * + * @return the axis unit. + */ +const common::UnitOfMeasure &CoordinateSystemAxis::unit() PROJ_CONST_DEFN { + return d->unit; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the minimum value normally allowed for this axis, in the unit + * for the axis. + * + * @return the minimum value, or empty. + */ +const util::optional & +CoordinateSystemAxis::minimumValue() PROJ_CONST_DEFN { + return d->minimumValue; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the maximum value normally allowed for this axis, in the unit + * for the axis. + * + * @return the maximum value, or empty. + */ +const util::optional & +CoordinateSystemAxis::maximumValue() PROJ_CONST_DEFN { + return d->maximumValue; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the meridian that the axis follows from the pole, for a + * coordinate + * reference system centered on a pole. + * + * @return the meridian, or null. + */ +const MeridianPtr &CoordinateSystemAxis::meridian() PROJ_CONST_DEFN { + return d->meridian; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CoordinateSystemAxis. + * + * @param properties See \ref general_properties. The name should generally be + * defined. + * @param abbreviationIn Axis abbreviation (might be empty) + * @param directionIn Axis direction + * @param unitIn Axis unit + * @param meridianIn The meridian that the axis follows from the pole, for a + * coordinate + * reference system centered on a pole, or nullptr + * @return a new CoordinateSystemAxis. + */ +CoordinateSystemAxisNNPtr CoordinateSystemAxis::create( + const util::PropertyMap &properties, const std::string &abbreviationIn, + const AxisDirection &directionIn, const common::UnitOfMeasure &unitIn, + const MeridianPtr &meridianIn) { + auto csa(CoordinateSystemAxis::nn_make_shared()); + csa->setProperties(properties); + csa->d->abbreviation = abbreviationIn; + csa->d->direction = &directionIn; + csa->d->unit = unitIn; + csa->d->meridian = meridianIn; + return csa; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void CoordinateSystemAxis::_exportToWKT( + // cppcheck-suppress passedByValue + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + _exportToWKT(formatter, 0, false); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::string CoordinateSystemAxis::normalizeAxisName(const std::string &str) { + if (str.empty()) { + return str; + } + // on import, transform from WKT2 "longitude" to "Longitude", as in the + // EPSG database. + return toupper(str.substr(0, 1)) + str.substr(1); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void CoordinateSystemAxis::_exportToWKT(io::WKTFormatter *formatter, int order, + bool disableAbbrev) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(io::WKTConstants::AXIS, !identifiers().empty()); + std::string axisName = *(name()->description()); + std::string abbrev = abbreviation(); + std::string parenthesedAbbrev = "(" + abbrev + ")"; + std::string dir = direction().toString(); + std::string axisDesignation; + + // It seems that the convention in WKT2 for axis name is first letter in + // lower case. Whereas in WKT1 GDAL, it is in upper case (as in the EPSG + // database) + if (!axisName.empty()) { + if (isWKT2) { + axisDesignation = + tolower(axisName.substr(0, 1)) + axisName.substr(1); + } else { + if (axisName == "Geodetic latitude") { + axisDesignation = "Latitude"; + } else if (axisName == "Geodetic longitude") { + axisDesignation = "Longitude"; + } else { + axisDesignation = axisName; + } + } + } + + if (!disableAbbrev && isWKT2 && + // For geodetic CS, export the axis name without abbreviation + !(axisName == AxisName::Latitude || axisName == AxisName::Longitude)) { + if (!axisDesignation.empty() && !abbrev.empty()) { + axisDesignation += " "; + } + if (!abbrev.empty()) { + axisDesignation += parenthesedAbbrev; + } + } + if (!isWKT2) { + dir = toupper(dir); + + if (direction() == AxisDirection::GEOCENTRIC_Z) { + dir = AxisDirectionWKT1::NORTH; + } else if (AxisDirectionWKT1::valueOf(dir) == nullptr) { + dir = AxisDirectionWKT1::OTHER; + } + } else if (!abbrev.empty()) { + // For geocentric CS, just put the abbreviation + if (direction() == AxisDirection::GEOCENTRIC_X || + direction() == AxisDirection::GEOCENTRIC_Y || + direction() == AxisDirection::GEOCENTRIC_Z) { + axisDesignation = parenthesedAbbrev; + } + // For cartesian CS with Easting/Northing, export only the abbreviation + else if ((order == 1 && axisName == AxisName::Easting && + abbrev == AxisAbbreviation::E) || + (order == 2 && axisName == AxisName::Northing && + abbrev == AxisAbbreviation::N)) { + axisDesignation = parenthesedAbbrev; + } + } + formatter->addQuotedString(axisDesignation); + formatter->add(dir); + const auto &l_meridian = meridian(); + if (isWKT2 && l_meridian) { + l_meridian->_exportToWKT(formatter); + } + if (formatter->outputAxisOrder() && order > 0) { + formatter->startNode(io::WKTConstants::ORDER, false); + formatter->add(order); + formatter->endNode(); + } + if (formatter->outputUnit() && + unit().type() != common::UnitOfMeasure::Type::NONE) { + unit()._exportToWKT(formatter); + } + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool CoordinateSystemAxis::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherCSA = dynamic_cast(other); + if (otherCSA == nullptr) { + return false; + } + // For approximate comparison, only care about axis direction and unit. + if (!(*(d->direction) == *(otherCSA->d->direction) && + d->unit._isEquivalentTo(otherCSA->d->unit, criterion))) { + return false; + } + if (criterion == util::IComparable::Criterion::STRICT) { + if (!IdentifiedObject::_isEquivalentTo(other, criterion)) { + return false; + } + if (abbreviation() != otherCSA->abbreviation()) { + return false; + } + // TODO other metadata + } + + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateSystemAxisNNPtr +CoordinateSystemAxis::alterUnit(const common::UnitOfMeasure &newUnit) const { + return create(util::PropertyMap().set(IdentifiedObject::NAME_KEY, name()), + abbreviation(), direction(), newUnit, meridian()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CoordinateSystem::Private { + std::vector axisList{}; + + explicit Private(const std::vector &axisListIn) + : axisList(axisListIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateSystem::CoordinateSystem( + const std::vector &axisIn) + : d(internal::make_unique(axisIn)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +CoordinateSystem::CoordinateSystem(const CoordinateSystem &other) + : IdentifiedObject(other), d(internal::make_unique(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateSystem::~CoordinateSystem() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the list of axes of this coordinate system. + * + * @return the axes. + */ +const std::vector & +CoordinateSystem::axisList() PROJ_CONST_DEFN { + return d->axisList; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void CoordinateSystem::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + if (formatter->outputAxis() != io::WKTFormatter::OutputAxisRule::YES) { + return; + } + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + + const auto &l_axisList = axisList(); + if (isWKT2) { + formatter->startNode(io::WKTConstants::CS, !identifiers().empty()); + formatter->add(getWKT2Type(formatter->use2018Keywords())); + formatter->add(static_cast(l_axisList.size())); + formatter->endNode(); + formatter->startNode(std::string(), + false); // anonymous indentation level + } + + common::UnitOfMeasure unit = common::UnitOfMeasure::NONE; + bool bAllSameUnit = true; + bool bFirstUnit = true; + for (const auto &axis : l_axisList) { + const auto &l_unit = axis->unit(); + if (bFirstUnit) { + unit = l_unit; + bFirstUnit = false; + } else if (unit != l_unit) { + bAllSameUnit = false; + } + } + + formatter->pushOutputUnit( + isWKT2 && (!bAllSameUnit || !formatter->outputCSUnitOnlyOnceIfSame())); + + int order = 1; + const bool disableAbbrev = + (l_axisList.size() == 3 && + l_axisList[0]->nameStr() == AxisName::Latitude && + l_axisList[1]->nameStr() == AxisName::Longitude && + l_axisList[2]->nameStr() == AxisName::Ellipsoidal_height); + + for (auto &axis : l_axisList) { + int axisOrder = (isWKT2 && l_axisList.size() > 1) ? order : 0; + axis->_exportToWKT(formatter, axisOrder, disableAbbrev); + order++; + } + if (isWKT2 && !l_axisList.empty() && bAllSameUnit && + formatter->outputCSUnitOnlyOnceIfSame()) { + unit._exportToWKT(formatter); + } + + formatter->popOutputUnit(); + + if (isWKT2) { + formatter->endNode(); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool CoordinateSystem::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherCS = dynamic_cast(other); + if (otherCS == nullptr || + !IdentifiedObject::_isEquivalentTo(other, criterion)) { + return false; + } + const auto &list = axisList(); + const auto &otherList = otherCS->axisList(); + if (list.size() != otherList.size()) { + return false; + } + if (getWKT2Type(true) != otherCS->getWKT2Type(true)) { + return false; + } + for (size_t i = 0; i < list.size(); i++) { + if (!list[i]->_isEquivalentTo(otherList[i].get(), criterion)) { + return false; + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +SphericalCS::~SphericalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +SphericalCS::SphericalCS(const std::vector &axisIn) + : CoordinateSystem(axisIn) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +SphericalCS::SphericalCS(const SphericalCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a SphericalCS. + * + * @param properties See \ref general_properties. + * @param axis1 The first axis. + * @param axis2 The second axis. + * @param axis3 The third axis. + * @return a new SphericalCS. + */ +SphericalCSNNPtr SphericalCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2, + const CoordinateSystemAxisNNPtr &axis3) { + std::vector axis{axis1, axis2, axis3}; + auto cs(SphericalCS::nn_make_shared(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +EllipsoidalCS::~EllipsoidalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +EllipsoidalCS::EllipsoidalCS( + const std::vector &axisIn) + : CoordinateSystem(axisIn) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +EllipsoidalCS::EllipsoidalCS(const EllipsoidalCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EllipsoidalCS. + * + * @param properties See \ref general_properties. + * @param axis1 The first axis. + * @param axis2 The second axis. + * @return a new EllipsoidalCS. + */ +EllipsoidalCSNNPtr +EllipsoidalCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2) { + std::vector axis{axis1, axis2}; + auto cs(EllipsoidalCS::nn_make_shared(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EllipsoidalCS. + * + * @param properties See \ref general_properties. + * @param axis1 The first axis. + * @param axis2 The second axis. + * @param axis3 The third axis. + * @return a new EllipsoidalCS. + */ +EllipsoidalCSNNPtr +EllipsoidalCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2, + const CoordinateSystemAxisNNPtr &axis3) { + std::vector axis{axis1, axis2, axis3}; + auto cs(EllipsoidalCS::nn_make_shared(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateSystemAxisNNPtr +CoordinateSystemAxis::createLAT_NORTH(const common::UnitOfMeasure &unit) { + return create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Latitude), + AxisAbbreviation::lat, AxisDirection::NORTH, unit); +} + +CoordinateSystemAxisNNPtr +CoordinateSystemAxis::createLONG_EAST(const common::UnitOfMeasure &unit) { + return create(util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Longitude), + AxisAbbreviation::lon, AxisDirection::EAST, unit); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EllipsoidalCS with a Latitude (first) and Longitude + * (second) axis. + * + * @param unit Angular unit of the axes. + * @return a new EllipsoidalCS. + */ +EllipsoidalCSNNPtr +EllipsoidalCS::createLatitudeLongitude(const common::UnitOfMeasure &unit) { + return EllipsoidalCS::create(util::PropertyMap(), + CoordinateSystemAxis::createLAT_NORTH(unit), + CoordinateSystemAxis::createLONG_EAST(unit)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EllipsoidalCS with a Latitude (first), Longitude + * (second) axis and ellipsoidal height (third) axis. + * + * @param angularUnit Angular unit of the latitude and longitude axes. + * @param linearUnit Linear unit of the ellipsoidal height axis. + * @return a new EllipsoidalCS. + */ +EllipsoidalCSNNPtr EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( + const common::UnitOfMeasure &angularUnit, + const common::UnitOfMeasure &linearUnit) { + return EllipsoidalCS::create( + util::PropertyMap(), CoordinateSystemAxis::createLAT_NORTH(angularUnit), + CoordinateSystemAxis::createLONG_EAST(angularUnit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Ellipsoidal_height), + AxisAbbreviation::h, AxisDirection::UP, linearUnit)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EllipsoidalCS with a Longitude (first) and Latitude + * (second) axis. + * + * @param unit Angular unit of the axes. + * @return a new EllipsoidalCS. + */ +EllipsoidalCSNNPtr +EllipsoidalCS::createLongitudeLatitude(const common::UnitOfMeasure &unit) { + return EllipsoidalCS::create(util::PropertyMap(), + CoordinateSystemAxis::createLONG_EAST(unit), + CoordinateSystemAxis::createLAT_NORTH(unit)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +/** \brief Return the axis order in an enumerated way. */ +EllipsoidalCS::AxisOrder EllipsoidalCS::axisOrder() const { + const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; + const auto &dir0 = l_axisList[0]->direction(); + const auto &dir1 = l_axisList[1]->direction(); + if (&dir0 == &AxisDirection::NORTH && &dir1 == &AxisDirection::EAST) { + if (l_axisList.size() == 2) { + return AxisOrder::LAT_NORTH_LONG_EAST; + } else if (&l_axisList[2]->direction() == &AxisDirection::UP) { + return AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP; + } + } else if (&dir0 == &AxisDirection::EAST && + &dir1 == &AxisDirection::NORTH) { + if (l_axisList.size() == 2) { + return AxisOrder::LONG_EAST_LAT_NORTH; + } else if (&l_axisList[2]->direction() == &AxisDirection::UP) { + return AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP; + } + } + + return AxisOrder::OTHER; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +EllipsoidalCSNNPtr EllipsoidalCS::alterAngularUnit( + const common::UnitOfMeasure &angularUnit) const { + const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; + if (l_axisList.size() == 2) { + return EllipsoidalCS::create(util::PropertyMap(), + l_axisList[0]->alterUnit(angularUnit), + l_axisList[1]->alterUnit(angularUnit)); + } else { + assert(l_axisList.size() == 3); + return EllipsoidalCS::create( + util::PropertyMap(), l_axisList[0]->alterUnit(angularUnit), + l_axisList[1]->alterUnit(angularUnit), l_axisList[2]); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +EllipsoidalCSNNPtr +EllipsoidalCS::alterLinearUnit(const common::UnitOfMeasure &linearUnit) const { + const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; + if (l_axisList.size() == 2) { + return EllipsoidalCS::create(util::PropertyMap(), l_axisList[0], + l_axisList[1]); + } else { + assert(l_axisList.size() == 3); + return EllipsoidalCS::create(util::PropertyMap(), l_axisList[0], + l_axisList[1], + l_axisList[2]->alterUnit(linearUnit)); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalCS::~VerticalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalCS::VerticalCS(const CoordinateSystemAxisNNPtr &axisIn) + : CoordinateSystem(std::vector{axisIn}) {} +//! @endcond + +// --------------------------------------------------------------------------- + +#ifdef notdef +VerticalCS::VerticalCS(const VerticalCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalCS. + * + * @param properties See \ref general_properties. + * @param axis The axis. + * @return a new VerticalCS. + */ +VerticalCSNNPtr VerticalCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis) { + auto cs(VerticalCS::nn_make_shared(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalCS with a Gravity-related height axis + * + * @param unit linear unit. + * @return a new VerticalCS. + */ +VerticalCSNNPtr +VerticalCS::createGravityRelatedHeight(const common::UnitOfMeasure &unit) { + auto cs(VerticalCS::nn_make_shared(CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + "Gravity-related height"), + "H", AxisDirection::UP, unit))); + return cs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalCSNNPtr VerticalCS::alterUnit(const common::UnitOfMeasure &unit) const { + const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; + return VerticalCS::nn_make_shared( + l_axisList[0]->alterUnit(unit)); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CartesianCS::~CartesianCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CartesianCS::CartesianCS(const std::vector &axisIn) + : CoordinateSystem(axisIn) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +CartesianCS::CartesianCS(const CartesianCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS. + * + * @param properties See \ref general_properties. + * @param axis1 The first axis. + * @param axis2 The second axis. + * @return a new CartesianCS. + */ +CartesianCSNNPtr CartesianCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2) { + std::vector axis{axis1, axis2}; + auto cs(CartesianCS::nn_make_shared(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS. + * + * @param properties See \ref general_properties. + * @param axis1 The first axis. + * @param axis2 The second axis. + * @param axis3 The third axis. + * @return a new CartesianCS. + */ +CartesianCSNNPtr CartesianCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2, + const CoordinateSystemAxisNNPtr &axis3) { + std::vector axis{axis1, axis2, axis3}; + auto cs(CartesianCS::nn_make_shared(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS with a Easting (first) and Northing + * (second) axis. + * + * @param unit Linear unit of the axes. + * @return a new CartesianCS. + */ +CartesianCSNNPtr +CartesianCS::createEastingNorthing(const common::UnitOfMeasure &unit) { + return create(util::PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Easting), + AxisAbbreviation::E, AxisDirection::EAST, unit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Northing), + AxisAbbreviation::N, AxisDirection::NORTH, unit)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS with a Northing (first) and Easting + * (second) axis. + * + * @param unit Linear unit of the axes. + * @return a new CartesianCS. + */ +CartesianCSNNPtr +CartesianCS::createNorthingEasting(const common::UnitOfMeasure &unit) { + return create(util::PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Northing), + AxisAbbreviation::N, AxisDirection::NORTH, unit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Easting), + AxisAbbreviation::E, AxisDirection::EAST, unit)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS with a Westing (first) and Southing + * (second) axis. + * + * @param unit Linear unit of the axes. + * @return a new CartesianCS. + */ +CartesianCSNNPtr +CartesianCS::createWestingSouthing(const common::UnitOfMeasure &unit) { + return create(util::PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Easting), + AxisAbbreviation::Y, AxisDirection::WEST, unit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Northing), + AxisAbbreviation::X, AxisDirection::SOUTH, unit)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS, north-pole centered, + * with a Easting (first) South-Oriented and + * Northing (second) South-Oriented axis. + * + * @param unit Linear unit of the axes. + * @return a new CartesianCS. + */ +CartesianCSNNPtr CartesianCS::createNorthPoleEastingSouthNorthingSouth( + const common::UnitOfMeasure &unit) { + return create(util::PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Easting), + AxisAbbreviation::E, AxisDirection::SOUTH, unit, + Meridian::create(common::Angle(90))), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Northing), + AxisAbbreviation::N, AxisDirection::SOUTH, unit, + Meridian::create(common::Angle(180)))); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS, south-pole centered, + * with a Easting (first) North-Oriented and + * Northing (second) North-Oriented axis. + * + * @param unit Linear unit of the axes. + * @return a new CartesianCS. + */ +CartesianCSNNPtr CartesianCS::createSouthPoleEastingNorthNorthingNorth( + const common::UnitOfMeasure &unit) { + return create(util::PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Easting), + AxisAbbreviation::E, AxisDirection::NORTH, unit, + Meridian::create(common::Angle(90))), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Northing), + AxisAbbreviation::N, AxisDirection::NORTH, unit, + Meridian::create(common::Angle(0)))); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS with the three geocentric axes. + * + * @param unit Liinear unit of the axes. + * @return a new CartesianCS. + */ +CartesianCSNNPtr +CartesianCS::createGeocentric(const common::UnitOfMeasure &unit) { + return create(util::PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Geocentric_X), + AxisAbbreviation::X, AxisDirection::GEOCENTRIC_X, unit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Geocentric_Y), + AxisAbbreviation::Y, AxisDirection::GEOCENTRIC_Y, unit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Geocentric_Z), + AxisAbbreviation::Z, AxisDirection::GEOCENTRIC_Z, unit)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CartesianCSNNPtr +CartesianCS::alterUnit(const common::UnitOfMeasure &unit) const { + const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; + if (l_axisList.size() == 2) { + return CartesianCS::create(util::PropertyMap(), + l_axisList[0]->alterUnit(unit), + l_axisList[1]->alterUnit(unit)); + } else { + assert(l_axisList.size() == 3); + return CartesianCS::create( + util::PropertyMap(), l_axisList[0]->alterUnit(unit), + l_axisList[1]->alterUnit(unit), l_axisList[2]->alterUnit(unit)); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +OrdinalCS::~OrdinalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +OrdinalCS::OrdinalCS(const std::vector &axisIn) + : CoordinateSystem(axisIn) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +OrdinalCS::OrdinalCS(const OrdinalCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a OrdinalCS. + * + * @param properties See \ref general_properties. + * @param axisIn List of axis. + * @return a new OrdinalCS. + */ +OrdinalCSNNPtr +OrdinalCS::create(const util::PropertyMap &properties, + const std::vector &axisIn) { + auto cs(OrdinalCS::nn_make_shared(axisIn)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ParametricCS::~ParametricCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +ParametricCS::ParametricCS(const std::vector &axisIn) + : CoordinateSystem(axisIn) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +ParametricCS::ParametricCS(const ParametricCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParametricCS. + * + * @param properties See \ref general_properties. + * @param axisIn Axis. + * @return a new ParametricCS. + */ +ParametricCSNNPtr +ParametricCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axisIn) { + auto cs(ParametricCS::nn_make_shared( + std::vector{axisIn})); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +AxisDirection::AxisDirection(const std::string &nameIn) : CodeList(nameIn) { + assert(registry.find(nameIn) == registry.end()); + registry[nameIn] = this; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const AxisDirection * +AxisDirection::valueOf(const std::string &nameIn) noexcept { + auto iter = registry.find(nameIn); + if (iter == registry.end()) + return nullptr; + return iter->second; +} +//! @endcond + +//! @cond Doxygen_Suppress +// --------------------------------------------------------------------------- + +AxisDirectionWKT1::AxisDirectionWKT1(const std::string &nameIn) + : CodeList(nameIn) { + assert(registry.find(nameIn) == registry.end()); + registry[nameIn] = this; +} + +// --------------------------------------------------------------------------- + +const AxisDirectionWKT1 *AxisDirectionWKT1::valueOf(const std::string &nameIn) { + auto iter = registry.find(nameIn); + if (iter == registry.end()) + return nullptr; + return iter->second; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalCS::~TemporalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalCS::TemporalCS(const CoordinateSystemAxisNNPtr &axisIn) + : CoordinateSystem(std::vector{axisIn}) {} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DateTimeTemporalCS::~DateTimeTemporalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DateTimeTemporalCS::DateTimeTemporalCS(const CoordinateSystemAxisNNPtr &axisIn) + : TemporalCS(axisIn) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DateTimeTemporalCS. + * + * @param properties See \ref general_properties. + * @param axisIn The axis. + * @return a new DateTimeTemporalCS. + */ +DateTimeTemporalCSNNPtr +DateTimeTemporalCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axisIn) { + auto cs(DateTimeTemporalCS::nn_make_shared(axisIn)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +std::string DateTimeTemporalCS::getWKT2Type(bool use2018Keywords) const { + return use2018Keywords ? "TemporalDateTime" : "temporal"; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalCountCS::~TemporalCountCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +TemporalCountCS::TemporalCountCS(const CoordinateSystemAxisNNPtr &axisIn) + : TemporalCS(axisIn) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a TemporalCountCS. + * + * @param properties See \ref general_properties. + * @param axisIn The axis. + * @return a new TemporalCountCS. + */ +TemporalCountCSNNPtr +TemporalCountCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axisIn) { + auto cs(TemporalCountCS::nn_make_shared(axisIn)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +std::string TemporalCountCS::getWKT2Type(bool use2018Keywords) const { + return use2018Keywords ? "TemporalCount" : "temporal"; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalMeasureCS::~TemporalMeasureCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +TemporalMeasureCS::TemporalMeasureCS(const CoordinateSystemAxisNNPtr &axisIn) + : TemporalCS(axisIn) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a TemporalMeasureCS. + * + * @param properties See \ref general_properties. + * @param axisIn The axis. + * @return a new TemporalMeasureCS. + */ +TemporalMeasureCSNNPtr +TemporalMeasureCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axisIn) { + auto cs(TemporalMeasureCS::nn_make_shared(axisIn)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +std::string TemporalMeasureCS::getWKT2Type(bool use2018Keywords) const { + return use2018Keywords ? "TemporalMeasure" : "temporal"; +} + +} // namespace cs +NS_PROJ_END diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp new file mode 100644 index 00000000..a05470ff --- /dev/null +++ b/src/iso19111/crs.cpp @@ -0,0 +1,4971 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 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 + +//! @cond Doxygen_Suppress +#define DO_NOT_DEFINE_EXTERN_DERIVED_CRS_TEMPLATE +//! @endcond + +#include "proj/crs.hpp" +#include "proj/common.hpp" +#include "proj/coordinateoperation.hpp" +#include "proj/coordinatesystem.hpp" +#include "proj/io.hpp" +#include "proj/util.hpp" + +#include "proj/internal/coordinatesystem_internal.hpp" +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include +#include +#include +#include +#include +#include +#include + +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; +template<> nn::~nn() = default; +template<> nn::~nn() = default; +template<> nn::~nn() = default; +template<> nn::~nn() = default; +template<> nn::~nn() = default; +}} +#endif + +NS_PROJ_START + +namespace crs { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CRS::Private { + BoundCRSPtr canonicalBoundCRS_{}; + std::string extensionProj4_{}; + bool implicitCS_ = false; + + void setImplicitCS(const util::PropertyMap &properties) { + const auto pVal = properties.get("IMPLICIT_CS"); + if (pVal) { + if (const auto genVal = + dynamic_cast(pVal->get())) { + if (genVal->type() == util::BoxedValue::Type::BOOLEAN && + genVal->booleanValue()) { + implicitCS_ = true; + } + } + } + } +}; +//! @endcond + +// --------------------------------------------------------------------------- + +CRS::CRS() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +CRS::CRS(const CRS &other) + : ObjectUsage(other), d(internal::make_unique(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CRS::~CRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the BoundCRS potentially attached to this CRS. + * + * In the case this method is called on a object returned by + * BoundCRS::baseCRSWithCanonicalBoundCRS(), this method will return this + * BoundCRS + * + * @return a BoundCRSPtr, that might be null. + */ +const BoundCRSPtr &CRS::canonicalBoundCRS() PROJ_CONST_DEFN { + return d->canonicalBoundCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the GeodeticCRS of the CRS. + * + * Returns the GeodeticCRS contained in a CRS. This works currently with + * input parameters of type GeodeticCRS or derived, ProjectedCRS, + * CompoundCRS or BoundCRS. + * + * @return a GeodeticCRSPtr, that might be null. + */ +GeodeticCRSPtr CRS::extractGeodeticCRS() const { + auto raw = extractGeodeticCRSRaw(); + if (raw) { + return std::dynamic_pointer_cast( + raw->shared_from_this().as_nullable()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const GeodeticCRS *CRS::extractGeodeticCRSRaw() const { + auto geodCRS = dynamic_cast(this); + if (geodCRS) { + return geodCRS; + } + auto projCRS = dynamic_cast(this); + if (projCRS) { + return projCRS->baseCRS()->extractGeodeticCRSRaw(); + } + auto compoundCRS = dynamic_cast(this); + if (compoundCRS) { + for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { + auto retGeogCRS = subCrs->extractGeodeticCRSRaw(); + if (retGeogCRS) { + return retGeogCRS; + } + } + } + auto boundCRS = dynamic_cast(this); + if (boundCRS) { + return boundCRS->baseCRS()->extractGeodeticCRSRaw(); + } + return nullptr; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const std::string &CRS::getExtensionProj4() const noexcept { + return d->extensionProj4_; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the GeographicCRS of the CRS. + * + * Returns the GeographicCRS contained in a CRS. This works currently with + * input parameters of type GeographicCRS or derived, ProjectedCRS, + * CompoundCRS or BoundCRS. + * + * @return a GeographicCRSPtr, that might be null. + */ +GeographicCRSPtr CRS::extractGeographicCRS() const { + auto raw = extractGeodeticCRSRaw(); + if (raw) { + return std::dynamic_pointer_cast( + raw->shared_from_this().as_nullable()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static util::PropertyMap +createPropertyMap(const common::IdentifiedObject *obj) { + auto props = util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + obj->nameStr()); + if (obj->isDeprecated()) { + props.set(common::IdentifiedObject::DEPRECATED_KEY, true); + } + return props; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CRSNNPtr CRS::alterGeodeticCRS(const GeodeticCRSNNPtr &newGeodCRS) const { + auto geodCRS = dynamic_cast(this); + if (geodCRS) { + return newGeodCRS; + } + + auto projCRS = dynamic_cast(this); + if (projCRS) { + return ProjectedCRS::create(createPropertyMap(this), newGeodCRS, + projCRS->derivingConversionRef(), + projCRS->coordinateSystem()); + } + + auto compoundCRS = dynamic_cast(this); + if (compoundCRS) { + std::vector components; + for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { + components.emplace_back(subCrs->alterGeodeticCRS(newGeodCRS)); + } + return CompoundCRS::create(createPropertyMap(this), components); + } + + return NN_NO_CHECK( + std::dynamic_pointer_cast(shared_from_this().as_nullable())); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CRSNNPtr CRS::alterCSLinearUnit(const common::UnitOfMeasure &unit) const { + { + auto projCRS = dynamic_cast(this); + if (projCRS) { + return ProjectedCRS::create( + createPropertyMap(this), projCRS->baseCRS(), + projCRS->derivingConversionRef(), + projCRS->coordinateSystem()->alterUnit(unit)); + } + } + + { + auto geodCRS = dynamic_cast(this); + if (geodCRS && geodCRS->isGeocentric()) { + auto cs = dynamic_cast( + geodCRS->coordinateSystem().get()); + assert(cs); + return GeodeticCRS::create( + createPropertyMap(this), geodCRS->datum(), + geodCRS->datumEnsemble(), cs->alterUnit(unit)); + } + } + + { + auto geogCRS = dynamic_cast(this); + if (geogCRS && geogCRS->coordinateSystem()->axisList().size() == 3) { + return GeographicCRS::create( + createPropertyMap(this), geogCRS->datum(), + geogCRS->datumEnsemble(), + geogCRS->coordinateSystem()->alterLinearUnit(unit)); + } + } + + { + auto vertCRS = dynamic_cast(this); + if (vertCRS) { + return VerticalCRS::create( + createPropertyMap(this), vertCRS->datum(), + vertCRS->datumEnsemble(), + vertCRS->coordinateSystem()->alterUnit(unit)); + } + } + + { + auto engCRS = dynamic_cast(this); + if (engCRS) { + auto cartCS = util::nn_dynamic_pointer_cast( + engCRS->coordinateSystem()); + if (cartCS) { + auto props = createPropertyMap(this); + props.set("FORCE_OUTPUT_CS", true); + return EngineeringCRS::create(props, engCRS->datum(), + cartCS->alterUnit(unit)); + } else { + auto vertCS = util::nn_dynamic_pointer_cast( + engCRS->coordinateSystem()); + if (vertCS) { + auto props = createPropertyMap(this); + props.set("FORCE_OUTPUT_CS", true); + return EngineeringCRS::create(props, engCRS->datum(), + vertCS->alterUnit(unit)); + } + } + } + } + + return NN_NO_CHECK( + std::dynamic_pointer_cast(shared_from_this().as_nullable())); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the VerticalCRS of the CRS. + * + * Returns the VerticalCRS contained in a CRS. This works currently with + * input parameters of type VerticalCRS or derived, CompoundCRS or BoundCRS. + * + * @return a VerticalCRSPtr, that might be null. + */ +VerticalCRSPtr CRS::extractVerticalCRS() const { + auto vertCRS = dynamic_cast(this); + if (vertCRS) { + return std::dynamic_pointer_cast( + shared_from_this().as_nullable()); + } + auto compoundCRS = dynamic_cast(this); + if (compoundCRS) { + for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { + auto retVertCRS = subCrs->extractVerticalCRS(); + if (retVertCRS) { + return retVertCRS; + } + } + } + auto boundCRS = dynamic_cast(this); + if (boundCRS) { + return boundCRS->baseCRS()->extractVerticalCRS(); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns potentially + * a BoundCRS, with a transformation to EPSG:4326, wrapping this CRS + * + * If no such BoundCRS is possible, the object will be returned. + * + * The purpose of this method is to be able to format a PROJ.4 string with + * a +towgs84 parameter or a WKT1:GDAL string with a TOWGS node. + * + * This method will fetch the GeographicCRS of this CRS and find a + * transformation to EPSG:4326 using the domain of the validity of the main CRS. + * + * @return a CRS. + */ +CRSNNPtr +CRS::createBoundCRSToWGS84IfPossible(const io::DatabaseContextPtr &dbContext, + bool allowIntermediateCRS) const { + auto thisAsCRS = NN_NO_CHECK( + std::static_pointer_cast(shared_from_this().as_nullable())); + auto boundCRS = util::nn_dynamic_pointer_cast(thisAsCRS); + if (!boundCRS) { + boundCRS = canonicalBoundCRS(); + } + if (boundCRS) { + if (boundCRS->hubCRS()->_isEquivalentTo( + GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + return NN_NO_CHECK(boundCRS); + } + } + + auto geodCRS = util::nn_dynamic_pointer_cast(thisAsCRS); + auto geogCRS = extractGeographicCRS(); + auto hubCRS = util::nn_static_pointer_cast(GeographicCRS::EPSG_4326); + if (geodCRS && !geogCRS) { + if (geodCRS->_isEquivalentTo( + GeographicCRS::EPSG_4978.get(), + util::IComparable::Criterion::EQUIVALENT)) { + return thisAsCRS; + } + hubCRS = util::nn_static_pointer_cast(GeodeticCRS::EPSG_4978); + } else if (!geogCRS || + geogCRS->_isEquivalentTo( + GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + return thisAsCRS; + } else { + geodCRS = geogCRS; + } + + if (!dbContext) { + return thisAsCRS; + } + + const auto &l_domains = domains(); + metadata::ExtentPtr extent; + if (!l_domains.empty()) { + extent = l_domains[0]->domainOfValidity(); + } + + std::string crs_authority; + const auto &l_identifiers = identifiers(); + // If the object has an authority, restrict the transformations to + // come from that codespace too. This avoids for example EPSG:4269 + // (NAD83) to use a (dubious) ESRI transformation. + if (!l_identifiers.empty()) { + crs_authority = *(l_identifiers[0]->codeSpace()); + } + + auto authorities = dbContext->getAllowedAuthorities(crs_authority, "EPSG"); + if (authorities.empty()) { + authorities.emplace_back(); + } + for (const auto &authority : authorities) { + try { + + auto authFactory = io::AuthorityFactory::create( + NN_NO_CHECK(dbContext), + authority == "any" ? std::string() : authority); + auto ctxt = operation::CoordinateOperationContext::create( + authFactory, extent, 0.0); + ctxt->setAllowUseIntermediateCRS(allowIntermediateCRS); + // ctxt->setSpatialCriterion( + // operation::CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); + auto list = + operation::CoordinateOperationFactory::create() + ->createOperations(NN_NO_CHECK(geodCRS), hubCRS, ctxt); + for (const auto &op : list) { + auto transf = + util::nn_dynamic_pointer_cast( + op); + if (transf && !starts_with(transf->nameStr(), "Null geo")) { + try { + transf->getTOWGS84Parameters(); + } catch (const std::exception &) { + continue; + } + return util::nn_static_pointer_cast(BoundCRS::create( + thisAsCRS, hubCRS, NN_NO_CHECK(transf))); + } else { + auto concatenated = + dynamic_cast( + op.get()); + if (concatenated) { + // Case for EPSG:4807 / "NTF (Paris)" that is made of a + // longitude rotation followed by a Helmert + // The prime meridian shift will be accounted elsewhere + const auto &subops = concatenated->operations(); + if (subops.size() == 2) { + auto firstOpIsTransformation = + dynamic_cast( + subops[0].get()); + auto firstOpIsConversion = + dynamic_cast( + subops[0].get()); + if ((firstOpIsTransformation && + firstOpIsTransformation + ->isLongitudeRotation()) || + (dynamic_cast(thisAsCRS.get()) && + firstOpIsConversion)) { + transf = util::nn_dynamic_pointer_cast< + operation::Transformation>(subops[1]); + if (transf && + !starts_with(transf->nameStr(), + "Null geo")) { + try { + transf->getTOWGS84Parameters(); + } catch (const std::exception &) { + continue; + } + return util::nn_static_pointer_cast( + BoundCRS::create(thisAsCRS, hubCRS, + NN_NO_CHECK(transf))); + } + } + } + } + } + } + } catch (const std::exception &) { + } + } + return thisAsCRS; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a CRS whose coordinate system does not contain a vertical + * component + * + * @return a CRS. + */ +CRSNNPtr CRS::stripVerticalComponent() const { + auto self = NN_NO_CHECK( + std::dynamic_pointer_cast(shared_from_this().as_nullable())); + + auto geogCRS = dynamic_cast(this); + if (geogCRS) { + const auto &axisList = geogCRS->coordinateSystem()->axisList(); + if (axisList.size() == 3) { + auto cs = cs::EllipsoidalCS::create(util::PropertyMap(), + axisList[0], axisList[1]); + return util::nn_static_pointer_cast(GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + nameStr()), + geogCRS->datum(), geogCRS->datumEnsemble(), cs)); + } + } + auto projCRS = dynamic_cast(this); + if (projCRS) { + const auto &axisList = projCRS->coordinateSystem()->axisList(); + if (axisList.size() == 3) { + auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0], + axisList[1]); + return util::nn_static_pointer_cast(ProjectedCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + nameStr()), + projCRS->baseCRS(), projCRS->derivingConversion(), cs)); + } + } + return self; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +/** \brief Return a shallow clone of this object. */ +CRSNNPtr CRS::shallowClone() const { return _shallowClone(); } + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +CRSNNPtr CRS::alterName(const std::string &newName) const { + auto crs = shallowClone(); + auto newNameMod(newName); + auto props = util::PropertyMap(); + if (ends_with(newNameMod, " (deprecated)")) { + newNameMod.resize(newNameMod.size() - strlen(" (deprecated)")); + props.set(common::IdentifiedObject::DEPRECATED_KEY, true); + } + props.set(common::IdentifiedObject::NAME_KEY, newNameMod); + crs->setProperties(props); + return crs; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +CRSNNPtr CRS::alterId(const std::string &authName, + const std::string &code) const { + auto crs = shallowClone(); + auto props = util::PropertyMap(); + props.set(metadata::Identifier::CODESPACE_KEY, authName) + .set(metadata::Identifier::CODE_KEY, code); + crs->setProperties(props); + return crs; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are either hard-coded, or looked in the database when + * authorityFactory is not null. + * + * The method returns a list of matching reference CRS, and the percentage + * (0-100) of confidence in the match. The list is sorted by decreasing + * confidence. + * + * 100% means that the name of the reference entry + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent), but the names do not match at all. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * Other confidence values may be returned by some specialized implementations. + * + * This is implemented for GeodeticCRS, ProjectedCRS, VerticalCRS and + * CompoundCRS. + * + * @param authorityFactory Authority factory (or null, but degraded + * functionality) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list> +CRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + return _identify(authorityFactory); +} + +// --------------------------------------------------------------------------- + +/** \brief Return CRSs that are non-deprecated substitutes for the current CRS. + */ +std::list +CRS::getNonDeprecated(const io::DatabaseContextNNPtr &dbContext) const { + std::list res; + const auto &l_identifiers = identifiers(); + if (l_identifiers.empty()) { + return res; + } + const char *tableName = nullptr; + if (dynamic_cast(this)) { + tableName = "geodetic_crs"; + } else if (dynamic_cast(this)) { + tableName = "projected_crs"; + } else if (dynamic_cast(this)) { + tableName = "vertical_crs"; + } else if (dynamic_cast(this)) { + tableName = "compound_crs"; + } + if (!tableName) { + return res; + } + const auto &id = l_identifiers[0]; + auto tmpRes = + dbContext->getNonDeprecated(tableName, *(id->codeSpace()), id->code()); + for (const auto &pair : tmpRes) { + res.emplace_back(io::AuthorityFactory::create(dbContext, pair.first) + ->createCoordinateReferenceSystem(pair.second)); + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list> +CRS::_identify(const io::AuthorityFactoryPtr &) const { + return {}; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct SingleCRS::Private { + datum::DatumPtr datum{}; + datum::DatumEnsemblePtr datumEnsemble{}; + cs::CoordinateSystemNNPtr coordinateSystem; + + Private(const datum::DatumPtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::CoordinateSystemNNPtr &csIn) + : datum(datumIn), datumEnsemble(datumEnsembleIn), + coordinateSystem(csIn) { + if ((datum ? 1 : 0) + (datumEnsemble ? 1 : 0) != 1) { + throw util::Exception("datum or datumEnsemble should be set"); + } + } +}; +//! @endcond + +// --------------------------------------------------------------------------- + +SingleCRS::SingleCRS(const datum::DatumPtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::CoordinateSystemNNPtr &csIn) + : d(internal::make_unique(datumIn, datumEnsembleIn, csIn)) {} + +// --------------------------------------------------------------------------- + +SingleCRS::SingleCRS(const SingleCRS &other) + : CRS(other), d(internal::make_unique(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +SingleCRS::~SingleCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::Datum associated with the CRS. + * + * This might be null, in which case datumEnsemble() return will not be null. + * + * @return a Datum that might be null. + */ +const datum::DatumPtr &SingleCRS::datum() PROJ_CONST_DEFN { return d->datum; } + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::DatumEnsemble associated with the CRS. + * + * This might be null, in which case datum() return will not be null. + * + * @return a DatumEnsemble that might be null. + */ +const datum::DatumEnsemblePtr &SingleCRS::datumEnsemble() PROJ_CONST_DEFN { + return d->datumEnsemble; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::CoordinateSystem associated with the CRS. + * + * This might be null, in which case datumEnsemble() return will not be null. + * + * @return a CoordinateSystem that might be null. + */ +const cs::CoordinateSystemNNPtr &SingleCRS::coordinateSystem() PROJ_CONST_DEFN { + return d->coordinateSystem; +} + +// --------------------------------------------------------------------------- + +bool SingleCRS::baseIsEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherSingleCRS = dynamic_cast(other); + if (otherSingleCRS == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion))) { + return false; + } + const auto &thisDatum = d->datum; + const auto &otherDatum = otherSingleCRS->d->datum; + if (thisDatum) { + if (!thisDatum->_isEquivalentTo(otherDatum.get(), criterion)) { + return false; + } + } else { + if (otherDatum) { + return false; + } + } + + // TODO test DatumEnsemble + return d->coordinateSystem->_isEquivalentTo( + otherSingleCRS->d->coordinateSystem.get(), criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void SingleCRS::exportDatumOrDatumEnsembleToWkt( + io::WKTFormatter *formatter) const // throw(io::FormattingException) +{ + const auto &l_datum = d->datum; + if (l_datum) { + l_datum->_exportToWKT(formatter); + } else { + const auto &l_datumEnsemble = d->datumEnsemble; + assert(l_datumEnsemble); + l_datumEnsemble->_exportToWKT(formatter); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeodeticCRS::Private { + std::vector velocityModel{}; + datum::GeodeticReferenceFramePtr datum_; + + explicit Private(const datum::GeodeticReferenceFramePtr &datumIn) + : datum_(datumIn) {} +}; + +// --------------------------------------------------------------------------- + +static const datum::DatumEnsemblePtr & +checkEnsembleForGeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &ensemble) { + const char *msg = "One of Datum or DatumEnsemble should be defined"; + if (datumIn) { + if (!ensemble) { + return ensemble; + } + msg = "Datum and DatumEnsemble should not be defined"; + } else if (ensemble) { + const auto &datums = ensemble->datums(); + assert(!datums.empty()); + auto grfFirst = + dynamic_cast(datums[0].get()); + if (grfFirst) { + return ensemble; + } + msg = "Ensemble should contain GeodeticReferenceFrame"; + } + throw util::Exception(msg); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::EllipsoidalCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique(datumIn)) {} + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::SphericalCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique(datumIn)) {} + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::CartesianCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique(datumIn)) {} + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const GeodeticCRS &other) + : SingleCRS(other), d(internal::make_unique(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeodeticCRS::~GeodeticCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr GeodeticCRS::_shallowClone() const { + auto crs(GeodeticCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::GeodeticReferenceFrame associated with the CRS. + * + * @return a GeodeticReferenceFrame or null (in which case datumEnsemble() + * should return a non-null pointer.) + */ +const datum::GeodeticReferenceFramePtr &GeodeticCRS::datum() PROJ_CONST_DEFN { + return d->datum_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static datum::GeodeticReferenceFrame *oneDatum(const GeodeticCRS *crs) { + const auto &l_datumEnsemble = crs->datumEnsemble(); + assert(l_datumEnsemble); + const auto &l_datums = l_datumEnsemble->datums(); + return static_cast(l_datums[0].get()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the PrimeMeridian associated with the GeodeticReferenceFrame + * or with one of the GeodeticReferenceFrame of the datumEnsemble(). + * + * @return the PrimeMeridian. + */ +const datum::PrimeMeridianNNPtr &GeodeticCRS::primeMeridian() PROJ_CONST_DEFN { + if (d->datum_) { + return d->datum_->primeMeridian(); + } + return oneDatum(this)->primeMeridian(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the ellipsoid associated with the GeodeticReferenceFrame + * or with one of the GeodeticReferenceFrame of the datumEnsemble(). + * + * @return the PrimeMeridian. + */ +const datum::EllipsoidNNPtr &GeodeticCRS::ellipsoid() PROJ_CONST_DEFN { + if (d->datum_) { + return d->datum_->ellipsoid(); + } + return oneDatum(this)->ellipsoid(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the velocity model associated with the CRS. + * + * @return a velocity model. might be null. + */ +const std::vector & +GeodeticCRS::velocityModel() PROJ_CONST_DEFN { + return d->velocityModel; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether the CRS is a geocentric one. + * + * A geocentric CRS is a geodetic CRS that has a Cartesian coordinate system + * with three axis, whose direction is respectively + * cs::AxisDirection::GEOCENTRIC_X, + * cs::AxisDirection::GEOCENTRIC_Y and cs::AxisDirection::GEOCENTRIC_Z. + * + * @return true if the CRS is a geocentric CRS. + */ +bool GeodeticCRS::isGeocentric() PROJ_CONST_DEFN { + const auto &cs = coordinateSystem(); + const auto &axisList = cs->axisList(); + return axisList.size() == 3 && + dynamic_cast(cs.get()) != nullptr && + &axisList[0]->direction() == &cs::AxisDirection::GEOCENTRIC_X && + &axisList[1]->direction() == &cs::AxisDirection::GEOCENTRIC_Y && + &axisList[2]->direction() == &cs::AxisDirection::GEOCENTRIC_Z; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame and a + * cs::SphericalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS. + * @param cs a SphericalCS. + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFrameNNPtr &datum, + const cs::SphericalCSNNPtr &cs) { + return create(properties, datum.as_nullable(), nullptr, cs); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame or + * datum::DatumEnsemble and a cs::SphericalCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS, or nullptr + * @param datumEnsemble The datum ensemble of the CRS, or nullptr. + * @param cs a SphericalCS. + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFramePtr &datum, + const datum::DatumEnsemblePtr &datumEnsemble, + const cs::SphericalCSNNPtr &cs) { + auto crs( + GeodeticCRS::nn_make_shared(datum, datumEnsemble, cs)); + crs->assignSelf(crs); + crs->setProperties(properties); + properties.getStringValue("EXTENSION_PROJ4", + crs->CRS::getPrivate()->extensionProj4_); + + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame and a + * cs::CartesianCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS. + * @param cs a CartesianCS. + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFrameNNPtr &datum, + const cs::CartesianCSNNPtr &cs) { + return create(properties, datum.as_nullable(), nullptr, cs); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame or + * datum::DatumEnsemble and a cs::CartesianCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS, or nullptr + * @param datumEnsemble The datum ensemble of the CRS, or nullptr. + * @param cs a CartesianCS + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFramePtr &datum, + const datum::DatumEnsemblePtr &datumEnsemble, + const cs::CartesianCSNNPtr &cs) { + auto crs( + GeodeticCRS::nn_make_shared(datum, datumEnsemble, cs)); + crs->assignSelf(crs); + crs->setProperties(properties); + properties.getStringValue("EXTENSION_PROJ4", + crs->CRS::getPrivate()->extensionProj4_); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? ((formatter->use2018Keywords() && + dynamic_cast(this)) + ? io::WKTConstants::GEOGCRS + : io::WKTConstants::GEODCRS) + : isGeocentric() ? io::WKTConstants::GEOCCS + : io::WKTConstants::GEOGCS, + !identifiers().empty()); + auto l_name = nameStr(); + const auto &cs = coordinateSystem(); + const auto &axisList = cs->axisList(); + + if (formatter->useESRIDialect()) { + if (axisList.size() != 2) { + io::FormattingException::Throw( + "Only export of Geographic 2D CRS is supported in WKT1_ESRI"); + } + + if (l_name == "WGS 84") { + l_name = "GCS_WGS_1984"; + } else { + bool aliasFound = false; + const auto &dbContext = formatter->databaseContext(); + if (dbContext) { + auto l_alias = dbContext->getAliasFromOfficialName( + l_name, "geodetic_crs", "ESRI"); + if (!l_alias.empty()) { + l_name = l_alias; + aliasFound = true; + } + } + if (!aliasFound) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + if (!starts_with(l_name, "GCS_")) { + l_name = "GCS_" + l_name; + } + } + } + } + if (!isWKT2 && !formatter->useESRIDialect() && isDeprecated()) { + l_name += " (deprecated)"; + } + formatter->addQuotedString(l_name); + + const auto &unit = axisList[0]->unit(); + formatter->pushAxisAngularUnit(common::UnitOfMeasure::create(unit)); + exportDatumOrDatumEnsembleToWkt(formatter); + primeMeridian()->_exportToWKT(formatter); + formatter->popAxisAngularUnit(); + if (!isWKT2) { + unit._exportToWKT(formatter); + } + + const auto oldAxisOutputRule = formatter->outputAxis(); + if (oldAxisOutputRule == + io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE && + isGeocentric()) { + formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES); + } + cs->_exportToWKT(formatter); + formatter->setOutputAxis(oldAxisOutputRule); + + ObjectUsage::baseExportToWKT(formatter); + + if (!isWKT2 && !formatter->useESRIDialect()) { + const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_; + if (!extensionProj4.empty()) { + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4"); + formatter->addQuotedString(extensionProj4); + formatter->endNode(); + } + } + + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::addGeocentricUnitConversionIntoPROJString( + io::PROJStringFormatter *formatter) const { + + const auto &axisList = coordinateSystem()->axisList(); + const auto &unit = axisList[0]->unit(); + if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE, + util::IComparable::Criterion::EQUIVALENT)) { + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + io::FormattingException::Throw("GeodeticCRS::exportToPROJString(): " + "non-meter unit not supported for " + "PROJ.4"); + } + + formatter->addStep("unitconvert"); + formatter->addParam("xy_in", "m"); + formatter->addParam("z_in", "m"); + { + auto projUnit = unit.exportToPROJString(); + if (!projUnit.empty()) { + formatter->addParam("xy_out", projUnit); + formatter->addParam("z_out", projUnit); + return; + } + } + + const auto &toSI = unit.conversionToSI(); + formatter->addParam("xy_out", toSI); + formatter->addParam("z_out", toSI); + } else if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + formatter->addParam("units", "m"); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_; + if (!extensionProj4.empty()) { + formatter->ingestPROJString(extensionProj4); + formatter->addNoDefs(false); + return; + } + } + + if (!isGeocentric()) { + io::FormattingException::Throw( + "GeodeticCRS::exportToPROJString() only " + "supports geocentric coordinate systems"); + } + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + formatter->addStep("geocent"); + } else { + formatter->addStep("cart"); + } + addDatumInfoToPROJString(formatter); + addGeocentricUnitConversionIntoPROJString(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::addDatumInfoToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + const auto &TOWGS84Params = formatter->getTOWGS84Parameters(); + bool datumWritten = false; + const auto &nadgrids = formatter->getHDatumExtension(); + const auto &l_datum = datum(); + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4 && + l_datum && TOWGS84Params.empty() && nadgrids.empty()) { + if (l_datum->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + datumWritten = true; + formatter->addParam("datum", "WGS84"); + } else if (l_datum->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6267.get(), + util::IComparable::Criterion::EQUIVALENT)) { + datumWritten = true; + formatter->addParam("datum", "NAD27"); + } else if (l_datum->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6269.get(), + util::IComparable::Criterion::EQUIVALENT)) { + datumWritten = true; + formatter->addParam("datum", "NAD83"); + } + } + if (!datumWritten) { + ellipsoid()->_exportToPROJString(formatter); + primeMeridian()->_exportToPROJString(formatter); + } + if (TOWGS84Params.size() == 7) { + formatter->addParam("towgs84", TOWGS84Params); + } + if (!nadgrids.empty()) { + formatter->addParam("nadgrids", nadgrids); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static util::IComparable::Criterion +getStandardCriterion(util::IComparable::Criterion criterion) { + return criterion == util::IComparable::Criterion:: + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS + ? util::IComparable::Criterion::EQUIVALENT + : criterion; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeodeticCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + const auto standardCriterion = getStandardCriterion(criterion); + auto otherGeodCRS = dynamic_cast(other); + // TODO test velocityModel + return otherGeodCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, standardCriterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +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); +} +//! @endcond + +// --------------------------------------------------------------------------- + +GeodeticCRSNNPtr GeodeticCRS::createEPSG_4978() { + return create( + createMapNameEPSGCode("WGS 84", 4978), + datum::GeodeticReferenceFrame::EPSG_6326, + cs::CartesianCS::createGeocentric(common::UnitOfMeasure::METRE)); +} + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are either hard-coded, or looked in the database when + * authorityFactory is not null. + * + * The method returns a list of matching reference CRS, and the percentage + * (0-100) of confidence in the match. + * 100% means that the name of the reference entry + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent datum and coordinate system), + * but the names do not match at all. + * 60% means that ellipsoid, prime meridian and coordinate systems are + * equivalent, but the CRS and datum names do not match. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * @param authorityFactory Authority factory (or null, but degraded + * functionality) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list> +GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair Pair; + std::list res; + const auto &thisName(nameStr()); + + const bool l_implicitCS = CRS::getPrivate()->implicitCS_; + const auto crsCriterion = + l_implicitCS + ? util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS + : util::IComparable::Criterion::EQUIVALENT; + + const GeographicCRSNNPtr candidatesCRS[] = {GeographicCRS::EPSG_4326, + GeographicCRS::EPSG_4267, + GeographicCRS::EPSG_4269}; + for (const auto &crs : candidatesCRS) { + const bool nameEquivalent = metadata::Identifier::isEquivalentName( + thisName.c_str(), crs->nameStr().c_str()); + const bool nameEqual = thisName == crs->nameStr(); + const bool isEq = _isEquivalentTo(crs.get(), crsCriterion); + if (nameEquivalent && isEq && (!authorityFactory || nameEqual)) { + res.emplace_back(util::nn_static_pointer_cast(crs), + nameEqual ? 100 : 90); + return res; + } else if (nameEqual && !isEq && !authorityFactory) { + res.emplace_back(util::nn_static_pointer_cast(crs), + 25); + return res; + } else if (isEq && !authorityFactory) { + res.emplace_back(util::nn_static_pointer_cast(crs), + 70); + return res; + } + } + + std::string geodetic_crs_type; + if (isGeocentric()) { + geodetic_crs_type = "geocentric"; + } else { + auto geogCRS = dynamic_cast(this); + if (geogCRS) { + if (coordinateSystem()->axisList().size() == 2) { + geodetic_crs_type = "geographic 2D"; + } else { + geodetic_crs_type = "geographic 3D"; + } + } + } + + if (authorityFactory) { + + const auto &thisDatum(datum()); + + auto searchByDatum = [this, &authorityFactory, &res, &thisDatum, + &geodetic_crs_type, crsCriterion]() { + for (const auto &id : thisDatum->identifiers()) { + try { + auto tempRes = authorityFactory->createGeodeticCRSFromDatum( + *id->codeSpace(), id->code(), geodetic_crs_type); + for (const auto &crs : tempRes) { + if (_isEquivalentTo(crs.get(), crsCriterion)) { + res.emplace_back(crs, 70); + } + } + } catch (const std::exception &) { + } + } + }; + + const auto &thisEllipsoid(ellipsoid()); + auto searchByEllipsoid = [this, &authorityFactory, &res, &thisDatum, + &thisEllipsoid, &geodetic_crs_type, + l_implicitCS]() { + const auto ellipsoids = + thisEllipsoid->identifiers().empty() + ? authorityFactory->createEllipsoidFromExisting( + thisEllipsoid) + : std::list{thisEllipsoid}; + for (const auto &ellps : ellipsoids) { + for (const auto &id : ellps->identifiers()) { + try { + auto tempRes = + authorityFactory->createGeodeticCRSFromEllipsoid( + *id->codeSpace(), id->code(), + geodetic_crs_type); + for (const auto &crs : tempRes) { + const auto &crsDatum(crs->datum()); + if (crsDatum && + crsDatum->ellipsoid()->_isEquivalentTo( + ellps.get(), + util::IComparable::Criterion::EQUIVALENT) && + crsDatum->primeMeridian()->_isEquivalentTo( + thisDatum->primeMeridian().get(), + util::IComparable::Criterion::EQUIVALENT) && + (!l_implicitCS || + coordinateSystem()->_isEquivalentTo( + crs->coordinateSystem().get(), + util::IComparable::Criterion:: + EQUIVALENT))) { + res.emplace_back(crs, 60); + } + } + } catch (const std::exception &) { + } + } + } + }; + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + + if (unsignificantName) { + if (thisDatum) { + if (!thisDatum->identifiers().empty()) { + searchByDatum(); + } else { + searchByEllipsoid(); + } + } + } else if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createGeodeticCRS(id->code()); + bool match = _isEquivalentTo(crs.get(), crsCriterion); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else { + bool gotAbove25Pct = false; + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::GEODETIC_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + if (_isEquivalentTo(crs.get(), crsCriterion)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + const bool eqName = + metadata::Identifier::isEquivalentName( + thisName.c_str(), crs->nameStr().c_str()); + res.emplace_back(crsNN, eqName ? 90 : 70); + gotAbove25Pct = true; + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + if (!gotAbove25Pct && thisDatum) { + if (!thisDatum->identifiers().empty()) { + searchByDatum(); + } else { + searchByEllipsoid(); + } + } + } + + const auto &thisCS(coordinateSystem()); + // Sort results + res.sort([&thisName, &thisDatum, &thisCS](const Pair &a, + const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Then datum matching + const auto &aDatum(a.first->datum()); + const auto &bDatum(b.first->datum()); + if (thisDatum && aDatum && bDatum) { + const auto thisEquivADatum(thisDatum->_isEquivalentTo( + aDatum.get(), util::IComparable::Criterion::EQUIVALENT)); + const auto thisEquivBDatum(thisDatum->_isEquivalentTo( + bDatum.get(), util::IComparable::Criterion::EQUIVALENT)); + + if (thisEquivADatum && !thisEquivBDatum) { + return true; + } + if (!thisEquivADatum && thisEquivBDatum) { + return false; + } + } + + // Then coordinate system matching + const auto &aCS(a.first->coordinateSystem()); + const auto &bCS(b.first->coordinateSystem()); + const auto thisEquivACs(thisCS->_isEquivalentTo( + aCS.get(), util::IComparable::Criterion::EQUIVALENT)); + const auto thisEquivBCs(thisCS->_isEquivalentTo( + bCS.get(), util::IComparable::Criterion::EQUIVALENT)); + if (thisEquivACs && !thisEquivBCs) { + return true; + } + if (!thisEquivACs && thisEquivBCs) { + return false; + } + + // Then dimension of the coordinate system matching + const auto thisCSAxisListSize = thisCS->axisList().size(); + const auto aCSAxistListSize = aCS->axisList().size(); + const auto bCSAxistListSize = bCS->axisList().size(); + if (thisCSAxisListSize == aCSAxistListSize && + thisCSAxisListSize != bCSAxistListSize) { + return true; + } + if (thisCSAxisListSize != aCSAxistListSize && + thisCSAxisListSize == bCSAxistListSize) { + return false; + } + + if (aDatum && bDatum) { + // Favor the CRS whole ellipsoid names matches the ellipsoid + // name (WGS84...) + const bool aEllpsNameEqCRSName = + metadata::Identifier::isEquivalentName( + aDatum->ellipsoid()->nameStr().c_str(), + a.first->nameStr().c_str()); + const bool bEllpsNameEqCRSName = + metadata::Identifier::isEquivalentName( + bDatum->ellipsoid()->nameStr().c_str(), + b.first->nameStr().c_str()); + if (aEllpsNameEqCRSName && !bEllpsNameEqCRSName) { + return true; + } + if (bEllpsNameEqCRSName && !aEllpsNameEqCRSName) { + return false; + } + } + + // Arbitrary final sorting criterion + return aName < bName; + }); + + // If there are results with 90% confidence, only keep those + if (res.size() >= 2 && res.front().second == 90) { + std::list newRes; + for (const auto &pair : res) { + if (pair.second == 90) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list> +GeodeticCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair Pair; + std::list res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeographicCRS::Private { + cs::EllipsoidalCSNNPtr coordinateSystem_; + explicit Private(const cs::EllipsoidalCSNNPtr &csIn) + : coordinateSystem_(csIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +GeographicCRS::GeographicCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::EllipsoidalCSNNPtr &csIn) + : SingleCRS(datumIn, datumEnsembleIn, csIn), + GeodeticCRS(datumIn, + checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), csIn), + d(internal::make_unique(csIn)) {} + +// --------------------------------------------------------------------------- + +GeographicCRS::GeographicCRS(const GeographicCRS &other) + : SingleCRS(other), GeodeticCRS(other), + d(internal::make_unique(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeographicCRS::~GeographicCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr GeographicCRS::_shallowClone() const { + auto crs(GeographicCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::EllipsoidalCS associated with the CRS. + * + * @return a EllipsoidalCS. + */ +const cs::EllipsoidalCSNNPtr & +GeographicCRS::coordinateSystem() PROJ_CONST_DEFN { + return d->coordinateSystem_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeographicCRS from a datum::GeodeticReferenceFrameNNPtr + * and a + * cs::EllipsoidalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS. + * @param cs a EllipsoidalCS. + * @return new GeographicCRS. + */ +GeographicCRSNNPtr +GeographicCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFrameNNPtr &datum, + const cs::EllipsoidalCSNNPtr &cs) { + return create(properties, datum.as_nullable(), nullptr, cs); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeographicCRS from a datum::GeodeticReferenceFramePtr + * or + * datum::DatumEnsemble and a + * cs::EllipsoidalCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS, or nullptr + * @param datumEnsemble The datum ensemble of the CRS, or nullptr. + * @param cs a EllipsoidalCS. + * @return new GeographicCRS. + */ +GeographicCRSNNPtr +GeographicCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFramePtr &datum, + const datum::DatumEnsemblePtr &datumEnsemble, + const cs::EllipsoidalCSNNPtr &cs) { + GeographicCRSNNPtr crs( + GeographicCRS::nn_make_shared(datum, datumEnsemble, cs)); + crs->assignSelf(crs); + crs->setProperties(properties); + properties.getStringValue("EXTENSION_PROJ4", + crs->CRS::getPrivate()->extensionProj4_); + crs->CRS::getPrivate()->setImplicitCS(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +/** \brief Return whether the current GeographicCRS is the 2D part of the + * other 3D GeographicCRS. + */ +bool GeographicCRS::is2DPartOf3D(util::nn other) + PROJ_CONST_DEFN { + const auto &axis = d->coordinateSystem_->axisList(); + const auto &otherAxis = other->d->coordinateSystem_->axisList(); + if (!(axis.size() == 2 && otherAxis.size() == 3)) { + return false; + } + const auto &firstAxis = axis[0]; + const auto &secondAxis = axis[1]; + const auto &otherFirstAxis = otherAxis[0]; + const auto &otherSecondAxis = otherAxis[1]; + if (!(firstAxis->_isEquivalentTo(otherFirstAxis.get()) && + secondAxis->_isEquivalentTo(otherSecondAxis.get()))) { + return false; + } + const auto &thisDatum = GeodeticCRS::getPrivate()->datum_; + const auto &otherDatum = other->GeodeticCRS::getPrivate()->datum_; + if (thisDatum && otherDatum) { + return thisDatum->_isEquivalentTo(otherDatum.get()); + } + return false; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeographicCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherGeogCRS = dynamic_cast(other); + if (otherGeogCRS == nullptr) { + return false; + } + const auto standardCriterion = getStandardCriterion(criterion); + if (GeodeticCRS::_isEquivalentTo(other, standardCriterion)) { + return true; + } + if (criterion != + util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) { + return false; + } + const auto axisOrder = coordinateSystem()->axisOrder(); + if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || + axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) { + const auto &unit = coordinateSystem()->axisList()[0]->unit(); + return GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + nameStr()), + datum(), datumEnsemble(), + axisOrder == + cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH + ? cs::EllipsoidalCS::createLatitudeLongitude(unit) + : cs::EllipsoidalCS::createLongitudeLatitude(unit)) + ->GeodeticCRS::_isEquivalentTo(other, standardCriterion); + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4267() { + return create(createMapNameEPSGCode("NAD27", 4267), + datum::GeodeticReferenceFrame::EPSG_6267, + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4269() { + return create(createMapNameEPSGCode("NAD83", 4269), + datum::GeodeticReferenceFrame::EPSG_6269, + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4326() { + return create(createMapNameEPSGCode("WGS 84", 4326), + datum::GeodeticReferenceFrame::EPSG_6326, + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createOGC_CRS84() { + util::PropertyMap propertiesCRS; + propertiesCRS + .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::OGC) + .set(metadata::Identifier::CODE_KEY, "CRS84") + .set(common::IdentifiedObject::NAME_KEY, "WGS 84 (CRS84)"); + return create(propertiesCRS, datum::GeodeticReferenceFrame::EPSG_6326, + cs::EllipsoidalCS::createLongitudeLatitude( // Long Lat ! + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4979() { + return create( + createMapNameEPSGCode("WGS 84", 4979), + datum::GeodeticReferenceFrame::EPSG_6326, + cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( + common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4807() { + auto ellps(datum::Ellipsoid::createFlattenedSphere( + createMapNameEPSGCode("Clarke 1880 (IGN)", 7011), + common::Length(6378249.2), common::Scale(293.4660212936269))); + + auto cs(cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::GRAD)); + + auto datum(datum::GeodeticReferenceFrame::create( + createMapNameEPSGCode("Nouvelle Triangulation Francaise (Paris)", 6807), + ellps, util::optional(), datum::PrimeMeridian::PARIS)); + + return create(createMapNameEPSGCode("NTF (Paris)", 4807), datum, cs); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeographicCRS::addAngularUnitConvertAndAxisSwap( + io::PROJStringFormatter *formatter) const { + const auto &axisList = coordinateSystem()->axisList(); + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + + formatter->addStep("unitconvert"); + formatter->addParam("xy_in", "rad"); + if (axisList.size() == 3 && !formatter->omitZUnitConversion()) { + formatter->addParam("z_in", "m"); + } + { + const auto &unitHoriz = axisList[0]->unit(); + const auto projUnit = unitHoriz.exportToPROJString(); + if (projUnit.empty()) { + formatter->addParam("xy_out", unitHoriz.conversionToSI()); + } else { + formatter->addParam("xy_out", projUnit); + } + } + if (axisList.size() == 3 && !formatter->omitZUnitConversion()) { + const auto &unitZ = axisList[2]->unit(); + auto projVUnit = unitZ.exportToPROJString(); + if (projVUnit.empty()) { + formatter->addParam("z_out", unitZ.conversionToSI()); + } else { + formatter->addParam("z_out", projVUnit); + } + } + + const char *order[2] = {nullptr, nullptr}; + const char *one = "1"; + const char *two = "2"; + for (int i = 0; i < 2; i++) { + const auto &dir = axisList[i]->direction(); + if (&dir == &cs::AxisDirection::WEST) { + order[i] = "-1"; + } else if (&dir == &cs::AxisDirection::EAST) { + order[i] = one; + } else if (&dir == &cs::AxisDirection::SOUTH) { + order[i] = "-2"; + } else if (&dir == &cs::AxisDirection::NORTH) { + order[i] = two; + } + } + if (order[0] && order[1] && (order[0] != one || order[1] != two)) { + formatter->addStep("axisswap"); + char orderStr[10]; + strcpy(orderStr, order[0]); + strcat(orderStr, ","); + strcat(orderStr, order[1]); + formatter->addParam("order", orderStr); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeographicCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_; + if (!extensionProj4.empty()) { + formatter->ingestPROJString(extensionProj4); + formatter->addNoDefs(false); + return; + } + } + + if (!formatter->omitProjLongLatIfPossible() || + primeMeridian()->longitude().getSIValue() != 0.0 || + !formatter->getTOWGS84Parameters().empty() || + !formatter->getHDatumExtension().empty()) { + formatter->addStep("longlat"); + addDatumInfoToPROJString(formatter); + } + + addAngularUnitConvertAndAxisSwap(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct VerticalCRS::Private { + std::vector geoidModel{}; + std::vector velocityModel{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const datum::DatumEnsemblePtr & +checkEnsembleForVerticalCRS(const datum::VerticalReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &ensemble) { + const char *msg = "One of Datum or DatumEnsemble should be defined"; + if (datumIn) { + if (!ensemble) { + return ensemble; + } + msg = "Datum and DatumEnsemble should not be defined"; + } else if (ensemble) { + const auto &datums = ensemble->datums(); + assert(!datums.empty()); + auto grfFirst = + dynamic_cast(datums[0].get()); + if (grfFirst) { + return ensemble; + } + msg = "Ensemble should contain VerticalReferenceFrame"; + } + throw util::Exception(msg); +} +//! @endcond + +// --------------------------------------------------------------------------- + +VerticalCRS::VerticalCRS(const datum::VerticalReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::VerticalCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForVerticalCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +VerticalCRS::VerticalCRS(const VerticalCRS &other) + : SingleCRS(other), d(internal::make_unique(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalCRS::~VerticalCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr VerticalCRS::_shallowClone() const { + auto crs(VerticalCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::VerticalReferenceFrame associated with the CRS. + * + * @return a VerticalReferenceFrame. + */ +const datum::VerticalReferenceFramePtr VerticalCRS::datum() const { + return std::static_pointer_cast( + SingleCRS::getPrivate()->datum); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the geoid model associated with the CRS. + * + * Geoid height model or height correction model linked to a geoid-based + * vertical CRS. + * + * @return a geoid model. might be null + */ +const std::vector & +VerticalCRS::geoidModel() PROJ_CONST_DEFN { + return d->geoidModel; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the velocity model associated with the CRS. + * + * @return a velocity model. might be null. + */ +const std::vector & +VerticalCRS::velocityModel() PROJ_CONST_DEFN { + return d->velocityModel; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::VerticalCS associated with the CRS. + * + * @return a VerticalCS. + */ +const cs::VerticalCSNNPtr VerticalCRS::coordinateSystem() const { + return util::nn_static_pointer_cast( + SingleCRS::getPrivate()->coordinateSystem); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void VerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::VERTCRS + : io::WKTConstants::VERT_CS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + exportDatumOrDatumEnsembleToWkt(formatter); + const auto &cs = SingleCRS::getPrivate()->coordinateSystem; + const auto &axisList = cs->axisList(); + if (!isWKT2) { + axisList[0]->unit()._exportToWKT(formatter); + } + + const auto oldAxisOutputRule = formatter->outputAxis(); + if (oldAxisOutputRule == + io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE) { + formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES); + } + cs->_exportToWKT(formatter); + formatter->setOutputAxis(oldAxisOutputRule); + + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void VerticalCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + auto geoidgrids = formatter->getVDatumExtension(); + if (!geoidgrids.empty()) { + formatter->addParam("geoidgrids", geoidgrids); + } + + auto &axisList = coordinateSystem()->axisList(); + if (!axisList.empty()) { + auto projUnit = axisList[0]->unit().exportToPROJString(); + if (projUnit.empty()) { + formatter->addParam("vto_meter", + axisList[0]->unit().conversionToSI()); + } else { + formatter->addParam("vunits", projUnit); + } + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void VerticalCRS::addLinearUnitConvert( + io::PROJStringFormatter *formatter) const { + auto &axisList = coordinateSystem()->axisList(); + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + if (!axisList.empty()) { + auto projUnit = axisList[0]->unit().exportToPROJString(); + if (axisList[0]->unit().conversionToSI() != 1.0) { + formatter->addStep("unitconvert"); + formatter->addParam("z_in", "m"); + auto projVUnit = axisList[0]->unit().exportToPROJString(); + if (projVUnit.empty()) { + formatter->addParam("z_out", + axisList[0]->unit().conversionToSI()); + } else { + formatter->addParam("z_out", projVUnit); + } + } + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalCRS from a datum::VerticalReferenceFrame and a + * cs::VerticalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn The datum of the CRS. + * @param csIn a VerticalCS. + * @return new VerticalCRS. + */ +VerticalCRSNNPtr +VerticalCRS::create(const util::PropertyMap &properties, + const datum::VerticalReferenceFrameNNPtr &datumIn, + const cs::VerticalCSNNPtr &csIn) { + return create(properties, datumIn.as_nullable(), nullptr, csIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalCRS from a datum::VerticalReferenceFrame or + * datum::DatumEnsemble and a cs::VerticalCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn The datum of the CRS, or nullptr + * @param datumEnsembleIn The datum ensemble of the CRS, or nullptr. + * @param csIn a VerticalCS. + * @return new VerticalCRS. + */ +VerticalCRSNNPtr +VerticalCRS::create(const util::PropertyMap &properties, + const datum::VerticalReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::VerticalCSNNPtr &csIn) { + auto crs(VerticalCRS::nn_make_shared(datumIn, datumEnsembleIn, + csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool VerticalCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherVertCRS = dynamic_cast(other); + // TODO test geoidModel and velocityModel + return otherVertCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are looked in the database when + * authorityFactory is not null. + * + * The method returns a list of matching reference CRS, and the percentage + * (0-100) of confidence in the match. + * 100% means that the name of the reference entry + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent datum and coordinate system), + * but the names do not match at all. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * @param authorityFactory Authority factory (if null, will return an empty + * list) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list> +VerticalCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair Pair; + std::list res; + + const auto &thisName(nameStr()); + + if (authorityFactory) { + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createVerticalCRS(id->code()); + bool match = _isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else if (!unsignificantName) { + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::VERTICAL_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + res.emplace_back(crsNN, 90); + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + } + + // Sort results + res.sort([&thisName](const Pair &a, const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Arbitrary final sorting criterion + return aName < bName; + }); + + // Keep only results of the highest confidence + if (res.size() >= 2) { + const auto highestConfidence = res.front().second; + std::list newRes; + for (const auto &pair : res) { + if (pair.second == highestConfidence) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list> +VerticalCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair Pair; + std::list res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedCRS::Private { + SingleCRSNNPtr baseCRS_; + operation::ConversionNNPtr derivingConversion_; + + Private(const SingleCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn) + : baseCRS_(baseCRSIn), derivingConversion_(derivingConversionIn) {} + + // For the conversion make a _shallowClone(), so that we can later set + // its targetCRS to this. + Private(const Private &other) + : baseCRS_(other.baseCRS_), + derivingConversion_(other.derivingConversion_->shallowClone()) {} +}; + +//! @endcond + +// --------------------------------------------------------------------------- + +// DerivedCRS is an abstract class, that virtually inherits from SingleCRS +// Consequently the base constructor in SingleCRS will never be called by +// that constructor. clang -Wabstract-vbase-init and VC++ underline this, but +// other +// compilers will complain if we don't call the base constructor. + +DerivedCRS::DerivedCRS(const SingleCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CoordinateSystemNNPtr & +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + cs +#endif + ) + : +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), cs), +#endif + d(internal::make_unique(baseCRSIn, derivingConversionIn)) { +} + +// --------------------------------------------------------------------------- + +DerivedCRS::DerivedCRS(const DerivedCRS &other) + : +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + SingleCRS(other), +#endif + d(internal::make_unique(*other.d)) { +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedCRS::~DerivedCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS of a DerivedCRS. + * + * @return the base CRS. + */ +const SingleCRSNNPtr &DerivedCRS::baseCRS() PROJ_CONST_DEFN { + return d->baseCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the deriving conversion from the base CRS to this CRS. + * + * @return the deriving conversion. + */ +const operation::ConversionNNPtr DerivedCRS::derivingConversion() const { + return d->derivingConversion_->shallowClone(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const operation::ConversionNNPtr & +DerivedCRS::derivingConversionRef() PROJ_CONST_DEFN { + return d->derivingConversion_; +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool DerivedCRS::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast(other); + const auto standardCriterion = getStandardCriterion(criterion); + if (otherDerivedCRS == nullptr || + !SingleCRS::baseIsEquivalentTo(other, standardCriterion)) { + return false; + } + return d->baseCRS_->_isEquivalentTo(otherDerivedCRS->d->baseCRS_.get(), + criterion) && + d->derivingConversion_->_isEquivalentTo( + otherDerivedCRS->d->derivingConversion_.get(), + standardCriterion); +} + +// --------------------------------------------------------------------------- + +void DerivedCRS::setDerivingConversionCRS() { + derivingConversionRef()->setWeakSourceTargetCRS( + baseCRS().as_nullable(), + std::static_pointer_cast(shared_from_this().as_nullable())); +} + +// --------------------------------------------------------------------------- + +void DerivedCRS::baseExportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + d->derivingConversion_->_exportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +void DerivedCRS::baseExportToWKT(io::WKTFormatter *&formatter, + const std::string &keyword, + const std::string &baseKeyword) const { + formatter->startNode(keyword, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + const auto &l_baseCRS = d->baseCRS_; + formatter->startNode(baseKeyword, !l_baseCRS->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); + formatter->endNode(); + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ProjectedCRS::Private { + GeodeticCRSNNPtr baseCRS_; + cs::CartesianCSNNPtr cs_; + Private(const GeodeticCRSNNPtr &baseCRSIn, const cs::CartesianCSNNPtr &csIn) + : baseCRS_(baseCRSIn), cs_(csIn) {} + + inline const GeodeticCRSNNPtr &baseCRS() const { return baseCRS_; } + + inline const cs::CartesianCSNNPtr &coordinateSystem() const { return cs_; } +}; +//! @endcond + +// --------------------------------------------------------------------------- + +ProjectedCRS::ProjectedCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), + d(internal::make_unique(baseCRSIn, csIn)) {} + +// --------------------------------------------------------------------------- + +ProjectedCRS::ProjectedCRS(const ProjectedCRS &other) + : SingleCRS(other), DerivedCRS(other), + d(internal::make_unique(other.baseCRS(), + other.coordinateSystem())) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ProjectedCRS::~ProjectedCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr ProjectedCRS::_shallowClone() const { + auto crs(ProjectedCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a GeodeticCRS, which is generally a + * GeographicCRS) of the ProjectedCRS. + * + * @return the base CRS. + */ +const GeodeticCRSNNPtr &ProjectedCRS::baseCRS() PROJ_CONST_DEFN { + return d->baseCRS(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::CartesianCS associated with the CRS. + * + * @return a CartesianCS + */ +const cs::CartesianCSNNPtr &ProjectedCRS::coordinateSystem() PROJ_CONST_DEFN { + return d->coordinateSystem(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + + const auto &l_identifiers = identifiers(); + // Try to perfectly round-trip ESRI projectedCRS if the current object + // perfectly matches the database definition + const auto &dbContext = formatter->databaseContext(); + + auto l_name = nameStr(); + std::string l_alias; + if (formatter->useESRIDialect() && dbContext) { + l_alias = dbContext->getAliasFromOfficialName(l_name, "projected_crs", + "ESRI"); + } + + if (!isWKT2 && formatter->useESRIDialect() && !l_identifiers.empty() && + *(l_identifiers[0]->codeSpace()) == "ESRI" && dbContext) { + try { + const auto definition = dbContext->getTextDefinition( + "projected_crs", "ESRI", l_identifiers[0]->code()); + if (starts_with(definition, "PROJCS")) { + auto crsFromFromDef = io::WKTParser() + .attachDatabaseContext(dbContext) + .createFromWKT(definition); + if (_isEquivalentTo( + dynamic_cast(crsFromFromDef.get()), + util::IComparable::Criterion::EQUIVALENT)) { + formatter->ingestWKTNode( + io::WKTNode::createFrom(definition)); + return; + } + } + } catch (const std::exception &) { + } + } else if (!isWKT2 && formatter->useESRIDialect() && !l_alias.empty()) { + try { + auto res = + io::AuthorityFactory::create(NN_NO_CHECK(dbContext), "ESRI") + ->createObjectsFromName( + l_alias, + {io::AuthorityFactory::ObjectType::PROJECTED_CRS}, + false); + if (res.size() == 1) { + const auto definition = dbContext->getTextDefinition( + "projected_crs", "ESRI", + res.front()->identifiers()[0]->code()); + if (starts_with(definition, "PROJCS")) { + if (_isEquivalentTo( + dynamic_cast(res.front().get()), + util::IComparable::Criterion::EQUIVALENT)) { + formatter->ingestWKTNode( + io::WKTNode::createFrom(definition)); + return; + } + } + } + } catch (const std::exception &) { + } + } + + const auto &l_coordinateSystem = d->coordinateSystem(); + const auto &axisList = l_coordinateSystem->axisList(); + if (axisList.size() == 3 && !(isWKT2 && formatter->use2018Keywords())) { + io::FormattingException::Throw( + "Projected 3D CRS can only be exported since WKT2:2018"); + } + + const auto exportAxis = [&l_coordinateSystem, &axisList, &formatter]() { + const auto oldAxisOutputRule = formatter->outputAxis(); + if (oldAxisOutputRule == + io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE) { + if (&axisList[0]->direction() == &cs::AxisDirection::EAST && + &axisList[1]->direction() == &cs::AxisDirection::NORTH) { + formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES); + } + } + l_coordinateSystem->_exportToWKT(formatter); + formatter->setOutputAxis(oldAxisOutputRule); + }; + + if (!isWKT2 && !formatter->useESRIDialect() && + starts_with(nameStr(), "Popular Visualisation CRS / Mercator")) { + formatter->startNode(io::WKTConstants::PROJCS, !l_identifiers.empty()); + formatter->addQuotedString(nameStr()); + formatter->setTOWGS84Parameters({0, 0, 0, 0, 0, 0, 0}); + baseCRS()->_exportToWKT(formatter); + formatter->setTOWGS84Parameters({}); + + formatter->startNode(io::WKTConstants::PROJECTION, false); + formatter->addQuotedString("Mercator_1SP"); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("central_meridian"); + formatter->add(0.0); + 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"); + formatter->add(0.0); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("false_northing"); + formatter->add(0.0); + formatter->endNode(); + + axisList[0]->unit()._exportToWKT(formatter); + exportAxis(); + derivingConversionRef()->addWKTExtensionNode(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); + return; + } + + formatter->startNode(isWKT2 ? io::WKTConstants::PROJCRS + : io::WKTConstants::PROJCS, + !l_identifiers.empty()); + + if (formatter->useESRIDialect()) { + if (l_alias.empty()) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + } else { + l_name = l_alias; + } + } + if (!isWKT2 && !formatter->useESRIDialect() && isDeprecated()) { + l_name += " (deprecated)"; + } + formatter->addQuotedString(l_name); + + const auto &l_baseCRS = d->baseCRS(); + const auto &geodeticCRSAxisList = l_baseCRS->coordinateSystem()->axisList(); + + if (isWKT2) { + formatter->startNode( + (formatter->use2018Keywords() && + dynamic_cast(l_baseCRS.get())) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !l_baseCRS->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); + // insert ellipsoidal cs unit when the units of the map + // projection angular parameters are not explicitly given within those + // parameters. See + // http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#61 + if (formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis()) { + geodeticCRSAxisList[0]->unit()._exportToWKT(formatter); + } + l_baseCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + } else { + const auto oldAxisOutputRule = formatter->outputAxis(); + formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::NO); + l_baseCRS->_exportToWKT(formatter); + formatter->setOutputAxis(oldAxisOutputRule); + } + + formatter->pushAxisLinearUnit( + common::UnitOfMeasure::create(axisList[0]->unit())); + + formatter->pushAxisAngularUnit( + common::UnitOfMeasure::create(geodeticCRSAxisList[0]->unit())); + + derivingConversionRef()->_exportToWKT(formatter); + + formatter->popAxisAngularUnit(); + + formatter->popAxisLinearUnit(); + + if (!isWKT2) { + axisList[0]->unit()._exportToWKT(formatter); + } + + exportAxis(); + + if (!isWKT2 && !formatter->useESRIDialect()) { + const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_; + if (!extensionProj4.empty()) { + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4"); + formatter->addQuotedString(extensionProj4); + formatter->endNode(); + } else { + derivingConversionRef()->addWKTExtensionNode(formatter); + } + } + + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); + return; +} +//! @endcond + +// --------------------------------------------------------------------------- + +void ProjectedCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_; + if (!extensionProj4.empty()) { + formatter->ingestPROJString(extensionProj4); + formatter->addNoDefs(false); + return; + } + } + + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS from a base CRS, a deriving + * operation::Conversion + * and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn The base CRS, a GeodeticCRS that is generally a + * GeographicCRS. + * @param derivingConversionIn The deriving operation::Conversion (typically + * using a map + * projection method) + * @param csIn The coordniate system. + * @return new ProjectedCRS. + */ +ProjectedCRSNNPtr +ProjectedCRS::create(const util::PropertyMap &properties, + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) { + auto crs = ProjectedCRS::nn_make_shared( + baseCRSIn, derivingConversionIn, csIn); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + properties.getStringValue("EXTENSION_PROJ4", + crs->CRS::getPrivate()->extensionProj4_); + crs->CRS::getPrivate()->setImplicitCS(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +bool ProjectedCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherProjCRS = dynamic_cast(other); + return otherProjCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ProjectedCRSNNPtr +ProjectedCRS::alterParametersLinearUnit(const common::UnitOfMeasure &unit, + bool convertToNewUnit) const { + return create(createPropertyMap(this), baseCRS(), + derivingConversionRef()->alterParametersLinearUnit( + unit, convertToNewUnit), + coordinateSystem()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ProjectedCRS::addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter, + bool axisSpecFound) const { + const auto &axisList = d->coordinateSystem()->axisList(); + const auto &unit = axisList[0]->unit(); + if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE, + util::IComparable::Criterion::EQUIVALENT)) { + auto projUnit = unit.exportToPROJString(); + const double toSI = unit.conversionToSI(); + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + formatter->addStep("unitconvert"); + formatter->addParam("xy_in", "m"); + formatter->addParam("z_in", "m"); + if (projUnit.empty()) { + formatter->addParam("xy_out", toSI); + formatter->addParam("z_out", toSI); + } else { + formatter->addParam("xy_out", projUnit); + formatter->addParam("z_out", projUnit); + } + } else { + if (projUnit.empty()) { + formatter->addParam("to_meter", toSI); + } else { + formatter->addParam("units", projUnit); + } + } + } else if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + // could come from the hardcoded def of webmerc + if (!formatter->hasParam("units")) { + formatter->addParam("units", "m"); + } + } + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5 && + !axisSpecFound) { + const auto &dir0 = axisList[0]->direction(); + const auto &dir1 = axisList[1]->direction(); + if (!(&dir0 == &cs::AxisDirection::EAST && + &dir1 == &cs::AxisDirection::NORTH) && + // For polar projections, that have south+south direction, + // we don't want to mess with axes. + dir0 != dir1) { + + const char *order[2] = {nullptr, nullptr}; + for (int i = 0; i < 2; i++) { + const auto &dir = axisList[i]->direction(); + if (&dir == &cs::AxisDirection::WEST) + order[i] = "-1"; + else if (&dir == &cs::AxisDirection::EAST) + order[i] = "1"; + else if (&dir == &cs::AxisDirection::SOUTH) + order[i] = "-2"; + else if (&dir == &cs::AxisDirection::NORTH) + order[i] = "2"; + } + + if (order[0] && order[1]) { + formatter->addStep("axisswap"); + char orderStr[10]; + strcpy(orderStr, order[0]); + strcat(orderStr, ","); + strcat(orderStr, order[1]); + formatter->addParam("order", orderStr); + } + } else { + const auto &name0 = axisList[0]->nameStr(); + const auto &name1 = axisList[1]->nameStr(); + const bool northingEasting = ci_starts_with(name0, "northing") && + ci_starts_with(name1, "easting"); + // case of EPSG:32661 ["WGS 84 / UPS North (N,E)]" + // case of EPSG:32761 ["WGS 84 / UPS South (N,E)]" + if (((&dir0 == &cs::AxisDirection::SOUTH && + &dir1 == &cs::AxisDirection::SOUTH) || + (&dir0 == &cs::AxisDirection::NORTH && + &dir1 == &cs::AxisDirection::NORTH)) && + northingEasting) { + formatter->addStep("axisswap"); + formatter->addParam("order", "2,1"); + } + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are either hard-coded, or looked in the database when + * authorityFactory is not null. + * + * The method returns a list of matching reference CRS, and the percentage + * (0-100) of confidence in the match. The list is sorted by decreasing + * confidence. + * + * 100% means that the name of the reference entry + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent base CRS, conversion and + * coordinate system), but the names do not match at all. + * 50% means that CRS have similarity (equivalent base CRS and conversion), + * but the coordinate system do not match (e.g. different axis ordering or + * axis unit). + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * For the purpose of this function, equivalence is tested with the + * util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, that is + * to say that the axis order of the base GeographicCRS is ignored. + * + * @param authorityFactory Authority factory (or null, but degraded + * functionality) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list> +ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair Pair; + std::list res; + + const auto &thisName(nameStr()); + + std::list> baseRes; + const auto &l_baseCRS(baseCRS()); + auto geogCRS = dynamic_cast(l_baseCRS.get()); + if (geogCRS && + geogCRS->coordinateSystem()->axisOrder() == + cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH) { + baseRes = + GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + geogCRS->nameStr()), + geogCRS->datum(), geogCRS->datumEnsemble(), + cs::EllipsoidalCS::createLatitudeLongitude( + geogCRS->coordinateSystem()->axisList()[0]->unit())) + ->identify(authorityFactory); + } else { + baseRes = l_baseCRS->identify(authorityFactory); + } + + int zone = 0; + bool north = false; + + auto computeConfidence = [&thisName](const std::string &crsName) { + return crsName == thisName ? 100 + : metadata::Identifier::isEquivalentName( + crsName.c_str(), thisName.c_str()) + ? 90 + : 70; + }; + auto computeUTMCRSName = [](const char *base, int l_zone, bool l_north) { + return base + toString(l_zone) + (l_north ? "N" : "S"); + }; + + const auto &conv = derivingConversionRef(); + const auto &cs = coordinateSystem(); + if (baseRes.size() == 1 && baseRes.front().second >= 70 && + conv->isUTM(zone, north) && + cs->_isEquivalentTo( + cs::CartesianCS::createEastingNorthing(common::UnitOfMeasure::METRE) + .get())) { + if (baseRes.front().first->_isEquivalentTo( + GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + std::string crsName( + computeUTMCRSName("WGS 84 / UTM zone ", zone, north)); + res.emplace_back( + ProjectedCRS::create( + createMapNameEPSGCode(crsName.c_str(), + (north ? 32600 : 32700) + zone), + GeographicCRS::EPSG_4326, conv->identify(), cs), + computeConfidence(crsName)); + return res; + } else if (((zone >= 1 && zone <= 22) || zone == 59 || zone == 60) && + north && + baseRes.front().first->_isEquivalentTo( + GeographicCRS::EPSG_4267.get(), + util::IComparable::Criterion::EQUIVALENT)) { + std::string crsName( + computeUTMCRSName("NAD27 / UTM zone ", zone, north)); + res.emplace_back( + ProjectedCRS::create( + createMapNameEPSGCode(crsName.c_str(), + (zone >= 59) ? 3370 + zone - 59 + : 26700 + zone), + GeographicCRS::EPSG_4267, conv->identify(), cs), + computeConfidence(crsName)); + return res; + } else if (((zone >= 1 && zone <= 23) || zone == 59 || zone == 60) && + north && + baseRes.front().first->_isEquivalentTo( + GeographicCRS::EPSG_4269.get(), + util::IComparable::Criterion::EQUIVALENT)) { + std::string crsName( + computeUTMCRSName("NAD83 / UTM zone ", zone, north)); + res.emplace_back( + ProjectedCRS::create( + createMapNameEPSGCode(crsName.c_str(), + (zone >= 59) ? 3372 + zone - 59 + : 26900 + zone), + GeographicCRS::EPSG_4269, conv->identify(), cs), + computeConfidence(crsName)); + return res; + } + } + + if (authorityFactory) { + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + bool foundEquivalentName = false; + + if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createProjectedCRS(id->code()); + bool match = _isEquivalentTo( + crs.get(), util::IComparable::Criterion:: + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else if (!unsignificantName) { + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::PROJECTED_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + const bool eqName = metadata::Identifier::isEquivalentName( + thisName.c_str(), crs->nameStr().c_str()); + foundEquivalentName |= eqName; + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion:: + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + res.emplace_back(crsNN, eqName ? 90 : 70); + } else if (crs->nameStr() == thisName && + CRS::getPrivate()->implicitCS_ && + l_baseCRS->_isEquivalentTo( + crs->baseCRS().get(), + util::IComparable::Criterion:: + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) && + derivingConversionRef()->_isEquivalentTo( + crs->derivingConversionRef().get(), + util::IComparable::Criterion::EQUIVALENT) && + objects.size() == 1) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + } + + const auto lambdaSort = [&thisName](const Pair &a, const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Arbitrary final sorting criterion + return aName < bName; + }; + + // Sort results + res.sort(lambdaSort); + + if (identifiers().empty() && !foundEquivalentName && + (res.empty() || res.front().second < 50)) { + std::set> alreadyKnown; + for (const auto &pair : res) { + const auto &ids = pair.first->identifiers(); + assert(!ids.empty()); + alreadyKnown.insert(std::pair( + *(ids[0]->codeSpace()), ids[0]->code())); + } + + auto self = NN_NO_CHECK(std::dynamic_pointer_cast( + shared_from_this().as_nullable())); + auto candidates = + authorityFactory->createProjectedCRSFromExisting(self); + const auto &ellipsoid = l_baseCRS->ellipsoid(); + for (const auto &crs : candidates) { + const auto &ids = crs->identifiers(); + assert(!ids.empty()); + if (alreadyKnown.find(std::pair( + *(ids[0]->codeSpace()), ids[0]->code())) != + alreadyKnown.end()) { + continue; + } + + if (_isEquivalentTo(crs.get(), + util::IComparable::Criterion:: + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)) { + res.emplace_back(crs, unsignificantName ? 90 : 70); + } else if (ellipsoid->_isEquivalentTo( + crs->baseCRS()->ellipsoid().get(), + util::IComparable::Criterion::EQUIVALENT) && + derivingConversionRef()->_isEquivalentTo( + crs->derivingConversionRef().get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (coordinateSystem()->_isEquivalentTo( + crs->coordinateSystem().get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(crs, 70); + } else { + res.emplace_back(crs, 50); + } + } else { + res.emplace_back(crs, 25); + } + } + + res.sort(lambdaSort); + } + + // Keep only results of the highest confidence + if (res.size() >= 2) { + const auto highestConfidence = res.front().second; + std::list newRes; + for (const auto &pair : res) { + if (pair.second == highestConfidence) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list> +ProjectedCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair Pair; + std::list res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CompoundCRS::Private { + std::vector components_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +CompoundCRS::CompoundCRS(const std::vector &components) + : CRS(), d(internal::make_unique()) { + d->components_ = components; +} + +// --------------------------------------------------------------------------- + +CompoundCRS::CompoundCRS(const CompoundCRS &other) + : CRS(other), d(internal::make_unique(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CompoundCRS::~CompoundCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr CompoundCRS::_shallowClone() const { + auto crs(CompoundCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the components of a CompoundCRS. + * + * @return the components. + */ +const std::vector & +CompoundCRS::componentReferenceSystems() PROJ_CONST_DEFN { + return d->components_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CompoundCRS from a vector of CRS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param components the component CRS of the CompoundCRS. + * @return new CompoundCRS. + */ +CompoundCRSNNPtr CompoundCRS::create(const util::PropertyMap &properties, + const std::vector &components) { + auto compoundCRS(CompoundCRS::nn_make_shared(components)); + compoundCRS->assignSelf(compoundCRS); + compoundCRS->setProperties(properties); + if (!properties.get(common::IdentifiedObject::NAME_KEY)) { + std::string name; + for (const auto &crs : components) { + if (!name.empty()) { + name += " + "; + } + const auto &l_name = crs->nameStr(); + if (!l_name.empty()) { + name += l_name; + } else { + name += "unnamed"; + } + } + util::PropertyMap propertyName; + propertyName.set(common::IdentifiedObject::NAME_KEY, name); + compoundCRS->setProperties(propertyName); + } + + return compoundCRS; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void CompoundCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::COMPOUNDCRS + : io::WKTConstants::COMPD_CS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + for (const auto &crs : componentReferenceSystems()) { + crs->_exportToWKT(formatter); + } + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void CompoundCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + for (const auto &crs : componentReferenceSystems()) { + auto crs_exportable = + dynamic_cast(crs.get()); + if (crs_exportable) { + crs_exportable->_exportToPROJString(formatter); + } + } +} + +// --------------------------------------------------------------------------- + +bool CompoundCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherCompoundCRS = dynamic_cast(other); + if (otherCompoundCRS == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion))) { + return false; + } + const auto &components = componentReferenceSystems(); + const auto &otherComponents = otherCompoundCRS->componentReferenceSystems(); + if (components.size() != otherComponents.size()) { + return false; + } + for (size_t i = 0; i < components.size(); i++) { + if (!components[i]->_isEquivalentTo(otherComponents[i].get(), + criterion)) { + return false; + } + } + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are looked in the database when + * authorityFactory is not null. + * + * The method returns a list of matching reference CRS, and the percentage + * (0-100) of confidence in the match. The list is sorted by decreasing + * confidence. + * + * 100% means that the name of the reference entry + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent horizontal and vertical CRS), + * but the names do not match at all. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * @param authorityFactory Authority factory (if null, will return an empty + * list) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list> +CompoundCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair Pair; + std::list res; + + const auto &thisName(nameStr()); + + if (authorityFactory) { + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + bool foundEquivalentName = false; + + if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createCompoundCRS(id->code()); + bool match = _isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else if (!unsignificantName) { + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::COMPOUND_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + const bool eqName = metadata::Identifier::isEquivalentName( + thisName.c_str(), crs->nameStr().c_str()); + foundEquivalentName |= eqName; + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + res.emplace_back(crsNN, eqName ? 90 : 70); + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + } + + const auto lambdaSort = [&thisName](const Pair &a, const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Arbitrary final sorting criterion + return aName < bName; + }; + + // Sort results + res.sort(lambdaSort); + + if (identifiers().empty() && !foundEquivalentName && + (res.empty() || res.front().second < 50)) { + std::set> alreadyKnown; + for (const auto &pair : res) { + const auto &ids = pair.first->identifiers(); + assert(!ids.empty()); + alreadyKnown.insert(std::pair( + *(ids[0]->codeSpace()), ids[0]->code())); + } + + auto self = NN_NO_CHECK(std::dynamic_pointer_cast( + shared_from_this().as_nullable())); + auto candidates = + authorityFactory->createCompoundCRSFromExisting(self); + for (const auto &crs : candidates) { + const auto &ids = crs->identifiers(); + assert(!ids.empty()); + if (alreadyKnown.find(std::pair( + *(ids[0]->codeSpace()), ids[0]->code())) != + alreadyKnown.end()) { + continue; + } + + if (_isEquivalentTo(crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(crs, unsignificantName ? 90 : 70); + } else { + res.emplace_back(crs, 25); + } + } + + res.sort(lambdaSort); + } + + // Keep only results of the highest confidence + if (res.size() >= 2) { + const auto highestConfidence = res.front().second; + std::list newRes; + for (const auto &pair : res) { + if (pair.second == highestConfidence) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list> +CompoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair Pair; + std::list res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct PROJ_INTERNAL BoundCRS::Private { + CRSNNPtr baseCRS_; + CRSNNPtr hubCRS_; + operation::TransformationNNPtr transformation_; + + Private(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn); + + inline const CRSNNPtr &baseCRS() const { return baseCRS_; } + inline const CRSNNPtr &hubCRS() const { return hubCRS_; } + inline const operation::TransformationNNPtr &transformation() const { + return transformation_; + } +}; + +BoundCRS::Private::Private( + const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn) + : baseCRS_(baseCRSIn), hubCRS_(hubCRSIn), + transformation_(transformationIn) {} + +//! @endcond + +// --------------------------------------------------------------------------- + +BoundCRS::BoundCRS(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn) + : d(internal::make_unique(baseCRSIn, hubCRSIn, transformationIn)) { +} + +// --------------------------------------------------------------------------- + +BoundCRS::BoundCRS(const BoundCRS &other) + : CRS(other), + d(internal::make_unique(other.d->baseCRS(), other.d->hubCRS(), + other.d->transformation())) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +BoundCRS::~BoundCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +BoundCRSNNPtr BoundCRS::shallowCloneAsBoundCRS() const { + auto crs(BoundCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +CRSNNPtr BoundCRS::_shallowClone() const { return shallowCloneAsBoundCRS(); } + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS. + * + * This is the CRS into which coordinates of the BoundCRS are expressed. + * + * @return the base CRS. + */ +const CRSNNPtr &BoundCRS::baseCRS() PROJ_CONST_DEFN { return d->baseCRS_; } + +// --------------------------------------------------------------------------- + +// The only legit caller is BoundCRS::baseCRSWithCanonicalBoundCRS() +void CRS::setCanonicalBoundCRS(const BoundCRSNNPtr &boundCRS) { + + d->canonicalBoundCRS_ = boundCRS; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a shallow clone of the base CRS that points to a + * shallow clone of this BoundCRS. + * + * The base CRS is the CRS into which coordinates of the BoundCRS are expressed. + * + * The returned CRS will actually be a shallow clone of the actual base CRS, + * with the extra property that CRS::canonicalBoundCRS() will point to a + * shallow clone of this BoundCRS. Use this only if you want to work with + * the base CRS object rather than the BoundCRS, but wanting to be able to + * retrieve the BoundCRS later. + * + * @return the base CRS. + */ +CRSNNPtr BoundCRS::baseCRSWithCanonicalBoundCRS() const { + auto baseCRSClone = baseCRS()->_shallowClone(); + baseCRSClone->setCanonicalBoundCRS(shallowCloneAsBoundCRS()); + return baseCRSClone; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the target / hub CRS. + * + * @return the hub CRS. + */ +const CRSNNPtr &BoundCRS::hubCRS() PROJ_CONST_DEFN { return d->hubCRS_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the transformation to the hub RS. + * + * @return transformation. + */ +const operation::TransformationNNPtr & +BoundCRS::transformation() PROJ_CONST_DEFN { + return d->transformation_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a BoundCRS from a base CRS, a hub CRS and a + * transformation. + * + * @param baseCRSIn base CRS. + * @param hubCRSIn hub CRS. + * @param transformationIn transformation from base CRS to hub CRS. + * @return new BoundCRS. + */ +BoundCRSNNPtr +BoundCRS::create(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn) { + auto crs = BoundCRS::nn_make_shared(baseCRSIn, hubCRSIn, + transformationIn); + crs->assignSelf(crs); + const auto &l_name = baseCRSIn->nameStr(); + if (!l_name.empty()) { + crs->setProperties(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, l_name)); + } + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a BoundCRS from a base CRS and TOWGS84 parameters + * + * @param baseCRSIn base CRS. + * @param TOWGS84Parameters a vector of 3 or 7 double values representing WKT1 + * TOWGS84 parameter. + * @return new BoundCRS. + */ +BoundCRSNNPtr +BoundCRS::createFromTOWGS84(const CRSNNPtr &baseCRSIn, + const std::vector &TOWGS84Parameters) { + + auto geodCRS = baseCRSIn->extractGeodeticCRS(); + auto targetCRS = + geodCRS.get() == nullptr || + dynamic_cast(geodCRS.get()) + ? util::nn_static_pointer_cast( + crs::GeographicCRS::EPSG_4326) + : util::nn_static_pointer_cast( + crs::GeodeticCRS::EPSG_4978); + return create( + baseCRSIn, targetCRS, + operation::Transformation::createTOWGS84(baseCRSIn, TOWGS84Parameters)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a BoundCRS from a base CRS and nadgrids parameters + * + * @param baseCRSIn base CRS. + * @param filename Horizontal grid filename + * @return new BoundCRS. + */ +BoundCRSNNPtr BoundCRS::createFromNadgrids(const CRSNNPtr &baseCRSIn, + const std::string &filename) { + const CRSPtr sourceGeographicCRS = baseCRSIn->extractGeographicCRS(); + auto transformationSourceCRS = + sourceGeographicCRS ? sourceGeographicCRS : baseCRSIn.as_nullable(); + std::string transformationName = transformationSourceCRS->nameStr(); + transformationName += " to WGS84"; + + return create( + baseCRSIn, GeographicCRS::EPSG_4326, + operation::Transformation::createNTv2( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + transformationName), + baseCRSIn, GeographicCRS::EPSG_4326, filename, + std::vector())); +} + +// --------------------------------------------------------------------------- + +bool BoundCRS::isTOWGS84Compatible() const { + return dynamic_cast(d->hubCRS().get()) != nullptr && + ci_equal(d->hubCRS()->nameStr(), "WGS 84"); +} + +// --------------------------------------------------------------------------- + +std::string BoundCRS::getHDatumPROJ4GRIDS() const { + if (ci_equal(d->hubCRS()->nameStr(), "WGS 84")) { + return d->transformation()->getNTv2Filename(); + } + return std::string(); +} + +// --------------------------------------------------------------------------- + +std::string BoundCRS::getVDatumPROJ4GRIDS() const { + if (dynamic_cast(d->baseCRS().get()) && + ci_equal(d->hubCRS()->nameStr(), "WGS 84")) { + return d->transformation()->getHeightToGeographic3DFilename(); + } + return std::string(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void BoundCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (isWKT2) { + formatter->startNode(io::WKTConstants::BOUNDCRS, false); + formatter->startNode(io::WKTConstants::SOURCECRS, false); + d->baseCRS()->_exportToWKT(formatter); + formatter->endNode(); + formatter->startNode(io::WKTConstants::TARGETCRS, false); + d->hubCRS()->_exportToWKT(formatter); + formatter->endNode(); + formatter->setAbridgedTransformation(true); + d->transformation()->_exportToWKT(formatter); + formatter->setAbridgedTransformation(false); + formatter->endNode(); + } else { + + auto vdatumProj4GridName = getVDatumPROJ4GRIDS(); + if (!vdatumProj4GridName.empty()) { + formatter->setVDatumExtension(vdatumProj4GridName); + d->baseCRS()->_exportToWKT(formatter); + formatter->setVDatumExtension(std::string()); + return; + } + + auto hdatumProj4GridName = getHDatumPROJ4GRIDS(); + if (!hdatumProj4GridName.empty()) { + formatter->setHDatumExtension(hdatumProj4GridName); + d->baseCRS()->_exportToWKT(formatter); + formatter->setHDatumExtension(std::string()); + return; + } + + if (!isTOWGS84Compatible()) { + io::FormattingException::Throw( + "Cannot export BoundCRS with non-WGS 84 hub CRS in WKT1"); + } + auto params = d->transformation()->getTOWGS84Parameters(); + if (!formatter->useESRIDialect()) { + formatter->setTOWGS84Parameters(params); + } + d->baseCRS()->_exportToWKT(formatter); + formatter->setTOWGS84Parameters(std::vector()); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +void BoundCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + io::FormattingException::Throw( + "BoundCRS cannot be exported as a PROJ.5 string, but its baseCRS " + "might"); + } + + auto crs_exportable = + dynamic_cast(d->baseCRS_.get()); + if (!crs_exportable) { + io::FormattingException::Throw( + "baseCRS of BoundCRS cannot be exported as a PROJ string"); + } + + auto vdatumProj4GridName = getVDatumPROJ4GRIDS(); + if (!vdatumProj4GridName.empty()) { + formatter->setVDatumExtension(vdatumProj4GridName); + crs_exportable->_exportToPROJString(formatter); + formatter->setVDatumExtension(std::string()); + } else { + auto hdatumProj4GridName = getHDatumPROJ4GRIDS(); + if (!hdatumProj4GridName.empty()) { + formatter->setHDatumExtension(hdatumProj4GridName); + crs_exportable->_exportToPROJString(formatter); + formatter->setHDatumExtension(std::string()); + } else { + if (isTOWGS84Compatible()) { + auto params = transformation()->getTOWGS84Parameters(); + formatter->setTOWGS84Parameters(params); + } + crs_exportable->_exportToPROJString(formatter); + formatter->setTOWGS84Parameters(std::vector()); + } + } +} + +// --------------------------------------------------------------------------- + +bool BoundCRS::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherBoundCRS = dynamic_cast(other); + if (otherBoundCRS == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion))) { + return false; + } + const auto standardCriterion = getStandardCriterion(criterion); + return d->baseCRS_->_isEquivalentTo(otherBoundCRS->d->baseCRS_.get(), + criterion) && + d->hubCRS_->_isEquivalentTo(otherBoundCRS->d->hubCRS_.get(), + criterion) && + d->transformation_->_isEquivalentTo( + otherBoundCRS->d->transformation_.get(), standardCriterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list> +BoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair Pair; + std::list res; + if (authorityFactory && + d->hubCRS_->_isEquivalentTo(GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + auto resTemp = d->baseCRS_->identify(authorityFactory); + for (const auto &pair : resTemp) { + const auto &candidateBaseCRS = pair.first; + auto projCRS = + dynamic_cast(candidateBaseCRS.get()); + auto geodCRS = projCRS ? projCRS->baseCRS().as_nullable() + : util::nn_dynamic_pointer_cast( + candidateBaseCRS); + if (geodCRS) { + auto context = operation::CoordinateOperationContext::create( + authorityFactory, nullptr, 0.0); + auto ops = + operation::CoordinateOperationFactory::create() + ->createOperations(NN_NO_CHECK(geodCRS), + GeographicCRS::EPSG_4326, context); + std::string refTransfPROJString; + bool refTransfPROJStringValid = false; + try { + refTransfPROJString = + d->transformation_->exportToPROJString( + io::PROJStringFormatter::create().get()); + refTransfPROJStringValid = true; + if (refTransfPROJString == "+proj=axisswap +order=2,1") { + refTransfPROJString.clear(); + } + } catch (const std::exception &) { + } + bool foundOp = false; + for (const auto &op : ops) { + std::string opTransfPROJString; + bool opTransfPROJStringValid = false; + if (op->nameStr().find("Null geographic") == 0) { + if (isTOWGS84Compatible()) { + auto params = + transformation()->getTOWGS84Parameters(); + if (params == + std::vector{0, 0, 0, 0, 0, 0, 0}) { + res.emplace_back(create(candidateBaseCRS, + d->hubCRS_, + transformation()), + pair.second); + foundOp = true; + break; + } + } + continue; + } + try { + opTransfPROJString = op->exportToPROJString( + io::PROJStringFormatter::create().get()); + opTransfPROJStringValid = true; + } catch (const std::exception &) { + } + if ((refTransfPROJStringValid && opTransfPROJStringValid && + refTransfPROJString == opTransfPROJString) || + op->_isEquivalentTo( + d->transformation_.get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back( + create(candidateBaseCRS, d->hubCRS_, + NN_NO_CHECK(util::nn_dynamic_pointer_cast< + operation::Transformation>(op))), + pair.second); + foundOp = true; + break; + } + } + if (!foundOp) { + res.emplace_back( + create(candidateBaseCRS, d->hubCRS_, transformation()), + std::min(70, pair.second)); + } + } + } + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedGeodeticCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedGeodeticCRS::~DerivedGeodeticCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedGeodeticCRS::DerivedGeodeticCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + GeodeticCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedGeodeticCRS::DerivedGeodeticCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::SphericalCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + GeodeticCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedGeodeticCRS::DerivedGeodeticCRS(const DerivedGeodeticCRS &other) + : SingleCRS(other), GeodeticCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedGeodeticCRS::_shallowClone() const { + auto crs(DerivedGeodeticCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a GeodeticCRS) of a DerivedGeodeticCRS. + * + * @return the base CRS. + */ +const GeodeticCRSNNPtr DerivedGeodeticCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedGeodeticCRS from a base CRS, a deriving + * conversion and a cs::CartesianCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedGeodeticCRS. + */ +DerivedGeodeticCRSNNPtr DerivedGeodeticCRS::create( + const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) { + auto crs(DerivedGeodeticCRS::nn_make_shared( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedGeodeticCRS from a base CRS, a deriving + * conversion and a cs::SphericalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedGeodeticCRS. + */ +DerivedGeodeticCRSNNPtr DerivedGeodeticCRS::create( + const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::SphericalCSNNPtr &csIn) { + auto crs(DerivedGeodeticCRS::nn_make_shared( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedGeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "DerivedGeodeticCRS can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::GEODCRS, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + auto l_baseCRS = baseCRS(); + formatter->startNode((formatter->use2018Keywords() && + dynamic_cast(l_baseCRS.get())) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !baseCRS()->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + auto l_datum = l_baseCRS->datum(); + if (l_datum) { + l_datum->_exportToWKT(formatter); + } else { + auto l_datumEnsemble = datumEnsemble(); + assert(l_datumEnsemble); + l_datumEnsemble->_exportToWKT(formatter); + } + l_baseCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedGeodeticCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedGeodeticCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list> +DerivedGeodeticCRS::_identify(const io::AuthorityFactoryPtr &factory) const { + return CRS::_identify(factory); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedGeographicCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedGeographicCRS::~DerivedGeographicCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedGeographicCRS::DerivedGeographicCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::EllipsoidalCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + GeographicCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedGeographicCRS::DerivedGeographicCRS(const DerivedGeographicCRS &other) + : SingleCRS(other), GeographicCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedGeographicCRS::_shallowClone() const { + auto crs(DerivedGeographicCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a GeodeticCRS) of a DerivedGeographicCRS. + * + * @return the base CRS. + */ +const GeodeticCRSNNPtr DerivedGeographicCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedGeographicCRS from a base CRS, a deriving + * conversion and a cs::EllipsoidalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedGeographicCRS. + */ +DerivedGeographicCRSNNPtr DerivedGeographicCRS::create( + const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::EllipsoidalCSNNPtr &csIn) { + auto crs(DerivedGeographicCRS::nn_make_shared( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedGeographicCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "DerivedGeographicCRS can only be exported to WKT2"); + } + formatter->startNode(formatter->use2018Keywords() + ? io::WKTConstants::GEOGCRS + : io::WKTConstants::GEODCRS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + auto l_baseCRS = baseCRS(); + formatter->startNode((formatter->use2018Keywords() && + dynamic_cast(l_baseCRS.get())) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !l_baseCRS->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); + l_baseCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedGeographicCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedGeographicCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list> +DerivedGeographicCRS::_identify(const io::AuthorityFactoryPtr &factory) const { + return CRS::_identify(factory); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedProjectedCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedProjectedCRS::~DerivedProjectedCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedProjectedCRS::DerivedProjectedCRS( + const ProjectedCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CoordinateSystemNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedProjectedCRS::DerivedProjectedCRS(const DerivedProjectedCRS &other) + : SingleCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedProjectedCRS::_shallowClone() const { + auto crs(DerivedProjectedCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a ProjectedCRS) of a DerivedProjectedCRS. + * + * @return the base CRS. + */ +const ProjectedCRSNNPtr DerivedProjectedCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedProjectedCRS from a base CRS, a deriving + * conversion and a cs::CS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedProjectedCRS. + */ +DerivedProjectedCRSNNPtr DerivedProjectedCRS::create( + const util::PropertyMap &properties, const ProjectedCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CoordinateSystemNNPtr &csIn) { + auto crs(DerivedProjectedCRS::nn_make_shared( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2 || !formatter->use2018Keywords()) { + io::FormattingException::Throw( + "DerivedProjectedCRS can only be exported to WKT2:2018"); + } + formatter->startNode(io::WKTConstants::DERIVEDPROJCRS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + { + auto l_baseProjCRS = baseCRS(); + formatter->startNode(io::WKTConstants::BASEPROJCRS, + !l_baseProjCRS->identifiers().empty()); + formatter->addQuotedString(l_baseProjCRS->nameStr()); + + auto l_baseGeodCRS = l_baseProjCRS->baseCRS(); + auto &geodeticCRSAxisList = + l_baseGeodCRS->coordinateSystem()->axisList(); + + formatter->startNode( + dynamic_cast(l_baseGeodCRS.get()) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !l_baseGeodCRS->identifiers().empty()); + formatter->addQuotedString(l_baseGeodCRS->nameStr()); + l_baseGeodCRS->exportDatumOrDatumEnsembleToWkt(formatter); + // insert ellipsoidal cs unit when the units of the map + // projection angular parameters are not explicitly given within those + // parameters. See + // http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#61 + if (formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis() && + !geodeticCRSAxisList.empty()) { + geodeticCRSAxisList[0]->unit()._exportToWKT(formatter); + } + l_baseGeodCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + + l_baseProjCRS->derivingConversionRef()->_exportToWKT(formatter); + formatter->endNode(); + } + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedProjectedCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedProjectedCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct TemporalCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalCRS::~TemporalCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +TemporalCRS::TemporalCRS(const datum::TemporalDatumNNPtr &datumIn, + const cs::TemporalCSNNPtr &csIn) + : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +TemporalCRS::TemporalCRS(const TemporalCRS &other) + : SingleCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr TemporalCRS::_shallowClone() const { + auto crs(TemporalCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::TemporalDatum associated with the CRS. + * + * @return a TemporalDatum + */ +const datum::TemporalDatumNNPtr TemporalCRS::datum() const { + return NN_NO_CHECK(std::static_pointer_cast( + SingleCRS::getPrivate()->datum)); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::TemporalCS associated with the CRS. + * + * @return a TemporalCS + */ +const cs::TemporalCSNNPtr TemporalCRS::coordinateSystem() const { + return util::nn_static_pointer_cast( + SingleCRS::getPrivate()->coordinateSystem); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a TemporalCRS from a datum and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn the datum. + * @param csIn the coordinate system. + * @return new TemporalCRS. + */ +TemporalCRSNNPtr TemporalCRS::create(const util::PropertyMap &properties, + const datum::TemporalDatumNNPtr &datumIn, + const cs::TemporalCSNNPtr &csIn) { + auto crs(TemporalCRS::nn_make_shared(datumIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void TemporalCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "TemporalCRS can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::TIMECRS, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + datum()->_exportToWKT(formatter); + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool TemporalCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherTemporalCRS = dynamic_cast(other); + return otherTemporalCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct EngineeringCRS::Private { + bool forceOutputCS_ = false; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +EngineeringCRS::~EngineeringCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +EngineeringCRS::EngineeringCRS(const datum::EngineeringDatumNNPtr &datumIn, + const cs::CoordinateSystemNNPtr &csIn) + : SingleCRS(datumIn.as_nullable(), nullptr, csIn), + d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +EngineeringCRS::EngineeringCRS(const EngineeringCRS &other) + : SingleCRS(other), d(internal::make_unique(*(other.d))) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr EngineeringCRS::_shallowClone() const { + auto crs(EngineeringCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::EngineeringDatum associated with the CRS. + * + * @return a EngineeringDatum + */ +const datum::EngineeringDatumNNPtr EngineeringCRS::datum() const { + return NN_NO_CHECK(std::static_pointer_cast( + SingleCRS::getPrivate()->datum)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EngineeringCRS from a datum and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn the datum. + * @param csIn the coordinate system. + * @return new EngineeringCRS. + */ +EngineeringCRSNNPtr +EngineeringCRS::create(const util::PropertyMap &properties, + const datum::EngineeringDatumNNPtr &datumIn, + const cs::CoordinateSystemNNPtr &csIn) { + auto crs(EngineeringCRS::nn_make_shared(datumIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + + const auto pVal = properties.get("FORCE_OUTPUT_CS"); + if (pVal) { + if (const auto genVal = + dynamic_cast(pVal->get())) { + if (genVal->type() == util::BoxedValue::Type::BOOLEAN && + genVal->booleanValue()) { + crs->d->forceOutputCS_ = true; + } + } + } + + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void EngineeringCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::ENGCRS + : io::WKTConstants::LOCAL_CS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + if (isWKT2 || !datum()->nameStr().empty()) { + datum()->_exportToWKT(formatter); + coordinateSystem()->_exportToWKT(formatter); + } + if (!isWKT2 && d->forceOutputCS_) { + coordinateSystem()->axisList()[0]->unit()._exportToWKT(formatter); + } + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool EngineeringCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherEngineeringCRS = dynamic_cast(other); + return otherEngineeringCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ParametricCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ParametricCRS::~ParametricCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +ParametricCRS::ParametricCRS(const datum::ParametricDatumNNPtr &datumIn, + const cs::ParametricCSNNPtr &csIn) + : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +ParametricCRS::ParametricCRS(const ParametricCRS &other) + : SingleCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr ParametricCRS::_shallowClone() const { + auto crs(ParametricCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::ParametricDatum associated with the CRS. + * + * @return a ParametricDatum + */ +const datum::ParametricDatumNNPtr ParametricCRS::datum() const { + return NN_NO_CHECK(std::static_pointer_cast( + SingleCRS::getPrivate()->datum)); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::TemporalCS associated with the CRS. + * + * @return a TemporalCS + */ +const cs::ParametricCSNNPtr ParametricCRS::coordinateSystem() const { + return util::nn_static_pointer_cast( + SingleCRS::getPrivate()->coordinateSystem); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParametricCRS from a datum and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn the datum. + * @param csIn the coordinate system. + * @return new ParametricCRS. + */ +ParametricCRSNNPtr +ParametricCRS::create(const util::PropertyMap &properties, + const datum::ParametricDatumNNPtr &datumIn, + const cs::ParametricCSNNPtr &csIn) { + auto crs(ParametricCRS::nn_make_shared(datumIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ParametricCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "ParametricCRS can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::PARAMETRICCRS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + datum()->_exportToWKT(formatter); + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool ParametricCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherParametricCRS = dynamic_cast(other); + return otherParametricCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedVerticalCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedVerticalCRS::~DerivedVerticalCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedVerticalCRS::DerivedVerticalCRS( + const VerticalCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::VerticalCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + VerticalCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedVerticalCRS::DerivedVerticalCRS(const DerivedVerticalCRS &other) + : SingleCRS(other), VerticalCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedVerticalCRS::_shallowClone() const { + auto crs(DerivedVerticalCRS::nn_make_shared(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a VerticalCRS) of a DerivedVerticalCRS. + * + * @return the base CRS. + */ +const VerticalCRSNNPtr DerivedVerticalCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedVerticalCRS from a base CRS, a deriving + * conversion and a cs::VerticalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedVerticalCRS. + */ +DerivedVerticalCRSNNPtr DerivedVerticalCRS::create( + const util::PropertyMap &properties, const VerticalCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::VerticalCSNNPtr &csIn) { + auto crs(DerivedVerticalCRS::nn_make_shared( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedVerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "DerivedVerticalCRS can only be exported to WKT2"); + } + baseExportToWKT(formatter, io::WKTConstants::VERTCRS, + io::WKTConstants::BASEVERTCRS); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedVerticalCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedVerticalCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list> +DerivedVerticalCRS::_identify(const io::AuthorityFactoryPtr &factory) const { + return CRS::_identify(factory); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +template +struct DerivedCRSTemplate::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +template +DerivedCRSTemplate::~DerivedCRSTemplate() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +template +DerivedCRSTemplate::DerivedCRSTemplate( + const BaseNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, const CSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum().as_nullable(), nullptr, csIn), + BaseType(baseCRSIn->datum(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +template +DerivedCRSTemplate::DerivedCRSTemplate( + const DerivedCRSTemplate &other) + : SingleCRS(other), BaseType(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +template +const typename DerivedCRSTemplate::BaseNNPtr +DerivedCRSTemplate::baseCRS() const { + auto l_baseCRS = DerivedCRS::getPrivate()->baseCRS_; + return NN_NO_CHECK(util::nn_dynamic_pointer_cast(l_baseCRS)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +template +CRSNNPtr DerivedCRSTemplate::_shallowClone() const { + auto crs(DerivedCRSTemplate::nn_make_shared(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +template +typename DerivedCRSTemplate::NNPtr +DerivedCRSTemplate::create( + const util::PropertyMap &properties, const BaseNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const CSNNPtr &csIn) { + auto crs(DerivedCRSTemplate::nn_make_shared( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +static void DerivedCRSTemplateCheckExportToWKT(io::WKTFormatter *&formatter, + const std::string &crsName, + bool wkt2_2018_only) { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2 || (wkt2_2018_only && !formatter->use2018Keywords())) { + io::FormattingException::Throw(crsName + + " can only be exported to WKT2" + + (wkt2_2018_only ? ":2018" : "")); + } +} + +// --------------------------------------------------------------------------- + +template +void DerivedCRSTemplate::_exportToWKT( + io::WKTFormatter *formatter) const { + DerivedCRSTemplateCheckExportToWKT(formatter, DerivedCRSTraits::CRSName(), + DerivedCRSTraits::wkt2_2018_only); + baseExportToWKT(formatter, DerivedCRSTraits::WKTKeyword(), + DerivedCRSTraits::WKTBaseKeyword()); +} + +// --------------------------------------------------------------------------- + +template +bool DerivedCRSTemplate::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string STRING_DerivedEngineeringCRS("DerivedEngineeringCRS"); +const std::string &DerivedEngineeringCRSTraits::CRSName() { + return STRING_DerivedEngineeringCRS; +} +const std::string &DerivedEngineeringCRSTraits::WKTKeyword() { + return io::WKTConstants::ENGCRS; +} +const std::string &DerivedEngineeringCRSTraits::WKTBaseKeyword() { + return io::WKTConstants::BASEENGCRS; +} + +template class DerivedCRSTemplate; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string STRING_DerivedParametricCRS("DerivedParametricCRS"); +const std::string &DerivedParametricCRSTraits::CRSName() { + return STRING_DerivedParametricCRS; +} +const std::string &DerivedParametricCRSTraits::WKTKeyword() { + return io::WKTConstants::PARAMETRICCRS; +} +const std::string &DerivedParametricCRSTraits::WKTBaseKeyword() { + return io::WKTConstants::BASEPARAMCRS; +} + +template class DerivedCRSTemplate; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string STRING_DerivedTemporalCRS("DerivedTemporalCRS"); +const std::string &DerivedTemporalCRSTraits::CRSName() { + return STRING_DerivedTemporalCRS; +} +const std::string &DerivedTemporalCRSTraits::WKTKeyword() { + return io::WKTConstants::TIMECRS; +} +const std::string &DerivedTemporalCRSTraits::WKTBaseKeyword() { + return io::WKTConstants::BASETIMECRS; +} + +template class DerivedCRSTemplate; +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace crs +NS_PROJ_END diff --git a/src/iso19111/datum.cpp b/src/iso19111/datum.cpp new file mode 100644 index 00000000..16e86296 --- /dev/null +++ b/src/iso19111/datum.cpp @@ -0,0 +1,1996 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 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/datum.hpp" +#include "proj/common.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "projects.h" +#include "proj_api.h" +// clang-format on + +#include +#include +#include +#include + +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; +}} +#endif + +NS_PROJ_START +namespace datum { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +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); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Datum::Private { + util::optional anchorDefinition{}; + util::optional publicationDate{}; + common::IdentifiedObjectPtr conventionalRS{}; + + // cppcheck-suppress functionStatic + void exportAnchorDefinition(io::WKTFormatter *formatter) const; +}; + +// --------------------------------------------------------------------------- + +void Datum::Private::exportAnchorDefinition(io::WKTFormatter *formatter) const { + if (anchorDefinition) { + formatter->startNode(io::WKTConstants::ANCHOR, false); + formatter->addQuotedString(*anchorDefinition); + formatter->endNode(); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +Datum::Datum() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +Datum::Datum(const Datum &other) + : ObjectUsage(other), d(internal::make_unique(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Datum::~Datum() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the anchor definition. + * + * A description - possibly including coordinates of an identified point or + * points - of the relationship used to anchor a coordinate system to the + * Earth or alternate object. + *
    + *
  • For modern geodetic reference frames the anchor may be a set of station + * coordinates; if the reference frame is dynamic it will also include + * coordinate velocities. For a traditional geodetic datum, this anchor may be + * a point known as the fundamental point, which is traditionally the point + * where the relationship between geoid and ellipsoid is defined, together + * with a direction from that point.
  • + *
  • For a vertical reference frame the anchor may be the zero level at one + * or more defined locations or a conventionally defined surface.
  • + *
  • For an engineering datum, the anchor may be an identified physical point + * with the orientation defined relative to the object.
  • + *
+ * + * @return the anchor definition, or empty. + */ +const util::optional &Datum::anchorDefinition() const { + return d->anchorDefinition; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the date on which the datum definition was published. + * + * \note Departure from \ref ISO_19111_2018 : we return a DateTime instead of + * a Citation::Date. + * + * @return the publication date, or empty. + */ +const util::optional &Datum::publicationDate() const { + return d->publicationDate; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the conventional reference system. + * + * This is the name, identifier, alias and remarks for the terrestrial + * reference system or vertical reference system realized by this reference + * frame, for example "ITRS" for ITRF88 through ITRF2008 and ITRF2014, or + * "EVRS" for EVRF2000 and EVRF2007. + * + * @return the conventional reference system, or nullptr. + */ +const common::IdentifiedObjectPtr &Datum::conventionalRS() const { + return d->conventionalRS; +} + +// --------------------------------------------------------------------------- + +void Datum::setAnchor(const util::optional &anchor) { + d->anchorDefinition = anchor; +} + +// --------------------------------------------------------------------------- + +bool Datum::__isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDatum = dynamic_cast(other); + if (otherDatum == nullptr || + !ObjectUsage::_isEquivalentTo(other, criterion)) { + return false; + } + if (criterion == util::IComparable::Criterion::STRICT) { + if ((anchorDefinition().has_value() ^ + otherDatum->anchorDefinition().has_value())) { + return false; + } + if (anchorDefinition().has_value() && + otherDatum->anchorDefinition().has_value() && + *anchorDefinition() != *otherDatum->anchorDefinition()) { + return false; + } + + if ((publicationDate().has_value() ^ + otherDatum->publicationDate().has_value())) { + return false; + } + if (publicationDate().has_value() && + otherDatum->publicationDate().has_value() && + publicationDate()->toString() != + otherDatum->publicationDate()->toString()) { + return false; + } + + if (((conventionalRS() != nullptr) ^ + (otherDatum->conventionalRS() != nullptr))) { + return false; + } + if (conventionalRS() && otherDatum->conventionalRS() && + conventionalRS()->_isEquivalentTo( + otherDatum->conventionalRS().get(), criterion)) { + return false; + } + } + return true; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct PrimeMeridian::Private { + common::Angle longitude_{}; + + explicit Private(const common::Angle &longitude) : longitude_(longitude) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +PrimeMeridian::PrimeMeridian(const common::Angle &longitudeIn) + : d(internal::make_unique(longitudeIn)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +PrimeMeridian::PrimeMeridian(const PrimeMeridian &other) + : common::IdentifiedObject(other), + d(internal::make_unique(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PrimeMeridian::~PrimeMeridian() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the longitude of the prime meridian. + * + * It is measured from the internationally-recognised reference meridian + * ('Greenwich meridian'), positive eastward. + * The default value is 0 degrees. + * + * @return the longitude of the prime meridian. + */ +const common::Angle &PrimeMeridian::longitude() PROJ_CONST_DEFN { + return d->longitude_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a PrimeMeridian. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param longitudeIn the longitude of the prime meridian. + * @return new PrimeMeridian. + */ +PrimeMeridianNNPtr PrimeMeridian::create(const util::PropertyMap &properties, + const common::Angle &longitudeIn) { + auto pm(PrimeMeridian::nn_make_shared(longitudeIn)); + pm->setProperties(properties); + return pm; +} + +// --------------------------------------------------------------------------- + +const PrimeMeridianNNPtr PrimeMeridian::createGREENWICH() { + return create(createMapNameEPSGCode("Greenwich", 8901), common::Angle(0)); +} + +// --------------------------------------------------------------------------- + +const PrimeMeridianNNPtr PrimeMeridian::createREFERENCE_MERIDIAN() { + return create(util::PropertyMap().set(IdentifiedObject::NAME_KEY, + "Reference meridian"), + common::Angle(0)); +} + +// --------------------------------------------------------------------------- + +const PrimeMeridianNNPtr PrimeMeridian::createPARIS() { + return create(createMapNameEPSGCode("Paris", 8903), + common::Angle(2.5969213, common::UnitOfMeasure::GRAD)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void PrimeMeridian::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + std::string l_name = + name()->description().has_value() ? nameStr() : "Greenwich"; + if (!(isWKT2 && formatter->primeMeridianOmittedIfGreenwich() && + l_name == "Greenwich")) { + formatter->startNode(io::WKTConstants::PRIMEM, !identifiers().empty()); + formatter->addQuotedString(l_name); + const auto &l_long = longitude(); + if (formatter->primeMeridianInDegree()) { + formatter->add(l_long.convertToUnit(common::UnitOfMeasure::DEGREE)); + } else { + formatter->add(l_long.value()); + } + const auto &unit = l_long.unit(); + if (isWKT2) { + if (!(formatter + ->primeMeridianOrParameterUnitOmittedIfSameAsAxis() && + unit == *(formatter->axisAngularUnit()))) { + unit._exportToWKT(formatter, io::WKTConstants::ANGLEUNIT); + } + } else if (!formatter->primeMeridianInDegree()) { + unit._exportToWKT(formatter); + } + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::string +PrimeMeridian::getPROJStringWellKnownName(const common::Angle &angle) { + const double valRad = angle.getSIValue(); + std::string projPMName; + projCtx ctxt = pj_ctx_alloc(); + auto proj_pm = proj_list_prime_meridians(); + for (int i = 0; proj_pm[i].id != nullptr; ++i) { + double valRefRad = dmstor_ctx(ctxt, proj_pm[i].defn, nullptr); + if (::fabs(valRad - valRefRad) < 1e-10) { + projPMName = proj_pm[i].id; + break; + } + } + pj_ctx_free(ctxt); + return projPMName; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void PrimeMeridian::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(FormattingException) +{ + if (longitude().getSIValue() != 0) { + std::string projPMName(getPROJStringWellKnownName(longitude())); + if (!projPMName.empty()) { + formatter->addParam("pm", projPMName); + } else { + const double valDeg = + longitude().convertToUnit(common::UnitOfMeasure::DEGREE); + formatter->addParam("pm", valDeg); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool PrimeMeridian::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherPM = dynamic_cast(other); + if (otherPM == nullptr || + !IdentifiedObject::_isEquivalentTo(other, criterion)) { + return false; + } + // In MapInfo, the Paris prime meridian is returned as 2.3372291666667 + // instead of the official value of 2.33722917, which is a relative + // error in the 1e-9 range. + return longitude()._isEquivalentTo(otherPM->longitude(), criterion, 1e-8); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Ellipsoid::Private { + common::Length semiMajorAxis_{}; + util::optional inverseFlattening_{}; + util::optional semiMinorAxis_{}; + util::optional semiMedianAxis_{}; + std::string celestialBody_{}; + + explicit Private(const common::Length &radius, + const std::string &celestialBody) + : semiMajorAxis_(radius), celestialBody_(celestialBody) {} + + Private(const common::Length &semiMajorAxisIn, + const common::Scale &invFlattening, + const std::string &celestialBody) + : semiMajorAxis_(semiMajorAxisIn), inverseFlattening_(invFlattening), + celestialBody_(celestialBody) {} + + Private(const common::Length &semiMajorAxisIn, + const common::Length &semiMinorAxisIn, + const std::string &celestialBody) + : semiMajorAxis_(semiMajorAxisIn), semiMinorAxis_(semiMinorAxisIn), + celestialBody_(celestialBody) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +Ellipsoid::Ellipsoid(const common::Length &radius, + const std::string &celestialBodyIn) + : d(internal::make_unique(radius, celestialBodyIn)) {} + +// --------------------------------------------------------------------------- + +Ellipsoid::Ellipsoid(const common::Length &semiMajorAxisIn, + const common::Scale &invFlattening, + const std::string &celestialBodyIn) + : d(internal::make_unique(semiMajorAxisIn, invFlattening, + celestialBodyIn)) {} + +// --------------------------------------------------------------------------- + +Ellipsoid::Ellipsoid(const common::Length &semiMajorAxisIn, + const common::Length &semiMinorAxisIn, + const std::string &celestialBodyIn) + : d(internal::make_unique(semiMajorAxisIn, semiMinorAxisIn, + celestialBodyIn)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +Ellipsoid::Ellipsoid(const Ellipsoid &other) + : common::IdentifiedObject(other), + d(internal::make_unique(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Ellipsoid::~Ellipsoid() = default; + +Ellipsoid::Ellipsoid(const Ellipsoid &other) + : IdentifiedObject(other), d(internal::make_unique(*(other.d))) {} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the length of the semi-major axis of the ellipsoid. + * + * @return the semi-major axis. + */ +const common::Length &Ellipsoid::semiMajorAxis() PROJ_CONST_DEFN { + return d->semiMajorAxis_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the inverse flattening value of the ellipsoid, if the + * ellipsoid + * has been defined with this value. + * + * @see computeInverseFlattening() that will always return a valid value of the + * inverse flattening, whether the ellipsoid has been defined through inverse + * flattening or semi-minor axis. + * + * @return the inverse flattening value of the ellipsoid, or empty. + */ +const util::optional & +Ellipsoid::inverseFlattening() PROJ_CONST_DEFN { + return d->inverseFlattening_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the length of the semi-minor axis of the ellipsoid, if the + * ellipsoid + * has been defined with this value. + * + * @see computeSemiMinorAxis() that will always return a valid value of the + * inverse flattening, whether the ellipsoid has been defined through inverse + * flattening or semi-minor axis. + * + * @return the semi-minor axis of the ellipsoid, or empty. + */ +const util::optional & +Ellipsoid::semiMinorAxis() PROJ_CONST_DEFN { + return d->semiMinorAxis_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether the ellipsoid is spherical. + * + * That is to say is semiMajorAxis() == computeSemiMinorAxis(). + * + * A sphere is completely defined by the semi-major axis, which is the radius + * of the sphere. + * + * @return true if the ellipsoid is spherical. + */ +bool Ellipsoid::isSphere() PROJ_CONST_DEFN { + if (d->inverseFlattening_.has_value()) { + return d->inverseFlattening_->value() == 0; + } + + if (semiMinorAxis().has_value()) { + return semiMajorAxis() == *semiMinorAxis(); + } + + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the length of the semi-median axis of a triaxial ellipsoid + * + * This parameter is not required for a biaxial ellipsoid. + * + * @return the semi-median axis of the ellipsoid, or empty. + */ +const util::optional & +Ellipsoid::semiMedianAxis() PROJ_CONST_DEFN { + return d->semiMedianAxis_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return or compute the inverse flattening value of the ellipsoid. + * + * If computed, the inverse flattening is the result of a / (a - b), + * where a is the semi-major axis and b the semi-minor axis. + * + * @return the inverse flattening value of the ellipsoid, or 0 for a sphere. + */ +double Ellipsoid::computedInverseFlattening() PROJ_CONST_DEFN { + if (d->inverseFlattening_.has_value()) { + return d->inverseFlattening_->getSIValue(); + } + + if (d->semiMinorAxis_.has_value()) { + const double a = d->semiMajorAxis_.getSIValue(); + const double b = d->semiMinorAxis_->getSIValue(); + return (a == b) ? 0.0 : a / (a - b); + } + + return 0.0; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the squared eccentricity of the ellipsoid. + * + * @return the squared eccentricity, or a negative value if invalid. + */ +double Ellipsoid::squaredEccentricity() PROJ_CONST_DEFN { + const double rf = computedInverseFlattening(); + const double f = rf != 0.0 ? 1. / rf : 0.0; + const double e2 = f * (2 - f); + return e2; +} + +// --------------------------------------------------------------------------- + +/** \brief Return or compute the length of the semi-minor axis of the ellipsoid. + * + * If computed, the semi-minor axis is the result of a * (1 - 1 / rf) + * where a is the semi-major axis and rf the reverse/inverse flattening. + + * @return the semi-minor axis of the ellipsoid. + */ +common::Length Ellipsoid::computeSemiMinorAxis() const { + if (d->semiMinorAxis_.has_value()) { + return *d->semiMinorAxis_; + } + + if (inverseFlattening().has_value()) { + return common::Length( + (1.0 - 1.0 / d->inverseFlattening_->getSIValue()) * + d->semiMajorAxis_.value(), + d->semiMajorAxis_.unit()); + } + + return d->semiMajorAxis_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the celestial body on which the ellipsoid refers + * to. + */ +const std::string &Ellipsoid::celestialBody() PROJ_CONST_DEFN { + return d->celestialBody_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Ellipsoid as a sphere. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param radius the sphere radius (semi-major axis). + * @param celestialBody Name of the celestial body on which the ellipsoid refers + * to. + * @return new Ellipsoid. + */ +EllipsoidNNPtr Ellipsoid::createSphere(const util::PropertyMap &properties, + const common::Length &radius, + const std::string &celestialBody) { + auto ellipsoid(Ellipsoid::nn_make_shared(radius, celestialBody)); + ellipsoid->setProperties(properties); + return ellipsoid; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Ellipsoid from its inverse/reverse flattening. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param semiMajorAxisIn the semi-major axis. + * @param invFlattening the inverse/reverse flattening. + * @param celestialBody Name of the celestial body on which the ellipsoid refers + * to. + * @return new Ellipsoid. + */ +EllipsoidNNPtr Ellipsoid::createFlattenedSphere( + const util::PropertyMap &properties, const common::Length &semiMajorAxisIn, + const common::Scale &invFlattening, const std::string &celestialBody) { + auto ellipsoid(Ellipsoid::nn_make_shared( + semiMajorAxisIn, invFlattening, celestialBody)); + ellipsoid->setProperties(properties); + return ellipsoid; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Ellipsoid from the value of its two semi axis. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param semiMajorAxisIn the semi-major axis. + * @param semiMinorAxisIn the semi-minor axis. + * @param celestialBody Name of the celestial body on which the ellipsoid refers + * to. + * @return new Ellipsoid. + */ +EllipsoidNNPtr Ellipsoid::createTwoAxis(const util::PropertyMap &properties, + const common::Length &semiMajorAxisIn, + const common::Length &semiMinorAxisIn, + const std::string &celestialBody) { + auto ellipsoid(Ellipsoid::nn_make_shared( + semiMajorAxisIn, semiMinorAxisIn, celestialBody)); + ellipsoid->setProperties(properties); + return ellipsoid; +} + +// --------------------------------------------------------------------------- + +const EllipsoidNNPtr Ellipsoid::createCLARKE_1866() { + return createTwoAxis(createMapNameEPSGCode("Clarke 1866", 7008), + common::Length(6378206.4), common::Length(6356583.8)); +} + +// --------------------------------------------------------------------------- + +const EllipsoidNNPtr Ellipsoid::createWGS84() { + return createFlattenedSphere(createMapNameEPSGCode("WGS 84", 7030), + common::Length(6378137), + common::Scale(298.257223563)); +} + +// --------------------------------------------------------------------------- + +const EllipsoidNNPtr Ellipsoid::createGRS1980() { + return createFlattenedSphere(createMapNameEPSGCode("GRS 1980", 7019), + common::Length(6378137), + common::Scale(298.257222101)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Ellipsoid::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::ELLIPSOID + : io::WKTConstants::SPHEROID, + !identifiers().empty()); + { + auto l_name = nameStr(); + if (l_name.empty()) { + formatter->addQuotedString("unnamed"); + } else { + if (formatter->useESRIDialect()) { + if (l_name == "WGS 84") { + l_name = "WGS_1984"; + } else { + bool aliasFound = false; + const auto &dbContext = formatter->databaseContext(); + if (dbContext) { + auto l_alias = dbContext->getAliasFromOfficialName( + l_name, "ellipsoid", "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 &semiMajor = semiMajorAxis(); + if (isWKT2) { + formatter->add(semiMajor.value()); + } else { + formatter->add(semiMajor.getSIValue()); + } + formatter->add(computedInverseFlattening()); + const auto &unit = semiMajor.unit(); + if (isWKT2 && + !(formatter->ellipsoidUnitOmittedIfMetre() && + unit == common::UnitOfMeasure::METRE)) { + unit._exportToWKT(formatter, io::WKTConstants::LENGTHUNIT); + } + if (formatter->outputId()) { + formatID(formatter); + } + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool Ellipsoid::lookForProjWellKnownEllps(std::string &projEllpsName, + std::string &ellpsName) const { + const double a = semiMajorAxis().getSIValue(); + const double b = computeSemiMinorAxis().getSIValue(); + const double rf = computedInverseFlattening(); + auto proj_ellps = proj_list_ellps(); + for (int i = 0; proj_ellps[i].id != nullptr; i++) { + assert(strncmp(proj_ellps[i].major, "a=", 2) == 0); + const double a_iter = c_locale_stod(proj_ellps[i].major + 2); + if (::fabs(a - a_iter) < 1e-10 * a_iter) { + if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) { + const double b_iter = c_locale_stod(proj_ellps[i].ell + 2); + if (::fabs(b - b_iter) < 1e-10 * b_iter) { + projEllpsName = proj_ellps[i].id; + ellpsName = proj_ellps[i].name; + if (ellpsName.find("GRS 1980") == 0) { + ellpsName = "GRS 1980"; + } + return true; + } + } else { + assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0); + const double rf_iter = c_locale_stod(proj_ellps[i].ell + 3); + if (::fabs(rf - rf_iter) < 1e-10 * rf_iter) { + projEllpsName = proj_ellps[i].id; + ellpsName = proj_ellps[i].name; + if (ellpsName.find("GRS 1980") == 0) { + ellpsName = "GRS 1980"; + } + return true; + } + } + } + } + return false; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Ellipsoid::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(FormattingException) +{ + const double a = semiMajorAxis().getSIValue(); + + std::string projEllpsName; + std::string ellpsName; + if (lookForProjWellKnownEllps(projEllpsName, ellpsName)) { + formatter->addParam("ellps", projEllpsName); + return; + } + + if (isSphere()) { + formatter->addParam("R", a); + } else { + formatter->addParam("a", a); + if (inverseFlattening().has_value()) { + const double rf = computedInverseFlattening(); + formatter->addParam("rf", rf); + } else { + const double b = computeSemiMinorAxis().getSIValue(); + formatter->addParam("b", b); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return a Ellipsoid object where some parameters are better + * identified. + * + * @return a new Ellipsoid. + */ +EllipsoidNNPtr Ellipsoid::identify() const { + auto newEllipsoid = Ellipsoid::nn_make_shared(*this); + newEllipsoid->assignSelf( + util::nn_static_pointer_cast(newEllipsoid)); + + if (name()->description()->empty() || nameStr() == "unknown") { + std::string projEllpsName; + std::string ellpsName; + if (lookForProjWellKnownEllps(projEllpsName, ellpsName)) { + newEllipsoid->setProperties( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, ellpsName)); + } + } + + return newEllipsoid; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool Ellipsoid::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherEllipsoid = dynamic_cast(other); + if (otherEllipsoid == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !IdentifiedObject::_isEquivalentTo(other, criterion))) { + return false; + } + + // PROJ "clrk80" name is "Clarke 1880 mod." and GDAL tends to + // export to it a number of Clarke 1880 variants, so be lax + if (criterion != util::IComparable::Criterion::STRICT && + (nameStr() == "Clarke 1880 mod." || + otherEllipsoid->nameStr() == "Clarke 1880 mod.")) { + return std::fabs(semiMajorAxis().getSIValue() - + otherEllipsoid->semiMajorAxis().getSIValue()) < + 1e-8 * semiMajorAxis().getSIValue() && + std::fabs(computedInverseFlattening() - + otherEllipsoid->computedInverseFlattening()) < + 1e-5 * computedInverseFlattening(); + } + + if (!semiMajorAxis()._isEquivalentTo(otherEllipsoid->semiMajorAxis(), + criterion)) { + return false; + } + + const auto &l_semiMinorAxis = semiMinorAxis(); + const auto &l_other_semiMinorAxis = otherEllipsoid->semiMinorAxis(); + if (l_semiMinorAxis.has_value() && l_other_semiMinorAxis.has_value()) { + if (!l_semiMinorAxis->_isEquivalentTo(*l_other_semiMinorAxis, + criterion)) { + return false; + } + } + + const auto &l_inverseFlattening = inverseFlattening(); + const auto &l_other_sinverseFlattening = + otherEllipsoid->inverseFlattening(); + if (l_inverseFlattening.has_value() && + l_other_sinverseFlattening.has_value()) { + if (!l_inverseFlattening->_isEquivalentTo(*l_other_sinverseFlattening, + criterion)) { + return false; + } + } + + if (criterion == util::IComparable::Criterion::STRICT) { + if ((l_semiMinorAxis.has_value() ^ l_other_semiMinorAxis.has_value())) { + return false; + } + + if ((l_inverseFlattening.has_value() ^ + l_other_sinverseFlattening.has_value())) { + return false; + } + + } else { + if (!otherEllipsoid->computeSemiMinorAxis()._isEquivalentTo( + otherEllipsoid->computeSemiMinorAxis(), criterion)) { + return false; + } + } + + const auto &l_semiMedianAxis = semiMedianAxis(); + const auto &l_other_semiMedianAxis = otherEllipsoid->semiMedianAxis(); + if ((l_semiMedianAxis.has_value() ^ l_other_semiMedianAxis.has_value())) { + return false; + } + if (l_semiMedianAxis.has_value() && l_other_semiMedianAxis.has_value()) { + if (!l_semiMedianAxis->_isEquivalentTo(*l_other_semiMedianAxis, + criterion)) { + return false; + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +std::string Ellipsoid::guessBodyName(const io::DatabaseContextPtr &dbContext, + double a) { + constexpr double relError = 0.005; + constexpr double earthMeanRadius = 6375000.0; + if (std::fabs(a - earthMeanRadius) < relError * earthMeanRadius) { + return Ellipsoid::EARTH; + } + if (dbContext) { + try { + auto factory = io::AuthorityFactory::create(NN_NO_CHECK(dbContext), + std::string()); + return factory->identifyBodyFromSemiMajorAxis(a, relError); + } catch (const std::exception &) { + } + } + return "Non-Earth body"; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeodeticReferenceFrame::Private { + PrimeMeridianNNPtr primeMeridian_; + EllipsoidNNPtr ellipsoid_; + + Private(const EllipsoidNNPtr &ellipsoidIn, + const PrimeMeridianNNPtr &primeMeridianIn) + : primeMeridian_(primeMeridianIn), ellipsoid_(ellipsoidIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +GeodeticReferenceFrame::GeodeticReferenceFrame( + const EllipsoidNNPtr &ellipsoidIn, + const PrimeMeridianNNPtr &primeMeridianIn) + : d(internal::make_unique(ellipsoidIn, primeMeridianIn)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +GeodeticReferenceFrame::GeodeticReferenceFrame( + const GeodeticReferenceFrame &other) + : Datum(other), d(internal::make_unique(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeodeticReferenceFrame::~GeodeticReferenceFrame() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the PrimeMeridian associated with a GeodeticReferenceFrame. + * + * @return the PrimeMeridian. + */ +const PrimeMeridianNNPtr & +GeodeticReferenceFrame::primeMeridian() PROJ_CONST_DEFN { + return d->primeMeridian_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the Ellipsoid associated with a GeodeticReferenceFrame. + * + * \note The \ref ISO_19111_2018 modelling allows (but discourages) a + * GeodeticReferenceFrame + * to not be associated with a Ellipsoid in the case where it is used by a + * geocentric crs::GeodeticCRS. We have made the choice of making the ellipsoid + * specification compulsory. + * + * @return the Ellipsoid. + */ +const EllipsoidNNPtr &GeodeticReferenceFrame::ellipsoid() PROJ_CONST_DEFN { + return d->ellipsoid_; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticReferenceFrame + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param ellipsoid the Ellipsoid. + * @param anchor the anchor definition, or empty. + * @param primeMeridian the PrimeMeridian. + * @return new GeodeticReferenceFrame. + */ +GeodeticReferenceFrameNNPtr +GeodeticReferenceFrame::create(const util::PropertyMap &properties, + const EllipsoidNNPtr &ellipsoid, + const util::optional &anchor, + const PrimeMeridianNNPtr &primeMeridian) { + GeodeticReferenceFrameNNPtr grf( + GeodeticReferenceFrame::nn_make_shared( + ellipsoid, primeMeridian)); + grf->setAnchor(anchor); + grf->setProperties(properties); + return grf; +} + +// --------------------------------------------------------------------------- + +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6267() { + return create(createMapNameEPSGCode("North American Datum 1927", 6267), + Ellipsoid::CLARKE_1866, util::optional(), + PrimeMeridian::GREENWICH); +} + +// --------------------------------------------------------------------------- + +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6269() { + return create(createMapNameEPSGCode("North American Datum 1983", 6269), + Ellipsoid::GRS1980, util::optional(), + PrimeMeridian::GREENWICH); +} + +// --------------------------------------------------------------------------- + +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6326() { + return create(createMapNameEPSGCode("World Geodetic System 1984", 6326), + Ellipsoid::WGS84, util::optional(), + PrimeMeridian::GREENWICH); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticReferenceFrame::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(io::WKTConstants::DATUM, !identifiers().empty()); + auto l_name = nameStr(); + if (l_name.empty()) { + l_name = "unnamed"; + } + if (!isWKT2) { + if (formatter->useESRIDialect()) { + if (l_name == "World Geodetic System 1984") { + l_name = "D_WGS_1984"; + } else { + bool aliasFound = false; + const auto &dbContext = formatter->databaseContext(); + if (dbContext) { + auto l_alias = dbContext->getAliasFromOfficialName( + l_name, "geodetic_datum", "ESRI"); + size_t pos; + if (!l_alias.empty()) { + l_name = l_alias; + aliasFound = true; + } else if ((pos = l_name.find(" (")) != std::string::npos) { + l_alias = dbContext->getAliasFromOfficialName( + l_name.substr(0, pos), "geodetic_datum", "ESRI"); + if (!l_alias.empty()) { + l_name = l_alias; + aliasFound = true; + } + } + } + if (!aliasFound) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + if (!starts_with(l_name, "D_")) { + l_name = "D_" + l_name; + } + } + } + // Replace spaces by underscore, except if it is a special MapInfo + // datum name + } else if (!starts_with(l_name, "MIF ")) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + if (l_name == "World_Geodetic_System_1984") { + l_name = "WGS_1984"; + } + } + } + formatter->addQuotedString(l_name); + + ellipsoid()->_exportToWKT(formatter); + if (isWKT2) { + Datum::getPrivate()->exportAnchorDefinition(formatter); + } else { + const auto &TOWGS84Params = formatter->getTOWGS84Parameters(); + if (TOWGS84Params.size() == 7) { + formatter->startNode(io::WKTConstants::TOWGS84, false); + for (const auto &val : TOWGS84Params) { + formatter->add(val); + } + formatter->endNode(); + } + std::string extension = formatter->getHDatumExtension(); + if (!extension.empty()) { + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4_GRIDS"); + formatter->addQuotedString(extension); + formatter->endNode(); + } + } + if (formatter->outputId()) { + formatID(formatter); + } + // the PRIMEM is exported as a child of the CRS + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeodeticReferenceFrame::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherGRF = dynamic_cast(other); + if (otherGRF == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + return false; + } + return primeMeridian()->_isEquivalentTo(otherGRF->primeMeridian().get(), + criterion) && + ellipsoid()->_isEquivalentTo(otherGRF->ellipsoid().get(), criterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DynamicGeodeticReferenceFrame::Private { + common::Measure frameReferenceEpoch{}; + util::optional deformationModelName{}; + + explicit Private(const common::Measure &frameReferenceEpochIn) + : frameReferenceEpoch(frameReferenceEpochIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +DynamicGeodeticReferenceFrame::DynamicGeodeticReferenceFrame( + const EllipsoidNNPtr &ellipsoidIn, + const PrimeMeridianNNPtr &primeMeridianIn, + const common::Measure &frameReferenceEpochIn, + const util::optional &deformationModelNameIn) + : GeodeticReferenceFrame(ellipsoidIn, primeMeridianIn), + d(internal::make_unique(frameReferenceEpochIn)) { + d->deformationModelName = deformationModelNameIn; +} + +// --------------------------------------------------------------------------- + +#ifdef notdef +DynamicGeodeticReferenceFrame::DynamicGeodeticReferenceFrame( + const DynamicGeodeticReferenceFrame &other) + : GeodeticReferenceFrame(other), + d(internal::make_unique(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DynamicGeodeticReferenceFrame::~DynamicGeodeticReferenceFrame() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the epoch to which the coordinates of stations defining the + * dynamic geodetic reference frame are referenced. + * + * Usually given as a decimal year e.g. 2016.47. + * + * @return the frame reference epoch. + */ +const common::Measure & +DynamicGeodeticReferenceFrame::frameReferenceEpoch() const { + return d->frameReferenceEpoch; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the deformation model. + * + * @note This is an extension to the \ref ISO_19111_2018 modeling, to + * hold the content of the DYNAMIC.MODEL WKT2 node. + * + * @return the name of the deformation model. + */ +const util::optional & +DynamicGeodeticReferenceFrame::deformationModelName() const { + return d->deformationModelName; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool DynamicGeodeticReferenceFrame::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDGRF = dynamic_cast(other); + if (otherDGRF == nullptr || + !GeodeticReferenceFrame::_isEquivalentTo(other, criterion)) { + return false; + } + return frameReferenceEpoch()._isEquivalentTo( + otherDGRF->frameReferenceEpoch(), criterion) && + metadata::Identifier::isEquivalentName( + deformationModelName()->c_str(), + otherDGRF->deformationModelName()->c_str()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DynamicGeodeticReferenceFrame::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (isWKT2 && formatter->use2018Keywords()) { + formatter->startNode(io::WKTConstants::DYNAMIC, false); + formatter->startNode(io::WKTConstants::FRAMEEPOCH, false); + formatter->add( + frameReferenceEpoch().convertToUnit(common::UnitOfMeasure::YEAR)); + formatter->endNode(); + if (deformationModelName().has_value() && + !deformationModelName()->empty()) { + formatter->startNode(io::WKTConstants::MODEL, false); + formatter->addQuotedString(*deformationModelName()); + formatter->endNode(); + } + formatter->endNode(); + } + GeodeticReferenceFrame::_exportToWKT(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DyanmicGeodeticReferenceFrame + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param ellipsoid the Ellipsoid. + * @param anchor the anchor definition, or empty. + * @param primeMeridian the PrimeMeridian. + * @param frameReferenceEpochIn the frame reference epoch. + * @param deformationModelNameIn deformation model name, or empty + * @return new DyanmicGeodeticReferenceFrame. + */ +DynamicGeodeticReferenceFrameNNPtr DynamicGeodeticReferenceFrame::create( + const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid, + const util::optional &anchor, + const PrimeMeridianNNPtr &primeMeridian, + const common::Measure &frameReferenceEpochIn, + const util::optional &deformationModelNameIn) { + DynamicGeodeticReferenceFrameNNPtr grf( + DynamicGeodeticReferenceFrame::nn_make_shared< + DynamicGeodeticReferenceFrame>(ellipsoid, primeMeridian, + frameReferenceEpochIn, + deformationModelNameIn)); + grf->setAnchor(anchor); + grf->setProperties(properties); + return grf; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DatumEnsemble::Private { + std::vector datums{}; + metadata::PositionalAccuracyNNPtr positionalAccuracy; + + Private(const std::vector &datumsIn, + const metadata::PositionalAccuracyNNPtr &accuracy) + : datums(datumsIn), positionalAccuracy(accuracy) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +DatumEnsemble::DatumEnsemble(const std::vector &datumsIn, + const metadata::PositionalAccuracyNNPtr &accuracy) + : d(internal::make_unique(datumsIn, accuracy)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +DatumEnsemble::DatumEnsemble(const DatumEnsemble &other) + : common::IdentifiedObject(other), + d(internal::make_unique(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DatumEnsemble::~DatumEnsemble() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the set of datums which may be considered to be + * insignificantly different from each other. + * + * @return the set of datums of the DatumEnsemble. + */ +const std::vector &DatumEnsemble::datums() const { + return d->datums; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the inaccuracy introduced through use of this collection of + * datums. + * + * It is an indication of the differences in coordinate values at all points + * between the various realizations that have been grouped into this datum + * ensemble. + * + * @return the accuracy. + */ +const metadata::PositionalAccuracyNNPtr & +DatumEnsemble::positionalAccuracy() const { + return d->positionalAccuracy; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DatumEnsemble::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2 || !formatter->use2018Keywords()) { + throw io::FormattingException( + "DatumEnsemble can only be exported to WKT2:2018"); + } + + auto l_datums = datums(); + assert(!l_datums.empty()); + + formatter->startNode(io::WKTConstants::ENSEMBLE, false); + const auto &l_name = nameStr(); + if (!l_name.empty()) { + formatter->addQuotedString(l_name); + } else { + formatter->addQuotedString("unnamed"); + } + + for (const auto &datum : l_datums) { + formatter->startNode(io::WKTConstants::MEMBER, + !datum->identifiers().empty()); + const auto &l_datum_name = datum->nameStr(); + if (!l_datum_name.empty()) { + formatter->addQuotedString(l_datum_name); + } else { + formatter->addQuotedString("unnamed"); + } + if (formatter->outputId()) { + datum->formatID(formatter); + } + formatter->endNode(); + } + + auto grfFirst = std::dynamic_pointer_cast( + l_datums[0].as_nullable()); + if (grfFirst) { + grfFirst->ellipsoid()->_exportToWKT(formatter); + } + + formatter->startNode(io::WKTConstants::ENSEMBLEACCURACY, false); + formatter->add(positionalAccuracy()->value()); + formatter->endNode(); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DatumEnsemble. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumsIn Array of at least 2 datums. + * @param accuracy Accuracy of the datum ensemble + * @return new DatumEnsemble. + * @throw util::Exception + */ +DatumEnsembleNNPtr DatumEnsemble::create( + const util::PropertyMap &properties, + const std::vector &datumsIn, + const metadata::PositionalAccuracyNNPtr &accuracy) // throw(Exception) +{ + if (datumsIn.size() < 2) { + throw util::Exception("ensemble should have at least 2 datums"); + } + if (auto grfFirst = + dynamic_cast(datumsIn[0].get())) { + for (size_t i = 1; i < datumsIn.size(); i++) { + auto grf = + dynamic_cast(datumsIn[i].get()); + if (!grf) { + throw util::Exception( + "ensemble should have consistent datum types"); + } + if (!grfFirst->ellipsoid()->_isEquivalentTo( + grf->ellipsoid().get())) { + throw util::Exception( + "ensemble should have datums with identical ellipsoid"); + } + if (!grfFirst->primeMeridian()->_isEquivalentTo( + grf->primeMeridian().get())) { + throw util::Exception( + "ensemble should have datums with identical " + "prime meridian"); + } + } + } else if (dynamic_cast(datumsIn[0].get())) { + for (size_t i = 1; i < datumsIn.size(); i++) { + if (!dynamic_cast(datumsIn[i].get())) { + throw util::Exception( + "ensemble should have consistent datum types"); + } + } + } + auto ensemble( + DatumEnsemble::nn_make_shared(datumsIn, accuracy)); + ensemble->setProperties(properties); + return ensemble; +} + +// --------------------------------------------------------------------------- + +RealizationMethod::RealizationMethod(const std::string &nameIn) + : CodeList(nameIn) {} + +// --------------------------------------------------------------------------- + +RealizationMethod::RealizationMethod(const RealizationMethod &) = default; + +// --------------------------------------------------------------------------- + +RealizationMethod &RealizationMethod:: +operator=(const RealizationMethod &other) { + CodeList::operator=(other); + return *this; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct VerticalReferenceFrame::Private { + util::optional realizationMethod_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +VerticalReferenceFrame::VerticalReferenceFrame( + const util::optional &realizationMethodIn) + : d(internal::make_unique()) { + if (!realizationMethodIn->toString().empty()) { + d->realizationMethod_ = *realizationMethodIn; + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalReferenceFrame::~VerticalReferenceFrame() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the method through which this vertical reference frame is + * realized. + * + * @return the realization method. + */ +const util::optional & +VerticalReferenceFrame::realizationMethod() const { + return d->realizationMethod_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalReferenceFrame + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param anchor the anchor definition, or empty. + * @param realizationMethodIn the realization method, or empty. + * @return new VerticalReferenceFrame. + */ +VerticalReferenceFrameNNPtr VerticalReferenceFrame::create( + const util::PropertyMap &properties, + const util::optional &anchor, + const util::optional &realizationMethodIn) { + auto rf(VerticalReferenceFrame::nn_make_shared( + realizationMethodIn)); + rf->setAnchor(anchor); + rf->setProperties(properties); + return rf; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void VerticalReferenceFrame::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::VDATUM + : io::WKTConstants::VERT_DATUM, + !identifiers().empty()); + const auto &l_name = nameStr(); + if (!l_name.empty()) { + formatter->addQuotedString(l_name); + } else { + formatter->addQuotedString("unnamed"); + } + if (isWKT2) { + Datum::getPrivate()->exportAnchorDefinition(formatter); + } else { + formatter->add(2005); // CS_VD_GeoidModelDerived from OGC 01-009 + const auto &extension = formatter->getVDatumExtension(); + if (!extension.empty()) { + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4_GRIDS"); + formatter->addQuotedString(extension); + formatter->endNode(); + } + } + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool VerticalReferenceFrame::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherVRF = dynamic_cast(other); + if (otherVRF == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + return false; + } + if ((realizationMethod().has_value() ^ + otherVRF->realizationMethod().has_value())) { + return false; + } + if (realizationMethod().has_value() && + otherVRF->realizationMethod().has_value()) { + if (*(realizationMethod()) != *(otherVRF->realizationMethod())) { + return false; + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DynamicVerticalReferenceFrame::Private { + common::Measure frameReferenceEpoch{}; + util::optional deformationModelName{}; + + explicit Private(const common::Measure &frameReferenceEpochIn) + : frameReferenceEpoch(frameReferenceEpochIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +DynamicVerticalReferenceFrame::DynamicVerticalReferenceFrame( + const util::optional &realizationMethodIn, + const common::Measure &frameReferenceEpochIn, + const util::optional &deformationModelNameIn) + : VerticalReferenceFrame(realizationMethodIn), + d(internal::make_unique(frameReferenceEpochIn)) { + d->deformationModelName = deformationModelNameIn; +} + +// --------------------------------------------------------------------------- + +#ifdef notdef +DynamicVerticalReferenceFrame::DynamicVerticalReferenceFrame( + const DynamicVerticalReferenceFrame &other) + : VerticalReferenceFrame(other), + d(internal::make_unique(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DynamicVerticalReferenceFrame::~DynamicVerticalReferenceFrame() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the epoch to which the coordinates of stations defining the + * dynamic geodetic reference frame are referenced. + * + * Usually given as a decimal year e.g. 2016.47. + * + * @return the frame reference epoch. + */ +const common::Measure & +DynamicVerticalReferenceFrame::frameReferenceEpoch() const { + return d->frameReferenceEpoch; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the deformation model. + * + * @note This is an extension to the \ref ISO_19111_2018 modeling, to + * hold the content of the DYNAMIC.MODEL WKT2 node. + * + * @return the name of the deformation model. + */ +const util::optional & +DynamicVerticalReferenceFrame::deformationModelName() const { + return d->deformationModelName; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool DynamicVerticalReferenceFrame::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDGRF = dynamic_cast(other); + if (otherDGRF == nullptr || + !VerticalReferenceFrame::_isEquivalentTo(other, criterion)) { + return false; + } + return frameReferenceEpoch()._isEquivalentTo( + otherDGRF->frameReferenceEpoch(), criterion) && + metadata::Identifier::isEquivalentName( + deformationModelName()->c_str(), + otherDGRF->deformationModelName()->c_str()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DynamicVerticalReferenceFrame::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (isWKT2 && formatter->use2018Keywords()) { + formatter->startNode(io::WKTConstants::DYNAMIC, false); + formatter->startNode(io::WKTConstants::FRAMEEPOCH, false); + formatter->add( + frameReferenceEpoch().convertToUnit(common::UnitOfMeasure::YEAR)); + formatter->endNode(); + if (!deformationModelName()->empty()) { + formatter->startNode(io::WKTConstants::MODEL, false); + formatter->addQuotedString(*deformationModelName()); + formatter->endNode(); + } + formatter->endNode(); + } + VerticalReferenceFrame::_exportToWKT(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DyanmicVerticalReferenceFrame + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param anchor the anchor definition, or empty. + * @param realizationMethodIn the realization method, or empty. + * @param frameReferenceEpochIn the frame reference epoch. + * @param deformationModelNameIn deformation model name, or empty + * @return new DyanmicVerticalReferenceFrame. + */ +DynamicVerticalReferenceFrameNNPtr DynamicVerticalReferenceFrame::create( + const util::PropertyMap &properties, + const util::optional &anchor, + const util::optional &realizationMethodIn, + const common::Measure &frameReferenceEpochIn, + const util::optional &deformationModelNameIn) { + DynamicVerticalReferenceFrameNNPtr grf( + DynamicVerticalReferenceFrame::nn_make_shared< + DynamicVerticalReferenceFrame>(realizationMethodIn, + frameReferenceEpochIn, + deformationModelNameIn)); + grf->setAnchor(anchor); + grf->setProperties(properties); + return grf; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct TemporalDatum::Private { + common::DateTime temporalOrigin_; + std::string calendar_; + + Private(const common::DateTime &temporalOriginIn, + const std::string &calendarIn) + : temporalOrigin_(temporalOriginIn), calendar_(calendarIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +TemporalDatum::TemporalDatum(const common::DateTime &temporalOriginIn, + const std::string &calendarIn) + : d(internal::make_unique(temporalOriginIn, calendarIn)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalDatum::~TemporalDatum() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the date and time to which temporal coordinates are + * referenced, expressed in conformance with ISO 8601. + * + * @return the temporal origin. + */ +const common::DateTime &TemporalDatum::temporalOrigin() const { + return d->temporalOrigin_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the calendar to which the temporal origin is referenced + * + * Default value: TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN. + * + * @return the calendar. + */ +const std::string &TemporalDatum::calendar() const { return d->calendar_; } + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a TemporalDatum + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param temporalOriginIn the temporal origin into which temporal coordinates + * are referenced. + * @param calendarIn the calendar (generally + * TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN) + * @return new TemporalDatum. + */ +TemporalDatumNNPtr +TemporalDatum::create(const util::PropertyMap &properties, + const common::DateTime &temporalOriginIn, + const std::string &calendarIn) { + auto datum(TemporalDatum::nn_make_shared(temporalOriginIn, + calendarIn)); + datum->setProperties(properties); + return datum; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void TemporalDatum::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + throw io::FormattingException( + "TemporalDatum can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::TDATUM, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + if (formatter->use2018Keywords()) { + formatter->startNode(io::WKTConstants::CALENDAR, false); + formatter->addQuotedString(calendar()); + formatter->endNode(); + } + + const auto &timeOriginStr = temporalOrigin().toString(); + if (!timeOriginStr.empty()) { + formatter->startNode(io::WKTConstants::TIMEORIGIN, false); + if (temporalOrigin().isISO_8601()) { + formatter->add(timeOriginStr); + } else { + formatter->addQuotedString(timeOriginStr); + } + formatter->endNode(); + } + + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool TemporalDatum::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherTD = dynamic_cast(other); + if (otherTD == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + return false; + } + return temporalOrigin().toString() == + otherTD->temporalOrigin().toString() && + calendar() == otherTD->calendar(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct EngineeringDatum::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +EngineeringDatum::EngineeringDatum() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +EngineeringDatum::~EngineeringDatum() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EngineeringDatum + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param anchor the anchor definition, or empty. + * @return new EngineeringDatum. + */ +EngineeringDatumNNPtr +EngineeringDatum::create(const util::PropertyMap &properties, + const util::optional &anchor) { + auto datum(EngineeringDatum::nn_make_shared()); + datum->setAnchor(anchor); + datum->setProperties(properties); + return datum; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void EngineeringDatum::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::EDATUM + : io::WKTConstants::LOCAL_DATUM, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + if (isWKT2) { + Datum::getPrivate()->exportAnchorDefinition(formatter); + } else { + // Somewhat picked up arbitrarily from OGC 01-009: + // CS_LD_Max (Attribute) : 32767 + // Highest possible value for local datum types. + formatter->add(32767); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool EngineeringDatum::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherTD = dynamic_cast(other); + if (otherTD == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + return false; + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ParametricDatum::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +ParametricDatum::ParametricDatum() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ParametricDatum::~ParametricDatum() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParametricDatum + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param anchor the anchor definition, or empty. + * @return new ParametricDatum. + */ +ParametricDatumNNPtr +ParametricDatum::create(const util::PropertyMap &properties, + const util::optional &anchor) { + auto datum(ParametricDatum::nn_make_shared()); + datum->setAnchor(anchor); + datum->setProperties(properties); + return datum; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ParametricDatum::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + throw io::FormattingException( + "ParametricDatum can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::PDATUM, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + Datum::getPrivate()->exportAnchorDefinition(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool ParametricDatum::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherTD = dynamic_cast(other); + if (otherTD == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + return false; + } + return true; +} +//! @endcond + +} // namespace datum +NS_PROJ_END diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp new file mode 100644 index 00000000..47d31db9 --- /dev/null +++ b/src/iso19111/factory.cpp @@ -0,0 +1,4973 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 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/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/coordinateoperation_internal.hpp" +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" +#include "proj/internal/lru_cache.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include // std::ostringstream +#include + +#include "proj_constants.h" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "projects.h" +// clang-format on + +#include + +// Custom SQLite VFS as our database is not supposed to be modified in +// parallel. This is slightly faster +#define ENABLE_CUSTOM_LOCKLESS_VFS + +using namespace NS_PROJ::internal; +using namespace NS_PROJ::common; + +NS_PROJ_START +namespace io { + +//! @cond Doxygen_Suppress + +#define GEOG_2D "'geographic 2D'" +#define GEOG_3D "'geographic 3D'" +#define GEOCENTRIC "'geocentric'" + +// --------------------------------------------------------------------------- + +struct SQLValues { + enum class Type { STRING, DOUBLE }; + + // cppcheck-suppress noExplicitConstructor + SQLValues(const std::string &value) : type_(Type::STRING), str_(value) {} + + // cppcheck-suppress noExplicitConstructor + SQLValues(double value) : type_(Type::DOUBLE), double_(value) {} + + const Type &type() const { return type_; } + + // cppcheck-suppress functionStatic + const std::string &stringValue() const { return str_; } + + // cppcheck-suppress functionStatic + double doubleValue() const { return double_; } + + private: + Type type_; + std::string str_{}; + double double_ = 0.0; +}; + +// --------------------------------------------------------------------------- + +using SQLRow = std::vector; +using SQLResultSet = std::list; +using ListOfParams = std::list; + +// --------------------------------------------------------------------------- + +struct DatabaseContext::Private { + Private(); + ~Private(); + + void open(const std::string &databasePath = std::string()); + void setHandle(sqlite3 *sqlite_handle); + + sqlite3 *handle() const { return sqlite_handle_; } + + PJ_CONTEXT *pjCtxt() const { return pjCtxt_; } + void setPjCtxt(PJ_CONTEXT *ctxt) { pjCtxt_ = ctxt; } + + SQLResultSet run(const std::string &sql, + const ListOfParams ¶meters = ListOfParams()); + + std::vector getDatabaseStructure(); + + // cppcheck-suppress functionStatic + const std::string &getPath() const { return databasePath_; } + + void attachExtraDatabases( + const std::vector &auxiliaryDatabasePaths); + + // Mechanism to detect recursion in calls from + // AuthorityFactory::createXXX() -> createFromUserInput() -> + // AuthorityFactory::createXXX() + struct RecursionDetector { + explicit RecursionDetector(const DatabaseContextNNPtr &context) + : dbContext_(context) { + if (dbContext_->getPrivate()->recLevel_ == 2) { + // Throw exception before incrementing, since the destructor + // will not be called + throw FactoryException("Too many recursive calls"); + } + ++dbContext_->getPrivate()->recLevel_; + } + + ~RecursionDetector() { --dbContext_->getPrivate()->recLevel_; } + + private: + DatabaseContextNNPtr dbContext_; + }; + + std::map> &getMapCanonicalizeGRFName() { + return mapCanonicalizeGRFName_; + } + + // cppcheck-suppress functionStatic + common::UnitOfMeasurePtr getUOMFromCache(const std::string &code); + // cppcheck-suppress functionStatic + void cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom); + + // cppcheck-suppress functionStatic + crs::CRSPtr getCRSFromCache(const std::string &code); + // cppcheck-suppress functionStatic + void cache(const std::string &code, const crs::CRSNNPtr &crs); + + datum::GeodeticReferenceFramePtr + // cppcheck-suppress functionStatic + getGeodeticDatumFromCache(const std::string &code); + // cppcheck-suppress functionStatic + void cache(const std::string &code, + const datum::GeodeticReferenceFrameNNPtr &datum); + + datum::PrimeMeridianPtr + // cppcheck-suppress functionStatic + getPrimeMeridianFromCache(const std::string &code); + // cppcheck-suppress functionStatic + void cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm); + + // cppcheck-suppress functionStatic + cs::CoordinateSystemPtr + getCoordinateSystemFromCache(const std::string &code); + // cppcheck-suppress functionStatic + void cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs); + + // cppcheck-suppress functionStatic + metadata::ExtentPtr getExtentFromCache(const std::string &code); + // cppcheck-suppress functionStatic + void cache(const std::string &code, const metadata::ExtentNNPtr &extent); + + // cppcheck-suppress functionStatic + bool getCRSToCRSCoordOpFromCache( + const std::string &code, + std::vector &list); + // cppcheck-suppress functionStatic + void cache(const std::string &code, + const std::vector &list); + + struct GridInfoCache { + std::string fullFilename{}; + std::string packageName{}; + std::string url{}; + bool found = false; + bool directDownload = false; + bool openLicense = false; + bool gridAvailable = false; + }; + + // cppcheck-suppress functionStatic + bool getGridInfoFromCache(const std::string &code, GridInfoCache &info); + // cppcheck-suppress functionStatic + void cache(const std::string &code, const GridInfoCache &info); + + private: + friend class DatabaseContext; + + std::string databasePath_{}; + bool close_handle_ = true; + sqlite3 *sqlite_handle_{}; + std::map mapSqlToStatement_{}; + PJ_CONTEXT *pjCtxt_ = nullptr; + int recLevel_ = 0; + bool detach_ = false; + std::string lastMetadataValue_{}; + std::map> mapCanonicalizeGRFName_{}; + + using LRUCacheOfObjects = lru11::Cache; + + static constexpr size_t CACHE_SIZE = 128; + LRUCacheOfObjects cacheUOM_{CACHE_SIZE}; + LRUCacheOfObjects cacheCRS_{CACHE_SIZE}; + LRUCacheOfObjects cacheGeodeticDatum_{CACHE_SIZE}; + LRUCacheOfObjects cachePrimeMeridian_{CACHE_SIZE}; + LRUCacheOfObjects cacheCS_{CACHE_SIZE}; + LRUCacheOfObjects cacheExtent_{CACHE_SIZE}; + lru11::Cache> + cacheCRSToCrsCoordOp_{CACHE_SIZE}; + lru11::Cache cacheGridInfo_{CACHE_SIZE}; + + static void insertIntoCache(LRUCacheOfObjects &cache, + const std::string &code, + const util::BaseObjectPtr &obj); + + static void getFromCache(LRUCacheOfObjects &cache, const std::string &code, + util::BaseObjectPtr &obj); + + void closeDB(); + + // cppcheck-suppress functionStatic + void registerFunctions(); + +#ifdef ENABLE_CUSTOM_LOCKLESS_VFS + std::string thisNamePtr_{}; + sqlite3_vfs *vfs_{}; + bool createCustomVFS(); +#endif + + Private(const Private &) = delete; + Private &operator=(const Private &) = delete; +}; + +// --------------------------------------------------------------------------- + +DatabaseContext::Private::Private() = default; + +// --------------------------------------------------------------------------- + +DatabaseContext::Private::~Private() { + assert(recLevel_ == 0); + + closeDB(); + +#ifdef ENABLE_CUSTOM_LOCKLESS_VFS + if (vfs_) { + sqlite3_vfs_unregister(vfs_); + delete vfs_; + } +#endif +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::closeDB() { + + if (detach_) { + // Workaround a bug visible in SQLite 3.8.1 and 3.8.2 that causes + // a crash in TEST(factory, attachExtraDatabases_auxiliary) + // due to possible wrong caching of key info. + // The bug is specific to using a memory file with shared cache as an + // auxiliary DB. + // The efinitive fix was likely in 3.8.8 + // https://github.com/mackyle/sqlite/commit/d412d4b8731991ecbd8811874aa463d0821673eb + // But just after 3.8.2, + // https://github.com/mackyle/sqlite/commit/ccf328c4318eacedab9ed08c404bc4f402dcad19 + // also seemed to hide the issue. + // Detaching a database hides the issue, not sure if it is by chance... + run("DETACH DATABASE db_0"); + detach_ = false; + } + + for (auto &pair : mapSqlToStatement_) { + sqlite3_finalize(pair.second); + } + mapSqlToStatement_.clear(); + + if (close_handle_ && sqlite_handle_ != nullptr) { + sqlite3_close(sqlite_handle_); + sqlite_handle_ = nullptr; + } +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache, + const std::string &code, + const util::BaseObjectPtr &obj) { + cache.insert(code, obj); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::getFromCache(LRUCacheOfObjects &cache, + const std::string &code, + util::BaseObjectPtr &obj) { + cache.tryGet(code, obj); +} + +// --------------------------------------------------------------------------- + +bool DatabaseContext::Private::getCRSToCRSCoordOpFromCache( + const std::string &code, + std::vector &list) { + return cacheCRSToCrsCoordOp_.tryGet(code, list); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::cache( + const std::string &code, + const std::vector &list) { + cacheCRSToCrsCoordOp_.insert(code, list); +} + +// --------------------------------------------------------------------------- + +crs::CRSPtr DatabaseContext::Private::getCRSFromCache(const std::string &code) { + util::BaseObjectPtr obj; + getFromCache(cacheCRS_, code, obj); + return std::static_pointer_cast(obj); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::cache(const std::string &code, + const crs::CRSNNPtr &crs) { + insertIntoCache(cacheCRS_, code, crs.as_nullable()); +} + +// --------------------------------------------------------------------------- + +common::UnitOfMeasurePtr +DatabaseContext::Private::getUOMFromCache(const std::string &code) { + util::BaseObjectPtr obj; + getFromCache(cacheUOM_, code, obj); + return std::static_pointer_cast(obj); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::cache(const std::string &code, + const common::UnitOfMeasureNNPtr &uom) { + insertIntoCache(cacheUOM_, code, uom.as_nullable()); +} + +// --------------------------------------------------------------------------- + +datum::GeodeticReferenceFramePtr +DatabaseContext::Private::getGeodeticDatumFromCache(const std::string &code) { + util::BaseObjectPtr obj; + getFromCache(cacheGeodeticDatum_, code, obj); + return std::static_pointer_cast(obj); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::cache( + const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) { + insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable()); +} + +// --------------------------------------------------------------------------- + +datum::PrimeMeridianPtr +DatabaseContext::Private::getPrimeMeridianFromCache(const std::string &code) { + util::BaseObjectPtr obj; + getFromCache(cachePrimeMeridian_, code, obj); + return std::static_pointer_cast(obj); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::cache(const std::string &code, + const datum::PrimeMeridianNNPtr &pm) { + insertIntoCache(cachePrimeMeridian_, code, pm.as_nullable()); +} + +// --------------------------------------------------------------------------- + +cs::CoordinateSystemPtr DatabaseContext::Private::getCoordinateSystemFromCache( + const std::string &code) { + util::BaseObjectPtr obj; + getFromCache(cacheCS_, code, obj); + return std::static_pointer_cast(obj); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::cache(const std::string &code, + const cs::CoordinateSystemNNPtr &cs) { + insertIntoCache(cacheCS_, code, cs.as_nullable()); +} + +// --------------------------------------------------------------------------- + +metadata::ExtentPtr +DatabaseContext::Private::getExtentFromCache(const std::string &code) { + util::BaseObjectPtr obj; + getFromCache(cacheExtent_, code, obj); + return std::static_pointer_cast(obj); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::cache(const std::string &code, + const metadata::ExtentNNPtr &extent) { + insertIntoCache(cacheExtent_, code, extent.as_nullable()); +} + +// --------------------------------------------------------------------------- + +bool DatabaseContext::Private::getGridInfoFromCache(const std::string &code, + GridInfoCache &info) { + return cacheGridInfo_.tryGet(code, info); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::cache(const std::string &code, + const GridInfoCache &info) { + cacheGridInfo_.insert(code, info); +} + +// --------------------------------------------------------------------------- + +#ifdef ENABLE_CUSTOM_LOCKLESS_VFS + +typedef int (*ClosePtr)(sqlite3_file *); + +static int VFSClose(sqlite3_file *file) { + sqlite3_vfs *defaultVFS = sqlite3_vfs_find(nullptr); + assert(defaultVFS); + ClosePtr defaultClosePtr; + std::memcpy(&defaultClosePtr, + reinterpret_cast(file) + defaultVFS->szOsFile, + sizeof(ClosePtr)); + void *methods = const_cast(file->pMethods); + int ret = defaultClosePtr(file); + std::free(methods); + return ret; +} + +// No-lock implementation +static int VSFLock(sqlite3_file *, int) { return SQLITE_OK; } + +static int VSFUnlock(sqlite3_file *, int) { return SQLITE_OK; } + +static int VFSOpen(sqlite3_vfs *vfs, const char *name, sqlite3_file *file, + int flags, int *outFlags) { + sqlite3_vfs *defaultVFS = static_cast(vfs->pAppData); + int ret = defaultVFS->xOpen(defaultVFS, name, file, flags, outFlags); + if (ret == SQLITE_OK) { + ClosePtr defaultClosePtr = file->pMethods->xClose; + assert(defaultClosePtr); + sqlite3_io_methods *methods = static_cast( + std::malloc(sizeof(sqlite3_io_methods))); + if (!methods) { + file->pMethods->xClose(file); + return SQLITE_NOMEM; + } + memcpy(methods, file->pMethods, sizeof(sqlite3_io_methods)); + methods->xClose = VFSClose; + methods->xLock = VSFLock; + methods->xUnlock = VSFUnlock; + file->pMethods = methods; + // Save original xClose pointer at end of file structure + std::memcpy(reinterpret_cast(file) + defaultVFS->szOsFile, + &defaultClosePtr, sizeof(ClosePtr)); + } + return ret; +} + +static int VFSAccess(sqlite3_vfs *vfs, const char *zName, int flags, + int *pResOut) { + sqlite3_vfs *defaultVFS = static_cast(vfs->pAppData); + // Do not bother stat'ing for journal or wal files + if (std::strstr(zName, "-journal") || std::strstr(zName, "-wal")) { + *pResOut = false; + return SQLITE_OK; + } + return defaultVFS->xAccess(defaultVFS, zName, flags, pResOut); +} + +// --------------------------------------------------------------------------- + +bool DatabaseContext::Private::createCustomVFS() { + + sqlite3_vfs *defaultVFS = sqlite3_vfs_find(nullptr); + assert(defaultVFS); + + std::ostringstream buffer; + buffer << this; + thisNamePtr_ = buffer.str(); + + vfs_ = new sqlite3_vfs(); + vfs_->iVersion = 1; + vfs_->szOsFile = defaultVFS->szOsFile + sizeof(ClosePtr); + vfs_->mxPathname = defaultVFS->mxPathname; + vfs_->zName = thisNamePtr_.c_str(); + vfs_->pAppData = defaultVFS; + vfs_->xOpen = VFSOpen; + vfs_->xDelete = defaultVFS->xDelete; + vfs_->xAccess = VFSAccess; + vfs_->xFullPathname = defaultVFS->xFullPathname; + vfs_->xDlOpen = defaultVFS->xDlOpen; + vfs_->xDlError = defaultVFS->xDlError; + vfs_->xDlSym = defaultVFS->xDlSym; + vfs_->xDlClose = defaultVFS->xDlClose; + vfs_->xRandomness = defaultVFS->xRandomness; + vfs_->xSleep = defaultVFS->xSleep; + vfs_->xCurrentTime = defaultVFS->xCurrentTime; + vfs_->xGetLastError = defaultVFS->xGetLastError; + vfs_->xCurrentTimeInt64 = defaultVFS->xCurrentTimeInt64; + return sqlite3_vfs_register(vfs_, false) == SQLITE_OK; +} + +#endif // ENABLE_CUSTOM_LOCKLESS_VFS + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::open(const std::string &databasePath) { + std::string path(databasePath); + if (path.empty()) { + const char *proj_lib = std::getenv("PROJ_LIB"); +#ifdef PROJ_LIB + if (!proj_lib) { + proj_lib = PROJ_LIB; + } +#endif + if (!proj_lib) { + throw FactoryException( + "Cannot find proj.db due to missing PROJ_LIB"); + } + path = std::string(proj_lib) + DIR_CHAR + "proj.db"; + } + + if ( +#ifdef ENABLE_CUSTOM_LOCKLESS_VFS + !createCustomVFS() || +#endif + sqlite3_open_v2(path.c_str(), &sqlite_handle_, + SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX, +#ifdef ENABLE_CUSTOM_LOCKLESS_VFS + thisNamePtr_.c_str() +#else + nullptr +#endif + ) != SQLITE_OK || + !sqlite_handle_) { + throw FactoryException("Open of " + path + " failed"); + } + + databasePath_ = path; + registerFunctions(); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) { + + assert(sqlite_handle); + assert(!sqlite_handle_); + sqlite_handle_ = sqlite_handle; + close_handle_ = false; + + registerFunctions(); +} + +// --------------------------------------------------------------------------- + +std::vector DatabaseContext::Private::getDatabaseStructure() { + auto sqlRes = run("SELECT sql FROM sqlite_master WHERE type " + "IN ('table', 'trigger', 'view') ORDER BY type"); + std::vector res; + for (const auto &row : sqlRes) { + res.emplace_back(row[0]); + } + return res; +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::attachExtraDatabases( + const std::vector &auxiliaryDatabasePaths) { + assert(close_handle_); + assert(sqlite_handle_); + + auto tables = + run("SELECT name FROM sqlite_master WHERE type IN ('table', 'view')"); + std::map> tableStructure; + for (const auto &rowTable : tables) { + auto tableName = rowTable[0]; + auto tableInfo = run("PRAGMA table_info(\"" + + replaceAll(tableName, "\"", "\"\"") + "\")"); + for (const auto &rowCol : tableInfo) { + const auto &colName = rowCol[1]; + tableStructure[tableName].push_back(colName); + } + } + + closeDB(); + + sqlite3_open_v2(":memory:", &sqlite_handle_, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX +#ifdef SQLITE_OPEN_URI + | SQLITE_OPEN_URI +#endif + , + nullptr); + if (!sqlite_handle_) { + throw FactoryException("cannot create in memory database"); + } + + run("ATTACH DATABASE '" + replaceAll(databasePath_, "'", "''") + + "' AS db_0"); + detach_ = true; + int count = 1; + for (const auto &otherDb : auxiliaryDatabasePaths) { + std::string sql = "ATTACH DATABASE '"; + sql += replaceAll(otherDb, "'", "''"); + sql += "' AS db_"; + sql += toString(static_cast(count)); + count++; + run(sql); + } + + for (const auto &pair : tableStructure) { + std::string sql("CREATE TEMP VIEW "); + sql += pair.first; + sql += " AS "; + for (size_t i = 0; i <= auxiliaryDatabasePaths.size(); ++i) { + std::string selectFromAux("SELECT "); + bool firstCol = true; + for (const auto &colName : pair.second) { + if (!firstCol) { + selectFromAux += ", "; + } + firstCol = false; + selectFromAux += colName; + } + selectFromAux += " FROM db_"; + selectFromAux += toString(static_cast(i)); + selectFromAux += "."; + selectFromAux += pair.first; + + try { + // Check that the request will succeed. In case of 'sparse' + // databases... + run(selectFromAux + " LIMIT 0"); + + if (i > 0) { + sql += " UNION ALL "; + } + sql += selectFromAux; + } catch (const std::exception &) { + } + } + run(sql); + } + + registerFunctions(); +} + +// --------------------------------------------------------------------------- + +static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) { + switch (sqlite3_value_type(val)) { + case SQLITE_FLOAT: + gotVal = true; + return sqlite3_value_double(val); + + case SQLITE_INTEGER: + gotVal = true; + return static_cast(sqlite3_value_int64(val)); + + default: + gotVal = false; + return 0.0; + } +} + +// --------------------------------------------------------------------------- + +static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext, + int /* argc */, + sqlite3_value **argv) { + bool b0, b1, b2, b3; + double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0); + double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1); + double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2); + double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3); + if (!b0 || !b1 || !b2 || !b3) { + sqlite3_result_null(pContext); + return; + } + // Deal with area crossing antimeridian + if (east_lon < west_lon) { + east_lon += 360.0; + } + // Integrate cos(lat) between south_lat and north_lat + double pseudo_area = (east_lon - west_lon) * + (std::sin(common::Angle(north_lat).getSIValue()) - + std::sin(common::Angle(south_lat).getSIValue())); + sqlite3_result_double(pContext, pseudo_area); +} + +// --------------------------------------------------------------------------- + +static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext, + int /* argc */, sqlite3_value **argv) { + bool b0, b1, b2, b3, b4, b5, b6, b7; + double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0); + double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1); + double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2); + double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3); + double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4); + double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5); + double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6); + double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7); + if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) { + sqlite3_result_null(pContext); + return; + } + auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1, + east_lon1, north_lat1); + auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2, + east_lon2, north_lat2); + sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0); +} + +// --------------------------------------------------------------------------- + +#ifndef SQLITE_DETERMINISTIC +#define SQLITE_DETERMINISTIC 0 +#endif + +void DatabaseContext::Private::registerFunctions() { + sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4, + SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, + PROJ_SQLITE_pseudo_area_from_swne, nullptr, + nullptr); + + sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8, + SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, + PROJ_SQLITE_intersects_bbox, nullptr, nullptr); +} + +// --------------------------------------------------------------------------- + +SQLResultSet DatabaseContext::Private::run(const std::string &sql, + const ListOfParams ¶meters) { + + sqlite3_stmt *stmt = nullptr; + auto iter = mapSqlToStatement_.find(sql); + if (iter != mapSqlToStatement_.end()) { + stmt = iter->second; + sqlite3_reset(stmt); + } else { + if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(), + static_cast(sql.size()), &stmt, + nullptr) != SQLITE_OK) { + throw FactoryException("SQLite error on " + sql + ": " + + sqlite3_errmsg(sqlite_handle_)); + } + mapSqlToStatement_.insert( + std::pair(sql, stmt)); + } + + int nBindField = 1; + for (const auto ¶m : parameters) { + if (param.type() == SQLValues::Type::STRING) { + auto strValue = param.stringValue(); + sqlite3_bind_text(stmt, nBindField, strValue.c_str(), + static_cast(strValue.size()), + SQLITE_TRANSIENT); + } else { + assert(param.type() == SQLValues::Type::DOUBLE); + sqlite3_bind_double(stmt, nBindField, param.doubleValue()); + } + nBindField++; + } + + SQLResultSet result; + const int column_count = sqlite3_column_count(stmt); + while (true) { + int ret = sqlite3_step(stmt); + if (ret == SQLITE_ROW) { + SQLRow row(column_count); + for (int i = 0; i < column_count; i++) { + const char *txt = reinterpret_cast( + sqlite3_column_text(stmt, i)); + if (txt) { + row[i] = txt; + } + } + result.emplace_back(std::move(row)); + } else if (ret == SQLITE_DONE) { + break; + } else { + throw FactoryException("SQLite error on " + sql + ": " + + sqlite3_errmsg(sqlite_handle_)); + } + } + return result; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DatabaseContext::~DatabaseContext() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DatabaseContext::DatabaseContext() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a database context, using the default proj.db file + * + * It will be searched in the directory pointed by the PROJ_LIB environment + * variable. If not found, on Unix builds, it will be then searched first in + * the pkgdatadir directory of the installation prefix. + * + * This database context should be used only by one thread at a time. + * @throw FactoryException + */ +DatabaseContextNNPtr DatabaseContext::create() { + return create(std::string(), {}); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a database context from a full filename. + * + * This database context should be used only by one thread at a time. + * @param databasePath Path and filename of the database. Might be empty + * string for the default rules to locate the default proj.db + * @throw FactoryException + */ +DatabaseContextNNPtr DatabaseContext::create(const std::string &databasePath) { + return create(databasePath, {}); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a database context from a full filename, and attach + * auxiliary databases to it. + * + * This database context should be used only by one thread at a time. + * @param databasePath Path and filename of the database. Might be empty + * string for the default rules to locate the default proj.db + * @param auxiliaryDatabasePaths Path and filename of auxiliary databases; + * @throw FactoryException + */ +DatabaseContextNNPtr DatabaseContext::create( + const std::string &databasePath, + const std::vector &auxiliaryDatabasePaths) { + auto ctxt = DatabaseContext::nn_make_shared(); + ctxt->getPrivate()->open(databasePath); + if (!auxiliaryDatabasePaths.empty()) { + ctxt->getPrivate()->attachExtraDatabases(auxiliaryDatabasePaths); + } + return ctxt; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the list of authorities used in the database. + */ +std::set DatabaseContext::getAuthorities() const { + auto res = d->run("SELECT auth_name FROM authority_list"); + std::set list; + for (const auto &row : res) { + list.insert(row[0]); + } + return list; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the list of SQL commands (CREATE TABLE, CREATE TRIGGER, + * CREATE VIEW) needed to initialize a new database. + */ +std::vector DatabaseContext::getDatabaseStructure() const { + return d->getDatabaseStructure(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the path to the database. + */ +const std::string &DatabaseContext::getPath() const { return d->getPath(); } + +// --------------------------------------------------------------------------- + +/** \brief Return a metadata item. + * + * Value remains valid while this is alive and to the next call to getMetadata + */ +const char *DatabaseContext::getMetadata(const char *key) const { + auto res = + d->run("SELECT value FROM metadata WHERE key = ?", {std::string(key)}); + if (res.empty()) { + return nullptr; + } + d->lastMetadataValue_ = res.front()[0]; + return d->lastMetadataValue_.c_str(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) { + auto ctxt = DatabaseContext::nn_make_shared(); + ctxt->getPrivate()->setHandle(static_cast(sqlite_handle)); + return ctxt; +} + +// --------------------------------------------------------------------------- + +void *DatabaseContext::getSqliteHandle() const { + return getPrivate()->handle(); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::attachPJContext(void *pjCtxt) { + d->setPjCtxt(static_cast(pjCtxt)); +} + +// --------------------------------------------------------------------------- + +bool DatabaseContext::lookForGridAlternative(const std::string &officialName, + std::string &projFilename, + std::string &projFormat, + bool &inverse) const { + auto res = d->run( + "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM " + "grid_alternatives WHERE original_grid_name = ?", + {officialName}); + if (res.empty()) { + return false; + } + const auto &row = res.front(); + projFilename = row[0]; + projFormat = row[1]; + inverse = row[2] == "1"; + return true; +} + +// --------------------------------------------------------------------------- + +bool DatabaseContext::lookForGridInfo(const std::string &projFilename, + std::string &fullFilename, + std::string &packageName, + std::string &url, bool &directDownload, + bool &openLicense, + bool &gridAvailable) const { + Private::GridInfoCache info; + if (d->getGridInfoFromCache(projFilename, info)) { + fullFilename = info.fullFilename; + packageName = info.packageName; + url = info.url; + directDownload = info.directDownload; + openLicense = info.openLicense; + gridAvailable = info.gridAvailable; + return info.found; + } + + fullFilename.clear(); + packageName.clear(); + url.clear(); + openLicense = false; + directDownload = false; + + fullFilename.resize(2048); + if (d->pjCtxt() == nullptr) { + d->setPjCtxt(pj_get_default_ctx()); + } + gridAvailable = + pj_find_file(d->pjCtxt(), projFilename.c_str(), &fullFilename[0], + fullFilename.size() - 1) != 0; + fullFilename.resize(strlen(fullFilename.c_str())); + + auto res = + d->run("SELECT " + "grid_packages.package_name, " + "grid_alternatives.url, " + "grid_packages.url AS package_url, " + "grid_alternatives.open_license, " + "grid_packages.open_license AS package_open_license, " + "grid_alternatives.direct_download, " + "grid_packages.direct_download AS package_direct_download " + "FROM grid_alternatives " + "LEFT JOIN grid_packages ON " + "grid_alternatives.package_name = grid_packages.package_name " + "WHERE proj_grid_name = ?", + {projFilename}); + bool ret = !res.empty(); + if (ret) { + const auto &row = res.front(); + packageName = std::move(row[0]); + url = row[1].empty() ? std::move(row[2]) : std::move(row[1]); + openLicense = (row[3].empty() ? row[4] : row[3]) == "1"; + directDownload = (row[5].empty() ? row[6] : row[5]) == "1"; + + info.fullFilename = fullFilename; + info.packageName = packageName; + info.url = url; + info.directDownload = directDownload; + info.openLicense = openLicense; + info.gridAvailable = gridAvailable; + } + info.found = ret; + d->cache(projFilename, info); + return ret; +} + +// --------------------------------------------------------------------------- + +bool DatabaseContext::isKnownName(const std::string &name, + const std::string &tableName) const { + std::string sql("SELECT 1 FROM \""); + sql += replaceAll(tableName, "\"", "\"\""); + sql += "\" WHERE name = ? LIMIT 1"; + return !d->run(sql, {name}).empty(); +} + +// --------------------------------------------------------------------------- + +/** \brief Gets the alias name from an official name. + * + * @param officialName Official name. + * @param tableName Table name/category. Mandatory + * @param source Source of the alias. Mandatory + * @return Alias name (or empty if not found). + * @throw FactoryException + */ +std::string +DatabaseContext::getAliasFromOfficialName(const std::string &officialName, + const std::string &tableName, + const std::string &source) const { + std::string sql("SELECT auth_name, code FROM \""); + sql += replaceAll(tableName, "\"", "\"\""); + sql += "\" WHERE name = ?"; + if (tableName == "geodetic_crs") { + sql += " AND type = " GEOG_2D; + } + auto res = d->run(sql, {officialName}); + if (res.empty()) { + return std::string(); + } + const auto &row = res.front(); + res = d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND " + "auth_name = ? AND code = ? AND source = ?", + {tableName, row[0], row[1], source}); + if (res.empty()) { + return std::string(); + } + return res.front()[0]; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the 'text_definition' column of a table for an object + * + * @param tableName Table name/category. + * @param authName Authority name of the object. + * @param code Code of the object + * @return Text definition (or empty) + * @throw FactoryException + */ +std::string DatabaseContext::getTextDefinition(const std::string &tableName, + const std::string &authName, + const std::string &code) const { + std::string sql("SELECT text_definition FROM \""); + sql += replaceAll(tableName, "\"", "\"\""); + sql += "\" WHERE auth_name = ? AND code = ?"; + auto res = d->run(sql, {authName, code}); + if (res.empty()) { + return std::string(); + } + return res.front()[0]; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the allowed authorities when researching transformations + * between different authorities. + * + * @throw FactoryException + */ +std::vector DatabaseContext::getAllowedAuthorities( + const std::string &sourceAuthName, + const std::string &targetAuthName) const { + auto res = d->run( + "SELECT allowed_authorities FROM authority_to_authority_preference " + "WHERE source_auth_name = ? AND target_auth_name = ?", + {sourceAuthName, targetAuthName}); + if (res.empty()) { + res = d->run( + "SELECT allowed_authorities FROM authority_to_authority_preference " + "WHERE source_auth_name = ? AND target_auth_name = 'any'", + {sourceAuthName}); + } + if (res.empty()) { + res = d->run( + "SELECT allowed_authorities FROM authority_to_authority_preference " + "WHERE source_auth_name = 'any' AND target_auth_name = ?", + {targetAuthName}); + } + if (res.empty()) { + res = d->run( + "SELECT allowed_authorities FROM authority_to_authority_preference " + "WHERE source_auth_name = 'any' AND target_auth_name = 'any'", + {}); + } + if (res.empty()) { + return std::vector(); + } + return split(res.front()[0], ','); +} + +// --------------------------------------------------------------------------- + +std::list> +DatabaseContext::getNonDeprecated(const std::string &tableName, + const std::string &authName, + const std::string &code) const { + auto sqlRes = + d->run("SELECT replacement_auth_name, replacement_code, source " + "FROM deprecation " + "WHERE table_name = ? AND deprecated_auth_name = ? " + "AND deprecated_code = ?", + {tableName, authName, code}); + std::list> res; + for (const auto &row : sqlRes) { + const auto &source = row[2]; + if (source == "PROJ") { + const auto &replacement_auth_name = row[0]; + const auto &replacement_code = row[1]; + res.emplace_back(replacement_auth_name, replacement_code); + } + } + if (!res.empty()) { + return res; + } + for (const auto &row : sqlRes) { + const auto &replacement_auth_name = row[0]; + const auto &replacement_code = row[1]; + res.emplace_back(replacement_auth_name, replacement_code); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct AuthorityFactory::Private { + Private(const DatabaseContextNNPtr &contextIn, + const std::string &authorityName) + : context_(contextIn), authority_(authorityName) {} + + inline const std::string &authority() PROJ_CONST_DEFN { return authority_; } + + inline const DatabaseContextNNPtr &context() PROJ_CONST_DEFN { + return context_; + } + + // cppcheck-suppress functionStatic + void setThis(AuthorityFactoryNNPtr factory) { + thisFactory_ = factory.as_nullable(); + } + + // cppcheck-suppress functionStatic + AuthorityFactoryPtr getSharedFromThis() { return thisFactory_.lock(); } + + inline AuthorityFactoryNNPtr createFactory(const std::string &auth_name) { + if (auth_name == authority_) { + return NN_NO_CHECK(thisFactory_.lock()); + } + return AuthorityFactory::create(context_, auth_name); + } + + bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op, + bool discardIfMissingGrid); + + UnitOfMeasure createUnitOfMeasure(const std::string &auth_name, + const std::string &code); + + util::PropertyMap createProperties(const std::string &code, + const std::string &name, bool deprecated, + const metadata::ExtentPtr &extent); + + util::PropertyMap createProperties(const std::string &code, + const std::string &name, bool deprecated, + const std::string &area_of_use_auth_name, + const std::string &area_of_use_code); + + SQLResultSet run(const std::string &sql, + const ListOfParams ¶meters = ListOfParams()); + + SQLResultSet runWithCodeParam(const std::string &sql, + const std::string &code); + + SQLResultSet runWithCodeParam(const char *sql, const std::string &code); + + bool hasAuthorityRestriction() const { + return !authority_.empty() && authority_ != "any"; + } + + private: + DatabaseContextNNPtr context_; + std::string authority_; + std::weak_ptr thisFactory_{}; +}; + +// --------------------------------------------------------------------------- + +SQLResultSet AuthorityFactory::Private::run(const std::string &sql, + const ListOfParams ¶meters) { + return context()->getPrivate()->run(sql, parameters); +} + +// --------------------------------------------------------------------------- + +SQLResultSet +AuthorityFactory::Private::runWithCodeParam(const std::string &sql, + const std::string &code) { + return run(sql, {authority(), code}); +} + +// --------------------------------------------------------------------------- + +SQLResultSet +AuthorityFactory::Private::runWithCodeParam(const char *sql, + const std::string &code) { + return runWithCodeParam(std::string(sql), code); +} + +// --------------------------------------------------------------------------- + +UnitOfMeasure +AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name, + const std::string &code) { + return *(createFactory(auth_name)->createUnitOfMeasure(code)); +} + +// --------------------------------------------------------------------------- + +util::PropertyMap AuthorityFactory::Private::createProperties( + const std::string &code, const std::string &name, bool deprecated, + const metadata::ExtentPtr &extent) { + auto props = util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, authority()) + .set(metadata::Identifier::CODE_KEY, code) + .set(common::IdentifiedObject::NAME_KEY, name); + if (deprecated) { + props.set(common::IdentifiedObject::DEPRECATED_KEY, true); + } + if (extent) { + props.set( + common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + NN_NO_CHECK(std::static_pointer_cast(extent))); + } + return props; +} + +// --------------------------------------------------------------------------- + +util::PropertyMap AuthorityFactory::Private::createProperties( + const std::string &code, const std::string &name, bool deprecated, + const std::string &area_of_use_auth_name, + const std::string &area_of_use_code) { + return createProperties(code, name, deprecated, + area_of_use_auth_name.empty() + ? nullptr + : createFactory(area_of_use_auth_name) + ->createExtent(area_of_use_code) + .as_nullable()); +} + +// --------------------------------------------------------------------------- + +bool AuthorityFactory::Private::rejectOpDueToMissingGrid( + const operation::CoordinateOperationNNPtr &op, bool discardIfMissingGrid) { + if (discardIfMissingGrid) { + for (const auto &gridDesc : op->gridsNeeded(context())) { + if (!gridDesc.available) { + return true; + } + } + } + return false; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +AuthorityFactory::~AuthorityFactory() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context, + const std::string &authorityName) + : d(internal::make_unique(context, authorityName)) {} + +// --------------------------------------------------------------------------- + +// clang-format off +/** \brief Instanciate a AuthorityFactory. + * + * The authority name might be set to the empty string in the particular case + * where createFromCoordinateReferenceSystemCodes(const std::string&,const std::string&,const std::string&,const std::string&) const + * is called. + * + * @param context Contexte. + * @param authorityName Authority name. + * @return new AuthorityFactory. + */ +// clang-format on + +AuthorityFactoryNNPtr +AuthorityFactory::create(const DatabaseContextNNPtr &context, + const std::string &authorityName) { + + auto factory = AuthorityFactory::nn_make_shared( + context, authorityName); + factory->d->setThis(factory); + return factory; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the database context. */ +const DatabaseContextNNPtr &AuthorityFactory::databaseContext() const { + return d->context(); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns an arbitrary object from a code. + * + * The returned object will typically be an instance of Datum, + * CoordinateSystem, ReferenceSystem or CoordinateOperation. If the type of + * the object is know at compile time, it is recommended to invoke the most + * precise method instead of this one (for example + * createCoordinateReferenceSystem(code) instead of createObject(code) + * if the caller know he is asking for a coordinate reference system). + * + * If there are several objects with the same code, a FactoryException is + * thrown. + * + * @param code Object code allocated by authority. (e.g. "4326") + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +util::BaseObjectNNPtr +AuthorityFactory::createObject(const std::string &code) const { + + auto res = d->runWithCodeParam( + "SELECT table_name FROM object_view WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("not found", d->authority(), code); + } + if (res.size() != 1) { + std::string msg( + "More than one object matching specified code. Objects found in "); + bool first = true; + for (const auto &row : res) { + if (!first) + msg += ", "; + msg += row[0]; + first = false; + } + throw FactoryException(msg); + } + const auto &table_name = res.front()[0]; + if (table_name == "area") { + return util::nn_static_pointer_cast( + createExtent(code)); + } + if (table_name == "unit_of_measure") { + return util::nn_static_pointer_cast( + createUnitOfMeasure(code)); + } + if (table_name == "prime_meridian") { + return util::nn_static_pointer_cast( + createPrimeMeridian(code)); + } + if (table_name == "ellipsoid") { + return util::nn_static_pointer_cast( + createEllipsoid(code)); + } + if (table_name == "geodetic_datum") { + return util::nn_static_pointer_cast( + createGeodeticDatum(code)); + } + if (table_name == "vertical_datum") { + return util::nn_static_pointer_cast( + createVerticalDatum(code)); + } + if (table_name == "geodetic_crs") { + return util::nn_static_pointer_cast( + createGeodeticCRS(code)); + } + if (table_name == "vertical_crs") { + return util::nn_static_pointer_cast( + createVerticalCRS(code)); + } + if (table_name == "projected_crs") { + return util::nn_static_pointer_cast( + createProjectedCRS(code)); + } + if (table_name == "compound_crs") { + return util::nn_static_pointer_cast( + createCompoundCRS(code)); + } + if (table_name == "conversion") { + return util::nn_static_pointer_cast( + createConversion(code)); + } + if (table_name == "helmert_transformation" || + table_name == "grid_transformation" || + table_name == "other_transformation" || + table_name == "concatenated_operation") { + return util::nn_static_pointer_cast( + createCoordinateOperation(code, false)); + } + throw FactoryException("unimplemented factory for " + res.front()[0]); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static FactoryException buildFactoryException(const char *type, + const std::string &code, + const std::exception &ex) { + return FactoryException(std::string("cannot build ") + type + " " + code + + ": " + ex.what()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns a metadata::Extent from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +metadata::ExtentNNPtr +AuthorityFactory::createExtent(const std::string &code) const { + const auto cacheKey(d->authority() + code); + { + auto extent = d->context()->d->getExtentFromCache(cacheKey); + if (extent) { + return NN_NO_CHECK(extent); + } + } + auto sql = "SELECT name, south_lat, north_lat, west_lon, east_lon, " + "deprecated FROM area WHERE auth_name = ? AND code = ?"; + auto res = d->runWithCodeParam(sql, code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("area not found", d->authority(), + code); + } + try { + const auto &row = res.front(); + const auto &name = row[0]; + double south_lat = c_locale_stod(row[1]); + double north_lat = c_locale_stod(row[2]); + double west_lon = c_locale_stod(row[3]); + double east_lon = c_locale_stod(row[4]); + auto bbox = metadata::GeographicBoundingBox::create( + west_lon, south_lat, east_lon, north_lat); + + auto extent = metadata::Extent::create( + util::optional(name), + std::vector{bbox}, + std::vector(), + std::vector()); + d->context()->d->cache(code, extent); + return extent; + + } catch (const std::exception &ex) { + throw buildFactoryException("area", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a common::UnitOfMeasure from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +UnitOfMeasureNNPtr +AuthorityFactory::createUnitOfMeasure(const std::string &code) const { + const auto cacheKey(d->authority() + code); + { + auto uom = d->context()->d->getUOMFromCache(cacheKey); + if (uom) { + return NN_NO_CHECK(uom); + } + } + auto res = d->runWithCodeParam( + "SELECT name, conv_factor, type, deprecated FROM unit_of_measure WHERE " + "auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("unit of measure not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + const auto &name = + (row[0] == "degree (supplier to define representation)") + ? UnitOfMeasure::DEGREE.name() + : row[0]; + double conv_factor = (code == "9107" || code == "9108") + ? UnitOfMeasure::DEGREE.conversionToSI() + : c_locale_stod(row[1]); + constexpr double EPS = 1e-10; + if (std::fabs(conv_factor - UnitOfMeasure::DEGREE.conversionToSI()) < + EPS * UnitOfMeasure::DEGREE.conversionToSI()) { + conv_factor = UnitOfMeasure::DEGREE.conversionToSI(); + } + if (std::fabs(conv_factor - + UnitOfMeasure::ARC_SECOND.conversionToSI()) < + EPS * UnitOfMeasure::ARC_SECOND.conversionToSI()) { + conv_factor = UnitOfMeasure::ARC_SECOND.conversionToSI(); + } + const auto &type_str = row[2]; + UnitOfMeasure::Type unitType = UnitOfMeasure::Type::UNKNOWN; + if (type_str == "length") + unitType = UnitOfMeasure::Type::LINEAR; + else if (type_str == "angle") + unitType = UnitOfMeasure::Type::ANGULAR; + else if (type_str == "scale") + unitType = UnitOfMeasure::Type::SCALE; + else if (type_str == "time") + unitType = UnitOfMeasure::Type::TIME; + auto uom = util::nn_make_shared( + name, conv_factor, unitType, d->authority(), code); + d->context()->d->cache(cacheKey, uom); + return uom; + } catch (const std::exception &ex) { + throw buildFactoryException("unit of measure", code, ex); + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static double normalizeMeasure(const std::string &uom_code, + const std::string &value, + std::string &normalized_uom_code) { + if (uom_code == "9110") // DDD.MMSSsss..... + { + double normalized_value = c_locale_stod(value); + std::ostringstream buffer; + buffer.imbue(std::locale::classic()); + constexpr size_t precision = 12; + buffer << std::fixed << std::setprecision(precision) + << normalized_value; + auto formatted = buffer.str(); + size_t dotPos = formatted.find('.'); + assert(dotPos + 1 + precision == formatted.size()); + auto minutes = formatted.substr(dotPos + 1, 2); + auto seconds = formatted.substr(dotPos + 3); + assert(seconds.size() == precision - 2); + normalized_value = + (normalized_value < 0 ? -1.0 : 1.0) * + (int(std::fabs(normalized_value)) + c_locale_stod(minutes) / 60. + + (c_locale_stod(seconds) / std::pow(10, seconds.size() - 2)) / + 3600.); + normalized_uom_code = common::UnitOfMeasure::DEGREE.code(); + return normalized_value; + } else { + normalized_uom_code = uom_code; + return c_locale_stod(value); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns a datum::PrimeMeridian from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +datum::PrimeMeridianNNPtr +AuthorityFactory::createPrimeMeridian(const std::string &code) const { + const auto cacheKey(d->authority() + code); + { + auto pm = d->context()->d->getPrimeMeridianFromCache(cacheKey); + if (pm) { + return NN_NO_CHECK(pm); + } + } + auto res = d->runWithCodeParam( + "SELECT name, longitude, uom_auth_name, uom_code, deprecated FROM " + "prime_meridian WHERE " + "auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("prime meridian not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + const auto &name = row[0]; + const auto &longitude = row[1]; + const auto &uom_auth_name = row[2]; + const auto &uom_code = row[3]; + const bool deprecated = row[4] == "1"; + + std::string normalized_uom_code(uom_code); + const double normalized_value = + normalizeMeasure(uom_code, longitude, normalized_uom_code); + + auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code); + auto props = d->createProperties(code, name, deprecated, nullptr); + auto pm = datum::PrimeMeridian::create( + props, common::Angle(normalized_value, uom)); + d->context()->d->cache(cacheKey, pm); + return pm; + } catch (const std::exception &ex) { + throw buildFactoryException("prime meridian", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Identify a celestial body from an approximate radius. + * + * @param semi_major_axis Approximate semi-major axis. + * @param tolerance Relative error allowed. + * @return celestial body name if one single match found. + * @throw FactoryException + */ + +std::string +AuthorityFactory::identifyBodyFromSemiMajorAxis(double semi_major_axis, + double tolerance) const { + auto res = + d->run("SELECT name, (ABS(semi_major_axis - ?) / semi_major_axis ) " + "AS rel_error FROM celestial_body WHERE rel_error <= ?", + {semi_major_axis, tolerance}); + if (res.empty()) { + throw FactoryException("no match found"); + } + if (res.size() > 1) { + throw FactoryException("more than one match found"); + } + return res.front()[0]; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a datum::Ellipsoid from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +datum::EllipsoidNNPtr +AuthorityFactory::createEllipsoid(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT ellipsoid.name, ellipsoid.semi_major_axis, " + "ellipsoid.uom_auth_name, ellipsoid.uom_code, " + "ellipsoid.inv_flattening, ellipsoid.semi_minor_axis, " + "celestial_body.name AS body_name, ellipsoid.deprecated FROM " + "ellipsoid JOIN celestial_body " + "ON ellipsoid.celestial_body_auth_name = celestial_body.auth_name AND " + "ellipsoid.celestial_body_code = celestial_body.code WHERE " + "ellipsoid.auth_name = ? AND ellipsoid.code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("ellipsoid not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + const auto &name = row[0]; + const auto &semi_major_axis_str = row[1]; + double semi_major_axis = c_locale_stod(semi_major_axis_str); + const auto &uom_auth_name = row[2]; + const auto &uom_code = row[3]; + const auto &inv_flattening_str = row[4]; + const auto &semi_minor_axis_str = row[5]; + const auto &body = row[6]; + const bool deprecated = row[7] == "1"; + auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code); + auto props = d->createProperties(code, name, deprecated, nullptr); + if (!inv_flattening_str.empty()) { + return datum::Ellipsoid::createFlattenedSphere( + props, common::Length(semi_major_axis, uom), + common::Scale(c_locale_stod(inv_flattening_str)), body); + } else if (semi_major_axis_str == semi_minor_axis_str) { + return datum::Ellipsoid::createSphere( + props, common::Length(semi_major_axis, uom), body); + } else { + return datum::Ellipsoid::createTwoAxis( + props, common::Length(semi_major_axis, uom), + common::Length(c_locale_stod(semi_minor_axis_str), uom), body); + } + } catch (const std::exception &ex) { + throw buildFactoryException("elllipsoid", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a datum::GeodeticReferenceFrame from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +datum::GeodeticReferenceFrameNNPtr +AuthorityFactory::createGeodeticDatum(const std::string &code) const { + const auto cacheKey(d->authority() + code); + { + auto datum = d->context()->d->getGeodeticDatumFromCache(cacheKey); + if (datum) { + return NN_NO_CHECK(datum); + } + } + auto res = d->runWithCodeParam( + "SELECT name, ellipsoid_auth_name, ellipsoid_code, " + "prime_meridian_auth_name, prime_meridian_code, area_of_use_auth_name, " + "area_of_use_code, deprecated FROM geodetic_datum WHERE " + "auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("geodetic datum not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + const auto &name = row[0]; + const auto &ellipsoid_auth_name = row[1]; + const auto &ellipsoid_code = row[2]; + const auto &prime_meridian_auth_name = row[3]; + const auto &prime_meridian_code = row[4]; + const auto &area_of_use_auth_name = row[5]; + const auto &area_of_use_code = row[6]; + const bool deprecated = row[7] == "1"; + auto ellipsoid = d->createFactory(ellipsoid_auth_name) + ->createEllipsoid(ellipsoid_code); + auto pm = d->createFactory(prime_meridian_auth_name) + ->createPrimeMeridian(prime_meridian_code); + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + auto anchor = util::optional(); + auto datum = + datum::GeodeticReferenceFrame::create(props, ellipsoid, anchor, pm); + d->context()->d->cache(cacheKey, datum); + return datum; + } catch (const std::exception &ex) { + throw buildFactoryException("geodetic reference frame", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a datum::VerticalReferenceFrame from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +datum::VerticalReferenceFrameNNPtr +AuthorityFactory::createVerticalDatum(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT name, area_of_use_auth_name, area_of_use_code, deprecated FROM " + "vertical_datum WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("vertical datum not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + const auto &name = row[0]; + const auto &area_of_use_auth_name = row[1]; + const auto &area_of_use_code = row[2]; + const bool deprecated = row[3] == "1"; + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + auto anchor = util::optional(); + return datum::VerticalReferenceFrame::create(props, anchor); + } catch (const std::exception &ex) { + throw buildFactoryException("vertical reference frame", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a datum::Datum from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +datum::DatumNNPtr AuthorityFactory::createDatum(const std::string &code) const { + auto res = + d->run("SELECT 'geodetic_datum' FROM geodetic_datum WHERE " + "auth_name = ? AND code = ? " + "UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE " + "auth_name = ? AND code = ?", + {d->authority(), code, d->authority(), code}); + if (res.empty()) { + throw NoSuchAuthorityCodeException("datum not found", d->authority(), + code); + } + if (res.front()[0] == "geodetic_datum") { + return createGeodeticDatum(code); + } + return createVerticalDatum(code); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static cs::MeridianPtr createMeridian(const std::string &val) { + try { + const std::string degW(std::string("\xC2\xB0") + "W"); + if (ends_with(val, degW)) { + return cs::Meridian::create(common::Angle( + -c_locale_stod(val.substr(0, val.size() - degW.size())))); + } + const std::string degE(std::string("\xC2\xB0") + "E"); + if (ends_with(val, degE)) { + return cs::Meridian::create(common::Angle( + c_locale_stod(val.substr(0, val.size() - degE.size())))); + } + } catch (const std::exception &) { + } + return nullptr; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns a cs::CoordinateSystem from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +cs::CoordinateSystemNNPtr +AuthorityFactory::createCoordinateSystem(const std::string &code) const { + const auto cacheKey(d->authority() + code); + { + auto cs = d->context()->d->getCoordinateSystemFromCache(cacheKey); + if (cs) { + return NN_NO_CHECK(cs); + } + } + auto res = d->runWithCodeParam( + "SELECT axis.name, abbrev, orientation, uom_auth_name, uom_code, " + "cs.type FROM " + "axis LEFT JOIN coordinate_system cs ON " + "axis.coordinate_system_auth_name = cs.auth_name AND " + "axis.coordinate_system_code = cs.code WHERE " + "coordinate_system_auth_name = ? AND coordinate_system_code = ? ORDER " + "BY coordinate_system_order", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("coordinate system not found", + d->authority(), code); + } + + const auto &csType = res.front()[5]; + std::vector axisList; + for (const auto &row : res) { + const auto &name = row[0]; + const auto &abbrev = row[1]; + const auto &orientation = row[2]; + const auto &uom_auth_name = row[3]; + const auto &uom_code = row[4]; + auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code); + auto props = + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name); + const cs::AxisDirection *direction = + cs::AxisDirection::valueOf(orientation); + cs::MeridianPtr meridian; + if (direction == nullptr) { + if (orientation == "Geocentre > equator/0" + "\xC2\xB0" + "E") { + direction = &(cs::AxisDirection::GEOCENTRIC_X); + } else if (orientation == "Geocentre > equator/90" + "\xC2\xB0" + "E") { + direction = &(cs::AxisDirection::GEOCENTRIC_Y); + } else if (orientation == "Geocentre > north pole") { + direction = &(cs::AxisDirection::GEOCENTRIC_Z); + } else if (starts_with(orientation, "North along ")) { + direction = &(cs::AxisDirection::NORTH); + meridian = + createMeridian(orientation.substr(strlen("North along "))); + } else if (starts_with(orientation, "South along ")) { + direction = &(cs::AxisDirection::SOUTH); + meridian = + createMeridian(orientation.substr(strlen("South along "))); + } else { + throw FactoryException("unknown axis direction: " + + orientation); + } + } + axisList.emplace_back(cs::CoordinateSystemAxis::create( + props, abbrev, *direction, uom, meridian)); + } + + const auto cacheAndRet = [this, + &cacheKey](const cs::CoordinateSystemNNPtr &cs) { + d->context()->d->cache(cacheKey, cs); + return cs; + }; + + auto props = util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, d->authority()) + .set(metadata::Identifier::CODE_KEY, code); + if (csType == "ellipsoidal") { + if (axisList.size() == 2) { + return cacheAndRet( + cs::EllipsoidalCS::create(props, axisList[0], axisList[1])); + } + if (axisList.size() == 3) { + return cacheAndRet(cs::EllipsoidalCS::create( + props, axisList[0], axisList[1], axisList[2])); + } + throw FactoryException("invalid number of axis for EllipsoidalCS"); + } + if (csType == "Cartesian") { + if (axisList.size() == 2) { + return cacheAndRet( + cs::CartesianCS::create(props, axisList[0], axisList[1])); + } + if (axisList.size() == 3) { + return cacheAndRet(cs::CartesianCS::create( + props, axisList[0], axisList[1], axisList[2])); + } + throw FactoryException("invalid number of axis for CartesianCS"); + } + if (csType == "vertical") { + if (axisList.size() == 1) { + return cacheAndRet(cs::VerticalCS::create(props, axisList[0])); + } + throw FactoryException("invalid number of axis for VerticalCS"); + } + throw FactoryException("unhandled coordinate system type: " + csType); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::GeodeticCRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::GeodeticCRSNNPtr +AuthorityFactory::createGeodeticCRS(const std::string &code) const { + return createGeodeticCRS(code, false); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::GeographicCRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::GeographicCRSNNPtr +AuthorityFactory::createGeographicCRS(const std::string &code) const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast( + createGeodeticCRS(code, true))); +} + +// --------------------------------------------------------------------------- + +static crs::GeodeticCRSNNPtr +cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS, + const util::PropertyMap &props) { + auto cs = geodCRS->coordinateSystem(); + auto datum = geodCRS->datum(); + if (!datum) { + return geodCRS; + } + auto ellipsoidalCS = util::nn_dynamic_pointer_cast(cs); + if (ellipsoidalCS) { + return crs::GeographicCRS::create(props, NN_NO_CHECK(datum), + NN_NO_CHECK(ellipsoidalCS)); + } + auto geocentricCS = util::nn_dynamic_pointer_cast(cs); + if (geocentricCS) { + return crs::GeodeticCRS::create(props, NN_NO_CHECK(datum), + NN_NO_CHECK(geocentricCS)); + } + return geodCRS; +} + +// --------------------------------------------------------------------------- + +crs::GeodeticCRSNNPtr +AuthorityFactory::createGeodeticCRS(const std::string &code, + bool geographicOnly) const { + const auto cacheKey(d->authority() + code); + auto crs = std::dynamic_pointer_cast( + d->context()->d->getCRSFromCache(cacheKey)); + if (crs) { + return NN_NO_CHECK(crs); + } + std::string sql("SELECT name, type, coordinate_system_auth_name, " + "coordinate_system_code, datum_auth_name, datum_code, " + "area_of_use_auth_name, area_of_use_code, text_definition, " + "deprecated FROM " + "geodetic_crs WHERE auth_name = ? AND code = ?"); + if (geographicOnly) { + sql += " AND type in (" GEOG_2D "," GEOG_3D ")"; + } + auto res = d->runWithCodeParam(sql, code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("geodeticCRS not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + const auto &name = row[0]; + const auto &type = row[1]; + const auto &cs_auth_name = row[2]; + const auto &cs_code = row[3]; + const auto &datum_auth_name = row[4]; + const auto &datum_code = row[5]; + const auto &area_of_use_auth_name = row[6]; + const auto &area_of_use_code = row[7]; + const auto &text_definition = row[8]; + const bool deprecated = row[9] == "1"; + + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + + if (!text_definition.empty()) { + DatabaseContext::Private::RecursionDetector detector(d->context()); + auto obj = createFromUserInput(text_definition, d->context()); + auto geodCRS = util::nn_dynamic_pointer_cast(obj); + if (geodCRS) { + return cloneWithProps(NN_NO_CHECK(geodCRS), props); + } + + auto boundCRS = dynamic_cast(obj.get()); + if (boundCRS) { + geodCRS = util::nn_dynamic_pointer_cast( + boundCRS->baseCRS()); + if (geodCRS) { + auto newBoundCRS = crs::BoundCRS::create( + cloneWithProps(NN_NO_CHECK(geodCRS), props), + boundCRS->hubCRS(), boundCRS->transformation()); + return NN_NO_CHECK( + util::nn_dynamic_pointer_cast( + newBoundCRS->baseCRSWithCanonicalBoundCRS())); + } + } + + throw FactoryException( + "text_definition does not define a GeodeticCRS"); + } + + auto cs = + d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); + auto datum = + d->createFactory(datum_auth_name)->createGeodeticDatum(datum_code); + + auto ellipsoidalCS = + util::nn_dynamic_pointer_cast(cs); + if ((type == "geographic 2D" || type == "geographic 3D") && + ellipsoidalCS) { + auto crsRet = crs::GeographicCRS::create( + props, datum, NN_NO_CHECK(ellipsoidalCS)); + d->context()->d->cache(cacheKey, crsRet); + return crsRet; + } + auto geocentricCS = util::nn_dynamic_pointer_cast(cs); + if (type == "geocentric" && geocentricCS) { + auto crsRet = crs::GeodeticCRS::create(props, datum, + NN_NO_CHECK(geocentricCS)); + d->context()->d->cache(cacheKey, crsRet); + return crsRet; + } + throw FactoryException("unsupported (type, CS type) for geodeticCRS: " + + type + ", " + cs->getWKT2Type(true)); + } catch (const std::exception &ex) { + throw buildFactoryException("geodeticCRS", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::VerticalCRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::VerticalCRSNNPtr +AuthorityFactory::createVerticalCRS(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT name, coordinate_system_auth_name, " + "coordinate_system_code, datum_auth_name, datum_code, " + "area_of_use_auth_name, area_of_use_code, deprecated FROM " + "vertical_crs WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("verticalCRS not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + const auto &name = row[0]; + const auto &cs_auth_name = row[1]; + const auto &cs_code = row[2]; + const auto &datum_auth_name = row[3]; + const auto &datum_code = row[4]; + const auto &area_of_use_auth_name = row[5]; + const auto &area_of_use_code = row[6]; + const bool deprecated = row[7] == "1"; + auto cs = + d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); + auto datum = + d->createFactory(datum_auth_name)->createVerticalDatum(datum_code); + + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + + auto verticalCS = util::nn_dynamic_pointer_cast(cs); + if (verticalCS) { + return crs::VerticalCRS::create(props, datum, + NN_NO_CHECK(verticalCS)); + } + throw FactoryException("unsupported CS type for verticalCRS: " + + cs->getWKT2Type(true)); + } catch (const std::exception &ex) { + throw buildFactoryException("verticalCRS", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a operation::Conversion from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +operation::ConversionNNPtr +AuthorityFactory::createConversion(const std::string &code) const { + + static const char *sql = + "SELECT name, area_of_use_auth_name, area_of_use_code, " + "method_auth_name, method_code, method_name, " + + "param1_auth_name, param1_code, param1_name, param1_value, " + "param1_uom_auth_name, param1_uom_code, " + + "param2_auth_name, param2_code, param2_name, param2_value, " + "param2_uom_auth_name, param2_uom_code, " + + "param3_auth_name, param3_code, param3_name, param3_value, " + "param3_uom_auth_name, param3_uom_code, " + + "param4_auth_name, param4_code, param4_name, param4_value, " + "param4_uom_auth_name, param4_uom_code, " + + "param5_auth_name, param5_code, param5_name, param5_value, " + "param5_uom_auth_name, param5_uom_code, " + + "param6_auth_name, param6_code, param6_name, param6_value, " + "param6_uom_auth_name, param6_uom_code, " + + "param7_auth_name, param7_code, param7_name, param7_value, " + "param7_uom_auth_name, param7_uom_code, " + + "deprecated FROM conversion WHERE auth_name = ? AND code = ?"; + + auto res = d->runWithCodeParam(sql, code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("conversion not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + size_t idx = 0; + const auto &name = row[idx++]; + const auto &area_of_use_auth_name = row[idx++]; + const auto &area_of_use_code = row[idx++]; + const auto &method_auth_name = row[idx++]; + const auto &method_code = row[idx++]; + const auto &method_name = row[idx++]; + const size_t base_param_idx = idx; + std::vector parameters; + std::vector values; + constexpr int N_MAX_PARAMS = 7; + for (int i = 0; i < N_MAX_PARAMS; ++i) { + const auto ¶m_auth_name = row[base_param_idx + i * 6 + 0]; + if (param_auth_name.empty()) { + break; + } + const auto ¶m_code = row[base_param_idx + i * 6 + 1]; + const auto ¶m_name = row[base_param_idx + i * 6 + 2]; + const auto ¶m_value = row[base_param_idx + i * 6 + 3]; + const auto ¶m_uom_auth_name = row[base_param_idx + i * 6 + 4]; + const auto ¶m_uom_code = row[base_param_idx + i * 6 + 5]; + parameters.emplace_back(operation::OperationParameter::create( + util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, param_auth_name) + .set(metadata::Identifier::CODE_KEY, param_code) + .set(common::IdentifiedObject::NAME_KEY, param_name))); + std::string normalized_uom_code(param_uom_code); + const double normalized_value = normalizeMeasure( + param_uom_code, param_value, normalized_uom_code); + auto uom = d->createUnitOfMeasure(param_uom_auth_name, + normalized_uom_code); + values.emplace_back(operation::ParameterValue::create( + common::Measure(normalized_value, uom))); + } + const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1"; + + auto propConversion = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + + auto propMethod = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, method_name); + if (!method_auth_name.empty()) { + propMethod + .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) + .set(metadata::Identifier::CODE_KEY, method_code); + } + + return operation::Conversion::create(propConversion, propMethod, + parameters, values); + } catch (const std::exception &ex) { + throw buildFactoryException("conversion", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::ProjectedCRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::ProjectedCRSNNPtr +AuthorityFactory::createProjectedCRS(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT name, coordinate_system_auth_name, " + "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, " + "conversion_auth_name, conversion_code, " + "area_of_use_auth_name, area_of_use_code, text_definition, " + "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("projectedCRS not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + const auto &name = row[0]; + const auto &cs_auth_name = row[1]; + const auto &cs_code = row[2]; + const auto &geodetic_crs_auth_name = row[3]; + const auto &geodetic_crs_code = row[4]; + const auto &conversion_auth_name = row[5]; + const auto &conversion_code = row[6]; + const auto &area_of_use_auth_name = row[7]; + const auto &area_of_use_code = row[8]; + const auto &text_definition = row[9]; + const bool deprecated = row[10] == "1"; + + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + + if (!text_definition.empty()) { + DatabaseContext::Private::RecursionDetector detector(d->context()); + auto obj = createFromUserInput(text_definition, d->context()); + auto projCRS = dynamic_cast(obj.get()); + if (projCRS) { + const auto &conv = projCRS->derivingConversionRef(); + auto newConv = + (conv->nameStr() == "unnamed") + ? operation::Conversion::create( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, name), + conv->method(), conv->parameterValues()) + : conv; + return crs::ProjectedCRS::create(props, projCRS->baseCRS(), + newConv, + projCRS->coordinateSystem()); + } + + auto boundCRS = dynamic_cast(obj.get()); + if (boundCRS) { + projCRS = dynamic_cast( + boundCRS->baseCRS().get()); + if (projCRS) { + auto newBoundCRS = crs::BoundCRS::create( + crs::ProjectedCRS::create( + props, projCRS->baseCRS(), + projCRS->derivingConversionRef(), + projCRS->coordinateSystem()), + boundCRS->hubCRS(), boundCRS->transformation()); + return NN_NO_CHECK( + util::nn_dynamic_pointer_cast( + newBoundCRS->baseCRSWithCanonicalBoundCRS())); + } + } + + throw FactoryException( + "text_definition does not define a ProjectedCRS"); + } + + auto cs = + d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); + + auto baseCRS = d->createFactory(geodetic_crs_auth_name) + ->createGeodeticCRS(geodetic_crs_code); + + auto conv = d->createFactory(conversion_auth_name) + ->createConversion(conversion_code); + + auto cartesianCS = util::nn_dynamic_pointer_cast(cs); + if (cartesianCS) { + return crs::ProjectedCRS::create(props, baseCRS, conv, + NN_NO_CHECK(cartesianCS)); + } + throw FactoryException("unsupported CS type for projectedCRS: " + + cs->getWKT2Type(true)); + } catch (const std::exception &ex) { + throw buildFactoryException("projectedCRS", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::CompoundCRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::CompoundCRSNNPtr +AuthorityFactory::createCompoundCRS(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT name, horiz_crs_auth_name, horiz_crs_code, " + "vertical_crs_auth_name, vertical_crs_code, " + "area_of_use_auth_name, area_of_use_code, deprecated FROM " + "compound_crs WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("compoundCRS not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + const auto &name = row[0]; + const auto &horiz_crs_auth_name = row[1]; + const auto &horiz_crs_code = row[2]; + const auto &vertical_crs_auth_name = row[3]; + const auto &vertical_crs_code = row[4]; + const auto &area_of_use_auth_name = row[5]; + const auto &area_of_use_code = row[6]; + const bool deprecated = row[7] == "1"; + + auto horizCRS = + d->createFactory(horiz_crs_auth_name) + ->createCoordinateReferenceSystem(horiz_crs_code, false); + auto vertCRS = d->createFactory(vertical_crs_auth_name) + ->createVerticalCRS(vertical_crs_code); + + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + return crs::CompoundCRS::create( + props, std::vector{horizCRS, vertCRS}); + } catch (const std::exception &ex) { + throw buildFactoryException("compoundCRS", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::CRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem( + const std::string &code) const { + return createCoordinateReferenceSystem(code, true); +} + +crs::CRSNNPtr +AuthorityFactory::createCoordinateReferenceSystem(const std::string &code, + bool allowCompound) const { + const auto cacheKey(d->authority() + code); + auto crs = d->context()->d->getCRSFromCache(cacheKey); + if (crs) { + return NN_NO_CHECK(crs); + } + auto res = d->runWithCodeParam( + "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("crs not found", d->authority(), + code); + } + const auto &type = res.front()[0]; + if (type == "geographic 2D" || type == "geographic 3D" || + type == "geocentric") { + return createGeodeticCRS(code); + } + if (type == "vertical") { + return createVerticalCRS(code); + } + if (type == "projected") { + return createProjectedCRS(code); + } + if (allowCompound && type == "compound") { + return createCompoundCRS(code); + } + throw FactoryException("unhandled CRS type: " + type); +} +// --------------------------------------------------------------------------- + +//! @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 operation::OperationParameterNNPtr createOpParamNameEPSGCode(int code) { + const char *name = operation::OperationParameter::getNameForEPSGCode(code); + assert(name); + return operation::OperationParameter::create( + createMapNameEPSGCode(name, code)); +} + +static operation::ParameterValueNNPtr createLength(const std::string &value, + const UnitOfMeasure &uom) { + return operation::ParameterValue::create( + common::Length(c_locale_stod(value), uom)); +} + +static operation::ParameterValueNNPtr createAngle(const std::string &value, + const UnitOfMeasure &uom) { + return operation::ParameterValue::create( + common::Angle(c_locale_stod(value), uom)); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns a operation::CoordinateOperation from the specified code. + * + * @param code Object code allocated by authority. + * @param usePROJAlternativeGridNames Whether PROJ alternative grid names + * should be substituted to the official grid names. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( + const std::string &code, bool usePROJAlternativeGridNames) const { + return createCoordinateOperation(code, true, usePROJAlternativeGridNames, + std::string()); +} + +operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( + const std::string &code, bool allowConcatenated, + bool usePROJAlternativeGridNames, const std::string &typeIn) const { + std::string type(typeIn); + if (type.empty()) { + auto res = d->runWithCodeParam( + "SELECT type FROM coordinate_operation_with_conversion_view " + "WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("coordinate operation not found", + d->authority(), code); + } + type = res.front()[0]; + } + + if (type == "conversion") { + return createConversion(code); + } + + if (type == "helmert_transformation") { + + auto res = d->runWithCodeParam( + "SELECT name, method_auth_name, method_code, method_name, " + "source_crs_auth_name, source_crs_code, target_crs_auth_name, " + "target_crs_code, area_of_use_auth_name, area_of_use_code, " + "accuracy, tx, ty, tz, translation_uom_auth_name, " + "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, " + "rotation_uom_code, scale_difference, " + "scale_difference_uom_auth_name, scale_difference_uom_code, " + "rate_tx, rate_ty, rate_tz, rate_translation_uom_auth_name, " + "rate_translation_uom_code, rate_rx, rate_ry, rate_rz, " + "rate_rotation_uom_auth_name, rate_rotation_uom_code, " + "rate_scale_difference, rate_scale_difference_uom_auth_name, " + "rate_scale_difference_uom_code, epoch, epoch_uom_auth_name, " + "epoch_uom_code, px, py, pz, pivot_uom_auth_name, pivot_uom_code, " + "deprecated FROM " + "helmert_transformation WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + // shouldn't happen if foreign keys are OK + throw NoSuchAuthorityCodeException( + "helmert_transformation not found", d->authority(), code); + } + try { + const auto &row = res.front(); + size_t idx = 0; + const auto &name = row[idx++]; + const auto &method_auth_name = row[idx++]; + const auto &method_code = row[idx++]; + const auto &method_name = row[idx++]; + const auto &source_crs_auth_name = row[idx++]; + const auto &source_crs_code = row[idx++]; + const auto &target_crs_auth_name = row[idx++]; + const auto &target_crs_code = row[idx++]; + const auto &area_of_use_auth_name = row[idx++]; + const auto &area_of_use_code = row[idx++]; + const auto &accuracy = row[idx++]; + + const auto &tx = row[idx++]; + const auto &ty = row[idx++]; + const auto &tz = row[idx++]; + const auto &translation_uom_auth_name = row[idx++]; + const auto &translation_uom_code = row[idx++]; + const auto &rx = row[idx++]; + const auto &ry = row[idx++]; + const auto &rz = row[idx++]; + const auto &rotation_uom_auth_name = row[idx++]; + const auto &rotation_uom_code = row[idx++]; + const auto &scale_difference = row[idx++]; + const auto &scale_difference_uom_auth_name = row[idx++]; + const auto &scale_difference_uom_code = row[idx++]; + + const auto &rate_tx = row[idx++]; + const auto &rate_ty = row[idx++]; + const auto &rate_tz = row[idx++]; + const auto &rate_translation_uom_auth_name = row[idx++]; + const auto &rate_translation_uom_code = row[idx++]; + const auto &rate_rx = row[idx++]; + const auto &rate_ry = row[idx++]; + const auto &rate_rz = row[idx++]; + const auto &rate_rotation_uom_auth_name = row[idx++]; + const auto &rate_rotation_uom_code = row[idx++]; + const auto &rate_scale_difference = row[idx++]; + const auto &rate_scale_difference_uom_auth_name = row[idx++]; + const auto &rate_scale_difference_uom_code = row[idx++]; + + const auto &epoch = row[idx++]; + const auto &epoch_uom_auth_name = row[idx++]; + const auto &epoch_uom_code = row[idx++]; + + const auto &px = row[idx++]; + const auto &py = row[idx++]; + const auto &pz = row[idx++]; + const auto &pivot_uom_auth_name = row[idx++]; + const auto &pivot_uom_code = row[idx++]; + + const auto &deprecated_str = row[idx++]; + const bool deprecated = deprecated_str == "1"; + assert(idx == row.size()); + + auto uom_translation = d->createUnitOfMeasure( + translation_uom_auth_name, translation_uom_code); + + auto uom_epoch = epoch_uom_auth_name.empty() + ? common::UnitOfMeasure::NONE + : d->createUnitOfMeasure(epoch_uom_auth_name, + epoch_uom_code); + + auto sourceCRS = + d->createFactory(source_crs_auth_name) + ->createCoordinateReferenceSystem(source_crs_code); + auto targetCRS = + d->createFactory(target_crs_auth_name) + ->createCoordinateReferenceSystem(target_crs_code); + + std::vector parameters; + std::vector values; + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION)); + values.emplace_back(createLength(tx, uom_translation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION)); + values.emplace_back(createLength(ty, uom_translation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION)); + values.emplace_back(createLength(tz, uom_translation)); + + if (!rx.empty()) { + // Helmert 7-, 8-, 10- or 15- parameter cases + auto uom_rotation = d->createUnitOfMeasure( + rotation_uom_auth_name, rotation_uom_code); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_X_AXIS_ROTATION)); + values.emplace_back(createAngle(rx, uom_rotation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_Y_AXIS_ROTATION)); + values.emplace_back(createAngle(ry, uom_rotation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_Z_AXIS_ROTATION)); + values.emplace_back(createAngle(rz, uom_rotation)); + + auto uom_scale_difference = + scale_difference_uom_auth_name.empty() + ? common::UnitOfMeasure::NONE + : d->createUnitOfMeasure(scale_difference_uom_auth_name, + scale_difference_uom_code); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_SCALE_DIFFERENCE)); + values.emplace_back(operation::ParameterValue::create( + common::Scale(c_locale_stod(scale_difference), + uom_scale_difference))); + } + + if (!rate_tx.empty()) { + // Helmert 15-parameter + + auto uom_rate_translation = d->createUnitOfMeasure( + rate_translation_uom_auth_name, rate_translation_uom_code); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION)); + values.emplace_back( + createLength(rate_tx, uom_rate_translation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION)); + values.emplace_back( + createLength(rate_ty, uom_rate_translation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION)); + values.emplace_back( + createLength(rate_tz, uom_rate_translation)); + + auto uom_rate_rotation = d->createUnitOfMeasure( + rate_rotation_uom_auth_name, rate_rotation_uom_code); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION)); + values.emplace_back(createAngle(rate_rx, uom_rate_rotation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION)); + values.emplace_back(createAngle(rate_ry, uom_rate_rotation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION)); + values.emplace_back(createAngle(rate_rz, uom_rate_rotation)); + + auto uom_rate_scale_difference = + d->createUnitOfMeasure(rate_scale_difference_uom_auth_name, + rate_scale_difference_uom_code); + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE)); + values.emplace_back(operation::ParameterValue::create( + common::Scale(c_locale_stod(rate_scale_difference), + uom_rate_scale_difference))); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_REFERENCE_EPOCH)); + values.emplace_back(operation::ParameterValue::create( + common::Measure(c_locale_stod(epoch), uom_epoch))); + } else if (uom_epoch != common::UnitOfMeasure::NONE) { + // Helmert 8-parameter + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH)); + values.emplace_back(operation::ParameterValue::create( + common::Measure(c_locale_stod(epoch), uom_epoch))); + } else if (!px.empty()) { + // Molodensky-Badekas case + auto uom_pivot = + d->createUnitOfMeasure(pivot_uom_auth_name, pivot_uom_code); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT)); + values.emplace_back(createLength(px, uom_pivot)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT)); + values.emplace_back(createLength(py, uom_pivot)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT)); + values.emplace_back(createLength(pz, uom_pivot)); + } + + auto props = + d->createProperties(code, name, deprecated, + area_of_use_auth_name, area_of_use_code); + + auto propsMethod = + util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) + .set(metadata::Identifier::CODE_KEY, method_code) + .set(common::IdentifiedObject::NAME_KEY, method_name); + + std::vector accuracies; + if (!accuracy.empty()) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(accuracy)); + } + return operation::Transformation::create( + props, sourceCRS, targetCRS, nullptr, propsMethod, parameters, + values, accuracies); + + } catch (const std::exception &ex) { + throw buildFactoryException("transformation", code, ex); + } + } + + if (type == "grid_transformation") { + auto res = d->runWithCodeParam( + "SELECT name, method_auth_name, method_code, method_name, " + "source_crs_auth_name, source_crs_code, target_crs_auth_name, " + "target_crs_code, area_of_use_auth_name, area_of_use_code, " + "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, " + "grid_name, " + "grid2_param_auth_name, grid2_param_code, grid2_param_name, " + "grid2_name, " + "interpolation_crs_auth_name, interpolation_crs_code, deprecated " + "FROM " + "grid_transformation WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + // shouldn't happen if foreign keys are OK + throw NoSuchAuthorityCodeException("grid_transformation not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + size_t idx = 0; + const auto &name = row[idx++]; + const auto &method_auth_name = row[idx++]; + const auto &method_code = row[idx++]; + const auto &method_name = row[idx++]; + const auto &source_crs_auth_name = row[idx++]; + const auto &source_crs_code = row[idx++]; + const auto &target_crs_auth_name = row[idx++]; + const auto &target_crs_code = row[idx++]; + const auto &area_of_use_auth_name = row[idx++]; + const auto &area_of_use_code = row[idx++]; + const auto &accuracy = row[idx++]; + const auto &grid_param_auth_name = row[idx++]; + const auto &grid_param_code = row[idx++]; + const auto &grid_param_name = row[idx++]; + const auto &grid_name = row[idx++]; + const auto &grid2_param_auth_name = row[idx++]; + const auto &grid2_param_code = row[idx++]; + const auto &grid2_param_name = row[idx++]; + const auto &grid2_name = row[idx++]; + const auto &interpolation_crs_auth_name = row[idx++]; + const auto &interpolation_crs_code = row[idx++]; + + const auto &deprecated_str = row[idx++]; + const bool deprecated = deprecated_str == "1"; + assert(idx == row.size()); + + auto sourceCRS = + d->createFactory(source_crs_auth_name) + ->createCoordinateReferenceSystem(source_crs_code); + auto targetCRS = + d->createFactory(target_crs_auth_name) + ->createCoordinateReferenceSystem(target_crs_code); + auto interpolationCRS = + interpolation_crs_auth_name.empty() + ? nullptr + : d->createFactory(interpolation_crs_auth_name) + ->createCoordinateReferenceSystem( + interpolation_crs_code) + .as_nullable(); + + std::vector parameters; + std::vector values; + + parameters.emplace_back(operation::OperationParameter::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, grid_param_name) + .set(metadata::Identifier::CODESPACE_KEY, + grid_param_auth_name) + .set(metadata::Identifier::CODE_KEY, grid_param_code))); + values.emplace_back( + operation::ParameterValue::createFilename(grid_name)); + if (!grid2_name.empty()) { + parameters.emplace_back(operation::OperationParameter::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, + grid2_param_name) + .set(metadata::Identifier::CODESPACE_KEY, + grid2_param_auth_name) + .set(metadata::Identifier::CODE_KEY, + grid2_param_code))); + values.emplace_back( + operation::ParameterValue::createFilename(grid2_name)); + } + + auto props = + d->createProperties(code, name, deprecated, + area_of_use_auth_name, area_of_use_code); + auto propsMethod = + util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) + .set(metadata::Identifier::CODE_KEY, method_code) + .set(common::IdentifiedObject::NAME_KEY, method_name); + + std::vector accuracies; + if (!accuracy.empty()) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(accuracy)); + } + auto transf = operation::Transformation::create( + props, sourceCRS, targetCRS, interpolationCRS, propsMethod, + parameters, values, accuracies); + if (usePROJAlternativeGridNames) { + return transf->substitutePROJAlternativeGridNames(d->context()); + } + return transf; + + } catch (const std::exception &ex) { + throw buildFactoryException("transformation", code, ex); + } + } + + if (type == "other_transformation") { + std::ostringstream buffer; + buffer.imbue(std::locale::classic()); + buffer + << "SELECT name, method_auth_name, method_code, method_name, " + "source_crs_auth_name, source_crs_code, target_crs_auth_name, " + "target_crs_code, area_of_use_auth_name, area_of_use_code, " + "accuracy"; + constexpr int N_MAX_PARAMS = 7; + for (int i = 1; i <= N_MAX_PARAMS; ++i) { + buffer << ", param" << i << "_auth_name"; + buffer << ", param" << i << "_code"; + buffer << ", param" << i << "_name"; + buffer << ", param" << i << "_value"; + buffer << ", param" << i << "_uom_auth_name"; + buffer << ", param" << i << "_uom_code"; + } + buffer << ", deprecated FROM other_transformation WHERE auth_name = ? " + "AND code = ?"; + + auto res = d->runWithCodeParam(buffer.str(), code); + if (res.empty()) { + // shouldn't happen if foreign keys are OK + throw NoSuchAuthorityCodeException("other_transformation not found", + d->authority(), code); + } + try { + const auto &row = res.front(); + size_t idx = 0; + const auto &name = row[idx++]; + const auto &method_auth_name = row[idx++]; + const auto &method_code = row[idx++]; + const auto &method_name = row[idx++]; + const auto &source_crs_auth_name = row[idx++]; + const auto &source_crs_code = row[idx++]; + const auto &target_crs_auth_name = row[idx++]; + const auto &target_crs_code = row[idx++]; + const auto &area_of_use_auth_name = row[idx++]; + const auto &area_of_use_code = row[idx++]; + const auto &accuracy = row[idx++]; + + const size_t base_param_idx = idx; + std::vector parameters; + std::vector values; + for (int i = 0; i < N_MAX_PARAMS; ++i) { + const auto ¶m_auth_name = row[base_param_idx + i * 6 + 0]; + if (param_auth_name.empty()) { + break; + } + const auto ¶m_code = row[base_param_idx + i * 6 + 1]; + const auto ¶m_name = row[base_param_idx + i * 6 + 2]; + const auto ¶m_value = row[base_param_idx + i * 6 + 3]; + const auto ¶m_uom_auth_name = + row[base_param_idx + i * 6 + 4]; + const auto ¶m_uom_code = row[base_param_idx + i * 6 + 5]; + parameters.emplace_back(operation::OperationParameter::create( + util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, + param_auth_name) + .set(metadata::Identifier::CODE_KEY, param_code) + .set(common::IdentifiedObject::NAME_KEY, param_name))); + std::string normalized_uom_code(param_uom_code); + const double normalized_value = normalizeMeasure( + param_uom_code, param_value, normalized_uom_code); + auto uom = d->createUnitOfMeasure(param_uom_auth_name, + normalized_uom_code); + values.emplace_back(operation::ParameterValue::create( + common::Measure(normalized_value, uom))); + } + idx = base_param_idx + 6 * N_MAX_PARAMS; + + const auto &deprecated_str = row[idx++]; + const bool deprecated = deprecated_str == "1"; + assert(idx == row.size()); + + auto sourceCRS = + d->createFactory(source_crs_auth_name) + ->createCoordinateReferenceSystem(source_crs_code); + auto targetCRS = + d->createFactory(target_crs_auth_name) + ->createCoordinateReferenceSystem(target_crs_code); + + auto props = + d->createProperties(code, name, deprecated, + area_of_use_auth_name, area_of_use_code); + + std::vector accuracies; + if (!accuracy.empty()) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(accuracy)); + } + + if (method_auth_name == "PROJ") { + if (method_code == "PROJString") { + return operation::SingleOperation::createPROJBased( + props, method_name, sourceCRS, targetCRS, accuracies); + } else if (method_code == "WKT") { + auto op = util::nn_dynamic_pointer_cast< + operation::CoordinateOperation>( + WKTParser().createFromWKT(method_name)); + if (!op) { + throw FactoryException("WKT string does not express a " + "coordinate operation"); + } + op->setCRSs(sourceCRS, targetCRS, nullptr); + return NN_NO_CHECK(op); + } + } + + auto propsMethod = + util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) + .set(metadata::Identifier::CODE_KEY, method_code) + .set(common::IdentifiedObject::NAME_KEY, method_name); + + if (method_auth_name == metadata::Identifier::EPSG && + operation::isAxisOrderReversal( + std::atoi(method_code.c_str()))) { + auto op = operation::Conversion::create(props, propsMethod, + parameters, values); + op->setCRSs(sourceCRS, targetCRS, nullptr); + return op; + } + return operation::Transformation::create( + props, sourceCRS, targetCRS, nullptr, propsMethod, parameters, + values, accuracies); + + } catch (const std::exception &ex) { + throw buildFactoryException("transformation", code, ex); + } + } + + if (allowConcatenated && type == "concatenated_operation") { + auto res = d->runWithCodeParam( + "SELECT name, source_crs_auth_name, source_crs_code, " + "target_crs_auth_name, target_crs_code, " + "area_of_use_auth_name, area_of_use_code, accuracy, " + "step1_auth_name, step1_code, step2_auth_name, step2_code, " + "step3_auth_name, step3_code, deprecated FROM " + "concatenated_operation WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + // shouldn't happen if foreign keys are OK + throw NoSuchAuthorityCodeException( + "concatenated_operation not found", d->authority(), code); + } + try { + const auto &row = res.front(); + size_t idx = 0; + const auto &name = row[idx++]; + const auto &source_crs_auth_name = row[idx++]; + const auto &source_crs_code = row[idx++]; + const auto &target_crs_auth_name = row[idx++]; + const auto &target_crs_code = row[idx++]; + const auto &area_of_use_auth_name = row[idx++]; + const auto &area_of_use_code = row[idx++]; + const auto &accuracy = row[idx++]; + const auto &step1_auth_name = row[idx++]; + const auto &step1_code = row[idx++]; + const auto &step2_auth_name = row[idx++]; + const auto &step2_code = row[idx++]; + const auto &step3_auth_name = row[idx++]; + const auto &step3_code = row[idx++]; + const auto &deprecated_str = row[idx++]; + const bool deprecated = deprecated_str == "1"; + + std::vector operations; + operations.push_back( + d->createFactory(step1_auth_name) + ->createCoordinateOperation(step1_code, false, + usePROJAlternativeGridNames, + std::string())); + operations.push_back( + d->createFactory(step2_auth_name) + ->createCoordinateOperation(step2_code, false, + usePROJAlternativeGridNames, + std::string())); + + if (!step3_auth_name.empty()) { + operations.push_back( + d->createFactory(step3_auth_name) + ->createCoordinateOperation(step3_code, false, + usePROJAlternativeGridNames, + std::string())); + } + + // In case the operation is a conversion (we hope this is the + // forward case!) + if (!operations[0]->sourceCRS() || !operations[0]->targetCRS()) { + if (!operations[1]->sourceCRS()) { + throw FactoryException( + "chaining of conversion not supported"); + } + operations[0]->setCRSs( + d->createFactory(source_crs_auth_name) + ->createCoordinateReferenceSystem(source_crs_code), + NN_NO_CHECK(operations[1]->sourceCRS()), nullptr); + } + + // Some concatenated operations, like 8443, might actually chain + // reverse operations rather than forward operations. + { + const auto &op0SrcId = + operations[0]->sourceCRS()->identifiers()[0]; + if (op0SrcId->code() != source_crs_code || + *op0SrcId->codeSpace() != source_crs_auth_name) { + operations[0] = operations[0]->inverse(); + } + } + + { + const auto &op0SrcId = + operations[0]->sourceCRS()->identifiers()[0]; + if (op0SrcId->code() != source_crs_code || + *op0SrcId->codeSpace() != source_crs_auth_name) { + throw FactoryException( + "Source CRS of first operation in concatenated " + "operation " + + code + " does not match source CRS of " + "concatenated operation"); + } + } + + // In case the operation is a conversion (we hope this is the + // forward case!) + if (!operations[1]->sourceCRS() || !operations[1]->targetCRS()) { + if (step3_auth_name.empty()) { + operations[1]->setCRSs( + NN_NO_CHECK(operations[0]->targetCRS()), + d->createFactory(target_crs_auth_name) + ->createCoordinateReferenceSystem(target_crs_code), + nullptr); + } else { + if (!operations[2]->sourceCRS()) { + throw FactoryException( + "chaining of conversion not supported"); + } + operations[1]->setCRSs( + NN_NO_CHECK(operations[0]->targetCRS()), + NN_NO_CHECK(operations[2]->sourceCRS()), nullptr); + } + } + + const auto &op1SrcId = operations[1]->sourceCRS()->identifiers()[0]; + const auto &op0TargetId = + operations[0]->targetCRS()->identifiers()[0]; + while (true) { + if (step3_auth_name.empty()) { + const auto &opLastTargetId = + operations.back()->targetCRS()->identifiers()[0]; + if (opLastTargetId->code() == target_crs_code && + *opLastTargetId->codeSpace() == target_crs_auth_name) { + // in case we have only 2 steps, and + // step2.targetCRS == concatenate.targetCRS do nothing, + // but ConcatenatedOperation::create() will ultimately + // check that step1.targetCRS == step2.sourceCRS + break; + } + } + if (op1SrcId->code() != op0TargetId->code() || + *op1SrcId->codeSpace() != *op0TargetId->codeSpace()) { + operations[1] = operations[1]->inverse(); + } + break; + } + + if (!step3_auth_name.empty()) { + + const auto &op2Src = operations[2]->sourceCRS(); + // In case the operation is a conversion (we hope this is the + // forward case!) + if (!op2Src || !operations[2]->targetCRS()) { + operations[2]->setCRSs( + NN_NO_CHECK(operations[1]->targetCRS()), + d->createFactory(target_crs_auth_name) + ->createCoordinateReferenceSystem(target_crs_code), + nullptr); + } + + const auto &op2SrcId = op2Src->identifiers()[0]; + const auto &op1TargetId = + operations[1]->targetCRS()->identifiers()[0]; + if (op2SrcId->code() != op1TargetId->code() || + *op2SrcId->codeSpace() != *op1TargetId->codeSpace()) { + operations[2] = operations[2]->inverse(); + } + } + + const auto &opLastTargetId = + operations.back()->targetCRS()->identifiers()[0]; + if (opLastTargetId->code() != target_crs_code || + *opLastTargetId->codeSpace() != target_crs_auth_name) { + throw FactoryException( + "Target CRS of last operation in concatenated operation " + + code + + " doest not match target CRS of concatenated operation"); + } + + auto props = + d->createProperties(code, name, deprecated, + area_of_use_auth_name, area_of_use_code); + + std::vector accuracies; + if (!accuracy.empty()) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(accuracy)); + } else { + // Try to compute a reasonable accuracy from the members + double totalAcc = -1; + try { + for (const auto &op : operations) { + auto accs = op->coordinateOperationAccuracies(); + if (accs.size() == 1) { + double acc = c_locale_stod(accs[0]->value()); + if (totalAcc < 0) { + totalAcc = acc; + } else { + totalAcc += acc; + } + } else { + totalAcc = -1; + break; + } + } + if (totalAcc >= 0) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create( + toString(totalAcc))); + } + } catch (const std::exception &) { + } + } + return operation::ConcatenatedOperation::create(props, operations, + accuracies); + + } catch (const std::exception &ex) { + throw buildFactoryException("transformation", code, ex); + } + } + + throw FactoryException("unhandled coordinate operation type: " + type); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a list operation::CoordinateOperation between two CRS. + * + * The list is ordered with preferred operations first. No attempt is made + * at infering operations that are not explicitly in the database. + * + * Deprecated operations are rejected. + * + * @param sourceCRSCode Source CRS code allocated by authority. + * @param targetCRSCode Source CRS code allocated by authority. + * @return list of coordinate operations + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +std::vector +AuthorityFactory::createFromCoordinateReferenceSystemCodes( + const std::string &sourceCRSCode, const std::string &targetCRSCode) const { + return createFromCoordinateReferenceSystemCodes( + d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false, + false, false); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a list operation::CoordinateOperation between two CRS. + * + * The list is ordered with preferred operations first. No attempt is made + * at infering operations that are not explicitly in the database (see + * createFromCRSCodesWithIntermediates() for that), and only + * source -> target operations are searched (ie if target -> source is present, + * you need to call this method with the arguments reversed, and apply the + * reverse transformations). + * + * Deprecated operations are rejected. + * + * If getAuthority() returns empty, then coordinate operations from all + * authorities are considered. + * + * @param sourceCRSAuthName Authority name of sourceCRSCode + * @param sourceCRSCode Source CRS code allocated by authority + * sourceCRSAuthName. + * @param targetCRSAuthName Authority name of targetCRSCode + * @param targetCRSCode Source CRS code allocated by authority + * targetCRSAuthName. + * @param usePROJAlternativeGridNames Whether PROJ alternative grid names + * should be substituted to the official grid names. + * @param discardIfMissingGrid Whether coordinate operations that reference + * missing grids should be removed from the result set. + * @param discardSuperseded Whether cordinate operations that are superseded + * (but not deprecated) should be removed from the result set. + * @return list of coordinate operations + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +std::vector +AuthorityFactory::createFromCoordinateReferenceSystemCodes( + const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, + const std::string &targetCRSAuthName, const std::string &targetCRSCode, + bool usePROJAlternativeGridNames, bool discardIfMissingGrid, + bool discardSuperseded) const { + + auto cacheKey(d->authority()); + cacheKey += sourceCRSAuthName; + cacheKey += sourceCRSCode; + cacheKey += targetCRSAuthName; + cacheKey += targetCRSCode; + cacheKey += (usePROJAlternativeGridNames ? '1' : '0'); + cacheKey += (discardIfMissingGrid ? '1' : '0'); + cacheKey += (discardSuperseded ? '1' : '0'); + + std::vector list; + + if (d->context()->d->getCRSToCRSCoordOpFromCache(cacheKey, list)) { + return list; + } + + // Look-up first for conversion which is the most precise. + std::string sql("SELECT conversion_auth_name, " + "geodetic_crs_auth_name, geodetic_crs_code FROM " + "projected_crs WHERE auth_name = ? AND code = ?"); + auto params = ListOfParams{targetCRSAuthName, targetCRSCode}; + auto res = d->run(sql, params); + if (!res.empty()) { + const auto &row = res.front(); + bool ok = row[1] == sourceCRSAuthName && row[2] == sourceCRSCode; + if (ok && d->hasAuthorityRestriction()) { + ok = row[0] == d->authority(); + } + if (ok) { + auto targetCRS = d->createFactory(targetCRSAuthName) + ->createProjectedCRS(targetCRSCode); + auto conv = targetCRS->derivingConversion(); + list.emplace_back(conv); + d->context()->d->cache(cacheKey, list); + return list; + } + } + if (discardSuperseded) { + sql = "SELECT cov.auth_name, cov.code, cov.table_name, " + "ss.replacement_auth_name, ss.replacement_code FROM " + "coordinate_operation_view cov JOIN area " + "ON cov.area_of_use_auth_name = area.auth_name AND " + "cov.area_of_use_code = area.code " + "LEFT JOIN supersession ss ON " + "ss.superseded_table_name = cov.table_name AND " + "ss.superseded_auth_name = cov.auth_name AND " + "ss.superseded_code = cov.code AND " + "ss.superseded_table_name = ss.replacement_table_name " + "WHERE source_crs_auth_name = ? AND source_crs_code = ? AND " + "target_crs_auth_name = ? AND target_crs_code = ? AND " + "cov.deprecated = 0"; + } else { + sql = "SELECT cov.auth_name, cov.code, cov.table_name FROM " + "coordinate_operation_view cov JOIN area " + "ON cov.area_of_use_auth_name = area.auth_name AND " + "cov.area_of_use_code = area.code " + "WHERE source_crs_auth_name = ? AND source_crs_code = ? AND " + "target_crs_auth_name = ? AND target_crs_code = ? AND " + "cov.deprecated = 0"; + } + params = {sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, + targetCRSCode}; + if (d->hasAuthorityRestriction()) { + sql += " AND cov.auth_name = ?"; + params.emplace_back(d->authority()); + } + sql += " ORDER BY pseudo_area_from_swne(south_lat, west_lon, north_lat, " + "east_lon) DESC, " + "(CASE WHEN accuracy is NULL THEN 1 ELSE 0 END), accuracy"; + res = d->run(sql, params); + std::set> setTransf; + if (discardSuperseded) { + for (const auto &row : res) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + setTransf.insert( + std::pair(auth_name, code)); + } + } + for (const auto &row : res) { + if (discardSuperseded) { + const auto &replacement_auth_name = row[3]; + const auto &replacement_code = row[4]; + if (!replacement_auth_name.empty() && + setTransf.find(std::pair( + replacement_auth_name, replacement_code)) != + setTransf.end()) { + // Skip transformations that are superseded by others that got + // returned in the result set. + continue; + } + } + + const auto &auth_name = row[0]; + const auto &code = row[1]; + const auto &table_name = row[2]; + auto op = d->createFactory(auth_name)->createCoordinateOperation( + code, true, usePROJAlternativeGridNames, table_name); + if (!d->rejectOpDueToMissingGrid(op, discardIfMissingGrid)) { + list.emplace_back(op); + } + } + d->context()->d->cache(cacheKey, list); + return list; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static std::string +buildIntermediateWhere(const std::vector> + &intermediateCRSAuthCodes, + const std::string &first_field, + const std::string &second_field) { + if (intermediateCRSAuthCodes.empty()) { + return std::string(); + } + std::string sql(" AND ("); + for (size_t i = 0; i < intermediateCRSAuthCodes.size(); ++i) { + if (i > 0) { + sql += " OR"; + } + sql += "(v1." + first_field + "_crs_auth_name = ? AND "; + sql += "v1." + first_field + "_crs_code = ? AND "; + sql += "v2." + second_field + "_crs_auth_name = ? AND "; + sql += "v2." + second_field + "_crs_code = ?) "; + } + sql += ")"; + return sql; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op, + const std::string &sourceCRSAuthName, + const std::string &sourceCRSCode, + const std::string &targetCRSAuthName, + const std::string &targetCRSCode) { + auto concat = + dynamic_cast(op.get()); + if (!concat) { + return false; + } + auto ops = concat->operations(); + for (size_t i = 0; i + 1 < ops.size(); i++) { + auto targetCRS = ops[i]->targetCRS(); + if (targetCRS) { + const auto &ids = targetCRS->identifiers(); + if (ids.size() == 1 && + ((*ids[0]->codeSpace() == sourceCRSAuthName && + ids[0]->code() == sourceCRSCode) || + (*ids[0]->codeSpace() == targetCRSAuthName && + ids[0]->code() == targetCRSCode))) { + return true; + } + } + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns a list operation::CoordinateOperation between two CRS, + * using intermediate codes. + * + * The list is ordered with preferred operations first. + * + * Deprecated operations are rejected. + * + * The method will take care of considering all potential combinations (ie + * contrary to createFromCoordinateReferenceSystemCodes(), you do not need to + * call it with sourceCRS and targetCRS switched) + * + * If getAuthority() returns empty, then coordinate operations from all + * authorities are considered. + * + * @param sourceCRSAuthName Authority name of sourceCRSCode + * @param sourceCRSCode Source CRS code allocated by authority + * sourceCRSAuthName. + * @param targetCRSAuthName Authority name of targetCRSCode + * @param targetCRSCode Source CRS code allocated by authority + * targetCRSAuthName. + * @param usePROJAlternativeGridNames Whether PROJ alternative grid names + * should be substituted to the official grid names. + * @param discardIfMissingGrid Whether coordinate operations that reference + * missing grids should be removed from the result set. + * @param discardSuperseded Whether cordinate operations that are superseded + * (but not deprecated) should be removed from the result set. + * @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be + * used as potential intermediate CRS. If the list is empty, the database will + * be used to find common CRS in operations involving both the source and + * target CRS. + * @return list of coordinate operations + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +std::vector +AuthorityFactory::createFromCRSCodesWithIntermediates( + const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, + const std::string &targetCRSAuthName, const std::string &targetCRSCode, + bool usePROJAlternativeGridNames, bool discardIfMissingGrid, + bool discardSuperseded, + const std::vector> + &intermediateCRSAuthCodes) const { + + std::vector listTmp; + + if (sourceCRSAuthName == targetCRSAuthName && + sourceCRSCode == targetCRSCode) { + return listTmp; + } + + const std::string sqlProlog( + discardSuperseded + ? + + "SELECT v1.table_name as table1, " + "v1.auth_name AS auth_name1, v1.code AS code1, " + "v1.accuracy AS accuracy1, " + "v2.table_name as table2, " + "v2.auth_name AS auth_name2, v2.code AS code2, " + "v2.accuracy as accuracy2, " + "a1.south_lat AS south_lat1, " + "a1.west_lon AS west_lon1, " + "a1.north_lat AS north_lat1, " + "a1.east_lon AS east_lon1, " + "a2.south_lat AS south_lat2, " + "a2.west_lon AS west_lon2, " + "a2.north_lat AS north_lat2, " + "a2.east_lon AS east_lon2, " + "ss1.replacement_auth_name AS replacement_auth_name1, " + "ss1.replacement_code AS replacement_code1, " + "ss2.replacement_auth_name AS replacement_auth_name2, " + "ss2.replacement_code AS replacement_code2 " + "FROM coordinate_operation_view v1 " + "JOIN coordinate_operation_view v2 " + : + + "SELECT v1.table_name as table1, " + "v1.auth_name AS auth_name1, v1.code AS code1, " + "v1.accuracy AS accuracy1, " + "v2.table_name as table2, " + "v2.auth_name AS auth_name2, v2.code AS code2, " + "v2.accuracy as accuracy2, " + "a1.south_lat AS south_lat1, " + "a1.west_lon AS west_lon1, " + "a1.north_lat AS north_lat1, " + "a1.east_lon AS east_lon1, " + "a2.south_lat AS south_lat2, " + "a2.west_lon AS west_lon2, " + "a2.north_lat AS north_lat2, " + "a2.east_lon AS east_lon2 " + "FROM coordinate_operation_view v1 " + "JOIN coordinate_operation_view v2 "); + + const std::string joinSupersession( + "LEFT JOIN supersession ss1 ON " + "ss1.superseded_table_name = v1.table_name AND " + "ss1.superseded_auth_name = v1.auth_name AND " + "ss1.superseded_code = v1.code AND " + "ss1.superseded_table_name = ss1.replacement_table_name " + "LEFT JOIN supersession ss2 ON " + "ss2.superseded_table_name = v2.table_name AND " + "ss2.superseded_auth_name = v2.auth_name AND " + "ss2.superseded_code = v2.code AND " + "ss2.superseded_table_name = ss2.replacement_table_name "); + const std::string joinArea( + (discardSuperseded ? joinSupersession : std::string()) + + "JOIN area a1 ON v1.area_of_use_auth_name = a1.auth_name " + "AND v1.area_of_use_code = a1.code " + "JOIN area a2 ON v2.area_of_use_auth_name = a2.auth_name " + "AND v2.area_of_use_code = a2.code "); + const std::string orderBy( + "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + " + "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), " + "accuracy1 + accuracy2"); + + // Case (source->intermediate) and (intermediate->target) + std::string sql( + sqlProlog + "ON v1.target_crs_auth_name = v2.source_crs_auth_name " + "AND v1.target_crs_code = v2.source_crs_code " + + joinArea + + "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? " + "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? "); + auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode}; + + std::string additionalWhere( + "AND v1.deprecated = 0 AND v2.deprecated = 0 " + "AND intersects_bbox(south_lat1, west_lon1, north_lat1, east_lon1, " + "south_lat2, west_lon2, north_lat2, east_lon2) == 1 "); + if (d->hasAuthorityRestriction()) { + additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? "; + params.emplace_back(d->authority()); + params.emplace_back(d->authority()); + } + std::string intermediateWhere = + buildIntermediateWhere(intermediateCRSAuthCodes, "target", "source"); + for (const auto &pair : intermediateCRSAuthCodes) { + params.emplace_back(pair.first); + params.emplace_back(pair.second); + params.emplace_back(pair.first); + params.emplace_back(pair.second); + } + auto res = + d->run(sql + additionalWhere + intermediateWhere + orderBy, params); + + const auto filterOutSuperseded = [](SQLResultSet &&resultSet) { + std::set> setTransf1; + std::set> setTransf2; + for (const auto &row : resultSet) { + // table1 + const auto &auth_name1 = row[1]; + const auto &code1 = row[2]; + // accuracy1 + // table2 + const auto &auth_name2 = row[5]; + const auto &code2 = row[6]; + setTransf1.insert( + std::pair(auth_name1, code1)); + setTransf2.insert( + std::pair(auth_name2, code2)); + } + SQLResultSet filteredResultSet; + for (const auto &row : resultSet) { + const auto &replacement_auth_name1 = row[16]; + const auto &replacement_code1 = row[17]; + const auto &replacement_auth_name2 = row[18]; + const auto &replacement_code2 = row[19]; + if (!replacement_auth_name1.empty() && + setTransf1.find(std::pair( + replacement_auth_name1, replacement_code1)) != + setTransf1.end()) { + // Skip transformations that are superseded by others that got + // returned in the result set. + continue; + } + if (!replacement_auth_name2.empty() && + setTransf2.find(std::pair( + replacement_auth_name2, replacement_code2)) != + setTransf2.end()) { + // Skip transformations that are superseded by others that got + // returned in the result set. + continue; + } + filteredResultSet.emplace_back(row); + } + return filteredResultSet; + }; + + if (discardSuperseded) { + res = filterOutSuperseded(std::move(res)); + } + for (const auto &row : res) { + const auto &table1 = row[0]; + const auto &auth_name1 = row[1]; + const auto &code1 = row[2]; + // const auto &accuracy1 = row[3]; + const auto &table2 = row[4]; + const auto &auth_name2 = row[5]; + const auto &code2 = row[6]; + // const auto &accuracy2 = row[7]; + auto op1 = d->createFactory(auth_name1) + ->createCoordinateOperation( + code1, true, usePROJAlternativeGridNames, table1); + if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + auto op2 = d->createFactory(auth_name2) + ->createCoordinateOperation( + code2, true, usePROJAlternativeGridNames, table2); + if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + + listTmp.emplace_back( + operation::ConcatenatedOperation::createComputeMetadata({op1, op2}, + false)); + } + + // Case (source->intermediate) and (target->intermediate) + sql = sqlProlog + "ON v1.target_crs_auth_name = v2.target_crs_auth_name " + "AND v1.target_crs_code = v2.target_crs_code " + + joinArea + + "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? " + "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? "; + intermediateWhere = + buildIntermediateWhere(intermediateCRSAuthCodes, "target", "target"); + res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); + if (discardSuperseded) { + res = filterOutSuperseded(std::move(res)); + } + for (const auto &row : res) { + const auto &table1 = row[0]; + const auto &auth_name1 = row[1]; + const auto &code1 = row[2]; + // const auto &accuracy1 = row[3]; + const auto &table2 = row[4]; + const auto &auth_name2 = row[5]; + const auto &code2 = row[6]; + // const auto &accuracy2 = row[7]; + auto op1 = d->createFactory(auth_name1) + ->createCoordinateOperation( + code1, true, usePROJAlternativeGridNames, table1); + if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + auto op2 = d->createFactory(auth_name2) + ->createCoordinateOperation( + code2, true, usePROJAlternativeGridNames, table2); + if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + + listTmp.emplace_back( + operation::ConcatenatedOperation::createComputeMetadata( + {op1, op2->inverse()}, false)); + } + + // Case (intermediate->source) and (intermediate->target) + sql = sqlProlog + "ON v1.source_crs_auth_name = v2.source_crs_auth_name " + "AND v1.source_crs_code = v2.source_crs_code " + + joinArea + + "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? " + "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? "; + intermediateWhere = + buildIntermediateWhere(intermediateCRSAuthCodes, "source", "source"); + res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); + if (discardSuperseded) { + res = filterOutSuperseded(std::move(res)); + } + for (const auto &row : res) { + const auto &table1 = row[0]; + const auto &auth_name1 = row[1]; + const auto &code1 = row[2]; + // const auto &accuracy1 = row[3]; + const auto &table2 = row[4]; + const auto &auth_name2 = row[5]; + const auto &code2 = row[6]; + // const auto &accuracy2 = row[7]; + auto op1 = d->createFactory(auth_name1) + ->createCoordinateOperation( + code1, true, usePROJAlternativeGridNames, table1); + if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + auto op2 = d->createFactory(auth_name2) + ->createCoordinateOperation( + code2, true, usePROJAlternativeGridNames, table2); + if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + + listTmp.emplace_back( + operation::ConcatenatedOperation::createComputeMetadata( + {op1->inverse(), op2}, false)); + } + + // Case (intermediate->source) and (target->intermediate) + sql = sqlProlog + "ON v1.source_crs_auth_name = v2.target_crs_auth_name " + "AND v1.source_crs_code = v2.target_crs_code " + + joinArea + + "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? " + "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? "; + intermediateWhere = + buildIntermediateWhere(intermediateCRSAuthCodes, "source", "target"); + res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); + if (discardSuperseded) { + res = filterOutSuperseded(std::move(res)); + } + for (const auto &row : res) { + const auto &table1 = row[0]; + const auto &auth_name1 = row[1]; + const auto &code1 = row[2]; + // const auto &accuracy1 = row[3]; + const auto &table2 = row[4]; + const auto &auth_name2 = row[5]; + const auto &code2 = row[6]; + // const auto &accuracy2 = row[7]; + auto op1 = d->createFactory(auth_name1) + ->createCoordinateOperation( + code1, true, usePROJAlternativeGridNames, table1); + if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + auto op2 = d->createFactory(auth_name2) + ->createCoordinateOperation( + code2, true, usePROJAlternativeGridNames, table2); + if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + + listTmp.emplace_back( + operation::ConcatenatedOperation::createComputeMetadata( + {op1->inverse(), op2->inverse()}, false)); + } + + std::vector list; + for (const auto &op : listTmp) { + if (!d->rejectOpDueToMissingGrid(op, discardIfMissingGrid)) { + list.emplace_back(op); + } + } + + return list; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the authority name associated to this factory. + * @return name. + */ +const std::string &AuthorityFactory::getAuthority() PROJ_CONST_DEFN { + return d->authority(); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the set of authority codes of the given object type. + * + * @param type Object type. + * @param allowDeprecated whether we should return deprecated objects as well. + * @return the set of authority codes for spatial reference objects of the given + * type + * @throw FactoryException + */ +std::set +AuthorityFactory::getAuthorityCodes(const ObjectType &type, + bool allowDeprecated) const { + std::string sql; + switch (type) { + case ObjectType::PRIME_MERIDIAN: + sql = "SELECT code FROM prime_meridian WHERE "; + break; + case ObjectType::ELLIPSOID: + sql = "SELECT code FROM ellipsoid WHERE "; + break; + case ObjectType::DATUM: + sql = "SELECT code FROM object_view WHERE table_name IN " + "('geodetic_datum', 'vertical_datum') AND "; + break; + case ObjectType::GEODETIC_REFERENCE_FRAME: + sql = "SELECT code FROM geodetic_datum WHERE "; + break; + case ObjectType::VERTICAL_REFERENCE_FRAME: + sql = "SELECT code FROM vertical_datum WHERE "; + break; + case ObjectType::CRS: + sql = "SELECT code FROM crs_view WHERE "; + break; + case ObjectType::GEODETIC_CRS: + sql = "SELECT code FROM geodetic_crs WHERE "; + break; + case ObjectType::GEOCENTRIC_CRS: + sql = "SELECT code FROM geodetic_crs WHERE type = " GEOCENTRIC " AND "; + break; + case ObjectType::GEOGRAPHIC_CRS: + sql = "SELECT code FROM geodetic_crs WHERE type IN (" GEOG_2D + "," GEOG_3D ") AND "; + break; + case ObjectType::GEOGRAPHIC_2D_CRS: + sql = "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D " AND "; + break; + case ObjectType::GEOGRAPHIC_3D_CRS: + sql = "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D " AND "; + break; + case ObjectType::VERTICAL_CRS: + sql = "SELECT code FROM vertical_crs WHERE "; + break; + case ObjectType::PROJECTED_CRS: + sql = "SELECT code FROM projected_crs WHERE "; + break; + case ObjectType::COMPOUND_CRS: + sql = "SELECT code FROM compound_crs WHERE "; + break; + case ObjectType::COORDINATE_OPERATION: + sql = + "SELECT code FROM coordinate_operation_with_conversion_view WHERE "; + break; + case ObjectType::CONVERSION: + sql = "SELECT code FROM conversion WHERE "; + break; + case ObjectType::TRANSFORMATION: + sql = "SELECT code FROM coordinate_operation_view WHERE table_name != " + "'concatenated_operation' AND "; + break; + case ObjectType::CONCATENATED_OPERATION: + sql = "SELECT code FROM concatenated_operation WHERE "; + break; + } + + sql += "auth_name = ?"; + if (!allowDeprecated) { + sql += " AND deprecated = 0"; + } + + auto res = d->run(sql, {d->authority()}); + std::set set; + for (const auto &row : res) { + set.insert(row[0]); + } + return set; +} + +// --------------------------------------------------------------------------- + +/** \brief Gets a description of the object corresponding to a code. + * + * \note In case of several objects of different types with the same code, + * one of them will be arbitrarily selected. + * + * @param code Object code allocated by authority. (e.g. "4326") + * @return description. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ +std::string +AuthorityFactory::getDescriptionText(const std::string &code) const { + auto sql = "SELECT name FROM object_view WHERE auth_name = ? AND code = " + "? ORDER BY table_name"; + auto res = d->runWithCodeParam(sql, code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("object not found", d->authority(), + code); + } + return res.front()[0]; +} + +// --------------------------------------------------------------------------- + +/** \brief Gets the official name from a possibly alias name. + * + * @param aliasedName Alias name. + * @param tableName Table name/category. Can help in case of ambiguities. + * Or empty otherwise. + * @param source Source of the alias. Can help in case of ambiguities. + * Or empty otherwise. + * @param tryEquivalentNameSpelling whether the comparison of aliasedName with + * the alt_name column of the alis_name table should be done with using + * metadata::Identifier::isEquivalentName() rather than strict string + * comparison; + * @param outTableName Table name in which the official name has been found. + * @param outAuthName Authority name of the official name that has been found. + * @param outCode Code of the official name that has been found. + * @return official name (or empty if not found). + * @throw FactoryException + */ +std::string AuthorityFactory::getOfficialNameFromAlias( + const std::string &aliasedName, const std::string &tableName, + const std::string &source, bool tryEquivalentNameSpelling, + std::string &outTableName, std::string &outAuthName, + std::string &outCode) const { + + if (tryEquivalentNameSpelling) { + std::string sql( + "SELECT table_name, auth_name, code, alt_name FROM alias_name"); + ListOfParams params; + if (!tableName.empty()) { + sql += " WHERE table_name = ?"; + params.push_back(tableName); + } + if (!source.empty()) { + if (!tableName.empty()) { + sql += " AND "; + } else { + sql += " WHERE "; + } + sql += "source = ?"; + params.push_back(source); + } + auto res = d->run(sql, params); + if (res.empty()) { + return std::string(); + } + for (const auto &row : res) { + const auto &alt_name = row[3]; + if (metadata::Identifier::isEquivalentName(alt_name.c_str(), + aliasedName.c_str())) { + outTableName = row[0]; + outAuthName = row[1]; + outCode = row[2]; + sql = "SELECT name FROM \""; + sql += replaceAll(outTableName, "\"", "\"\""); + sql += "\" WHERE auth_name = ? AND code = ?"; + res = d->run(sql, {outAuthName, outCode}); + if (res.empty()) { // shouldn't happen normally + return std::string(); + } + return res.front()[0]; + } + } + return std::string(); + } else { + std::string sql( + "SELECT table_name, auth_name, code FROM alias_name WHERE " + "alt_name = ?"); + ListOfParams params{aliasedName}; + if (!tableName.empty()) { + sql += " AND table_name = ?"; + params.push_back(tableName); + } + if (!source.empty()) { + sql += " AND source = ?"; + params.push_back(source); + } + auto res = d->run(sql, params); + if (res.empty()) { + return std::string(); + } + const auto &row = res.front(); + outTableName = row[0]; + outAuthName = row[1]; + outCode = row[2]; + sql = "SELECT name FROM \""; + sql += replaceAll(outTableName, "\"", "\"\""); + sql += "\" WHERE auth_name = ? AND code = ?"; + res = d->run(sql, {outAuthName, outCode}); + if (res.empty()) { // shouldn't happen normally + return std::string(); + } + return res.front()[0]; + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static void addToListString(std::string &out, const char *in) { + if (!out.empty()) { + out += ','; + } + out += in; +} + +static void addToListStringWithOR(std::string &out, const char *in) { + if (!out.empty()) { + out += " OR "; + } + out += in; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return a list of objects by their name + * + * @param searchedName Searched name. Must be at least 2 character long. + * @param allowedObjectTypes List of object types into which to search. If + * empty, all object types will be searched. + * @param approximateMatch Whether approximate name identification is allowed. + * @param limitResultCount Maximum number of results to return. + * Or 0 for unlimited. + * @return list of matched objects. + * @throw FactoryException + */ +std::list +AuthorityFactory::createObjectsFromName( + const std::string &searchedName, + const std::vector &allowedObjectTypes, bool approximateMatch, + size_t limitResultCount) const { + + std::string searchedNameWithoutDeprecated(searchedName); + bool deprecated = false; + if (ends_with(searchedNameWithoutDeprecated, " (deprecated)")) { + deprecated = true; + searchedNameWithoutDeprecated.resize( + searchedNameWithoutDeprecated.size() - strlen(" (deprecated)")); + } + + const std::string canonicalizedSearchedName( + metadata::Identifier::canonicalizeName(searchedNameWithoutDeprecated)); + if (canonicalizedSearchedName.size() <= 1) { + return {}; + } + + std::string sql( + "SELECT table_name, auth_name, code, name FROM object_view WHERE " + "deprecated = ? AND "); + ListOfParams params{deprecated ? 1.0 : 0.0}; + if (!approximateMatch) { + sql += "name LIKE ? AND "; + params.push_back(searchedNameWithoutDeprecated); + } + if (d->hasAuthorityRestriction()) { + sql += " auth_name = ? AND "; + params.emplace_back(d->authority()); + } + + if (allowedObjectTypes.empty()) { + sql += "table_name IN (" + "'prime_meridian','ellipsoid','geodetic_datum'," + "'vertical_datum','geodetic_crs','projected_crs'," + "'vertical_crs','compound_crs','conversion'," + "'helmert_transformation','grid_transformation'," + "'other_transformation','concatenated_operation'" + ")"; + } else { + std::string tableNameList; + std::string otherConditions; + for (const auto type : allowedObjectTypes) { + switch (type) { + case ObjectType::PRIME_MERIDIAN: + addToListString(tableNameList, "'prime_meridian'"); + break; + case ObjectType::ELLIPSOID: + addToListString(tableNameList, "'ellipsoid'"); + break; + case ObjectType::DATUM: + addToListString(tableNameList, + "'geodetic_datum','vertical_datum'"); + break; + case ObjectType::GEODETIC_REFERENCE_FRAME: + addToListString(tableNameList, "'geodetic_datum'"); + break; + case ObjectType::VERTICAL_REFERENCE_FRAME: + addToListString(tableNameList, "'vertical_datum'"); + break; + case ObjectType::CRS: + addToListString(tableNameList, "'geodetic_crs','projected_crs'," + "'vertical_crs','compound_crs'"); + break; + case ObjectType::GEODETIC_CRS: + addToListString(tableNameList, "'geodetic_crs'"); + break; + case ObjectType::GEOCENTRIC_CRS: + addToListStringWithOR(otherConditions, + "(table_name = " GEOCENTRIC " AND " + "type = " GEOCENTRIC ")"); + break; + case ObjectType::GEOGRAPHIC_CRS: + addToListStringWithOR(otherConditions, + "(table_name = 'geodetic_crs' AND " + "type IN (" GEOG_2D "," GEOG_3D "))"); + break; + case ObjectType::GEOGRAPHIC_2D_CRS: + addToListStringWithOR(otherConditions, + "(table_name = 'geodetic_crs' AND " + "type = " GEOG_2D ")"); + break; + case ObjectType::GEOGRAPHIC_3D_CRS: + addToListStringWithOR(otherConditions, + "(table_name = 'geodetic_crs' AND " + "type = " GEOG_3D ")"); + break; + case ObjectType::PROJECTED_CRS: + addToListString(tableNameList, "'projected_crs'"); + break; + case ObjectType::VERTICAL_CRS: + addToListString(tableNameList, "'vertical_crs'"); + break; + case ObjectType::COMPOUND_CRS: + addToListString(tableNameList, "'compound_crs'"); + break; + case ObjectType::COORDINATE_OPERATION: + addToListString(tableNameList, + "'conversion','helmert_transformation'," + "'grid_transformation','other_transformation'," + "'concatenated_operation'"); + break; + case ObjectType::CONVERSION: + addToListString(tableNameList, "'conversion'"); + break; + case ObjectType::TRANSFORMATION: + addToListString(tableNameList, + "'helmert_transformation'," + "'grid_transformation','other_transformation'"); + break; + case ObjectType::CONCATENATED_OPERATION: + addToListString(tableNameList, "'concatenated_operation'"); + break; + } + } + if (!tableNameList.empty()) { + sql += "((table_name IN ("; + sql += tableNameList; + sql += "))"; + if (!otherConditions.empty()) { + sql += " OR "; + sql += otherConditions; + } + sql += ')'; + } else if (!otherConditions.empty()) { + sql += "("; + sql += otherConditions; + sql += ')'; + } + } + sql += " ORDER BY length(name), name"; + if (limitResultCount > 0 && + limitResultCount < + static_cast(std::numeric_limits::max()) && + !approximateMatch) { + sql += " LIMIT "; + sql += toString(static_cast(limitResultCount)); + } + + std::list res; + + // Querying geodetic datum is a super hot path when importing from WKT1 + // so cache results. + if (allowedObjectTypes.size() == 1 && + allowedObjectTypes[0] == ObjectType::GEODETIC_REFERENCE_FRAME && + approximateMatch && d->authority().empty()) { + auto &mapCanonicalizeGRFName = + d->context()->getPrivate()->getMapCanonicalizeGRFName(); + if (mapCanonicalizeGRFName.empty()) { + auto sqlRes = d->run(sql, params); + for (const auto &row : sqlRes) { + const auto &name = row[3]; + const auto canonicalizedName( + metadata::Identifier::canonicalizeName(name)); + mapCanonicalizeGRFName[canonicalizedName].push_back(row); + } + } + auto iter = mapCanonicalizeGRFName.find(canonicalizedSearchedName); + if (iter != mapCanonicalizeGRFName.end()) { + const auto &listOfRow = iter->second; + for (const auto &row : listOfRow) { + const auto &auth_name = row[1]; + const auto &code = row[2]; + auto factory = d->createFactory(auth_name); + res.emplace_back(factory->createGeodeticDatum(code)); + if (limitResultCount > 0 && res.size() == limitResultCount) { + break; + } + } + } else { + for (const auto &pair : mapCanonicalizeGRFName) { + const auto &listOfRow = pair.second; + for (const auto &row : listOfRow) { + const auto &name = row[3]; + if (approximateMatch) { + bool match = + ci_find(name, searchedNameWithoutDeprecated) != + std::string::npos; + if (!match) { + const auto &canonicalizedName(pair.first); + match = ci_find(canonicalizedName, + canonicalizedSearchedName) != + std::string::npos; + } + if (!match) { + continue; + } + } + + const auto &auth_name = row[1]; + const auto &code = row[2]; + auto factory = d->createFactory(auth_name); + res.emplace_back(factory->createGeodeticDatum(code)); + if (limitResultCount > 0 && + res.size() == limitResultCount) { + break; + } + } + if (limitResultCount > 0 && res.size() == limitResultCount) { + break; + } + } + } + } else { + auto sqlRes = d->run(sql, params); + for (const auto &row : sqlRes) { + const auto &name = row[3]; + if (approximateMatch) { + bool match = ci_find(name, searchedNameWithoutDeprecated) != + std::string::npos; + if (!match) { + const auto canonicalizedName( + metadata::Identifier::canonicalizeName(name)); + match = + ci_find(canonicalizedName, canonicalizedSearchedName) != + std::string::npos; + } + if (!match) { + continue; + } + } + const auto &table_name = row[0]; + const auto &auth_name = row[1]; + const auto &code = row[2]; + auto factory = d->createFactory(auth_name); + if (table_name == "prime_meridian") { + res.emplace_back(factory->createPrimeMeridian(code)); + } else if (table_name == "ellipsoid") { + res.emplace_back(factory->createEllipsoid(code)); + } else if (table_name == "geodetic_datum") { + res.emplace_back(factory->createGeodeticDatum(code)); + } else if (table_name == "vertical_datum") { + res.emplace_back(factory->createVerticalDatum(code)); + } else if (table_name == "geodetic_crs") { + res.emplace_back(factory->createGeodeticCRS(code)); + } else if (table_name == "projected_crs") { + res.emplace_back(factory->createProjectedCRS(code)); + } else if (table_name == "vertical_crs") { + res.emplace_back(factory->createVerticalCRS(code)); + } else if (table_name == "compound_crs") { + res.emplace_back(factory->createCompoundCRS(code)); + } else if (table_name == "conversion") { + res.emplace_back(factory->createConversion(code)); + } else if (table_name == "grid_transformation" || + table_name == "helmert_transformation" || + table_name == "other_transformation" || + table_name == "concatenated_operation") { + res.emplace_back( + factory->createCoordinateOperation(code, true)); + } else { + assert(false); + } + if (limitResultCount > 0 && res.size() == limitResultCount) { + break; + } + } + } + + if (res.empty() && !deprecated) { + return createObjectsFromName(searchedName + " (deprecated)", + allowedObjectTypes, approximateMatch, + limitResultCount); + } + + auto sortLambda = [](const common::IdentifiedObjectNNPtr &a, + const common::IdentifiedObjectNNPtr &b) { + const auto &aName = a->nameStr(); + const auto &bName = b->nameStr(); + if (aName.size() < bName.size()) { + return true; + } + if (aName.size() > bName.size()) { + return false; + } + + const auto &aIds = a->identifiers(); + const auto &bIds = b->identifiers(); + if (aIds.size() < bIds.size()) { + return true; + } + if (aIds.size() > bIds.size()) { + return false; + } + for (size_t idx = 0; idx < aIds.size(); idx++) { + const auto &aCodeSpace = *aIds[idx]->codeSpace(); + const auto &bCodeSpace = *bIds[idx]->codeSpace(); + const auto codeSpaceComparison = aCodeSpace.compare(bCodeSpace); + if (codeSpaceComparison < 0) { + return true; + } + if (codeSpaceComparison > 0) { + return false; + } + const auto &aCode = aIds[idx]->code(); + const auto &bCode = bIds[idx]->code(); + const auto codeComparison = aCode.compare(bCode); + if (codeComparison < 0) { + return true; + } + if (codeComparison > 0) { + return false; + } + } + return strcmp(typeid(a.get()).name(), typeid(b.get()).name()) < 0; + }; + + res.sort(sortLambda); + + return res; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a list of area of use from their name + * + * @param name Searched name. + * @param approximateMatch Whether approximate name identification is allowed. + * @return list of (auth_name, code) of matched objects. + * @throw FactoryException + */ +std::list> +AuthorityFactory::listAreaOfUseFromName(const std::string &name, + bool approximateMatch) const { + std::string sql( + "SELECT auth_name, code FROM area WHERE deprecated = 0 AND "); + ListOfParams params; + if (d->hasAuthorityRestriction()) { + sql += " auth_name = ? AND "; + params.emplace_back(d->authority()); + } + sql += "name LIKE ?"; + if (!approximateMatch) { + params.push_back(name); + } else { + params.push_back('%' + name + '%'); + } + auto sqlRes = d->run(sql, params); + std::list> res; + for (const auto &row : sqlRes) { + res.emplace_back(row[0], row[1]); + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::list AuthorityFactory::createEllipsoidFromExisting( + const datum::EllipsoidNNPtr &ellipsoid) const { + std::string sql( + "SELECT auth_name, code FROM ellipsoid WHERE " + "abs(semi_major_axis - ?) < 1e-10 * abs(semi_major_axis) AND " + "((semi_minor_axis IS NOT NULL AND " + "abs(semi_minor_axis - ?) < 1e-10 * abs(semi_minor_axis)) OR " + "((inv_flattening IS NOT NULL AND " + "abs(inv_flattening - ?) < 1e-10 * abs(inv_flattening))))"); + ListOfParams params{ellipsoid->semiMajorAxis().getSIValue(), + ellipsoid->computeSemiMinorAxis().getSIValue(), + ellipsoid->computedInverseFlattening()}; + auto sqlRes = d->run(sql, params); + std::list res; + for (const auto &row : sqlRes) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back(d->createFactory(auth_name)->createEllipsoid(code)); + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::list AuthorityFactory::createGeodeticCRSFromDatum( + const std::string &datum_auth_name, const std::string &datum_code, + const std::string &geodetic_crs_type) const { + std::string sql( + "SELECT auth_name, code FROM geodetic_crs WHERE " + "datum_auth_name = ? AND datum_code = ? AND deprecated = 0"); + ListOfParams params{datum_auth_name, datum_code}; + if (d->hasAuthorityRestriction()) { + sql += " AND auth_name = ?"; + params.emplace_back(d->authority()); + } + if (!geodetic_crs_type.empty()) { + sql += " AND type = ?"; + params.emplace_back(geodetic_crs_type); + } + auto sqlRes = d->run(sql, params); + std::list res; + for (const auto &row : sqlRes) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code)); + } + return res; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::list +AuthorityFactory::createGeodeticCRSFromEllipsoid( + const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code, + const std::string &geodetic_crs_type) const { + std::string sql( + "SELECT geodetic_crs.auth_name, geodetic_crs.code FROM geodetic_crs " + "JOIN geodetic_datum ON " + "geodetic_crs.datum_auth_name = geodetic_datum.auth_name AND " + "geodetic_crs.datum_code = geodetic_datum.code WHERE " + "geodetic_datum.ellipsoid_auth_name = ? AND " + "geodetic_datum.ellipsoid_code = ? AND " + "geodetic_datum.deprecated = 0 AND " + "geodetic_crs.deprecated = 0"); + ListOfParams params{ellipsoid_auth_name, ellipsoid_code}; + if (d->hasAuthorityRestriction()) { + sql += " AND geodetic_crs.auth_name = ?"; + params.emplace_back(d->authority()); + } + if (!geodetic_crs_type.empty()) { + sql += " AND geodetic_crs.type = ?"; + params.emplace_back(geodetic_crs_type); + } + auto sqlRes = d->run(sql, params); + std::list res; + for (const auto &row : sqlRes) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code)); + } + return res; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static std::string buildSqlLookForAuthNameCode( + const std::list> &list, ListOfParams ¶ms, + const char *prefixField) { + std::string sql("("); + + std::set authorities; + for (const auto &crs : list) { + const auto &ids = crs.first->identifiers(); + if (!ids.empty()) { + authorities.insert(*(ids[0]->codeSpace())); + } + } + bool firstAuth = true; + for (const auto &auth_name : authorities) { + if (!firstAuth) { + sql += " OR "; + } + firstAuth = false; + sql += "( "; + sql += prefixField; + sql += "auth_name = ? AND "; + sql += prefixField; + sql += "code IN ("; + params.emplace_back(auth_name); + bool firstGeodCRSForAuth = true; + for (const auto &crs : list) { + const auto &ids = crs.first->identifiers(); + if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) { + if (!firstGeodCRSForAuth) { + sql += ','; + } + firstGeodCRSForAuth = false; + sql += '?'; + params.emplace_back(ids[0]->code()); + } + } + sql += "))"; + } + sql += ')'; + return sql; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::list +AuthorityFactory::createProjectedCRSFromExisting( + const crs::ProjectedCRSNNPtr &crs) const { + std::list res; + + const auto &conv = crs->derivingConversionRef(); + const auto &method = conv->method(); + const auto methodEPSGCode = method->getEPSGCode(); + if (methodEPSGCode == 0) { + return res; + } + + auto lockedThisFactory(d->getSharedFromThis()); + assert(lockedThisFactory); + const auto &baseCRS(crs->baseCRS()); + auto candidatesGeodCRS = baseCRS->crs::CRS::identify(lockedThisFactory); + auto geogCRS = dynamic_cast(baseCRS.get()); + if (geogCRS) { + const auto axisOrder = geogCRS->coordinateSystem()->axisOrder(); + if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || + axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) { + const auto &unit = + geogCRS->coordinateSystem()->axisList()[0]->unit(); + auto otherOrderGeogCRS = crs::GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + geogCRS->nameStr()), + geogCRS->datum(), geogCRS->datumEnsemble(), + axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH + ? cs::EllipsoidalCS::createLatitudeLongitude(unit) + : cs::EllipsoidalCS::createLongitudeLatitude(unit)); + auto otherCandidatesGeodCRS = + otherOrderGeogCRS->crs::CRS::identify(lockedThisFactory); + candidatesGeodCRS.insert(candidatesGeodCRS.end(), + otherCandidatesGeodCRS.begin(), + otherCandidatesGeodCRS.end()); + } + } + + std::string sql( + "SELECT projected_crs.auth_name, projected_crs.code FROM projected_crs " + "JOIN conversion ON " + "projected_crs.conversion_auth_name = conversion.auth_name AND " + "projected_crs.conversion_code = conversion.code WHERE " + "projected_crs.deprecated = 0 AND "); + ListOfParams params; + if (!candidatesGeodCRS.empty()) { + sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params, + "projected_crs.geodetic_crs_"); + sql += " AND "; + } + sql += "conversion.method_auth_name = 'EPSG' AND " + "conversion.method_code = ?"; + params.emplace_back(toString(methodEPSGCode)); + if (d->hasAuthorityRestriction()) { + sql += " AND projected_crs.auth_name = ?"; + params.emplace_back(d->authority()); + } + + int iParam = 1; + for (const auto &genOpParamvalue : conv->parameterValues()) { + auto opParamvalue = + dynamic_cast( + genOpParamvalue.get()); + if (!opParamvalue) { + break; + } + const auto paramEPSGCode = opParamvalue->parameter()->getEPSGCode(); + const auto ¶meterValue = opParamvalue->parameterValue(); + if (!(paramEPSGCode > 0 && + parameterValue->type() == + operation::ParameterValue::Type::MEASURE)) { + break; + } + const auto &measure = parameterValue->value(); + const auto &unit = measure.unit(); + if (unit == common::UnitOfMeasure::DEGREE && + geogCRS->coordinateSystem()->axisList()[0]->unit() == unit) { + const auto iParamAsStr(toString(iParam)); + sql += " AND conversion.param"; + sql += iParamAsStr; + sql += "_code = ? AND conversion.param"; + sql += iParamAsStr; + sql += "_auth_name = 'EPSG' AND conversion.param"; + sql += iParamAsStr; + sql += "_value BETWEEN ? AND ?"; + // As angles might be expressed with the odd unit EPSG:9110 + // "sexagesimal DMS", we have to provide a broad range + params.emplace_back(toString(paramEPSGCode)); + params.emplace_back(measure.value() - 1); + params.emplace_back(measure.value() + 1); + } + iParam++; + } + auto sqlRes = d->run(sql, params); + + params.clear(); + + sql = "SELECT auth_name, code FROM projected_crs WHERE " + "deprecated = 0 AND conversion_auth_name IS NULL AND "; + if (!candidatesGeodCRS.empty()) { + sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params, + "geodetic_crs_"); + sql += " AND "; + } + + const auto escapeLikeStr = [](const std::string &str) { + return replaceAll(replaceAll(replaceAll(str, "\\", "\\\\"), "_", "\\_"), + "%", "\\%"); + }; + + const auto ellpsSemiMajorStr = + toString(baseCRS->ellipsoid()->semiMajorAxis().getSIValue(), 10); + + sql += "(text_definition LIKE ? ESCAPE '\\'"; + + // WKT2 definition + { + std::string patternVal("%"); + + patternVal += ','; + patternVal += ellpsSemiMajorStr; + patternVal += '%'; + + patternVal += escapeLikeStr(method->nameStr()); + patternVal += '%'; + + params.emplace_back(patternVal); + } + + const auto *mapping = getMapping(method.get()); + if (mapping && mapping->proj_name_main) { + sql += " OR (text_definition LIKE ? AND (text_definition LIKE ?"; + + std::string patternVal("%"); + patternVal += "proj="; + patternVal += mapping->proj_name_main; + patternVal += '%'; + params.emplace_back(patternVal); + + // could be a= or R= + patternVal = "%="; + patternVal += ellpsSemiMajorStr; + patternVal += '%'; + params.emplace_back(patternVal); + + std::string projEllpsName; + std::string ellpsName; + if (baseCRS->ellipsoid()->lookForProjWellKnownEllps(projEllpsName, + ellpsName)) { + sql += " OR text_definition LIKE ?"; + // Could be ellps= or datum= + patternVal = "%="; + patternVal += projEllpsName; + patternVal += '%'; + params.emplace_back(patternVal); + } + + sql += "))"; + } + + // WKT1_GDAL definition + const char *wkt1GDALMethodName = conv->getWKT1GDALMethodName(); + if (wkt1GDALMethodName) { + sql += " OR text_definition LIKE ? ESCAPE '\\'"; + std::string patternVal("%"); + + patternVal += ','; + patternVal += ellpsSemiMajorStr; + patternVal += '%'; + + patternVal += escapeLikeStr(wkt1GDALMethodName); + patternVal += '%'; + + params.emplace_back(patternVal); + } + + // WKT1_ESRI definition + const char *esriMethodName = conv->getESRIMethodName(); + if (esriMethodName) { + sql += " OR text_definition LIKE ? ESCAPE '\\'"; + std::string patternVal("%"); + + patternVal += ','; + patternVal += ellpsSemiMajorStr; + patternVal += '%'; + + patternVal += escapeLikeStr(esriMethodName); + patternVal += '%'; + + auto fe = + &conv->parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING); + if (*fe == Measure()) { + fe = &conv->parameterValueMeasure( + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN); + } + if (!(*fe == Measure())) { + patternVal += "PARAMETER[\"False\\_Easting\","; + patternVal += + toString(fe->convertToUnit( + crs->coordinateSystem()->axisList()[0]->unit()), + 10); + patternVal += '%'; + } + + auto lat = &conv->parameterValueMeasure( + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); + if (*lat == Measure()) { + lat = &conv->parameterValueMeasure( + EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN); + } + if (!(*lat == Measure())) { + patternVal += "PARAMETER[\"Latitude\\_Of\\_Origin\","; + const auto &angularUnit = + dynamic_cast(crs->baseCRS().get()) + ? crs->baseCRS()->coordinateSystem()->axisList()[0]->unit() + : UnitOfMeasure::DEGREE; + patternVal += toString(lat->convertToUnit(angularUnit), 10); + patternVal += '%'; + } + + params.emplace_back(patternVal); + } + sql += ")"; + if (d->hasAuthorityRestriction()) { + sql += " AND auth_name = ?"; + params.emplace_back(d->authority()); + } + + auto sqlRes2 = d->run(sql, params); + + if (sqlRes.size() <= 200) { + for (const auto &row : sqlRes) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back( + d->createFactory(auth_name)->createProjectedCRS(code)); + } + } + if (sqlRes2.size() <= 200) { + for (const auto &row : sqlRes2) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back( + d->createFactory(auth_name)->createProjectedCRS(code)); + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +std::list +AuthorityFactory::createCompoundCRSFromExisting( + const crs::CompoundCRSNNPtr &crs) const { + std::list res; + + auto lockedThisFactory(d->getSharedFromThis()); + assert(lockedThisFactory); + + const auto &components = crs->componentReferenceSystems(); + if (components.size() != 2) { + return res; + } + auto candidatesHorizCRS = components[0]->identify(lockedThisFactory); + auto candidatesVertCRS = components[1]->identify(lockedThisFactory); + if (candidatesHorizCRS.empty() && candidatesVertCRS.empty()) { + return res; + } + + std::string sql("SELECT auth_name, code FROM compound_crs WHERE " + "deprecated = 0 AND "); + ListOfParams params; + bool addAnd = false; + if (!candidatesHorizCRS.empty()) { + sql += buildSqlLookForAuthNameCode(candidatesHorizCRS, params, + "horiz_crs_"); + addAnd = true; + } + if (!candidatesVertCRS.empty()) { + if (addAnd) { + sql += " AND "; + } + sql += buildSqlLookForAuthNameCode(candidatesVertCRS, params, + "vertical_crs_"); + addAnd = true; + } + if (d->hasAuthorityRestriction()) { + if (addAnd) { + sql += " AND "; + } + sql += "auth_name = ?"; + params.emplace_back(d->authority()); + } + + auto sqlRes = d->run(sql, params); + for (const auto &row : sqlRes) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back(d->createFactory(auth_name)->createCompoundCRS(code)); + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +FactoryException::FactoryException(const char *message) : Exception(message) {} + +// --------------------------------------------------------------------------- + +FactoryException::FactoryException(const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +FactoryException::~FactoryException() = default; + +// --------------------------------------------------------------------------- + +FactoryException::FactoryException(const FactoryException &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +struct NoSuchAuthorityCodeException::Private { + std::string authority_; + std::string code_; + + Private(const std::string &authority, const std::string &code) + : authority_(authority), code_(code) {} +}; + +// --------------------------------------------------------------------------- + +NoSuchAuthorityCodeException::NoSuchAuthorityCodeException( + const std::string &message, const std::string &authority, + const std::string &code) + : FactoryException(message), + d(internal::make_unique(authority, code)) {} + +// --------------------------------------------------------------------------- + +NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() = default; + +// --------------------------------------------------------------------------- + +NoSuchAuthorityCodeException::NoSuchAuthorityCodeException( + const NoSuchAuthorityCodeException &other) + : FactoryException(other), d(internal::make_unique(*(other.d))) {} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns authority name. */ +const std::string &NoSuchAuthorityCodeException::getAuthority() const { + return d->authority_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns authority code. */ +const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const { + return d->code_; +} + +// --------------------------------------------------------------------------- + +} // namespace io +NS_PROJ_END diff --git a/src/iso19111/internal.cpp b/src/iso19111/internal.cpp new file mode 100644 index 00000000..c43605d1 --- /dev/null +++ b/src/iso19111/internal.cpp @@ -0,0 +1,374 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 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/internal/internal.hpp" + +#include +#include +#ifdef _MSC_VER +#include +#else +#include +#endif +#include +#include // std::setprecision +#include +#include // std::istringstream and std::ostringstream +#include + +#include "sqlite3.h" + +NS_PROJ_START + +namespace internal { + +// --------------------------------------------------------------------------- + +/** + * Replace all occurrences of before with after. + */ +std::string replaceAll(const std::string &str, const std::string &before, + const std::string &after) { + std::string ret(str); + const size_t nBeforeSize = before.size(); + const size_t nAfterSize = after.size(); + if (nBeforeSize) { + size_t nStartPos = 0; + while ((nStartPos = ret.find(before, nStartPos)) != std::string::npos) { + ret.replace(nStartPos, nBeforeSize, after); + nStartPos += nAfterSize; + } + } + return ret; +} + +// --------------------------------------------------------------------------- + +inline static bool EQUALN(const char *a, const char *b, size_t size) { +#ifdef _MSC_VER + return _strnicmp(a, b, size) == 0; +#else + return strncasecmp(a, b, size) == 0; +#endif +} + +/** + * Case-insensitive equality test + */ +bool ci_equal(const std::string &a, const std::string &b) noexcept { + const auto size = a.size(); + if (size != b.size()) { + return false; + } + return EQUALN(a.c_str(), b.c_str(), size); +} + +bool ci_equal(const std::string &a, const char *b) noexcept { + const auto size = a.size(); + if (size != strlen(b)) { + return false; + } + return EQUALN(a.c_str(), b, size); +} + +bool ci_equal(const char *a, const char *b) noexcept { + const auto size = strlen(a); + if (size != strlen(b)) { + return false; + } + return EQUALN(a, b, size); +} + +// --------------------------------------------------------------------------- + +bool ci_less(const std::string &a, const std::string &b) noexcept { +#ifdef _MSC_VER + return _stricmp(a.c_str(), b.c_str()) < 0; +#else + return strcasecmp(a.c_str(), b.c_str()) < 0; +#endif +} + +// --------------------------------------------------------------------------- + +/** + * Convert to lower case. + */ + +std::string tolower(const std::string &str) + +{ + std::string ret(str); + for (size_t i = 0; i < ret.size(); i++) + ret[i] = static_cast(::tolower(ret[i])); + return ret; +} + +// --------------------------------------------------------------------------- + +/** + * Convert to upper case. + */ + +std::string toupper(const std::string &str) + +{ + std::string ret(str); + for (size_t i = 0; i < ret.size(); i++) + ret[i] = static_cast(::toupper(ret[i])); + return ret; +} + +// --------------------------------------------------------------------------- + +/** Strip leading and trailing double quote characters */ +std::string stripQuotes(const std::string &str) { + if (str.size() >= 2 && str[0] == '"' && str.back() == '"') { + return str.substr(1, str.size() - 2); + } + return str; +} + +// --------------------------------------------------------------------------- + +size_t ci_find(const std::string &str, const char *needle) noexcept { + const size_t needleSize = strlen(needle); + for (size_t i = 0; i + needleSize <= str.size(); i++) { + if (EQUALN(str.c_str() + i, needle, needleSize)) { + return i; + } + } + return std::string::npos; +} + +// --------------------------------------------------------------------------- + +size_t ci_find(const std::string &str, const std::string &needle, + size_t startPos) noexcept { + const size_t needleSize = needle.size(); + for (size_t i = startPos; i + needleSize <= str.size(); i++) { + if (EQUALN(str.c_str() + i, needle.c_str(), needleSize)) { + return i; + } + } + return std::string::npos; +} + +// --------------------------------------------------------------------------- + +/* +bool starts_with(const std::string &str, const std::string &prefix) noexcept { + if (str.size() < prefix.size()) { + return false; + } + return std::memcmp(str.c_str(), prefix.c_str(), prefix.size()) == 0; +} +*/ + +// --------------------------------------------------------------------------- + +/* +bool starts_with(const std::string &str, const char *prefix) noexcept { + const size_t prefixSize = std::strlen(prefix); + if (str.size() < prefixSize) { + return false; + } + return std::memcmp(str.c_str(), prefix, prefixSize) == 0; +} +*/ + +// --------------------------------------------------------------------------- + +bool ci_starts_with(const char *str, const char *prefix) noexcept { + const auto str_size = strlen(str); + const auto prefix_size = strlen(prefix); + if (str_size < prefix_size) { + return false; + } + return EQUALN(str, prefix, prefix_size); +} + +// --------------------------------------------------------------------------- + +bool ci_starts_with(const std::string &str, + const std::string &prefix) noexcept { + if (str.size() < prefix.size()) { + return false; + } + return EQUALN(str.c_str(), prefix.c_str(), prefix.size()); +} + +// --------------------------------------------------------------------------- + +bool ends_with(const std::string &str, const std::string &suffix) noexcept { + if (str.size() < suffix.size()) { + return false; + } + return std::memcmp(str.c_str() + str.size() - suffix.size(), suffix.c_str(), + suffix.size()) == 0; +} + +// --------------------------------------------------------------------------- + +double c_locale_stod(const std::string &s) { + + const auto s_size = s.size(); + // Fast path + if (s_size > 0 && s_size < 15) { + std::int64_t acc = 0; + std::int64_t div = 1; + bool afterDot = false; + size_t i = 0; + if (s[0] == '-') { + ++i; + div = -1; + } else if (s[0] == '+') { + ++i; + } + for (; i < s_size; ++i) { + const auto ch = s[i]; + if (ch >= '0' && ch <= '9') { + acc = acc * 10 + ch - '0'; + if (afterDot) { + div *= 10; + } + } else if (ch == '.') { + afterDot = true; + } else { + div = 0; + } + } + if (div) { + return static_cast(acc) / div; + } + } + + std::istringstream iss(s); + iss.imbue(std::locale::classic()); + double d; + iss >> d; + if (!iss.eof() || iss.fail()) { + throw std::invalid_argument("non double value"); + } + return d; +} + +// --------------------------------------------------------------------------- + +std::vector split(const std::string &str, char separator) { + std::vector res; + size_t lastPos = 0; + size_t newPos = 0; + while ((newPos = str.find(separator, lastPos)) != std::string::npos) { + res.push_back(str.substr(lastPos, newPos - lastPos)); + lastPos = newPos + 1; + } + res.push_back(str.substr(lastPos)); + return res; +} + +// --------------------------------------------------------------------------- + +#ifdef _WIN32 + +// For some reason, sqlite3_snprintf() in the sqlite3 builds used on AppVeyor +// doesn't round identically to the Unix builds, and thus breaks a number of +// unit test. So to avoid this, use the stdlib formatting + +std::string toString(int val) { + std::ostringstream buffer; + buffer.imbue(std::locale::classic()); + buffer << val; + return buffer.str(); +} + +std::string toString(double val, int precision) { + std::ostringstream buffer; + buffer.imbue(std::locale::classic()); + buffer << std::setprecision(precision); + buffer << val; + auto str = buffer.str(); + if (precision == 15 && str.find("9999999999") != std::string::npos) { + buffer.str(""); + buffer.clear(); + buffer << std::setprecision(14); + buffer << val; + return buffer.str(); + } + return str; +} + +#else + +std::string toString(int val) { + // use sqlite3 API that is slighly faster than std::ostringstream + // with forcing the C locale. sqlite3_snprintf() emulates a C locale. + constexpr int BUF_SIZE = 16; + char szBuffer[BUF_SIZE]; + sqlite3_snprintf(BUF_SIZE, szBuffer, "%d", val); + return szBuffer; +} + +std::string toString(double val, int precision) { + // use sqlite3 API that is slighly faster than std::ostringstream + // with forcing the C locale. sqlite3_snprintf() emulates a C locale. + constexpr int BUF_SIZE = 32; + char szBuffer[BUF_SIZE]; + sqlite3_snprintf(BUF_SIZE, szBuffer, "%.*g", precision, val); + if (precision == 15 && strstr(szBuffer, "9999999999")) { + sqlite3_snprintf(BUF_SIZE, szBuffer, "%.14g", val); + } + return szBuffer; +} + +#endif + +// --------------------------------------------------------------------------- + +std::string concat(const char *a, const std::string &b) { + std::string res(a); + res += b; + return res; +} + +std::string concat(const char *a, const std::string &b, const char *c) { + std::string res(a); + res += b; + res += c; + return res; +} + +// --------------------------------------------------------------------------- + +} // namespace internal + +NS_PROJ_END diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp new file mode 100644 index 00000000..fe3680fb --- /dev/null +++ b/src/iso19111/io.cpp @@ -0,0 +1,7501 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 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 +#include +#include +#include +#include +#include +#include +#include +#include // std::istringstream +#include +#include +#include + +#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/coordinateoperation_internal.hpp" +#include "proj/internal/coordinatesystem_internal.hpp" +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include "proj_constants.h" + +#include "pj_wkt1_parser.h" +#include "pj_wkt2_parser.h" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "projects.h" +#include "proj_api.h" +// clang-format on + +using namespace NS_PROJ::common; +using namespace NS_PROJ::crs; +using namespace NS_PROJ::cs; +using namespace NS_PROJ::datum; +using namespace NS_PROJ::internal; +using namespace NS_PROJ::metadata; +using namespace NS_PROJ::operation; +using namespace NS_PROJ::util; + +//! @cond Doxygen_Suppress +static const std::string emptyString{}; +//! @endcond + +#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; +}} +#endif + +NS_PROJ_START +namespace io { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +IWKTExportable::~IWKTExportable() = default; + +// --------------------------------------------------------------------------- + +std::string IWKTExportable::exportToWKT(WKTFormatter *formatter) const { + _exportToWKT(formatter); + return formatter->toString(); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct WKTFormatter::Private { + struct Params { + WKTFormatter::Convention convention_ = WKTFormatter::Convention::WKT2; + WKTFormatter::Version version_ = WKTFormatter::Version::WKT2; + bool multiLine_ = true; + bool strict_ = true; + int indentWidth_ = 4; + bool idOnTopLevelOnly_ = false; + bool outputAxisOrder_ = false; + bool primeMeridianOmittedIfGreenwich_ = false; + bool ellipsoidUnitOmittedIfMetre_ = false; + bool primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = false; + bool forceUNITKeyword_ = false; + bool outputCSUnitOnlyOnceIfSame_ = false; + bool primeMeridianInDegree_ = false; + bool use2018Keywords_ = false; + bool useESRIDialect_ = false; + OutputAxisRule outputAxis_ = WKTFormatter::OutputAxisRule::YES; + }; + Params params_{}; + DatabaseContextPtr dbContext_{}; + + int indentLevel_ = 0; + int level_ = 0; + std::vector stackHasChild_{}; + std::vector stackHasId_{false}; + std::vector stackEmptyKeyword_{}; + std::vector outputUnitStack_{true}; + std::vector outputIdStack_{true}; + std::vector axisLinearUnitStack_{ + util::nn_make_shared(UnitOfMeasure::METRE)}; + std::vector axisAngularUnitStack_{ + util::nn_make_shared(UnitOfMeasure::DEGREE)}; + bool abridgedTransformation_ = false; + bool useDerivingConversion_ = false; + std::vector toWGS84Parameters_{}; + std::string hDatumExtension_{}; + std::string vDatumExtension_{}; + std::vector inversionStack_{false}; + std::string result_{}; + + // cppcheck-suppress functionStatic + void addNewLine(); + void addIndentation(); + // cppcheck-suppress functionStatic + void startNewChild(); +}; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Constructs a new formatter. + * + * A formatter can be used only once (its internal state is mutated) + * + * Its default behaviour can be adjusted with the different setters. + * + * @param convention WKT flavor. Defaults to Convention::WKT2 + * @param dbContext Database context, to allow queries in it if needed. + * This is used for example for WKT1_ESRI output to do name substitutions. + * + * @return new formatter. + */ +WKTFormatterNNPtr WKTFormatter::create(Convention convention, + // cppcheck-suppress passedByValue + DatabaseContextPtr dbContext) { + auto ret = NN_NO_CHECK(WKTFormatter::make_unique(convention)); + ret->d->dbContext_ = dbContext; + return ret; +} + +// --------------------------------------------------------------------------- + +/** \brief Constructs a new formatter from another one. + * + * A formatter can be used only once (its internal state is mutated) + * + * Its default behaviour can be adjusted with the different setters. + * + * @param other source formatter. + * @return new formatter. + */ +WKTFormatterNNPtr WKTFormatter::create(const WKTFormatterNNPtr &other) { + auto f = create(other->d->params_.convention_, other->d->dbContext_); + f->d->params_ = other->d->params_; + return f; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +WKTFormatter::~WKTFormatter() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Whether to use multi line output or not. */ +WKTFormatter &WKTFormatter::setMultiLine(bool multiLine) noexcept { + d->params_.multiLine_ = multiLine; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set number of spaces for each indentation level (defaults to 4). + */ +WKTFormatter &WKTFormatter::setIndentationWidth(int width) noexcept { + d->params_.indentWidth_ = width; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether AXIS nodes should be output. + */ +WKTFormatter & +WKTFormatter::setOutputAxis(OutputAxisRule outputAxisIn) noexcept { + d->params_.outputAxis_ = outputAxisIn; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether the formatter should operate on strict more or not. + * + * The default is strit mode, in which case a FormattingException can be thrown. + */ +WKTFormatter &WKTFormatter::setStrict(bool strictIn) noexcept { + d->params_.strict_ = strictIn; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns whether the formatter is in strict mode. */ +bool WKTFormatter::isStrict() const noexcept { return d->params_.strict_; } + +// --------------------------------------------------------------------------- + +/** Returns the WKT string from the formatter. */ +const std::string &WKTFormatter::toString() const { + if (d->indentLevel_ > 0 || d->level_ > 0) { + // For intermediary nodes, the formatter is in a inconsistent + // state. + throw FormattingException("toString() called on intermediate nodes"); + } + if (d->axisLinearUnitStack_.size() != 1) + throw FormattingException( + "Unbalanced pushAxisLinearUnit() / popAxisLinearUnit()"); + if (d->axisAngularUnitStack_.size() != 1) + throw FormattingException( + "Unbalanced pushAxisAngularUnit() / popAxisAngularUnit()"); + if (d->outputIdStack_.size() != 1) + throw FormattingException("Unbalanced pushOutputId() / popOutputId()"); + if (d->outputUnitStack_.size() != 1) + throw FormattingException( + "Unbalanced pushOutputUnit() / popOutputUnit()"); + + return d->result_; +} + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +WKTFormatter::WKTFormatter(Convention convention) + : d(internal::make_unique()) { + d->params_.convention_ = convention; + switch (convention) { + case Convention::WKT2_2018: + d->params_.use2018Keywords_ = true; + PROJ_FALLTHROUGH + case Convention::WKT2: + d->params_.version_ = WKTFormatter::Version::WKT2; + d->params_.outputAxisOrder_ = true; + break; + + case Convention::WKT2_2018_SIMPLIFIED: + d->params_.use2018Keywords_ = true; + PROJ_FALLTHROUGH + case Convention::WKT2_SIMPLIFIED: + d->params_.version_ = WKTFormatter::Version::WKT2; + d->params_.idOnTopLevelOnly_ = true; + d->params_.outputAxisOrder_ = false; + d->params_.primeMeridianOmittedIfGreenwich_ = true; + d->params_.ellipsoidUnitOmittedIfMetre_ = true; + d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = true; + d->params_.forceUNITKeyword_ = true; + d->params_.outputCSUnitOnlyOnceIfSame_ = true; + break; + + case Convention::WKT1_GDAL: + d->params_.version_ = WKTFormatter::Version::WKT1; + d->params_.outputAxisOrder_ = false; + d->params_.forceUNITKeyword_ = true; + d->params_.primeMeridianInDegree_ = true; + d->params_.outputAxis_ = + WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE; + break; + + case Convention::WKT1_ESRI: + d->params_.version_ = WKTFormatter::Version::WKT1; + d->params_.outputAxisOrder_ = false; + d->params_.forceUNITKeyword_ = true; + d->params_.primeMeridianInDegree_ = true; + d->params_.useESRIDialect_ = true; + d->params_.multiLine_ = false; + d->params_.outputAxis_ = WKTFormatter::OutputAxisRule::NO; + break; + + default: + assert(false); + break; + } +} + +// --------------------------------------------------------------------------- + +WKTFormatter &WKTFormatter::setOutputId(bool outputIdIn) { + if (d->indentLevel_ != 0) { + throw Exception( + "setOutputId() shall only be called when the stack state is empty"); + } + d->outputIdStack_[0] = outputIdIn; + return *this; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::Private::addNewLine() { result_ += '\n'; } + +// --------------------------------------------------------------------------- + +void WKTFormatter::Private::addIndentation() { + result_ += std::string(indentLevel_ * params_.indentWidth_, ' '); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::enter() { + if (d->indentLevel_ == 0 && d->level_ == 0) { + d->stackHasChild_.push_back(false); + } + ++d->level_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::leave() { + assert(d->level_ > 0); + --d->level_; + if (d->indentLevel_ == 0 && d->level_ == 0) { + d->stackHasChild_.pop_back(); + } +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::startNode(const std::string &keyword, bool hasId) { + if (!d->stackHasChild_.empty()) { + d->startNewChild(); + } else if (!d->result_.empty()) { + d->result_ += ','; + if (d->params_.multiLine_ && !keyword.empty()) { + d->addNewLine(); + } + } + + if (d->params_.multiLine_) { + if ((d->indentLevel_ || d->level_) && !keyword.empty()) { + if (!d->result_.empty()) { + d->addNewLine(); + } + d->addIndentation(); + } + } + + if (!keyword.empty()) { + d->result_ += keyword; + d->result_ += '['; + } + d->indentLevel_++; + d->stackHasChild_.push_back(false); + d->stackEmptyKeyword_.push_back(keyword.empty()); + + // Starting from a node that has a ID, we should emit ID nodes for : + // - this node + // - and for METHOD&PARAMETER nodes in WKT2, unless idOnTopLevelOnly_ is + // set. + // For WKT2, all other intermediate nodes shouldn't have ID ("not + // recommended") + if (!d->params_.idOnTopLevelOnly_ && d->indentLevel_ >= 2 && + d->params_.version_ == WKTFormatter::Version::WKT2 && + (keyword == WKTConstants::METHOD || + keyword == WKTConstants::PARAMETER)) { + pushOutputId(d->outputIdStack_[0]); + } else if (d->indentLevel_ >= 2 && + d->params_.version_ == WKTFormatter::Version::WKT2) { + pushOutputId(d->outputIdStack_[0] && !d->stackHasId_.back()); + } else { + pushOutputId(outputId()); + } + + d->stackHasId_.push_back(hasId || d->stackHasId_.back()); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::endNode() { + assert(d->indentLevel_ > 0); + d->stackHasId_.pop_back(); + popOutputId(); + d->indentLevel_--; + bool emptyKeyword = d->stackEmptyKeyword_.back(); + d->stackEmptyKeyword_.pop_back(); + d->stackHasChild_.pop_back(); + if (!emptyKeyword) + d->result_ += ']'; +} + +// --------------------------------------------------------------------------- + +WKTFormatter &WKTFormatter::simulCurNodeHasId() { + d->stackHasId_.back() = true; + return *this; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::Private::startNewChild() { + assert(!stackHasChild_.empty()); + if (stackHasChild_.back()) { + result_ += ','; + } + stackHasChild_.back() = true; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::addQuotedString(const char *str) { + addQuotedString(std::string(str)); +} + +void WKTFormatter::addQuotedString(const std::string &str) { + d->startNewChild(); + d->result_ += '"'; + d->result_ += replaceAll(str, "\"", "\"\""); + d->result_ += '"'; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::add(const std::string &str) { + d->startNewChild(); + d->result_ += str; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::add(int number) { + d->startNewChild(); + d->result_ += internal::toString(number); +} + +// --------------------------------------------------------------------------- + +#ifdef __MINGW32__ +static std::string normalizeSerializedString(const std::string &in) { + // mingw will output 1e-0xy instead of 1e-xy. Fix that + auto pos = in.find("e-0"); + if (pos == std::string::npos) { + return in; + } + if (pos + 4 < in.size() && isdigit(in[pos + 3]) && isdigit(in[pos + 4])) { + return in.substr(0, pos + 2) + in.substr(pos + 3); + } + return in; +} +#else +static inline std::string normalizeSerializedString(const std::string &in) { + return in; +} +#endif + +// --------------------------------------------------------------------------- + +void WKTFormatter::add(double number, int precision) { + d->startNewChild(); + if (number == 0.0) { + if (d->params_.useESRIDialect_) { + d->result_ += "0.0"; + } else { + d->result_ += '0'; + } + } else { + std::string val( + normalizeSerializedString(internal::toString(number, precision))); + d->result_ += val; + if (d->params_.useESRIDialect_ && val.find('.') == std::string::npos) { + d->result_ += ".0"; + } + } +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::pushOutputUnit(bool outputUnitIn) { + d->outputUnitStack_.push_back(outputUnitIn); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::popOutputUnit() { d->outputUnitStack_.pop_back(); } + +// --------------------------------------------------------------------------- + +bool WKTFormatter::outputUnit() const { return d->outputUnitStack_.back(); } + +// --------------------------------------------------------------------------- + +void WKTFormatter::pushOutputId(bool outputIdIn) { + d->outputIdStack_.push_back(outputIdIn); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::popOutputId() { d->outputIdStack_.pop_back(); } + +// --------------------------------------------------------------------------- + +bool WKTFormatter::outputId() const { + return !d->params_.useESRIDialect_ && d->outputIdStack_.back(); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::pushAxisLinearUnit(const UnitOfMeasureNNPtr &unit) { + d->axisLinearUnitStack_.push_back(unit); +} +// --------------------------------------------------------------------------- + +void WKTFormatter::popAxisLinearUnit() { d->axisLinearUnitStack_.pop_back(); } + +// --------------------------------------------------------------------------- + +const UnitOfMeasureNNPtr &WKTFormatter::axisLinearUnit() const { + return d->axisLinearUnitStack_.back(); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::pushAxisAngularUnit(const UnitOfMeasureNNPtr &unit) { + d->axisAngularUnitStack_.push_back(unit); +} +// --------------------------------------------------------------------------- + +void WKTFormatter::popAxisAngularUnit() { d->axisAngularUnitStack_.pop_back(); } + +// --------------------------------------------------------------------------- + +const UnitOfMeasureNNPtr &WKTFormatter::axisAngularUnit() const { + return d->axisAngularUnitStack_.back(); +} + +// --------------------------------------------------------------------------- + +WKTFormatter::OutputAxisRule WKTFormatter::outputAxis() const { + return d->params_.outputAxis_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::outputAxisOrder() const { + return d->params_.outputAxisOrder_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::primeMeridianOmittedIfGreenwich() const { + return d->params_.primeMeridianOmittedIfGreenwich_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::ellipsoidUnitOmittedIfMetre() const { + return d->params_.ellipsoidUnitOmittedIfMetre_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::primeMeridianOrParameterUnitOmittedIfSameAsAxis() const { + return d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::outputCSUnitOnlyOnceIfSame() const { + return d->params_.outputCSUnitOnlyOnceIfSame_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::forceUNITKeyword() const { + return d->params_.forceUNITKeyword_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::primeMeridianInDegree() const { + return d->params_.primeMeridianInDegree_; +} + +// --------------------------------------------------------------------------- + +WKTFormatter::Version WKTFormatter::version() const { + return d->params_.version_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::use2018Keywords() const { + return d->params_.use2018Keywords_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::useESRIDialect() const { return d->params_.useESRIDialect_; } + +// --------------------------------------------------------------------------- + +const DatabaseContextPtr &WKTFormatter::databaseContext() const { + return d->dbContext_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::setAbridgedTransformation(bool outputIn) { + d->abridgedTransformation_ = outputIn; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::abridgedTransformation() const { + return d->abridgedTransformation_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::setUseDerivingConversion(bool useDerivingConversionIn) { + d->useDerivingConversion_ = useDerivingConversionIn; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::useDerivingConversion() const { + return d->useDerivingConversion_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::setTOWGS84Parameters(const std::vector ¶ms) { + d->toWGS84Parameters_ = params; +} + +// --------------------------------------------------------------------------- + +const std::vector &WKTFormatter::getTOWGS84Parameters() const { + return d->toWGS84Parameters_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::setVDatumExtension(const std::string &filename) { + d->vDatumExtension_ = filename; +} + +// --------------------------------------------------------------------------- + +const std::string &WKTFormatter::getVDatumExtension() const { + return d->vDatumExtension_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::setHDatumExtension(const std::string &filename) { + d->hDatumExtension_ = filename; +} + +// --------------------------------------------------------------------------- + +const std::string &WKTFormatter::getHDatumExtension() const { + return d->hDatumExtension_; +} + +// --------------------------------------------------------------------------- + +std::string WKTFormatter::morphNameToESRI(const std::string &name) { + std::string ret; + bool insertUnderscore = false; + // Replace any special character by underscore, except at the beginning + // and of the name where those characters are removed. + for (char ch : name) { + if (ch == '+' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z')) { + if (insertUnderscore && !ret.empty()) { + ret += '_'; + } + ret += ch; + insertUnderscore = false; + } else { + insertUnderscore = true; + } + } + return ret; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::ingestWKTNode(const WKTNodeNNPtr &node) { + startNode(node->value(), true); + for (const auto &child : node->children()) { + if (!child->children().empty()) { + ingestWKTNode(child); + } else { + add(child->value()); + } + } + endNode(); +} + +#ifdef unused +// --------------------------------------------------------------------------- + +void WKTFormatter::startInversion() { + d->inversionStack_.push_back(!d->inversionStack_.back()); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::stopInversion() { + assert(!d->inversionStack_.empty()); + d->inversionStack_.pop_back(); +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::isInverted() const { return d->inversionStack_.back(); } +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static WKTNodeNNPtr + null_node(NN_NO_CHECK(internal::make_unique(std::string()))); + +static inline bool isNull(const WKTNodeNNPtr &node) { + return &node == &null_node; +} + +struct WKTNode::Private { + std::string value_{}; + std::vector children_{}; + + explicit Private(const std::string &valueIn) : value_(valueIn) {} + + // cppcheck-suppress functionStatic + inline const std::string &value() PROJ_CONST_DEFN { return value_; } + + // cppcheck-suppress functionStatic + inline const std::vector &children() PROJ_CONST_DEFN { + return children_; + } + + // cppcheck-suppress functionStatic + inline size_t childrenSize() PROJ_CONST_DEFN { return children_.size(); } + + // cppcheck-suppress functionStatic + const WKTNodeNNPtr &lookForChild(const std::string &childName, + int occurrence) const noexcept; + + // cppcheck-suppress functionStatic + const WKTNodeNNPtr &lookForChild(const std::string &name) const noexcept; + + // cppcheck-suppress functionStatic + const WKTNodeNNPtr &lookForChild(const std::string &name, + const std::string &name2) const noexcept; + + // cppcheck-suppress functionStatic + const WKTNodeNNPtr &lookForChild(const std::string &name, + const std::string &name2, + const std::string &name3) const noexcept; + + // cppcheck-suppress functionStatic + const WKTNodeNNPtr &lookForChild(const std::string &name, + const std::string &name2, + const std::string &name3, + const std::string &name4) const noexcept; +}; + +#define GP() getPrivate() + +// --------------------------------------------------------------------------- + +const WKTNodeNNPtr &WKTNode::Private::lookForChild(const std::string &childName, + int occurrence) const + noexcept { + int occCount = 0; + for (const auto &child : children_) { + if (ci_equal(child->GP()->value(), childName)) { + if (occurrence == occCount) { + return child; + } + occCount++; + } + } + return null_node; +} + +const WKTNodeNNPtr & +WKTNode::Private::lookForChild(const std::string &name) const noexcept { + for (const auto &child : children_) { + const auto &v = child->GP()->value(); + if (ci_equal(v, name)) { + return child; + } + } + return null_node; +} + +const WKTNodeNNPtr & +WKTNode::Private::lookForChild(const std::string &name, + const std::string &name2) const noexcept { + for (const auto &child : children_) { + const auto &v = child->GP()->value(); + if (ci_equal(v, name) || ci_equal(v, name2)) { + return child; + } + } + return null_node; +} + +const WKTNodeNNPtr & +WKTNode::Private::lookForChild(const std::string &name, + const std::string &name2, + const std::string &name3) const noexcept { + for (const auto &child : children_) { + const auto &v = child->GP()->value(); + if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3)) { + return child; + } + } + return null_node; +} + +const WKTNodeNNPtr &WKTNode::Private::lookForChild( + const std::string &name, const std::string &name2, const std::string &name3, + const std::string &name4) const noexcept { + for (const auto &child : children_) { + const auto &v = child->GP()->value(); + if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3) || + ci_equal(v, name4)) { + return child; + } + } + return null_node; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a WKTNode. + * + * @param valueIn the name of the node. + */ +WKTNode::WKTNode(const std::string &valueIn) + : d(internal::make_unique(valueIn)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +WKTNode::~WKTNode() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Adds a child to the current node. + * + * @param child child to add. This should not be a parent of this node. + */ +void WKTNode::addChild(WKTNodeNNPtr &&child) { + d->children_.push_back(std::move(child)); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the (occurrence-1)th sub-node of name childName. + * + * @param childName name of the child. + * @param occurrence occurrence index (starting at 0) + * @return the child, or nullptr. + */ +const WKTNodePtr &WKTNode::lookForChild(const std::string &childName, + int occurrence) const noexcept { + int occCount = 0; + for (const auto &child : d->children_) { + if (ci_equal(child->GP()->value(), childName)) { + if (occurrence == occCount) { + return child; + } + occCount++; + } + } + return null_node; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the count of children of given name. + * + * @param childName name of the children to look for. + * @return count + */ +int WKTNode::countChildrenOfName(const std::string &childName) const noexcept { + int occCount = 0; + for (const auto &child : d->children_) { + if (ci_equal(child->GP()->value(), childName)) { + occCount++; + } + } + return occCount; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the value of a node. + */ +const std::string &WKTNode::value() const { return d->value_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the children of a node. + */ +const std::vector &WKTNode::children() const { + return d->children_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static size_t skipSpace(const std::string &str, size_t start) { + size_t i = start; + while (i < str.size() && ::isspace(str[i])) { + ++i; + } + return i; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +// As used in examples of OGC 12-063r5 +static const std::string startPrintedQuote("\xE2\x80\x9C"); +static const std::string endPrintedQuote("\xE2\x80\x9D"); +//! @endcond + +WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart, + int recLevel, size_t &indexEnd) { + if (recLevel == 16) { + throw ParsingException("too many nesting levels"); + } + std::string value; + size_t i = skipSpace(wkt, indexStart); + if (i == wkt.size()) { + throw ParsingException("whitespace only string"); + } + std::string closingStringMarker; + bool inString = false; + + for (; i < wkt.size() && + (inString || (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' && + wkt[i] != ']' && wkt[i] != ')' && !::isspace(wkt[i]))); + ++i) { + if (wkt[i] == '"') { + if (!inString) { + inString = true; + closingStringMarker = "\""; + } else if (closingStringMarker == "\"") { + if (i + 1 < wkt.size() && wkt[i + 1] == '"') { + i++; + } else { + inString = false; + closingStringMarker.clear(); + } + } + } else if (i + 3 <= wkt.size() && + wkt.substr(i, 3) == startPrintedQuote) { + if (!inString) { + inString = true; + closingStringMarker = endPrintedQuote; + value += '"'; + i += 2; + continue; + } + } else if (i + 3 <= wkt.size() && + closingStringMarker == endPrintedQuote && + wkt.substr(i, 3) == endPrintedQuote) { + inString = false; + closingStringMarker.clear(); + value += '"'; + i += 2; + continue; + } + value += wkt[i]; + } + i = skipSpace(wkt, i); + if (i == wkt.size()) { + if (indexStart == 0) { + throw ParsingException("missing ["); + } else { + throw ParsingException("missing , or ]"); + } + } + + auto node = NN_NO_CHECK(internal::make_unique(value)); + + if (indexStart > 0) { + if (wkt[i] == ',') { + indexEnd = i + 1; + return node; + } + if (wkt[i] == ']' || wkt[i] == ')') { + indexEnd = i; + return node; + } + } + if (wkt[i] != '[' && wkt[i] != '(') { + throw ParsingException("missing ["); + } + ++i; // skip [ + i = skipSpace(wkt, i); + while (i < wkt.size() && wkt[i] != ']' && wkt[i] != ')') { + size_t indexEndChild; + node->addChild(createFrom(wkt, i, recLevel + 1, indexEndChild)); + assert(indexEndChild > i); + i = indexEndChild; + i = skipSpace(wkt, i); + if (i < wkt.size() && wkt[i] == ',') { + ++i; + i = skipSpace(wkt, i); + } + } + if (i == wkt.size() || (wkt[i] != ']' && wkt[i] != ')')) { + throw ParsingException("missing ]"); + } + indexEnd = i + 1; + return node; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a WKTNode hierarchy from a WKT string. + * + * @param wkt the WKT string to parse. + * @param indexStart the start index in the wkt string. + * @throw ParsingException + */ +WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart) { + size_t indexEnd; + return createFrom(wkt, indexStart, 0, indexEnd); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static std::string escapeIfQuotedString(const std::string &str) { + if (str.size() > 2 && str[0] == '"' && str.back() == '"') { + std::string res("\""); + res += replaceAll(str.substr(1, str.size() - 2), "\"", "\"\""); + res += '"'; + return res; + } else { + return str; + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return a WKT representation of the tree structure. + */ +std::string WKTNode::toString() const { + std::string str(escapeIfQuotedString(d->value_)); + if (!d->children_.empty()) { + str += "["; + bool first = true; + for (auto &child : d->children_) { + if (!first) { + str += ','; + } + first = false; + str += child->toString(); + } + str += "]"; + } + return str; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct WKTParser::Private { + bool strict_ = true; + std::list warningList_{}; + std::vector toWGS84Parameters_{}; + std::string datumPROJ4Grids_{}; + bool esriStyle_ = false; + DatabaseContextPtr dbContext_{}; + + static constexpr int MAX_PROPERTY_SIZE = 1024; + PropertyMap **properties_{}; + int propertyCount_ = 0; + + Private() { properties_ = new PropertyMap *[MAX_PROPERTY_SIZE]; } + + ~Private() { + for (int i = 0; i < propertyCount_; i++) { + delete properties_[i]; + } + delete[] properties_; + } + Private(const Private &) = delete; + Private &operator=(const Private &) = delete; + + void emitRecoverableAssertion(const std::string &errorMsg); + + BaseObjectNNPtr build(const WKTNodeNNPtr &node); + + IdentifierPtr buildId(const WKTNodeNNPtr &node, bool tolerant = true); + + PropertyMap &buildProperties(const WKTNodeNNPtr &node); + + ObjectDomainPtr buildObjectDomain(const WKTNodeNNPtr &node); + + static std::string stripQuotes(const WKTNodeNNPtr &node); + + static double asDouble(const WKTNodeNNPtr &node); + + UnitOfMeasure + buildUnit(const WKTNodeNNPtr &node, + UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN); + + UnitOfMeasure buildUnitInSubNode( + const WKTNodeNNPtr &node, + common::UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN); + + EllipsoidNNPtr buildEllipsoid(const WKTNodeNNPtr &node); + + PrimeMeridianNNPtr + buildPrimeMeridian(const WKTNodeNNPtr &node, + const UnitOfMeasure &defaultAngularUnit); + + optional getAnchor(const WKTNodeNNPtr &node); + + static void parseDynamic(const WKTNodeNNPtr &dynamicNode, + double &frameReferenceEpoch, + util::optional &modelName); + + GeodeticReferenceFrameNNPtr + buildGeodeticReferenceFrame(const WKTNodeNNPtr &node, + const PrimeMeridianNNPtr &primeMeridian, + const WKTNodeNNPtr &dynamicNode); + + DatumEnsembleNNPtr buildDatumEnsemble(const WKTNodeNNPtr &node, + const PrimeMeridianPtr &primeMeridian, + bool expectEllipsoid); + + MeridianNNPtr buildMeridian(const WKTNodeNNPtr &node); + CoordinateSystemAxisNNPtr buildAxis(const WKTNodeNNPtr &node, + const UnitOfMeasure &unitIn, + const UnitOfMeasure::Type &unitType, + bool isGeocentric, + int expectedOrderNum); + + CoordinateSystemNNPtr buildCS(const WKTNodeNNPtr &node, /* maybe null */ + const WKTNodeNNPtr &parentNode, + const UnitOfMeasure &defaultAngularUnit); + + GeodeticCRSNNPtr buildGeodeticCRS(const WKTNodeNNPtr &node); + + CRSNNPtr buildDerivedGeodeticCRS(const WKTNodeNNPtr &node); + + static UnitOfMeasure + guessUnitForParameter(const std::string ¶mName, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged, + std::vector ¶meters, + std::vector &values, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + static void addExtensionProj4ToProp(const WKTNode::Private *nodeP, + PropertyMap &props); + + ConversionNNPtr buildConversion(const WKTNodeNNPtr &node, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + static bool hasWebMercPROJ4String(const WKTNodeNNPtr &projCRSNode, + const WKTNodeNNPtr &projectionNode); + + static std::string projectionGetParameter(const WKTNodeNNPtr &projCRSNode, + const char *paramName); + + ConversionNNPtr buildProjection(const WKTNodeNNPtr &projCRSNode, + const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + ConversionNNPtr + buildProjectionStandard(const WKTNodeNNPtr &projCRSNode, + const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + ConversionNNPtr + buildProjectionFromESRI(const WKTNodeNNPtr &projCRSNode, + const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + ProjectedCRSNNPtr buildProjectedCRS(const WKTNodeNNPtr &node); + + VerticalReferenceFrameNNPtr + buildVerticalReferenceFrame(const WKTNodeNNPtr &node, + const WKTNodeNNPtr &dynamicNode); + + TemporalDatumNNPtr buildTemporalDatum(const WKTNodeNNPtr &node); + + EngineeringDatumNNPtr buildEngineeringDatum(const WKTNodeNNPtr &node); + + ParametricDatumNNPtr buildParametricDatum(const WKTNodeNNPtr &node); + + CRSNNPtr buildVerticalCRS(const WKTNodeNNPtr &node); + + DerivedVerticalCRSNNPtr buildDerivedVerticalCRS(const WKTNodeNNPtr &node); + + CompoundCRSNNPtr buildCompoundCRS(const WKTNodeNNPtr &node); + + BoundCRSNNPtr buildBoundCRS(const WKTNodeNNPtr &node); + + TemporalCSNNPtr buildTemporalCS(const WKTNodeNNPtr &parentNode); + + TemporalCRSNNPtr buildTemporalCRS(const WKTNodeNNPtr &node); + + DerivedTemporalCRSNNPtr buildDerivedTemporalCRS(const WKTNodeNNPtr &node); + + EngineeringCRSNNPtr buildEngineeringCRS(const WKTNodeNNPtr &node); + + EngineeringCRSNNPtr + buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node); + + DerivedEngineeringCRSNNPtr + buildDerivedEngineeringCRS(const WKTNodeNNPtr &node); + + ParametricCSNNPtr buildParametricCS(const WKTNodeNNPtr &parentNode); + + ParametricCRSNNPtr buildParametricCRS(const WKTNodeNNPtr &node); + + DerivedParametricCRSNNPtr + buildDerivedParametricCRS(const WKTNodeNNPtr &node); + + DerivedProjectedCRSNNPtr buildDerivedProjectedCRS(const WKTNodeNNPtr &node); + + CRSPtr buildCRS(const WKTNodeNNPtr &node); + + CoordinateOperationNNPtr buildCoordinateOperation(const WKTNodeNNPtr &node); + + ConcatenatedOperationNNPtr + buildConcatenatedOperation(const WKTNodeNNPtr &node); +}; + +// --------------------------------------------------------------------------- + +WKTParser::WKTParser() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +WKTParser::~WKTParser() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Set whether parsing should be done in strict mode. + */ +WKTParser &WKTParser::setStrict(bool strict) { + d->strict_ = strict; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the list of warnings found during parsing. + * + * \note The list might be non-empty only is setStrict(false) has been called. + */ +std::list WKTParser::warningList() const { + return d->warningList_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void WKTParser::Private::emitRecoverableAssertion(const std::string &errorMsg) { + if (strict_) { + throw ParsingException(errorMsg); + } else { + warningList_.push_back(errorMsg); + } +} + +// --------------------------------------------------------------------------- + +static double asDouble(const std::string &val) { return c_locale_stod(val); } + +// --------------------------------------------------------------------------- + +PROJ_NO_RETURN static void ThrowNotEnoughChildren(const std::string &nodeName) { + throw ParsingException( + concat("not enough children in ", nodeName, " node")); +} + +// --------------------------------------------------------------------------- + +PROJ_NO_RETURN static void +ThrowNotRequiredNumberOfChildren(const std::string &nodeName) { + throw ParsingException( + concat("not required number of children in ", nodeName, " node")); +} + +// --------------------------------------------------------------------------- + +PROJ_NO_RETURN static void ThrowMissing(const std::string &nodeName) { + throw ParsingException(concat("missing ", nodeName, " node")); +} + +// --------------------------------------------------------------------------- + +PROJ_NO_RETURN static void +ThrowNotExpectedCSType(const std::string &expectedCSType) { + throw ParsingException(concat("CS node is not of type ", expectedCSType)); +} + +// --------------------------------------------------------------------------- + +static ParsingException buildRethrow(const char *funcName, + const std::exception &e) { + std::string res(funcName); + res += ": "; + res += e.what(); + return ParsingException(res); +} + +// --------------------------------------------------------------------------- + +std::string WKTParser::Private::stripQuotes(const WKTNodeNNPtr &node) { + return ::stripQuotes(node->GP()->value()); +} + +// --------------------------------------------------------------------------- + +double WKTParser::Private::asDouble(const WKTNodeNNPtr &node) { + return io::asDouble(node->GP()->value()); +} + +// --------------------------------------------------------------------------- + +IdentifierPtr WKTParser::Private::buildId(const WKTNodeNNPtr &node, + bool tolerant) { + const auto *nodeP = node->GP(); + const auto &nodeChidren = nodeP->children(); + if (nodeChidren.size() >= 2) { + auto codeSpace = stripQuotes(nodeChidren[0]); + auto code = stripQuotes(nodeChidren[1]); + auto &citationNode = nodeP->lookForChild(WKTConstants::CITATION); + auto &uriNode = nodeP->lookForChild(WKTConstants::URI); + PropertyMap propertiesId; + propertiesId.set(Identifier::CODESPACE_KEY, codeSpace); + bool authoritySet = false; + /*if (!isNull(citationNode))*/ { + const auto *citationNodeP = citationNode->GP(); + if (citationNodeP->childrenSize() == 1) { + authoritySet = true; + propertiesId.set(Identifier::AUTHORITY_KEY, + stripQuotes(citationNodeP->children()[0])); + } + } + if (!authoritySet) { + propertiesId.set(Identifier::AUTHORITY_KEY, codeSpace); + } + /*if (!isNull(uriNode))*/ { + const auto *uriNodeP = uriNode->GP(); + if (uriNodeP->childrenSize() == 1) { + propertiesId.set(Identifier::URI_KEY, + stripQuotes(uriNodeP->children()[0])); + } + } + if (nodeChidren.size() >= 3 && + nodeChidren[2]->GP()->childrenSize() == 0) { + auto version = stripQuotes(nodeChidren[2]); + propertiesId.set(Identifier::VERSION_KEY, version); + } + return Identifier::create(code, propertiesId); + } else if (strict_ || !tolerant) { + ThrowNotEnoughChildren(nodeP->value()); + } else { + std::string msg("not enough children in "); + msg += nodeP->value(); + msg += " node"; + warningList_.emplace_back(std::move(msg)); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +PropertyMap &WKTParser::Private::buildProperties(const WKTNodeNNPtr &node) { + + if (propertyCount_ == MAX_PROPERTY_SIZE) { + throw ParsingException("MAX_PROPERTY_SIZE reached"); + } + properties_[propertyCount_] = new PropertyMap(); + auto &&properties = properties_[propertyCount_]; + propertyCount_++; + + std::string authNameFromAlias; + std::string codeFromAlias; + const auto *nodeP = node->GP(); + const auto &nodeChildren = nodeP->children(); + if (!nodeChildren.empty()) { + const auto &nodeName(nodeP->value()); + auto name(stripQuotes(nodeChildren[0])); + if (ends_with(name, " (deprecated)")) { + name.resize(name.size() - strlen(" (deprecated)")); + properties->set(common::IdentifiedObject::DEPRECATED_KEY, true); + } + + const char *tableNameForAlias = nullptr; + if (ci_equal(nodeName, WKTConstants::GEOGCS)) { + if (starts_with(name, "GCS_")) { + esriStyle_ = true; + if (name == "GCS_WGS_1984") { + name = "WGS 84"; + } else { + tableNameForAlias = "geodetic_crs"; + } + } + } else if (esriStyle_ && ci_equal(nodeName, WKTConstants::SPHEROID)) { + if (name == "WGS_1984") { + name = "WGS 84"; + authNameFromAlias = Identifier::EPSG; + codeFromAlias = "7030"; + } else { + tableNameForAlias = "ellipsoid"; + } + } + + if (dbContext_ && tableNameForAlias) { + std::string outTableName; + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto officialName = authFactory->getOfficialNameFromAlias( + name, tableNameForAlias, "ESRI", false, outTableName, + authNameFromAlias, codeFromAlias); + if (!officialName.empty()) { + name = officialName; + + // Clearing authority for geodetic_crs because of + // potential axis order mismatch. + if (strcmp(tableNameForAlias, "geodetic_crs") == 0) { + authNameFromAlias.clear(); + codeFromAlias.clear(); + } + } + } + + properties->set(IdentifiedObject::NAME_KEY, name); + } + + auto identifiers = ArrayOfBaseObject::create(); + 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); + if (id) { + identifiers->add(NN_NO_CHECK(id)); + } + } + } + if (identifiers->empty() && !authNameFromAlias.empty()) { + identifiers->add(Identifier::create( + codeFromAlias, + PropertyMap() + .set(Identifier::CODESPACE_KEY, authNameFromAlias) + .set(Identifier::AUTHORITY_KEY, authNameFromAlias))); + } + if (!identifiers->empty()) { + properties->set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); + } + + auto &remarkNode = nodeP->lookForChild(WKTConstants::REMARK); + if (!isNull(remarkNode)) { + const auto &remarkChildren = remarkNode->GP()->children(); + if (remarkChildren.size() == 1) { + properties->set(IdentifiedObject::REMARKS_KEY, + stripQuotes(remarkChildren[0])); + } else { + ThrowNotRequiredNumberOfChildren(remarkNode->GP()->value()); + } + } + + ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create(); + for (const auto &subNode : nodeP->children()) { + const auto &subNodeName(subNode->GP()->value()); + if (ci_equal(subNodeName, WKTConstants::USAGE)) { + auto objectDomain = buildObjectDomain(subNode); + if (!objectDomain) { + throw ParsingException( + concat("missing children in ", subNodeName, " node")); + } + array->add(NN_NO_CHECK(objectDomain)); + } + } + if (!array->empty()) { + properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, array); + } else { + auto objectDomain = buildObjectDomain(node); + if (objectDomain) { + properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, + NN_NO_CHECK(objectDomain)); + } + } + + return *properties; +} + +// --------------------------------------------------------------------------- + +ObjectDomainPtr +WKTParser::Private::buildObjectDomain(const WKTNodeNNPtr &node) { + + const auto *nodeP = node->GP(); + auto &scopeNode = nodeP->lookForChild(WKTConstants::SCOPE); + auto &areaNode = nodeP->lookForChild(WKTConstants::AREA); + auto &bboxNode = nodeP->lookForChild(WKTConstants::BBOX); + auto &verticalExtentNode = + nodeP->lookForChild(WKTConstants::VERTICALEXTENT); + auto &temporalExtentNode = nodeP->lookForChild(WKTConstants::TIMEEXTENT); + if (!isNull(scopeNode) || !isNull(areaNode) || !isNull(bboxNode) || + !isNull(verticalExtentNode) || !isNull(temporalExtentNode)) { + optional scope; + const auto *scopeNodeP = scopeNode->GP(); + const auto &scopeChildren = scopeNodeP->children(); + if (scopeChildren.size() == 1) { + scope = stripQuotes(scopeChildren[0]); + } + ExtentPtr extent; + if (!isNull(areaNode) || !isNull(bboxNode)) { + util::optional description; + std::vector geogExtent; + std::vector verticalExtent; + std::vector temporalExtent; + if (!isNull(areaNode)) { + const auto &areaChildren = areaNode->GP()->children(); + if (areaChildren.size() == 1) { + description = stripQuotes(areaChildren[0]); + } else { + ThrowNotRequiredNumberOfChildren(areaNode->GP()->value()); + } + } + if (!isNull(bboxNode)) { + const auto &bboxChildren = bboxNode->GP()->children(); + if (bboxChildren.size() == 4) { + try { + double south = asDouble(bboxChildren[0]); + double west = asDouble(bboxChildren[1]); + double north = asDouble(bboxChildren[2]); + double east = asDouble(bboxChildren[3]); + auto bbox = GeographicBoundingBox::create(west, south, + east, north); + geogExtent.emplace_back(bbox); + } catch (const std::exception &) { + throw ParsingException(concat("not 4 double values in ", + bboxNode->GP()->value(), + " node")); + } + } else { + ThrowNotRequiredNumberOfChildren(bboxNode->GP()->value()); + } + } + + if (!isNull(verticalExtentNode)) { + const auto &verticalExtentChildren = + verticalExtentNode->GP()->children(); + const auto verticalExtentChildrenSize = + verticalExtentChildren.size(); + if (verticalExtentChildrenSize == 2 || + verticalExtentChildrenSize == 3) { + double min; + double max; + try { + min = asDouble(verticalExtentChildren[0]); + max = asDouble(verticalExtentChildren[1]); + } catch (const std::exception &) { + throw ParsingException( + concat("not 2 double values in ", + verticalExtentNode->GP()->value(), " node")); + } + UnitOfMeasure unit = UnitOfMeasure::METRE; + if (verticalExtentChildrenSize == 3) { + unit = buildUnit(verticalExtentChildren[2], + UnitOfMeasure::Type::LINEAR); + } + verticalExtent.emplace_back(VerticalExtent::create( + min, max, util::nn_make_shared(unit))); + } else { + ThrowNotRequiredNumberOfChildren( + verticalExtentNode->GP()->value()); + } + } + + if (!isNull(temporalExtentNode)) { + const auto &temporalExtentChildren = + temporalExtentNode->GP()->children(); + if (temporalExtentChildren.size() == 2) { + temporalExtent.emplace_back(TemporalExtent::create( + stripQuotes(temporalExtentChildren[0]), + stripQuotes(temporalExtentChildren[1]))); + } else { + ThrowNotRequiredNumberOfChildren( + temporalExtentNode->GP()->value()); + } + } + extent = Extent::create(description, geogExtent, verticalExtent, + temporalExtent) + .as_nullable(); + } + return ObjectDomain::create(scope, extent).as_nullable(); + } + + return nullptr; +} + +// --------------------------------------------------------------------------- + +UnitOfMeasure WKTParser::Private::buildUnit(const WKTNodeNNPtr &node, + UnitOfMeasure::Type type) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if ((type != UnitOfMeasure::Type::TIME && children.size() < 2) || + (type == UnitOfMeasure::Type::TIME && children.size() < 1)) { + ThrowNotEnoughChildren(nodeP->value()); + } + try { + std::string unitName(stripQuotes(children[0])); + PropertyMap properties(buildProperties(node)); + auto &idNode = + nodeP->lookForChild(WKTConstants::ID, WKTConstants::AUTHORITY); + if (!isNull(idNode) && idNode->GP()->childrenSize() < 2) { + emitRecoverableAssertion("not enough children in " + + idNode->GP()->value() + " node"); + } + const bool hasValidIdNode = + !isNull(idNode) && idNode->GP()->childrenSize() >= 2; + + const auto &idNodeChildren(idNode->GP()->children()); + std::string codeSpace(hasValidIdNode ? stripQuotes(idNodeChildren[0]) + : std::string()); + std::string code(hasValidIdNode ? stripQuotes(idNodeChildren[1]) + : std::string()); + + bool queryDb = true; + if (type == UnitOfMeasure::Type::UNKNOWN) { + if (ci_equal(unitName, "METER") || ci_equal(unitName, "METRE")) { + type = UnitOfMeasure::Type::LINEAR; + unitName = "metre"; + if (codeSpace.empty()) { + codeSpace = Identifier::EPSG; + code = "9001"; + queryDb = false; + } + } else if (ci_equal(unitName, "DEGREE") || + ci_equal(unitName, "GRAD")) { + type = UnitOfMeasure::Type::ANGULAR; + } + } + + if (esriStyle_ && dbContext_ && queryDb) { + std::string outTableName; + std::string authNameFromAlias; + std::string codeFromAlias; + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto officialName = authFactory->getOfficialNameFromAlias( + unitName, "unit_of_measure", "ESRI", false, outTableName, + authNameFromAlias, codeFromAlias); + if (!officialName.empty()) { + unitName = officialName; + codeSpace = authNameFromAlias; + code = codeFromAlias; + } + } + + double convFactor = children.size() >= 2 ? asDouble(children[1]) : 0.0; + constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37; + constexpr double REL_ERROR = 1e-10; + // Fix common rounding errors + if (std::fabs(convFactor - UnitOfMeasure::DEGREE.conversionToSI()) < + REL_ERROR * convFactor) { + convFactor = UnitOfMeasure::DEGREE.conversionToSI(); + } else if (std::fabs(convFactor - US_FOOT_CONV_FACTOR) < + REL_ERROR * convFactor) { + convFactor = US_FOOT_CONV_FACTOR; + } + + return UnitOfMeasure(unitName, convFactor, type, codeSpace, code); + } catch (const std::exception &e) { + throw buildRethrow(__FUNCTION__, e); + } +} + +// --------------------------------------------------------------------------- + +// node here is a parent node, not a UNIT/LENGTHUNIT/ANGLEUNIT/TIMEUNIT/... node +UnitOfMeasure WKTParser::Private::buildUnitInSubNode(const WKTNodeNNPtr &node, + UnitOfMeasure::Type type) { + const auto *nodeP = node->GP(); + { + auto &unitNode = nodeP->lookForChild(WKTConstants::LENGTHUNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, UnitOfMeasure::Type::LINEAR); + } + } + + { + auto &unitNode = nodeP->lookForChild(WKTConstants::ANGLEUNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, UnitOfMeasure::Type::ANGULAR); + } + } + + { + auto &unitNode = nodeP->lookForChild(WKTConstants::SCALEUNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, UnitOfMeasure::Type::SCALE); + } + } + + { + auto &unitNode = nodeP->lookForChild(WKTConstants::TIMEUNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, UnitOfMeasure::Type::TIME); + } + } + { + auto &unitNode = nodeP->lookForChild(WKTConstants::TEMPORALQUANTITY); + if (!isNull(unitNode)) { + return buildUnit(unitNode, UnitOfMeasure::Type::TIME); + } + } + + { + auto &unitNode = nodeP->lookForChild(WKTConstants::PARAMETRICUNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, UnitOfMeasure::Type::PARAMETRIC); + } + } + + { + auto &unitNode = nodeP->lookForChild(WKTConstants::UNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, type); + } + } + + return UnitOfMeasure::NONE; +} + +// --------------------------------------------------------------------------- + +EllipsoidNNPtr WKTParser::Private::buildEllipsoid(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if (children.size() < 3) { + ThrowNotEnoughChildren(nodeP->value()); + } + try { + UnitOfMeasure unit = + buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + unit = UnitOfMeasure::METRE; + } + Length semiMajorAxis(asDouble(children[1]), unit); + Scale invFlattening(asDouble(children[2])); + const auto celestialBody( + Ellipsoid::guessBodyName(dbContext_, semiMajorAxis.getSIValue())); + if (invFlattening.getSIValue() == 0) { + return Ellipsoid::createSphere(buildProperties(node), semiMajorAxis, + celestialBody); + } else { + return Ellipsoid::createFlattenedSphere( + buildProperties(node), semiMajorAxis, invFlattening, + celestialBody); + } + } catch (const std::exception &e) { + throw buildRethrow(__FUNCTION__, e); + } +} + +// --------------------------------------------------------------------------- + +PrimeMeridianNNPtr WKTParser::Private::buildPrimeMeridian( + const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if (children.size() < 2) { + ThrowNotEnoughChildren(nodeP->value()); + } + auto name = stripQuotes(children[0]); + UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR); + if (unit == UnitOfMeasure::NONE) { + unit = defaultAngularUnit; + if (unit == UnitOfMeasure::NONE) { + unit = UnitOfMeasure::DEGREE; + } + } + try { + double angleValue = asDouble(children[1]); + + // Correct for GDAL WKT1 departure + if (name == "Paris" && std::fabs(angleValue - 2.33722917) < 1e-8 && + unit == UnitOfMeasure::GRAD) { + angleValue = 2.5969213; + } + + Angle angle(angleValue, unit); + return PrimeMeridian::create(buildProperties(node), angle); + } catch (const std::exception &e) { + throw buildRethrow(__FUNCTION__, e); + } +} + +// --------------------------------------------------------------------------- + +optional WKTParser::Private::getAnchor(const WKTNodeNNPtr &node) { + + auto &anchorNode = node->GP()->lookForChild(WKTConstants::ANCHOR); + if (anchorNode->GP()->childrenSize() == 1) { + return optional( + stripQuotes(anchorNode->GP()->children()[0])); + } + return optional(); +} + +// --------------------------------------------------------------------------- + +static const PrimeMeridianNNPtr & +fixupPrimeMeridan(const EllipsoidNNPtr &ellipsoid, + const PrimeMeridianNNPtr &pm) { + return (ellipsoid->celestialBody() != Ellipsoid::EARTH && + pm.get() == PrimeMeridian::GREENWICH.get()) + ? PrimeMeridian::REFERENCE_MERIDIAN + : pm; +} + +// --------------------------------------------------------------------------- + +GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame( + const WKTNodeNNPtr &node, const PrimeMeridianNNPtr &primeMeridian, + const WKTNodeNNPtr &dynamicNode) { + const auto *nodeP = node->GP(); + auto &ellipsoidNode = + nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID); + if (isNull(ellipsoidNode)) { + ThrowMissing(WKTConstants::ELLIPSOID); + } + auto &properties = buildProperties(node); + + // do that before buildEllipsoid() so that esriStyle_ can be set + auto name = stripQuotes(nodeP->children()[0]); + if (name == "WGS_1984") { + properties.set(IdentifiedObject::NAME_KEY, + GeodeticReferenceFrame::EPSG_6326->nameStr()); + } else if (starts_with(name, "D_")) { + esriStyle_ = true; + const char *tableNameForAlias = nullptr; + std::string authNameFromAlias; + std::string codeFromAlias; + if (name == "D_WGS_1984") { + name = "World Geodetic System 1984"; + authNameFromAlias = Identifier::EPSG; + codeFromAlias = "6326"; + } else { + tableNameForAlias = "geodetic_datum"; + } + + if (dbContext_ && tableNameForAlias) { + std::string outTableName; + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto officialName = authFactory->getOfficialNameFromAlias( + name, tableNameForAlias, "ESRI", false, outTableName, + authNameFromAlias, codeFromAlias); + if (!officialName.empty()) { + if (primeMeridian->nameStr() != + PrimeMeridian::GREENWICH->nameStr()) { + auto nameWithPM = + officialName + " (" + primeMeridian->nameStr() + ")"; + if (dbContext_->isKnownName(nameWithPM, "geodetic_datum")) { + officialName = nameWithPM; + } + } + name = officialName; + } + } + + properties.set(IdentifiedObject::NAME_KEY, name); + 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); + } + } else if (name.find('_') != std::string::npos) { + // Likely coming from WKT1 + if (dbContext_) { + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto res = authFactory->createObjectsFromName( + name, {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, + true, 1); + bool foundDatumName = false; + if (!res.empty()) { + const auto &refDatum = res.front(); + if (metadata::Identifier::isEquivalentName( + name.c_str(), refDatum->nameStr().c_str())) { + foundDatumName = true; + properties.set(IdentifiedObject::NAME_KEY, + refDatum->nameStr()); + if (!properties.get(Identifier::CODESPACE_KEY) && + refDatum->identifiers().size() == 1) { + const auto &id = refDatum->identifiers()[0]; + auto identifiers = ArrayOfBaseObject::create(); + identifiers->add(Identifier::create( + id->code(), PropertyMap() + .set(Identifier::CODESPACE_KEY, + *id->codeSpace()) + .set(Identifier::AUTHORITY_KEY, + *id->codeSpace()))); + properties.set(IdentifiedObject::IDENTIFIERS_KEY, + identifiers); + } + } + } else { + // Get official name from database if AUTHORITY is present + auto &idNode = nodeP->lookForChild(WKTConstants::AUTHORITY); + if (!isNull(idNode)) { + try { + auto id = buildId(idNode); + auto authFactory2 = AuthorityFactory::create( + NN_NO_CHECK(dbContext_), *id->codeSpace()); + auto dbDatum = + authFactory2->createGeodeticDatum(id->code()); + foundDatumName = true; + properties.set(IdentifiedObject::NAME_KEY, + dbDatum->nameStr()); + } catch (const std::exception &) { + } + } + } + + if (!foundDatumName) { + std::string outTableName; + std::string authNameFromAlias; + std::string codeFromAlias; + auto officialName = authFactory->getOfficialNameFromAlias( + name, "geodetic_datum", std::string(), true, outTableName, + authNameFromAlias, codeFromAlias); + if (!officialName.empty()) { + properties.set(IdentifiedObject::NAME_KEY, officialName); + } + } + } + } + + auto ellipsoid = buildEllipsoid(ellipsoidNode); + const auto &primeMeridianModified = + fixupPrimeMeridan(ellipsoid, primeMeridian); + + auto &TOWGS84Node = nodeP->lookForChild(WKTConstants::TOWGS84); + if (!isNull(TOWGS84Node)) { + const auto &TOWGS84Children = TOWGS84Node->GP()->children(); + const size_t TOWGS84Size = TOWGS84Children.size(); + if (TOWGS84Size == 3 || TOWGS84Size == 7) { + try { + for (const auto &child : TOWGS84Children) { + toWGS84Parameters_.push_back(asDouble(child)); + } + for (size_t i = TOWGS84Size; i < 7; ++i) { + toWGS84Parameters_.push_back(0.0); + } + } catch (const std::exception &) { + throw ParsingException("Invalid TOWGS84 node"); + } + } else { + throw ParsingException("Invalid TOWGS84 node"); + } + } + + auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION); + const auto &extensionChildren = extensionNode->GP()->children(); + if (extensionChildren.size() == 2) { + if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) { + datumPROJ4Grids_ = stripQuotes(extensionChildren[1]); + } + } + + if (!isNull(dynamicNode)) { + double frameReferenceEpoch = 0.0; + util::optional modelName; + parseDynamic(dynamicNode, frameReferenceEpoch, modelName); + return DynamicGeodeticReferenceFrame::create( + properties, ellipsoid, getAnchor(node), primeMeridianModified, + common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR), + modelName); + } + + return GeodeticReferenceFrame::create( + properties, ellipsoid, getAnchor(node), primeMeridianModified); +} + +// --------------------------------------------------------------------------- + +DatumEnsembleNNPtr +WKTParser::Private::buildDatumEnsemble(const WKTNodeNNPtr &node, + const PrimeMeridianPtr &primeMeridian, + bool expectEllipsoid) { + const auto *nodeP = node->GP(); + auto &ellipsoidNode = + nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID); + if (expectEllipsoid && isNull(ellipsoidNode)) { + ThrowMissing(WKTConstants::ELLIPSOID); + } + + std::vector datums; + for (const auto &subNode : nodeP->children()) { + if (ci_equal(subNode->GP()->value(), WKTConstants::MEMBER)) { + if (subNode->GP()->childrenSize() == 0) { + throw ParsingException("Invalid MEMBER node"); + } + if (expectEllipsoid) { + datums.emplace_back(GeodeticReferenceFrame::create( + buildProperties(subNode), buildEllipsoid(ellipsoidNode), + optional(), + primeMeridian ? NN_NO_CHECK(primeMeridian) + : PrimeMeridian::GREENWICH)); + } else { + datums.emplace_back( + VerticalReferenceFrame::create(buildProperties(subNode))); + } + } + } + + auto &accuracyNode = nodeP->lookForChild(WKTConstants::ENSEMBLEACCURACY); + auto &accuracyNodeChildren = accuracyNode->GP()->children(); + if (accuracyNodeChildren.empty()) { + ThrowMissing(WKTConstants::ENSEMBLEACCURACY); + } + auto accuracy = + PositionalAccuracy::create(accuracyNodeChildren[0]->GP()->value()); + + try { + return DatumEnsemble::create(buildProperties(node), datums, accuracy); + } catch (const util::Exception &e) { + throw buildRethrow(__FUNCTION__, e); + } +} + +// --------------------------------------------------------------------------- + +MeridianNNPtr WKTParser::Private::buildMeridian(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if (children.size() < 2) { + ThrowNotEnoughChildren(nodeP->value()); + } + UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR); + try { + double angleValue = asDouble(children[0]); + Angle angle(angleValue, unit); + return Meridian::create(angle); + } catch (const std::exception &e) { + throw buildRethrow(__FUNCTION__, e); + } +} + +// --------------------------------------------------------------------------- + +PROJ_NO_RETURN static void ThrowParsingExceptionMissingUNIT() { + throw ParsingException("buildCS: missing UNIT"); +} + +// --------------------------------------------------------------------------- + +CoordinateSystemAxisNNPtr +WKTParser::Private::buildAxis(const WKTNodeNNPtr &node, + const UnitOfMeasure &unitIn, + const UnitOfMeasure::Type &unitType, + bool isGeocentric, int expectedOrderNum) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if (children.size() < 2) { + ThrowNotEnoughChildren(nodeP->value()); + } + + auto &orderNode = nodeP->lookForChild(WKTConstants::ORDER); + if (!isNull(orderNode)) { + const auto &orderNodeChildren = orderNode->GP()->children(); + if (orderNodeChildren.size() != 1) { + ThrowNotEnoughChildren(WKTConstants::ORDER); + } + const auto &order = orderNodeChildren[0]->GP()->value(); + int orderNum; + try { + orderNum = std::stoi(order); + } catch (const std::exception &) { + throw ParsingException( + concat("buildAxis: invalid ORDER value: ", order)); + } + if (orderNum != expectedOrderNum) { + throw ParsingException( + concat("buildAxis: did not get expected ORDER value: ", order)); + } + } + + // The axis designation in WK2 can be: "name", "(abbrev)" or "name + // (abbrev)" + std::string axisDesignation(stripQuotes(children[0])); + size_t sepPos = axisDesignation.find(" ("); + std::string axisName; + std::string abbreviation; + if (sepPos != std::string::npos && axisDesignation.back() == ')') { + axisName = CoordinateSystemAxis::normalizeAxisName( + axisDesignation.substr(0, sepPos)); + abbreviation = axisDesignation.substr(sepPos + 2); + abbreviation.resize(abbreviation.size() - 1); + } else if (!axisDesignation.empty() && axisDesignation[0] == '(' && + axisDesignation.back() == ')') { + abbreviation = axisDesignation.substr(1, axisDesignation.size() - 2); + if (abbreviation == AxisAbbreviation::E) { + axisName = AxisName::Easting; + } else if (abbreviation == AxisAbbreviation::N) { + axisName = AxisName::Northing; + } else if (abbreviation == AxisAbbreviation::lat) { + axisName = AxisName::Latitude; + } else if (abbreviation == AxisAbbreviation::lon) { + axisName = AxisName::Longitude; + } + } else { + axisName = CoordinateSystemAxis::normalizeAxisName(axisDesignation); + if (axisName == AxisName::Latitude) { + abbreviation = AxisAbbreviation::lat; + } else if (axisName == AxisName::Longitude) { + abbreviation = AxisAbbreviation::lon; + } else if (axisName == AxisName::Ellipsoidal_height) { + abbreviation = AxisAbbreviation::h; + } + } + const std::string &dirString = children[1]->GP()->value(); + const AxisDirection *direction = AxisDirection::valueOf(dirString); + + // WKT2, geocentric CS: axis names are omitted + if (axisName.empty()) { + if (direction == &AxisDirection::GEOCENTRIC_X && + abbreviation == AxisAbbreviation::X) { + axisName = AxisName::Geocentric_X; + } else if (direction == &AxisDirection::GEOCENTRIC_Y && + abbreviation == AxisAbbreviation::Y) { + axisName = AxisName::Geocentric_Y; + } else if (direction == &AxisDirection::GEOCENTRIC_Z && + abbreviation == AxisAbbreviation::Z) { + axisName = AxisName::Geocentric_Z; + } + } + + // WKT1 + if (!direction && isGeocentric && axisName == AxisName::Geocentric_X) { + abbreviation = AxisAbbreviation::X; + direction = &AxisDirection::GEOCENTRIC_X; + } else if (!direction && isGeocentric && + axisName == AxisName::Geocentric_Y) { + abbreviation = AxisAbbreviation::Y; + direction = &AxisDirection::GEOCENTRIC_Y; + } else if (isGeocentric && axisName == AxisName::Geocentric_Z && + (dirString == AxisDirectionWKT1::NORTH.toString() || + dirString == AxisDirectionWKT1::OTHER.toString())) { + abbreviation = AxisAbbreviation::Z; + direction = &AxisDirection::GEOCENTRIC_Z; + } else if (dirString == AxisDirectionWKT1::OTHER.toString()) { + direction = &AxisDirection::UNSPECIFIED; + } else if (!direction && AxisDirectionWKT1::valueOf(dirString) != nullptr) { + direction = AxisDirection::valueOf(tolower(dirString)); + } + + if (!direction) { + throw ParsingException( + concat("unhandled axis direction: ", children[1]->GP()->value())); + } + UnitOfMeasure unit(buildUnitInSubNode(node)); + if (unit == UnitOfMeasure::NONE) { + // If no unit in the AXIS node, use the one potentially coming from + // the CS. + unit = unitIn; + if (unit == UnitOfMeasure::NONE && + unitType != UnitOfMeasure::Type::NONE && + unitType != UnitOfMeasure::Type::TIME) { + ThrowParsingExceptionMissingUNIT(); + } + } + + auto &meridianNode = nodeP->lookForChild(WKTConstants::MERIDIAN); + + return CoordinateSystemAxis::create( + buildProperties(node).set(IdentifiedObject::NAME_KEY, axisName), + abbreviation, *direction, unit, + !isNull(meridianNode) ? buildMeridian(meridianNode).as_nullable() + : nullptr); +} + +// --------------------------------------------------------------------------- + +static const PropertyMap emptyPropertyMap{}; + +PROJ_NO_RETURN static void ThrowParsingException(const std::string &msg) { + throw ParsingException(msg); +} + +static ParsingException +buildParsingExceptionInvalidAxisCount(const std::string &csType) { + return ParsingException( + concat("buildCS: invalid CS axis count for ", csType)); +} + +CoordinateSystemNNPtr +WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */ + const WKTNodeNNPtr &parentNode, + const UnitOfMeasure &defaultAngularUnit) { + bool isGeocentric = false; + std::string csType; + const int numberOfAxis = + parentNode->countChildrenOfName(WKTConstants::AXIS); + int axisCount = numberOfAxis; + if (!isNull(node)) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if (children.size() < 2) { + ThrowNotEnoughChildren(nodeP->value()); + } + csType = children[0]->GP()->value(); + try { + axisCount = std::stoi(children[1]->GP()->value()); + } catch (const std::exception &) { + ThrowParsingException(concat("buildCS: invalid CS axis count: ", + children[1]->GP()->value())); + } + } else { + const char *csTypeCStr = ""; + const auto &parentNodeName = parentNode->GP()->value(); + if (ci_equal(parentNodeName, WKTConstants::GEOCCS)) { + csTypeCStr = "Cartesian"; + isGeocentric = true; + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + ThrowParsingExceptionMissingUNIT(); + } + return CartesianCS::createGeocentric(unit); + } + } else if (ci_equal(parentNodeName, WKTConstants::GEOGCS)) { + csTypeCStr = "Ellipsoidal"; + if (axisCount == 0) { + // Missing axis with GEOGCS ? Presumably Long/Lat order + // implied + auto unit = buildUnitInSubNode(parentNode, + UnitOfMeasure::Type::ANGULAR); + if (unit == UnitOfMeasure::NONE) { + ThrowParsingExceptionMissingUNIT(); + } + // WKT1 --> long/lat + return EllipsoidalCS::createLongitudeLatitude(unit); + } + } else if (ci_equal(parentNodeName, WKTConstants::BASEGEODCRS) || + ci_equal(parentNodeName, WKTConstants::BASEGEOGCRS)) { + csTypeCStr = "Ellipsoidal"; + if (axisCount == 0) { + auto unit = buildUnitInSubNode(parentNode, + UnitOfMeasure::Type::ANGULAR); + if (unit == UnitOfMeasure::NONE) { + unit = defaultAngularUnit; + } + // WKT2 --> presumably lat/long + return EllipsoidalCS::createLatitudeLongitude(unit); + } + } else if (ci_equal(parentNodeName, WKTConstants::PROJCS) || + ci_equal(parentNodeName, WKTConstants::BASEPROJCRS) || + ci_equal(parentNodeName, WKTConstants::BASEENGCRS)) { + csTypeCStr = "Cartesian"; + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + if (ci_equal(parentNodeName, WKTConstants::PROJCS)) { + ThrowParsingExceptionMissingUNIT(); + } else { + unit = UnitOfMeasure::METRE; + } + } + return CartesianCS::createEastingNorthing(unit); + } + } else if (ci_equal(parentNodeName, WKTConstants::VERT_CS) || + ci_equal(parentNodeName, WKTConstants::BASEVERTCRS)) { + csTypeCStr = "vertical"; + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + if (ci_equal(parentNodeName, WKTConstants::VERT_CS)) { + ThrowParsingExceptionMissingUNIT(); + } else { + unit = UnitOfMeasure::METRE; + } + } + return VerticalCS::createGravityRelatedHeight(unit); + } + } else if (ci_equal(parentNodeName, WKTConstants::LOCAL_CS)) { + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + unit = UnitOfMeasure::METRE; + } + return CartesianCS::createEastingNorthing(unit); + } else if (axisCount == 1) { + csTypeCStr = "vertical"; + } else if (axisCount == 2) { + csTypeCStr = "Cartesian"; + } else { + throw ParsingException( + "buildCS: unexpected AXIS count for LOCAL_CS"); + } + } else if (ci_equal(parentNodeName, WKTConstants::BASEPARAMCRS)) { + csTypeCStr = "parametric"; + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + unit = UnitOfMeasure("unknown", 1, + UnitOfMeasure::Type::PARAMETRIC); + } + return ParametricCS::create( + emptyPropertyMap, + CoordinateSystemAxis::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, + "unknown parametric"), + std::string(), AxisDirection::UNSPECIFIED, unit)); + } + } else if (ci_equal(parentNodeName, WKTConstants::BASETIMECRS)) { + csTypeCStr = "temporal"; + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::TIME); + if (unit == UnitOfMeasure::NONE) { + unit = + UnitOfMeasure("unknown", 1, UnitOfMeasure::Type::TIME); + } + return DateTimeTemporalCS::create( + emptyPropertyMap, + CoordinateSystemAxis::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, + "unknown temporal"), + std::string(), AxisDirection::FUTURE, unit)); + } + } else { + // Shouldn't happen normally + throw ParsingException( + concat("buildCS: unexpected parent node: ", parentNodeName)); + } + csType = csTypeCStr; + } + + if (axisCount != 1 && axisCount != 2 && axisCount != 3) { + throw buildParsingExceptionInvalidAxisCount(csType); + } + if (numberOfAxis != axisCount) { + throw ParsingException("buildCS: declared number of axis by CS node " + "and number of AXIS are inconsistent"); + } + + const auto unitType = + ci_equal(csType, "ellipsoidal") + ? UnitOfMeasure::Type::ANGULAR + : ci_equal(csType, "ordinal") + ? UnitOfMeasure::Type::NONE + : ci_equal(csType, "parametric") + ? UnitOfMeasure::Type::PARAMETRIC + : ci_equal(csType, "Cartesian") || + ci_equal(csType, "vertical") + ? UnitOfMeasure::Type::LINEAR + : (ci_equal(csType, "temporal") || + ci_equal(csType, "TemporalDateTime") || + ci_equal(csType, "TemporalCount") || + ci_equal(csType, "TemporalMeasure")) + ? UnitOfMeasure::Type::TIME + : UnitOfMeasure::Type::UNKNOWN; + UnitOfMeasure unit = buildUnitInSubNode(parentNode, unitType); + + std::vector axisList; + for (int i = 0; i < axisCount; i++) { + axisList.emplace_back( + buildAxis(parentNode->GP()->lookForChild(WKTConstants::AXIS, i), + unit, unitType, isGeocentric, i + 1)); + }; + + const PropertyMap &csMap = emptyPropertyMap; + if (ci_equal(csType, "ellipsoidal")) { + if (axisCount == 2) { + return EllipsoidalCS::create(csMap, axisList[0], axisList[1]); + } else if (axisCount == 3) { + return EllipsoidalCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + } else if (ci_equal(csType, "Cartesian")) { + if (axisCount == 2) { + return CartesianCS::create(csMap, axisList[0], axisList[1]); + } else if (axisCount == 3) { + return CartesianCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + } else if (ci_equal(csType, "vertical")) { + if (axisCount == 1) { + return VerticalCS::create(csMap, axisList[0]); + } + } else if (ci_equal(csType, "spherical")) { + if (axisCount == 3) { + return SphericalCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + } else if (ci_equal(csType, "ordinal")) { // WKT2-2018 + return OrdinalCS::create(csMap, axisList); + } else if (ci_equal(csType, "parametric")) { + if (axisCount == 1) { + return ParametricCS::create(csMap, axisList[0]); + } + } else if (ci_equal(csType, "temporal")) { // WKT2-2015 + if (axisCount == 1) { + return DateTimeTemporalCS::create( + csMap, + axisList[0]); // FIXME: there are 3 possible subtypes of + // TemporalCS + } + } else if (ci_equal(csType, "TemporalDateTime")) { // WKT2-2018 + if (axisCount == 1) { + return DateTimeTemporalCS::create(csMap, axisList[0]); + } + } else if (ci_equal(csType, "TemporalCount")) { // WKT2-2018 + if (axisCount == 1) { + return TemporalCountCS::create(csMap, axisList[0]); + } + } else if (ci_equal(csType, "TemporalMeasure")) { // WKT2-2018 + if (axisCount == 1) { + return TemporalMeasureCS::create(csMap, axisList[0]); + } + } else { + throw ParsingException(concat("unhandled CS type: ", csType)); + } + throw buildParsingExceptionInvalidAxisCount(csType); +} + +// --------------------------------------------------------------------------- + +void WKTParser::Private::addExtensionProj4ToProp(const WKTNode::Private *nodeP, + PropertyMap &props) { + auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION); + const auto &extensionChildren = extensionNode->GP()->children(); + if (extensionChildren.size() == 2) { + if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) { + props.set("EXTENSION_PROJ4", stripQuotes(extensionChildren[1])); + } + } +} + +// --------------------------------------------------------------------------- + +GeodeticCRSNNPtr +WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &datumNode = nodeP->lookForChild( + WKTConstants::DATUM, WKTConstants::GEODETICDATUM, WKTConstants::TRF); + auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE); + if (isNull(datumNode) && isNull(ensembleNode)) { + throw ParsingException("Missing DATUM or ENSEMBLE node"); + } + + auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC); + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + const auto &nodeName = nodeP->value(); + if (isNull(csNode) && !ci_equal(nodeName, WKTConstants::GEOGCS) && + !ci_equal(nodeName, WKTConstants::GEOCCS) && + !ci_equal(nodeName, WKTConstants::BASEGEODCRS) && + !ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) { + ThrowMissing(WKTConstants::CS); + } + + auto &primeMeridianNode = + nodeP->lookForChild(WKTConstants::PRIMEM, WKTConstants::PRIMEMERIDIAN); + if (isNull(primeMeridianNode)) { + // PRIMEM is required in WKT1 + if (ci_equal(nodeName, WKTConstants::GEOGCS) || + ci_equal(nodeName, WKTConstants::GEOCCS)) { + emitRecoverableAssertion(nodeName + " should have a PRIMEM node"); + } + } + + auto angularUnit = + buildUnitInSubNode(node, ci_equal(nodeName, WKTConstants::GEOGCS) + ? UnitOfMeasure::Type::ANGULAR + : UnitOfMeasure::Type::UNKNOWN); + if (angularUnit.type() != UnitOfMeasure::Type::ANGULAR) { + angularUnit = UnitOfMeasure::NONE; + } + + auto primeMeridian = + !isNull(primeMeridianNode) + ? buildPrimeMeridian(primeMeridianNode, angularUnit) + : PrimeMeridian::GREENWICH; + if (angularUnit == UnitOfMeasure::NONE) { + angularUnit = primeMeridian->longitude().unit(); + } + + auto props = buildProperties(node); + addExtensionProj4ToProp(nodeP, props); + + // No explicit AXIS node ? (WKT1) + if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) { + props.set("IMPLICIT_CS", true); + } + + auto datum = + !isNull(datumNode) + ? buildGeodeticReferenceFrame(datumNode, primeMeridian, dynamicNode) + .as_nullable() + : nullptr; + auto datumEnsemble = + !isNull(ensembleNode) + ? buildDatumEnsemble(ensembleNode, primeMeridian, true) + .as_nullable() + : nullptr; + auto cs = buildCS(csNode, node, angularUnit); + auto ellipsoidalCS = nn_dynamic_pointer_cast(cs); + if (ellipsoidalCS) { + assert(!ci_equal(nodeName, WKTConstants::GEOCCS)); + try { + return GeographicCRS::create(props, datum, datumEnsemble, + NN_NO_CHECK(ellipsoidalCS)); + } catch (const util::Exception &e) { + throw ParsingException(std::string("buildGeodeticCRS: ") + + e.what()); + } + } else if (ci_equal(nodeName, WKTConstants::GEOGCRS) || + ci_equal(nodeName, WKTConstants::GEOGRAPHICCRS) || + ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) { + // This is a WKT2-2018 GeographicCRS. An ellipsoidal CS is expected + throw ParsingException(concat("ellipsoidal CS expected, but found ", + cs->getWKT2Type(true))); + } + + auto cartesianCS = nn_dynamic_pointer_cast(cs); + if (cartesianCS) { + if (cartesianCS->axisList().size() != 3) { + throw ParsingException( + "Cartesian CS for a GeodeticCRS should have 3 axis"); + } + try { + return GeodeticCRS::create(props, datum, datumEnsemble, + NN_NO_CHECK(cartesianCS)); + } catch (const util::Exception &e) { + throw ParsingException(std::string("buildGeodeticCRS: ") + + e.what()); + } + } + + auto sphericalCS = nn_dynamic_pointer_cast(cs); + if (sphericalCS) { + try { + return GeodeticCRS::create(props, datum, datumEnsemble, + NN_NO_CHECK(sphericalCS)); + } catch (const util::Exception &e) { + throw ParsingException(std::string("buildGeodeticCRS: ") + + e.what()); + } + } + + throw ParsingException( + concat("unhandled CS type: ", cs->getWKT2Type(true))); +} + +// --------------------------------------------------------------------------- + +CRSNNPtr WKTParser::Private::buildDerivedGeodeticCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseGeodCRSNode = nodeP->lookForChild(WKTConstants::BASEGEODCRS, + WKTConstants::BASEGEOGCRS); + // given the constraints enforced on calling code path + assert(!isNull(baseGeodCRSNode)); + + auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode); + + auto &derivingConversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(derivingConversionNode)) { + ThrowMissing(WKTConstants::DERIVINGCONVERSION); + } + auto derivingConversion = buildConversion( + derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + if (isNull(csNode)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + + auto ellipsoidalCS = nn_dynamic_pointer_cast(cs); + if (ellipsoidalCS) { + return DerivedGeographicCRS::create(buildProperties(node), baseGeodCRS, + derivingConversion, + NN_NO_CHECK(ellipsoidalCS)); + } else if (ci_equal(nodeP->value(), WKTConstants::GEOGCRS)) { + // This is a WKT2-2018 GeographicCRS. An ellipsoidal CS is expected + throw ParsingException(concat("ellipsoidal CS expected, but found ", + cs->getWKT2Type(true))); + } + + auto cartesianCS = nn_dynamic_pointer_cast(cs); + if (cartesianCS) { + if (cartesianCS->axisList().size() != 3) { + throw ParsingException( + "Cartesian CS for a GeodeticCRS should have 3 axis"); + } + return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS, + derivingConversion, + NN_NO_CHECK(cartesianCS)); + } + + auto sphericalCS = nn_dynamic_pointer_cast(cs); + if (sphericalCS) { + return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS, + derivingConversion, + NN_NO_CHECK(sphericalCS)); + } + + throw ParsingException( + concat("unhandled CS type: ", cs->getWKT2Type(true))); +} + +// --------------------------------------------------------------------------- + +UnitOfMeasure WKTParser::Private::guessUnitForParameter( + const std::string ¶mName, const UnitOfMeasure &defaultLinearUnit, + 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) { + unit = UnitOfMeasure::SCALE_UNITY; + } else if (ci_find(paramName, "latitude") != std::string::npos || + ci_find(paramName, "longitude") != std::string::npos || + ci_find(paramName, "meridian") != std::string::npos || + ci_find(paramName, "parallel") != std::string::npos || + ci_find(paramName, "azimuth") != std::string::npos || + ci_find(paramName, "angle") != std::string::npos || + ci_find(paramName, "heading") != std::string::npos) { + unit = defaultAngularUnit; + } else if (ci_find(paramName, "easting") != std::string::npos || + ci_find(paramName, "northing") != std::string::npos || + ci_find(paramName, "height") != std::string::npos) { + unit = defaultLinearUnit; + } + return unit; +} + +// --------------------------------------------------------------------------- + +void WKTParser::Private::consumeParameters( + const WKTNodeNNPtr &node, bool isAbridged, + std::vector ¶meters, + std::vector &values, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit) { + for (const auto &childNode : node->GP()->children()) { + const auto &childNodeChildren = childNode->GP()->children(); + if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { + if (childNodeChildren.size() < 2) { + ThrowNotEnoughChildren(childNode->GP()->value()); + } + parameters.push_back( + OperationParameter::create(buildProperties(childNode))); + const auto ¶mValue = childNodeChildren[1]->GP()->value(); + if (!paramValue.empty() && paramValue[0] == '"') { + values.push_back( + ParameterValue::create(stripQuotes(childNodeChildren[1]))); + } else { + try { + double val = asDouble(childNodeChildren[1]); + auto unit = buildUnitInSubNode(childNode); + if (unit == UnitOfMeasure::NONE) { + const auto ¶mName = + childNodeChildren[0]->GP()->value(); + unit = guessUnitForParameter( + paramName, defaultLinearUnit, defaultAngularUnit); + } + + if (isAbridged) { + const auto ¶mName = parameters.back()->nameStr(); + int paramEPSGCode = 0; + const auto ¶mIds = parameters.back()->identifiers(); + if (paramIds.size() == 1 && + ci_equal(*(paramIds[0]->codeSpace()), + Identifier::EPSG)) { + paramEPSGCode = ::atoi(paramIds[0]->code().c_str()); + } + const common::UnitOfMeasure *pUnit = nullptr; + if (OperationParameterValue::convertFromAbridged( + paramName, val, pUnit, paramEPSGCode)) { + unit = *pUnit; + parameters.back() = OperationParameter::create( + buildProperties(childNode) + .set(Identifier::CODESPACE_KEY, + Identifier::EPSG) + .set(Identifier::CODE_KEY, paramEPSGCode)); + } + } + + values.push_back( + ParameterValue::create(Measure(val, unit))); + } catch (const std::exception &) { + throw ParsingException(concat( + "unhandled parameter value type : ", paramValue)); + } + } + } else if (ci_equal(childNode->GP()->value(), + WKTConstants::PARAMETERFILE)) { + if (childNodeChildren.size() < 2) { + ThrowNotEnoughChildren(childNode->GP()->value()); + } + parameters.push_back( + OperationParameter::create(buildProperties(childNode))); + values.push_back(ParameterValue::createFilename( + stripQuotes(childNodeChildren[1]))); + } + } +} + +// --------------------------------------------------------------------------- + +ConversionNNPtr +WKTParser::Private::buildConversion(const WKTNodeNNPtr &node, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit) { + auto &methodNode = node->GP()->lookForChild(WKTConstants::METHOD, + WKTConstants::PROJECTION); + if (isNull(methodNode)) { + ThrowMissing(WKTConstants::METHOD); + } + if (methodNode->GP()->childrenSize() == 0) { + ThrowNotEnoughChildren(WKTConstants::METHOD); + } + + std::vector parameters; + std::vector values; + consumeParameters(node, false, parameters, values, defaultLinearUnit, + defaultAngularUnit); + + return Conversion::create(buildProperties(node), + buildProperties(methodNode), parameters, values); +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr +WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD); + if (isNull(methodNode)) { + ThrowMissing(WKTConstants::METHOD); + } + if (methodNode->GP()->childrenSize() == 0) { + ThrowNotEnoughChildren(WKTConstants::METHOD); + } + + auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS); + if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) { + ThrowMissing(WKTConstants::SOURCECRS); + } + auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]); + if (!sourceCRS) { + throw ParsingException("Invalid content in SOURCECRS node"); + } + + auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS); + if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) { + ThrowMissing(WKTConstants::TARGETCRS); + } + auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]); + if (!targetCRS) { + throw ParsingException("Invalid content in TARGETCRS node"); + } + + auto &interpolationCRSNode = + nodeP->lookForChild(WKTConstants::INTERPOLATIONCRS); + CRSPtr interpolationCRS; + if (/*!isNull(interpolationCRSNode) && */ interpolationCRSNode->GP() + ->childrenSize() == 1) { + interpolationCRS = buildCRS(interpolationCRSNode->GP()->children()[0]); + } + + std::vector parameters; + std::vector values; + auto defaultLinearUnit = UnitOfMeasure::NONE; + auto defaultAngularUnit = UnitOfMeasure::NONE; + consumeParameters(node, false, parameters, values, defaultLinearUnit, + defaultAngularUnit); + + std::vector accuracies; + auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY); + if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) { + accuracies.push_back(PositionalAccuracy::create( + stripQuotes(accuracyNode->GP()->children()[0]))); + } + + return util::nn_static_pointer_cast( + Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS), + NN_NO_CHECK(targetCRS), interpolationCRS, + buildProperties(methodNode), parameters, values, + accuracies)); +} + +// --------------------------------------------------------------------------- + +ConcatenatedOperationNNPtr +WKTParser::Private::buildConcatenatedOperation(const WKTNodeNNPtr &node) { + std::vector operations; + for (const auto &childNode : node->GP()->children()) { + if (ci_equal(childNode->GP()->value(), WKTConstants::STEP)) { + if (childNode->GP()->childrenSize() != 1) { + throw ParsingException("Invalid content in STEP node"); + } + auto op = nn_dynamic_pointer_cast( + build(childNode->GP()->children()[0])); + if (!op) { + throw ParsingException("Invalid content in STEP node"); + } + operations.emplace_back(NN_NO_CHECK(op)); + } + } + try { + return ConcatenatedOperation::create( + buildProperties(node), operations, + std::vector()); + } catch (const InvalidOperation &e) { + throw ParsingException( + std::string("Cannot build concatenated operation: ") + e.what()); + } +} + +// --------------------------------------------------------------------------- + +bool WKTParser::Private::hasWebMercPROJ4String( + const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode) { + if (projectionNode->GP()->childrenSize() == 0) { + ThrowNotEnoughChildren(WKTConstants::PROJECTION); + } + const std::string wkt1ProjectionName = + stripQuotes(projectionNode->GP()->children()[0]); + + auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION); + + if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(), + "Mercator_1SP") && + projCRSNode->countChildrenOfName("center_latitude") == 0) { + + // Hack to detect the hacky way of encodign webmerc in GDAL WKT1 + // with a EXTENSION["PROJ4", "+proj=merc +a=6378137 +b=6378137 + // +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m + // +nadgrids=@null +wktext +no_defs"] node + if (extensionNode && extensionNode->GP()->childrenSize() == 2 && + ci_equal(stripQuotes(extensionNode->GP()->children()[0]), + "PROJ4")) { + std::string projString = + stripQuotes(extensionNode->GP()->children()[1]); + if (projString.find("+proj=merc") != std::string::npos && + projString.find("+a=6378137") != std::string::npos && + projString.find("+b=6378137") != std::string::npos && + projString.find("+lon_0=0") != std::string::npos && + projString.find("+x_0=0") != std::string::npos && + projString.find("+y_0=0") != std::string::npos && + projString.find("+nadgrids=@null") != std::string::npos && + (projString.find("+lat_ts=") == std::string::npos || + projString.find("+lat_ts=0") != std::string::npos) && + (projString.find("+k=") == std::string::npos || + projString.find("+k=1") != std::string::npos) && + (projString.find("+units=") == std::string::npos || + projString.find("+units=m") != std::string::npos)) { + return true; + } + } + } + return false; +} + +// --------------------------------------------------------------------------- + +ConversionNNPtr WKTParser::Private::buildProjectionFromESRI( + const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit) { + const std::string esriProjectionName = + stripQuotes(projectionNode->GP()->children()[0]); + + // Lambert_Conformal_Conic or Krovak may map to different WKT2 methods + // depending + // on the parameters / their values + const auto esriMappings = getMappingsFromESRI(esriProjectionName); + if (esriMappings.empty()) { + return buildProjectionStandard(projCRSNode, projectionNode, + defaultLinearUnit, defaultAngularUnit); + } + + struct ci_less_struct { + bool operator()(const std::string &lhs, const std::string &rhs) const + noexcept { + return ci_less(lhs, rhs); + } + }; + + // Build a map of present parameters + std::map mapParamNameToValue; + for (const auto &childNode : projCRSNode->GP()->children()) { + if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { + const auto &childNodeChildren = childNode->GP()->children(); + if (childNodeChildren.size() < 2) { + ThrowNotEnoughChildren(WKTConstants::PARAMETER); + } + const std::string parameterName(stripQuotes(childNodeChildren[0])); + const auto ¶mValue = childNodeChildren[1]->GP()->value(); + mapParamNameToValue[parameterName] = paramValue; + } + } + + // Compare parameters present with the ones expected in the mapping + const ESRIMethodMapping *esriMapping = esriMappings[0]; + int bestMatchCount = -1; + for (const auto &mapping : esriMappings) { + int matchCount = 0; + for (const auto *param = mapping->params; param->esri_name; ++param) { + auto iter = mapParamNameToValue.find(param->esri_name); + if (iter != mapParamNameToValue.end()) { + if (param->wkt2_name == nullptr) { + try { + if (param->fixed_value == io::asDouble(iter->second)) { + matchCount++; + } + } catch (const std::exception &) { + } + } else { + matchCount++; + } + } + } + if (matchCount > bestMatchCount) { + esriMapping = mapping; + bestMatchCount = matchCount; + } + } + + std::map mapWKT2NameToESRIName; + for (const auto *param = esriMapping->params; param->esri_name; ++param) { + if (param->wkt2_name) { + mapWKT2NameToESRIName[param->wkt2_name] = param->esri_name; + } + } + + const auto *wkt2_mapping = getMapping(esriMapping->wkt2_name); + assert(wkt2_mapping); + if (ci_equal(esriProjectionName, "Stereographic")) { + try { + if (std::fabs(io::asDouble( + mapParamNameToValue["Latitude_Of_Origin"])) == 90.0) { + wkt2_mapping = + getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A); + } + } catch (const std::exception &) { + } + } + + PropertyMap propertiesMethod; + propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt2_mapping->wkt2_name); + if (wkt2_mapping->epsg_code != 0) { + propertiesMethod.set(Identifier::CODE_KEY, wkt2_mapping->epsg_code); + propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG); + } + + std::vector parameters; + std::vector values; + + if (wkt2_mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && + ci_equal(esriProjectionName, "Plate_Carree")) { + // Add a fixed Latitude of 1st parallel = 0 so as to have all + // parameters expected by Equidistant Cylindrical. + mapWKT2NameToESRIName[EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL] = + "Standard_Parallel_1"; + mapParamNameToValue["Standard_Parallel_1"] = "0"; + } else if ((wkt2_mapping->epsg_code == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A || + wkt2_mapping->epsg_code == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) && + !ci_equal(esriProjectionName, + "Rectified_Skew_Orthomorphic_Natural_Origin") && + !ci_equal(esriProjectionName, + "Rectified_Skew_Orthomorphic_Center")) { + // ESRI WKT lacks the angle to skew grid + // Take it from the azimuth value + mapWKT2NameToESRIName + [EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID] = "Azimuth"; + } + + for (int i = 0; wkt2_mapping->params[i] != nullptr; i++) { + const auto *paramMapping = wkt2_mapping->params[i]; + + auto iter = mapWKT2NameToESRIName.find(paramMapping->wkt2_name); + if (iter == mapWKT2NameToESRIName.end()) { + continue; + } + const auto &esriParamName = iter->second; + auto iter2 = mapParamNameToValue.find(esriParamName); + auto mapParamNameToValueEnd = mapParamNameToValue.end(); + if (iter2 == mapParamNameToValueEnd) { + // In case we don't find a direct match, try the aliases + for (iter2 = mapParamNameToValue.begin(); + iter2 != mapParamNameToValueEnd; ++iter2) { + if (areEquivalentParameters(iter2->first, esriParamName)) { + break; + } + } + if (iter2 == mapParamNameToValueEnd) { + continue; + } + } + + PropertyMap propertiesParameter; + propertiesParameter.set(IdentifiedObject::NAME_KEY, + paramMapping->wkt2_name); + if (paramMapping->epsg_code != 0) { + propertiesParameter.set(Identifier::CODE_KEY, + paramMapping->epsg_code); + propertiesParameter.set(Identifier::CODESPACE_KEY, + Identifier::EPSG); + } + parameters.push_back(OperationParameter::create(propertiesParameter)); + + try { + double val = io::asDouble(iter2->second); + auto unit = guessUnitForParameter( + paramMapping->wkt2_name, defaultLinearUnit, defaultAngularUnit); + values.push_back(ParameterValue::create(Measure(val, unit))); + } catch (const std::exception &) { + throw ParsingException( + concat("unhandled parameter value type : ", iter2->second)); + } + } + + return Conversion::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + propertiesMethod, parameters, values) + ->identify(); +} + +// --------------------------------------------------------------------------- + +ConversionNNPtr +WKTParser::Private::buildProjection(const WKTNodeNNPtr &projCRSNode, + const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit) { + if (projectionNode->GP()->childrenSize() == 0) { + ThrowNotEnoughChildren(WKTConstants::PROJECTION); + } + if (esriStyle_) { + return buildProjectionFromESRI(projCRSNode, projectionNode, + defaultLinearUnit, defaultAngularUnit); + } + return buildProjectionStandard(projCRSNode, projectionNode, + defaultLinearUnit, defaultAngularUnit); +} + +// --------------------------------------------------------------------------- + +std::string +WKTParser::Private::projectionGetParameter(const WKTNodeNNPtr &projCRSNode, + const char *paramName) { + for (const auto &childNode : projCRSNode->GP()->children()) { + if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { + const auto &childNodeChildren = childNode->GP()->children(); + if (childNodeChildren.size() == 2 && + metadata::Identifier::isEquivalentName( + stripQuotes(childNodeChildren[0]).c_str(), paramName)) { + return childNodeChildren[1]->GP()->value(); + } + } + } + return std::string(); +} + +// --------------------------------------------------------------------------- + +ConversionNNPtr WKTParser::Private::buildProjectionStandard( + const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit) { + std::string wkt1ProjectionName = + stripQuotes(projectionNode->GP()->children()[0]); + + std::vector parameters; + std::vector values; + bool tryToIdentifyWKT1Method = true; + + auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION); + const auto &extensionChildren = extensionNode->GP()->children(); + + bool gdal_3026_hack = false; + if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(), + "Mercator_1SP") && + projectionGetParameter(projCRSNode, "center_latitude").empty()) { + + // Hack for https://trac.osgeo.org/gdal/ticket/3026 + std::string lat0( + projectionGetParameter(projCRSNode, "latitude_of_origin")); + if (!lat0.empty() && lat0 != "0" && lat0 != "0.0") { + wkt1ProjectionName = "Mercator_2SP"; + gdal_3026_hack = true; + } else { + // The latitude of origin, which should always be zero, is + // missing + // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP + // definition, + // so add it manually. + PropertyMap propertiesParameter; + propertiesParameter.set(IdentifiedObject::NAME_KEY, + "Latitude of natural origin"); + propertiesParameter.set(Identifier::CODE_KEY, 8801); + propertiesParameter.set(Identifier::CODESPACE_KEY, + Identifier::EPSG); + parameters.push_back( + OperationParameter::create(propertiesParameter)); + values.push_back( + ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE))); + } + + } else if (metadata::Identifier::isEquivalentName( + wkt1ProjectionName.c_str(), "Polar_Stereographic")) { + std::map mapParameters; + for (const auto &childNode : projCRSNode->GP()->children()) { + const auto &childNodeChildren = childNode->GP()->children(); + if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) && + childNodeChildren.size() == 2) { + const std::string wkt1ParameterName( + stripQuotes(childNodeChildren[0])); + try { + double val = asDouble(childNodeChildren[1]); + auto unit = guessUnitForParameter(wkt1ParameterName, + defaultLinearUnit, + defaultAngularUnit); + mapParameters.insert(std::pair( + tolower(wkt1ParameterName), Measure(val, unit))); + } catch (const std::exception &) { + } + } + } + + Measure latitudeOfOrigin = mapParameters["latitude_of_origin"]; + Measure centralMeridian = mapParameters["central_meridian"]; + Measure scaleFactorFromMap = mapParameters["scale_factor"]; + Measure scaleFactor((scaleFactorFromMap.unit() == UnitOfMeasure::NONE) + ? Measure(1.0, UnitOfMeasure::SCALE_UNITY) + : scaleFactorFromMap); + Measure falseEasting = mapParameters["false_easting"]; + Measure falseNorthing = mapParameters["false_northing"]; + if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE && + scaleFactor.getSIValue() == 1.0) { + return Conversion::createPolarStereographicVariantB( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()), + Angle(centralMeridian.value(), centralMeridian.unit()), + Length(falseEasting.value(), falseEasting.unit()), + Length(falseNorthing.value(), falseNorthing.unit())); + } + + if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE && + std::fabs(std::fabs(latitudeOfOrigin.convertToUnit( + UnitOfMeasure::DEGREE)) - + 90.0) < 1e-10) { + return Conversion::createPolarStereographicVariantA( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()), + Angle(centralMeridian.value(), centralMeridian.unit()), + Scale(scaleFactor.value(), scaleFactor.unit()), + Length(falseEasting.value(), falseEasting.unit()), + Length(falseNorthing.value(), falseNorthing.unit())); + } + + tryToIdentifyWKT1Method = false; + // Import GDAL PROJ4 extension nodes + } else if (extensionChildren.size() == 2 && + ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) { + std::string projString = stripQuotes(extensionChildren[1]); + if (starts_with(projString, "+proj=")) { + try { + auto projObj = + PROJStringParser().createFromPROJString(projString); + auto projObjCrs = + nn_dynamic_pointer_cast(projObj); + if (projObjCrs) { + return projObjCrs->derivingConversion(); + } + } catch (const io::ParsingException &) { + } + } + } + + std::string projectionName(wkt1ProjectionName); + const MethodMapping *mapping = + tryToIdentifyWKT1Method ? getMappingFromWKT1(projectionName) : nullptr; + + // For Krovak, we need to look at axis to decide between the Krovak and + // Krovak East-North Oriented methods + if (ci_equal(projectionName, "Krovak") && + projCRSNode->countChildrenOfName(WKTConstants::AXIS) == 2 && + &buildAxis( + projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 0), + defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, + 1)->direction() == &AxisDirection::SOUTH && + &buildAxis( + projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 1), + defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, + 2)->direction() == &AxisDirection::WEST) { + mapping = getMapping(EPSG_CODE_METHOD_KROVAK); + } + + PropertyMap propertiesMethod; + if (mapping) { + projectionName = mapping->wkt2_name; + if (mapping->epsg_code != 0) { + propertiesMethod.set(Identifier::CODE_KEY, mapping->epsg_code); + propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG); + } + } + propertiesMethod.set(IdentifiedObject::NAME_KEY, projectionName); + + for (const auto &childNode : projCRSNode->GP()->children()) { + if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { + const auto &childNodeChildren = childNode->GP()->children(); + if (childNodeChildren.size() < 2) { + ThrowNotEnoughChildren(WKTConstants::PARAMETER); + } + const auto ¶mValue = childNodeChildren[1]->GP()->value(); + + PropertyMap propertiesParameter; + const std::string wkt1ParameterName( + stripQuotes(childNodeChildren[0])); + std::string parameterName(wkt1ParameterName); + if (gdal_3026_hack) { + if (ci_equal(parameterName, "latitude_of_origin")) { + parameterName = "standard_parallel_1"; + } else if (ci_equal(parameterName, "scale_factor") && + paramValue == "1") { + continue; + } + } + const auto *paramMapping = + mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr; + if (mapping && + mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && + ci_equal(parameterName, "latitude_of_origin")) { + parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN; + propertiesParameter.set( + Identifier::CODE_KEY, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); + propertiesParameter.set(Identifier::CODESPACE_KEY, + Identifier::EPSG); + } else if (paramMapping) { + parameterName = paramMapping->wkt2_name; + if (paramMapping->epsg_code != 0) { + propertiesParameter.set(Identifier::CODE_KEY, + paramMapping->epsg_code); + propertiesParameter.set(Identifier::CODESPACE_KEY, + Identifier::EPSG); + } + } + propertiesParameter.set(IdentifiedObject::NAME_KEY, parameterName); + parameters.push_back( + OperationParameter::create(propertiesParameter)); + try { + double val = io::asDouble(paramValue); + auto unit = guessUnitForParameter( + wkt1ParameterName, defaultLinearUnit, defaultAngularUnit); + values.push_back(ParameterValue::create(Measure(val, unit))); + } catch (const std::exception &) { + throw ParsingException( + concat("unhandled parameter value type : ", paramValue)); + } + } + } + + return Conversion::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + propertiesMethod, parameters, values) + ->identify(); +} + +// --------------------------------------------------------------------------- + +static ProjectedCRSNNPtr createPseudoMercator(const PropertyMap &props) { + auto conversion = Conversion::createPopularVisualisationPseudoMercator( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0), + Angle(0), Length(0), Length(0)); + return ProjectedCRS::create( + props, GeographicCRS::EPSG_4326, conversion, + CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); +} + +// --------------------------------------------------------------------------- + +ProjectedCRSNNPtr +WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) { + + const auto *nodeP = node->GP(); + auto &conversionNode = nodeP->lookForChild(WKTConstants::CONVERSION); + auto &projectionNode = nodeP->lookForChild(WKTConstants::PROJECTION); + if (isNull(conversionNode) && isNull(projectionNode)) { + ThrowMissing(WKTConstants::CONVERSION); + } + + auto &baseGeodCRSNode = + nodeP->lookForChild(WKTConstants::BASEGEODCRS, + WKTConstants::BASEGEOGCRS, WKTConstants::GEOGCS); + if (isNull(baseGeodCRSNode)) { + throw ParsingException( + "Missing BASEGEODCRS / BASEGEOGCRS / GEOGCS node"); + } + auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode); + + auto props = buildProperties(node); + + const std::string projCRSName = stripQuotes(nodeP->children()[0]); + if (esriStyle_ && dbContext_) { + // It is likely that the ESRI definition of EPSG:32661 (UPS North) & + // EPSG:32761 (UPS South) uses the easting-northing order, instead + // of the EPSG northing-easting order + // so don't substitue names to avoid confusion. + if (projCRSName == "UPS_North") { + props.set(IdentifiedObject::NAME_KEY, "WGS 84 / UPS North (E,N)"); + } else if (projCRSName == "UPS_South") { + props.set(IdentifiedObject::NAME_KEY, "WGS 84 / UPS South (E,N)"); + } else { + std::string outTableName; + std::string authNameFromAlias; + std::string codeFromAlias; + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto officialName = authFactory->getOfficialNameFromAlias( + projCRSName, "projected_crs", "ESRI", false, outTableName, + authNameFromAlias, codeFromAlias); + if (!officialName.empty()) { + props.set(IdentifiedObject::NAME_KEY, officialName); + } + } + } + + if (isNull(conversionNode) && hasWebMercPROJ4String(node, projectionNode)) { + toWGS84Parameters_.clear(); + return createPseudoMercator(props); + } + + // WGS_84_Pseudo_Mercator: Particular case for corrupted ESRI WKT generated + // by older GDAL versions + // https://trac.osgeo.org/gdal/changeset/30732 + // WGS_1984_Web_Mercator: deprecated ESRI:102113 + if (metadata::Identifier::isEquivalentName(projCRSName.c_str(), + "WGS_84_Pseudo_Mercator") || + metadata::Identifier::isEquivalentName(projCRSName.c_str(), + "WGS_1984_Web_Mercator")) { + toWGS84Parameters_.clear(); + return createPseudoMercator(props); + } + + auto linearUnit = buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR); + auto angularUnit = baseGeodCRS->coordinateSystem()->axisList()[0]->unit(); + + auto conversion = + !isNull(conversionNode) + ? buildConversion(conversionNode, linearUnit, angularUnit) + : buildProjection(node, projectionNode, linearUnit, angularUnit); + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + const auto &nodeValue = nodeP->value(); + if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) && + !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + auto cartesianCS = nn_dynamic_pointer_cast(cs); + + // No explicit AXIS node ? (WKT1) + if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) { + props.set("IMPLICIT_CS", true); + } + + if (isNull(csNode) && node->countChildrenOfName(WKTConstants::AXIS) == 0) { + + const auto methodCode = conversion->method()->getEPSGCode(); + // Krovak south oriented ? + if (methodCode == EPSG_CODE_METHOD_KROVAK) { + cartesianCS = + CartesianCS::create( + PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Southing), + emptyString, AxisDirection::SOUTH, linearUnit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Westing), + emptyString, AxisDirection::WEST, linearUnit)) + .as_nullable(); + } else if (methodCode == + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A || + methodCode == + EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) { + // It is likely that the ESRI definition of EPSG:32661 (UPS North) & + // EPSG:32761 (UPS South) uses the easting-northing order, instead + // of the EPSG northing-easting order. + // Same for WKT1_GDAL + const double lat0 = conversion->parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + common::UnitOfMeasure::DEGREE); + if (std::fabs(lat0 - 90) < 1e-10) { + cartesianCS = + CartesianCS::createNorthPoleEastingSouthNorthingSouth( + linearUnit) + .as_nullable(); + } else if (std::fabs(lat0 - -90) < 1e-10) { + cartesianCS = + CartesianCS::createSouthPoleEastingNorthNorthingNorth( + linearUnit) + .as_nullable(); + } + } else if (methodCode == + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) { + const double lat_ts = conversion->parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, + common::UnitOfMeasure::DEGREE); + if (lat_ts > 0) { + cartesianCS = + CartesianCS::createNorthPoleEastingSouthNorthingSouth( + linearUnit) + .as_nullable(); + } else if (lat_ts < 0) { + cartesianCS = + CartesianCS::createSouthPoleEastingNorthNorthingNorth( + linearUnit) + .as_nullable(); + } + } else if (methodCode == + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) { + cartesianCS = + CartesianCS::createWestingSouthing(linearUnit).as_nullable(); + } + } + if (!cartesianCS) { + ThrowNotExpectedCSType("Cartesian"); + } + + addExtensionProj4ToProp(nodeP, props); + + return ProjectedCRS::create(props, baseGeodCRS, conversion, + NN_NO_CHECK(cartesianCS)); +} + +// --------------------------------------------------------------------------- + +void WKTParser::Private::parseDynamic(const WKTNodeNNPtr &dynamicNode, + double &frameReferenceEpoch, + util::optional &modelName) { + auto &frameEpochNode = dynamicNode->lookForChild(WKTConstants::FRAMEEPOCH); + const auto &frameEpochChildren = frameEpochNode->GP()->children(); + if (frameEpochChildren.empty()) { + ThrowMissing(WKTConstants::FRAMEEPOCH); + } + try { + frameReferenceEpoch = asDouble(frameEpochChildren[0]); + } catch (const std::exception &) { + throw ParsingException("Invalid FRAMEEPOCH node"); + } + auto &modelNode = dynamicNode->GP()->lookForChild( + WKTConstants::MODEL, WKTConstants::VELOCITYGRID); + const auto &modelChildren = modelNode->GP()->children(); + if (modelChildren.size() == 1) { + modelName = stripQuotes(modelChildren[0]); + } +} + +// --------------------------------------------------------------------------- + +VerticalReferenceFrameNNPtr WKTParser::Private::buildVerticalReferenceFrame( + const WKTNodeNNPtr &node, const WKTNodeNNPtr &dynamicNode) { + + if (!isNull(dynamicNode)) { + double frameReferenceEpoch = 0.0; + util::optional modelName; + parseDynamic(dynamicNode, frameReferenceEpoch, modelName); + return DynamicVerticalReferenceFrame::create( + buildProperties(node), getAnchor(node), + optional(), + common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR), + modelName); + } + + // WKT1 VERT_DATUM has a datum type after the datum name that we ignore. + return VerticalReferenceFrame::create(buildProperties(node), + getAnchor(node)); +} + +// --------------------------------------------------------------------------- + +TemporalDatumNNPtr +WKTParser::Private::buildTemporalDatum(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &calendarNode = nodeP->lookForChild(WKTConstants::CALENDAR); + std::string calendar = TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN; + const auto &calendarChildren = calendarNode->GP()->children(); + if (calendarChildren.size() == 1) { + calendar = stripQuotes(calendarChildren[0]); + } + + auto &timeOriginNode = nodeP->lookForChild(WKTConstants::TIMEORIGIN); + std::string originStr; + const auto &timeOriginNodeChildren = timeOriginNode->GP()->children(); + if (timeOriginNodeChildren.size() == 1) { + originStr = stripQuotes(timeOriginNodeChildren[0]); + } + auto origin = DateTime::create(originStr); + return TemporalDatum::create(buildProperties(node), origin, calendar); +} + +// --------------------------------------------------------------------------- + +EngineeringDatumNNPtr +WKTParser::Private::buildEngineeringDatum(const WKTNodeNNPtr &node) { + return EngineeringDatum::create(buildProperties(node), getAnchor(node)); +} + +// --------------------------------------------------------------------------- + +ParametricDatumNNPtr +WKTParser::Private::buildParametricDatum(const WKTNodeNNPtr &node) { + return ParametricDatum::create(buildProperties(node), getAnchor(node)); +} + +// --------------------------------------------------------------------------- + +CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &datumNode = + nodeP->lookForChild(WKTConstants::VDATUM, WKTConstants::VERT_DATUM, + WKTConstants::VERTICALDATUM, WKTConstants::VRF); + auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE); + if (isNull(datumNode) && isNull(ensembleNode)) { + throw ParsingException("Missing VDATUM or ENSEMBLE node"); + } + + auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC); + auto datum = + !isNull(datumNode) + ? buildVerticalReferenceFrame(datumNode, dynamicNode).as_nullable() + : nullptr; + auto datumEnsemble = + !isNull(ensembleNode) + ? buildDatumEnsemble(ensembleNode, nullptr, false).as_nullable() + : nullptr; + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + const auto &nodeValue = nodeP->value(); + if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::VERT_CS) && + !ci_equal(nodeValue, WKTConstants::BASEVERTCRS)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + auto verticalCS = nn_dynamic_pointer_cast(cs); + if (!verticalCS) { + ThrowNotExpectedCSType("vertical"); + } + + auto crs = nn_static_pointer_cast(VerticalCRS::create( + buildProperties(node), datum, datumEnsemble, NN_NO_CHECK(verticalCS))); + + if (!isNull(datumNode)) { + auto &extensionNode = datumNode->lookForChild(WKTConstants::EXTENSION); + const auto &extensionChildren = extensionNode->GP()->children(); + if (extensionChildren.size() == 2) { + if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) { + std::string transformationName(crs->nameStr()); + if (!ends_with(transformationName, " height")) { + transformationName += " height"; + } + transformationName += " to WGS84 ellipsoidal height"; + auto transformation = + Transformation::createGravityRelatedHeightToGeographic3D( + PropertyMap().set(IdentifiedObject::NAME_KEY, + transformationName), + crs, GeographicCRS::EPSG_4979, + stripQuotes(extensionChildren[1]), + std::vector()); + return nn_static_pointer_cast(BoundCRS::create( + crs, GeographicCRS::EPSG_4979, transformation)); + } + } + } + + return crs; +} + +// --------------------------------------------------------------------------- + +DerivedVerticalCRSNNPtr +WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseVertCRSNode = nodeP->lookForChild(WKTConstants::BASEVERTCRS); + // given the constraints enforced on calling code path + assert(!isNull(baseVertCRSNode)); + + auto baseVertCRS_tmp = buildVerticalCRS(baseVertCRSNode); + auto baseVertCRS = NN_NO_CHECK(baseVertCRS_tmp->extractVerticalCRS()); + + auto &derivingConversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(derivingConversionNode)) { + ThrowMissing(WKTConstants::DERIVINGCONVERSION); + } + auto derivingConversion = buildConversion( + derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + if (isNull(csNode)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + + auto verticalCS = nn_dynamic_pointer_cast(cs); + if (!verticalCS) { + throw ParsingException( + concat("vertical CS expected, but found ", cs->getWKT2Type(true))); + } + + return DerivedVerticalCRS::create(buildProperties(node), baseVertCRS, + derivingConversion, + NN_NO_CHECK(verticalCS)); +} + +// --------------------------------------------------------------------------- + +CompoundCRSNNPtr +WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) { + std::vector components; + for (const auto &child : node->GP()->children()) { + auto crs = buildCRS(child); + if (crs) { + components.push_back(NN_NO_CHECK(crs)); + } + } + return CompoundCRS::create(buildProperties(node), components); +} + +// --------------------------------------------------------------------------- + +BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &abridgedNode = + nodeP->lookForChild(WKTConstants::ABRIDGEDTRANSFORMATION); + if (isNull(abridgedNode)) { + ThrowNotEnoughChildren(WKTConstants::ABRIDGEDTRANSFORMATION); + } + + auto &methodNode = abridgedNode->GP()->lookForChild(WKTConstants::METHOD); + if (isNull(methodNode)) { + ThrowMissing(WKTConstants::METHOD); + } + if (methodNode->GP()->childrenSize() == 0) { + ThrowNotEnoughChildren(WKTConstants::METHOD); + } + + auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS); + const auto &sourceCRSNodeChildren = sourceCRSNode->GP()->children(); + if (sourceCRSNodeChildren.size() != 1) { + ThrowNotEnoughChildren(WKTConstants::SOURCECRS); + } + auto sourceCRS = buildCRS(sourceCRSNodeChildren[0]); + if (!sourceCRS) { + throw ParsingException("Invalid content in SOURCECRS node"); + } + + auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS); + const auto &targetCRSNodeChildren = targetCRSNode->GP()->children(); + if (targetCRSNodeChildren.size() != 1) { + ThrowNotEnoughChildren(WKTConstants::TARGETCRS); + } + auto targetCRS = buildCRS(targetCRSNodeChildren[0]); + if (!targetCRS) { + throw ParsingException("Invalid content in TARGETCRS node"); + } + + std::vector parameters; + std::vector values; + auto defaultLinearUnit = UnitOfMeasure::NONE; + auto defaultAngularUnit = UnitOfMeasure::NONE; + consumeParameters(abridgedNode, true, parameters, values, defaultLinearUnit, + defaultAngularUnit); + + CRSPtr sourceTransformationCRS; + if (dynamic_cast(targetCRS.get())) { + sourceTransformationCRS = sourceCRS->extractGeographicCRS(); + if (!sourceTransformationCRS) { + throw ParsingException("Cannot find GeographicCRS in sourceCRS"); + } + } else { + sourceTransformationCRS = sourceCRS; + } + + auto transformation = Transformation::create( + buildProperties(abridgedNode), NN_NO_CHECK(sourceTransformationCRS), + NN_NO_CHECK(targetCRS), nullptr, buildProperties(methodNode), + parameters, values, std::vector()); + + return BoundCRS::create(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), + transformation); +} + +// --------------------------------------------------------------------------- + +TemporalCSNNPtr +WKTParser::Private::buildTemporalCS(const WKTNodeNNPtr &parentNode) { + + auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS); + if (isNull(csNode) && + !ci_equal(parentNode->GP()->value(), WKTConstants::BASETIMECRS)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE); + auto temporalCS = nn_dynamic_pointer_cast(cs); + if (!temporalCS) { + ThrowNotExpectedCSType("temporal"); + } + return NN_NO_CHECK(temporalCS); +} + +// --------------------------------------------------------------------------- + +TemporalCRSNNPtr +WKTParser::Private::buildTemporalCRS(const WKTNodeNNPtr &node) { + auto &datumNode = + node->GP()->lookForChild(WKTConstants::TDATUM, WKTConstants::TIMEDATUM); + if (isNull(datumNode)) { + throw ParsingException("Missing TDATUM / TIMEDATUM node"); + } + + return TemporalCRS::create(buildProperties(node), + buildTemporalDatum(datumNode), + buildTemporalCS(node)); +} + +// --------------------------------------------------------------------------- + +DerivedTemporalCRSNNPtr +WKTParser::Private::buildDerivedTemporalCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseCRSNode = nodeP->lookForChild(WKTConstants::BASETIMECRS); + // given the constraints enforced on calling code path + assert(!isNull(baseCRSNode)); + + auto &derivingConversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(derivingConversionNode)) { + ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); + } + + return DerivedTemporalCRS::create( + buildProperties(node), buildTemporalCRS(baseCRSNode), + buildConversion(derivingConversionNode, UnitOfMeasure::NONE, + UnitOfMeasure::NONE), + buildTemporalCS(node)); +} + +// --------------------------------------------------------------------------- + +EngineeringCRSNNPtr +WKTParser::Private::buildEngineeringCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &datumNode = nodeP->lookForChild(WKTConstants::EDATUM, + WKTConstants::ENGINEERINGDATUM); + if (isNull(datumNode)) { + throw ParsingException("Missing EDATUM / ENGINEERINGDATUM node"); + } + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::BASEENGCRS)) { + ThrowMissing(WKTConstants::CS); + } + + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + return EngineeringCRS::create(buildProperties(node), + buildEngineeringDatum(datumNode), cs); +} + +// --------------------------------------------------------------------------- + +EngineeringCRSNNPtr +WKTParser::Private::buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node) { + auto &datumNode = node->GP()->lookForChild(WKTConstants::LOCAL_DATUM); + auto cs = buildCS(null_node, node, UnitOfMeasure::NONE); + auto datum = EngineeringDatum::create( + !isNull(datumNode) + ? buildProperties(datumNode) + : + // In theory OGC 01-009 mandates LOCAL_DATUM, but GDAL has a + // tradition of emitting just LOCAL_CS["foo"] + emptyPropertyMap); + return EngineeringCRS::create(buildProperties(node), datum, cs); +} + +// --------------------------------------------------------------------------- + +DerivedEngineeringCRSNNPtr +WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseEngCRSNode = nodeP->lookForChild(WKTConstants::BASEENGCRS); + // given the constraints enforced on calling code path + assert(!isNull(baseEngCRSNode)); + + auto baseEngCRS = buildEngineeringCRS(baseEngCRSNode); + + auto &derivingConversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(derivingConversionNode)) { + ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); + } + auto derivingConversion = buildConversion( + derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + if (isNull(csNode)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + + return DerivedEngineeringCRS::create(buildProperties(node), baseEngCRS, + derivingConversion, cs); +} + +// --------------------------------------------------------------------------- + +ParametricCSNNPtr +WKTParser::Private::buildParametricCS(const WKTNodeNNPtr &parentNode) { + + auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS); + if (isNull(csNode) && + !ci_equal(parentNode->GP()->value(), WKTConstants::BASEPARAMCRS)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE); + auto parametricCS = nn_dynamic_pointer_cast(cs); + if (!parametricCS) { + ThrowNotExpectedCSType("parametric"); + } + return NN_NO_CHECK(parametricCS); +} + +// --------------------------------------------------------------------------- + +ParametricCRSNNPtr +WKTParser::Private::buildParametricCRS(const WKTNodeNNPtr &node) { + auto &datumNode = node->GP()->lookForChild(WKTConstants::PDATUM, + WKTConstants::PARAMETRICDATUM); + if (isNull(datumNode)) { + throw ParsingException("Missing PDATUM / PARAMETRICDATUM node"); + } + + return ParametricCRS::create(buildProperties(node), + buildParametricDatum(datumNode), + buildParametricCS(node)); +} + +// --------------------------------------------------------------------------- + +DerivedParametricCRSNNPtr +WKTParser::Private::buildDerivedParametricCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseParamCRSNode = nodeP->lookForChild(WKTConstants::BASEPARAMCRS); + // given the constraints enforced on calling code path + assert(!isNull(baseParamCRSNode)); + + auto &derivingConversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(derivingConversionNode)) { + ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); + } + + return DerivedParametricCRS::create( + buildProperties(node), buildParametricCRS(baseParamCRSNode), + buildConversion(derivingConversionNode, UnitOfMeasure::NONE, + UnitOfMeasure::NONE), + buildParametricCS(node)); +} + +// --------------------------------------------------------------------------- + +DerivedProjectedCRSNNPtr +WKTParser::Private::buildDerivedProjectedCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseProjCRSNode = nodeP->lookForChild(WKTConstants::BASEPROJCRS); + if (isNull(baseProjCRSNode)) { + ThrowNotEnoughChildren(WKTConstants::BASEPROJCRS); + } + auto baseProjCRS = buildProjectedCRS(baseProjCRSNode); + + auto &conversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(conversionNode)) { + ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); + } + + auto linearUnit = buildUnitInSubNode(node); + auto angularUnit = + baseProjCRS->baseCRS()->coordinateSystem()->axisList()[0]->unit(); + + auto conversion = buildConversion(conversionNode, linearUnit, angularUnit); + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::PROJCS)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + return DerivedProjectedCRS::create(buildProperties(node), baseProjCRS, + conversion, cs); +} + +// --------------------------------------------------------------------------- + +static bool isGeodeticCRS(const std::string &name) { + return ci_equal(name, WKTConstants::GEODCRS) || // WKT2 + ci_equal(name, WKTConstants::GEODETICCRS) || // WKT2 + ci_equal(name, WKTConstants::GEOGCRS) || // WKT2 2018 + ci_equal(name, WKTConstants::GEOGRAPHICCRS) || // WKT2 2018 + ci_equal(name, WKTConstants::GEOGCS) || // WKT1 + ci_equal(name, WKTConstants::GEOCCS); // WKT1 +} + +// --------------------------------------------------------------------------- + +CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + const std::string &name(nodeP->value()); + + if (isGeodeticCRS(name)) { + if (!isNull(nodeP->lookForChild(WKTConstants::BASEGEOGCRS, + WKTConstants::BASEGEODCRS))) { + return buildDerivedGeodeticCRS(node); + } else { + return util::nn_static_pointer_cast(buildGeodeticCRS(node)); + } + } + + if (ci_equal(name, WKTConstants::PROJCS) || + ci_equal(name, WKTConstants::PROJCRS) || + ci_equal(name, WKTConstants::PROJECTEDCRS)) { + return util::nn_static_pointer_cast(buildProjectedCRS(node)); + } + + if (ci_equal(name, WKTConstants::VERT_CS) || + ci_equal(name, WKTConstants::VERTCRS) || + ci_equal(name, WKTConstants::VERTICALCRS)) { + if (!isNull(nodeP->lookForChild(WKTConstants::BASEVERTCRS))) { + return util::nn_static_pointer_cast( + buildDerivedVerticalCRS(node)); + } else { + return util::nn_static_pointer_cast(buildVerticalCRS(node)); + } + } + + if (ci_equal(name, WKTConstants::COMPD_CS) || + ci_equal(name, WKTConstants::COMPOUNDCRS)) { + return util::nn_static_pointer_cast(buildCompoundCRS(node)); + } + + if (ci_equal(name, WKTConstants::BOUNDCRS)) { + return util::nn_static_pointer_cast(buildBoundCRS(node)); + } + + if (ci_equal(name, WKTConstants::TIMECRS)) { + if (!isNull(nodeP->lookForChild(WKTConstants::BASETIMECRS))) { + return util::nn_static_pointer_cast( + buildDerivedTemporalCRS(node)); + } else { + return util::nn_static_pointer_cast(buildTemporalCRS(node)); + } + } + + if (ci_equal(name, WKTConstants::DERIVEDPROJCRS)) { + return util::nn_static_pointer_cast( + buildDerivedProjectedCRS(node)); + } + + if (ci_equal(name, WKTConstants::ENGCRS) || + ci_equal(name, WKTConstants::ENGINEERINGCRS)) { + if (!isNull(nodeP->lookForChild(WKTConstants::BASEENGCRS))) { + return util::nn_static_pointer_cast( + buildDerivedEngineeringCRS(node)); + } else { + return util::nn_static_pointer_cast(buildEngineeringCRS(node)); + } + } + + if (ci_equal(name, WKTConstants::LOCAL_CS)) { + return util::nn_static_pointer_cast( + buildEngineeringCRSFromLocalCS(node)); + } + + if (ci_equal(name, WKTConstants::PARAMETRICCRS)) { + if (!isNull(nodeP->lookForChild(WKTConstants::BASEPARAMCRS))) { + return util::nn_static_pointer_cast( + buildDerivedParametricCRS(node)); + } else { + return util::nn_static_pointer_cast(buildParametricCRS(node)); + } + } + + return nullptr; +} + +// --------------------------------------------------------------------------- + +BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + const std::string &name(nodeP->value()); + + auto crs = buildCRS(node); + if (crs) { + if (!toWGS84Parameters_.empty()) { + return util::nn_static_pointer_cast( + BoundCRS::createFromTOWGS84(NN_NO_CHECK(crs), + toWGS84Parameters_)); + } + if (!datumPROJ4Grids_.empty()) { + return util::nn_static_pointer_cast( + BoundCRS::createFromNadgrids(NN_NO_CHECK(crs), + datumPROJ4Grids_)); + } + return util::nn_static_pointer_cast(NN_NO_CHECK(crs)); + } + + if (ci_equal(name, WKTConstants::DATUM) || + ci_equal(name, WKTConstants::GEODETICDATUM) || + ci_equal(name, WKTConstants::TRF)) { + return util::nn_static_pointer_cast( + buildGeodeticReferenceFrame(node, PrimeMeridian::GREENWICH, + null_node)); + } + + if (ci_equal(name, WKTConstants::ENSEMBLE)) { + return util::nn_static_pointer_cast(buildDatumEnsemble( + node, PrimeMeridian::GREENWICH, + !isNull(nodeP->lookForChild(WKTConstants::ELLIPSOID)))); + } + + if (ci_equal(name, WKTConstants::VDATUM) || + ci_equal(name, WKTConstants::VERT_DATUM) || + ci_equal(name, WKTConstants::VERTICALDATUM) || + ci_equal(name, WKTConstants::VRF)) { + return util::nn_static_pointer_cast( + buildVerticalReferenceFrame(node, null_node)); + } + + if (ci_equal(name, WKTConstants::TDATUM) || + ci_equal(name, WKTConstants::TIMEDATUM)) { + return util::nn_static_pointer_cast( + buildTemporalDatum(node)); + } + + if (ci_equal(name, WKTConstants::EDATUM) || + ci_equal(name, WKTConstants::ENGINEERINGDATUM)) { + return util::nn_static_pointer_cast( + buildEngineeringDatum(node)); + } + + if (ci_equal(name, WKTConstants::PDATUM) || + ci_equal(name, WKTConstants::PARAMETRICDATUM)) { + return util::nn_static_pointer_cast( + buildParametricDatum(node)); + } + + if (ci_equal(name, WKTConstants::ELLIPSOID) || + ci_equal(name, WKTConstants::SPHEROID)) { + return util::nn_static_pointer_cast(buildEllipsoid(node)); + } + + if (ci_equal(name, WKTConstants::COORDINATEOPERATION)) { + return util::nn_static_pointer_cast( + buildCoordinateOperation(node)); + } + + if (ci_equal(name, WKTConstants::CONVERSION)) { + return util::nn_static_pointer_cast( + buildConversion(node, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE)); + } + + if (ci_equal(name, WKTConstants::CONCATENATEDOPERATION)) { + return util::nn_static_pointer_cast( + buildConcatenatedOperation(node)); + } + + if (ci_equal(name, WKTConstants::ID) || + ci_equal(name, WKTConstants::AUTHORITY)) { + return util::nn_static_pointer_cast( + NN_NO_CHECK(buildId(node, false))); + } + + throw ParsingException(concat("unhandled keyword: ", name)); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a sub-class of BaseObject from a user specified text. + * + * The text can be a: + *
    + *
  • WKT string
  • + *
  • PROJ string
  • + *
  • database code, prefixed by its authoriy. e.g. "EPSG:4326"
  • + *
  • URN. e.g. "urn:ogc:def:crs:EPSG::4326", + * "urn:ogc:def:coordinateOperation:EPSG::1671"
  • + *
  • an objet name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as + * uniqueness is not guaranteed, the function may apply heuristics to + * determine the appropriate best match.
  • + *
+ * + * @param text One of the above mentionned text format + * @param dbContext Database context, or nullptr (in which case database + * lookups will not work) + * @param usePROJ4InitRules When set to true, + * init=epsg:XXXX syntax will be allowed and will be interpreted according to + * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude + * order and will expect/output coordinates in radians. ProjectedCRS will have + * easting, northing axis order (except the ones with Transverse Mercator South + * Orientated projection). In that mode, the epsg:XXXX syntax will be also + * interprated the same way. + * @throw ParsingException + */ +BaseObjectNNPtr createFromUserInput(const std::string &text, + const DatabaseContextPtr &dbContext, + bool usePROJ4InitRules) { + + for (const auto &wktConstants : WKTConstants::constants()) { + if (ci_starts_with(text, wktConstants)) { + return WKTParser() + .attachDatabaseContext(dbContext) + .setStrict(false) + .createFromWKT(text); + } + } + const char *textWithoutPlusPrefix = text.c_str(); + if (textWithoutPlusPrefix[0] == '+') + textWithoutPlusPrefix++; + + if (strncmp(textWithoutPlusPrefix, "proj=", strlen("proj=")) == 0 || + strncmp(textWithoutPlusPrefix, "init=", strlen("init=")) == 0 || + strncmp(textWithoutPlusPrefix, "title=", strlen("title=")) == 0) { + return PROJStringParser() + .attachDatabaseContext(dbContext) + .setUsePROJ4InitRules(usePROJ4InitRules) + .createFromPROJString(text); + } + + auto tokens = split(text, ':'); + if (tokens.size() == 2) { + if (!dbContext) { + throw ParsingException("no database context specified"); + } + DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext)); + const auto &authName = tokens[0]; + const auto &code = tokens[1]; + auto factory = AuthorityFactory::create(dbContextNNPtr, authName); + try { + return factory->createCoordinateReferenceSystem(code); + } catch (...) { + const auto authorities = dbContextNNPtr->getAuthorities(); + for (const auto &authCandidate : authorities) { + if (ci_equal(authCandidate, authName)) { + return AuthorityFactory::create(dbContextNNPtr, + authCandidate) + ->createCoordinateReferenceSystem(code); + } + } + throw; + } + } + + // urn:ogc:def:crs:EPSG::4326 + if (tokens.size() == 7) { + if (!dbContext) { + throw ParsingException("no database context specified"); + } + const auto &type = tokens[3]; + auto factory = + AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[4]); + const auto &code = tokens[6]; + if (type == "crs") { + return factory->createCoordinateReferenceSystem(code); + } + if (type == "coordinateOperation") { + return factory->createCoordinateOperation(code, true); + } + if (type == "datum") { + return factory->createDatum(code); + } + if (type == "ellipsoid") { + return factory->createEllipsoid(code); + } + if (type == "meridian") { + return factory->createPrimeMeridian(code); + } + throw ParsingException(concat("unhandled object type: ", type)); + } + + if (dbContext) { + auto factory = + AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string()); + // First pass: exact match on CRS objects + // Second pass: exact match on other objects + // Third pass: approximate match on CRS objects + // Fourth pass: approximate match on other objects + constexpr size_t limitResultCount = 10; + for (int pass = 0; pass <= 3; ++pass) { + const bool approximateMatch = (pass >= 2); + auto res = factory->createObjectsFromName( + text, + (pass == 0 || pass == 2) + ? std::vector< + AuthorityFactory::ObjectType>{AuthorityFactory:: + ObjectType::CRS} + : std::vector< + AuthorityFactory:: + ObjectType>{AuthorityFactory::ObjectType:: + ELLIPSOID, + AuthorityFactory::ObjectType::DATUM, + AuthorityFactory::ObjectType:: + COORDINATE_OPERATION}, + approximateMatch, limitResultCount); + if (res.size() == 1) { + return res.front(); + } + if (res.size() > 1) { + if (pass == 0 || pass == 2) { + for (size_t ndim = 2; ndim <= 3; ndim++) { + for (const auto &obj : res) { + auto crs = + dynamic_cast(obj.get()); + if (crs && + crs->coordinateSystem()->axisList().size() == + ndim) { + return obj; + } + } + } + } + + std::string msg("several objects matching this name: "); + bool first = true; + for (const auto &obj : res) { + if (msg.size() > 200) { + msg += ", ..."; + break; + } + if (!first) { + msg += ", "; + } + first = false; + msg += obj->nameStr(); + } + throw ParsingException(msg); + } + } + } + + throw ParsingException("unrecognized format / unknown name"); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a sub-class of BaseObject from a WKT string. + * + * By default, validation is strict (to the extent of the checks that are + * actually implemented. Currently only WKT1 strict grammar is checked), and + * any issue detected will cause an exception to be thrown, unless + * setStrict(false) is called priorly. + * + * In non-strict mode, non-fatal issues will be recovered and simply listed + * in warningList(). This does not prevent more severe errors to cause an + * exception to be thrown. + * + * @throw ParsingException + */ +BaseObjectNNPtr WKTParser::createFromWKT(const std::string &wkt) { + WKTNodeNNPtr root = WKTNode::createFrom(wkt); + auto obj = d->build(root); + + const auto dialect = guessDialect(wkt); + if (dialect == WKTGuessedDialect::WKT1_GDAL || + dialect == WKTGuessedDialect::WKT1_ESRI) { + auto errorMsg = pj_wkt1_parse(wkt); + if (!errorMsg.empty()) { + d->emitRecoverableAssertion(errorMsg); + } + } else if (dialect == WKTGuessedDialect::WKT2_2015 || + dialect == WKTGuessedDialect::WKT2_2018) { + auto errorMsg = pj_wkt2_parse(wkt); + if (!errorMsg.empty()) { + d->emitRecoverableAssertion(errorMsg); + } + } + + return obj; +} + +// --------------------------------------------------------------------------- + +/** \brief Attach a database context, to allow queries in it if needed. + */ +WKTParser & +WKTParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) { + d->dbContext_ = dbContext; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Guess the "dialect" of the WKT string. + */ +WKTParser::WKTGuessedDialect +WKTParser::guessDialect(const std::string &wkt) noexcept { + const std::string *const wkt1_keywords[] = { + &WKTConstants::GEOCCS, &WKTConstants::GEOGCS, &WKTConstants::COMPD_CS, + &WKTConstants::PROJCS, &WKTConstants::VERT_CS, &WKTConstants::LOCAL_CS}; + for (const auto &pointerKeyword : wkt1_keywords) { + if (ci_starts_with(wkt, *pointerKeyword)) { + + if (ci_find(wkt, "GEOGCS[\"GCS_") != std::string::npos) { + return WKTGuessedDialect::WKT1_ESRI; + } + + return WKTGuessedDialect::WKT1_GDAL; + } + } + + const std::string *const wkt2_2018_only_keywords[] = { + &WKTConstants::GEOGCRS, + // contained in previous one + // &WKTConstants::BASEGEOGCRS, + &WKTConstants::CONCATENATEDOPERATION, &WKTConstants::USAGE, + &WKTConstants::DYNAMIC, &WKTConstants::FRAMEEPOCH, &WKTConstants::MODEL, + &WKTConstants::VELOCITYGRID, &WKTConstants::ENSEMBLE, + &WKTConstants::DERIVEDPROJCRS, &WKTConstants::BASEPROJCRS, + &WKTConstants::GEOGRAPHICCRS, &WKTConstants::TRF, &WKTConstants::VRF}; + + for (const auto &pointerKeyword : wkt2_2018_only_keywords) { + auto pos = ci_find(wkt, *pointerKeyword); + if (pos != std::string::npos && + wkt[pos + pointerKeyword->size()] == '[') { + return WKTGuessedDialect::WKT2_2018; + } + } + static const char *const wkt2_2018_only_substrings[] = { + "CS[TemporalDateTime,", "CS[TemporalCount,", "CS[TemporalMeasure,", + }; + for (const auto &substrings : wkt2_2018_only_substrings) { + if (ci_find(wkt, substrings) != std::string::npos) { + return WKTGuessedDialect::WKT2_2018; + } + } + + for (const auto &wktConstants : WKTConstants::constants()) { + if (ci_starts_with(wkt, wktConstants)) { + return WKTGuessedDialect::WKT2_2015; + } + } + + return WKTGuessedDialect::NOT_WKT; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +FormattingException::FormattingException(const char *message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +FormattingException::FormattingException(const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +FormattingException::FormattingException(const FormattingException &) = default; + +// --------------------------------------------------------------------------- + +FormattingException::~FormattingException() = default; + +// --------------------------------------------------------------------------- + +void FormattingException::Throw(const char *msg) { + throw FormattingException(msg); +} + +// --------------------------------------------------------------------------- + +void FormattingException::Throw(const std::string &msg) { + throw FormattingException(msg); +} + +// --------------------------------------------------------------------------- + +ParsingException::ParsingException(const char *message) : Exception(message) {} + +// --------------------------------------------------------------------------- + +ParsingException::ParsingException(const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +ParsingException::ParsingException(const ParsingException &) = default; + +// --------------------------------------------------------------------------- + +ParsingException::~ParsingException() = default; + +// --------------------------------------------------------------------------- + +IPROJStringExportable::~IPROJStringExportable() = default; + +// --------------------------------------------------------------------------- + +std::string IPROJStringExportable::exportToPROJString( + PROJStringFormatter *formatter) const { + _exportToPROJString(formatter); + if (formatter->getAddNoDefs() && + formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4 && + dynamic_cast(this)) { + if (!formatter->hasParam("no_defs")) { + formatter->addParam("no_defs"); + } + } + return formatter->toString(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +struct Step { + std::string name{}; + bool isInit = false; + bool inverted{false}; + + struct KeyValue { + std::string key{}; + std::string value{}; + + explicit KeyValue(const std::string &keyIn) : key(keyIn) {} + + KeyValue(const char *keyIn, const std::string &valueIn); + + KeyValue(const std::string &keyIn, const std::string &valueIn) + : key(keyIn), value(valueIn) {} + + // cppcheck-suppress functionStatic + bool keyEquals(const char *otherKey) const noexcept { + return key == otherKey; + } + + // cppcheck-suppress functionStatic + bool equals(const char *otherKey, const char *otherVal) const noexcept { + return key == otherKey && value == otherVal; + } + + bool operator!=(const KeyValue &other) const noexcept { + return key != other.key || value != other.value; + } + }; + + std::vector paramValues{}; +}; + +Step::KeyValue::KeyValue(const char *keyIn, const std::string &valueIn) + : key(keyIn), value(valueIn) {} + +struct PROJStringFormatter::Private { + PROJStringFormatter::Convention convention_ = + PROJStringFormatter::Convention::PROJ_5; + std::vector toWGS84Parameters_{}; + std::string vDatumExtension_{}; + std::string hDatumExtension_{}; + + std::list steps_{}; + std::vector globalParamValues_{}; + + struct InversionStackElt { + std::list::iterator startIter{}; + bool iterValid = false; + bool currentInversionState = false; + }; + std::vector inversionStack_{InversionStackElt()}; + bool omitProjLongLatIfPossible_ = false; + bool omitZUnitConversion_ = false; + DatabaseContextPtr dbContext_{}; + bool useETMercForTMerc_ = false; + bool useETMercForTMercSet_ = false; + bool addNoDefs_ = true; + bool coordOperationOptimizations_ = false; + + std::string result_{}; + + // cppcheck-suppress functionStatic + void appendToResult(const char *str); + + // cppcheck-suppress functionStatic + void addStep(); +}; + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PROJStringFormatter::PROJStringFormatter(Convention conventionIn, + const DatabaseContextPtr &dbContext) + : d(internal::make_unique()) { + d->convention_ = conventionIn; + d->dbContext_ = dbContext; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PROJStringFormatter::~PROJStringFormatter() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Constructs a new formatter. + * + * A formatter can be used only once (its internal state is mutated) + * + * Its default behaviour can be adjusted with the different setters. + * + * @param conventionIn PROJ string flavor. Defaults to Convention::PROJ_5 + * @param dbContext Database context (can help to find alternative grid names). + * May be nullptr + * @return new formatter. + */ +PROJStringFormatterNNPtr +PROJStringFormatter::create(Convention conventionIn, + DatabaseContextPtr dbContext) { + return NN_NO_CHECK(PROJStringFormatter::make_unique( + conventionIn, dbContext)); +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether Extented Transverse Mercator (etmerc) should be used + * instead of tmerc */ +void PROJStringFormatter::setUseETMercForTMerc(bool flag) { + d->useETMercForTMerc_ = flag; + d->useETMercForTMercSet_ = true; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the PROJ string. */ +const std::string &PROJStringFormatter::toString() const { + + assert(d->inversionStack_.size() == 1); + + d->result_.clear(); + + for (auto iter = d->steps_.begin(); iter != d->steps_.end();) { + // Remove no-op helmert + auto &step = *iter; + const auto paramCount = step.paramValues.size(); + if (step.name == "helmert" && (paramCount == 3 || paramCount == 8) && + step.paramValues[0].equals("x", "0") && + step.paramValues[1].equals("y", "0") && + step.paramValues[2].equals("z", "0") && + (paramCount == 3 || + (step.paramValues[3].equals("rx", "0") && + step.paramValues[4].equals("ry", "0") && + step.paramValues[5].equals("rz", "0") && + step.paramValues[6].equals("s", "0") && + step.paramValues[7].keyEquals("convention")))) { + iter = d->steps_.erase(iter); + } else if (d->coordOperationOptimizations_ && + step.name == "unitconvert" && paramCount == 2 && + step.paramValues[0].keyEquals("xy_in") && + step.paramValues[1].keyEquals("xy_out") && + step.paramValues[0].value == step.paramValues[1].value) { + iter = d->steps_.erase(iter); + } else { + ++iter; + } + } + + for (auto &step : d->steps_) { + if (!step.inverted) { + continue; + } + + const auto paramCount = step.paramValues.size(); + + // axisswap order=2,1 is its own inverse + if (step.name == "axisswap" && paramCount == 1 && + step.paramValues[0].equals("order", "2,1")) { + step.inverted = false; + continue; + } + + // handle unitconvert inverse + if (step.name == "unitconvert" && paramCount == 2 && + step.paramValues[0].keyEquals("xy_in") && + step.paramValues[1].keyEquals("xy_out")) { + std::swap(step.paramValues[0].value, step.paramValues[1].value); + step.inverted = false; + continue; + } + + if (step.name == "unitconvert" && paramCount == 2 && + step.paramValues[0].keyEquals("z_in") && + step.paramValues[1].keyEquals("z_out")) { + std::swap(step.paramValues[0].value, step.paramValues[1].value); + step.inverted = false; + continue; + } + + if (step.name == "unitconvert" && paramCount == 4 && + step.paramValues[0].keyEquals("xy_in") && + step.paramValues[1].keyEquals("z_in") && + step.paramValues[2].keyEquals("xy_out") && + step.paramValues[3].keyEquals("z_out")) { + std::swap(step.paramValues[0].value, step.paramValues[2].value); + std::swap(step.paramValues[1].value, step.paramValues[3].value); + step.inverted = false; + continue; + } + } + + bool changeDone; + do { + changeDone = false; + auto iterPrev = d->steps_.begin(); + if (iterPrev == d->steps_.end()) { + break; + } + auto iterCur = iterPrev; + iterCur++; + for (size_t i = 1; i < d->steps_.size(); ++i, ++iterCur, ++iterPrev) { + + auto &prevStep = *iterPrev; + auto &curStep = *iterCur; + + const auto curStepParamCount = curStep.paramValues.size(); + const auto prevStepParamCount = prevStep.paramValues.size(); + + // longlat (or its inverse) with ellipsoid only is a no-op + // do that only for an internal step + if (i + 1 < d->steps_.size() && curStep.name == "longlat" && + curStepParamCount == 1 && + curStep.paramValues[0].keyEquals("ellps")) { + d->steps_.erase(iterCur); + changeDone = true; + break; + } + + // unitconvert (xy) followed by its inverse is a no-op + if (curStep.name == "unitconvert" && + prevStep.name == "unitconvert" && !curStep.inverted && + !prevStep.inverted && curStepParamCount == 2 && + prevStepParamCount == 2 && + curStep.paramValues[0].keyEquals("xy_in") && + prevStep.paramValues[0].keyEquals("xy_in") && + curStep.paramValues[1].keyEquals("xy_out") && + prevStep.paramValues[1].keyEquals("xy_out") && + curStep.paramValues[0].value == prevStep.paramValues[1].value && + curStep.paramValues[1].value == prevStep.paramValues[0].value) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + + // unitconvert (z) followed by its inverse is a no-op + if (curStep.name == "unitconvert" && + prevStep.name == "unitconvert" && !curStep.inverted && + !prevStep.inverted && curStepParamCount == 2 && + prevStepParamCount == 2 && + curStep.paramValues[0].keyEquals("z_in") && + prevStep.paramValues[0].keyEquals("z_in") && + curStep.paramValues[1].keyEquals("z_out") && + prevStep.paramValues[1].keyEquals("z_out") && + curStep.paramValues[0].value == prevStep.paramValues[1].value && + curStep.paramValues[1].value == prevStep.paramValues[0].value) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + + // unitconvert (xyz) followed by its inverse is a no-op + if (curStep.name == "unitconvert" && + prevStep.name == "unitconvert" && !curStep.inverted && + !prevStep.inverted && curStepParamCount == 4 && + prevStepParamCount == 4 && + curStep.paramValues[0].keyEquals("xy_in") && + prevStep.paramValues[0].keyEquals("xy_in") && + curStep.paramValues[1].keyEquals("z_in") && + prevStep.paramValues[1].keyEquals("z_in") && + curStep.paramValues[2].keyEquals("xy_out") && + prevStep.paramValues[2].keyEquals("xy_out") && + curStep.paramValues[3].keyEquals("z_out") && + prevStep.paramValues[3].keyEquals("z_out") && + curStep.paramValues[0].value == prevStep.paramValues[2].value && + curStep.paramValues[1].value == prevStep.paramValues[3].value && + curStep.paramValues[2].value == prevStep.paramValues[0].value && + curStep.paramValues[3].value == prevStep.paramValues[1].value) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + + // combine unitconvert (xy) and unitconvert (z) + for (int k = 0; k < 2; ++k) { + auto &first = (k == 0) ? curStep : prevStep; + auto &second = (k == 0) ? prevStep : curStep; + if (first.name == "unitconvert" && + second.name == "unitconvert" && !first.inverted && + !second.inverted && first.paramValues.size() == 2 && + second.paramValues.size() == 2 && + second.paramValues[0].keyEquals("xy_in") && + second.paramValues[1].keyEquals("xy_out") && + first.paramValues[0].keyEquals("z_in") && + first.paramValues[1].keyEquals("z_out")) { + + auto xy_in = second.paramValues[0].value; + auto xy_out = second.paramValues[1].value; + auto z_in = first.paramValues[0].value; + auto z_out = first.paramValues[1].value; + d->steps_.erase(iterPrev, iterCur); + iterCur->paramValues.clear(); + iterCur->paramValues.emplace_back( + Step::KeyValue("xy_in", xy_in)); + iterCur->paramValues.emplace_back( + Step::KeyValue("z_in", z_in)); + iterCur->paramValues.emplace_back( + Step::KeyValue("xy_out", xy_out)); + iterCur->paramValues.emplace_back( + Step::KeyValue("z_out", z_out)); + changeDone = true; + break; + } + } + if (changeDone) { + break; + } + + // +step +proj=unitconvert +xy_in=X1 +xy_out=X2 + // +step +proj=unitconvert +xy_in=X2 +z_in=Z1 +xy_out=X1 +z_out=Z2 + // ==> step +proj=unitconvert +z_in=Z1 +z_out=Z2 + for (int k = 0; k < 2; ++k) { + auto &first = (k == 0) ? curStep : prevStep; + auto &second = (k == 0) ? prevStep : curStep; + if (first.name == "unitconvert" && + second.name == "unitconvert" && !first.inverted && + !second.inverted && first.paramValues.size() == 4 && + second.paramValues.size() == 2 && + first.paramValues[0].keyEquals("xy_in") && + first.paramValues[1].keyEquals("z_in") && + first.paramValues[2].keyEquals("xy_out") && + first.paramValues[3].keyEquals("z_out") && + second.paramValues[0].keyEquals("xy_in=") && + second.paramValues[1].keyEquals("xy_out") && + first.paramValues[0].value == second.paramValues[1].value && + first.paramValues[2].value == second.paramValues[0].value) { + auto z_in = first.paramValues[1].value; + auto z_out = first.paramValues[3].value; + if (z_in != z_out) { + d->steps_.erase(iterPrev, iterCur); + iterCur->paramValues.clear(); + iterCur->paramValues.emplace_back( + Step::KeyValue("z_in", z_in)); + iterCur->paramValues.emplace_back( + Step::KeyValue("z_out", z_out)); + } else { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + } + changeDone = true; + break; + } + } + if (changeDone) { + break; + } + + // axisswap order=2,1 followed by itself is a no-op + if (curStep.name == "axisswap" && prevStep.name == "axisswap" && + curStepParamCount == 1 && prevStepParamCount == 1 && + curStep.paramValues[0].equals("order", "2,1") && + prevStep.paramValues[0].equals("order", "2,1")) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + + // axisswap order=2,1, unitconvert, axisswap order=2,1 -> can + // suppress axisswap + if (i + 1 < d->steps_.size() && prevStep.name == "axisswap" && + curStep.name == "unitconvert" && prevStepParamCount == 1 && + prevStep.paramValues[0].equals("order", "2,1")) { + auto iterNext = iterCur; + ++iterNext; + auto &nextStep = *iterNext; + if (nextStep.name == "axisswap" && + nextStep.paramValues.size() == 1 && + nextStep.paramValues[0].equals("order", "2,1")) { + d->steps_.erase(iterPrev); + d->steps_.erase(iterNext); + changeDone = true; + break; + } + } + + // for practical purposes WGS84 and GRS80 ellipsoids are + // equivalents (cartesian transform between both lead to differences + // of the order of 1e-14 deg..). + // No need to do a cart roundtrip for that... + // and actually IGNF uses the GRS80 definition for the WGS84 datum + if (curStep.name == "cart" && prevStep.name == "cart" && + curStep.inverted == !prevStep.inverted && + curStepParamCount == 1 && prevStepParamCount == 1 && + ((curStep.paramValues[0].equals("ellps", "WGS84") && + prevStep.paramValues[0].equals("ellps", "GRS80")) || + (curStep.paramValues[0].equals("ellps", "GRS80") && + prevStep.paramValues[0].equals("ellps", "WGS84")))) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + + if (curStep.name == "helmert" && prevStep.name == "helmert" && + !curStep.inverted && !prevStep.inverted && + curStepParamCount == 3 && + curStepParamCount == prevStepParamCount) { + std::map leftParamsMap; + std::map rightParamsMap; + try { + for (const auto &kv : prevStep.paramValues) { + leftParamsMap[kv.key] = c_locale_stod(kv.value); + } + for (const auto &kv : curStep.paramValues) { + rightParamsMap[kv.key] = c_locale_stod(kv.value); + } + } catch (const std::invalid_argument &) { + break; + } + const std::string x("x"); + const std::string y("y"); + const std::string z("z"); + if (leftParamsMap.find(x) != leftParamsMap.end() && + leftParamsMap.find(y) != leftParamsMap.end() && + leftParamsMap.find(z) != leftParamsMap.end() && + rightParamsMap.find(x) != rightParamsMap.end() && + rightParamsMap.find(y) != rightParamsMap.end() && + rightParamsMap.find(z) != rightParamsMap.end()) { + + const double xSum = leftParamsMap[x] + rightParamsMap[x]; + const double ySum = leftParamsMap[y] + rightParamsMap[y]; + const double zSum = leftParamsMap[z] + rightParamsMap[z]; + if (xSum == 0.0 && ySum == 0.0 && zSum == 0.0) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + } else { + prevStep.paramValues[0] = + Step::KeyValue("x", internal::toString(xSum)); + prevStep.paramValues[1] = + Step::KeyValue("y", internal::toString(ySum)); + prevStep.paramValues[2] = + Step::KeyValue("z", internal::toString(zSum)); + + d->steps_.erase(iterCur); + } + changeDone = true; + break; + } + } + + // hermert followed by its inverse is a no-op + if (curStep.name == "helmert" && prevStep.name == "helmert" && + !curStep.inverted && !prevStep.inverted && + curStepParamCount == prevStepParamCount) { + std::set leftParamsSet; + std::set rightParamsSet; + std::map leftParamsMap; + std::map rightParamsMap; + for (const auto &kv : prevStep.paramValues) { + leftParamsSet.insert(kv.key); + leftParamsMap[kv.key] = kv.value; + } + for (const auto &kv : curStep.paramValues) { + rightParamsSet.insert(kv.key); + rightParamsMap[kv.key] = kv.value; + } + if (leftParamsSet == rightParamsSet) { + bool doErase = true; + try { + for (const auto ¶m : leftParamsSet) { + if (param == "convention" || param == "t_epoch" || + param == "t_obs") { + if (leftParamsMap[param] != + rightParamsMap[param]) { + doErase = false; + break; + } + } else if (c_locale_stod(leftParamsMap[param]) != + -c_locale_stod(rightParamsMap[param])) { + doErase = false; + break; + } + } + } catch (const std::invalid_argument &) { + break; + } + if (doErase) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + } + } + + // detect a step and its inverse + if (curStep.inverted != prevStep.inverted && + curStep.name == prevStep.name && + curStepParamCount == prevStepParamCount) { + bool allSame = true; + for (size_t j = 0; j < curStepParamCount; j++) { + if (curStep.paramValues[j] != prevStep.paramValues[j]) { + allSame = false; + break; + } + } + if (allSame) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + } + } + } while (changeDone); + + if (d->steps_.size() > 1 || + (d->steps_.size() == 1 && + (d->steps_.front().inverted || !d->globalParamValues_.empty()))) { + d->appendToResult("+proj=pipeline"); + + for (const auto ¶mValue : d->globalParamValues_) { + d->appendToResult("+"); + d->result_ += paramValue.key; + if (!paramValue.value.empty()) { + d->result_ += "="; + d->result_ += paramValue.value; + } + } + } + + for (const auto &step : d->steps_) { + if (!d->result_.empty()) { + d->appendToResult("+step"); + } + if (step.inverted) { + d->appendToResult("+inv"); + } + if (!step.name.empty()) { + d->appendToResult(step.isInit ? "+init=" : "+proj="); + d->result_ += step.name; + } + for (const auto ¶mValue : step.paramValues) { + d->appendToResult("+"); + d->result_ += paramValue.key; + if (!paramValue.value.empty()) { + d->result_ += "="; + d->result_ += paramValue.value; + } + } + } + return d->result_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +PROJStringFormatter::Convention PROJStringFormatter::convention() const { + return d->convention_; +} + +// --------------------------------------------------------------------------- + +bool PROJStringFormatter::getUseETMercForTMerc(bool &settingSetOut) const { + settingSetOut = d->useETMercForTMercSet_; + return d->useETMercForTMerc_; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setCoordinateOperationOptimizations(bool enable) { + d->coordOperationOptimizations_ = enable; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::Private::appendToResult(const char *str) { + if (!result_.empty()) { + result_ += " "; + } + result_ += str; +} + +// --------------------------------------------------------------------------- + +static void +PROJStringSyntaxParser(const std::string &projString, std::vector &steps, + std::vector &globalParamValues, + std::string &title, std::string &vunits, + std::string &vto_meter) { + std::string word; + std::istringstream iss(projString, std::istringstream::in); + bool inverted = false; + bool prevWasStep = false; + bool inProj = false; + bool inPipeline = false; + bool prevWasTitle = false; + bool prevWasInit = false; + + while (iss >> word) { + if (word[0] == '+') { + word = word.substr(1); + } else if (prevWasTitle && word.find('=') == std::string::npos) { + title += " "; + title += word; + continue; + } + + prevWasTitle = false; + if (word == "proj=pipeline") { + if (inPipeline) { + throw ParsingException("nested pipeline not supported"); + } + inverted = false; + prevWasStep = false; + inProj = true; + inPipeline = true; + } else if (word == "step") { + if (!inPipeline) { + throw ParsingException("+step found outside pipeline"); + } + inverted = false; + prevWasStep = true; + prevWasInit = false; + } else if (word == "inv") { + if (prevWasStep) { + inverted = true; + } else { + if (steps.empty()) { + throw ParsingException("+inv found at unexpected place"); + } + steps.back().inverted = true; + } + prevWasStep = false; + } else if (starts_with(word, "proj=")) { + auto stepName = word.substr(strlen("proj=")); + if (prevWasInit) { + steps.back() = Step(); + prevWasInit = false; + } else { + steps.push_back(Step()); + } + steps.back().name = stepName; + steps.back().inverted = inverted; + prevWasStep = false; + inProj = true; + } else if (starts_with(word, "init=")) { + if (prevWasInit) { + throw ParsingException("+init= found at unexpected place"); + } + auto initName = word.substr(strlen("init=")); + steps.push_back(Step()); + steps.back().name = initName; + steps.back().isInit = true; + steps.back().inverted = inverted; + prevWasStep = false; + prevWasInit = true; + inProj = true; + } else if (inProj) { + const auto pos = word.find('='); + auto key = word.substr(0, pos); + auto pair = (pos != std::string::npos) + ? Step::KeyValue(key, word.substr(pos + 1)) + : Step::KeyValue(key); + if (steps.empty()) { + globalParamValues.push_back(pair); + } else { + steps.back().paramValues.push_back(pair); + } + prevWasStep = false; + } else if (starts_with(word, "vunits=")) { + vunits = word.substr(strlen("vunits=")); + } else if (starts_with(word, "vto_meter=")) { + vto_meter = word.substr(strlen("vto_meter=")); + } else if (starts_with(word, "title=")) { + title = word.substr(strlen("title=")); + prevWasTitle = true; + } else { + throw ParsingException("Unexpected token: " + word); + } + } +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::ingestPROJString( + const std::string &str) // throw ParsingException +{ + std::vector steps; + std::string title; + std::string vunits; + std::string vto_meter; + PROJStringSyntaxParser(str, steps, d->globalParamValues_, title, vunits, + vto_meter); + d->steps_.insert(d->steps_.end(), steps.begin(), steps.end()); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::startInversion() { + PROJStringFormatter::Private::InversionStackElt elt; + elt.startIter = d->steps_.end(); + if (elt.startIter != d->steps_.begin()) { + elt.iterValid = true; + --elt.startIter; // point to the last valid element + } else { + elt.iterValid = false; + } + elt.currentInversionState = + !d->inversionStack_.back().currentInversionState; + d->inversionStack_.push_back(elt); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::stopInversion() { + assert(!d->inversionStack_.empty()); + auto startIter = d->inversionStack_.back().startIter; + if (!d->inversionStack_.back().iterValid) { + startIter = d->steps_.begin(); + } else { + ++startIter; // advance after the last valid element we marked above + } + // Invert the inversion status of the steps between the start point and + // the current end of steps + for (auto iter = startIter; iter != d->steps_.end(); ++iter) { + iter->inverted = !iter->inverted; + } + // And reverse the order of steps in that range as well. + std::reverse(startIter, d->steps_.end()); + d->inversionStack_.pop_back(); +} + +// --------------------------------------------------------------------------- + +bool PROJStringFormatter::isInverted() const { + return d->inversionStack_.back().currentInversionState; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::Private::addStep() { steps_.emplace_back(Step()); } + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addStep(const char *stepName) { + d->addStep(); + d->steps_.back().name.assign(stepName); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addStep(const std::string &stepName) { + d->addStep(); + d->steps_.back().name = stepName; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setCurrentStepInverted(bool inverted) { + assert(!d->steps_.empty()); + d->steps_.back().inverted = inverted; +} + +// --------------------------------------------------------------------------- + +bool PROJStringFormatter::hasParam(const char *paramName) const { + if (!d->steps_.empty()) { + for (const auto ¶mValue : d->steps_.back().paramValues) { + if (paramValue.keyEquals(paramName)) { + return true; + } + } + } + return false; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addNoDefs(bool b) { d->addNoDefs_ = b; } + +// --------------------------------------------------------------------------- + +bool PROJStringFormatter::getAddNoDefs() const { return d->addNoDefs_; } + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const std::string ¶mName) { + if (d->steps_.empty()) { + d->addStep(); + } + d->steps_.back().paramValues.push_back(Step::KeyValue(paramName)); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const char *paramName, int val) { + addParam(std::string(paramName), val); +} + +void PROJStringFormatter::addParam(const std::string ¶mName, int val) { + addParam(paramName, internal::toString(val)); +} + +// --------------------------------------------------------------------------- + +static std::string formatToString(double val) { + if (std::abs(val * 10 - std::round(val * 10)) < 1e-8) { + // For the purpose of + // https://www.epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::27561 + // Latitude of natural of origin to be properly rounded from 55 grad + // to + // 49.5 deg + val = std::round(val * 10) / 10; + } + return normalizeSerializedString(internal::toString(val)); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const char *paramName, double val) { + addParam(std::string(paramName), val); +} + +void PROJStringFormatter::addParam(const std::string ¶mName, double val) { + addParam(paramName, formatToString(val)); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const char *paramName, + const std::vector &vals) { + std::string paramValue; + for (size_t i = 0; i < vals.size(); ++i) { + if (i > 0) { + paramValue += ','; + } + paramValue += formatToString(vals[i]); + } + addParam(paramName, paramValue); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const char *paramName, const char *val) { + addParam(std::string(paramName), val); +} + +void PROJStringFormatter::addParam(const char *paramName, + const std::string &val) { + addParam(std::string(paramName), val); +} + +void PROJStringFormatter::addParam(const std::string ¶mName, + const char *val) { + addParam(paramName, std::string(val)); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const std::string ¶mName, + const std::string &val) { + if (d->steps_.empty()) { + d->addStep(); + } + d->steps_.back().paramValues.push_back(Step::KeyValue(paramName, val)); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setTOWGS84Parameters( + const std::vector ¶ms) { + d->toWGS84Parameters_ = params; +} + +// --------------------------------------------------------------------------- + +const std::vector &PROJStringFormatter::getTOWGS84Parameters() const { + return d->toWGS84Parameters_; +} + +// --------------------------------------------------------------------------- + +std::set PROJStringFormatter::getUsedGridNames() const { + std::set res; + for (const auto &step : d->steps_) { + for (const auto ¶m : step.paramValues) { + if (param.keyEquals("grids")) { + res.insert(param.value); + } + } + } + return res; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setVDatumExtension(const std::string &filename) { + d->vDatumExtension_ = filename; +} + +// --------------------------------------------------------------------------- + +const std::string &PROJStringFormatter::getVDatumExtension() const { + return d->vDatumExtension_; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setHDatumExtension(const std::string &filename) { + d->hDatumExtension_ = filename; +} + +// --------------------------------------------------------------------------- + +const std::string &PROJStringFormatter::getHDatumExtension() const { + return d->hDatumExtension_; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setOmitProjLongLatIfPossible(bool omit) { + assert(d->omitProjLongLatIfPossible_ ^ omit); + d->omitProjLongLatIfPossible_ = omit; +} + +// --------------------------------------------------------------------------- + +bool PROJStringFormatter::omitProjLongLatIfPossible() const { + return d->omitProjLongLatIfPossible_; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setOmitZUnitConversion(bool omit) { + assert(d->omitZUnitConversion_ ^ omit); + d->omitZUnitConversion_ = omit; +} + +// --------------------------------------------------------------------------- + +bool PROJStringFormatter::omitZUnitConversion() const { + return d->omitZUnitConversion_; +} + +// --------------------------------------------------------------------------- + +const DatabaseContextPtr &PROJStringFormatter::databaseContext() const { + return d->dbContext_; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +struct PROJStringParser::Private { + DatabaseContextPtr dbContext_{}; + bool usePROJ4InitRules_ = false; + std::vector warningList_{}; + + std::string projString_{}; + + std::vector steps_{}; + std::vector globalParamValues_{}; + std::string title_{}; + + template + // cppcheck-suppress functionStatic + bool hasParamValue(const Step &step, const T key) { + for (const auto &pair : globalParamValues_) { + if (ci_equal(pair.key, key)) { + return true; + } + } + for (const auto &pair : step.paramValues) { + if (ci_equal(pair.key, key)) { + return true; + } + } + return false; + } + + template + // cppcheck-suppress functionStatic + const std::string &getParamValue(const Step &step, const T key) { + for (const auto &pair : globalParamValues_) { + if (ci_equal(pair.key, key)) { + return pair.value; + } + } + for (const auto &pair : step.paramValues) { + if (ci_equal(pair.key, key)) { + return pair.value; + } + } + return emptyString; + } + + // cppcheck-suppress functionStatic + const std::string &getParamValueK(const Step &step) { + for (const auto &pair : step.paramValues) { + if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) { + return pair.value; + } + } + return emptyString; + } + + // cppcheck-suppress functionStatic + std::string guessBodyName(double a); + + PrimeMeridianNNPtr buildPrimeMeridian(const Step &step); + GeodeticReferenceFrameNNPtr buildDatum(const Step &step, + const std::string &title); + GeographicCRSNNPtr buildGeographicCRS(int iStep, int iUnitConvert, + int iAxisSwap, bool ignoreVUnits, + bool ignorePROJAxis); + GeodeticCRSNNPtr buildGeocentricCRS(int iStep, int iUnitConvert); + CRSNNPtr buildProjectedCRS(int iStep, GeographicCRSNNPtr geogCRS, + int iUnitConvert, int iAxisSwap); + CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs); + UnitOfMeasure buildUnit(const Step &step, const std::string &unitsParamName, + const std::string &toMeterParamName); + CoordinateOperationNNPtr buildHelmertTransformation( + int iStep, int iFirstAxisSwap = -1, int iFirstUnitConvert = -1, + int iFirstGeogStep = -1, int iSecondGeogStep = -1, + int iSecondAxisSwap = -1, int iSecondUnitConvert = -1); + CoordinateOperationNNPtr buildMolodenskyTransformation( + int iStep, int iFirstAxisSwap = -1, int iFirstUnitConvert = -1, + int iFirstGeogStep = -1, int iSecondGeogStep = -1, + int iSecondAxisSwap = -1, int iSecondUnitConvert = -1); + + enum class AxisType { REGULAR, NORTH_POLE, SOUTH_POLE }; + + std::vector + processAxisSwap(const Step &step, const UnitOfMeasure &unit, int iAxisSwap, + AxisType axisType, bool ignorePROJAxis); + + EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert, + int iAxisSwap, bool ignoreVUnits, + bool ignorePROJAxis); +}; + +// --------------------------------------------------------------------------- + +PROJStringParser::PROJStringParser() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PROJStringParser::~PROJStringParser() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Attach a database context, to allow queries in it if needed. + */ +PROJStringParser & +PROJStringParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) { + d->dbContext_ = dbContext; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set how init=epsg:XXXX syntax should be interpreted. + * + * @param enable When set to true, + * init=epsg:XXXX syntax will be allowed and will be interpreted according to + * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude + * order and will expect/output coordinates in radians. ProjectedCRS will have + * easting, northing axis order (except the ones with Transverse Mercator South + * Orientated projection). + */ +PROJStringParser &PROJStringParser::setUsePROJ4InitRules(bool enable) { + d->usePROJ4InitRules_ = enable; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the list of warnings found during parsing. + */ +std::vector PROJStringParser::warningList() const { + return d->warningList_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +static const struct LinearUnitDesc { + const char *projName; + const char *convToMeter; + const char *name; + int epsgCode; +} linearUnitDescs[] = { + {"mm", "0.001", "millimetre", 1025}, + {"cm", "0.01", "centimetre", 1033}, + {"m", "1.0", "metre", 9001}, + {"meter", "1.0", "metre", 9001}, // alternative + {"metre", "1.0", "metre", 9001}, // alternative + {"ft", "0.3048", "foot", 9002}, + {"us-ft", "0.3048006096012192", "US survey foot", 9003}, + {"fath", "1.8288", "fathom", 9014}, + {"kmi", "1852", "nautical mile", 9030}, + {"us-ch", "20.11684023368047", "US survey chain", 9033}, + {"us-mi", "1609.347218694437", "US survey mile", 9035}, + {"km", "1000.0", "kilometre", 9036}, + {"ind-ft", "0.30479841", "Indian foot (1937)", 9081}, + {"ind-yd", "0.91439523", "Indian yard (1937)", 9085}, + {"mi", "1609.344", "Statute mile", 9093}, + {"yd", "0.9144", "yard", 9096}, + {"ch", "20.1168", "chain", 9097}, + {"link", "0.201168", "link", 9098}, + {"dm", "0.1", "decimetre", 0}, // no EPSG equivalent + {"in", "0.0254", "inch", 0}, // no EPSG equivalent + {"us-in", "0.025400050800101", "US survey inch", 0}, // no EPSG equivalent + {"us-yd", "0.914401828803658", "US survey yard", 0}, // no EPSG equivalent + {"ind-ch", "20.11669506", "Indian chain", 0}, // no EPSG equivalent +}; + +static const LinearUnitDesc *getLinearUnits(const std::string &projName) { + for (const auto &desc : linearUnitDescs) { + if (desc.projName == projName) + return &desc; + } + return nullptr; +} + +static const LinearUnitDesc *getLinearUnits(double toMeter) { + for (const auto &desc : linearUnitDescs) { + if (std::fabs(c_locale_stod(desc.convToMeter) - toMeter) < + 1e-10 * toMeter) { + return &desc; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +static UnitOfMeasure _buildUnit(const LinearUnitDesc *unitsMatch) { + std::string unitsCode; + if (unitsMatch->epsgCode) { + std::ostringstream buffer; + buffer.imbue(std::locale::classic()); + buffer << unitsMatch->epsgCode; + unitsCode = buffer.str(); + } + return UnitOfMeasure( + unitsMatch->name, c_locale_stod(unitsMatch->convToMeter), + UnitOfMeasure::Type::LINEAR, + unitsMatch->epsgCode ? Identifier::EPSG : std::string(), unitsCode); +} + +// --------------------------------------------------------------------------- + +static UnitOfMeasure _buildUnit(double to_meter_value) { + // TODO: look-up in EPSG catalog + return UnitOfMeasure("unknown", to_meter_value, + UnitOfMeasure::Type::LINEAR); +} + +// --------------------------------------------------------------------------- + +UnitOfMeasure +PROJStringParser::Private::buildUnit(const Step &step, + const std::string &unitsParamName, + const std::string &toMeterParamName) { + UnitOfMeasure unit = UnitOfMeasure::METRE; + const LinearUnitDesc *unitsMatch = nullptr; + const auto &projUnits = getParamValue(step, unitsParamName); + if (!projUnits.empty()) { + unitsMatch = getLinearUnits(projUnits); + if (unitsMatch == nullptr) { + throw ParsingException("unhandled " + unitsParamName + "=" + + projUnits); + } + } + + const auto &toMeter = getParamValue(step, toMeterParamName); + if (!toMeter.empty()) { + double to_meter_value; + try { + to_meter_value = c_locale_stod(toMeter); + } catch (const std::invalid_argument &) { + throw ParsingException("invalid value for " + toMeterParamName); + } + unitsMatch = getLinearUnits(to_meter_value); + if (unitsMatch == nullptr) { + unit = _buildUnit(to_meter_value); + } + } + + if (unitsMatch) { + unit = _buildUnit(unitsMatch); + } + + return unit; +} + +// --------------------------------------------------------------------------- + +static const struct DatumDesc { + const char *projName; + const char *gcsName; + int gcsCode; + const char *datumName; + int datumCode; + const char *ellipsoidName; + int ellipsoidCode; + double a; + double rf; +} datumDescs[] = { + {"GGRS87", "GGRS87", 4121, "Greek Geodetic Reference System 1987", 6121, + "GRS 1980", 7019, 6378137, 298.257222101}, + {"postdam", "DHDN", 4314, "Deutsches Hauptdreiecksnetz", 6314, + "Bessel 1841", 7004, 6377397.155, 299.1528128}, + {"carthage", "Carthage", 4223, "Carthage", 6223, "Clarke 1880 (IGN)", 7011, + 6378249.2, 293.4660213}, + {"hermannskogel", "MGI", 4312, "Militar-Geographische Institut", 6312, + "Bessel 1841", 7004, 6377397.155, 299.1528128}, + {"ire65", "TM65", 4299, "TM65", 6299, "Airy Modified 1849", 7002, + 6377340.189, 299.3249646}, + {"nzgd49", "NZGD49", 4272, "New Zealand Geodetic Datum 1949", 6272, + "International 1924", 7022, 6378388, 297}, + {"OSGB36", "OSGB 1936", 4277, "OSGB 1936", 6277, "Airy 1830", 7001, + 6377563.396, 299.3249646}, +}; + +// --------------------------------------------------------------------------- + +static bool isGeographicStep(const std::string &name) { + return name == "longlat" || name == "lonlat" || name == "latlong" || + name == "latlon"; +} + +// --------------------------------------------------------------------------- + +static bool isGeocentricStep(const std::string &name) { + return name == "geocent" || name == "cart"; +} + +// --------------------------------------------------------------------------- + +static bool isProjectedStep(const std::string &name) { + if (name == "etmerc" || name == "utm" || + !getMappingsFromPROJName(name).empty()) { + return true; + } + // IMPROVE ME: have a better way of distinguishing projections from + // other + // transformations. + if (name == "pipeline" || name == "geoc" || name == "deformation" || + name == "helmert" || name == "hgridshift" || name == "molodensky" || + name == "vgridshit") { + return false; + } + const auto *operations = proj_list_operations(); + for (int i = 0; operations[i].id != nullptr; ++i) { + if (name == operations[i].id) { + return true; + } + } + return false; +} + +// --------------------------------------------------------------------------- + +static PropertyMap createMapWithUnknownName() { + return PropertyMap().set(common::IdentifiedObject::NAME_KEY, "unknown"); +} + +// --------------------------------------------------------------------------- + +PrimeMeridianNNPtr +PROJStringParser::Private::buildPrimeMeridian(const Step &step) { + + PrimeMeridianNNPtr pm = PrimeMeridian::GREENWICH; + const auto &pmStr = getParamValue(step, "pm"); + if (!pmStr.empty()) { + char *end; + double pmValue = dmstor(pmStr.c_str(), &end) * RAD_TO_DEG; + if (pmValue != HUGE_VAL && *end == '\0') { + pm = PrimeMeridian::create(createMapWithUnknownName(), + Angle(pmValue)); + } else { + bool found = false; + if (pmStr == "paris") { + found = true; + pm = PrimeMeridian::PARIS; + } + auto proj_prime_meridians = proj_list_prime_meridians(); + for (int i = 0; !found && proj_prime_meridians[i].id != nullptr; + i++) { + if (pmStr == proj_prime_meridians[i].id) { + found = true; + std::string name = static_cast(::toupper(pmStr[0])) + + pmStr.substr(1); + pmValue = dmstor(proj_prime_meridians[i].defn, nullptr) * + RAD_TO_DEG; + pm = PrimeMeridian::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, name), + Angle(pmValue)); + break; + } + } + if (!found) { + throw ParsingException("unknown pm " + pmStr); + } + } + } + return pm; +} + +// --------------------------------------------------------------------------- + +std::string PROJStringParser::Private::guessBodyName(double a) { + return Ellipsoid::guessBodyName(dbContext_, a); +} + +// --------------------------------------------------------------------------- + +GeodeticReferenceFrameNNPtr +PROJStringParser::Private::buildDatum(const Step &step, + const std::string &title) { + + const auto &ellpsStr = getParamValue(step, "ellps"); + const auto &datumStr = getParamValue(step, "datum"); + const auto &RStr = getParamValue(step, "R"); + const auto &aStr = getParamValue(step, "a"); + const auto &bStr = getParamValue(step, "b"); + const auto &rfStr = getParamValue(step, "rf"); + const auto &fStr = getParamValue(step, "f"); + const auto &esStr = getParamValue(step, "es"); + const auto &eStr = getParamValue(step, "e"); + double a = -1.0; + double b = -1.0; + double rf = -1.0; + const util::optional optionalEmptyString{}; + const bool numericParamPresent = + !RStr.empty() || !aStr.empty() || !bStr.empty() || !rfStr.empty() || + !fStr.empty() || !esStr.empty() || !eStr.empty(); + + PrimeMeridianNNPtr pm(buildPrimeMeridian(step)); + PropertyMap grfMap; + + // It is arguable that we allow the prime meridian of a datum defined by + // its name to be overriden, but this is found at least in a regression test + // of GDAL. So let's keep the ellipsoid part of the datum in that case and + // use the specified prime meridian. + const auto overridePmIfNeeded = + [&pm](const GeodeticReferenceFrameNNPtr &grf) { + if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) { + return grf; + } else { + return GeodeticReferenceFrame::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, + "Unknown based on " + + grf->ellipsoid()->nameStr() + + " ellipsoid"), + grf->ellipsoid(), grf->anchorDefinition(), pm); + } + }; + + // R take precedence + if (!RStr.empty()) { + double R; + try { + R = c_locale_stod(RStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid R value"); + } + auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(), + Length(R), guessBodyName(R)); + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title.c_str()), + ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); + } + + if (!datumStr.empty()) { + auto l_datum = [&datumStr, &overridePmIfNeeded, &grfMap, + &optionalEmptyString, &pm]() { + if (datumStr == "WGS84") { + return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326); + } else if (datumStr == "NAD83") { + return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6269); + } else if (datumStr == "NAD27") { + return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6267); + } else { + + for (const auto &datumDesc : datumDescs) { + if (datumStr == datumDesc.projName) { + (void)datumDesc.gcsName; // to please cppcheck + (void)datumDesc.gcsCode; // to please cppcheck + auto ellipsoid = Ellipsoid::createFlattenedSphere( + grfMap + .set(IdentifiedObject::NAME_KEY, + datumDesc.ellipsoidName) + .set(Identifier::CODESPACE_KEY, + Identifier::EPSG) + .set(Identifier::CODE_KEY, + datumDesc.ellipsoidCode), + Length(datumDesc.a), Scale(datumDesc.rf)); + return GeodeticReferenceFrame::create( + grfMap + .set(IdentifiedObject::NAME_KEY, + datumDesc.datumName) + .set(Identifier::CODESPACE_KEY, + Identifier::EPSG) + .set(Identifier::CODE_KEY, datumDesc.datumCode), + ellipsoid, optionalEmptyString, pm); + } + } + } + throw ParsingException("unknown datum " + datumStr); + }(); + if (!numericParamPresent) { + return l_datum; + } + a = l_datum->ellipsoid()->semiMajorAxis().getSIValue(); + rf = l_datum->ellipsoid()->computedInverseFlattening(); + } + + else if (!ellpsStr.empty()) { + auto l_datum = [&ellpsStr, &title, &grfMap, &optionalEmptyString, + &pm]() { + if (ellpsStr == "WGS84") { + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() + ? "Unknown based on WGS84 ellipsoid" + : title.c_str()), + Ellipsoid::WGS84, optionalEmptyString, pm); + } else if (ellpsStr == "GRS80") { + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() + ? "Unknown based on GRS80 ellipsoid" + : title.c_str()), + Ellipsoid::GRS1980, optionalEmptyString, pm); + } else { + auto proj_ellps = proj_list_ellps(); + for (int i = 0; proj_ellps[i].id != nullptr; i++) { + if (ellpsStr == proj_ellps[i].id) { + assert(strncmp(proj_ellps[i].major, "a=", 2) == 0); + const double a_iter = + c_locale_stod(proj_ellps[i].major + 2); + EllipsoidPtr ellipsoid; + PropertyMap ellpsMap; + if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) { + const double b_iter = + c_locale_stod(proj_ellps[i].ell + 2); + ellipsoid = + Ellipsoid::createTwoAxis( + ellpsMap.set(IdentifiedObject::NAME_KEY, + proj_ellps[i].name), + Length(a_iter), Length(b_iter)) + .as_nullable(); + } else { + assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0); + const double rf_iter = + c_locale_stod(proj_ellps[i].ell + 3); + ellipsoid = + Ellipsoid::createFlattenedSphere( + ellpsMap.set(IdentifiedObject::NAME_KEY, + proj_ellps[i].name), + Length(a_iter), Scale(rf_iter)) + .as_nullable(); + } + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() + ? std::string("Unknown based on ") + + proj_ellps[i].name + + " ellipsoid" + : title), + NN_NO_CHECK(ellipsoid), optionalEmptyString, pm); + } + } + throw ParsingException("unknown ellipsoid " + ellpsStr); + } + }(); + if (!numericParamPresent) { + return l_datum; + } + a = l_datum->ellipsoid()->semiMajorAxis().getSIValue(); + if (l_datum->ellipsoid()->semiMinorAxis().has_value()) { + b = l_datum->ellipsoid()->semiMinorAxis()->getSIValue(); + } else { + rf = l_datum->ellipsoid()->computedInverseFlattening(); + } + } + + if (!aStr.empty()) { + try { + a = c_locale_stod(aStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid a value"); + } + } + + if (a > 0 && (b > 0 || !bStr.empty())) { + if (!bStr.empty()) { + try { + b = c_locale_stod(bStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid b value"); + } + } + auto ellipsoid = + Ellipsoid::createTwoAxis(createMapWithUnknownName(), Length(a), + Length(b), guessBodyName(a)) + ->identify(); + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title.c_str()), + ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); + } + + else if (a > 0 && (rf >= 0 || !rfStr.empty())) { + if (!rfStr.empty()) { + try { + rf = c_locale_stod(rfStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid rf value"); + } + } + auto ellipsoid = Ellipsoid::createFlattenedSphere( + createMapWithUnknownName(), Length(a), Scale(rf), + guessBodyName(a)) + ->identify(); + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title.c_str()), + ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); + } + + else if (a > 0 && !fStr.empty()) { + double f; + try { + f = c_locale_stod(fStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid f value"); + } + auto ellipsoid = Ellipsoid::createFlattenedSphere( + createMapWithUnknownName(), Length(a), + Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a)) + ->identify(); + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title.c_str()), + ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); + } + + else if (a > 0 && !eStr.empty()) { + double e; + try { + e = c_locale_stod(eStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid e value"); + } + double alpha = asin(e); /* angular eccentricity */ + double f = 1 - cos(alpha); /* = 1 - sqrt (1 - es); */ + auto ellipsoid = Ellipsoid::createFlattenedSphere( + createMapWithUnknownName(), Length(a), + Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a)) + ->identify(); + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title.c_str()), + ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); + } + + else if (a > 0 && !esStr.empty()) { + double es; + try { + es = c_locale_stod(esStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid es value"); + } + double f = 1 - sqrt(1 - es); + auto ellipsoid = Ellipsoid::createFlattenedSphere( + createMapWithUnknownName(), Length(a), + Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a)) + ->identify(); + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title.c_str()), + ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); + } + + // If only a is specified, create a sphere + if (a > 0 && bStr.empty() && rfStr.empty() && eStr.empty() && + esStr.empty()) { + auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(), + Length(a), guessBodyName(a)); + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title.c_str()), + ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); + } + + if (!bStr.empty() && aStr.empty()) { + throw ParsingException("b found, but a missing"); + } + + if (!rfStr.empty() && aStr.empty()) { + throw ParsingException("rf found, but a missing"); + } + + if (!fStr.empty() && aStr.empty()) { + throw ParsingException("f found, but a missing"); + } + + if (!eStr.empty() && aStr.empty()) { + throw ParsingException("e found, but a missing"); + } + + if (!esStr.empty() && aStr.empty()) { + throw ParsingException("es found, but a missing"); + } + + return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326); +} + +// --------------------------------------------------------------------------- + +static const MeridianPtr nullMeridian{}; + +static CoordinateSystemAxisNNPtr +createAxis(const std::string &name, const std::string &abbreviation, + const AxisDirection &direction, const common::UnitOfMeasure &unit, + const MeridianPtr &meridian = nullMeridian) { + return CoordinateSystemAxis::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, name), abbreviation, + direction, unit, meridian); +} + +std::vector +PROJStringParser::Private::processAxisSwap(const Step &step, + const UnitOfMeasure &unit, + int iAxisSwap, AxisType axisType, + bool ignorePROJAxis) { + assert(iAxisSwap < 0 || ci_equal(steps_[iAxisSwap].name, "axisswap")); + + const bool isGeographic = unit.type() == UnitOfMeasure::Type::ANGULAR; + const auto &eastName = + isGeographic ? AxisName::Longitude : AxisName::Easting; + const auto &eastAbbev = + isGeographic ? AxisAbbreviation::lon : AxisAbbreviation::E; + const auto &eastDir = isGeographic + ? AxisDirection::EAST + : (axisType == AxisType::NORTH_POLE) + ? AxisDirection::SOUTH + : (axisType == AxisType::SOUTH_POLE) + ? AxisDirection::NORTH + : AxisDirection::EAST; + CoordinateSystemAxisNNPtr east = createAxis( + eastName, eastAbbev, eastDir, unit, + (!isGeographic && + (axisType == AxisType::NORTH_POLE || axisType == AxisType::SOUTH_POLE)) + ? Meridian::create(Angle(90, UnitOfMeasure::DEGREE)).as_nullable() + : nullMeridian); + + const auto &northName = + isGeographic ? AxisName::Latitude : AxisName::Northing; + const auto &northAbbev = + isGeographic ? AxisAbbreviation::lat : AxisAbbreviation::N; + const auto &northDir = isGeographic + ? AxisDirection::NORTH + : (axisType == AxisType::NORTH_POLE) + ? AxisDirection::SOUTH + : (axisType == AxisType::SOUTH_POLE) + ? AxisDirection::NORTH + : AxisDirection::NORTH; + CoordinateSystemAxisNNPtr north = createAxis( + northName, northAbbev, northDir, unit, + (!isGeographic && axisType == AxisType::NORTH_POLE) + ? Meridian::create(Angle(180, UnitOfMeasure::DEGREE)).as_nullable() + : (!isGeographic && axisType == AxisType::SOUTH_POLE) + ? Meridian::create(Angle(0, UnitOfMeasure::DEGREE)) + .as_nullable() + : nullMeridian); + + CoordinateSystemAxisNNPtr west = + createAxis(isGeographic ? AxisName::Longitude : AxisName::Westing, + isGeographic ? AxisAbbreviation::lon : std::string(), + AxisDirection::WEST, unit); + + CoordinateSystemAxisNNPtr south = + createAxis(isGeographic ? AxisName::Latitude : AxisName::Southing, + isGeographic ? AxisAbbreviation::lat : std::string(), + AxisDirection::SOUTH, unit); + + std::vector axis{east, north}; + + const auto &axisStr = getParamValue(step, "axis"); + if (!ignorePROJAxis && !axisStr.empty()) { + if (axisStr.size() == 3) { + for (int i = 0; i < 2; i++) { + if (axisStr[i] == 'n') { + axis[i] = north; + } else if (axisStr[i] == 's') { + axis[i] = south; + } else if (axisStr[i] == 'e') { + axis[i] = east; + } else if (axisStr[i] == 'w') { + axis[i] = west; + } else { + throw ParsingException("Unhandled axis=" + axisStr); + } + } + } else { + throw ParsingException("Unhandled axis=" + axisStr); + } + } else if (iAxisSwap >= 0) { + const auto &stepAxisSwap = steps_[iAxisSwap]; + const auto &orderStr = getParamValue(stepAxisSwap, "order"); + auto orderTab = split(orderStr, ','); + if (orderTab.size() != 2) { + throw ParsingException("Unhandled order=" + orderStr); + } + if (stepAxisSwap.inverted) { + throw ParsingException("Unhandled +inv for +proj=axisswap"); + } + + for (size_t i = 0; i < 2; i++) { + if (orderTab[i] == "1") { + axis[i] = east; + } else if (orderTab[i] == "-1") { + axis[i] = west; + } else if (orderTab[i] == "2") { + axis[i] = north; + } else if (orderTab[i] == "-2") { + axis[i] = south; + } else { + throw ParsingException("Unhandled order=" + orderStr); + } + } + } + return axis; +} + +// --------------------------------------------------------------------------- + +EllipsoidalCSNNPtr +PROJStringParser::Private::buildEllipsoidalCS(int iStep, int iUnitConvert, + int iAxisSwap, bool ignoreVUnits, + bool ignorePROJAxis) { + const auto &step = steps_[iStep]; + assert(iUnitConvert < 0 || + ci_equal(steps_[iUnitConvert].name, "unitconvert")); + + UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE; + if (iUnitConvert >= 0) { + const auto &stepUnitConvert = steps_[iUnitConvert]; + const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); + const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); + if (stepUnitConvert.inverted) { + std::swap(xy_in, xy_out); + } + if (iUnitConvert < iStep) { + std::swap(xy_in, xy_out); + } + if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" || + (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) { + throw ParsingException("unhandled values for xy_in and/or xy_out"); + } + if (*xy_out == "rad") { + angularUnit = UnitOfMeasure::RADIAN; + } else if (*xy_out == "grad") { + angularUnit = UnitOfMeasure::GRAD; + } + } + + std::vector axis = processAxisSwap( + step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis); + CoordinateSystemAxisNNPtr up = CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Ellipsoidal_height), + AxisAbbreviation::h, AxisDirection::UP, + buildUnit(step, "vunits", "vto_meter")); + + return (!ignoreVUnits && !hasParamValue(step, "geoidgrids") && + (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter"))) + ? EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1], up) + : EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1]); +} + +// --------------------------------------------------------------------------- + +static double getNumericValue(const std::string ¶mValue, + bool *pHasError = nullptr) { + try { + double value = c_locale_stod(paramValue); + if (pHasError) + *pHasError = false; + return value; + } catch (const std::invalid_argument &) { + if (pHasError) + *pHasError = true; + return 0.0; + } +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr +PROJStringParser::Private::buildGeographicCRS(int iStep, int iUnitConvert, + int iAxisSwap, bool ignoreVUnits, + bool ignorePROJAxis) { + const auto &step = steps_[iStep]; + + const bool l_isGeographicStep = isGeographicStep(step.name); + const auto &title = l_isGeographicStep ? title_ : emptyString; + + auto datum = buildDatum(step, title); + + auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title); + if (l_isGeographicStep && + (hasParamValue(step, "wktext") || + hasParamValue(step, "lon_wrap") | hasParamValue(step, "geoc") || + getNumericValue(getParamValue(step, "lon_0")) != 0.0)) { + props.set("EXTENSION_PROJ4", projString_); + } + + return GeographicCRS::create( + props, datum, buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, + ignoreVUnits, ignorePROJAxis)); +} + +// --------------------------------------------------------------------------- + +GeodeticCRSNNPtr +PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) { + const auto &step = steps_[iStep]; + + assert(isGeocentricStep(step.name)); + assert(iUnitConvert < 0 || + ci_equal(steps_[iUnitConvert].name, "unitconvert")); + + const auto &title = title_; + + auto datum = buildDatum(step, title); + + UnitOfMeasure unit = UnitOfMeasure::METRE; + if (iUnitConvert >= 0) { + const auto &stepUnitConvert = steps_[iUnitConvert]; + const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); + const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); + const std::string *z_in = &getParamValue(stepUnitConvert, "z_in"); + const std::string *z_out = &getParamValue(stepUnitConvert, "z_out"); + if (stepUnitConvert.inverted) { + std::swap(xy_in, xy_out); + std::swap(z_in, z_out); + } + if (xy_in->empty() || xy_out->empty() || *xy_in != "m" || + *z_in != "m" || *xy_out != *z_out) { + throw ParsingException( + "unhandled values for xy_in, z_in, xy_out or z_out"); + } + + const LinearUnitDesc *unitsMatch = nullptr; + try { + double to_meter_value = c_locale_stod(*xy_out); + unitsMatch = getLinearUnits(to_meter_value); + if (unitsMatch == nullptr) { + unit = _buildUnit(to_meter_value); + } + } catch (const std::invalid_argument &) { + unitsMatch = getLinearUnits(*xy_out); + if (!unitsMatch) { + throw ParsingException( + "unhandled values for xy_in, z_in, xy_out or z_out"); + } + unit = _buildUnit(unitsMatch); + } + } + + auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title); + if (hasParamValue(step, "wktext")) { + props.set("EXTENSION_PROJ4", projString_); + } + + auto cs = CartesianCS::createGeocentric(unit); + return GeodeticCRS::create(props, datum, cs); +} + +// --------------------------------------------------------------------------- + +CRSNNPtr +PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep, + CRSNNPtr crs) { + const auto &step = steps_[iStep]; + const auto &towgs84 = getParamValue(step, "towgs84"); + if (!towgs84.empty()) { + std::vector towgs84Values; + const auto tokens = split(towgs84, ','); + for (const auto &str : tokens) { + try { + towgs84Values.push_back(c_locale_stod(str)); + } catch (const std::invalid_argument &) { + throw ParsingException("Non numerical value in towgs84 clause"); + } + } + crs = BoundCRS::createFromTOWGS84(crs, towgs84Values); + } + + const auto &nadgrids = getParamValue(step, "nadgrids"); + if (!nadgrids.empty()) { + crs = BoundCRS::createFromNadgrids(crs, nadgrids); + } + + const auto &geoidgrids = getParamValue(step, "geoidgrids"); + if (!geoidgrids.empty()) { + auto vdatum = + VerticalReferenceFrame::create(createMapWithUnknownName()); + + const UnitOfMeasure unit = buildUnit(step, "vunits", "vto_meter"); + + auto vcrs = + VerticalCRS::create(createMapWithUnknownName(), vdatum, + VerticalCS::createGravityRelatedHeight(unit)); + + auto transformation = + Transformation::createGravityRelatedHeightToGeographic3D( + PropertyMap().set(IdentifiedObject::NAME_KEY, + "unknown to WGS84 ellipsoidal height"), + crs, GeographicCRS::EPSG_4979, geoidgrids, + std::vector()); + auto boundvcrs = + BoundCRS::create(vcrs, GeographicCRS::EPSG_4979, transformation); + + crs = CompoundCRS::create(createMapWithUnknownName(), + std::vector{crs, boundvcrs}); + } + + return crs; +} + +// --------------------------------------------------------------------------- + +static double getAngularValue(const std::string ¶mValue, + bool *pHasError = nullptr) { + char *endptr = nullptr; + double value = dmstor(paramValue.c_str(), &endptr) * RAD_TO_DEG; + if (value == HUGE_VAL || endptr != paramValue.c_str() + paramValue.size()) { + if (pHasError) + *pHasError = true; + return 0.0; + } + if (pHasError) + *pHasError = false; + return value; +} + +// --------------------------------------------------------------------------- + +CRSNNPtr PROJStringParser::Private::buildProjectedCRS( + int iStep, GeographicCRSNNPtr geogCRS, int iUnitConvert, int iAxisSwap) { + auto &step = steps_[iStep]; + auto mappings = getMappingsFromPROJName(step.name); + const MethodMapping *mapping = mappings.empty() ? nullptr : mappings[0]; + + assert(isProjectedStep(step.name)); + assert(iUnitConvert < 0 || + ci_equal(steps_[iUnitConvert].name, "unitconvert")); + + const auto &title = title_; + + if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo( + geogCRS->primeMeridian()->longitude(), + util::IComparable::Criterion::EQUIVALENT)) { + throw ParsingException("inconsistent pm values between projectedCRS " + "and its base geographicalCRS"); + } + + auto axisType = AxisType::REGULAR; + + if (step.name == "tmerc" && + ((getParamValue(step, "axis") == "wsu" && iAxisSwap < 0) || + (iAxisSwap > 0 && + getParamValue(steps_[iAxisSwap], "order") == "-1,-2"))) { + mapping = + getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED); + } else if (step.name == "etmerc") { + mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR); + } else if (step.name == "lcc") { + const auto &lat_0 = getParamValue(step, "lat_0"); + const auto &lat_1 = getParamValue(step, "lat_1"); + const auto &lat_2 = getParamValue(step, "lat_2"); + const auto &k = getParamValueK(step); + if (lat_2.empty() && !lat_0.empty() && !lat_1.empty() && + getAngularValue(lat_0) == getAngularValue(lat_1)) { + mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); + } else if (!k.empty() && getNumericValue(k) != 1.0) { + mapping = getMapping( + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN); + } else { + mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); + } + } else if (step.name == "aeqd" && hasParamValue(step, "guam")) { + mapping = getMapping(EPSG_CODE_METHOD_GUAM_PROJECTION); + } else if (step.name == "cea" && !geogCRS->ellipsoid()->isSphere()) { + mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA); + } else if (step.name == "geos" && getParamValue(step, "sweep") == "x") { + mapping = + getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X); + } else if (step.name == "geos") { + mapping = + getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y); + } else if (step.name == "omerc") { + if (hasParamValue(step, "no_rot")) { + mapping = nullptr; + } else if (hasParamValue(step, "no_uoff") || + hasParamValue(step, "no_off")) { + mapping = + getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A); + } else if (hasParamValue(step, "lat_1") && + hasParamValue(step, "lon_1") && + hasParamValue(step, "lat_2") && + hasParamValue(step, "lon_2")) { + mapping = getMapping( + PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN); + } else { + mapping = + getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B); + } + } else if (step.name == "somerc") { + mapping = + getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B); + step.paramValues.emplace_back(Step::KeyValue("alpha", "90")); + step.paramValues.emplace_back(Step::KeyValue("gamma", "90")); + step.paramValues.emplace_back( + Step::KeyValue("lonc", getParamValue(step, "lon_0"))); + } else if (step.name == "krovak" && + ((getParamValue(step, "axis") == "swu" && iAxisSwap < 0) || + (iAxisSwap > 0 && + getParamValue(steps_[iAxisSwap], "order") == "-2,-1"))) { + mapping = getMapping(EPSG_CODE_METHOD_KROVAK); + } else if (step.name == "merc") { + if (hasParamValue(step, "a") && hasParamValue(step, "b") && + getParamValue(step, "a") == getParamValue(step, "b") && + (!hasParamValue(step, "lat_ts") || + getAngularValue(getParamValue(step, "lat_ts")) == 0.0) && + getNumericValue(getParamValueK(step)) == 1.0 && + getParamValue(step, "nadgrids") == "@null") { + mapping = getMapping( + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR); + for (size_t i = 0; i < step.paramValues.size(); ++i) { + if (ci_equal(step.paramValues[i].key, "nadgrids")) { + step.paramValues.erase(step.paramValues.begin() + i); + break; + } + } + } else if (hasParamValue(step, "lat_ts")) { + mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); + } else { + mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A); + } + } else if (step.name == "stere") { + if (hasParamValue(step, "lat_0") && + std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) - + 90.0) < 1e-10) { + const double lat_0 = getAngularValue(getParamValue(step, "lat_0")); + if (lat_0 > 0) { + axisType = AxisType::NORTH_POLE; + } else { + axisType = AxisType::SOUTH_POLE; + } + const auto &lat_ts = getParamValue(step, "lat_ts"); + const auto &k = getParamValueK(step); + if (!lat_ts.empty() && + std::fabs(getAngularValue(lat_ts) - lat_0) > 1e-10 && + !k.empty() && std::fabs(getNumericValue(k) - 1) > 1e-10) { + throw ParsingException("lat_ts != lat_0 and k != 1 not " + "supported for Polar Stereographic"); + } + if (!lat_ts.empty() && + (k.empty() || std::fabs(getNumericValue(k) - 1) < 1e-10)) { + mapping = + getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B); + } else { + mapping = + getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A); + } + } else { + mapping = getMapping(PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC); + } + } else if (step.name == "laea") { + if (hasParamValue(step, "lat_0") && + std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) - + 90.0) < 1e-10) { + const double lat_0 = getAngularValue(getParamValue(step, "lat_0")); + if (lat_0 > 0) { + axisType = AxisType::NORTH_POLE; + } else { + axisType = AxisType::SOUTH_POLE; + } + } + if (geogCRS->ellipsoid()->isSphere()) { + mapping = getMapping( + EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL); + } + } else if (step.name == "eqc") { + if (geogCRS->ellipsoid()->isSphere()) { + mapping = + getMapping(EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL); + } + } + + UnitOfMeasure unit = buildUnit(step, "units", "to_meter"); + if (iUnitConvert >= 0) { + const auto &stepUnitConvert = steps_[iUnitConvert]; + const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); + const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); + if (stepUnitConvert.inverted) { + std::swap(xy_in, xy_out); + } + if (xy_in->empty() || xy_out->empty() || *xy_in != "m") { + if (step.name != "ob_tran") { + throw ParsingException( + "unhandled values for xy_in and/or xy_out"); + } + } + + const LinearUnitDesc *unitsMatch = nullptr; + try { + double to_meter_value = c_locale_stod(*xy_out); + unitsMatch = getLinearUnits(to_meter_value); + if (unitsMatch == nullptr) { + unit = _buildUnit(to_meter_value); + } + } catch (const std::invalid_argument &) { + unitsMatch = getLinearUnits(*xy_out); + if (!unitsMatch) { + if (step.name != "ob_tran") { + throw ParsingException( + "unhandled values for xy_in and/or xy_out"); + } + } else { + unit = _buildUnit(unitsMatch); + } + } + } + + ConversionPtr conv; + + auto mapWithUnknownName = createMapWithUnknownName(); + + if (step.name == "utm") { + const int zone = std::atoi(getParamValue(step, "zone").c_str()); + const bool north = !hasParamValue(step, "south"); + conv = + Conversion::createUTM(emptyPropertyMap, zone, north).as_nullable(); + } else if (mapping) { + + auto methodMap = + PropertyMap().set(IdentifiedObject::NAME_KEY, mapping->wkt2_name); + if (mapping->epsg_code) { + methodMap.set(Identifier::CODESPACE_KEY, Identifier::EPSG) + .set(Identifier::CODE_KEY, mapping->epsg_code); + } + std::vector parameters; + std::vector values; + for (int i = 0; mapping->params[i] != nullptr; i++) { + const auto *param = mapping->params[i]; + std::string proj_name(param->proj_name ? param->proj_name : ""); + const std::string *paramValue = + (proj_name == "k" || proj_name == "k_0") + ? &getParamValueK(step) + : !proj_name.empty() ? &getParamValue(step, proj_name) + : &emptyString; + double value = 0; + if (!paramValue->empty()) { + bool hasError = false; + if (param->unit_type == UnitOfMeasure::Type::ANGULAR) { + value = getAngularValue(*paramValue, &hasError); + } else { + value = getNumericValue(*paramValue, &hasError); + } + if (hasError) { + throw ParsingException("invalid value for " + proj_name); + } + + } else if (param->unit_type == UnitOfMeasure::Type::SCALE) { + value = 1; + } else { + // For omerc, if gamma is missing, the default value is + // alpha + if (step.name == "omerc" && proj_name == "gamma") { + paramValue = &getParamValue(step, "alpha"); + if (!paramValue->empty()) { + value = getAngularValue(*paramValue); + } + } else if (step.name == "krovak") { + if (param->epsg_code == + EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS) { + value = 30.28813975277777776; + } else if ( + param->epsg_code == + EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL) { + value = 78.5; + } + } + } + + PropertyMap propertiesParameter; + propertiesParameter.set(IdentifiedObject::NAME_KEY, + param->wkt2_name); + if (param->epsg_code) { + propertiesParameter.set(Identifier::CODE_KEY, param->epsg_code); + propertiesParameter.set(Identifier::CODESPACE_KEY, + Identifier::EPSG); + } + parameters.push_back( + OperationParameter::create(propertiesParameter)); + // In PROJ convention, angular parameters are always in degree + // and linear parameters always in metre. + double valRounded = + param->unit_type == UnitOfMeasure::Type::LINEAR + ? Length(value, UnitOfMeasure::METRE).convertToUnit(unit) + : value; + if (std::fabs(valRounded - std::round(valRounded)) < 1e-8) { + valRounded = std::round(valRounded); + } + values.push_back(ParameterValue::create(Measure( + valRounded, + param->unit_type == UnitOfMeasure::Type::ANGULAR + ? UnitOfMeasure::DEGREE + : param->unit_type == UnitOfMeasure::Type::LINEAR + ? unit + : param->unit_type == UnitOfMeasure::Type::SCALE + ? UnitOfMeasure::SCALE_UNITY + : UnitOfMeasure::NONE))); + } + + if (step.name == "etmerc") { + methodMap.set("proj_method", "etmerc"); + } + + conv = Conversion::create(mapWithUnknownName, methodMap, parameters, + values) + .as_nullable(); + } else { + std::vector parameters; + std::vector values; + std::string methodName = "PROJ " + step.name; + for (const auto ¶m : step.paramValues) { + if (param.key == "wktext" || param.key == "no_defs" || + param.key == "datum" || param.key == "ellps" || + param.key == "a" || param.key == "b" || param.key == "R" || + param.key == "towgs84" || param.key == "nadgrids" || + param.key == "geoidgrids" || param.key == "units" || + param.key == "to_meter" || param.key == "vunits" || + param.key == "vto_meter") { + continue; + } + if (param.value.empty()) { + methodName += " " + param.key; + } else if (param.key == "o_proj") { + methodName += " " + param.key + "=" + param.value; + } else { + parameters.push_back(OperationParameter::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, param.key))); + bool hasError = false; + if (param.key == "x_0" || param.key == "y_0") { + double value = getNumericValue(param.value, &hasError); + values.push_back(ParameterValue::create( + Measure(value, UnitOfMeasure::METRE))); + } else if (param.key == "k" || param.key == "k_0") { + double value = getNumericValue(param.value, &hasError); + values.push_back(ParameterValue::create( + Measure(value, UnitOfMeasure::SCALE_UNITY))); + } else { + double value = getAngularValue(param.value, &hasError); + values.push_back(ParameterValue::create( + Measure(value, UnitOfMeasure::DEGREE))); + } + if (hasError) { + throw ParsingException("invalid value for " + param.key); + } + } + } + conv = Conversion::create( + mapWithUnknownName, + PropertyMap().set(IdentifiedObject::NAME_KEY, methodName), + parameters, values) + .as_nullable(); + + if (methodName == "PROJ ob_tran o_proj=longlat") { + return DerivedGeographicCRS::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + geogCRS, NN_NO_CHECK(conv), + buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, false, + false)); + } + } + + std::vector axis = + processAxisSwap(step, unit, iAxisSwap, axisType, false); + + auto cs = CartesianCS::create(emptyPropertyMap, axis[0], axis[1]); + + auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title); + + if (hasParamValue(step, "wktext")) { + props.set("EXTENSION_PROJ4", projString_); + } + + CRSNNPtr crs = ProjectedCRS::create(props, geogCRS, NN_NO_CHECK(conv), cs); + + if (!hasParamValue(step, "geoidgrids") && + (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter"))) { + auto vdatum = VerticalReferenceFrame::create(mapWithUnknownName); + + const UnitOfMeasure vunit = buildUnit(step, "vunits", "vto_meter"); + + auto vcrs = + VerticalCRS::create(mapWithUnknownName, vdatum, + VerticalCS::createGravityRelatedHeight(vunit)); + + crs = CompoundCRS::create(mapWithUnknownName, + std::vector{crs, vcrs}); + } + + return crs; +} + +// --------------------------------------------------------------------------- + +static bool isDatumDefiningParam(const std::string ¶m) { + return (param == "datum" || param == "ellps" || param == "a" || + param == "b" || param == "rf" || param == "f" || param == "R"); +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr PROJStringParser::Private::buildHelmertTransformation( + int iStep, int iFirstAxisSwap, int iFirstUnitConvert, int iFirstGeogStep, + int iSecondGeogStep, int iSecondAxisSwap, int iSecondUnitConvert) { + auto &step = steps_[iStep]; + auto datum = buildDatum(step, std::string()); + auto cs = CartesianCS::createGeocentric(UnitOfMeasure::METRE); + + auto mapWithUnknownName = createMapWithUnknownName(); + + auto sourceCRS = + iFirstGeogStep >= 0 + ? util::nn_static_pointer_cast( + buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, + iFirstAxisSwap, true, false)) + : util::nn_static_pointer_cast( + GeodeticCRS::create(mapWithUnknownName, datum, cs)); + auto targetCRS = + iSecondGeogStep >= 0 + ? util::nn_static_pointer_cast( + buildGeographicCRS(iSecondGeogStep, iSecondUnitConvert, + iSecondAxisSwap, true, false)) + : util::nn_static_pointer_cast( + GeodeticCRS::create(mapWithUnknownName, datum, cs)); + + double x = 0; + double y = 0; + double z = 0; + double rx = 0; + double ry = 0; + double rz = 0; + double s = 0; + double dx = 0; + double dy = 0; + double dz = 0; + double drx = 0; + double dry = 0; + double drz = 0; + double ds = 0; + double t_epoch = 0; + bool rotationTerms = false; + bool timeDependent = false; + bool conventionFound = false; + bool positionVectorConvention = false; + + struct Params { + double *pValue; + const char *name; + bool *pPresent; + }; + const Params knownParams[] = { + {&x, "x", nullptr}, + {&y, "y", nullptr}, + {&z, "z", nullptr}, + {&rx, "rx", &rotationTerms}, + {&ry, "ry", &rotationTerms}, + {&rz, "rz", &rotationTerms}, + {&s, "s", &rotationTerms}, + {&dx, "dx", &timeDependent}, + {&dy, "dy", &timeDependent}, + {&dz, "dz", &timeDependent}, + {&drx, "drx", &timeDependent}, + {&dry, "dry", &timeDependent}, + {&drz, "drz", &timeDependent}, + {&ds, "ds", &timeDependent}, + {&t_epoch, "t_epoch", &timeDependent}, + {nullptr, "exact", nullptr}, + }; + + for (const auto ¶m : step.paramValues) { + if (isDatumDefiningParam(param.key)) { + continue; + } + if (param.key == "convention") { + if (param.value == "position_vector") { + positionVectorConvention = true; + conventionFound = true; + } else if (param.value == "coordinate_frame") { + positionVectorConvention = false; + conventionFound = true; + } else { + throw ParsingException("unsupported convention"); + } + } else { + bool found = false; + for (auto &&knownParam : knownParams) { + if (param.key == knownParam.name) { + found = true; + if (knownParam.pValue) + *(knownParam.pValue) = getNumericValue(param.value); + if (knownParam.pPresent) + *(knownParam.pPresent) = true; + break; + } + } + if (!found) { + throw ParsingException("unsupported keyword for Helmert: " + + param.key); + } + } + } + + rotationTerms |= timeDependent; + if (rotationTerms && !conventionFound) { + throw ParsingException("missing convention"); + } + + std::vector emptyAccuracies; + + auto transf = ([&]() { + if (!rotationTerms) { + return Transformation::createGeocentricTranslations( + mapWithUnknownName, sourceCRS, targetCRS, x, y, z, + emptyAccuracies); + } else if (positionVectorConvention) { + if (timeDependent) { + return Transformation::createTimeDependentPositionVector( + mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry, + rz, s, dx, dy, dz, drx, dry, drz, ds, t_epoch, + emptyAccuracies); + } else { + return Transformation::createPositionVector( + mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry, + rz, s, emptyAccuracies); + } + } else { + if (timeDependent) { + return Transformation:: + createTimeDependentCoordinateFrameRotation( + mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, + ry, rz, s, dx, dy, dz, drx, dry, drz, ds, t_epoch, + emptyAccuracies); + } else { + return Transformation::createCoordinateFrameRotation( + mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry, + rz, s, emptyAccuracies); + } + } + })(); + + if (step.inverted) { + return util::nn_static_pointer_cast( + transf->inverse()); + } else { + return util::nn_static_pointer_cast(transf); + } +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr +PROJStringParser::Private::buildMolodenskyTransformation( + int iStep, int iFirstAxisSwap, int iFirstUnitConvert, int iFirstGeogStep, + int iSecondGeogStep, int iSecondAxisSwap, int iSecondUnitConvert) { + auto &step = steps_[iStep]; + + double dx = 0; + double dy = 0; + double dz = 0; + double da = 0; + double df = 0; + + struct Params { + double *pValue; + const char *name; + }; + const Params knownParams[] = { + {&dx, "dx"}, {&dy, "dy"}, {&dz, "dz"}, {&da, "da"}, {&df, "df"}, + }; + bool abridged = false; + + for (const auto ¶m : step.paramValues) { + if (isDatumDefiningParam(param.key)) { + continue; + } else if (param.key == "abridged") { + abridged = true; + } else { + bool found = false; + for (auto &&knownParam : knownParams) { + if (param.key == knownParam.name) { + found = true; + if (knownParam.pValue) + *(knownParam.pValue) = getNumericValue(param.value); + break; + } + } + if (!found) { + throw ParsingException("unsupported keyword for Molodensky: " + + param.key); + } + } + } + + auto datum = buildDatum(step, std::string()); + auto sourceCRS = iFirstGeogStep >= 0 + ? buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, + iFirstAxisSwap, true, false) + : buildGeographicCRS(iStep, -1, -1, true, false); + + const auto &ellps = sourceCRS->ellipsoid(); + const double a = ellps->semiMajorAxis().getSIValue(); + const double rf = ellps->computedInverseFlattening(); + const double target_a = a + da; + const double target_rf = 1.0 / (1.0 / rf + df); + + auto mapWithUnknownName = createMapWithUnknownName(); + + auto target_ellipsoid = + Ellipsoid::createFlattenedSphere(mapWithUnknownName, Length(target_a), + Scale(target_rf)) + ->identify(); + auto target_datum = GeodeticReferenceFrame::create( + mapWithUnknownName, target_ellipsoid, util::optional(), + PrimeMeridian::GREENWICH); + + auto targetCRS = util::nn_static_pointer_cast( + iSecondGeogStep >= 0 + ? buildGeographicCRS(iSecondGeogStep, iSecondUnitConvert, + iSecondAxisSwap, true, false) + : GeographicCRS::create(mapWithUnknownName, target_datum, + EllipsoidalCS::createLongitudeLatitude( + UnitOfMeasure::DEGREE))); + + auto sourceCRS_as_CRS = util::nn_static_pointer_cast(sourceCRS); + + std::vector emptyAccuracies; + + auto transf = ([&]() { + if (abridged) { + return Transformation::createAbridgedMolodensky( + mapWithUnknownName, sourceCRS_as_CRS, targetCRS, dx, dy, dz, da, + df, emptyAccuracies); + } else { + return Transformation::createMolodensky( + mapWithUnknownName, sourceCRS_as_CRS, targetCRS, dx, dy, dz, da, + df, emptyAccuracies); + } + })(); + + if (step.inverted) { + return util::nn_static_pointer_cast( + transf->inverse()); + } else { + return util::nn_static_pointer_cast(transf); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const metadata::ExtentPtr nullExtent{}; + +static const metadata::ExtentPtr &getExtent(const crs::CRS *crs) { + const auto &domains = crs->domains(); + if (!domains.empty()) { + return domains[0]->domainOfValidity(); + } + return nullExtent; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a sub-class of BaseObject from a PROJ string. + * @throw ParsingException + */ +BaseObjectNNPtr +PROJStringParser::createFromPROJString(const std::string &projString) { + std::string vunits; + std::string vto_meter; + + d->steps_.clear(); + d->title_.clear(); + d->globalParamValues_.clear(); + d->projString_ = projString; + PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_, + d->title_, vunits, vto_meter); + + if (d->steps_.empty()) { + + if (!vunits.empty() || !vto_meter.empty()) { + Step fakeStep; + if (!vunits.empty()) { + fakeStep.paramValues.emplace_back( + Step::KeyValue("vunits", vunits)); + } + if (!vto_meter.empty()) { + fakeStep.paramValues.emplace_back( + Step::KeyValue("vto_meter", vto_meter)); + } + auto vdatum = + VerticalReferenceFrame::create(createMapWithUnknownName()); + auto vcrs = VerticalCRS::create( + createMapWithUnknownName(), vdatum, + VerticalCS::createGravityRelatedHeight( + d->buildUnit(fakeStep, "vunits", "vto_meter"))); + return vcrs; + } + } + + if ((d->steps_.size() == 1 || + (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) && + isGeocentricStep(d->steps_[0].name)) { + return d->buildBoundOrCompoundCRSIfNeeded( + 0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 && + d->steps_[1].name == "unitconvert") + ? 1 + : -1)); + } + + // +init=xxxx:yyyy syntax + if (d->steps_.size() == 1 && d->steps_[0].isInit && + d->steps_[0].paramValues.size() == 0) { + + // Those used to come from a text init file + // We only support them in compatibility mode + const std::string &stepName = d->steps_[0].name; + if (ci_starts_with(stepName, "epsg:") || + ci_starts_with(stepName, "IGNF:")) { + bool usePROJ4InitRules = d->usePROJ4InitRules_; + if (!usePROJ4InitRules) { + PJ_CONTEXT *ctx = proj_context_create(); + if (ctx) { + usePROJ4InitRules = proj_context_get_use_proj4_init_rules( + ctx, FALSE) == TRUE; + proj_context_destroy(ctx); + } + } + if (!usePROJ4InitRules) { + throw ParsingException("init=epsg:/init=IGNF: syntax not " + "supported in non-PROJ4 emulation mode"); + } + + PJ_CONTEXT *ctx = proj_context_create(); + char unused[256]; + std::string initname(stepName); + initname.resize(initname.find(':')); + int file_found = + pj_find_file(ctx, initname.c_str(), unused, sizeof(unused)); + proj_context_destroy(ctx); + if (!file_found) { + auto obj = createFromUserInput(stepName, d->dbContext_, true); + auto crs = dynamic_cast(obj.get()); + if (crs) { + PropertyMap properties; + properties.set(IdentifiedObject::NAME_KEY, crs->nameStr()); + const auto &extent = getExtent(crs); + if (extent) { + properties.set( + common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + NN_NO_CHECK(extent)); + } + auto geogCRS = dynamic_cast(crs); + if (geogCRS) { + // Override with longitude latitude in radian + return GeographicCRS::create( + properties, geogCRS->datum(), + geogCRS->datumEnsemble(), + EllipsoidalCS::createLongitudeLatitude( + UnitOfMeasure::RADIAN)); + } + auto projCRS = dynamic_cast(crs); + if (projCRS) { + // Override with easting northing order + const auto &conv = projCRS->derivingConversionRef(); + if (conv->method()->getEPSGCode() != + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) { + return ProjectedCRS::create( + properties, projCRS->baseCRS(), conv, + CartesianCS::createEastingNorthing( + projCRS->coordinateSystem() + ->axisList()[0] + ->unit())); + } + } + } + return obj; + } + } + + paralist *init = pj_mkparam(("init=" + d->steps_[0].name).c_str()); + if (!init) { + throw ParsingException("out of memory"); + } + PJ_CONTEXT *ctx = proj_context_create(); + if (!ctx) { + pj_dealloc(init); + throw ParsingException("out of memory"); + } + paralist *list = pj_expand_init(ctx, init); + proj_context_destroy(ctx); + if (!list) { + pj_dealloc(init); + throw ParsingException("cannot expand " + projString); + } + std::string expanded; + bool first = true; + bool has_init_term = false; + for (auto t = list; t;) { + if (!expanded.empty()) { + expanded += ' '; + } + if (first) { + // first parameter is the init= itself + first = false; + } else if (starts_with(t->param, "init=")) { + has_init_term = true; + } else { + expanded += t->param; + } + + auto n = t->next; + pj_dealloc(t); + t = n; + } + + if (!has_init_term) { + return createFromPROJString(expanded); + } + } + + int iFirstGeogStep = -1; + int iSecondGeogStep = -1; + int iProjStep = -1; + int iFirstUnitConvert = -1; + int iSecondUnitConvert = -1; + int iFirstAxisSwap = -1; + int iSecondAxisSwap = -1; + int iHelmert = -1; + int iFirstCart = -1; + int iSecondCart = -1; + int iMolodensky = -1; + bool unexpectedStructure = false; + for (int i = 0; i < static_cast(d->steps_.size()); i++) { + const auto &stepName = d->steps_[i].name; + if (isGeographicStep(stepName)) { + if (iFirstGeogStep < 0) { + iFirstGeogStep = i; + } else if (iSecondGeogStep < 0) { + iSecondGeogStep = i; + } else { + unexpectedStructure = true; + break; + } + } else if (ci_equal(stepName, "unitconvert")) { + if (iFirstUnitConvert < 0) { + iFirstUnitConvert = i; + } else if (iSecondUnitConvert < 0) { + iSecondUnitConvert = i; + } else { + unexpectedStructure = true; + break; + } + } else if (ci_equal(stepName, "axisswap")) { + if (iFirstAxisSwap < 0) { + iFirstAxisSwap = i; + } else if (iSecondAxisSwap < 0) { + iSecondAxisSwap = i; + } else { + unexpectedStructure = true; + break; + } + } else if (stepName == "helmert") { + if (iHelmert >= 0) { + unexpectedStructure = true; + break; + } + iHelmert = i; + } else if (stepName == "cart") { + if (iFirstCart < 0) { + iFirstCart = i; + } else if (iSecondCart < 0) { + iSecondCart = i; + } else { + unexpectedStructure = true; + break; + } + } else if (stepName == "molodensky") { + if (iMolodensky >= 0) { + unexpectedStructure = true; + break; + } + iMolodensky = i; + } else if (isProjectedStep(stepName)) { + if (iProjStep >= 0) { + unexpectedStructure = true; + break; + } + iProjStep = i; + } else { + unexpectedStructure = true; + break; + } + } + + if (!unexpectedStructure) { + if (iFirstGeogStep == 0 && iSecondGeogStep < 0 && iProjStep < 0 && + iHelmert < 0 && iFirstCart < 0 && iMolodensky < 0 && + (iFirstUnitConvert < 0 || iSecondUnitConvert < 0) && + (iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) { + const bool ignoreVUnits = false; + return d->buildBoundOrCompoundCRSIfNeeded( + 0, d->buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, + iFirstAxisSwap, ignoreVUnits, false)); + } + if (iProjStep >= 0 && !d->steps_[iProjStep].inverted && + (iFirstGeogStep < 0 || iFirstGeogStep + 1 == iProjStep) && + iMolodensky < 0 && iSecondGeogStep < 0 && iFirstCart < 0 && + iHelmert < 0) { + if (iFirstGeogStep < 0) + iFirstGeogStep = iProjStep; + const bool ignoreVUnits = true; + return d->buildBoundOrCompoundCRSIfNeeded( + iProjStep, + d->buildProjectedCRS( + iProjStep, + d->buildGeographicCRS( + iFirstGeogStep, + iFirstUnitConvert < iFirstGeogStep ? iFirstUnitConvert + : -1, + iFirstAxisSwap < iFirstGeogStep ? iFirstAxisSwap : -1, + ignoreVUnits, true), + iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert + : iFirstUnitConvert, + iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap + : iFirstAxisSwap)); + } + if (d->steps_.size() == 1 && iHelmert == 0) { + return d->buildHelmertTransformation(iHelmert); + } + + if (iProjStep < 0 && iHelmert > 0 && iMolodensky < 0 && + (iFirstGeogStep < 0 || iFirstGeogStep == iFirstCart - 1 || + (iFirstGeogStep == iSecondCart + 1 && iSecondGeogStep < 0)) && + iFirstCart == iHelmert - 1 && iSecondCart == iHelmert + 1 && + (iSecondGeogStep < 0 || iSecondGeogStep == iSecondCart + 1) && + !d->steps_[iFirstCart].inverted && + d->steps_[iSecondCart].inverted && iFirstAxisSwap < iHelmert && + iFirstUnitConvert < iHelmert && + (iSecondAxisSwap < 0 || iSecondAxisSwap > iHelmert) && + (iSecondUnitConvert < 0 || iSecondUnitConvert > iHelmert)) { + return d->buildHelmertTransformation( + iHelmert, iFirstAxisSwap, iFirstUnitConvert, + iFirstGeogStep >= 0 && iFirstGeogStep == iFirstCart - 1 + ? iFirstGeogStep + : iFirstCart, + iFirstGeogStep == iSecondCart + 1 + ? iFirstGeogStep + : iSecondGeogStep == iSecondCart + 1 ? iSecondGeogStep + : iSecondCart, + iSecondAxisSwap, iSecondUnitConvert); + } + + if (d->steps_.size() == 1 && iMolodensky == 0) { + return d->buildMolodenskyTransformation(iMolodensky); + } + + if (iProjStep < 0 && iHelmert < 0 && iMolodensky > 0 && + (iFirstGeogStep < 0 || iFirstGeogStep == iMolodensky - 1 || + (iFirstGeogStep == iMolodensky + 1 && iSecondGeogStep < 0)) && + (iSecondGeogStep < 0 || iSecondGeogStep == iMolodensky + 1) && + iFirstAxisSwap < iMolodensky && iFirstUnitConvert < iMolodensky && + (iSecondAxisSwap < 0 || iSecondAxisSwap > iMolodensky) && + (iSecondUnitConvert < 0 || iSecondUnitConvert > iMolodensky)) { + return d->buildMolodenskyTransformation( + iMolodensky, iFirstAxisSwap, iFirstUnitConvert, + iFirstGeogStep >= 0 && iFirstGeogStep == iMolodensky - 1 + ? iFirstGeogStep + : iMolodensky, + iFirstGeogStep == iMolodensky + 1 + ? iFirstGeogStep + : iSecondGeogStep == iMolodensky + 1 ? iSecondGeogStep + : iMolodensky, + iSecondAxisSwap, iSecondUnitConvert); + } + } + + struct Logger { + std::string msg{}; + + // cppcheck-suppress functionStatic + void setMessage(const char *msgIn) noexcept { + try { + msg = msgIn; + } catch (const std::exception &) { + } + } + + static void log(void *user_data, int level, const char *msg) { + if (level == PJ_LOG_ERROR) { + static_cast(user_data)->setMessage(msg); + } + } + }; + + // If the structure is not recognized, then try to instanciate the + // pipeline, and if successful, wrap it in a PROJBasedOperation + Logger logger; + auto pj_context = proj_context_create(); + if (!pj_context) { + throw ParsingException("out of memory"); + } + proj_log_func(pj_context, &logger, Logger::log); + proj_context_use_proj4_init_rules(pj_context, d->usePROJ4InitRules_); + auto pj = proj_create(pj_context, projString.c_str()); + bool valid = pj != nullptr; + proj_destroy(pj); + + if (!valid && logger.msg.empty()) { + logger.setMessage(proj_errno_string(proj_context_errno(pj_context))); + } + + proj_context_destroy(pj_context); + + if (!valid) { + throw ParsingException(logger.msg); + } + + auto props = PropertyMap(); + if (!d->title_.empty()) { + props.set(IdentifiedObject::NAME_KEY, d->title_); + } + return operation::SingleOperation::createPROJBased(props, projString, + nullptr, nullptr, {}); +} + +} // namespace io +NS_PROJ_END diff --git a/src/iso19111/metadata.cpp b/src/iso19111/metadata.cpp new file mode 100644 index 00000000..2be9dac3 --- /dev/null +++ b/src/iso19111/metadata.cpp @@ -0,0 +1,1285 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 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/metadata.hpp" +#include "proj/common.hpp" +#include "proj/io.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include +#include +#include +#include + +using namespace NS_PROJ::internal; +using namespace NS_PROJ::io; +using namespace NS_PROJ::util; + +#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; +}} +#endif + +NS_PROJ_START +namespace metadata { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Citation::Private { + optional title{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Citation::Citation() : d(internal::make_unique()) {} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Constructs a citation by its title. */ +Citation::Citation(const std::string &titleIn) + : d(internal::make_unique()) { + d->title = titleIn; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Citation::Citation(const Citation &other) + : d(internal::make_unique(*(other.d))) {} + +// --------------------------------------------------------------------------- + +Citation::~Citation() = default; + +// --------------------------------------------------------------------------- + +Citation &Citation::operator=(const Citation &other) { + if (this != &other) { + *d = *other.d; + } + return *this; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns the name by which the cited resource is known. */ +const optional &Citation::title() PROJ_CONST_DEFN { + return d->title; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeographicExtent::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +GeographicExtent::GeographicExtent() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeographicExtent::~GeographicExtent() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeographicBoundingBox::Private { + double west_{}; + double south_{}; + double east_{}; + double north_{}; + + Private(double west, double south, double east, double north) + : west_(west), south_(south), east_(east), north_(north) {} + + bool intersects(const Private &other) const; + + std::unique_ptr intersection(const Private &other) const; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +GeographicBoundingBox::GeographicBoundingBox(double west, double south, + double east, double north) + : GeographicExtent(), + d(internal::make_unique(west, south, east, north)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeographicBoundingBox::~GeographicBoundingBox() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns the western-most coordinate of the limit of the dataset + * extent. + * + * The unit is degrees. + * + * If eastBoundLongitude < westBoundLongitude(), then the bounding box crosses + * the anti-meridian. + */ +double GeographicBoundingBox::westBoundLongitude() PROJ_CONST_DEFN { + return d->west_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the southern-most coordinate of the limit of the dataset + * extent. + * + * The unit is degrees. + */ +double GeographicBoundingBox::southBoundLatitude() PROJ_CONST_DEFN { + return d->south_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the eastern-most coordinate of the limit of the dataset + * extent. + * + * The unit is degrees. + * + * If eastBoundLongitude < westBoundLongitude(), then the bounding box crosses + * the anti-meridian. + */ +double GeographicBoundingBox::eastBoundLongitude() PROJ_CONST_DEFN { + return d->east_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the northern-most coordinate of the limit of the dataset + * extent. + * + * The unit is degrees. + */ +double GeographicBoundingBox::northBoundLatitude() PROJ_CONST_DEFN { + return d->north_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeographicBoundingBox. + * + * If east < west, then the bounding box crosses the anti-meridian. + * + * @param west Western-most coordinate of the limit of the dataset extent (in + * degrees). + * @param south Southern-most coordinate of the limit of the dataset extent (in + * degrees). + * @param east Eastern-most coordinate of the limit of the dataset extent (in + * degrees). + * @param north Northern-most coordinate of the limit of the dataset extent (in + * degrees). + * @return a new GeographicBoundingBox. + */ +GeographicBoundingBoxNNPtr GeographicBoundingBox::create(double west, + double south, + double east, + double north) { + return GeographicBoundingBox::nn_make_shared( + west, south, east, north); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeographicBoundingBox::_isEquivalentTo( + const util::IComparable *other, util::IComparable::Criterion) const { + auto otherExtent = dynamic_cast(other); + if (!otherExtent) + return false; + return d->west_ == otherExtent->d->west_ && + d->south_ == otherExtent->d->south_ && + d->east_ == otherExtent->d->east_ && + d->north_ == otherExtent->d->north_; +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool GeographicBoundingBox::contains(const GeographicExtentNNPtr &other) const { + auto otherExtent = dynamic_cast(other.get()); + if (!otherExtent) { + return false; + } + const double W = d->west_; + const double E = d->east_; + const double N = d->north_; + const double S = d->south_; + const double oW = otherExtent->d->west_; + const double oE = otherExtent->d->east_; + const double oN = otherExtent->d->north_; + const double oS = otherExtent->d->south_; + + if (!(S <= oS && N >= oN)) { + return false; + } + + if (W == -180.0 && E == 180.0) { + return true; + } + + if (oW == -180.0 && oE == 180.0) { + return false; + } + + // Normal bounding box ? + if (W < E) { + if (oW < oE) { + return W <= oW && E >= oE; + } else { + return false; + } + // No: crossing antimerian + } else { + if (oW < oE) { + if (oW >= W) { + return true; + } else if (oE <= E) { + return true; + } else { + return false; + } + } else { + return W <= oW && E >= oE; + } + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeographicBoundingBox::Private::intersects(const Private &other) const { + const double W = west_; + const double E = east_; + const double N = north_; + const double S = south_; + const double oW = other.west_; + const double oE = other.east_; + const double oN = other.north_; + const double oS = other.south_; + + if (N < oS || S > oN) { + return false; + } + + if (W == -180.0 && E == 180.0 && oW > oE) { + return true; + } + + if (oW == -180.0 && oE == 180.0 && W > E) { + return true; + } + + // Normal bounding box ? + if (W <= E) { + if (oW < oE) { + if (std::max(W, oW) < std::min(E, oE)) { + return true; + } + return false; + } + + return intersects(Private(oW, oS, 180.0, oN)) || + intersects(Private(-180.0, oS, oE, oN)); + + // No: crossing antimerian + } else { + if (oW <= oE) { + return other.intersects(*this); + } + + return true; + } +} +//! @endcond + +bool GeographicBoundingBox::intersects( + const GeographicExtentNNPtr &other) const { + auto otherExtent = dynamic_cast(other.get()); + if (!otherExtent) { + return false; + } + return d->intersects(*(otherExtent->d)); +} + +// --------------------------------------------------------------------------- + +GeographicExtentPtr +GeographicBoundingBox::intersection(const GeographicExtentNNPtr &other) const { + auto otherExtent = dynamic_cast(other.get()); + if (!otherExtent) { + return nullptr; + } + auto ret = d->intersection(*(otherExtent->d)); + if (ret) { + auto bbox = GeographicBoundingBox::create(ret->west_, ret->south_, + ret->east_, ret->north_); + return bbox.as_nullable(); + } + return nullptr; +} + +//! @cond Doxygen_Suppress +std::unique_ptr +GeographicBoundingBox::Private::intersection(const Private &otherExtent) const { + const double W = west_; + const double E = east_; + const double N = north_; + const double S = south_; + const double oW = otherExtent.west_; + const double oE = otherExtent.east_; + const double oN = otherExtent.north_; + const double oS = otherExtent.south_; + + if (N < oS || S > oN) { + return nullptr; + } + + if (W == -180.0 && E == 180.0 && oW > oE) { + return internal::make_unique(oW, std::max(S, oS), oE, + std::min(N, oN)); + } + + if (oW == -180.0 && oE == 180.0 && W > E) { + return internal::make_unique(W, std::max(S, oS), E, + std::min(N, oN)); + } + + // Normal bounding box ? + if (W <= E) { + if (oW < oE) { + auto res = internal::make_unique( + std::max(W, oW), std::max(S, oS), std::min(E, oE), + std::min(N, oN)); + if (res->west_ < res->east_) { + return res; + } + return nullptr; + } + + // Return larger of two parts of the multipolygon + auto inter1 = intersection(Private(oW, oS, 180.0, oN)); + auto inter2 = intersection(Private(-180.0, oS, oE, oN)); + if (!inter1) { + return inter2; + } + if (!inter2) { + return inter1; + } + if (inter1->east_ - inter1->west_ > inter2->east_ - inter2->west_) { + return inter1; + } + return inter2; + // No: crossing antimerian + } else { + if (oW <= oE) { + return otherExtent.intersection(*this); + } + + return internal::make_unique(std::max(W, oW), std::max(S, oS), + std::min(E, oE), std::min(N, oN)); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct VerticalExtent::Private { + double minimum_{}; + double maximum_{}; + common::UnitOfMeasureNNPtr unit_; + + Private(double minimum, double maximum, + const common::UnitOfMeasureNNPtr &unit) + : minimum_(minimum), maximum_(maximum), unit_(unit) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +VerticalExtent::VerticalExtent(double minimumIn, double maximumIn, + const common::UnitOfMeasureNNPtr &unitIn) + : d(internal::make_unique(minimumIn, maximumIn, unitIn)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalExtent::~VerticalExtent() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns the minimum of the vertical extent. + */ +double VerticalExtent::minimumValue() PROJ_CONST_DEFN { return d->minimum_; } + +// --------------------------------------------------------------------------- + +/** \brief Returns the maximum of the vertical extent. + */ +double VerticalExtent::maximumValue() PROJ_CONST_DEFN { return d->maximum_; } + +// --------------------------------------------------------------------------- + +/** \brief Returns the unit of the vertical extent. + */ +common::UnitOfMeasureNNPtr &VerticalExtent::unit() PROJ_CONST_DEFN { + return d->unit_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalExtent. + * + * @param minimumIn minimum. + * @param maximumIn maximum. + * @param unitIn unit. + * @return a new VerticalExtent. + */ +VerticalExtentNNPtr +VerticalExtent::create(double minimumIn, double maximumIn, + const common::UnitOfMeasureNNPtr &unitIn) { + return VerticalExtent::nn_make_shared(minimumIn, maximumIn, + unitIn); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool VerticalExtent::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion) const { + auto otherExtent = dynamic_cast(other); + if (!otherExtent) + return false; + return d->minimum_ == otherExtent->d->minimum_ && + d->maximum_ == otherExtent->d->maximum_ && + d->unit_ == otherExtent->d->unit_; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent contains the other one. + */ +bool VerticalExtent::contains(const VerticalExtentNNPtr &other) const { + const double thisUnitToSI = d->unit_->conversionToSI(); + const double otherUnitToSI = other->d->unit_->conversionToSI(); + return d->minimum_ * thisUnitToSI <= other->d->minimum_ * otherUnitToSI && + d->maximum_ * thisUnitToSI >= other->d->maximum_ * otherUnitToSI; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent intersects the other one. + */ +bool VerticalExtent::intersects(const VerticalExtentNNPtr &other) const { + const double thisUnitToSI = d->unit_->conversionToSI(); + const double otherUnitToSI = other->d->unit_->conversionToSI(); + return d->minimum_ * thisUnitToSI <= other->d->maximum_ * otherUnitToSI && + d->maximum_ * thisUnitToSI >= other->d->minimum_ * otherUnitToSI; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct TemporalExtent::Private { + std::string start_{}; + std::string stop_{}; + + Private(const std::string &start, const std::string &stop) + : start_(start), stop_(stop) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +TemporalExtent::TemporalExtent(const std::string &startIn, + const std::string &stopIn) + : d(internal::make_unique(startIn, stopIn)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalExtent::~TemporalExtent() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns the start of the temporal extent. + */ +const std::string &TemporalExtent::start() PROJ_CONST_DEFN { return d->start_; } + +// --------------------------------------------------------------------------- + +/** \brief Returns the end of the temporal extent. + */ +const std::string &TemporalExtent::stop() PROJ_CONST_DEFN { return d->stop_; } + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a TemporalExtent. + * + * @param start start. + * @param stop stop. + * @return a new TemporalExtent. + */ +TemporalExtentNNPtr TemporalExtent::create(const std::string &start, + const std::string &stop) { + return TemporalExtent::nn_make_shared(start, stop); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool TemporalExtent::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion) const { + auto otherExtent = dynamic_cast(other); + if (!otherExtent) + return false; + return start() == otherExtent->start() && stop() == otherExtent->stop(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent contains the other one. + */ +bool TemporalExtent::contains(const TemporalExtentNNPtr &other) const { + return start() <= other->start() && stop() >= other->stop(); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent intersects the other one. + */ +bool TemporalExtent::intersects(const TemporalExtentNNPtr &other) const { + return start() <= other->stop() && stop() >= other->start(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Extent::Private { + optional description_{}; + std::vector geographicElements_{}; + std::vector verticalElements_{}; + std::vector temporalElements_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Extent::Extent() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +Extent::Extent(const Extent &other) + : d(internal::make_unique(*other.d)) {} + +// --------------------------------------------------------------------------- + +Extent::~Extent() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** Return a textual description of the extent. + * + * @return the description, or empty. + */ +const optional &Extent::description() PROJ_CONST_DEFN { + return d->description_; +} + +// --------------------------------------------------------------------------- + +/** Return the geographic element(s) of the extent + * + * @return the geographic element(s), or empty. + */ +const std::vector & +Extent::geographicElements() PROJ_CONST_DEFN { + return d->geographicElements_; +} + +// --------------------------------------------------------------------------- + +/** Return the vertical element(s) of the extent + * + * @return the vertical element(s), or empty. + */ +const std::vector & +Extent::verticalElements() PROJ_CONST_DEFN { + return d->verticalElements_; +} + +// --------------------------------------------------------------------------- + +/** Return the temporal element(s) of the extent + * + * @return the temporal element(s), or empty. + */ +const std::vector & +Extent::temporalElements() PROJ_CONST_DEFN { + return d->temporalElements_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Extent. + * + * @param descriptionIn Textual description, or empty. + * @param geographicElementsIn Geographic element(s), or empty. + * @param verticalElementsIn Vertical element(s), or empty. + * @param temporalElementsIn Temporal element(s), or empty. + * @return a new Extent. + */ +ExtentNNPtr +Extent::create(const optional &descriptionIn, + const std::vector &geographicElementsIn, + const std::vector &verticalElementsIn, + const std::vector &temporalElementsIn) { + auto extent = Extent::nn_make_shared(); + extent->assignSelf(extent); + extent->d->description_ = descriptionIn; + extent->d->geographicElements_ = geographicElementsIn; + extent->d->verticalElements_ = verticalElementsIn; + extent->d->temporalElements_ = temporalElementsIn; + return extent; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Extent from a bounding box + * + * @param west Western-most coordinate of the limit of the dataset extent (in + * degrees). + * @param south Southern-most coordinate of the limit of the dataset extent (in + * degrees). + * @param east Eastern-most coordinate of the limit of the dataset extent (in + * degrees). + * @param north Northern-most coordinate of the limit of the dataset extent (in + * degrees). + * @param descriptionIn Textual description, or empty. + * @return a new Extent. + */ +ExtentNNPtr +Extent::createFromBBOX(double west, double south, double east, double north, + const util::optional &descriptionIn) { + return create( + descriptionIn, + std::vector{ + nn_static_pointer_cast( + GeographicBoundingBox::create(west, south, east, north))}, + std::vector(), std::vector()); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool Extent::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherExtent = dynamic_cast(other); + bool ret = + (otherExtent && + description().has_value() == otherExtent->description().has_value() && + *description() == *otherExtent->description() && + d->geographicElements_.size() == + otherExtent->d->geographicElements_.size() && + d->verticalElements_.size() == + otherExtent->d->verticalElements_.size() && + d->temporalElements_.size() == + otherExtent->d->temporalElements_.size()); + if (ret) { + for (size_t i = 0; ret && i < d->geographicElements_.size(); ++i) { + ret = d->geographicElements_[i]->_isEquivalentTo( + otherExtent->d->geographicElements_[i].get(), criterion); + } + for (size_t i = 0; ret && i < d->verticalElements_.size(); ++i) { + ret = d->verticalElements_[i]->_isEquivalentTo( + otherExtent->d->verticalElements_[i].get(), criterion); + } + for (size_t i = 0; ret && i < d->temporalElements_.size(); ++i) { + ret = d->temporalElements_[i]->_isEquivalentTo( + otherExtent->d->temporalElements_[i].get(), criterion); + } + } + return ret; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent contains the other one. + * + * Behaviour only well specified if each sub-extent category as at most + * one element. + */ +bool Extent::contains(const ExtentNNPtr &other) const { + bool res = true; + if (d->geographicElements_.size() == 1 && + other->d->geographicElements_.size() == 1) { + res = d->geographicElements_[0]->contains( + other->d->geographicElements_[0]); + } + if (res && d->verticalElements_.size() == 1 && + other->d->verticalElements_.size() == 1) { + res = d->verticalElements_[0]->contains(other->d->verticalElements_[0]); + } + if (res && d->temporalElements_.size() == 1 && + other->d->temporalElements_.size() == 1) { + res = d->temporalElements_[0]->contains(other->d->temporalElements_[0]); + } + return res; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent intersects the other one. + * + * Behaviour only well specified if each sub-extent category as at most + * one element. + */ +bool Extent::intersects(const ExtentNNPtr &other) const { + bool res = true; + if (d->geographicElements_.size() == 1 && + other->d->geographicElements_.size() == 1) { + res = d->geographicElements_[0]->intersects( + other->d->geographicElements_[0]); + } + if (res && d->verticalElements_.size() == 1 && + other->d->verticalElements_.size() == 1) { + res = + d->verticalElements_[0]->intersects(other->d->verticalElements_[0]); + } + if (res && d->temporalElements_.size() == 1 && + other->d->temporalElements_.size() == 1) { + res = + d->temporalElements_[0]->intersects(other->d->temporalElements_[0]); + } + return res; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the intersection of this extent with another one. + * + * Behaviour only well specified if there is one single GeographicExtent + * in each object. + * Returns nullptr otherwise. + */ +ExtentPtr Extent::intersection(const ExtentNNPtr &other) const { + if (d->geographicElements_.size() == 1 && + other->d->geographicElements_.size() == 1) { + if (contains(other)) { + return other.as_nullable(); + } + auto self = util::nn_static_pointer_cast(shared_from_this()); + if (other->contains(self)) { + return self.as_nullable(); + } + auto geogIntersection = d->geographicElements_[0]->intersection( + other->d->geographicElements_[0]); + if (geogIntersection) { + return create(util::optional(), + std::vector{ + NN_NO_CHECK(geogIntersection)}, + std::vector{}, + std::vector{}); + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Identifier::Private { + optional authority_{}; + std::string code_{}; + optional codeSpace_{}; + optional version_{}; + optional description_{}; + optional uri_{}; + + Private() = default; + + Private(const std::string &codeIn, const PropertyMap &properties) + : code_(codeIn) { + setProperties(properties); + } + + private: + // cppcheck-suppress functionStatic + void setProperties(const PropertyMap &properties); +}; + +// --------------------------------------------------------------------------- + +void Identifier::Private::setProperties( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + { + const auto pVal = properties.get(AUTHORITY_KEY); + if (pVal) { + if (auto genVal = dynamic_cast(pVal->get())) { + if (genVal->type() == BoxedValue::Type::STRING) { + authority_ = Citation(genVal->stringValue()); + } else { + throw InvalidValueTypeException("Invalid value type for " + + AUTHORITY_KEY); + } + } else { + if (auto citation = + dynamic_cast(pVal->get())) { + authority_ = *citation; + } else { + throw InvalidValueTypeException("Invalid value type for " + + AUTHORITY_KEY); + } + } + } + } + + { + const auto pVal = properties.get(CODE_KEY); + if (pVal) { + if (auto genVal = dynamic_cast(pVal->get())) { + if (genVal->type() == BoxedValue::Type::INTEGER) { + code_ = toString(genVal->integerValue()); + } else if (genVal->type() == BoxedValue::Type::STRING) { + code_ = genVal->stringValue(); + } else { + throw InvalidValueTypeException("Invalid value type for " + + CODE_KEY); + } + } else { + throw InvalidValueTypeException("Invalid value type for " + + CODE_KEY); + } + } + } + + properties.getStringValue(CODESPACE_KEY, codeSpace_); + properties.getStringValue(VERSION_KEY, version_); + properties.getStringValue(DESCRIPTION_KEY, description_); + properties.getStringValue(URI_KEY, uri_); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +Identifier::Identifier(const std::string &codeIn, + const util::PropertyMap &properties) + : d(internal::make_unique(codeIn, properties)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +Identifier::Identifier() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +Identifier::Identifier(const Identifier &other) + : d(internal::make_unique(*(other.d))) {} + +// --------------------------------------------------------------------------- + +Identifier::~Identifier() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Identifier. + * + * @param codeIn Alphanumeric value identifying an instance in the codespace + * @param properties See \ref general_properties. + * Generally, the Identifier::CODESPACE_KEY should be set. + * @return a new Identifier. + */ +IdentifierNNPtr Identifier::create(const std::string &codeIn, + const PropertyMap &properties) { + return Identifier::nn_make_shared(codeIn, properties); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +IdentifierNNPtr +Identifier::createFromDescription(const std::string &descriptionIn) { + auto id = Identifier::nn_make_shared(); + id->d->description_ = descriptionIn; + return id; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return a citation for the organization responsible for definition and + * maintenance of the code. + * + * @return the citation for the authority, or empty. + */ +const optional &Identifier::authority() PROJ_CONST_DEFN { + return d->authority_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the alphanumeric value identifying an instance in the + * codespace. + * + * e.g. "4326" (for EPSG:4326 WGS 84 GeographicCRS) + * + * @return the code. + */ +const std::string &Identifier::code() PROJ_CONST_DEFN { return d->code_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the organization responsible for definition and maintenance of + * the code. + * + * e.g "EPSG" + * + * @return the authority codespace, or empty. + */ +const optional &Identifier::codeSpace() PROJ_CONST_DEFN { + return d->codeSpace_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the version identifier for the namespace. + * + * When appropriate, the edition is identified by the effective date, coded + * using ISO 8601 date format. + * + * @return the version or empty. + */ +const optional &Identifier::version() PROJ_CONST_DEFN { + return d->version_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the natural language description of the meaning of the code + * value. + * + * @return the description or empty. + */ +const optional &Identifier::description() PROJ_CONST_DEFN { + return d->description_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the URI of the identifier. + * + * @return the URI or empty. + */ +const optional &Identifier::uri() PROJ_CONST_DEFN { + return d->uri_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Identifier::_exportToWKT(WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; + const std::string &l_code = code(); + const std::string &l_codeSpace = *codeSpace(); + if (!l_codeSpace.empty() && !l_code.empty()) { + if (isWKT2) { + formatter->startNode(WKTConstants::ID, false); + formatter->addQuotedString(l_codeSpace); + try { + (void)std::stoi(l_code); + formatter->add(l_code); + } catch (const std::exception &) { + formatter->addQuotedString(l_code); + } + if (version().has_value()) { + auto l_version = *(version()); + try { + (void)c_locale_stod(l_version); + formatter->add(l_version); + } catch (const std::exception &) { + formatter->addQuotedString(l_version); + } + } + if (authority().has_value() && + *(authority()->title()) != l_codeSpace) { + formatter->startNode(WKTConstants::CITATION, false); + formatter->addQuotedString(*(authority()->title())); + formatter->endNode(); + } + if (uri().has_value()) { + formatter->startNode(WKTConstants::URI, false); + formatter->addQuotedString(*(uri())); + formatter->endNode(); + } + formatter->endNode(); + } else { + formatter->startNode(WKTConstants::AUTHORITY, false); + formatter->addQuotedString(l_codeSpace); + formatter->addQuotedString(l_code); + formatter->endNode(); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static bool isIgnoredChar(char ch) { + return ch == ' ' || ch == '_' || ch == '-' || ch == '/' || ch == '(' || + ch == ')' || ch == '.' || ch == '&'; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const struct utf8_to_lower { + const char *utf8; + char ascii; +} map_utf8_to_lower[] = { + {"\xc3\xa1", 'a'}, // a acute + {"\xc3\xa4", 'a'}, // a tremma + + {"\xc4\x9b", 'e'}, // e reverse circumflex + {"\xc3\xa8", 'e'}, // e grave + {"\xc3\xa9", 'e'}, // e acute + {"\xc3\xab", 'e'}, // e tremma + + {"\xc3\xad", 'i'}, // i grave + + {"\xc3\xb4", 'o'}, // o circumflex + {"\xc3\xb6", 'o'}, // o tremma + + {"\xc3\xa7", 'c'}, // c cedilla +}; + +static const struct utf8_to_lower *get_ascii_replacement(const char *c_str) { + for (const auto &pair : map_utf8_to_lower) { + if (*c_str == pair.utf8[0] && + strncmp(c_str, pair.utf8, strlen(pair.utf8)) == 0) { + return &pair; + } + } + return nullptr; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::string Identifier::canonicalizeName(const std::string &str) { + std::string res; + const char *c_str = str.c_str(); + for (size_t i = 0; c_str[i] != 0; ++i) { + const auto ch = c_str[i]; + if (ch == ' ' && c_str[i + 1] == '+' && c_str[i + 2] == ' ') { + i += 2; + continue; + } + if (ch == '1' && !res.empty() && + !(res.back() >= '0' && res.back() <= '9') && c_str[i + 1] == '9' && + c_str[i + 2] >= '0' && c_str[i + 2] <= '9') { + ++i; + continue; + } + if (static_cast(ch) > 127) { + const auto *replacement = get_ascii_replacement(c_str + i); + if (replacement) { + res.push_back(replacement->ascii); + i += strlen(replacement->utf8) - 1; + continue; + } + } + if (!isIgnoredChar(ch)) { + res.push_back(static_cast(::tolower(ch))); + } + } + return res; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether two names are considered equivalent. + * + * Two names are equivalent by removing any space, underscore, dash, slash, + * { or } character from them, and comparing in a case insensitive way. + */ +bool Identifier::isEquivalentName(const char *a, const char *b) noexcept { + size_t i = 0; + size_t j = 0; + char lastValidA = 0; + char lastValidB = 0; + while (a[i] != 0 && b[j] != 0) { + char aCh = a[i]; + char bCh = b[j]; + if (aCh == ' ' && a[i + 1] == '+' && a[i + 2] == ' ') { + i += 3; + continue; + } + if (bCh == ' ' && b[j + 1] == '+' && b[j + 2] == ' ') { + j += 3; + continue; + } + if (isIgnoredChar(aCh)) { + ++i; + continue; + } + if (isIgnoredChar(bCh)) { + ++j; + continue; + } + if (aCh == '1' && !(lastValidA >= '0' && lastValidA <= '9') && + a[i + 1] == '9' && a[i + 2] >= '0' && a[i + 2] <= '9') { + i += 2; + lastValidA = '9'; + continue; + } + if (bCh == '1' && !(lastValidB >= '0' && lastValidB <= '9') && + b[j + 1] == '9' && b[j + 2] >= '0' && b[j + 2] <= '9') { + j += 2; + lastValidB = '9'; + continue; + } + if (static_cast(aCh) > 127) { + const auto *replacement = get_ascii_replacement(a + i); + if (replacement) { + aCh = replacement->ascii; + i += strlen(replacement->utf8) - 1; + } + } + if (static_cast(bCh) > 127) { + const auto *replacement = get_ascii_replacement(b + j); + if (replacement) { + bCh = replacement->ascii; + j += strlen(replacement->utf8) - 1; + } + } + if (::tolower(aCh) != ::tolower(bCh)) { + return false; + } + lastValidA = aCh; + lastValidB = bCh; + ++i; + ++j; + } + while (a[i] != 0 && isIgnoredChar(a[i])) { + ++i; + } + while (b[j] != 0 && isIgnoredChar(b[j])) { + ++j; + } + return a[i] == b[j]; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct PositionalAccuracy::Private { + std::string value_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +PositionalAccuracy::PositionalAccuracy(const std::string &valueIn) + : d(internal::make_unique()) { + d->value_ = valueIn; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PositionalAccuracy::~PositionalAccuracy() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the value of the positional accuracy. + */ +const std::string &PositionalAccuracy::value() PROJ_CONST_DEFN { + return d->value_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a PositionalAccuracy. + * + * @param valueIn positional accuracy value. + * @return a new PositionalAccuracy. + */ +PositionalAccuracyNNPtr PositionalAccuracy::create(const std::string &valueIn) { + return PositionalAccuracy::nn_make_shared(valueIn); +} + +} // namespace metadata +NS_PROJ_END diff --git a/src/iso19111/static.cpp b/src/iso19111/static.cpp new file mode 100644 index 00000000..5de046f1 --- /dev/null +++ b/src/iso19111/static.cpp @@ -0,0 +1,644 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 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/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/coordinatesystem_internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +// We put all static definitions in the same compilation unit, and in +// increasing order of dependency, to avoid the "static initialization fiasco" +// See https://isocpp.org/wiki/faq/ctors#static-init-order + +using namespace NS_PROJ::crs; +using namespace NS_PROJ::datum; +using namespace NS_PROJ::io; +using namespace NS_PROJ::metadata; +using namespace NS_PROJ::util; + +NS_PROJ_START + +// --------------------------------------------------------------------------- + +/** \brief Key to set the authority citation of a metadata::Identifier. + * + * The value is to be provided as a string or a metadata::Citation. + */ +const std::string Identifier::AUTHORITY_KEY("authority"); + +/** \brief Key to set the code of a metadata::Identifier. + * + * The value is to be provided as a integer or a string. + */ +const std::string Identifier::CODE_KEY("code"); + +/** \brief Key to set the organization responsible for definition and + * maintenance of the code of a metadata::Identifier. + * + * The value is to be provided as a string. + */ +const std::string Identifier::CODESPACE_KEY("codespace"); + +/** \brief Key to set the version identifier for the namespace of a + * metadata::Identifier. + * + * The value is to be provided as a string. + */ +const std::string Identifier::VERSION_KEY("version"); + +/** \brief Key to set the natural language description of the meaning of the + * code value of a metadata::Identifier. + * + * The value is to be provided as a string. + */ +const std::string Identifier::DESCRIPTION_KEY("description"); + +/** \brief Key to set the URI of a metadata::Identifier. + * + * The value is to be provided as a string. + */ +const std::string Identifier::URI_KEY("uri"); + +/** \brief EPSG codespace. + */ +const std::string Identifier::EPSG("EPSG"); + +/** \brief OGC codespace. + */ +const std::string Identifier::OGC("OGC"); + +// --------------------------------------------------------------------------- + +/** \brief Key to set the name of a common::IdentifiedObject + * + * The value is to be provided as a string or metadata::IdentifierNNPtr. + */ +const std::string common::IdentifiedObject::NAME_KEY("name"); + +/** \brief Key to set the identifier(s) of a common::IdentifiedObject + * + * The value is to be provided as a common::IdentifierNNPtr or a + * util::ArrayOfBaseObjectNNPtr + * of common::IdentifierNNPtr. + */ +const std::string common::IdentifiedObject::IDENTIFIERS_KEY("identifiers"); + +/** \brief Key to set the alias(es) of a common::IdentifiedObject + * + * The value is to be provided as string, a util::GenericNameNNPtr or a + * util::ArrayOfBaseObjectNNPtr + * of util::GenericNameNNPtr. + */ +const std::string common::IdentifiedObject::ALIAS_KEY("alias"); + +/** \brief Key to set the remarks of a common::IdentifiedObject + * + * The value is to be provided as a string. + */ +const std::string common::IdentifiedObject::REMARKS_KEY("remarks"); + +/** \brief Key to set the deprecation flag of a common::IdentifiedObject + * + * The value is to be provided as a boolean. + */ +const std::string common::IdentifiedObject::DEPRECATED_KEY("deprecated"); + +// --------------------------------------------------------------------------- + +/** \brief Key to set the scope of a common::ObjectUsage + * + * The value is to be provided as a string. + */ +const std::string common::ObjectUsage::SCOPE_KEY("scope"); + +/** \brief Key to set the domain of validity of a common::ObjectUsage + * + * The value is to be provided as a common::ExtentNNPtr. + */ +const std::string + common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY("domainOfValidity"); + +/** \brief Key to set the object domain(s) of a common::ObjectUsage + * + * The value is to be provided as a common::ObjectDomainNNPtr or a + * util::ArrayOfBaseObjectNNPtr + * of common::ObjectDomainNNPtr. + */ +const std::string common::ObjectUsage::OBJECT_DOMAIN_KEY("objectDomain"); + +// --------------------------------------------------------------------------- + +/** \brief World extent. */ +const ExtentNNPtr + Extent::WORLD(Extent::createFromBBOX(-180, -90, 180, 90, + util::optional("World"))); + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::vector WKTConstants::constants_; + +const char *WKTConstants::createAndAddToConstantList(const char *text) { + WKTConstants::constants_.push_back(text); + return text; +} + +#define DEFINE_WKT_CONSTANT(x) \ + const std::string WKTConstants::x(createAndAddToConstantList(#x)) + +DEFINE_WKT_CONSTANT(GEOCCS); +DEFINE_WKT_CONSTANT(GEOGCS); +DEFINE_WKT_CONSTANT(DATUM); +DEFINE_WKT_CONSTANT(UNIT); +DEFINE_WKT_CONSTANT(SPHEROID); +DEFINE_WKT_CONSTANT(AXIS); +DEFINE_WKT_CONSTANT(PRIMEM); +DEFINE_WKT_CONSTANT(AUTHORITY); +DEFINE_WKT_CONSTANT(PROJCS); +DEFINE_WKT_CONSTANT(PROJECTION); +DEFINE_WKT_CONSTANT(PARAMETER); +DEFINE_WKT_CONSTANT(VERT_CS); +DEFINE_WKT_CONSTANT(VERT_DATUM); +DEFINE_WKT_CONSTANT(COMPD_CS); +DEFINE_WKT_CONSTANT(TOWGS84); +DEFINE_WKT_CONSTANT(EXTENSION); +DEFINE_WKT_CONSTANT(LOCAL_CS); +DEFINE_WKT_CONSTANT(LOCAL_DATUM); + +DEFINE_WKT_CONSTANT(GEODCRS); +DEFINE_WKT_CONSTANT(LENGTHUNIT); +DEFINE_WKT_CONSTANT(ANGLEUNIT); +DEFINE_WKT_CONSTANT(SCALEUNIT); +DEFINE_WKT_CONSTANT(TIMEUNIT); +DEFINE_WKT_CONSTANT(ELLIPSOID); +DEFINE_WKT_CONSTANT(CS); +DEFINE_WKT_CONSTANT(ID); +DEFINE_WKT_CONSTANT(PROJCRS); +DEFINE_WKT_CONSTANT(BASEGEODCRS); +DEFINE_WKT_CONSTANT(MERIDIAN); +DEFINE_WKT_CONSTANT(ORDER); +DEFINE_WKT_CONSTANT(ANCHOR); +DEFINE_WKT_CONSTANT(CONVERSION); +DEFINE_WKT_CONSTANT(METHOD); +DEFINE_WKT_CONSTANT(REMARK); +DEFINE_WKT_CONSTANT(GEOGCRS); +DEFINE_WKT_CONSTANT(BASEGEOGCRS); +DEFINE_WKT_CONSTANT(SCOPE); +DEFINE_WKT_CONSTANT(AREA); +DEFINE_WKT_CONSTANT(BBOX); +DEFINE_WKT_CONSTANT(CITATION); +DEFINE_WKT_CONSTANT(URI); +DEFINE_WKT_CONSTANT(VERTCRS); +DEFINE_WKT_CONSTANT(VDATUM); +DEFINE_WKT_CONSTANT(COMPOUNDCRS); +DEFINE_WKT_CONSTANT(PARAMETERFILE); +DEFINE_WKT_CONSTANT(COORDINATEOPERATION); +DEFINE_WKT_CONSTANT(SOURCECRS); +DEFINE_WKT_CONSTANT(TARGETCRS); +DEFINE_WKT_CONSTANT(INTERPOLATIONCRS); +DEFINE_WKT_CONSTANT(OPERATIONACCURACY); +DEFINE_WKT_CONSTANT(CONCATENATEDOPERATION); +DEFINE_WKT_CONSTANT(STEP); +DEFINE_WKT_CONSTANT(BOUNDCRS); +DEFINE_WKT_CONSTANT(ABRIDGEDTRANSFORMATION); +DEFINE_WKT_CONSTANT(DERIVINGCONVERSION); +DEFINE_WKT_CONSTANT(TDATUM); +DEFINE_WKT_CONSTANT(CALENDAR); +DEFINE_WKT_CONSTANT(TIMEORIGIN); +DEFINE_WKT_CONSTANT(TIMECRS); +DEFINE_WKT_CONSTANT(VERTICALEXTENT); +DEFINE_WKT_CONSTANT(TIMEEXTENT); +DEFINE_WKT_CONSTANT(USAGE); +DEFINE_WKT_CONSTANT(DYNAMIC); +DEFINE_WKT_CONSTANT(FRAMEEPOCH); +DEFINE_WKT_CONSTANT(MODEL); +DEFINE_WKT_CONSTANT(VELOCITYGRID); +DEFINE_WKT_CONSTANT(ENSEMBLE); +DEFINE_WKT_CONSTANT(MEMBER); +DEFINE_WKT_CONSTANT(ENSEMBLEACCURACY); +DEFINE_WKT_CONSTANT(DERIVEDPROJCRS); +DEFINE_WKT_CONSTANT(BASEPROJCRS); +DEFINE_WKT_CONSTANT(EDATUM); +DEFINE_WKT_CONSTANT(ENGCRS); +DEFINE_WKT_CONSTANT(PDATUM); +DEFINE_WKT_CONSTANT(PARAMETRICCRS); +DEFINE_WKT_CONSTANT(PARAMETRICUNIT); +DEFINE_WKT_CONSTANT(BASEVERTCRS); +DEFINE_WKT_CONSTANT(BASEENGCRS); +DEFINE_WKT_CONSTANT(BASEPARAMCRS); +DEFINE_WKT_CONSTANT(BASETIMECRS); + +DEFINE_WKT_CONSTANT(GEODETICCRS); +DEFINE_WKT_CONSTANT(GEODETICDATUM); +DEFINE_WKT_CONSTANT(PROJECTEDCRS); +DEFINE_WKT_CONSTANT(PRIMEMERIDIAN); +DEFINE_WKT_CONSTANT(GEOGRAPHICCRS); +DEFINE_WKT_CONSTANT(TRF); +DEFINE_WKT_CONSTANT(VERTICALCRS); +DEFINE_WKT_CONSTANT(VERTICALDATUM); +DEFINE_WKT_CONSTANT(VRF); +DEFINE_WKT_CONSTANT(TIMEDATUM); +DEFINE_WKT_CONSTANT(TEMPORALQUANTITY); +DEFINE_WKT_CONSTANT(ENGINEERINGDATUM); +DEFINE_WKT_CONSTANT(ENGINEERINGCRS); +DEFINE_WKT_CONSTANT(PARAMETRICDATUM); + +//! @endcond + +// --------------------------------------------------------------------------- + +namespace common { + +/** \brief "Empty"/"None", unit of measure of type NONE. */ +const UnitOfMeasure UnitOfMeasure::NONE("", 1.0, UnitOfMeasure::Type::NONE); + +/** \brief Scale unity, unit of measure of type SCALE. */ +const UnitOfMeasure UnitOfMeasure::SCALE_UNITY("unity", 1.0, + UnitOfMeasure::Type::SCALE, + Identifier::EPSG, "9201"); + +/** \brief Parts-per-million, unit of measure of type SCALE. */ +const UnitOfMeasure UnitOfMeasure::PARTS_PER_MILLION("parts per million", 1e-6, + UnitOfMeasure::Type::SCALE, + Identifier::EPSG, "9202"); + +/** \brief Metre, unit of measure of type LINEAR (SI unit). */ +const UnitOfMeasure UnitOfMeasure::METRE("metre", 1.0, + UnitOfMeasure::Type::LINEAR, + Identifier::EPSG, "9001"); + +/** \brief Degree, unit of measure of type ANGULAR. */ +const UnitOfMeasure UnitOfMeasure::DEGREE("degree", M_PI / 180., + UnitOfMeasure::Type::ANGULAR, + Identifier::EPSG, "9122"); + +/** \brief Arc-second, unit of measure of type ANGULAR. */ +const UnitOfMeasure UnitOfMeasure::ARC_SECOND("arc-second", M_PI / 180. / 3600., + UnitOfMeasure::Type::ANGULAR, + Identifier::EPSG, "9104"); + +/** \brief Grad, unit of measure of type ANGULAR. */ +const UnitOfMeasure UnitOfMeasure::GRAD("grad", M_PI / 200., + UnitOfMeasure::Type::ANGULAR, + Identifier::EPSG, "9105"); + +/** \brief Radian, unit of measure of type ANGULAR (SI unit). */ +const UnitOfMeasure UnitOfMeasure::RADIAN("radian", 1.0, + UnitOfMeasure::Type::ANGULAR, + Identifier::EPSG, "9101"); + +/** \brief Microradian, unit of measure of type ANGULAR. */ +const UnitOfMeasure UnitOfMeasure::MICRORADIAN("microradian", 1e-6, + UnitOfMeasure::Type::ANGULAR, + Identifier::EPSG, "9109"); + +/** \brief Second, unit of measure of type TIME (SI unit). */ +const UnitOfMeasure UnitOfMeasure::SECOND("second", 1.0, + UnitOfMeasure::Type::TIME, + Identifier::EPSG, "1029"); + +/** \brief Year, unit of measure of type TIME */ +const UnitOfMeasure UnitOfMeasure::YEAR("year", 31556925.445, + UnitOfMeasure::Type::TIME, + Identifier::EPSG, "1040"); + +/** \brief Metre per year, unit of measure of type LINEAR. */ +const UnitOfMeasure UnitOfMeasure::METRE_PER_YEAR("metres per year", + 1.0 / 31556925.445, + UnitOfMeasure::Type::LINEAR, + Identifier::EPSG, "1042"); + +/** \brief Arc-second per year, unit of measure of type ANGULAR. */ +const UnitOfMeasure UnitOfMeasure::ARC_SECOND_PER_YEAR( + "arc-seconds per year", M_PI / 180. / 3600. / 31556925.445, + UnitOfMeasure::Type::ANGULAR, Identifier::EPSG, "1043"); + +/** \brief Part-sper-million per year, unit of measure of type SCALE. */ +const UnitOfMeasure UnitOfMeasure::PPM_PER_YEAR("parts per million per year", + 1e-6 / 31556925.445, + UnitOfMeasure::Type::SCALE, + Identifier::EPSG, "1036"); + +} // namespace common + +// --------------------------------------------------------------------------- + +namespace cs { +std::map AxisDirection::registry; + +/** Axis positive direction is north. In a geodetic or projected CRS, north is + * defined through the geodetic reference frame. In an engineering CRS, north + * may be defined with respect to an engineering object rather than a + * geographical direction. */ +const AxisDirection AxisDirection::NORTH("north"); + +/** Axis positive direction is approximately north-north-east. */ +const AxisDirection AxisDirection::NORTH_NORTH_EAST("northNorthEast"); + +/** Axis positive direction is approximately north-east. */ +const AxisDirection AxisDirection::NORTH_EAST("northEast"); + +/** Axis positive direction is approximately east-north-east. */ +const AxisDirection AxisDirection::EAST_NORTH_EAST("eastNorthEast"); + +/** Axis positive direction is 90deg clockwise from north. */ +const AxisDirection AxisDirection::EAST("east"); + +/** Axis positive direction is approximately east-south-east. */ +const AxisDirection AxisDirection::EAST_SOUTH_EAST("eastSouthEast"); + +/** Axis positive direction is approximately south-east. */ +const AxisDirection AxisDirection::SOUTH_EAST("southEast"); + +/** Axis positive direction is approximately south-south-east. */ +const AxisDirection AxisDirection::SOUTH_SOUTH_EAST("southSouthEast"); + +/** Axis positive direction is 180deg clockwise from north. */ +const AxisDirection AxisDirection::SOUTH("south"); + +/** Axis positive direction is approximately south-south-west. */ +const AxisDirection AxisDirection::SOUTH_SOUTH_WEST("southSouthWest"); + +/** Axis positive direction is approximately south-west. */ +const AxisDirection AxisDirection::SOUTH_WEST("southWest"); + +/** Axis positive direction is approximately west-south-west. */ +const AxisDirection AxisDirection::WEST_SOUTH_WEST("westSouthWest"); + +/** Axis positive direction is 270deg clockwise from north. */ +const AxisDirection AxisDirection::WEST("west"); + +/** Axis positive direction is approximately west-north-west. */ +const AxisDirection AxisDirection::WEST_NORTH_WEST("westNorthWest"); + +/** Axis positive direction is approximately north-west. */ +const AxisDirection AxisDirection::NORTH_WEST("northWest"); + +/** Axis positive direction is approximately north-north-west. */ +const AxisDirection AxisDirection::NORTH_NORTH_WEST("northNorthWest"); + +/** Axis positive direction is up relative to gravity. */ +const AxisDirection AxisDirection::UP("up"); + +/** Axis positive direction is down relative to gravity. */ +const AxisDirection AxisDirection::DOWN("down"); + +/** Axis positive direction is in the equatorial plane from the centre of the + * modelled Earth towards the intersection of the equator with the prime + * meridian. */ +const AxisDirection AxisDirection::GEOCENTRIC_X("geocentricX"); + +/** Axis positive direction is in the equatorial plane from the centre of the + * modelled Earth towards the intersection of the equator and the meridian 90deg + * eastwards from the prime meridian. */ +const AxisDirection AxisDirection::GEOCENTRIC_Y("geocentricY"); + +/** Axis positive direction is from the centre of the modelled Earth parallel to + * its rotation axis and towards its north pole. */ +const AxisDirection AxisDirection::GEOCENTRIC_Z("geocentricZ"); + +/** Axis positive direction is towards higher pixel column. */ +const AxisDirection AxisDirection::COLUMN_POSITIVE("columnPositive"); + +/** Axis positive direction is towards lower pixel column. */ +const AxisDirection AxisDirection::COLUMN_NEGATIVE("columnNegative"); + +/** Axis positive direction is towards higher pixel row. */ +const AxisDirection AxisDirection::ROW_POSITIVE("rowPositive"); + +/** Axis positive direction is towards lower pixel row. */ +const AxisDirection AxisDirection::ROW_NEGATIVE("rowNegative"); + +/** Axis positive direction is right in display. */ +const AxisDirection AxisDirection::DISPLAY_RIGHT("displayRight"); + +/** Axis positive direction is left in display. */ +const AxisDirection AxisDirection::DISPLAY_LEFT("displayLeft"); + +/** Axis positive direction is towards top of approximately vertical display + * surface. */ +const AxisDirection AxisDirection::DISPLAY_UP("displayUp"); + +/** Axis positive direction is towards bottom of approximately vertical display + * surface. */ +const AxisDirection AxisDirection::DISPLAY_DOWN("displayDown"); + +/** Axis positive direction is forward; for an observer at the centre of the + * object this is will be towards its front, bow or nose. */ +const AxisDirection AxisDirection::FORWARD("forward"); + +/** Axis positive direction is aft; for an observer at the centre of the object + * this will be towards its back, stern or tail. */ +const AxisDirection AxisDirection::AFT("aft"); + +/** Axis positive direction is port; for an observer at the centre of the object + * this will be towards its left. */ +const AxisDirection AxisDirection::PORT("port"); + +/** Axis positive direction is starboard; for an observer at the centre of the + * object this will be towards its right. */ +const AxisDirection AxisDirection::STARBOARD("starboard"); + +/** Axis positive direction is clockwise from a specified direction. */ +const AxisDirection AxisDirection::CLOCKWISE("clockwise"); + +/** Axis positive direction is counter clockwise from a specified direction. */ +const AxisDirection AxisDirection::COUNTER_CLOCKWISE("counterClockwise"); + +/** Axis positive direction is towards the object. */ +const AxisDirection AxisDirection::TOWARDS("towards"); + +/** Axis positive direction is away from the object. */ +const AxisDirection AxisDirection::AWAY_FROM("awayFrom"); + +/** Temporal axis positive direction is towards the future. */ +const AxisDirection AxisDirection::FUTURE("future"); + +/** Temporal axis positive direction is towards the past. */ +const AxisDirection AxisDirection::PAST("past"); + +/** Axis positive direction is unspecified. */ +const AxisDirection AxisDirection::UNSPECIFIED("unspecified"); + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::map AxisDirectionWKT1::registry; + +const AxisDirectionWKT1 AxisDirectionWKT1::NORTH("NORTH"); +const AxisDirectionWKT1 AxisDirectionWKT1::EAST("EAST"); +const AxisDirectionWKT1 AxisDirectionWKT1::SOUTH("SOUTH"); +const AxisDirectionWKT1 AxisDirectionWKT1::WEST("WEST"); +const AxisDirectionWKT1 AxisDirectionWKT1::UP("UP"); +const AxisDirectionWKT1 AxisDirectionWKT1::DOWN("DOWN"); +const AxisDirectionWKT1 AxisDirectionWKT1::OTHER("OTHER"); + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const std::string AxisName::Longitude("Longitude"); +const std::string AxisName::Latitude("Latitude"); +const std::string AxisName::Easting("Easting"); +const std::string AxisName::Northing("Northing"); +const std::string AxisName::Westing("Westing"); +const std::string AxisName::Southing("Southing"); +const std::string AxisName::Ellipsoidal_height("Ellipsoidal height"); +const std::string AxisName::Geocentric_X("Geocentric X"); +const std::string AxisName::Geocentric_Y("Geocentric Y"); +const std::string AxisName::Geocentric_Z("Geocentric Z"); +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const std::string AxisAbbreviation::lon("lon"); +const std::string AxisAbbreviation::lat("lat"); +const std::string AxisAbbreviation::E("E"); +const std::string AxisAbbreviation::N("N"); +const std::string AxisAbbreviation::h("h"); +const std::string AxisAbbreviation::X("X"); +const std::string AxisAbbreviation::Y("Y"); +const std::string AxisAbbreviation::Z("Z"); +//! @endcond + +} // namespace cs + +// --------------------------------------------------------------------------- + +/** \brief The realization is by adjustment of a levelling network fixed to one + * or more tide gauges. */ +const RealizationMethod RealizationMethod::LEVELLING("levelling"); +/** \brief The realization is through a geoid height model or a height + * correction model. This is applied to a specified geodetic CRS. */ +const RealizationMethod RealizationMethod::GEOID("geoid"); +/** \brief The realization is through a tidal model or by tidal predictions. */ +const RealizationMethod RealizationMethod::TIDAL("tidal"); + +// --------------------------------------------------------------------------- + +/** \brief The Greenwich PrimeMeridian */ +const PrimeMeridianNNPtr + PrimeMeridian::GREENWICH(PrimeMeridian::createGREENWICH()); +/** \brief The "Reference Meridian" PrimeMeridian. + * + * This is a meridian of longitude 0 to be used with non-Earth bodies. */ +const PrimeMeridianNNPtr PrimeMeridian::REFERENCE_MERIDIAN( + PrimeMeridian::createREFERENCE_MERIDIAN()); +/** \brief The Paris PrimeMeridian */ +const PrimeMeridianNNPtr PrimeMeridian::PARIS(PrimeMeridian::createPARIS()); + +// --------------------------------------------------------------------------- + +/** \brief Earth celestial body */ +const std::string Ellipsoid::EARTH("Earth"); + +/** \brief The EPSG:7008 / "Clarke 1866" Ellipsoid */ +const EllipsoidNNPtr Ellipsoid::CLARKE_1866(Ellipsoid::createCLARKE_1866()); + +/** \brief The EPSG:7030 / "WGS 84" Ellipsoid */ +const EllipsoidNNPtr Ellipsoid::WGS84(Ellipsoid::createWGS84()); + +/** \brief The EPSG:7019 / "GRS 1980" Ellipsoid */ +const EllipsoidNNPtr Ellipsoid::GRS1980(Ellipsoid::createGRS1980()); + +// --------------------------------------------------------------------------- + +/** \brief The EPSG:6267 / "North_American_Datum_1927" GeodeticReferenceFrame */ +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::EPSG_6267( + GeodeticReferenceFrame::createEPSG_6267()); + +/** \brief The EPSG:6269 / "North_American_Datum_1983" GeodeticReferenceFrame */ +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::EPSG_6269( + GeodeticReferenceFrame::createEPSG_6269()); + +/** \brief The EPSG:6326 / "WGS_1984" GeodeticReferenceFrame */ +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::EPSG_6326( + GeodeticReferenceFrame::createEPSG_6326()); + +// --------------------------------------------------------------------------- + +/** \brief The proleptic Gregorian calendar. */ +const std::string + TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN("proleptic Gregorian"); + +// --------------------------------------------------------------------------- + +/** \brief EPSG:4978 / "WGS 84" Geocentric */ +const GeodeticCRSNNPtr GeodeticCRS::EPSG_4978(GeodeticCRS::createEPSG_4978()); + +// --------------------------------------------------------------------------- + +/** \brief EPSG:4267 / "NAD27" 2D GeographicCRS */ +const GeographicCRSNNPtr + GeographicCRS::EPSG_4267(GeographicCRS::createEPSG_4267()); + +/** \brief EPSG:4269 / "NAD83" 2D GeographicCRS */ +const GeographicCRSNNPtr + GeographicCRS::EPSG_4269(GeographicCRS::createEPSG_4269()); + +/** \brief EPSG:4326 / "WGS 84" 2D GeographicCRS */ +const GeographicCRSNNPtr + GeographicCRS::EPSG_4326(GeographicCRS::createEPSG_4326()); + +/** \brief OGC:CRS84 / "CRS 84" 2D GeographicCRS (long, lat)*/ +const GeographicCRSNNPtr + GeographicCRS::OGC_CRS84(GeographicCRS::createOGC_CRS84()); + +/** \brief EPSG:4807 / "NTF (Paris)" 2D GeographicCRS */ +const GeographicCRSNNPtr + GeographicCRS::EPSG_4807(GeographicCRS::createEPSG_4807()); + +/** \brief EPSG:4979 / "WGS 84" 3D GeographicCRS */ +const GeographicCRSNNPtr + GeographicCRS::EPSG_4979(GeographicCRS::createEPSG_4979()); + +// --------------------------------------------------------------------------- + +NS_PROJ_END diff --git a/src/iso19111/util.cpp b/src/iso19111/util.cpp new file mode 100644 index 00000000..ac6357a2 --- /dev/null +++ b/src/iso19111/util.cpp @@ -0,0 +1,689 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 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/util.hpp" +#include "proj/io.hpp" + +#include "proj/internal/internal.hpp" + +#include +#include +#include + +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; +}} +#endif + +NS_PROJ_START +namespace util { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct BaseObject::Private { + // This is a manual implementation of std::enable_shared_from_this<> that + // avoids publicly deriving from it. + std::weak_ptr self_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +BaseObject::BaseObject() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +BaseObject::~BaseObject() = default; + +// --------------------------------------------------------------------------- + +BaseObjectNNPtr::~BaseObjectNNPtr() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** Keep a reference to ourselves as an internal weak pointer. So that + * extractGeographicBaseObject() can later return a shared pointer on itself. + */ +void BaseObject::assignSelf(const BaseObjectNNPtr &self) { + assert(self.get() == this); + d->self_ = self.as_nullable(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +BaseObjectNNPtr BaseObject::shared_from_this() const { + // This assertion checks that in all code paths where we create a + // shared pointer, we took care of assigning it to self_, by calling + // assignSelf(); + return NN_CHECK_ASSERT(d->self_.lock()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct BoxedValue::Private { + BoxedValue::Type type_{BoxedValue::Type::INTEGER}; + std::string stringValue_{}; + int integerValue_{}; + bool booleanValue_{}; + + explicit Private(const std::string &stringValueIn) + : type_(BoxedValue::Type::STRING), stringValue_(stringValueIn) {} + + explicit Private(int integerValueIn) + : type_(BoxedValue::Type::INTEGER), integerValue_(integerValueIn) {} + + explicit Private(bool booleanValueIn) + : type_(BoxedValue::Type::BOOLEAN), booleanValue_(booleanValueIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +BoxedValue::BoxedValue() : d(internal::make_unique(std::string())) {} + +// --------------------------------------------------------------------------- + +/** \brief Constructs a BoxedValue from a string. + */ +BoxedValue::BoxedValue(const char *stringValueIn) + : d(internal::make_unique( + std::string(stringValueIn ? stringValueIn : ""))) {} + +// --------------------------------------------------------------------------- + +/** \brief Constructs a BoxedValue from a string. + */ +BoxedValue::BoxedValue(const std::string &stringValueIn) + : d(internal::make_unique(stringValueIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Constructs a BoxedValue from an integer. + */ +BoxedValue::BoxedValue(int integerValueIn) + : d(internal::make_unique(integerValueIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Constructs a BoxedValue from a boolean. + */ +BoxedValue::BoxedValue(bool booleanValueIn) + : d(internal::make_unique(booleanValueIn)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +BoxedValue::BoxedValue(const BoxedValue &other) + : d(internal::make_unique(*other.d)) {} + +// --------------------------------------------------------------------------- + +BoxedValue::~BoxedValue() = default; + +// --------------------------------------------------------------------------- + +const BoxedValue::Type &BoxedValue::type() const { return d->type_; } + +// --------------------------------------------------------------------------- + +const std::string &BoxedValue::stringValue() const { return d->stringValue_; } + +// --------------------------------------------------------------------------- + +int BoxedValue::integerValue() const { return d->integerValue_; } + +// --------------------------------------------------------------------------- + +bool BoxedValue::booleanValue() const { return d->booleanValue_; } +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ArrayOfBaseObject::Private { + std::vector values_{}; +}; +//! @endcond +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ArrayOfBaseObject::ArrayOfBaseObject() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +ArrayOfBaseObject::~ArrayOfBaseObject() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Adds an object to the array. + * + * @param obj the object to add. + */ +void ArrayOfBaseObject::add(const BaseObjectNNPtr &obj) { + d->values_.emplace_back(obj); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::vector::const_iterator ArrayOfBaseObject::begin() const { + return d->values_.begin(); +} + +// --------------------------------------------------------------------------- + +std::vector::const_iterator ArrayOfBaseObject::end() const { + return d->values_.end(); +} + +// --------------------------------------------------------------------------- + +bool ArrayOfBaseObject::empty() const { return d->values_.empty(); } +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ArrayOfBaseObject. + * + * @return a new ArrayOfBaseObject. + */ +ArrayOfBaseObjectNNPtr ArrayOfBaseObject::create() { + return ArrayOfBaseObject::nn_make_shared(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct PropertyMap::Private { + std::list> list_{}; + + // cppcheck-suppress functionStatic + void set(const std::string &key, const BoxedValueNNPtr &val) { + for (auto &pair : list_) { + if (pair.first == key) { + pair.second = val; + return; + } + } + list_.emplace_back(key, val); + } +}; +//! @endcond + +// --------------------------------------------------------------------------- + +PropertyMap::PropertyMap() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PropertyMap::PropertyMap(const PropertyMap &other) + : d(internal::make_unique(*(other.d))) {} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PropertyMap::~PropertyMap() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const BaseObjectNNPtr *PropertyMap::get(const std::string &key) const { + for (const auto &pair : d->list_) { + if (pair.first == key) { + return &(pair.second); + } + } + return nullptr; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Set a BaseObjectNNPtr as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, + const BaseObjectNNPtr &val) { + for (auto &pair : d->list_) { + if (pair.first == key) { + pair.second = val; + return *this; + } + } + d->list_.emplace_back(key, val); + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set a string as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, const std::string &val) { + d->set(key, util::nn_make_shared(val)); + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set a string as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, const char *val) { + d->set(key, util::nn_make_shared(val)); + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set a integer as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, int val) { + d->set(key, util::nn_make_shared(val)); + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set a boolean as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, bool val) { + d->set(key, util::nn_make_shared(val)); + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set a vector of strings as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, + const std::vector &arrayIn) { + ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create(); + for (const auto &str : arrayIn) { + array->add(util::nn_make_shared(str)); + } + return set(key, array); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool PropertyMap::getStringValue( + const std::string &key, + std::string &outVal) const // throw(InvalidValueTypeException) +{ + for (const auto &pair : d->list_) { + if (pair.first == key) { + auto genVal = dynamic_cast(pair.second.get()); + if (genVal && genVal->type() == BoxedValue::Type::STRING) { + outVal = genVal->stringValue(); + return true; + } + throw InvalidValueTypeException("Invalid value type for " + key); + } + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool PropertyMap::getStringValue( + const std::string &key, + optional &outVal) const // throw(InvalidValueTypeException) +{ + for (const auto &pair : d->list_) { + if (pair.first == key) { + auto genVal = dynamic_cast(pair.second.get()); + if (genVal && genVal->type() == BoxedValue::Type::STRING) { + outVal = genVal->stringValue(); + return true; + } + throw InvalidValueTypeException("Invalid value type for " + key); + } + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GenericName::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +GenericName::GenericName() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +GenericName::GenericName(const GenericName &other) + : d(internal::make_unique(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GenericName::~GenericName() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct NameSpace::Private { + GenericNamePtr name{}; + bool isGlobal{}; + std::string separator = std::string(":"); + std::string separatorHead = std::string(":"); +}; +//! @endcond + +// --------------------------------------------------------------------------- + +NameSpace::NameSpace(const GenericNamePtr &nameIn) + : d(internal::make_unique()) { + d->name = nameIn; +} + +// --------------------------------------------------------------------------- + +NameSpace::NameSpace(const NameSpace &other) + : d(internal::make_unique(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +NameSpace::~NameSpace() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this is a global namespace. */ +bool NameSpace::isGlobal() const { return d->isGlobal; } + +// --------------------------------------------------------------------------- + +NameSpaceNNPtr NameSpace::getGlobalFromThis() const { + NameSpaceNNPtr ns(NameSpace::nn_make_shared(*this)); + ns->d->isGlobal = true; + ns->d->name = LocalName::make_shared("global"); + return ns; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the name of this namespace. */ +const GenericNamePtr &NameSpace::name() const { return d->name; } + +// --------------------------------------------------------------------------- + +const std::string &NameSpace::separator() const { return d->separator; } + +// --------------------------------------------------------------------------- + +NameSpaceNNPtr NameSpace::createGLOBAL() { + NameSpaceNNPtr ns(NameSpace::nn_make_shared( + LocalName::make_shared("global"))); + ns->d->isGlobal = true; + return ns; +} + +// --------------------------------------------------------------------------- + +const NameSpaceNNPtr NameSpace::GLOBAL(NameSpace::createGLOBAL()); + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct LocalName::Private { + NameSpacePtr scope{}; + std::string name{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +LocalName::LocalName(const std::string &name) + : d(internal::make_unique()) { + d->name = name; +} + +// --------------------------------------------------------------------------- + +LocalName::LocalName(const NameSpacePtr &ns, const std::string &name) + : d(internal::make_unique()) { + d->scope = ns ? ns : static_cast(NameSpace::GLOBAL); + d->name = name; +} + +// --------------------------------------------------------------------------- + +LocalName::LocalName(const LocalName &other) + : GenericName(other), d(internal::make_unique(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +LocalName::~LocalName() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +const NameSpacePtr LocalName::scope() const { + if (d->scope) + return d->scope; + return NameSpace::GLOBAL; +} + +// --------------------------------------------------------------------------- + +GenericNameNNPtr LocalName::toFullyQualifiedName() const { + if (scope()->isGlobal()) + return LocalName::nn_make_shared(*this); + + return LocalName::nn_make_shared( + d->scope->getGlobalFromThis(), + d->scope->name()->toFullyQualifiedName()->toString() + + d->scope->separator() + d->name); +} + +// --------------------------------------------------------------------------- + +std::string LocalName::toString() const { return d->name; } + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a NameSpace. + * + * @param name name of the namespace. + * @param properties Properties. Allowed keys are "separator" and + * "separator.head". + * @return a new NameFactory. + */ +NameSpaceNNPtr NameFactory::createNameSpace(const GenericNameNNPtr &name, + const PropertyMap &properties) { + NameSpaceNNPtr ns(NameSpace::nn_make_shared(name)); + properties.getStringValue("separator", ns->d->separator); + properties.getStringValue("separator.head", ns->d->separatorHead); + + return ns; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a LocalName. + * + * @param scope scope. + * @param name string of the local name. + * @return a new LocalName. + */ +LocalNameNNPtr NameFactory::createLocalName(const NameSpacePtr &scope, + const std::string &name) { + return LocalName::nn_make_shared(scope, name); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GenericName. + * + * @param scope scope. + * @param parsedNames the components of the name. + * @return a new GenericName. + */ +GenericNameNNPtr +NameFactory::createGenericName(const NameSpacePtr &scope, + const std::vector &parsedNames) { + std::string name; + const std::string separator(scope ? scope->separator() + : NameSpace::GLOBAL->separator()); + bool first = true; + for (const auto &str : parsedNames) { + if (!first) + name += separator; + first = false; + name += str; + } + return LocalName::nn_make_shared(scope, name); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CodeList::~CodeList() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CodeList &CodeList::operator=(const CodeList &other) { + name_ = other.name_; + return *this; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Exception::Exception(const char *message) : msg_(message) {} + +// --------------------------------------------------------------------------- + +Exception::Exception(const std::string &message) : msg_(message) {} + +// --------------------------------------------------------------------------- + +Exception::Exception(const Exception &) = default; + +// --------------------------------------------------------------------------- + +Exception::~Exception() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** Return the exception text. */ +const char *Exception::what() const noexcept { return msg_.c_str(); } + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +InvalidValueTypeException::InvalidValueTypeException(const char *message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +InvalidValueTypeException::InvalidValueTypeException(const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +InvalidValueTypeException::~InvalidValueTypeException() = default; + +// --------------------------------------------------------------------------- + +InvalidValueTypeException::InvalidValueTypeException( + const InvalidValueTypeException &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +UnsupportedOperationException::UnsupportedOperationException( + const char *message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +UnsupportedOperationException::UnsupportedOperationException( + const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +UnsupportedOperationException::~UnsupportedOperationException() = default; + +// --------------------------------------------------------------------------- + +UnsupportedOperationException::UnsupportedOperationException( + const UnsupportedOperationException &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +IComparable::~IComparable() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether an object is equivalent to another one. + * @param other other object to compare to + * @param criterion comparaison criterion. + * @return true if objects are equivalent. + */ +bool IComparable::isEquivalentTo(const IComparable *other, + Criterion criterion) const { + return _isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +} // namespace util +NS_PROJ_END diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake index 7be6302b..237f26ec 100644 --- a/src/lib_proj.cmake +++ b/src/lib_proj.cmake @@ -54,219 +54,179 @@ endif() ############################################## ### library source list and include_list ### ############################################## -SET(SRC_LIBPROJ_PJ - nad_init.cpp - PJ_aea.cpp - PJ_aeqd.cpp - PJ_affine.cpp - PJ_airy.cpp - PJ_aitoff.cpp - PJ_august.cpp - PJ_axisswap.cpp - PJ_bacon.cpp - PJ_bertin1953.cpp - PJ_bipc.cpp - PJ_boggs.cpp - PJ_bonne.cpp - PJ_calcofi.cpp - PJ_cart.cpp - PJ_cass.cpp - PJ_cc.cpp - PJ_ccon.cpp - PJ_cea.cpp - PJ_chamb.cpp - PJ_collg.cpp - PJ_comill.cpp - PJ_crast.cpp - PJ_deformation.cpp - PJ_denoy.cpp - PJ_eck1.cpp - PJ_eck2.cpp - PJ_eck3.cpp - PJ_eck4.cpp - PJ_eck5.cpp - PJ_eqc.cpp - PJ_eqdc.cpp - PJ_eqearth.cpp - PJ_fahey.cpp - PJ_fouc_s.cpp - PJ_gall.cpp - PJ_geoc.cpp - PJ_geos.cpp - PJ_gins8.cpp - PJ_gnom.cpp - PJ_gn_sinu.cpp - PJ_goode.cpp - PJ_gstmerc.cpp - PJ_hammer.cpp - PJ_hatano.cpp - PJ_helmert.cpp - PJ_hgridshift.cpp - PJ_horner.cpp - PJ_igh.cpp - PJ_isea.cpp - PJ_imw_p.cpp - PJ_krovak.cpp - PJ_labrd.cpp - PJ_laea.cpp - PJ_lagrng.cpp - PJ_larr.cpp - PJ_lask.cpp - PJ_latlong.cpp - PJ_lcca.cpp - PJ_lcc.cpp - PJ_loxim.cpp - PJ_lsat.cpp - PJ_misrsom.cpp - PJ_mbt_fps.cpp - PJ_mbtfpp.cpp - PJ_mbtfpq.cpp - PJ_merc.cpp - PJ_mill.cpp - PJ_mod_ster.cpp - PJ_moll.cpp - PJ_molodensky.cpp - PJ_natearth.cpp - PJ_natearth2.cpp - PJ_nell.cpp - PJ_nell_h.cpp - PJ_nocol.cpp - PJ_nsper.cpp - PJ_nzmg.cpp - PJ_ob_tran.cpp - PJ_ocea.cpp - PJ_oea.cpp - PJ_omerc.cpp - PJ_ortho.cpp - PJ_patterson.cpp - PJ_pipeline.cpp - PJ_poly.cpp - PJ_putp2.cpp - PJ_putp3.cpp - PJ_putp4p.cpp - PJ_putp5.cpp - PJ_putp6.cpp - PJ_qsc.cpp - PJ_robin.cpp - PJ_rpoly.cpp - PJ_sch.cpp - PJ_sconics.cpp - PJ_somerc.cpp - PJ_sterea.cpp - PJ_stere.cpp - PJ_sts.cpp - PJ_tcc.cpp - PJ_tcea.cpp - PJ_times.cpp - PJ_tmerc.cpp - PJ_tobmerc.cpp - PJ_tpeqd.cpp - PJ_unitconvert.cpp - PJ_urm5.cpp - PJ_urmfps.cpp - PJ_vandg.cpp - PJ_vandg2.cpp - PJ_vandg4.cpp - PJ_vgridshift.cpp - PJ_wag2.cpp - PJ_wag3.cpp - PJ_wag7.cpp - PJ_wink1.cpp - PJ_wink2.cpp - proj_etmerc.cpp + +SET(SRC_LIBPROJ_PROJECTIONS + projections/PJ_aeqd.cpp + projections/PJ_gnom.cpp + projections/PJ_laea.cpp + projections/PJ_mod_ster.cpp + projections/PJ_nsper.cpp + projections/PJ_nzmg.cpp + projections/PJ_ortho.cpp + projections/PJ_stere.cpp + projections/PJ_sterea.cpp + projections/PJ_aea.cpp + projections/PJ_bipc.cpp + projections/PJ_bonne.cpp + projections/PJ_eqdc.cpp + projections/PJ_isea.cpp + projections/PJ_ccon.cpp + projections/PJ_imw_p.cpp + projections/PJ_krovak.cpp + projections/PJ_lcc.cpp + projections/PJ_poly.cpp + projections/PJ_rpoly.cpp + projections/PJ_sconics.cpp + projections/proj_rouss.cpp + projections/PJ_cass.cpp + projections/PJ_cc.cpp + projections/PJ_cea.cpp + projections/PJ_eqc.cpp + projections/PJ_gall.cpp + projections/PJ_labrd.cpp + projections/PJ_lsat.cpp + projections/PJ_misrsom.cpp + projections/PJ_merc.cpp + projections/PJ_mill.cpp + projections/PJ_ocea.cpp + projections/PJ_omerc.cpp + projections/PJ_somerc.cpp + projections/PJ_tcc.cpp + projections/PJ_tcea.cpp + projections/PJ_times.cpp + projections/PJ_tmerc.cpp + projections/PJ_tobmerc.cpp + projections/PJ_airy.cpp + projections/PJ_aitoff.cpp + projections/PJ_august.cpp + projections/PJ_bacon.cpp + projections/PJ_bertin1953.cpp + projections/PJ_chamb.cpp + projections/PJ_hammer.cpp + projections/PJ_lagrng.cpp + projections/PJ_larr.cpp + projections/PJ_lask.cpp + projections/PJ_latlong.cpp + projections/PJ_nocol.cpp + projections/PJ_ob_tran.cpp + projections/PJ_oea.cpp + projections/PJ_tpeqd.cpp + projections/PJ_vandg.cpp + projections/PJ_vandg2.cpp + projections/PJ_vandg4.cpp + projections/PJ_wag7.cpp + projections/PJ_lcca.cpp + projections/PJ_geos.cpp + projections/proj_etmerc.cpp + projections/PJ_boggs.cpp + projections/PJ_collg.cpp + projections/PJ_comill.cpp + projections/PJ_crast.cpp + projections/PJ_denoy.cpp + projections/PJ_eck1.cpp + projections/PJ_eck2.cpp + projections/PJ_eck3.cpp + projections/PJ_eck4.cpp + projections/PJ_eck5.cpp + projections/PJ_fahey.cpp + projections/PJ_fouc_s.cpp + projections/PJ_gins8.cpp + projections/PJ_gstmerc.cpp + projections/PJ_gn_sinu.cpp + projections/PJ_goode.cpp + projections/PJ_igh.cpp + projections/PJ_hatano.cpp + projections/PJ_loxim.cpp + projections/PJ_mbt_fps.cpp + projections/PJ_mbtfpp.cpp + projections/PJ_mbtfpq.cpp + projections/PJ_moll.cpp + projections/PJ_nell.cpp + projections/PJ_nell_h.cpp + projections/PJ_patterson.cpp + projections/PJ_putp2.cpp + projections/PJ_putp3.cpp + projections/PJ_putp4p.cpp + projections/PJ_putp5.cpp + projections/PJ_putp6.cpp + projections/PJ_qsc.cpp + projections/PJ_robin.cpp + projections/PJ_sch.cpp + projections/PJ_sts.cpp + projections/PJ_urm5.cpp + projections/PJ_urmfps.cpp + projections/PJ_wag2.cpp + projections/PJ_wag3.cpp + projections/PJ_wink1.cpp + projections/PJ_wink2.cpp + projections/PJ_healpix.cpp + projections/PJ_natearth.cpp + projections/PJ_natearth2.cpp + projections/PJ_calcofi.cpp + projections/PJ_eqearth.cpp +) + +SET(SRC_LIBPROJ_CONVERSIONS + conversions/PJ_axisswap.cpp + conversions/PJ_cart.cpp + conversions/PJ_geoc.cpp + conversions/pj_geocent.cpp + conversions/PJ_unitconvert.cpp +) + +SET(SRC_LIBPROJ_TRANSFORMATIONS + transformations/PJ_affine.cpp + transformations/PJ_deformation.cpp + transformations/PJ_helmert.cpp + transformations/PJ_hgridshift.cpp + transformations/PJ_horner.cpp + transformations/PJ_molodensky.cpp + transformations/PJ_vgridshift.cpp +) + +SET(SRC_LIBPROJ_ISO19111 + iso19111/static.cpp + iso19111/util.cpp + iso19111/metadata.cpp + iso19111/common.cpp + 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 ) SET(SRC_LIBPROJ_CORE - aasincos.cpp - adjlon.cpp - bch2bps.cpp - bchgen.cpp - biveval.cpp - dmstor.cpp - emess.cpp - emess.h - geocent.cpp - geocent.h - geodesic.cpp - mk_cheby.cpp - nad_cvt.cpp - nad_init.cpp - nad_intr.cpp - pj_apply_gridshift.cpp - pj_apply_vgridshift.cpp - pj_auth.cpp - pj_ctx.cpp + pj_list.h proj_internal.h proj_math.h projects.h + aasincos.cpp adjlon.cpp bch2bps.cpp bchgen.cpp + biveval.cpp dmstor.cpp mk_cheby.cpp pj_auth.cpp + pj_deriv.cpp pj_ell_set.cpp pj_ellps.cpp pj_errno.cpp + pj_factors.cpp pj_fwd.cpp pj_init.cpp pj_inv.cpp + pj_list.cpp pj_malloc.cpp pj_mlfn.cpp pj_msfn.cpp proj_mdist.cpp + pj_open_lib.cpp pj_param.cpp pj_phi2.cpp pj_pr_list.cpp + pj_qsfn.cpp pj_strerrno.cpp + pj_tsfn.cpp pj_units.cpp pj_ctx.cpp pj_log.cpp pj_zpoly1.cpp rtodms.cpp + vector1.cpp pj_release.cpp pj_gauss.cpp pj_fileapi.cpp - pj_datum_set.cpp - pj_datums.cpp - pj_deriv.cpp - pj_ell_set.cpp - pj_ellps.cpp - pj_errno.cpp - pj_factors.cpp - pj_fwd.cpp - pj_gauss.cpp - pj_gc_reader.cpp - pj_geocent.cpp - pj_gridcatalog.cpp - pj_gridinfo.cpp - pj_gridlist.cpp - PJ_healpix.cpp - pj_init.cpp - pj_initcache.cpp - pj_inv.cpp - pj_list.cpp - pj_list.h - pj_log.cpp - pj_malloc.cpp - pj_math.cpp - pj_mlfn.cpp - pj_msfn.cpp - pj_mutex.cpp - proj_4D_api.cpp + pj_gc_reader.cpp pj_gridcatalog.cpp + nad_cvt.cpp nad_init.cpp nad_intr.cpp + pj_apply_gridshift.cpp pj_datums.cpp pj_datum_set.cpp pj_transform.cpp + geocent.cpp geocent.h pj_utils.cpp pj_gridinfo.cpp pj_gridlist.cpp + jniproj.cpp pj_mutex.cpp pj_initcache.cpp pj_apply_vgridshift.cpp geodesic.cpp + pj_strtod.cpp pj_math.cpp + proj_4D_api.cpp PJ_pipeline.cpp pj_internal.cpp - proj_internal.h - pj_open_lib.cpp - pj_param.cpp - pj_phi2.cpp - pj_pr_list.cpp - pj_qsfn.cpp - pj_release.cpp - pj_strerrno.cpp - pj_transform.cpp - pj_tsfn.cpp - pj_units.cpp - pj_utils.cpp - pj_zpoly1.cpp - proj_mdist.cpp - proj_math.h - proj_rouss.cpp - rtodms.cpp - vector1.cpp - pj_strtod.cpp - pj_wkt1_generated_parser.c - pj_wkt2_generated_parser.c + pj_wkt_parser.hpp pj_wkt_parser.cpp + pj_wkt1_parser.h pj_wkt1_parser.cpp + pj_wkt1_generated_parser.h pj_wkt1_generated_parser.c + pj_wkt2_parser.h pj_wkt2_parser.cpp + pj_wkt2_generated_parser.h pj_wkt2_generated_parser.c ${CMAKE_CURRENT_BINARY_DIR}/proj_config.h ) -set(SRC_LIBPROJ_CPP - static.cpp - common.cpp - coordinateoperation.cpp - coordinatesystem.cpp - crs.cpp - datum.cpp - io.cpp - metadata.cpp - util.cpp - internal.cpp - factory.cpp - c_api.cpp - pj_wkt_parser.cpp - pj_wkt1_parser.cpp - pj_wkt2_parser.cpp -) - set(HEADERS_LIBPROJ proj_api.h proj.h @@ -278,8 +238,10 @@ set(HEADERS_LIBPROJ # Group source files for IDE source explorers (e.g. Visual Studio) source_group("Header Files" FILES ${HEADERS_LIBPROJ}) source_group("Source Files\\Core" FILES ${SRC_LIBPROJ_CORE}) -source_group("Source Files\\PJ" FILES ${SRC_LIBPROJ_PJ}) -source_group("Source Files\\C++" FILES ${SRC_LIBPROJ_CPP}) +source_group("Source Files\\Conversions" FILES ${SRC_LIBPROJ_CONVERSIONS}) +source_group("Source Files\\Projections" FILES ${SRC_LIBPROJ_PROJECTIONS}) +source_group("Source Files\\Transformations" FILES ${SRC_LIBPROJ_TRANSFORMATIONS}) +source_group("Source Files\\ISO19111" FILES ${SRC_LIBPROJ_ISO19111}) include_directories(${CMAKE_SOURCE_DIR}/include) @@ -313,7 +275,12 @@ endif(JNI_SUPPORT) ################################################# ## targets: libproj and proj_config.h ################################################# -set(ALL_LIBPROJ_SOURCES ${SRC_LIBPROJ_PJ} ${SRC_LIBPROJ_CORE} ${SRC_LIBPROJ_CPP}) +set(ALL_LIBPROJ_SOURCES ${SRC_LIBPROJ_CORE} + ${SRC_LIBPROJ_CONVERSIONS} + ${SRC_LIBPROJ_PROJECTIONS} + ${SRC_LIBPROJ_TRANSFORMATIONS} + ${SRC_LIBPROJ_ISO19111} +) set(ALL_LIBPROJ_HEADERS ${HEADERS_LIBPROJ} ) # Core targets configuration diff --git a/src/metadata.cpp b/src/metadata.cpp deleted file mode 100644 index 2be9dac3..00000000 --- a/src/metadata.cpp +++ /dev/null @@ -1,1285 +0,0 @@ -/****************************************************************************** - * - * Project: PROJ - * Purpose: ISO19111:2018 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/metadata.hpp" -#include "proj/common.hpp" -#include "proj/io.hpp" -#include "proj/util.hpp" - -#include "proj/internal/internal.hpp" -#include "proj/internal/io_internal.hpp" - -#include -#include -#include -#include - -using namespace NS_PROJ::internal; -using namespace NS_PROJ::io; -using namespace NS_PROJ::util; - -#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; -}} -#endif - -NS_PROJ_START -namespace metadata { - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct Citation::Private { - optional title{}; -}; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -Citation::Citation() : d(internal::make_unique()) {} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Constructs a citation by its title. */ -Citation::Citation(const std::string &titleIn) - : d(internal::make_unique()) { - d->title = titleIn; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -Citation::Citation(const Citation &other) - : d(internal::make_unique(*(other.d))) {} - -// --------------------------------------------------------------------------- - -Citation::~Citation() = default; - -// --------------------------------------------------------------------------- - -Citation &Citation::operator=(const Citation &other) { - if (this != &other) { - *d = *other.d; - } - return *this; -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Returns the name by which the cited resource is known. */ -const optional &Citation::title() PROJ_CONST_DEFN { - return d->title; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct GeographicExtent::Private {}; -//! @endcond - -// --------------------------------------------------------------------------- - -GeographicExtent::GeographicExtent() : d(internal::make_unique()) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -GeographicExtent::~GeographicExtent() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct GeographicBoundingBox::Private { - double west_{}; - double south_{}; - double east_{}; - double north_{}; - - Private(double west, double south, double east, double north) - : west_(west), south_(south), east_(east), north_(north) {} - - bool intersects(const Private &other) const; - - std::unique_ptr intersection(const Private &other) const; -}; -//! @endcond - -// --------------------------------------------------------------------------- - -GeographicBoundingBox::GeographicBoundingBox(double west, double south, - double east, double north) - : GeographicExtent(), - d(internal::make_unique(west, south, east, north)) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -GeographicBoundingBox::~GeographicBoundingBox() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Returns the western-most coordinate of the limit of the dataset - * extent. - * - * The unit is degrees. - * - * If eastBoundLongitude < westBoundLongitude(), then the bounding box crosses - * the anti-meridian. - */ -double GeographicBoundingBox::westBoundLongitude() PROJ_CONST_DEFN { - return d->west_; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns the southern-most coordinate of the limit of the dataset - * extent. - * - * The unit is degrees. - */ -double GeographicBoundingBox::southBoundLatitude() PROJ_CONST_DEFN { - return d->south_; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns the eastern-most coordinate of the limit of the dataset - * extent. - * - * The unit is degrees. - * - * If eastBoundLongitude < westBoundLongitude(), then the bounding box crosses - * the anti-meridian. - */ -double GeographicBoundingBox::eastBoundLongitude() PROJ_CONST_DEFN { - return d->east_; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns the northern-most coordinate of the limit of the dataset - * extent. - * - * The unit is degrees. - */ -double GeographicBoundingBox::northBoundLatitude() PROJ_CONST_DEFN { - return d->north_; -} - -// --------------------------------------------------------------------------- - -/** \brief Instanciate a GeographicBoundingBox. - * - * If east < west, then the bounding box crosses the anti-meridian. - * - * @param west Western-most coordinate of the limit of the dataset extent (in - * degrees). - * @param south Southern-most coordinate of the limit of the dataset extent (in - * degrees). - * @param east Eastern-most coordinate of the limit of the dataset extent (in - * degrees). - * @param north Northern-most coordinate of the limit of the dataset extent (in - * degrees). - * @return a new GeographicBoundingBox. - */ -GeographicBoundingBoxNNPtr GeographicBoundingBox::create(double west, - double south, - double east, - double north) { - return GeographicBoundingBox::nn_make_shared( - west, south, east, north); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool GeographicBoundingBox::_isEquivalentTo( - const util::IComparable *other, util::IComparable::Criterion) const { - auto otherExtent = dynamic_cast(other); - if (!otherExtent) - return false; - return d->west_ == otherExtent->d->west_ && - d->south_ == otherExtent->d->south_ && - d->east_ == otherExtent->d->east_ && - d->north_ == otherExtent->d->north_; -} -//! @endcond - -// --------------------------------------------------------------------------- - -bool GeographicBoundingBox::contains(const GeographicExtentNNPtr &other) const { - auto otherExtent = dynamic_cast(other.get()); - if (!otherExtent) { - return false; - } - const double W = d->west_; - const double E = d->east_; - const double N = d->north_; - const double S = d->south_; - const double oW = otherExtent->d->west_; - const double oE = otherExtent->d->east_; - const double oN = otherExtent->d->north_; - const double oS = otherExtent->d->south_; - - if (!(S <= oS && N >= oN)) { - return false; - } - - if (W == -180.0 && E == 180.0) { - return true; - } - - if (oW == -180.0 && oE == 180.0) { - return false; - } - - // Normal bounding box ? - if (W < E) { - if (oW < oE) { - return W <= oW && E >= oE; - } else { - return false; - } - // No: crossing antimerian - } else { - if (oW < oE) { - if (oW >= W) { - return true; - } else if (oE <= E) { - return true; - } else { - return false; - } - } else { - return W <= oW && E >= oE; - } - } -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool GeographicBoundingBox::Private::intersects(const Private &other) const { - const double W = west_; - const double E = east_; - const double N = north_; - const double S = south_; - const double oW = other.west_; - const double oE = other.east_; - const double oN = other.north_; - const double oS = other.south_; - - if (N < oS || S > oN) { - return false; - } - - if (W == -180.0 && E == 180.0 && oW > oE) { - return true; - } - - if (oW == -180.0 && oE == 180.0 && W > E) { - return true; - } - - // Normal bounding box ? - if (W <= E) { - if (oW < oE) { - if (std::max(W, oW) < std::min(E, oE)) { - return true; - } - return false; - } - - return intersects(Private(oW, oS, 180.0, oN)) || - intersects(Private(-180.0, oS, oE, oN)); - - // No: crossing antimerian - } else { - if (oW <= oE) { - return other.intersects(*this); - } - - return true; - } -} -//! @endcond - -bool GeographicBoundingBox::intersects( - const GeographicExtentNNPtr &other) const { - auto otherExtent = dynamic_cast(other.get()); - if (!otherExtent) { - return false; - } - return d->intersects(*(otherExtent->d)); -} - -// --------------------------------------------------------------------------- - -GeographicExtentPtr -GeographicBoundingBox::intersection(const GeographicExtentNNPtr &other) const { - auto otherExtent = dynamic_cast(other.get()); - if (!otherExtent) { - return nullptr; - } - auto ret = d->intersection(*(otherExtent->d)); - if (ret) { - auto bbox = GeographicBoundingBox::create(ret->west_, ret->south_, - ret->east_, ret->north_); - return bbox.as_nullable(); - } - return nullptr; -} - -//! @cond Doxygen_Suppress -std::unique_ptr -GeographicBoundingBox::Private::intersection(const Private &otherExtent) const { - const double W = west_; - const double E = east_; - const double N = north_; - const double S = south_; - const double oW = otherExtent.west_; - const double oE = otherExtent.east_; - const double oN = otherExtent.north_; - const double oS = otherExtent.south_; - - if (N < oS || S > oN) { - return nullptr; - } - - if (W == -180.0 && E == 180.0 && oW > oE) { - return internal::make_unique(oW, std::max(S, oS), oE, - std::min(N, oN)); - } - - if (oW == -180.0 && oE == 180.0 && W > E) { - return internal::make_unique(W, std::max(S, oS), E, - std::min(N, oN)); - } - - // Normal bounding box ? - if (W <= E) { - if (oW < oE) { - auto res = internal::make_unique( - std::max(W, oW), std::max(S, oS), std::min(E, oE), - std::min(N, oN)); - if (res->west_ < res->east_) { - return res; - } - return nullptr; - } - - // Return larger of two parts of the multipolygon - auto inter1 = intersection(Private(oW, oS, 180.0, oN)); - auto inter2 = intersection(Private(-180.0, oS, oE, oN)); - if (!inter1) { - return inter2; - } - if (!inter2) { - return inter1; - } - if (inter1->east_ - inter1->west_ > inter2->east_ - inter2->west_) { - return inter1; - } - return inter2; - // No: crossing antimerian - } else { - if (oW <= oE) { - return otherExtent.intersection(*this); - } - - return internal::make_unique(std::max(W, oW), std::max(S, oS), - std::min(E, oE), std::min(N, oN)); - } -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct VerticalExtent::Private { - double minimum_{}; - double maximum_{}; - common::UnitOfMeasureNNPtr unit_; - - Private(double minimum, double maximum, - const common::UnitOfMeasureNNPtr &unit) - : minimum_(minimum), maximum_(maximum), unit_(unit) {} -}; -//! @endcond - -// --------------------------------------------------------------------------- - -VerticalExtent::VerticalExtent(double minimumIn, double maximumIn, - const common::UnitOfMeasureNNPtr &unitIn) - : d(internal::make_unique(minimumIn, maximumIn, unitIn)) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -VerticalExtent::~VerticalExtent() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Returns the minimum of the vertical extent. - */ -double VerticalExtent::minimumValue() PROJ_CONST_DEFN { return d->minimum_; } - -// --------------------------------------------------------------------------- - -/** \brief Returns the maximum of the vertical extent. - */ -double VerticalExtent::maximumValue() PROJ_CONST_DEFN { return d->maximum_; } - -// --------------------------------------------------------------------------- - -/** \brief Returns the unit of the vertical extent. - */ -common::UnitOfMeasureNNPtr &VerticalExtent::unit() PROJ_CONST_DEFN { - return d->unit_; -} - -// --------------------------------------------------------------------------- - -/** \brief Instanciate a VerticalExtent. - * - * @param minimumIn minimum. - * @param maximumIn maximum. - * @param unitIn unit. - * @return a new VerticalExtent. - */ -VerticalExtentNNPtr -VerticalExtent::create(double minimumIn, double maximumIn, - const common::UnitOfMeasureNNPtr &unitIn) { - return VerticalExtent::nn_make_shared(minimumIn, maximumIn, - unitIn); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool VerticalExtent::_isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion) const { - auto otherExtent = dynamic_cast(other); - if (!otherExtent) - return false; - return d->minimum_ == otherExtent->d->minimum_ && - d->maximum_ == otherExtent->d->maximum_ && - d->unit_ == otherExtent->d->unit_; -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Returns whether this extent contains the other one. - */ -bool VerticalExtent::contains(const VerticalExtentNNPtr &other) const { - const double thisUnitToSI = d->unit_->conversionToSI(); - const double otherUnitToSI = other->d->unit_->conversionToSI(); - return d->minimum_ * thisUnitToSI <= other->d->minimum_ * otherUnitToSI && - d->maximum_ * thisUnitToSI >= other->d->maximum_ * otherUnitToSI; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns whether this extent intersects the other one. - */ -bool VerticalExtent::intersects(const VerticalExtentNNPtr &other) const { - const double thisUnitToSI = d->unit_->conversionToSI(); - const double otherUnitToSI = other->d->unit_->conversionToSI(); - return d->minimum_ * thisUnitToSI <= other->d->maximum_ * otherUnitToSI && - d->maximum_ * thisUnitToSI >= other->d->minimum_ * otherUnitToSI; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct TemporalExtent::Private { - std::string start_{}; - std::string stop_{}; - - Private(const std::string &start, const std::string &stop) - : start_(start), stop_(stop) {} -}; -//! @endcond - -// --------------------------------------------------------------------------- - -TemporalExtent::TemporalExtent(const std::string &startIn, - const std::string &stopIn) - : d(internal::make_unique(startIn, stopIn)) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -TemporalExtent::~TemporalExtent() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Returns the start of the temporal extent. - */ -const std::string &TemporalExtent::start() PROJ_CONST_DEFN { return d->start_; } - -// --------------------------------------------------------------------------- - -/** \brief Returns the end of the temporal extent. - */ -const std::string &TemporalExtent::stop() PROJ_CONST_DEFN { return d->stop_; } - -// --------------------------------------------------------------------------- - -/** \brief Instanciate a TemporalExtent. - * - * @param start start. - * @param stop stop. - * @return a new TemporalExtent. - */ -TemporalExtentNNPtr TemporalExtent::create(const std::string &start, - const std::string &stop) { - return TemporalExtent::nn_make_shared(start, stop); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool TemporalExtent::_isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion) const { - auto otherExtent = dynamic_cast(other); - if (!otherExtent) - return false; - return start() == otherExtent->start() && stop() == otherExtent->stop(); -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Returns whether this extent contains the other one. - */ -bool TemporalExtent::contains(const TemporalExtentNNPtr &other) const { - return start() <= other->start() && stop() >= other->stop(); -} - -// --------------------------------------------------------------------------- - -/** \brief Returns whether this extent intersects the other one. - */ -bool TemporalExtent::intersects(const TemporalExtentNNPtr &other) const { - return start() <= other->stop() && stop() >= other->start(); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct Extent::Private { - optional description_{}; - std::vector geographicElements_{}; - std::vector verticalElements_{}; - std::vector temporalElements_{}; -}; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -Extent::Extent() : d(internal::make_unique()) {} - -// --------------------------------------------------------------------------- - -Extent::Extent(const Extent &other) - : d(internal::make_unique(*other.d)) {} - -// --------------------------------------------------------------------------- - -Extent::~Extent() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** Return a textual description of the extent. - * - * @return the description, or empty. - */ -const optional &Extent::description() PROJ_CONST_DEFN { - return d->description_; -} - -// --------------------------------------------------------------------------- - -/** Return the geographic element(s) of the extent - * - * @return the geographic element(s), or empty. - */ -const std::vector & -Extent::geographicElements() PROJ_CONST_DEFN { - return d->geographicElements_; -} - -// --------------------------------------------------------------------------- - -/** Return the vertical element(s) of the extent - * - * @return the vertical element(s), or empty. - */ -const std::vector & -Extent::verticalElements() PROJ_CONST_DEFN { - return d->verticalElements_; -} - -// --------------------------------------------------------------------------- - -/** Return the temporal element(s) of the extent - * - * @return the temporal element(s), or empty. - */ -const std::vector & -Extent::temporalElements() PROJ_CONST_DEFN { - return d->temporalElements_; -} - -// --------------------------------------------------------------------------- - -/** \brief Instanciate a Extent. - * - * @param descriptionIn Textual description, or empty. - * @param geographicElementsIn Geographic element(s), or empty. - * @param verticalElementsIn Vertical element(s), or empty. - * @param temporalElementsIn Temporal element(s), or empty. - * @return a new Extent. - */ -ExtentNNPtr -Extent::create(const optional &descriptionIn, - const std::vector &geographicElementsIn, - const std::vector &verticalElementsIn, - const std::vector &temporalElementsIn) { - auto extent = Extent::nn_make_shared(); - extent->assignSelf(extent); - extent->d->description_ = descriptionIn; - extent->d->geographicElements_ = geographicElementsIn; - extent->d->verticalElements_ = verticalElementsIn; - extent->d->temporalElements_ = temporalElementsIn; - return extent; -} - -// --------------------------------------------------------------------------- - -/** \brief Instanciate a Extent from a bounding box - * - * @param west Western-most coordinate of the limit of the dataset extent (in - * degrees). - * @param south Southern-most coordinate of the limit of the dataset extent (in - * degrees). - * @param east Eastern-most coordinate of the limit of the dataset extent (in - * degrees). - * @param north Northern-most coordinate of the limit of the dataset extent (in - * degrees). - * @param descriptionIn Textual description, or empty. - * @return a new Extent. - */ -ExtentNNPtr -Extent::createFromBBOX(double west, double south, double east, double north, - const util::optional &descriptionIn) { - return create( - descriptionIn, - std::vector{ - nn_static_pointer_cast( - GeographicBoundingBox::create(west, south, east, north))}, - std::vector(), std::vector()); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool Extent::_isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion criterion) const { - auto otherExtent = dynamic_cast(other); - bool ret = - (otherExtent && - description().has_value() == otherExtent->description().has_value() && - *description() == *otherExtent->description() && - d->geographicElements_.size() == - otherExtent->d->geographicElements_.size() && - d->verticalElements_.size() == - otherExtent->d->verticalElements_.size() && - d->temporalElements_.size() == - otherExtent->d->temporalElements_.size()); - if (ret) { - for (size_t i = 0; ret && i < d->geographicElements_.size(); ++i) { - ret = d->geographicElements_[i]->_isEquivalentTo( - otherExtent->d->geographicElements_[i].get(), criterion); - } - for (size_t i = 0; ret && i < d->verticalElements_.size(); ++i) { - ret = d->verticalElements_[i]->_isEquivalentTo( - otherExtent->d->verticalElements_[i].get(), criterion); - } - for (size_t i = 0; ret && i < d->temporalElements_.size(); ++i) { - ret = d->temporalElements_[i]->_isEquivalentTo( - otherExtent->d->temporalElements_[i].get(), criterion); - } - } - return ret; -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Returns whether this extent contains the other one. - * - * Behaviour only well specified if each sub-extent category as at most - * one element. - */ -bool Extent::contains(const ExtentNNPtr &other) const { - bool res = true; - if (d->geographicElements_.size() == 1 && - other->d->geographicElements_.size() == 1) { - res = d->geographicElements_[0]->contains( - other->d->geographicElements_[0]); - } - if (res && d->verticalElements_.size() == 1 && - other->d->verticalElements_.size() == 1) { - res = d->verticalElements_[0]->contains(other->d->verticalElements_[0]); - } - if (res && d->temporalElements_.size() == 1 && - other->d->temporalElements_.size() == 1) { - res = d->temporalElements_[0]->contains(other->d->temporalElements_[0]); - } - return res; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns whether this extent intersects the other one. - * - * Behaviour only well specified if each sub-extent category as at most - * one element. - */ -bool Extent::intersects(const ExtentNNPtr &other) const { - bool res = true; - if (d->geographicElements_.size() == 1 && - other->d->geographicElements_.size() == 1) { - res = d->geographicElements_[0]->intersects( - other->d->geographicElements_[0]); - } - if (res && d->verticalElements_.size() == 1 && - other->d->verticalElements_.size() == 1) { - res = - d->verticalElements_[0]->intersects(other->d->verticalElements_[0]); - } - if (res && d->temporalElements_.size() == 1 && - other->d->temporalElements_.size() == 1) { - res = - d->temporalElements_[0]->intersects(other->d->temporalElements_[0]); - } - return res; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns the intersection of this extent with another one. - * - * Behaviour only well specified if there is one single GeographicExtent - * in each object. - * Returns nullptr otherwise. - */ -ExtentPtr Extent::intersection(const ExtentNNPtr &other) const { - if (d->geographicElements_.size() == 1 && - other->d->geographicElements_.size() == 1) { - if (contains(other)) { - return other.as_nullable(); - } - auto self = util::nn_static_pointer_cast(shared_from_this()); - if (other->contains(self)) { - return self.as_nullable(); - } - auto geogIntersection = d->geographicElements_[0]->intersection( - other->d->geographicElements_[0]); - if (geogIntersection) { - return create(util::optional(), - std::vector{ - NN_NO_CHECK(geogIntersection)}, - std::vector{}, - std::vector{}); - } - } - return nullptr; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct Identifier::Private { - optional authority_{}; - std::string code_{}; - optional codeSpace_{}; - optional version_{}; - optional description_{}; - optional uri_{}; - - Private() = default; - - Private(const std::string &codeIn, const PropertyMap &properties) - : code_(codeIn) { - setProperties(properties); - } - - private: - // cppcheck-suppress functionStatic - void setProperties(const PropertyMap &properties); -}; - -// --------------------------------------------------------------------------- - -void Identifier::Private::setProperties( - const PropertyMap &properties) // throw(InvalidValueTypeException) -{ - { - const auto pVal = properties.get(AUTHORITY_KEY); - if (pVal) { - if (auto genVal = dynamic_cast(pVal->get())) { - if (genVal->type() == BoxedValue::Type::STRING) { - authority_ = Citation(genVal->stringValue()); - } else { - throw InvalidValueTypeException("Invalid value type for " + - AUTHORITY_KEY); - } - } else { - if (auto citation = - dynamic_cast(pVal->get())) { - authority_ = *citation; - } else { - throw InvalidValueTypeException("Invalid value type for " + - AUTHORITY_KEY); - } - } - } - } - - { - const auto pVal = properties.get(CODE_KEY); - if (pVal) { - if (auto genVal = dynamic_cast(pVal->get())) { - if (genVal->type() == BoxedValue::Type::INTEGER) { - code_ = toString(genVal->integerValue()); - } else if (genVal->type() == BoxedValue::Type::STRING) { - code_ = genVal->stringValue(); - } else { - throw InvalidValueTypeException("Invalid value type for " + - CODE_KEY); - } - } else { - throw InvalidValueTypeException("Invalid value type for " + - CODE_KEY); - } - } - } - - properties.getStringValue(CODESPACE_KEY, codeSpace_); - properties.getStringValue(VERSION_KEY, version_); - properties.getStringValue(DESCRIPTION_KEY, description_); - properties.getStringValue(URI_KEY, uri_); -} - -//! @endcond - -// --------------------------------------------------------------------------- - -Identifier::Identifier(const std::string &codeIn, - const util::PropertyMap &properties) - : d(internal::make_unique(codeIn, properties)) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -// --------------------------------------------------------------------------- - -Identifier::Identifier() : d(internal::make_unique()) {} - -// --------------------------------------------------------------------------- - -Identifier::Identifier(const Identifier &other) - : d(internal::make_unique(*(other.d))) {} - -// --------------------------------------------------------------------------- - -Identifier::~Identifier() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instanciate a Identifier. - * - * @param codeIn Alphanumeric value identifying an instance in the codespace - * @param properties See \ref general_properties. - * Generally, the Identifier::CODESPACE_KEY should be set. - * @return a new Identifier. - */ -IdentifierNNPtr Identifier::create(const std::string &codeIn, - const PropertyMap &properties) { - return Identifier::nn_make_shared(codeIn, properties); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -IdentifierNNPtr -Identifier::createFromDescription(const std::string &descriptionIn) { - auto id = Identifier::nn_make_shared(); - id->d->description_ = descriptionIn; - return id; -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Return a citation for the organization responsible for definition and - * maintenance of the code. - * - * @return the citation for the authority, or empty. - */ -const optional &Identifier::authority() PROJ_CONST_DEFN { - return d->authority_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the alphanumeric value identifying an instance in the - * codespace. - * - * e.g. "4326" (for EPSG:4326 WGS 84 GeographicCRS) - * - * @return the code. - */ -const std::string &Identifier::code() PROJ_CONST_DEFN { return d->code_; } - -// --------------------------------------------------------------------------- - -/** \brief Return the organization responsible for definition and maintenance of - * the code. - * - * e.g "EPSG" - * - * @return the authority codespace, or empty. - */ -const optional &Identifier::codeSpace() PROJ_CONST_DEFN { - return d->codeSpace_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the version identifier for the namespace. - * - * When appropriate, the edition is identified by the effective date, coded - * using ISO 8601 date format. - * - * @return the version or empty. - */ -const optional &Identifier::version() PROJ_CONST_DEFN { - return d->version_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the natural language description of the meaning of the code - * value. - * - * @return the description or empty. - */ -const optional &Identifier::description() PROJ_CONST_DEFN { - return d->description_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the URI of the identifier. - * - * @return the URI or empty. - */ -const optional &Identifier::uri() PROJ_CONST_DEFN { - return d->uri_; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void Identifier::_exportToWKT(WKTFormatter *formatter) const { - const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; - const std::string &l_code = code(); - const std::string &l_codeSpace = *codeSpace(); - if (!l_codeSpace.empty() && !l_code.empty()) { - if (isWKT2) { - formatter->startNode(WKTConstants::ID, false); - formatter->addQuotedString(l_codeSpace); - try { - (void)std::stoi(l_code); - formatter->add(l_code); - } catch (const std::exception &) { - formatter->addQuotedString(l_code); - } - if (version().has_value()) { - auto l_version = *(version()); - try { - (void)c_locale_stod(l_version); - formatter->add(l_version); - } catch (const std::exception &) { - formatter->addQuotedString(l_version); - } - } - if (authority().has_value() && - *(authority()->title()) != l_codeSpace) { - formatter->startNode(WKTConstants::CITATION, false); - formatter->addQuotedString(*(authority()->title())); - formatter->endNode(); - } - if (uri().has_value()) { - formatter->startNode(WKTConstants::URI, false); - formatter->addQuotedString(*(uri())); - formatter->endNode(); - } - formatter->endNode(); - } else { - formatter->startNode(WKTConstants::AUTHORITY, false); - formatter->addQuotedString(l_codeSpace); - formatter->addQuotedString(l_code); - formatter->endNode(); - } - } -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static bool isIgnoredChar(char ch) { - return ch == ' ' || ch == '_' || ch == '-' || ch == '/' || ch == '(' || - ch == ')' || ch == '.' || ch == '&'; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static const struct utf8_to_lower { - const char *utf8; - char ascii; -} map_utf8_to_lower[] = { - {"\xc3\xa1", 'a'}, // a acute - {"\xc3\xa4", 'a'}, // a tremma - - {"\xc4\x9b", 'e'}, // e reverse circumflex - {"\xc3\xa8", 'e'}, // e grave - {"\xc3\xa9", 'e'}, // e acute - {"\xc3\xab", 'e'}, // e tremma - - {"\xc3\xad", 'i'}, // i grave - - {"\xc3\xb4", 'o'}, // o circumflex - {"\xc3\xb6", 'o'}, // o tremma - - {"\xc3\xa7", 'c'}, // c cedilla -}; - -static const struct utf8_to_lower *get_ascii_replacement(const char *c_str) { - for (const auto &pair : map_utf8_to_lower) { - if (*c_str == pair.utf8[0] && - strncmp(c_str, pair.utf8, strlen(pair.utf8)) == 0) { - return &pair; - } - } - return nullptr; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -std::string Identifier::canonicalizeName(const std::string &str) { - std::string res; - const char *c_str = str.c_str(); - for (size_t i = 0; c_str[i] != 0; ++i) { - const auto ch = c_str[i]; - if (ch == ' ' && c_str[i + 1] == '+' && c_str[i + 2] == ' ') { - i += 2; - continue; - } - if (ch == '1' && !res.empty() && - !(res.back() >= '0' && res.back() <= '9') && c_str[i + 1] == '9' && - c_str[i + 2] >= '0' && c_str[i + 2] <= '9') { - ++i; - continue; - } - if (static_cast(ch) > 127) { - const auto *replacement = get_ascii_replacement(c_str + i); - if (replacement) { - res.push_back(replacement->ascii); - i += strlen(replacement->utf8) - 1; - continue; - } - } - if (!isIgnoredChar(ch)) { - res.push_back(static_cast(::tolower(ch))); - } - } - return res; -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Returns whether two names are considered equivalent. - * - * Two names are equivalent by removing any space, underscore, dash, slash, - * { or } character from them, and comparing in a case insensitive way. - */ -bool Identifier::isEquivalentName(const char *a, const char *b) noexcept { - size_t i = 0; - size_t j = 0; - char lastValidA = 0; - char lastValidB = 0; - while (a[i] != 0 && b[j] != 0) { - char aCh = a[i]; - char bCh = b[j]; - if (aCh == ' ' && a[i + 1] == '+' && a[i + 2] == ' ') { - i += 3; - continue; - } - if (bCh == ' ' && b[j + 1] == '+' && b[j + 2] == ' ') { - j += 3; - continue; - } - if (isIgnoredChar(aCh)) { - ++i; - continue; - } - if (isIgnoredChar(bCh)) { - ++j; - continue; - } - if (aCh == '1' && !(lastValidA >= '0' && lastValidA <= '9') && - a[i + 1] == '9' && a[i + 2] >= '0' && a[i + 2] <= '9') { - i += 2; - lastValidA = '9'; - continue; - } - if (bCh == '1' && !(lastValidB >= '0' && lastValidB <= '9') && - b[j + 1] == '9' && b[j + 2] >= '0' && b[j + 2] <= '9') { - j += 2; - lastValidB = '9'; - continue; - } - if (static_cast(aCh) > 127) { - const auto *replacement = get_ascii_replacement(a + i); - if (replacement) { - aCh = replacement->ascii; - i += strlen(replacement->utf8) - 1; - } - } - if (static_cast(bCh) > 127) { - const auto *replacement = get_ascii_replacement(b + j); - if (replacement) { - bCh = replacement->ascii; - j += strlen(replacement->utf8) - 1; - } - } - if (::tolower(aCh) != ::tolower(bCh)) { - return false; - } - lastValidA = aCh; - lastValidB = bCh; - ++i; - ++j; - } - while (a[i] != 0 && isIgnoredChar(a[i])) { - ++i; - } - while (b[j] != 0 && isIgnoredChar(b[j])) { - ++j; - } - return a[i] == b[j]; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct PositionalAccuracy::Private { - std::string value_{}; -}; -//! @endcond - -// --------------------------------------------------------------------------- - -PositionalAccuracy::PositionalAccuracy(const std::string &valueIn) - : d(internal::make_unique()) { - d->value_ = valueIn; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -PositionalAccuracy::~PositionalAccuracy() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Return the value of the positional accuracy. - */ -const std::string &PositionalAccuracy::value() PROJ_CONST_DEFN { - return d->value_; -} - -// --------------------------------------------------------------------------- - -/** \brief Instanciate a PositionalAccuracy. - * - * @param valueIn positional accuracy value. - * @return a new PositionalAccuracy. - */ -PositionalAccuracyNNPtr PositionalAccuracy::create(const std::string &valueIn) { - return PositionalAccuracy::nn_make_shared(valueIn); -} - -} // namespace metadata -NS_PROJ_END diff --git a/src/multistresstest.cpp b/src/multistresstest.cpp deleted file mode 100644 index 234783b3..00000000 --- a/src/multistresstest.cpp +++ /dev/null @@ -1,488 +0,0 @@ -/****************************************************************************** - * - * Project: PROJ.4 - * Purpose: Mainline program to stress test multithreaded PROJ.4 processing. - * Author: Frank Warmerdam, warmerdam@pobox.com - * - ****************************************************************************** - * Copyright (c) 2010, Frank Warmerdam - * - * 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 -#include -#include - -#ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H -#define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H -#endif - -#include "proj_api.h" - -#ifdef _WIN32 - #include -#else - #include - #include -#endif - -#define num_threads 10 -static int num_iterations = 1000000; -static int reinit_every_iteration=0; -static int add_no_defs = 0; - -typedef struct { - const char *src_def; - const char *dst_def; - - double src_x, src_y, src_z; - double dst_x, dst_y, dst_z; - - int dst_error; - int skip; -} TestItem; - -static TestItem test_list[] = { - { - "+proj=utm +zone=11 +datum=WGS84", - "+proj=latlong +datum=WGS84", - 150000.0, 3000000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=utm +zone=11 +datum=NAD83", - "+proj=latlong +datum=NAD27", - 150000.0, 3000000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=utm +zone=11 +datum=NAD83", - "+proj=latlong +nadgrids=@null +ellps=WGS84", - 150000.0, 3000000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=utm +zone=11 +datum=WGS84", - "+proj=merc +datum=potsdam", - 150000.0, 3000000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=latlong +nadgrids=nzgd2kgrid0005.gsb", - "+proj=latlong +datum=WGS84", - 150000.0, 3000000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=latlong +nadgrids=nzgd2kgrid0005.gsb", - "+proj=latlong +datum=WGS84", - 170 * DEG_TO_RAD, -40 * DEG_TO_RAD, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=latlong +ellps=GRS80 +towgs84=2,3,5", - "+proj=latlong +ellps=intl +towgs84=10,12,15", - 170 * DEG_TO_RAD, -40 * DEG_TO_RAD, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=eqc +lat_0=11 +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", - "+proj=stere +lat_0=11 +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", - 150000.0, 250000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=cea +lat_ts=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", - "+proj=merc +lon_0=12 +k=0.999 +x_0=100000 +y_0=200000 +datum=WGS84 ", - 150000.0, 250000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=bonne +lat_1=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", - "+proj=cass +lat_0=11 +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", - 150000.0, 250000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=nzmg +lat_0=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", - "+proj=gnom +lat_0=11 +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", - 150000.0, 250000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=ortho +lat_0=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", - "+proj=laea +lat_0=11 +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", - 150000.0, 250000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=aeqd +lat_0=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", - "+proj=eqdc +lat_1=20 +lat_2=5 +lat_0=11 +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", - 150000.0, 250000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+proj=mill +lat_0=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", - "+proj=moll +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", - 150000.0, 250000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - "+init=epsg:3309", - "+init=epsg:4326", - 150000.0, 30000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - }, - { - /* Bad projection (invalid ellipsoid parameter +R_A=0) */ - "+proj=utm +zone=11 +datum=WGS84", - "+proj=merc +datum=potsdam +R_A=0", - 150000.0, 3000000.0, 0.0, - 0.0, 0.0, 0.0, - 0, 0 - } -}; - -static volatile int active_thread_count = 0; - -static projPJ custom_pj_init_plus_ctx(projCtx ctx, const char* def) -{ - if( add_no_defs ) - { - char szBuffer[256]; - strcpy(szBuffer, def); - strcat(szBuffer, " +no_defs"); - return pj_init_plus_ctx(ctx, szBuffer); - } - else - return pj_init_plus_ctx(ctx, def); -} - -/************************************************************************/ -/* TestThread() */ -/************************************************************************/ - -static void TestThread() - -{ - int i, test_count = sizeof(test_list) / sizeof(TestItem); - int repeat_count = num_iterations; - int i_iter; - -/* -------------------------------------------------------------------- */ -/* Initialize coordinate system definitions. */ -/* -------------------------------------------------------------------- */ - projPJ *src_pj_list, *dst_pj_list; - projCtx ctx = pj_ctx_alloc(); - - src_pj_list = (projPJ *) calloc(test_count,sizeof(projPJ)); - dst_pj_list = (projPJ *) calloc(test_count,sizeof(projPJ)); - - if(!reinit_every_iteration) - { - for( i = 0; i < test_count; i++ ) - { - TestItem *test = test_list + i; - - src_pj_list[i] = custom_pj_init_plus_ctx( ctx, test->src_def ); - dst_pj_list[i] = custom_pj_init_plus_ctx( ctx, test->dst_def ); - } - } - -/* -------------------------------------------------------------------- */ -/* Perform tests - over and over. */ -/* -------------------------------------------------------------------- */ - - for( i_iter = 0; i_iter < repeat_count; i_iter++ ) - { - for( i = 0; i < test_count; i++ ) - { - TestItem *test = test_list + i; - double x, y, z; - int error; - - x = test->src_x; - y = test->src_y; - z = test->src_z; - - if( reinit_every_iteration ) - { - src_pj_list[i] = custom_pj_init_plus_ctx( ctx, test->src_def ); - dst_pj_list[i] = custom_pj_init_plus_ctx( ctx, test->dst_def ); - - { - int skipTest = (src_pj_list[i] == nullptr || dst_pj_list[i] == nullptr); - - if ( skipTest != test->skip ) - fprintf( stderr, "Threaded projection initialization does not match unthreaded initialization\n" ); - - if (skipTest) - { - pj_free( src_pj_list[i] ); - pj_free( dst_pj_list[i] ); - continue; - } - } - } - - if ( test->skip ) - continue; - - error = pj_transform( src_pj_list[i], dst_pj_list[i], 1, 0, - &x, &y, &z ); - - - if( error != test->dst_error ) - { - fprintf( stderr, "Got error %d, expected %d\n", - error, test->dst_error ); - } - - if( x != test->dst_x || y != test->dst_y || z != test->dst_z ) - { - fprintf( stderr, - "Got %.15g,%.15g,%.15g\n" - "Expected %.15g,%.15g,%.15g\n" - "Diff %.15g,%.15g,%.15g\n", - x, y, z, - test->dst_x, test->dst_y, test->dst_z, - x-test->dst_x, y-test->dst_y, z-test->dst_z); - } - - if( reinit_every_iteration ) - { - pj_free( src_pj_list[i] ); - pj_free( dst_pj_list[i] ); - } - } - } - -/* -------------------------------------------------------------------- */ -/* Cleanup */ -/* -------------------------------------------------------------------- */ - if( !reinit_every_iteration ) - { - for( i = 0; i < test_count; i++ ) - { - pj_free( src_pj_list[i] ); - pj_free( dst_pj_list[i] ); - } - } - - free( src_pj_list ); - free( dst_pj_list ); - - pj_ctx_free( ctx ); - - printf( "%d iterations of the %d tests complete in thread X\n", - repeat_count, test_count ); - - active_thread_count--; -} - -#ifdef _WIN32 -/************************************************************************/ -/* WinTestThread() */ -/************************************************************************/ - -static DWORD WINAPI WinTestThread( LPVOID lpParameter ) - -{ - TestThread(); - - return 0; -} - -#else -/************************************************************************/ -/* PosixTestThread() */ -/************************************************************************/ - -static void *PosixTestThread( void *pData ) - -{ - (void)pData; - TestThread(); - return nullptr; -} -#endif - -/************************************************************************/ -/* main() */ -/************************************************************************/ -#ifdef _WIN32 -static DWORD WINAPI do_main( LPVOID unused ) -#else -static int do_main(void) -#endif -{ -/* -------------------------------------------------------------------- */ -/* Our first pass is to establish the correct answers for all */ -/* the tests. */ -/* -------------------------------------------------------------------- */ - int i, test_count = sizeof(test_list) / sizeof(TestItem); - - for( i = 0; i < test_count; i++ ) - { - TestItem *test = test_list + i; - - projPJ src_pj, dst_pj; - - src_pj = custom_pj_init_plus_ctx( pj_get_default_ctx(), test->src_def ); - dst_pj = custom_pj_init_plus_ctx( pj_get_default_ctx(), test->dst_def ); - - if( src_pj == nullptr ) - { - printf( "Unable to translate:\n%s\n", test->src_def ); - test->skip = 1; - pj_free (dst_pj); - continue; - } - - if( dst_pj == nullptr ) - { - printf( "Unable to translate:\n%s\n", test->dst_def ); - test->skip = 1; - pj_free (src_pj); - continue; - } - - test->dst_x = test->src_x; - test->dst_y = test->src_y; - test->dst_z = test->src_z; - - test->dst_error = pj_transform( src_pj, dst_pj, 1, 0, - &(test->dst_x), - &(test->dst_y), - &(test->dst_z) ); - - pj_free( src_pj ); - pj_free( dst_pj ); - - test->skip = 0; - -#ifdef notdef - printf( "Test %d - output %.14g,%.14g,%g\n", i, test->dst_x, test->dst_y, test->dst_z ); -#endif - } - - printf( "%d tests initialized.\n", test_count ); - -/* -------------------------------------------------------------------- */ -/* Now launch a bunch of threads to repeat the tests. */ -/* -------------------------------------------------------------------- */ -#ifdef _WIN32 - - { //Scoped to workaround lack of c99 support in VS - HANDLE ahThread[num_threads]; - - for( i = 0; i < num_threads; i++ ) - { - active_thread_count++; - - ahThread[i] = CreateThread(NULL, 0, WinTestThread, NULL, 0, NULL); - - if (ahThread[i] == 0) - { - printf( "Thread creation failed."); - return 1; - } - } - - printf( "%d test threads launched.\n", num_threads ); - - WaitForMultipleObjects(num_threads, ahThread, TRUE, INFINITE); - } - -#else - { - pthread_t ahThread[num_threads]; - pthread_attr_t hThreadAttr; - - pthread_attr_init( &hThreadAttr ); - pthread_attr_setdetachstate( &hThreadAttr, PTHREAD_CREATE_DETACHED ); - - for( i = 0; i < num_threads; i++ ) - { - active_thread_count++; - - pthread_create( &(ahThread[i]), &hThreadAttr, - PosixTestThread, nullptr ); - } - - printf( "%d test threads launched.\n", num_threads ); - - while( active_thread_count > 0 ) - sleep( 1 ); - } -#endif - - printf( "all tests complete.\n" ); - - return 0; -} - - -int main( int argc, char **argv ) -{ - int i; - for(i=0;i -#include - -#define PJ_LIB__ -#include "proj_internal.h" -#include "projects.h" -#define U_SEC_TO_RAD 4.848136811095359935899141023e-12 - -/************************************************************************/ -/* swap_words() */ -/* */ -/* Convert the byte order of the given word(s) in place. */ -/************************************************************************/ - -static const int byte_order_test = 1; -#define IS_LSB (((const unsigned char *) (&byte_order_test))[0] == 1) - -static void swap_words( void *data_in, int word_size, int word_count ) - -{ - int word; - unsigned char *data = (unsigned char *) data_in; - - for( word = 0; word < word_count; word++ ) - { - int i; - - for( i = 0; i < word_size/2; i++ ) - { - unsigned char t; - - t = data[i]; - data[i] = data[word_size-i-1]; - data[word_size-i-1] = t; - } - - data += word_size; - } -} - -/************************************************************************/ -/* Usage() */ -/************************************************************************/ - -static void Usage() -{ - fprintf(stderr, - "usage: nad2bin [-f ctable/ctable2/ntv2] binary_output < ascii_source\n" ); - exit(1); -} - -/************************************************************************/ -/* main() */ -/************************************************************************/ -int main(int argc, char **argv) { - struct CTABLE ct; - FLP *p, t; - size_t tsize; - int i, j, ichk; - long lam, laml, phi, phil; - FILE *fp; - - const char *output_file = nullptr; - - const char *format = "ctable2"; - const char *GS_TYPE = "SECONDS"; - const char *VERSION = ""; - const char *SYSTEM_F = "NAD27"; - const char *SYSTEM_T = "NAD83"; - const char *SUB_NAME = ""; - const char *CREATED = ""; - const char *UPDATED = ""; - -/* ==================================================================== */ -/* Process arguments. */ -/* ==================================================================== */ - for( i = 1; i < argc; i++ ) - { - if( i < argc-1 && strcmp(argv[i],"-f") == 0 ) - { - format = argv[++i]; - } - else if( output_file == nullptr ) - { - output_file = argv[i]; - } - else - Usage(); - } - - if( output_file == nullptr ) - Usage(); - - fprintf( stdout, "Output Binary File Format: %s\n", format ); - -/* ==================================================================== */ -/* Read the ASCII Table */ -/* ==================================================================== */ - - memset(ct.id,0,MAX_TAB_ID); - if ( nullptr == fgets(ct.id, MAX_TAB_ID, stdin) ) { - perror("fgets"); - exit(1); - } - /* cppcheck-suppress invalidscanf */ - if ( EOF == scanf("%d %d %*d %lf %lf %lf %lf", &ct.lim.lam, &ct.lim.phi, - &ct.ll.lam, &ct.del.lam, &ct.ll.phi, &ct.del.phi) ) { - perror("scanf"); - exit(1); - } - if (!(ct.cvs = (FLP *)malloc(tsize = ct.lim.lam * ct.lim.phi * - sizeof(FLP)))) { - perror("mem. alloc"); - exit(1); - } - ct.ll.lam *= DEG_TO_RAD; - ct.ll.phi *= DEG_TO_RAD; - ct.del.lam *= DEG_TO_RAD; - ct.del.phi *= DEG_TO_RAD; - /* load table */ - p = ct.cvs; - for (i = 0; i < ct.lim.phi; ++i) { - /* cppcheck-suppress invalidscanf */ - if ( EOF == scanf("%d:%ld %ld", &ichk, &laml, &phil) ) { - perror("scanf on row"); - exit(1); - } - if (ichk != i) { - fprintf(stderr,"format check on row\n"); - exit(1); - } - t.lam = (float) (laml * U_SEC_TO_RAD); - t.phi = (float) (phil * U_SEC_TO_RAD); - *p++ = t; - for (j = 1; j < ct.lim.lam; ++j) { - /* cppcheck-suppress invalidscanf */ - if ( EOF == scanf("%ld %ld", &lam, &phi) ) { - perror("scanf on column"); - exit(1); - } - t.lam = (float) ((laml += lam) * U_SEC_TO_RAD); - t.phi = (float) ((phil += phi) * U_SEC_TO_RAD); - *p++ = t; - } - } - if (feof(stdin)) { - fprintf(stderr, "premature EOF\n"); - exit(1); - } - -/* ==================================================================== */ -/* Write out the old ctable format - this is machine and byte */ -/* order specific. */ -/* ==================================================================== */ - if( strcmp(format,"ctable") == 0 ) - { - if (!(fp = fopen(output_file, "wb"))) { - perror(output_file); - exit(2); - } - if (fwrite(&ct, sizeof(ct), 1, fp) != 1 || - fwrite(ct.cvs, tsize, 1, fp) != 1) { - fprintf(stderr, "output failure\n"); - exit(2); - } - fclose( fp ); - exit(0); /* normal completion */ - } - -/* ==================================================================== */ -/* Write out the old ctable format - this is machine and byte */ -/* order specific. */ -/* ==================================================================== */ - if( strcmp(format,"ctable2") == 0 ) - { - char header[160]; - - if (!(fp = fopen(output_file, "wb"))) { - perror(output_file); - exit(2); - } - - /* cppcheck-suppress sizeofCalculation */ - STATIC_ASSERT( MAX_TAB_ID == 80 ); - /* cppcheck-suppress sizeofCalculation */ - STATIC_ASSERT( sizeof(pj_int32) == 4 ); /* for ct.lim.lam/phi */ - - memset( header, 0, sizeof(header) ); - - memcpy( header + 0, "CTABLE V2.0 ", 16 ); - memcpy( header + 16, ct.id, 80 ); - memcpy( header + 96, &ct.ll.lam, 8 ); - memcpy( header + 104, &ct.ll.phi, 8 ); - memcpy( header + 112, &ct.del.lam, 8 ); - memcpy( header + 120, &ct.del.phi, 8 ); - memcpy( header + 128, &ct.lim.lam, 4 ); - memcpy( header + 132, &ct.lim.phi, 4 ); - - /* force into LSB format */ - if( !IS_LSB ) - { - swap_words( header + 96, 8, 4 ); - swap_words( header + 128, 4, 2 ); - swap_words( ct.cvs, 4, ct.lim.lam * 2 * ct.lim.phi ); - } - - if( fwrite( header, sizeof(header), 1, fp ) != 1 ) { - perror( "fwrite" ); - exit( 2 ); - } - - if (fwrite(ct.cvs, tsize, 1, fp) != 1) { - perror( "fwrite" ); - exit(2); - } - - fclose( fp ); - exit(0); /* normal completion */ - } - -/* ==================================================================== */ -/* Write out the NTv2 format grid shift file. */ -/* ==================================================================== */ - if( strcmp(format,"ntv2") == 0 ) - { - if (!(fp = fopen(output_file, "wb"))) - { - perror(output_file); - exit(2); - } - -/* -------------------------------------------------------------------- */ -/* Write the file header. */ -/* -------------------------------------------------------------------- */ - { - char achHeader[11*16]; - - memset( achHeader, 0, sizeof(achHeader) ); - - memcpy( achHeader + 0*16, "NUM_OREC", 8 ); - achHeader[ 0*16 + 8] = 0xb; - - memcpy( achHeader + 1*16, "NUM_SREC", 8 ); - achHeader[ 1*16 + 8] = 0xb; - - memcpy( achHeader + 2*16, "NUM_FILE", 8 ); - achHeader[ 2*16 + 8] = 0x1; - - memcpy( achHeader + 3*16, "GS_TYPE ", 16 ); - memcpy( achHeader + 3*16+8, GS_TYPE, MIN(16,strlen(GS_TYPE)) ); - - memcpy( achHeader + 4*16, "VERSION ", 16 ); - memcpy( achHeader + 4*16+8, VERSION, MIN(16,strlen(VERSION)) ); - - memcpy( achHeader + 5*16, "SYSTEM_F ", 16 ); - memcpy( achHeader + 5*16+8, SYSTEM_F, MIN(16,strlen(SYSTEM_F)) ); - - memcpy( achHeader + 6*16, "SYSTEM_T ", 16 ); - memcpy( achHeader + 6*16+8, SYSTEM_T, MIN(16,strlen(SYSTEM_T)) ); - - memcpy( achHeader + 7*16, "MAJOR_F ", 8); - memcpy( achHeader + 8*16, "MINOR_F ", 8 ); - memcpy( achHeader + 9*16, "MAJOR_T ", 8 ); - memcpy( achHeader + 10*16, "MINOR_T ", 8 ); - - fwrite( achHeader, 1, sizeof(achHeader), fp ); - } - -/* -------------------------------------------------------------------- */ -/* Write the grid header. */ -/* -------------------------------------------------------------------- */ - { - unsigned char achHeader[11*16]; - double dfValue; - pj_int32 nGSCount = ct.lim.lam * ct.lim.phi; - LP ur; - - ur.lam = ct.ll.lam + (ct.lim.lam-1) * ct.del.lam; - ur.phi = ct.ll.phi + (ct.lim.phi-1) * ct.del.phi; - - /* cppcheck-suppress sizeofCalculation */ - STATIC_ASSERT( sizeof(nGSCount) == 4 ); - - memset( achHeader, 0, sizeof(achHeader) ); - - memcpy( achHeader + 0*16, "SUB_NAME ", 16 ); - memcpy( achHeader + 0*16+8, SUB_NAME, MIN(16,strlen(SUB_NAME)) ); - - memcpy( achHeader + 1*16, "PARENT ", 16 ); - memcpy( achHeader + 1*16+8, "NONE", MIN(16,strlen("NONE")) ); - - memcpy( achHeader + 2*16, "CREATED ", 16 ); - memcpy( achHeader + 2*16+8, CREATED, MIN(16,strlen(CREATED)) ); - - memcpy( achHeader + 3*16, "UPDATED ", 16 ); - memcpy( achHeader + 3*16+8, UPDATED, MIN(16,strlen(UPDATED)) ); - - memcpy( achHeader + 4*16, "S_LAT ", 8 ); - dfValue = ct.ll.phi * 3600.0 / DEG_TO_RAD; - memcpy( achHeader + 4*16 + 8, &dfValue, 8 ); - - memcpy( achHeader + 5*16, "N_LAT ", 8 ); - dfValue = ur.phi * 3600.0 / DEG_TO_RAD; - memcpy( achHeader + 5*16 + 8, &dfValue, 8 ); - - memcpy( achHeader + 6*16, "E_LONG ", 8 ); - dfValue = -1 * ur.lam * 3600.0 / DEG_TO_RAD; - memcpy( achHeader + 6*16 + 8, &dfValue, 8 ); - - memcpy( achHeader + 7*16, "W_LONG ", 8 ); - dfValue = -1 * ct.ll.lam * 3600.0 / DEG_TO_RAD; - memcpy( achHeader + 7*16 + 8, &dfValue, 8 ); - - memcpy( achHeader + 8*16, "LAT_INC ", 8 ); - dfValue = ct.del.phi * 3600.0 / DEG_TO_RAD; - memcpy( achHeader + 8*16 + 8, &dfValue, 8 ); - - memcpy( achHeader + 9*16, "LONG_INC", 8 ); - dfValue = ct.del.lam * 3600.0 / DEG_TO_RAD; - memcpy( achHeader + 9*16 + 8, &dfValue, 8 ); - - memcpy( achHeader + 10*16, "GS_COUNT", 8 ); - memcpy( achHeader + 10*16+8, &nGSCount, 4 ); - - if( !IS_LSB ) - { - swap_words( achHeader + 4*16 + 8, 8, 1 ); - swap_words( achHeader + 5*16 + 8, 8, 1 ); - swap_words( achHeader + 6*16 + 8, 8, 1 ); - swap_words( achHeader + 7*16 + 8, 8, 1 ); - swap_words( achHeader + 8*16 + 8, 8, 1 ); - swap_words( achHeader + 9*16 + 8, 8, 1 ); - swap_words( achHeader + 10*16 + 8, 4, 1 ); - } - - fwrite( achHeader, 1, sizeof(achHeader), fp ); - } - -/* -------------------------------------------------------------------- */ -/* Write the actual grid cells. */ -/* -------------------------------------------------------------------- */ - { - float *row_buf; - int row; - - row_buf = (float *) pj_malloc(ct.lim.lam * sizeof(float) * 4); - memset( row_buf, 0, sizeof(float)*4 ); - - for( row = 0; row < ct.lim.phi; row++ ) - { - for( i = 0; i < ct.lim.lam; i++ ) - { - FLP *cvs = ct.cvs + (row) * ct.lim.lam - + (ct.lim.lam - i - 1); - - /* convert radians to seconds */ - row_buf[i*4+0] = (float) (cvs->phi * (3600.0 / (M_PI/180.0))); - row_buf[i*4+1] = (float) (cvs->lam * (3600.0 / (M_PI/180.0))); - - /* We leave the accuracy values as zero */ - } - - if( !IS_LSB ) - swap_words( row_buf, 4, ct.lim.lam * 4 ); - - if( fwrite( row_buf, sizeof(float), ct.lim.lam*4, fp ) - != (size_t)( 4 * ct.lim.lam ) ) - { - perror( "write()" ); - exit( 2 ); - } - } - } - - fclose( fp ); - exit(0); /* normal completion */ - } - - fprintf( stderr, "Unsupported format, nothing written.\n" ); - exit( 3 ); -} diff --git a/src/optargpm.h b/src/optargpm.h deleted file mode 100644 index 035c6f92..00000000 --- a/src/optargpm.h +++ /dev/null @@ -1,635 +0,0 @@ -/*********************************************************************** - - OPTARGPM - a header-only library for decoding - PROJ.4 style command line options - - Thomas Knudsen, 2017-09-10 - -************************************************************************ - -For PROJ.4 command line programs, we have a somewhat complex option -decoding situation, since we have to navigate in a cocktail of classic -single letter style options, prefixed by "-", GNU style long options -prefixed by "--", transformation specification elements prefixed by "+", -and input file names prefixed by "" (i.e. nothing). - -Hence, classic getopt.h style decoding does not cut the mustard, so -this is an attempt to catch up and chop the ketchup. - -Since optargpm (for "optarg plus minus") does not belong, in any -obvious way, in any systems development library, it is provided as -a "header only" library. - -While this is conventional in C++, it is frowned at in plain C. -But frown away - "header only" has its places, and this is one of -them. - -By convention, we expect a command line to consist of the following -elements: - - - [short ("-")/long ("--") options} - [operator ("+") specs] - [operands/input files] - -or less verbose: - - [options] [operator specs] [operands] - -or less abstract: - - proj -I --output=foo +proj=utm +zone=32 +ellps=GRS80 bar baz... - -Where - -Operator is proj -Options are -I --output=foo -Operator specs are +proj=utm +zone=32 +ellps=GRS80 -Operands are bar baz - - -While neither claiming to save the world, nor to hint at the "shape of -jazz to come", at least optargpm has shown useful in constructing cs2cs -style transformation filters. - -Supporting a wide range of option syntax, the getoptpm API is somewhat -quirky, but also compact, consisting of one data type, 3(+2) functions, -and one enumeration: - -OPTARGS - Housekeeping data type. An instance of OPTARGS is conventionally - called o or opt -opt_parse (opt, argc, argv ...): - The work horse: Define supported options; Split (argc, argv) - into groups (options, op specs, operands); Parse option - arguments. -opt_given (o, option): - The number of times